From d85985d141ce2687726e3cbe744d68a542a3d506 Mon Sep 17 00:00:00 2001 From: Jack May Date: Wed, 21 Nov 2018 14:26:15 -0800 Subject: [PATCH 1/8] nudge --- ci/docker-llvm/Dockerfile | 77 +++++++++++++++++++++++++++++++++++++++ ci/docker-llvm/build.sh | 7 ++++ 2 files changed, 84 insertions(+) create mode 100644 ci/docker-llvm/Dockerfile create mode 100755 ci/docker-llvm/build.sh diff --git a/ci/docker-llvm/Dockerfile b/ci/docker-llvm/Dockerfile new file mode 100644 index 00000000000000..1bfbc6a354d0eb --- /dev/null +++ b/ci/docker-llvm/Dockerfile @@ -0,0 +1,77 @@ +FROM launcher.gcr.io/google/debian8:latest as builder +LABEL maintainer "Solana Maintainers" + +# Install build dependencies of llvm. +# First, Update the apt's source list and include the sources of the packages. +RUN grep deb /etc/apt/sources.list | \ + sed 's/^deb/deb-src /g' >> /etc/apt/sources.list + +# Install compiler, python and subversion. +RUN apt-get update && \ + apt-get install -y \ + --no-install-recommends \ + ca-certificates gnupg \ + build-essential \ + python \ + wget \ + unzip \ + git \ + ssh && \ + rm -rf /var/lib/apt/lists/* + +# Install a newer ninja release. It seems the older version in the debian repos +# randomly crashes when compiling llvm. +RUN wget "https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-linux.zip" && \ + echo "d2fea9ff33b3ef353161ed906f260d565ca55b8ca0568fa07b1d2cab90a84a07 ninja-linux.zip" \ + | sha256sum -c && \ + unzip ninja-linux.zip -d /usr/local/bin && \ + rm ninja-linux.zip + +# Import public key required for verifying signature of cmake download. +RUN gpg --keyserver hkp://pgp.mit.edu --recv 0x2D2CEF1034921684 + +# Download, verify and install cmake version that can compile clang into /usr/local. +# (Version in debian8 repos is too old) +RUN mkdir /tmp/cmake-install && cd /tmp/cmake-install && \ + wget "https://cmake.org/files/v3.7/cmake-3.7.2-SHA-256.txt.asc" && \ + wget "https://cmake.org/files/v3.7/cmake-3.7.2-SHA-256.txt" && \ + # gpg --verify cmake-3.7.2-SHA-256.txt.asc cmake-3.7.2-SHA-256.txt && \ + wget "https://cmake.org/files/v3.7/cmake-3.7.2-Linux-x86_64.tar.gz" && \ + ( grep "cmake-3.7.2-Linux-x86_64.tar.gz" cmake-3.7.2-SHA-256.txt | \ + sha256sum -c - ) && \ + tar xzf cmake-3.7.2-Linux-x86_64.tar.gz -C /usr/local --strip-components=1 && \ + cd / && rm -rf /tmp/cmake-install + +# ADD checksums /tmp/checksums +# ADD scripts /tmp/scripts + +# Checkout the source code +RUN git clone https://github.com/solana-labs/llvm.git && \ + git clone https://github.com/solana-labs/clang.git llvm/tools/clang && \ + git clone http://llvm.org/git/clang-tools-extra.git llvm/tools/clang/tools/extra && \ + git clone https://github.com/solana-labs/lld.git llvm/tools/lld && \ + git clone http://llvm.org/git/compiler-rt.git llvm/projects/compiler-rt + +RUN mkdir /llvm/build && \ + cd /llvm/build && \ + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_INSTALL_PREFIX=$HOME/local -G "Ninja" .. && \ + ninja -j6 && \ + ninja install + +# RUN cd /llvm/build && \ +# cmake -G "Unix Makefiles" .. + +# RUN cd /llvm/build/tools/clang/ && \ +# make -j 6 install + +# RUN cd /llvm/build/tools/lld/ && \ +# make -j 6 install + +# RUN cd /llmv/build/tools/llvm-objdump/ && \ +# make -j 6 install + +# Produce stage 2 docker with just the peices needed +FROM launcher.gcr.io/google/debian8:latest +LABEL maintainer "Solana Maintainers" + +COPY --from=builder root/local/bin /usr/local/bin diff --git a/ci/docker-llvm/build.sh b/ci/docker-llvm/build.sh new file mode 100755 index 00000000000000..47a35f0a2775b3 --- /dev/null +++ b/ci/docker-llvm/build.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -ex + +cd "$(dirname "$0")" + +docker build -t solanalabs/llvm . +# docker push solanalabs/llvm From 4469e61bb2b7e838bcbdef74a1703155586bc808 Mon Sep 17 00:00:00 2001 From: Jack May Date: Mon, 26 Nov 2018 10:11:09 -0800 Subject: [PATCH 2/8] Build with dockererized llvm --- ci/docker-llvm/Dockerfile | 22 +++++-------------- programs/bpf/c/sdk/bpf.mk | 4 ++++ programs/bpf/c/sdk/llvm-wrappers/bin/clang | 6 +++++ programs/bpf/c/sdk/llvm-wrappers/bin/clang++ | 6 +++++ programs/bpf/c/sdk/llvm-wrappers/bin/llc | 6 +++++ .../bpf/c/sdk/llvm-wrappers/bin/llvm-objdump | 6 +++++ .../bpf/c/sdk/llvm-wrappers/utils/copy.sh | 7 ++++++ .../bpf/c/sdk/llvm-wrappers/utils/llvm.sh | 6 +++++ 8 files changed, 47 insertions(+), 16 deletions(-) create mode 100755 programs/bpf/c/sdk/llvm-wrappers/bin/clang create mode 100755 programs/bpf/c/sdk/llvm-wrappers/bin/clang++ create mode 100755 programs/bpf/c/sdk/llvm-wrappers/bin/llc create mode 100755 programs/bpf/c/sdk/llvm-wrappers/bin/llvm-objdump create mode 100755 programs/bpf/c/sdk/llvm-wrappers/utils/copy.sh create mode 100755 programs/bpf/c/sdk/llvm-wrappers/utils/llvm.sh diff --git a/ci/docker-llvm/Dockerfile b/ci/docker-llvm/Dockerfile index 1bfbc6a354d0eb..4effa39cc223b0 100644 --- a/ci/docker-llvm/Dockerfile +++ b/ci/docker-llvm/Dockerfile @@ -1,3 +1,6 @@ +# This docker file is based on the llvm docker file example located here: +# https://github.com/llvm-mirror/llvm/blob/master/utils/docker/debian8/Dockerfile + FROM launcher.gcr.io/google/debian8:latest as builder LABEL maintainer "Solana Maintainers" @@ -28,7 +31,7 @@ RUN wget "https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-li rm ninja-linux.zip # Import public key required for verifying signature of cmake download. -RUN gpg --keyserver hkp://pgp.mit.edu --recv 0x2D2CEF1034921684 +RUN gpg --no-tty --keyserver hkp://pgp.mit.edu --recv 0x2D2CEF1034921684 # Download, verify and install cmake version that can compile clang into /usr/local. # (Version in debian8 repos is too old) @@ -48,9 +51,9 @@ RUN mkdir /tmp/cmake-install && cd /tmp/cmake-install && \ # Checkout the source code RUN git clone https://github.com/solana-labs/llvm.git && \ git clone https://github.com/solana-labs/clang.git llvm/tools/clang && \ - git clone http://llvm.org/git/clang-tools-extra.git llvm/tools/clang/tools/extra && \ + git clone https://github.com/solana-labs/clang-tools-extra.git llvm/tools/clang/tools/extra && \ git clone https://github.com/solana-labs/lld.git llvm/tools/lld && \ - git clone http://llvm.org/git/compiler-rt.git llvm/projects/compiler-rt + git clone https://github.com/solana-labs/compiler-rt.git llvm/projects/compiler-rt RUN mkdir /llvm/build && \ cd /llvm/build && \ @@ -58,20 +61,7 @@ RUN mkdir /llvm/build && \ ninja -j6 && \ ninja install -# RUN cd /llvm/build && \ -# cmake -G "Unix Makefiles" .. - -# RUN cd /llvm/build/tools/clang/ && \ -# make -j 6 install - -# RUN cd /llvm/build/tools/lld/ && \ -# make -j 6 install - -# RUN cd /llmv/build/tools/llvm-objdump/ && \ -# make -j 6 install - # Produce stage 2 docker with just the peices needed FROM launcher.gcr.io/google/debian8:latest LABEL maintainer "Solana Maintainers" - COPY --from=builder root/local/bin /usr/local/bin diff --git a/programs/bpf/c/sdk/bpf.mk b/programs/bpf/c/sdk/bpf.mk index 06c565c29b1d97..c457503655759a 100644 --- a/programs/bpf/c/sdk/bpf.mk +++ b/programs/bpf/c/sdk/bpf.mk @@ -12,10 +12,14 @@ SRC_DIR ?= ./src TEST_DIR ?= ./test OUT_DIR ?= ./out +ifeq ($(USE_DOCKER),1) +LLVM_DIR = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/llvm-wrappers +else OS=$(shell uname) ifeq ($(OS),Darwin) LLVM_DIR ?= $(shell brew --prefix llvm) endif +endif ifdef LLVM_DIR CC := $(LLVM_DIR)/bin/clang diff --git a/programs/bpf/c/sdk/llvm-wrappers/bin/clang b/programs/bpf/c/sdk/llvm-wrappers/bin/clang new file mode 100755 index 00000000000000..c13cfbd58dbc07 --- /dev/null +++ b/programs/bpf/c/sdk/llvm-wrappers/bin/clang @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -ex + + +docker run --workdir /solana_sdk --volume /Users/jack/sandbox/git/solana/programs/bpf/c:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" diff --git a/programs/bpf/c/sdk/llvm-wrappers/bin/clang++ b/programs/bpf/c/sdk/llvm-wrappers/bin/clang++ new file mode 100755 index 00000000000000..c13cfbd58dbc07 --- /dev/null +++ b/programs/bpf/c/sdk/llvm-wrappers/bin/clang++ @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -ex + + +docker run --workdir /solana_sdk --volume /Users/jack/sandbox/git/solana/programs/bpf/c:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" diff --git a/programs/bpf/c/sdk/llvm-wrappers/bin/llc b/programs/bpf/c/sdk/llvm-wrappers/bin/llc new file mode 100755 index 00000000000000..c13cfbd58dbc07 --- /dev/null +++ b/programs/bpf/c/sdk/llvm-wrappers/bin/llc @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -ex + + +docker run --workdir /solana_sdk --volume /Users/jack/sandbox/git/solana/programs/bpf/c:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" diff --git a/programs/bpf/c/sdk/llvm-wrappers/bin/llvm-objdump b/programs/bpf/c/sdk/llvm-wrappers/bin/llvm-objdump new file mode 100755 index 00000000000000..c13cfbd58dbc07 --- /dev/null +++ b/programs/bpf/c/sdk/llvm-wrappers/bin/llvm-objdump @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -ex + + +docker run --workdir /solana_sdk --volume /Users/jack/sandbox/git/solana/programs/bpf/c:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" diff --git a/programs/bpf/c/sdk/llvm-wrappers/utils/copy.sh b/programs/bpf/c/sdk/llvm-wrappers/utils/copy.sh new file mode 100755 index 00000000000000..0d003cf4e74492 --- /dev/null +++ b/programs/bpf/c/sdk/llvm-wrappers/utils/copy.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +mkdir -p ../bin +cp llvm.sh ../bin/clang +cp llvm.sh ../bin/clang++ +cp llvm.sh ../bin/llc +cp llvm.sh ../bin/llvm-objdump diff --git a/programs/bpf/c/sdk/llvm-wrappers/utils/llvm.sh b/programs/bpf/c/sdk/llvm-wrappers/utils/llvm.sh new file mode 100755 index 00000000000000..c13cfbd58dbc07 --- /dev/null +++ b/programs/bpf/c/sdk/llvm-wrappers/utils/llvm.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -ex + + +docker run --workdir /solana_sdk --volume /Users/jack/sandbox/git/solana/programs/bpf/c:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" From d6b2e0f0fe0d45013434a630ea96fee249f412f6 Mon Sep 17 00:00:00 2001 From: Jack May Date: Tue, 27 Nov 2018 16:42:26 -0800 Subject: [PATCH 3/8] optionally use docker --- book/.nojekyll | 1 + book/FontAwesome/css/font-awesome.css | 4 + book/FontAwesome/fonts/FontAwesome.ttf | Bin 0 -> 138204 bytes .../FontAwesome/fonts/fontawesome-webfont.eot | Bin 0 -> 68875 bytes .../FontAwesome/fonts/fontawesome-webfont.svg | 640 +++++ .../FontAwesome/fonts/fontawesome-webfont.ttf | Bin 0 -> 138204 bytes .../fonts/fontawesome-webfont.woff | Bin 0 -> 81284 bytes .../fonts/fontawesome-webfont.woff2 | Bin 0 -> 64464 bytes book/appendix.html | 202 ++ book/avalanche.html | 217 ++ book/ayu-highlight.css | 71 + book/bank.rs | 2403 +++++++++++++++++ book/banking_stage.rs | 449 +++ book/bin/bench-streamer.rs | 124 + book/bin/bench-tps.rs | 873 ++++++ book/bin/fullnode-config.rs | 81 + book/bin/fullnode.rs | 213 ++ book/bin/genesis.rs | 85 + book/bin/keygen.rs | 37 + book/bin/ledger-tool.rs | 162 ++ book/bin/replicator.rs | 153 ++ book/bin/upload-perf.rs | 116 + book/bin/wallet.rs | 235 ++ book/blob_fetch_stage.rs | 47 + book/bloom.rs | 105 + book/book.js | 600 ++++ book/bpf_loader.rs | 24 + book/broadcast_stage.rs | 299 ++ book/budget_expr.rs | 232 ++ book/budget_instruction.rs | 24 + book/budget_program.rs | 641 +++++ book/budget_transaction.rs | 346 +++ book/chacha.rs | 92 + book/chacha_cuda.rs | 212 ++ book/client.rs | 8 + book/clipboard.min.js | 7 + book/cluster_info.rs | 1346 +++++++++ book/compute_leader_finality_service.rs | 205 ++ book/contact_info.rs | 237 ++ book/counter.rs | 183 ++ book/crds.rs | 351 +++ book/crds_gossip.rs | 486 ++++ book/crds_gossip_error.rs | 7 + book/crds_gossip_pull.rs | 378 +++ book/crds_gossip_push.rs | 459 ++++ book/crds_traits_impls.rs | 26 + book/crds_value.rs | 147 + book/css/chrome.css | 417 +++ book/css/general.css | 144 + book/css/print.css | 54 + book/css/variables.css | 210 ++ book/db_ledger.rs | 634 +++++ book/db_window.rs | 578 ++++ book/elasticlunr.min.js | 10 + book/entry.rs | 379 +++ book/erasure.rs | 943 +++++++ book/favicon.png | Bin 0 -> 5679 bytes book/fetch_stage.rs | 48 + book/fullnode.html | 223 ++ book/fullnode.rs | 1040 +++++++ book/highlight.css | 69 + book/highlight.js | 2 + book/img/fullnode.svg | 278 ++ book/img/leader-scheduler.svg | 330 +++ book/img/runtime.svg | 211 ++ book/img/sdk-tools.svg | 237 ++ book/img/tpu.svg | 323 +++ book/img/tvu.svg | 323 +++ book/index.html | 215 ++ book/introduction.html | 223 ++ book/jsonrpc-api.html | 514 ++++ book/jsonrpc-service.html | 200 ++ book/leader-scheduler.html | 286 ++ book/leader_scheduler.rs | 1407 ++++++++++ book/ledger.html | 200 ++ book/ledger.rs | 1007 +++++++ book/ledger_write_stage.rs | 92 + book/lib.rs | 143 + book/loader_transaction.rs | 49 + book/logger.rs | 16 + book/mark.min.js | 7 + book/mint.rs | 163 ++ book/native_loader.rs | 127 + book/ncp.html | 201 ++ book/ncp.rs | 86 + book/netutil.rs | 304 +++ book/packet.rs | 569 ++++ book/payment_plan.rs | 27 + book/poh.html | 235 ++ book/poh.rs | 116 + book/poh_recorder.rs | 149 + book/poh_service.rs | 94 + book/print.html | 1282 +++++++++ book/programs.html | 226 ++ book/recvmmsg.rs | 184 ++ book/replicate_stage.rs | 683 +++++ book/replicator.rs | 337 +++ book/result.rs | 187 ++ book/retransmit_stage.rs | 127 + book/rpc.rs | 771 ++++++ book/rpc_pubsub.rs | 632 +++++ book/rpc_request.rs | 191 ++ book/runtime.html | 279 ++ book/searcher.js | 477 ++++ book/searchindex.js | 1 + book/searchindex.json | 1 + book/service.rs | 30 + book/signature.rs | 67 + book/sigverify.rs | 473 ++++ book/sigverify_stage.rs | 156 ++ book/storage.html | 296 ++ book/storage_program.rs | 75 + book/storage_stage.rs | 456 ++++ book/storage_transaction.rs | 22 + book/store_ledger_stage.rs | 72 + book/streamer.rs | 200 ++ book/synchronization.html | 235 ++ book/system_program.rs | 306 +++ book/system_transaction.rs | 221 ++ book/terminology.html | 230 ++ book/thin_client.rs | 739 +++++ book/token_program.rs | 24 + book/tomorrow-night.css | 96 + book/tpu.html | 201 ++ book/tpu.rs | 96 + book/tpu_forwarder.rs | 198 ++ book/transaction.rs | 1 + book/tvu.html | 201 ++ book/tvu.rs | 350 +++ book/vdf.html | 214 ++ book/vote_program.rs | 175 ++ book/vote_stage.rs | 82 + book/vote_transaction.rs | 118 + book/wallet.html | 473 ++++ book/wallet.rs | 1610 +++++++++++ book/window.rs | 555 ++++ book/window_service.rs | 626 +++++ ci/docker-llvm/build.sh | 2 +- programs/bpf/c/sdk/bpf.mk | 6 +- programs/bpf/c/sdk/llvm-wrappers/bin/clang | 6 - programs/bpf/c/sdk/llvm-wrappers/bin/clang++ | 6 - programs/bpf/c/sdk/llvm-wrappers/bin/llc | 6 - .../bpf/c/sdk/llvm-wrappers/bin/llvm-objdump | 6 - .../bpf/c/sdk/llvm-wrappers/utils/copy.sh | 7 - .../bpf/c/sdk/llvm-wrappers/utils/llvm.sh | 6 - programs/bpf/c/sdk/llvm/llvm-docker/bin/clang | 3 + .../bpf/c/sdk/llvm/llvm-docker/bin/clang++ | 3 + programs/bpf/c/sdk/llvm/llvm-docker/bin/llc | 3 + .../c/sdk/llvm/llvm-docker/bin/llvm-objdump | 3 + .../bpf/c/sdk/llvm/llvm-docker/generate.sh | 12 + 150 files changed, 39416 insertions(+), 40 deletions(-) create mode 100644 book/.nojekyll create mode 100644 book/FontAwesome/css/font-awesome.css create mode 100644 book/FontAwesome/fonts/FontAwesome.ttf create mode 100644 book/FontAwesome/fonts/fontawesome-webfont.eot create mode 100644 book/FontAwesome/fonts/fontawesome-webfont.svg create mode 100644 book/FontAwesome/fonts/fontawesome-webfont.ttf create mode 100644 book/FontAwesome/fonts/fontawesome-webfont.woff create mode 100644 book/FontAwesome/fonts/fontawesome-webfont.woff2 create mode 100644 book/appendix.html create mode 100644 book/avalanche.html create mode 100644 book/ayu-highlight.css create mode 100644 book/bank.rs create mode 100644 book/banking_stage.rs create mode 100644 book/bin/bench-streamer.rs create mode 100644 book/bin/bench-tps.rs create mode 100644 book/bin/fullnode-config.rs create mode 100644 book/bin/fullnode.rs create mode 100644 book/bin/genesis.rs create mode 100644 book/bin/keygen.rs create mode 100644 book/bin/ledger-tool.rs create mode 100644 book/bin/replicator.rs create mode 100644 book/bin/upload-perf.rs create mode 100644 book/bin/wallet.rs create mode 100644 book/blob_fetch_stage.rs create mode 100644 book/bloom.rs create mode 100644 book/book.js create mode 100644 book/bpf_loader.rs create mode 100644 book/broadcast_stage.rs create mode 100644 book/budget_expr.rs create mode 100644 book/budget_instruction.rs create mode 100644 book/budget_program.rs create mode 100644 book/budget_transaction.rs create mode 100644 book/chacha.rs create mode 100644 book/chacha_cuda.rs create mode 100644 book/client.rs create mode 100644 book/clipboard.min.js create mode 100644 book/cluster_info.rs create mode 100644 book/compute_leader_finality_service.rs create mode 100644 book/contact_info.rs create mode 100644 book/counter.rs create mode 100644 book/crds.rs create mode 100644 book/crds_gossip.rs create mode 100644 book/crds_gossip_error.rs create mode 100644 book/crds_gossip_pull.rs create mode 100644 book/crds_gossip_push.rs create mode 100644 book/crds_traits_impls.rs create mode 100644 book/crds_value.rs create mode 100644 book/css/chrome.css create mode 100644 book/css/general.css create mode 100644 book/css/print.css create mode 100644 book/css/variables.css create mode 100644 book/db_ledger.rs create mode 100644 book/db_window.rs create mode 100644 book/elasticlunr.min.js create mode 100644 book/entry.rs create mode 100644 book/erasure.rs create mode 100644 book/favicon.png create mode 100644 book/fetch_stage.rs create mode 100644 book/fullnode.html create mode 100644 book/fullnode.rs create mode 100644 book/highlight.css create mode 100644 book/highlight.js create mode 100644 book/img/fullnode.svg create mode 100644 book/img/leader-scheduler.svg create mode 100644 book/img/runtime.svg create mode 100644 book/img/sdk-tools.svg create mode 100644 book/img/tpu.svg create mode 100644 book/img/tvu.svg create mode 100644 book/index.html create mode 100644 book/introduction.html create mode 100644 book/jsonrpc-api.html create mode 100644 book/jsonrpc-service.html create mode 100644 book/leader-scheduler.html create mode 100644 book/leader_scheduler.rs create mode 100644 book/ledger.html create mode 100644 book/ledger.rs create mode 100644 book/ledger_write_stage.rs create mode 100644 book/lib.rs create mode 100644 book/loader_transaction.rs create mode 100644 book/logger.rs create mode 100644 book/mark.min.js create mode 100644 book/mint.rs create mode 100644 book/native_loader.rs create mode 100644 book/ncp.html create mode 100644 book/ncp.rs create mode 100644 book/netutil.rs create mode 100644 book/packet.rs create mode 100644 book/payment_plan.rs create mode 100644 book/poh.html create mode 100644 book/poh.rs create mode 100644 book/poh_recorder.rs create mode 100644 book/poh_service.rs create mode 100644 book/print.html create mode 100644 book/programs.html create mode 100644 book/recvmmsg.rs create mode 100644 book/replicate_stage.rs create mode 100644 book/replicator.rs create mode 100644 book/result.rs create mode 100644 book/retransmit_stage.rs create mode 100644 book/rpc.rs create mode 100644 book/rpc_pubsub.rs create mode 100644 book/rpc_request.rs create mode 100644 book/runtime.html create mode 100644 book/searcher.js create mode 100644 book/searchindex.js create mode 100644 book/searchindex.json create mode 100644 book/service.rs create mode 100644 book/signature.rs create mode 100644 book/sigverify.rs create mode 100644 book/sigverify_stage.rs create mode 100644 book/storage.html create mode 100644 book/storage_program.rs create mode 100644 book/storage_stage.rs create mode 100644 book/storage_transaction.rs create mode 100644 book/store_ledger_stage.rs create mode 100644 book/streamer.rs create mode 100644 book/synchronization.html create mode 100644 book/system_program.rs create mode 100644 book/system_transaction.rs create mode 100644 book/terminology.html create mode 100644 book/thin_client.rs create mode 100644 book/token_program.rs create mode 100644 book/tomorrow-night.css create mode 100644 book/tpu.html create mode 100644 book/tpu.rs create mode 100644 book/tpu_forwarder.rs create mode 100644 book/transaction.rs create mode 100644 book/tvu.html create mode 100644 book/tvu.rs create mode 100644 book/vdf.html create mode 100644 book/vote_program.rs create mode 100644 book/vote_stage.rs create mode 100644 book/vote_transaction.rs create mode 100644 book/wallet.html create mode 100644 book/wallet.rs create mode 100644 book/window.rs create mode 100644 book/window_service.rs delete mode 100755 programs/bpf/c/sdk/llvm-wrappers/bin/clang delete mode 100755 programs/bpf/c/sdk/llvm-wrappers/bin/clang++ delete mode 100755 programs/bpf/c/sdk/llvm-wrappers/bin/llc delete mode 100755 programs/bpf/c/sdk/llvm-wrappers/bin/llvm-objdump delete mode 100755 programs/bpf/c/sdk/llvm-wrappers/utils/copy.sh delete mode 100755 programs/bpf/c/sdk/llvm-wrappers/utils/llvm.sh create mode 100755 programs/bpf/c/sdk/llvm/llvm-docker/bin/clang create mode 100755 programs/bpf/c/sdk/llvm/llvm-docker/bin/clang++ create mode 100755 programs/bpf/c/sdk/llvm/llvm-docker/bin/llc create mode 100755 programs/bpf/c/sdk/llvm/llvm-docker/bin/llvm-objdump create mode 100755 programs/bpf/c/sdk/llvm/llvm-docker/generate.sh diff --git a/book/.nojekyll b/book/.nojekyll new file mode 100644 index 00000000000000..8631215945962e --- /dev/null +++ b/book/.nojekyll @@ -0,0 +1 @@ +This file makes sure that Github Pages doesn't process mdBook's output. \ No newline at end of file diff --git a/book/FontAwesome/css/font-awesome.css b/book/FontAwesome/css/font-awesome.css new file mode 100644 index 00000000000000..ee4e9782bf8b32 --- /dev/null +++ b/book/FontAwesome/css/font-awesome.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.4.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.4.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.4.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.4.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.4.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.4.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"} diff --git a/book/FontAwesome/fonts/FontAwesome.ttf b/book/FontAwesome/fonts/FontAwesome.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d7994e13086b1ac1a216bd754c93e1bccd65f237 GIT binary patch literal 138204 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 literal 0 HcmV?d00001 diff --git a/book/FontAwesome/fonts/fontawesome-webfont.eot b/book/FontAwesome/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..a30335d748c65c0bab5880b4e6dba53f5c79206c GIT binary patch literal 68875 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

7r8$?kb-D3z%-c} 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 F002TuuHFCu literal 0 HcmV?d00001 diff --git a/book/FontAwesome/fonts/fontawesome-webfont.svg b/book/FontAwesome/fonts/fontawesome-webfont.svg new file mode 100644 index 00000000000000..6fd19abcb9ec83 --- /dev/null +++ b/book/FontAwesome/fonts/fontawesome-webfont.svg @@ -0,0 +1,640 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/book/FontAwesome/fonts/fontawesome-webfont.ttf b/book/FontAwesome/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d7994e13086b1ac1a216bd754c93e1bccd65f237 GIT binary patch literal 138204 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 literal 0 HcmV?d00001 diff --git a/book/FontAwesome/fonts/fontawesome-webfont.woff b/book/FontAwesome/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..6fd4ede0f30f170eecb4156beb7235bf01fff00b GIT binary patch literal 81284 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äY>7uxS6S37IVP_>DW-kg%dn+sFHLnFhvXTU%&ox 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 ZUtABnb?tu62nVk>KX+wfW(Hek zvyp+(?)D*}+YmVX&(<`Z!i+@NrNIkT9jIaB0KojcX7>O8|Nq%XMaE3R(ryC)Kvh-$ zU)TtXtU;}Nq=b9uDJj@AW62eX%`$1Hntbp{o=%*VFKp~;#HbSWI^EoF@Q}N5qQgP! zXe3uW@<7Kk8y+0!#-n5DD^^Z)ywHbqdfzz6!f3GQI>kDq%MF`XHqXMmk(Fg9TU6mJ z5M(qrZjoUQHivF(b8Wk0(6O0pX^++qmrIy;kEUaaX2bR~0w&v*wz3D>u*oLFhHYMk z-h+bnPojwtd+Pcva?Kg$=$o?syro@!Lu(dOP4U%LW=Old_&$q9xu3I&{GCVKrQk^4IQ4Tt)tA5Wvg*01hHrVb#Mm_>WXRGR z`?Q33zOE|X`%F|-caNkR-DFfQz|-!WSGoy06FETJ>?j)q2?0eyOca%{Fo;x8K(Kpe zfjxC|MlW=8n{F;#yLMf_?N{#it6%^3;$6@)y-(Q#iE7)eqauutrbx~vq5pnA-JH2W z&=!ieLg8~8Fs9a%(Lb(-HLavOmXgCbgA^D7D5-{%jCaS&+2yqLG5p-|0rLPrSS7{I zK^$C!%Qymtr@8%GQrp;I)QBCUMu@~l)Q(X#Xc5@aqe4pIPEeVGl72)HhLxxo2+A4t zzlC5VGYg~s{~P*OXU!~EXYTsUdnyK}$f~v>8`A>m{gr^zj8huR>CuTm0ZTlAHgzZOuaN*4oL6!laS-dWDyakH zs#JCF_4=&#_eKsl2@}V##?&zb+h&a8n3w8QjP`w1^QMu*7T+*WRC!&AFn^4|9O z&w^?irPg>e>A^Y10q8(C&<%70oev2*|7TNMSw7MxEI2NMT}Hve&-MVQDpNhKlVXF|8J0=f+Lyon_s*X;b!*R=x%!LBDS{B9Ok8&dYw&Kw zc5w|oBU6n%veZl%Yj`8|*~#K-=>~)l8JrjL$N>Pi`7c$gD?kx8 zvm75Do_$Yg$5|gCfSKnCuySH(Y?`Wdexg^xOLy=&8d%l+{9~huiig{pHWfz!sFaif>vSC_%Q#!SdK8sA=~xto)AG=_bawA2T}60= zF*|eArIaQV!SZ}8#zk3B2n+P?e{Y$qzlAx8+LsGuH(Q#4q}SzkoQq{0>Ka98g{r8o zAFush2DVupQrUGRtz!#8LpAaC9-kXzP6t-|o~=5ih!FUK4-&|^L~V@B97roNBIj~! zN%B9)Y5ZT5-u0%|Of@Pb1l6_BZLIhH_=#=(|6<8j?asmA5@hjbO-Ft%ZLg%CY?=ah6y*p#&C14+Q97E~OU2J0)`#BGSJdHGkq!Sv? zjEBs>e_EvZ6&-W!7{opt#MRFIn%#nO?AoWMY0?4*Jt#GPE>hGFyJvCcN@{(-88l9CiBokU_O zEoWapTlnwa=k}S+lbM4~rI1NP0v?B8m=GqI31R|d8oH(`vLp&T#}NFj-&?=4erx^4 z`nB~dc&+@O_}#SeB>q$PiJB_+DE0Y&(m%b3f^Q#P$0Y55`)Oh6kW#F#N>2dTABoLy zej|Kxp-WLO*uP4i2gQWBl3_`{cH4r%pg0siUJQzDWl|N>kyk7^O)0hTVo>b!F5)D6 zX%jA+G*-5*9T5SX#+m8{tTGpaPj&}-UA$i1&EvH1uc0-gW$nD zaFoO?(kSV@TOhYv(Ed~nBD8KK1OfCsF=FkR9ngcv#6 zg`r}sL>#FtyPVe8bHNbs6lC?XOY>?@A?+c-(^F(+jgl;&etH9WVu{I++aI{9u-XbV zk<&s(N|)py# z{=Ok$Z>Ls8g-Eu~?5ds!_A?G5hx1vp6t1%NskL=Fn<<4qaUnN5eot4u0|pfFl56Hi z&K;jOUwy>^Ryk$`DAN%Ji>nlGb@Xayx41i8)q=W4^=c?63NMhARDvxTL3Ve&NFkyl zOeWLWb~SrPJdOpKiJ~oc4xa%UKFpA12Q*`msC_;^UwHI)liQYgtFYyGOcWCBVGbrH z1-H*ye{=nMyU9m;e0-1(1{)QLgUpsywV~7{D~_*e_?fw?_77eHYH%O>#hVsd6LH-z zL%W?&%4^H`TZ8`FeC8{d_pH{P}i3orrTQwhMW9E#f)3&KJKQN(TI1U06-J~Hb zX5Ww*42*{O`P$uY@EHWI8u8JSXLz#~>=k`UP^b%!QX6f5Owt_vIsi=SE8C*ooW8f0 zIzrHNtHXX>H~C$XUoqb&ZL}+n#D3x1JnDtYJUoiP0AoOy0ghym zDP+wYZ)K6~iuIx@GB+%kA+$+2zt18%Ae43$h9f@30#T}K<6#*D2fXwTQ;~inVz50z zJ^tBz=E?rJ6gg$p5a9V9w`C!SWF7GHuHk}~aK+XD*QAykGzFCIXw+yCP>(!foiA@@ zgx=@9h^WL@hu6iC1wxMNVdBTI23mK=^(bGFd?dIPSJWZfY{dN}vp8-YaxEzI17mrl z^~vM(171E*5{vEmD7N_svoR!FUSt%mi8<*z6RG^adK34LSt*iAZj61?AsPGJvJ;#S ztBX6~-*Jd(tEaD~}_t-Ej8QnL8dK{j!2J$GWwb__8#a=gxR)E%P zj4~;;K}bX#>1&Myzdy++x>|A7Xwi;_p6h-d5C@|g6=oyLO=QS0j)aLS3hLjY&?(N5 zDpiEUR;nmpYST?i)n(0_hqUUUb3L(XspX1@xngi!-9&4*UmsRQ7o99-vQDhKVi8kW zF@+(klDt@UdA8gPsI0{a1@HX zM+M}sZ4&}%jkZNLOpQp|!2}_z(MS)vOI@u8TISnCtjmIH#!4nfqFr4vxdFmpEQi^^ zj3X7%GzQ14li|SS#x-fWiCAfx6)`JG5JZ70{lFITn=OU<{h8D%%3i;$(-?7Q=2Gf% z36Z75SfZ-1--e`beW%-7-9mMTp>*b&*I#}_0@fm>(C#ur#xnEF(tWheu~Q&W zc+RQnbi$c~&p4tW=tL|LXk%inF!jte)2vdd9@<#WTls)!T>w|>ppMoq$P@U#H9hT(tvD5l?_1rgVyTa4yJJI+6Yw2FtU=Qb&fDh z?YnLh1iM^S>+w32u9Md_HgS7nf3Zl5YBIlm``~a%vTbT;z19<8y@u`Da0o|{)?#?B z^%?Ila`!AYp8<)5pTlZ(9ll!h$}gJPvGJ8b9t3z#n~Kz7!f3Q>XtQJ%CX=MQ+@K&g zU`~qCwWVgWJP%IUMwj;4Iw-5i-Fbkh;83-7>CM5cb+ndcD%n|; z52ZR;59GUJ`AqxvH8=4&jaYkYvJBh%f$^tGLZ)46?<{GDY{va|pd9 zW(~_FJojQou#Dqb%8-ypiZfrkmbN8Zra8at{hY0{+0AX;x24P21clE5ks{=Lw|39UH^_0&&WyiG+FCWIj}hu5Ep- z+T^Usw9*&DecV(lkDc*~x3;mq@f@zYqcBtz5K~!#)V&DzZO-|LiXhba{qN&^+7;d% zUF`Bi8QVvy8Ahq)U#Y!}86=c)zUak>NzKDoo!eY-qkE_4&&x@j8}Y^k4P=i94|=4p zS76(BG`>~%o~63YX9GMDWFl2iNl6Sw~3zEEKK0uT@il>87A<6sD>|5q@Jxmi#B}Q%hM6 zQ+d1q^)SF%#;95Ir2@*E*?tCAD@HswJi2=I9ES{vDb(+ZgtwOjJtJGaw!>GRO{KWn z#2)ZI6-#KJCXuymv{pSSfZ}U-%5kNqvAdJ0(}%saV>EDIbA@J~O*m{8oGzIcFsE^q z#pa;zk@Ct{32Q8js}SY6x#958>}&~^KZv3+Ba|_^^o7{*^fc*{PA@;RMJ^ZisoOi! zu5?~+-4_&;%18_#IGtF>UfDKvL$@A{Ol0y|JFuF@70rN1Ls=7Gc(RN*cw=GYV4E=Z zbcsOhtlvO<;N*QC*-{_CiqCIW@NFfUS?Th>cR$3J2gP^HItkVD)-J^m^Q>N#Wm?RZ zE$$xmtVdSHW} zdIOa&y@NT!gWkvp$}VdzrOtc879s&8+Nx$IVFok zatt|u&X(ntC&X`y`?I95)!<;D1J=$T{L+g{>>mApnVa78Mpy%iV{H`;=8Bv;Q*&pd)hSMvz1VV`N9p^6ri>D?yehdiP-xbHvclBJ} zvkpc_s7$*HF_IXkql?((qLMo`#C3ojW+=C^Y;V3!I1KM-rjtvOV%Qy?zgj|u@PfU) zc?UyI@IXKd_l}vP!Vi8hHWx05spb_sR8vkHy~AfMc30N{0{;fg+8ucy(0{-QLF14F z-iMjh7{pbE8tcP2Mvyy%r2Jbr4sTub*3e>Jstyb&4#wItH!jax_s$ zI@C zE33P#VX-aXZvg72IV+52)}GDVP{zcEf!2Xd+HCf}&7)Jnl`QFf@cX9p7)AgFjzlDL z9uP}yg@)BObVuwY4Sqk?{S<;%iVCg0a5mFCwlf)|{q-X*PE%Z*H4u}{!O+l{BZ!dh z$iM*E0I~FZR9tTy;4nj}jPvJlB*LAJ2scHG|4<_3b`=B7NkHP(kWx194gJ0r z9q73{k2e^i-sulXMlX9JET5IGy+javq}K#2y42dnOLJHk!iGN25J#7l=T`sfd($ALWRZnag1x;lDR_#)q%!7*VRkT>#Gbq>_2@zux(OEX zA_|*-eh^mq=Z8^B@A5;0OiHp&#r0P9Qrawx((+4VjwJE>hSsylgjtk0g62|i2Azu5 zO{7QRsXY)6wvZouQwoZUCsAOO-4}ka33;20G&~qe22R|x;%OQg!Gt4bseU6WlL`)X z&83VJuom*RhOe54mKBt(zX}sO2p?liU3Bvg%^g9eM|q9IeEsvGql1|BABnO$f}q(8 z_>8DmmePN{5kIRpD%V6a-;}B<-wc}6AG5$*DWc60-s?*IDWbbds1=HvTL~BDK(cuE z-Q?4?4YqWTb^wgh%ylu-I4hU6&kA^mIrX*adn~5L2_pJ**W0(Vh1{ts6bEa zi9Ezm9Km9O)kg|hAC{ruhiZhh#LQH$_ z^tDjq34m^KOxlY=n=z?cqu)Nbvwdv!(|sPPv5V> zM|LWE$kl7J=1sD}o(P`H`ho`3o&mq)$kAkg5tSV+A7x!*BF`a3I(|zL=RAgwT!pEy z=siwEr{sC>pVryeE|GV8LCzZR?EM@)zzJXXgLuLWg;+!b&*4xe9EPnTRE2P&P0#Kg zyq2^A)b;O2Hpt5LVYQ^^5d|gGal{R!Lm|M@`YOM~G=)DeDp~zv-c`LDh{lyt9Lr1~ zJJWMm-HYTo?JA#E>ZU9LLXN_WQL**-VAQHK?s{O`@7U828{d1_%SD}fPZ^CcZP(6l zCK`LYnx%&EsGh4cdj% z`+?BA&nSZHKB9Y;>+nnUA*c5o%JtaYWTBHY_g}}em?J2UO7O9il0X1w@*v~>Z>?n2 zrJL?|x{ey`+=j%r*njYEcP@oS(SdN3;YZvyLs!AnA^5My3uJD7{)`m2 zN6`kks?sRU8&6B@{L=~j#hY2XRAAw;z46m<@*`1Ywe0Y)6FEa-V5!d$)11MdO&~Pqo9FUKVq`$Gb>?;3l>0I%R~^UVrx5-!9CDMOCEVU&h*z>D z$}!$u4NvFBgLBiHsZA4qn(juqKoW9=~ODVcb%#Te6M7n1P8X{ z<4D4Vu^>B?gL8<)263E4{GRi)HV!3&sxAehra}}SUZ0tBd=(4qZqqW7hBAgOm+=B_AP5fMSDfJ^L-iy7x0Ic#dvcb1``lCw>y z$Ckqw%w)64S4F~n#yDKL0%k#PyCDBq@B#$4a)!{PM}AuvPHaC?<1%r;ZTCogT7wT< zTPr>&(##2Y0?@f+J65R5D0zhtTvFK!n?lpVqPxQ^7kV6_7a{N~kCR+RWflTc+(MyM z`(8Lts9a3ykZpTfWHfqOOa>*rTPtliqWR+y(VQ;3*VJS2I4uQ>_6M5hI89rN_&v!@ z?WbTA${qH?v6tk%uWXCtqz5>xAi)FF#n&uLINVGRgoJQVL>^>}vCRONUJ?hgP?2+p zF5_SqqD#S46buh>C%|MI{KQcxSv^c>lupOfw8<^RY`wgm;L_>e2t{Zy(59M-%-A6a zQ*Mi3Ta7?>_^6!>rh&M?TH`GjAEcZVnThAIR%Hj7^`>ZWicjGqh$SRAVJC$?WIW=l z{^mVSE&j7flL{5jyb_cO`>GZ!BnpbCkRuZlh}!uYM5d&OUGz!`#HS80V0Ri=9cw>? zJ4oES;Kq5Y&>#Q-jU(D_cm{VPQW5@Iwg$Py>MjbC7gSR#SbEQ>NYi&8#4MuW(~oq# z{a0!WUDy25Wumynz;FdA0j$8{yyLJUH$-dXD2g@>Xt2%UXt8^kE5>2fYgLB1lev!^ z15JRxg}>=#L!Ua{I8)*{rs-i7nhaz(YS_hdPX?Cd2YT+CZYjl^<_Q>I^xA^eF;LrV zS)|U@vQO)X4HY)c{=2tcYU1!coA0*Z3fg|vzaP6`X+?{Tu2i<~XJVx*#)RL0w{;NWxUFKhd54dG&CBQ1X5a_6r<2`DSEYj4jLIXOb2{Fet@b=7nsRvSsurgF!0 zg}%O4H`=1n)82TCLEcDk=r+hhr@O%*m;GtI$=24ffXUqBTLEm%HtT$A&!=ymx0P%7h;tlp@r=)}@{9EW1cX!dO zJajluO>FfFt`~oFbZ+qqjX0@DF+esCYh8cdvx`RLJ-xilP(Rpk zRq;v;REAPc$~0hRDDF(j3W<^;c7|fP`D!83)A=6=sywO3svfnXQTZ^Hz( z`w<9WQzD#Ssz2Qj7n#TaFjI$;GRa??T%j<|kGNN!@91V@OwT+hA8Dx;N= z7q*dA%q45>jWSHP^%KcwkOnuagEe^Dnv?nP{sF$kow-QLT*X1j;~?^?-$`li7q)wqR*c z3TAz3rZm)gB7q)>32RIgze1AXqZC@p&`WQQM8ieI9d<}O86=$0M@){PgEJ*YQxB}d zJ}E-83XR21p^6sZ_8>*ZL1I(%#k6fRN>HEz)D@{VnvNQ5Vf2#Sd(P4ZC1YO=hrISZBluvP@f z75olgIL00vXwuHT*wxSnHqICXFv?y;E)3pJXY+rAQ(m-ny_(j zk3dJ~6L16fz}&T&dZF=As3Q&B_Q8qRW({Q&r)kJB;*q=SB3o=Y!PFPVhCF0W`5Sfm z9=U2VG$wWW4xiHFVkolIlQq)5U+6?|=MAo6sn=7#vU+!s$gZeheN3U;@h0}+b1;L_ z7CQpSu67%uAF+9X#5uachz#+hJT^ikX^Muk)D@Og9$7a}w!hTW`KGFpDdyi^TcCX7`yH%|)1sc8?*V5Jy&kX|>`SqFa7SRm z)2nKMhv#WzET`X_R8?EZZjNb;A2_X*;BSe_KrLUr5KU)=vdL$RC+R=SFSl(hZF)PD z^_-}x@;dMK-knNk*qDOmEvi(j(s>`#Wc7bZRJ8e(R_83VLkVGwb8BI{^qWCc{1_oO+-yyxJYC?!5nT81O`10-Rc(9o#pH0U_W$|a zYhj2W0tjG$xgy?|gjo+#0w@$3XC01d^z0bKy_X4QabG&{5oGeSN2^w{dUfx-(-&wA zJ}5A;r>9z4{*E(&q3R8~p}_zF1QEm#$HUJB>4?z#omfMEp}#*oBzh+63O9Z$AhZhcsXN4kqmR>t@=*)-rn!U|f zGDev-dW`Hv(7wqAY4Un{!=p)D79CgkgJb)-^6owj`gZ!o z$F(l~#k+slVYE7lK;la%Q&=;>rBx|&7u+eP9qchXwgb=2G`l^dUSzY#H&1hLlro6WV#7!yH_XOxC;3kRhb}FTmAEOIrTD zW7!HM8x<%^sE68?VaFS0!{WL4EKfeLOQobG`Ywt7?9zl_DO|=9?EDFHb-zMmg;NY` zDxF;M0c*$40KIF#ZFpwnO_p&p*<7&wQp=BeoAh4nlSto5#6Lm8g|UoSs+nfU`ntN&+rf>X}XieWBckSD21M%6G zgOf7OQIA)ktWli6I;HrcH8Ff%?^Mn(dM~9r+cT!}dDssnM$)Og>*TGgro(i`ZSw&k zw!G$EpHv5Kj4R$YV%Sx+8N+pO4xz&WAjl@|goc4ZK~_Co6&k}`Fb4~@dbbmtk_%?b z7*V)@qp0#*1Wg_+m8;^Xp2Gp$v{@f@I#d6mD;t~c+cN~!v6={tBQYIf9TD_Vh=;Fa zkV7PY3{y-1VhoUBVOqU4BmPb5Rmzwtw)Zr$3#|Bu}d`&$PET7QRtMsS7OaqSc2{wQDP zZ@czHxS$DNpCF*wEWb(z=6?zvIhP3?`YhG81*y=Aq^un-C!me*bpfloME!a5*lMbpp>;v(TQBIyGtW~AVNLyvk)cs-4%8WfG z8?@fJ<9{AFP{JDvWHE@QXbOcH{(n<#Z{XP3k%_ZYi%z?jhnxrDK)o89FLnmZH5|KK zM+;#Y7KLGMl6zBqf(8;d>rU!@AdtJzByPp#`ksb0xM@6^Nqr-Hs_zEnIQM*SoIbf- z!`|0=W2lpJ#vCE)GnS16pV1`dGQ5QDJ6k@miJhHdp<>Kk>-v!?l2JtkjSoKeKJABK z*GO@jk>y=wb46Y8tywSvkhcCkEqu+Z$07Z1E+b;ULS{ z4zVoC@K|J9B4 zW^S$VRLprH{0+Y8v*H&?Lvawb$d3P|?9U|*D z)V7YsFbnI!AMHEPT}E@X?wn>79YQJ1^4K0(Z3(IPa~#C8Uvz>%J{r`?W!7W4E^!=@ z8ePwwHxP|rQrYdA3aVnX{o*}W+&43Zov)c#oj#YrTXO0aj z8kW7Kq?kfC^da3YJ8PuV&Ow4dL`0uTcSbrjWypcvXFSZX;UP;CUT>iCOV_P=n)=L|8E0lrLnTC%b{AE zuiCw(Dq0hUbFcp_^0wg^Qbu`);wmPizHe(4Qls=bS5%^9z zSPV2hkGtvGGq$IZC;h15qmu>Ed^J?6VpyBbvxd_?aLDoVw~tj!Qms|SD?9kOxuQoC zqpd_1d4_8gH4&wkFDiSM@trZLEY8*jpMy$m+W{6B&7>Zgbw4^J`OJat2%WJz!6rk_ zjE58Y^=ie}Qd#VeK0TNv2?Sv6z?+T3RO-Cji~X2<+}UeQ7fuFrKyF~YG5owldy;pq zM+d|U@)mMaS|cx;GRifQfa^#(w!RH37kxfql4H#^tk#j`=Z=|VwJlMiFDG4Q_xP3P z*SSA>KYbxV;XZmZG`?wqw`7C4{poSLhNQ54%TMWrH<0IeERv%yvM#S#WWI;EGsg_D z;@5CcB5QQ**LKeDYYnWTs7mL{I6X#xjUa+e-?gX&J|=(AYRqX4k2fnhToJ@@CKtDH z!$;@Na!1QgjHPbZk?JhRY`YE^dxIL&V8!toKJ4Y5*p8I?W`b{{=QwL-X0rb`@+kXO z6$=jP4K{0pW-ICs(^Pf=V);bqzAcz}|5LNFO`)b&eu@-vEY~gLI}vXfLu%VS_5YCs zfz`EJh?YD;`|hoU(>`^fWNeS`aaXa$_$^`e({cKj5?2vJ+i+ntL%Y{6^GNj(MeIY5)q~f zYW+c_s^ULxbEPOwxT+od0+r!V_Q5l{C!NFd@4G0my^rgcT~kT)b4%d@!_(|I7C6!^ zdF(4LE7WXY|1c`~A!;RCO@o@4p}nS;i8yyKHak%xuVc%itDtO&pLL?0<&tt{DVYUa zqt1cSicVm^#eoymsR0E+cgx&RAm>=!omT^tjC4rV)|?7b2}mrmhfj{tlFKl3Kpl_D z6Xh7l!^68RET=d9D>x;mPE>p$TKVS(r}yn2ixoVGbc2p>UxF{ODd0pIwDN{xVk+yr zOIj*>X9D`0MHi_m3+Nle*c_{^8&vXe|GZks)nmxa93WT#dpuiFX&w{k00Ux%2ws#= zrB|@_$belfyxMgNxRdB+-d0=i)msx0Nr{{f(ahyrBz@s(M-XxJYb-DmZAt(@KwvqoWEgIMh~Soqer_3KVv8ub zTU%gtgWY*2YX%b)>D*hXm>mN7x;# z2j3$b;M}expJBBr2+C-u3JriW*i6Q6R3AB(CVAEO7RTI|eJF=A7(S@J*K5xqK^902 z4MW*{3h!^nS3rkpIg0ECfb2;$ztg=tw_H#%C$k8jQ=+{-KESHfgQ zzlGTcM%ls0L7t;EXdJ}*_F!IM93qqKnL{F%dKzC*!odZ*AaJ4Ttx3h?6Mxk%jGy%; z{P+fr=WqGIX1&H@uugY{XrC!`#k2F(8FU}8V86XEy$y~DO-ntQL&}uEIdh$7XcWm-5mX!x zM}wUn`;<}(urPhr#=A0x92_z6nAM*cm4}@_U&bnZgK6M6H_u>GXO-hv5{G62BO?xu z@~zSs*U?+2hk9T#p`pV=MtjaM)&#G4UUF>4FMo{UY$JOOW2cLpFXl&XkK!A~m6&wd zF#|EK4i7a10BEwIr=9K)ns%E4ttn92OiU77NV|WUSfxkGspsk*SPe|xi8R=KWAlP0 z(M(93qWCxa4`o}|j=E&7lXY^V**hwSOOgK2HJ9%&O`r@F@J+lt4mMd^5G1fi&$an@ zOl%cq*rF<PsS#4d4dN<$7;_H3c26?*8fr^jt}-owK1@u5;d z0fo?RGN(+RXrm)G++EZtuSV)6hg)uF40aKp;AxttC@IGC4U5mRslGBP+h9CKd`1qq z{3j?SG}#(WP0jW7tr#x$0c|$=5(ERGD8ziN%w$@ zEFJ{g2F1HlWqtG{N}Qpj&erU7gD0JykkuZ3M)J6qQ7G_fgVI^CwQ@eWDP3= zVH7mPj#N|XP&*LV_>)XYa#7YCO$LKvI@RAlotq{HnAd0bg{91_yNk$N3v4=?)x;Cw z(&S`Gaiz+7dV1Ylda_~o{r32YF2PM2LTLms>TC}9s0N?bt9?fdImBAA96NSR_?k@p zo;)lAa+1)(<6-YM}pQFOlGPVy0X|FP&vlT&vDsy;^@Ci)8b#Z~tA+=1g8%J8L~IgPMmBDEXq= zjCuuJ_z0@Q5M^7Kn?@W?ckR>%dlW3edPpUd`-?MMG-~b8!;5Kl&Ko{6->m!GZ2Gd|*uI0wz+Lo4tMMKRfi6!R!MuyoRlR~m@T28ydb@+&>@~H4LW5G~VXQcRyL^Q^u$oWBPIFhP zm5U!siAzFLe2V@&VJ~-Rvc?wYZtyixHAmtG(x-#f!lCpEbyT<>fbPkV0?OVfVob#e zaTgLgLy7~I__K$G zJ(7Y4!bE!H6z>z4G>$#vwP2qn@;t?boB`Y767H6-fj+?M!>h+FEEBhlg<>-#;+&K2 znzXfD`8zF1zAH6RsL)2Vm8FX$WMkQ*tKO3WD|U108UTbU@1a`!Ue`fbx*RaxXOJRU zN*kDZI>jAU7(9%<`kf8_g%K2!y6hWOBRq7Ie8d%OjSf*mGt3vHT9ngMJ!(m&p58OR z!jiIHC*A{(ND#ey1LrhGUNi>F8zMF7Mb&4jIuw_3u zAeNzP>pbU@@<|tB7ze~kUp>JorwgHZreW4%KAU(>Pm@M0cdbe!s?;$nweKx$tx0?UyWh6Br`q1w$py~<{_n0ZOt znsVG?nax&Zqzv7&1`e7bdK!PoI#ZX0_obxgM3-MfCF*8g(`$C=5KnY&;sfY;xwu1W z=I~HIrYZn*5b-X1>Tjhuk{URCa4G7!qpRvgluxfv=2hl%gFeCN_Ayn5pW`qk?pO|c z=rv{|&g5f)k8Mo`@|?8sCa0V_?Ik$(=0BO+U<-CQ7~XLzD=rmhHis+91GkT|q)&qJ zuv^4EHmVFEHyU5bi-tz&NvT&^^vj$tgw<%<7`9ASOiM&|3O!7@GGQVP0Ya*_*9$ix(1%qzRXpTl7WYImmp_riMYMZcTcp9(JChcJ&NH|QsF?231 zc{oR9Sy7|(;kt)lk~A=()Mv;Wy4zq&0@Wp{AFUS2PuqZe5N43#gc*Z06fl|E>A@Z) zohp**0_EzZye&3ew+-6UkqQ|TzwcIa|E=t$e2!PzpvuvEz9p$U!Ja`ue99cIX# z&oE~OJ=ya6i!gP;Zh(FcpOpH()2|hBzw$>TO0vz=NorFE7@L1gd=Zg5_$jGKtz~xG zA8`(JG1A5Vm{ABPTU{L)dQY@sqKf+e;K+gZo*aae51~UnUWrw-%fUeO6K!WIw&m*5 z>~5lQf4oC?Sem@RV0zU4>caf^I5mS(!bZsOx+4)p&8x}tf0Qa2EP5)+2P9^-TCXg7 zG%MB0ga#Jyv2mBP2<$k#4`p<4^nr~&vG+?l5$JM{AG5pc(MVGLeDc=h6xJIm{k#Ri zPEtWN(s#uSr6Yt|cpFmmAeWnZa!zGN#~mWN{O4jj=?kz1JdG7h#HkZkqxY?zp;<)6 z#ETba@OrQSD!F^wrv!YHr!3Y~tQVO;?5u3GpYyO$pAQ_CCg1V0as;5}o<<`dW>yo8 z-KOYZMc+A3_|=U*xY6WOyR_Za2)mzSy-o>Z9~BNIQ#y1>EL3g-S}dE7L0vNZzy^bH zNB}e(I=@iN38pPh+pn z+J{FD*mR0bP)<7s_4Z*`ir|cJSG+(Wlw%1LfP0zpoLU?1ct1l_Eag4{nwgJ%JYHzn)5)u zb`YboN(F_-UhRJw)+frb&1TgrQI~0~hgZW&YVtvJCDGGr6-;6ax5qysz;&NGbuGtS zxUHB3KZ?aszZT}VQ8lCGjbkzgH1Ad zKY~~Rhh(pZwi@!OTvp0N3+$lM(Y^AZ35-%S`~S5b7o|@{^s<;b>Z{1wB0-|KF3!Ag z2Ab*BRH~tw9+!eTngGXP#Pe%FjD2wz8Fs$7}RC;HxQynPE^25TBdXGbDch z{#PW$P4UgMS1*pu9Fjlv!+1@Jo$SpWZY!@Ja=z_hWOlK8cCsr1TCUL8Z|gj;fA)iw zfn1P3b>>2G@tM*YHa#^zeez$nOdPB+UYzBLz28)Kb>&oqKFD;~dT30!2!nWKl#ioZ z&u8N!y9#noB|X9pRl=y-(Rp+RXFTT73zz;k^!q4^?UP?&;Y?jDXQB&@h5m1eFg4O5nV+)kl5sXNDxQst)XG6kV(H zh!2Rg_@Srq8EY2wuwK@OY5?fpQ$MZOGM`#J%fZ?>#;#RDm?D`%4x52Yhh~Ru48T z8H;^My}opQ-|}K&_rd$tg2qo9ii;mTU0my^qz1&PjDB~B?qYwzE`k_VjDGGFlycv> zqh~9Y20gzFaEi%U^;cn@?qT31Kk$bbdMh{}Lf>`T#Y|(9D&_OqjB`0sDVBCAD8$9R zTx7{r$;{;(tw;6Q8P4qEv)NtWari+<^Z|6>IYTw;F+BV$kME5?Pe`;131~ z8BJc#dVV%BpviR@i4z)@V!fzEebr$uE3YTr(5N}RnzRcQyoq=VR+jMf4f=bd)Q7!u zBOU!C7tboY`6jRQ-HEm|mGpQOZ}@05A#@&_(gKzo}VXa{KffAG-3vS5p@H699fF=;iKeqsE$U3 z?-cq-W+P8D=@7poT)zQe&aOo_lUVWiF%UiGWyLoh`eoWXbP9fX>0Xv{82sYbfAz_q zKeTd(MIX~JF1&*6Pzu>zZob@_`M&i65dv%XXwV|UNPKebwF|%j;C3*j6j*76UOvt7 zWV~J2acvjw!z7)k2O`?wDj_oizfW>Jqyc2h?9q0|X#Hz)_+x)s)SqMe0 zAzFCn{9MDMa_6*o@2+0g^rQo(Am!TQeTc(qzY&_bM@oM(Wt?pSjhQ3BcpN-1{z9o_ zibH)cf;HYW<~58{fAZEq36{#|?*uK!a3DN0ExUd~hg=mC71y@G<|8+uT!gpWmhF#h zXHYKlbxb>-Uvh{nKUAkWecK&QyV_$fu0P=udbWyh^QNBc)6VsmeT1OCzz!Km zG&wPB4jwO+0QDuEXLBdE`^RsbVGbY@v`YmO<_G__4BS za9>1Xhc+yN9dTLQ-ORSYn@(sfAQl8cq6U=eJB`828Ev=HG9R@tCsNW7; zoN*2qwF*Fx0jpHUz0}$%Uj&f9=%_i0%SNj?oU@VOHPcAxwLYZKkkrECGG=-qTQx08 zC-@Qz!wP><=86MMN?zi2IP+Jj2jtyT_i30l+k*`rJ1x+rM5O4Wz97mbEwTpLCDBp(_dHUW2Xb%P*ij8q)(3eM@q zKjKy<9Dy22RObANDfvO;uE)q5;+^LHU5WP#qI$eUUm6VF#Wr8|2fJDSJAwPR`+7`~ z;urDeC-lBM{?=IAaAke(drzMBdT&RQReGHI?8eYc{V>s@3VecPXeI@D$eIC1#kGE@ zJ%n;X^ibp>I4o?=*ba6QFyZY>6K0z36C7V4P8B;$_JQn>t5C*;`>rq*JbUCz9*zgbhox zC2^Q`wLSkRXN}N^8+4}$CwPVLX_xOoW#_GEktI%-@cs!rjSl7o+DL6yfG1_${@^09 z{NAH9p1R0!UjDdcR}HzFCNRf69Xlt^zf^e8l-n^36Lu%!-o=na9GG0$a3?MzP7oug zf@w|EMP=x1v;i3O3Pfcx34!R6_1CcUcQ0?-P`qIiXt-`!zk~&maBo4Z(cpKZZdWS2 zW!1MYB5siN^CqUkhPrim^6o|am@Qi|yLHPW8hTTT`0ev$v)Q}A9Nf%ZY{PA4M5B)( zr<1j-C~R$b?8p;W-KxhIryU0fiz=61lp0NBQ=O2RXk5ORaRiQ%GN#mzi=C^;;qLxT zON@mMxC*leUshO0Rat#znLGeDdcJiZ6(&~Me1$HNQuc5D`h!xdb@0aXByd06%0l{r z*~oTFYHv{6EiTF~nN;E!BG?aGn5E*!r_?aS`(dzZV|Icw1Ta9LMj@4G=2h*(=J(dp zhFM2LkRS%oXVIro1($S8`3aHgXXC$6WD@NmMhxAg9IHLNpHx^r8|>%tX8bw)84N#x zKLwVa}g6vIX5{2IuTKvLM_V3e>ej-s~5R;Lm zn;)(`iwm4MapMAljo&8~#wRC-+B=TZQ7uMf7s5sBPR4Sv{?rX;5*$+6H6lK8ji;p$2eh$yNaf@%*wAL9rT@_B-RxPKFAE4))ptqKFgY1?~|hDmY8yReQ9 z%10g&eCeI&x8+LLtLXs9JpV|UTKn>gQZKS6c$XL6fWv2T>#GVXrc6_%LWY>SyS0rA z!xOGIWKzvad>)`J(!Em_||3)8_sp~P2n!JnrDG%wJt)q$%0{_!bcVBdxNJ=EVzZ0@wt(>8JZTc@5}S_ zg~nY`32CSdX2(l&e)C5}P(>YVSc8gb1@eo7lw9<~bwp;QadoolkcqD)&Pz3*r6&GR zxk%K-q%of;vzIECdY=`q%q3rA<2ohfUyAxDiyBxk8z=XPGU3OYk8Dp z9QMkGL12WAf2*-tF-7`12VzGYsqF|RCp*Qi6|Be0iQqs7hhnS@Vf2$Ld||)1r=?Cm zjvi5UF-pWIEH095IxUK2@2y|eo~Sq*M})a1{Td$Hg4Epc;ER!fa1qfbB?GEw^J~K$ zZ}eyg{2OMz_9&^1y%e*rWh8lnQ%5VhN0rDou}Ny*YK@Fau{J>6%b&Yf6A+Zh1FlAE z{Cu9?yjFUj8FEdC5BrB|n&aqUKURn`Y+^lF6{mkZ({VxnmB8S;xZ@bht`YKY>n-tZ zx(M(BTg>Va9qc}@m?MR9Cudr-${JaP$7)R5X2)ooO*zeEBl=xM9}JNAOuw-j@f+n! zNiCQEsAhWEQ;`XXt(BkJH7uVwAGMnUtO}5n3Nol+R_rhvsBW%AJAEfnRmAJfb>@-d z20d(ekzlAYf_S<98|!Tq!wNfa)6jPLyG`C81ViHg^-a5M6xBbZ(I#U%HO@Q>9iX6Y zH4ibZ`gPo-)4O+nG=NqPlpiJ?ShP{eA)FsjR4q@?@QhlArt-P2#`Fu5#ASPN@ zy|!4r@qSV0*FS1Esm(#xrGrFAkPc!5v9W~%P(Lmb?xdycm&$uO6jvQLuARD zt6kAEq=6Xrguv56vOUg1EjcXS`2mQP+7keL?;#AU-p zDfd^HRowBQ_dC73e%Qlx49!@;eX&NDM!iMtV?IX}-3lB~t3m|xpip6K?=&IPavLlQ zV{DQeNTdfW?(w@7v_ei%E!Ny{Zx)+{4h_y=iB^;B|JYug0^>uVN%b~{FG4sSA@h=4 zg{DQVaMMjbhw;J>n8-VOV#^&4I_8l@76q4`33q%d5#5YXoX%7ha9#{?{m_X{E(?Gw zfu<^^1`+9)e?3sSn)kCs8dSavAIoAo8&wz+1cichg?%2ew@-hI_%n*?Rp*8UiP_V_ zD447OS8!zF&gJM{7X46AQ+8GLtZBG`GAxzmFx8y1GTFO0a8!k!*)KGM^=Dm|>xxr5 zDk_vV3{UMg;yP&3%_E3fNA<+z@n{p$Lhiw-ev|B!X<0g}nOEfDK^VU1h1$1 zpV4zESv#(R?GV%;&PF&Nj`VfPJvyX*OSbjuk$C6a38q%&M!Yx;U=lH#zQ+)dva zA?>mK`XtdM@av>Y9Sw6R=v70$xfOn0F#%^CynSc%ksyq>BBLioebp%X-k3&8^_0^* z68p~ZlG#6O1EnJx%vX4S6Z3{HrCwA29WrW#WrLF_+7qd~(c8%qZ_%~45NMy<#qzBF zWbNvmyAuxCH7Z|bWVvXdb<1@>|MQ=7)6L3b`xX^N;=slQV-Xig5T-c5%45aty{#V` zjn#|F$9j$M*+qCvr$;0wu2Mya;0`#h_l5sc?^7I~3I7`n9ML+u)8uZ0{2oTBXP_Yp z5vDk!SHWckWg1M(0Lp*HHE9R3nj%|hjwJqk8<3ILEa zm+rk%yW>=))0=nsryZ{JM594cX$a0ZJ&Di^)8U&JC4_KQtixt43!))Id)!jpmY@Q} z?r6UJYZex!a4;_=Q9!&7xSO%{3!?d-r&{a@;m~uYDr@ip?!YYbSZd_!H3SX0o2}Nw zvHEG#t4$T{>Y|-c?&TFwrDIs?Iak#YcT0>;*--(YG7X+Q{%lu!X%!`g@8OX4|HEAI zWh(oQsuk~yPu8-pF=me`XCv4}CMZ+At*Td^sMz_0W}PH_EsSomMV@GL)CtC>0_p1@jN;i}=0t^wd$J?V{^p6G^C-oac~Ib*@mCHgH- z=RgI)Cbv6r&tZ9Y_>+iL<23=fE~f$aFez&L4UNo!c$D$TBHuH?pC)}yCY?bv(83E! z_F~sr$tlZ1f$dk|&jR!=Y_BtizT0Z2axqsu7D3<5op_@A09DcrXC{-^8T{dz)XgTG zqzz|1&fY0eIE`H>2G(qL%S&4T)z<&5&m@enBDMk)fU>5Ik-nov$#;&f!wpclQvmpr z(?ZN&U>c&6{K@m4iB$O6i0@=&@e~S026Ex50wsX!3@s#f4s#dIHOjBLBBKcswJG%` z`L^Ro11Ms&q~K2>2Yk8qCn=Xi6m{;ZSHzQI(RN zYW4yB!`--@dyrGif|P48a!$5w;Iua7XDHLF<@3`ZxEGWe?ItgPgTTrQI&+UX)Un5O zxk|nbS7@tDmpSd16l+eLM-S51#>xI6PPHR49}1Q# zsJtvZY&hDa^LR21ZhM(immtm+R++u*6~A{ClRSi#(AJ4prnbcAO9*NB@WPX*DY0#A zKrX367#_sbvN)AUY3v~}3fg2n7{S2# zajo-^(SX^AnlR;1L9*!!$zNBDde)VwB|WbX3>q#4k!xIp_LPf9HCX;T;;YH}j_n6c zb=$*WCOS&U;(|_|Xzek_PdgbC*UVyWI@JYSaT!=S8b7qmaOmF)@)TrtAjKr{wlZti zOaXhz4Q>ciZWxH?9wjDQEPgs|jm=ZrYO`;kAXYx%bvI$HOQZ8pRuQth+xJdF9GDa7 z=}1_N>JAgqJ&PcuVqT(EvMdc%glP}4NmRaL`S^y|{mVJV?ADy}ytNsJmQfBllw3yt zh0(8}>FJVZN577y?pO};uF=qQL&P5z%AbquM(OdWwAWjzjIhKAO2F4~M=`SIO2Nv! zH|3&=<0dx*)v;t*omeo*M18wYr-=o$;$6vR$qocM(#%GOqqck56oq-3I@|8x>JvFG zn?O|Ho*k}{F`00y41rMYcy1tdoE|Pu93Ij+;=(=&`29F|Dw3w|IId8j^2;HWXCp;f9C4eo&5Xh4~JNAd0?u9M+ z!WQQOk?;<9mo`WFJ(jqxs>(ghXGwtan5TqNs-Gox|L2rB?_hyDPzq=#49F4cOT57i z7)L|ht_SXmKJ)Ik)VrmlEMvkr06o~w<)IA@}Q;@J@+93oh2^8Xprm@TdN0~_uoSuJL?T; zX7~iYX~BKxe+1qV|KR}}6HcH94&X9t9W(l8did(Bp(}HRh6lkTYz8Nl;w7QOFqHIi z@ky*Mp8nnl>ke^qDy;5$dj$UrKxnFHMc?l++f()djmlpyG|kmf;Pa8?n9yU4(n!z< zhs!jA*grrLr+EmGASB^1byk~TL6qDdOg+}Uf6tRtSSqDIz{GkqCiQ&}7*~H36Vo?q z$~`p>CEd_Sd!ODL7qFNl;3aOc;#{YN=-B==umAk>jEsZdzoCsRXY7PL zzd@0B1q;@*RzcYYZ=*#gykBs^4TjZWy$$G( zx-FzuRQ&~F#mJV|U_?{S4lc=n_mx+eR|Oxy5l91c_h_ub8>Kh)%Xu2s!V^pn&Md6u z){9f1eM1T2i)^~a?=)ShY)=f_-LDd-LqB6Z2!J(QO8QHq{wev3Rhj39?}Ttyufn^X z|3sm~pz~s8ewy?Te$&80Yj>}!?k7A13FB&G{l`w4D87&_Ekx03Au)km)TWNzP7n5 z{rNf23+LykA%mXE1kM_0L}tx_Mfaft9>@@kGOp}7ywY%RW^Hv%*ZvUC6CN!kO~EXu%_XzvsaF*wFw{P zF{KKlN#wQe{S-YcXm0P6STj@aWSS$VJ8OM(oamvy6x^L0e2tjK267cLo_|&897t5~ zsn_dhyF)t8R3%@Uu%=h@c1k|N>Bks2efq*6z9I#pnThzNn3Fe&li;s+h%(us*uvRMPsK`u4xt)k<>;)6*^fj|ZX zzC>wV+1O&IDt2-r5S*r4Wd{STD^XtnokvdtU?`ucqXA?qN7X@{^c~$*c;B@x37tkc zQ)~i(J(XkBnec3g_ob$FqEc9#o-QUrYPPXT;Hkg_tg;1t77-cV*^a9N9{l%1>4({s zhQ^3oT?e>zV|ISZbNvqS3SQk1z$ z&#LZQsPa7DhwotN)q#)}wljaL??j`53AX)1x744nJ8x2@$>1PeZ28m_TI#x1)fd)# z!*xPx4!fG0EzAfK=h*ma_}j$^mf5XXrh+uo@XfIa5(_i9+3aeP0n$r9rE(Ml=roM8 zo*8K>>(L-gd;$8|+^jI!d7%_?-qb>dB#wteiRK0xM#lhU(`?EaFGl=8rP_X2AluZ~ zoMe1@y0vvYfO`rG00dzx>yA!i2Z-(d|1=b*I?fpYSd}oHM^`H40`(c_^HakwV?<$i zxRCKTTe=_o&_fg(s5icH#kk&sAXZmzJa9ze-&MR{Mt}2*AsfmJE4*1RlT55!IU%_Y zfH=v7tiJH()iOg2*{?SR^r5a|g+Cg$9SO4SiIw$Fmf{Q)Lkgf3@C2>RiheegtRV0F z3@G1r2p4&W{><97BctL+w7>QoGqw_#hg@-9^OajVR_oL5tSmb6LA&!*k?A7_pJsei zds%zoI&H?T#){kmI<4VZNUGuR^&^~9U+FG?r`kAX{BU)hSh9hp+1}H+MxTCZWudUt z>O#3TeGIE{$!EN6?fQ7ccmO~o2%dFXIw%$0v>x>FC^o7;qa^W3${9OyI6l8x*5%tJ zbNe*x7)3_o)9X&lz18u@5D%%spl#2hT=_8H9nZ3+kih~e3Mn~>91H^(ro|}<5Q5)b z(E9l;MRR~>W}0xH}H#+TZRw*=JH4UBtujOz3E4nIbD5 zTKfk2T3fI4YXh0;? zWsm%lZ2#u3@9mFIRy&;^no;nXu|vMHRlUBrMcBD#-~_>YoG#Wn(4$jYn6nhSiXkB5 zpppU-Y4t_wbTdl|b1{r3V9Rl&Coka1<-z`kLNZspTpCoNu+6T~$LTaekFFCAI_ukV zVJKY<*6NSc?bh`jGFk|r>yLL+`GR^viG2b85pc&sd!6WII9=tU!=q9sN2Qx$A{D?3 zk3`263JB1dE)K$vsi~E-{TL1*8L%lp{@WZw)!bj9!tn5;^WO}lfkpoqt=fC5YQ66g z`-QHn?89*Fesv}%k@Et25jF6(cC2X3v(YQg#jVLU%{9)J&PfC^UA4`-$LScP7bh=F zfle*e8BoZgVB-KSlZ>q1{S^L&Xo7I}W>s)>7+;nSp{yrGUZ@v$407`B+19t*fJ8mJ z>RPKL2#`_0o2=k(l7Lcak}7eS2wDRLS^hDG;lr4&MPfH(P1M<88!-4raU&knfVI;R zt$QUzH}&a!S=+!pfoHAk^hB48h7bi;yJ>+SQbQUtT!?xi{Om93+YVg)VZ0rG7LMxD zvoqdjJP4?Z@3LkoP%+>E=gFp1f#16U18VG_Wmv{y{ty*rfUV1@9FT(n2Rn&<;?7tA z&<8{!0A>t@d^1o0*a*U&8_3${lFe^gLwq5oI7pMrL0}Dah5`t3feq%cgf+P+ISkGi zW(=?=YZY!eXp|~TTjYv=dzZtR+RIlGI0o)idhR>e6xYDfU%nJf=QXfxMEavrV9yoE z$UlxveL@OB8=i%5mH1Vw7I<%F8*1LWt7wsNZ+8UQorP1RS=nOZi5*D;5BZ)FoCYB~!`;xNuP3%G`#G>9BuS0eHsW+IEV19TI@nM1N;z zbb}(Y1s4C&qEEcfvAq+{ZFIi+3{v%HyCy%^KfdJij@f1ZQjdlb$f6(Cuo4SL7_1gM zP)@0|{ZhCYGI&aJ3VaGQo@qnCo!A%~ZVDmVn6m=kpaac9AdzsujuA!{);sNu2-O0&rZ?KEqE26|^g zSDEdR-g>x(9#M8e-DOW4tZ_g_;jw8b4P1u+k+u^V=Pfr`lFHHcT_7?05DHiUj8sfO zc0)Yqy?~{a$@MAmWQP{8E}_`8@(kWHDdYk| zqnkbpHkefjwlJ_`JI;+#t8IflNL7^V{_$G|0ERUL`&gx^pk?qR0cHJKk#GrU9ZV4H z&)XP8%a$mgZcq8KmNHS^tWA{1AE-Q5_s4WEP|f102wIv-73x7qeJ&xHVTXqR zA=4a)s%bwkBwZtF7;-LGZX)+E=)4qmw-?`DoShUJ8O=xE38b$xbkc+Pv`Fpx;j^mT zDquS#=6@_Y-QC-2ckHOejr2o^FrEScE)oq5sd^hX$45IX z@8;q+SF(TNK+&qyH*dxziZIM3TcNM7nP+O6r>RvHUK$z_kYI%(T7|Hm_DtLNV#{l;XK_eRAHzy>nk$7uLZO z@Ej|J*Mg)AGvkR*ib(^BRBZHH0>r_7xqz<_9?1i6Fj<7u$vE362M(1X|HNv;=Mh$C zJ6CP^9L+1N&c(=toq+BT%Kl@VGyphbX<0LuHJk7!7&dxZM}%bJ)~)i(pFc;FJ3-LM zo_J0`$2=c6%%1j^PU-QOVvOk>Fo-s04^A%P#)H8V| ztkeZO0k7|`$B$KT*RV{CSp@|yeo1C#tUro{{DI;Q+j~T{H>Gs#*4X4P=*-K^mh4;_ zvrQ17W*~41cQv9bozULh0@ye#UfuFbqR|*jo5jg%1JOm3YDjBM^EGsFT(J zW@;`J_oFxL9k600?p?wyQYebJlOO@+%`I|}D($&_IxH1$-Y6_Pu#OIZ`U*=$K@J8W z@PEsr2fkgN7i%1FGA7HV%1O<~s#hj8N%;<7*T6`Yi zSbRsbwO3e}6o|3hoSNT_K$*2(Yn@rSFf+lonm3f|P7a`oHMB|E%n{kxW9uf9Z5IT~ zhr2H+!P}b#bMH9)5fL37_n?9u9T5@h?i3Xj&Q)Qi@%@xX4OAk#dCztG8wM!`x~#Nc zUyiIZB-zqi25EP{xXa1gC@R^Q$GNL9=DI{2u0^?<$FXeT+ZhmOR67Z~-J!C`#H!^q17H);AU zxn~*`gZBJdN$qX-Hk5BaxI}T9zkle)wV=4`rkqyVI3LVq-A@+jzm<>5+j%R!0 za`7%93FKDmav&B=@zch3_y-RXs+%B1JGDfoN#mp;(IHN$u80_Q0t@=L#mlP}U~ap! z9De=`|D{kxAXB*S?Xzm1{Q9vAefL;>f(nj(%Px1$&Cn>^gFnu0Jb18?J+3#DR9~OO zc01)5=+gH54x4dDukD%3!0`+ioCUaFjA~Ev%bYA?aN=zQd5PCLR(wzc$;LN-DVT6d z8PcKE2mSI7F#GT|@Tc3!a=%Ewb#KmT!xyA7g~*T&WrNEUk@|~%t!?A8rOpQ58YWwU zoy&qGYj>|Y@hGfink9Q*H(q&gTcuxFhG^dbj|>1ae3Fqk78qJ>LJR9}+Q{EpkF_rU z#b9344B90casbmKP87^Bnl{Ws0N@SNq8-tvAmdQ*A&}&dL!!xu2!&$ksF;pQb7^Gg zNt*s2Fi~&{z9N;%8N|n{CjR`H*v6%8kRoyhm3c})B>utOp3mKdq7S~V8yp&nh?o=s zv;p;aDHn!dsc1MLmtYtIl^w~zLPRHfvJ)AYIMmw5g*GXz$&RFWHxLi$2$!3UJ82t6 zBLU?Zs3!%eG}4A4>BsTNH?+Alu-(G-UKlx=cFQG%A8z8HjLU{9e@n9GOynZbMbHz% zp#o68lIRve*Y`K~9oXiT7I}!81s{vi#{B$1Pwo4Y2i{SpAGK@N@KQ9ofPC!4}3m~%qZ}*mXhEj`_(l0n5bZG2lb?vc zVEXcTugA;4dT{Mkm3-4s_wI|YF2=JRwZMyb(1gX<$3zB8UxZ)FXM8rjlvcZK_Co-` zq_R-qo@ady#rZ3kFHb}`ZOxTFB6OLoh7rIG4;PxzZ?2~Y2uLHsfjuO4{AN3esDN^> zmyZizWoEK8%2<1OQtAEs<9xfQC_Dc6;Nbq|6Pqtz-mEy`?0iD8GCk3}KPyWr<=ta_ zHwS-i^!~({$oSZl1s?vuLpB}o%mgZNih3U}^!#~_mW^rNP${m|Xoq^EsvK3Z#&Fa7W*XSDuWHv#yUB zck#zTwC3A`)hJeIDJ>e12>(1Q8?BeEzE$1hIGXsq$*ZbaeY0LRDm(ipR{})yq-ZCY z8gX9eJkEdIr9LLkI@!8C%m$>q%hJ^M{9V%RyQ967NSl+4%cz+K|H?mbqR%&3% zWnOw{QtG&ECB3aX+E>KUtl7_MO+!+Q6nN12kNKYkx8<$1i!(&kGnzb%ERYa_mOi^D z7#!og6-K(sNl3?K@svRK+|+7%QtA3`EkLQEn!uLBe zt(CQZjfUeZiteVj*;bCHh9;%+QZ5IkvQ$cgPtNFQ?z~reQWZ@^6Swnq>`bIfnuXR@ zPyZ6A%xv+qDbb$}P;lig2F7tlUdgH*>j628r_939orEg7(OUs zb{>GPCv(YR@92C4f%Ls>P99oK%za4|5UXA;v(9`n;!wSDx)P%K zXNPdugnz`LSoVf{U7i>N8o@X`;#u$HDHac$%?3s=?E;W}aS9Nt2rRa;yLkjwJiN_s zNQ$FicI@BZ)<$`N4eXE$EhP{>1QfEb2UqEGpo-L+wK}-e9yUA$Qs+%5~*wa3&=Aj_W#9bM$iOxHd;HJsp*VwvGsC#gH?7U}zzFygO4r)?Hh{dkLw0 z@#jfPB#8btw43mc-|t_7GWOJB1D24W1#M#Te}H`eV&=|! zz*lWQzP5pAYo3a8>i0I+RheOeUVHHv**KwXFXIu_=ZtsszwI?VtixhZljIAqv%B|S(uKb$e zfS#`k91l9BstbDM6^ITu$7mFpI$QGvwiZn9fOhOZsC7UJ!PO2OJuArcOJXJ3Ie+;r zlGKt$Q+gR9kjMywmy(v|N{ZY?PPa>BCHZAyx}(u{h%78kvdKyE?;FQUj~SRAp--oc z`T7+CQ25F-XXN2X%un0QZEAdk3mC+eb*$bs5+`>FP2&tmDFA$7LwU9*lbrxLIzT7| z06%XXj@22M4-_~TFWLQDR5c;S65}Fu4_6BJ#aY+{@cjfxQ|kkX)O6q8Uf*<^a|!9D za5<#g+F~t_?$oe;s(8@STXI)45P%fPFQ$>PylH>`X`bc_ZdDrx889GH>5vf5F8`}i zZeXr3Em;h=ZwUuae7JYSvv7y?QlBH^`R=Y<(3S>=RH!{;q8K+YUkMdCslWn;YIRgB zBdlg9e(yQ+N_xk!V;!qfvI;RN{`9ikkVJF9``e#p8>4wL`Ee&_m;e7^{jz~f-%1KH`X=-H$%t3ohhYnY$%zL<*drVI#@AoUcE1P!4W3%J_3n>uvZ7=d&&v(x+dn8#lGri4&@( zauP7IUb7?Lc2Di14NZcQsSWoU!Olp%)Jzr|Bw~=XoCg?w`UeZcio!9dt$q}J>R!5Y zp~pjWU^;IoY(>23z~f>=@9qhp4H^`ljia0SZmS|9pw>{8AgL9wcC!6D^8 zYQewSU+j=jaC(EjU3@HXy8cyHxImA8I7C(O)0G;RTmLg#OZ%p#1C8e7jgI7PyJHmM zvXs1TKrsiT5c#^$g<|E+vKCvu-KbTkNGy1;vUOT9RmW|D{UR)XZy(ZNR;@jV)AMKsk-2=c8LFcAW65nK7PWTYqtq-g1*mUd6|8Hqrl<5jS z_0WeSP?rvi3J>N5PK%EN5?P4y_pxIV2{|6+%i~|*p1eEbR_E z6A{y!)9Odud5W`h%gtbJ_;+_onR(Kw&ISM99a+%&(aI6bRaZhgxW@S;R z6?E+UZYyx2Y4aWAz*r0z^BV?a?Ibp-%>z-@6djVfk@D+bgX&hVEEwdKAGN|iLt>T= zfn9-_js&?(I?6J$P>=+n4|TIsNN!tBtTJdNRGn0`*9ix9=mJg_Kpa1c?}Y!`oIW?$ zb(hZhW+`>woAN<``mGLF$v10qIo`+Em9bGLHQPFP2Kg`wd3F%w7$p+dIOhJ|2Q3Fl z{df>_)?@0^|I`}6zerM*_*ra5Iu~1jWqr6`j#T$Y#;(rhdy}1pJL7>l3flcV=m*#` zKh-qS1;S)loj$D=gQOA{xSYL}*a9<l<{~3RwZKHl3 z-_e{05~p9yYva6d{+Kopnwd z8F|#>f)g^^f7`lRq+C0&iE{ia?<;+_TqyBEaZhSP@Iwy|;PLzv)0PFytf<-Y9YQT~nY(S|{U=0Qe1OpgM zP-5jIXnVFS08U1LWYp6vaj%Y!s~BRZaSVjo^4agHrk_)eJbJx(9NK6(7}gB4|@u-s0*Tm`tdW*JOz5#E#uGarkZ ze*d`WWJZ@s;#TA}43j0*ZKi?#`T^dxcHy?vC1?S710X?Q{`j?P<9xeW-v4v9GVLX> zGVK-J$3OBIcKq=xS9jU+$GW@5Prlr^Yn*TMH){h7E@!oSQ&A?Qw#Z_%3;3)Pi`;25Lm+>R2?TJ3hdy{bGHn zxi7YFpf=PJA1laQO^pcW7i~+3UfIaFqG0&r-S+T{Ta9DHO z{m`dX%}NN&49jr4J}Tw8Ul42vHSL@4vY7@7$wnC*PtgIr!zD#baTyy|yUIl_L592T z?hM0rdEiU}k2hsfDguCNM{ui|(fc$=#z!mHudnRP$nYLCD=Awy`RK{ehO&~xq;V_c zRV2JWDLF!!o!>v4pBEnP-D>_(%a4lUYd?y`qcoD9MCl$M|CFB~t+X2NW3{EF+Su!2 zaYhq^dM&cK(Sud0VPBm;h>R8B&U8Vl;t`2C3R~IH|@&y9_7^?u%4tsvC z>1eID(4Puo^7;M4%^y?6CYLdI3Q*`|ev}QH86{u~+~3&}E)$q;y1=qz|3F2CR>l%( zHJ)N$Gc+i9V`$ob1PW1^-^r>nA~OoyfQ_zJ#PT6&cxO}oQ1YqPDETWDC?Xp9B7tH6 zSZ(y_(7lCTsR;+#4Q)+OR)vkgvdD4RA`)H~WD~YG3UCRn3fm(K^qi!4DRVtL zeRqq3A4XIXuDlP_spa+n1ZFNM1;;Gf1+iOAXwe#QIpn7QEjZcy7(dAMS{2-c3ZB7j zVT(Of?u#+ZxZG&F(y2eR+|AkYb&)1%hyj>B5GtnIOl5#>gY>$q=HcLKE0i8?>V73v zZlV83G_|p;gxA1MV+s-B>z(+DfH41jav^14royv|$qNe#$5vTKu2&s1Yt?5p9RgR{ z!N8MpV0mcN7h06+_0G0jI(Gb^kk>YmO7p2ZJ zLCvyr=UD&y;?@0WB!eS9ZP8s1Il~jk@n_rxf`8ex6aD0P%K$@!RO%Mk1|u*CUwQhp z9IggygU*UMy9lhSJRgAPQz0y(U|eMKA*u7=2cA{dgR$BSM)B*WUVU9^$6LFpMZ+!+ zNrRPsnBOYPCkH0lNSi(8ee?W-YmcwY`7}~ib>VqqZ{C4`rj+;w_jXZk-Lo&3W62UkIU#4xL7iaD`17_OZ5t4`bMM#l7B7!NBvu_Laqep zBlVuH78ldZ*d@*N3p((t^DjEdy1JSJ^8DQW{tYQ{b?$A#?1;5W@>xajT7EQ`6gzVL z?)KrW8=Gf4-sJ}=q92vgE3naI9}eFqSXUAyFG*W(n}HlZl=ZL40DCG#Ss~ZBMcBxk z7$Og*k4R0^A{dyB$PFVw*H^dqDv&{f2p%_5M=Rh2hw6j4>Nr|)-nE!e^Tg56?ADkh zV_h;%c$MH$y310-dhZ{@X3)h)RV1aQb5M;#UXUP1;NzkmoM`ZPbKtmxoEhX1VN-e1 z$TxmHSklG&7kf-6htDTL% zWiip&?%6?_ST-a(FEF1F&-P6UAv}9g>bv@z*o*pw!p(sfIcoj5i9D$!za+51-gjT& zJ==&!1&{6)-+fpBETdSeaj|T0r808p447hEe!j|Wjk3N`H6kED<~|nPvvshqJcwyV z&sBo_u{#{#lj1qFlP76&6xa*!3GsIJ%+qlIpA35;Q5g{0A00n01t?OD##A^h`X*(0 ztQq;rVhhcFhwMxJT;QxuakCM^aY^&N9$QWdB$BvQiSzAwe|4mHNtu=2Y!Jb9g6$f2 zZL5<#>2TLarmc~)s-X#?BYftLGa6eE!9a6n2cJ0-AF6+E`*6iTXoNOL#pI?7GLiu7 z^qOa3&ysxrd0v_htj$PMgk`60%}AC1;pnfh$x~NFhw8+_ooqJ+sQ0tBc32-cbW-Y< z?GQS5W7)JJSwsUtw#WS@p`UG3zgR^5i=?)pmQQPv0k%2v&9x)rrl<{7jpo zLHoT7P&5Jv1M>?suh@LLV;$R=Uh&DkBWM`Lt*VtJRD=D01Jmzk4+A)4*&EiVs#-w zfLuQyK5HCH@4oqNWl8VlW<|6QF~v6CQ#~3|SFojBY#Mwu*rB7dO3s^6JTy$Z+I!0G zibUT0u0z;sWSQr$+mo%$8%^;`^`k|h_nuIw;Njx|j;j>YUO%clL`UDLHXq!m&1N!9 zQvW3M-46K0Uq;VR&$eGALA#)qBCB}U;Y@Ecoy!g~tHFTaQluO`hB6D=e!VBH6p98) zMlCYmU8#$J;CB>$Y%nt-7}|>NB7i^ftoU}7<0iRW2BMp#Qf1=Z!4g}9u#nDh9$ae- z9^)A(u2#(DiMUfJn<=g+Z>~zQvg*%EY*5#!S|JXM@q~|A!g+8C!QtTlOkL-RRaTMD z1~&gf=>jcJrT2UEr>Blk8BK!Ew#+4g_g)@gxVtZJ*S>R6NMZEtmn%Px_>uu8i7t^y zgF6=ZaF*3v(#c`rlWe)Tz9^!@qnjW=>dLc35feQVhB2$YUj+y>Rw;@wWUkFFSqu#4 zmHE8$b?WYR;#@s%$b8P^MY5BH@L&jk{;FVWJ-+XJcx8d%i`gY>Gh(;*l^o=s5D*~Y zn!x0*k^}$~n5k!H=t?+3O9)z>08XRYd!6vej2@6*O-!}Urnig8l6+owVQOP- ze-|qb{Beja8tKCUWjXO8M3nf-pd=&M6hluPEX`=W64Kk6Bc;+C!q37V_uXCK9dQ31 zCsEDz{dO0Nt2SByFoQi_CPL)I2b9r=EgCV9J2(BnMuzxPZ%b&1G_9>?(qyTOmz->y zMnAgW32+jLCSCA-j{N?3>hpp8qr+zC%^!G9^+z1{!TU{U+(6$y2R5VI!uorXoMpk8 zer4_hd@=@XFKGi%0N<>KD3EagTh^ga&4$oX^2-IjQ3M1h{=XtKPKRz7jKPvjB3y2l z$Vq-f)npC7)8vB8mGG?tG(&(-!Li16Y3Yu-wOT6gdn=qtD1LUUi~tn|#j>97(9p8+ z@nwLMcuj!?kR;?95DPG7hw$-*7?Y|$4+I##wE3P@MRN}DT<8x;bIHee&%#Vf31gc)b^(W<_9qT$-NSzpfL^f>ztCPPo2!qg`X zFvhE65QM8f7n~x5Lx3VdmQr}uWti;QP1GX9rgg(Y2{?}7=%kRbTg63{9E@`(RS=Mf zkkI!6Fn~=&vwTNnl8)0&v=qq+Cn;b#u}WQkj>>yD{j7dY|J2l6V2#+A*Gi4Rg+SaK zAN?*(O~oi+@=!oD{QU!dotH+X`;b61a&P|VKT1%*-&uGYk?*ICIZojl2ZI+ zjheR`lq&>VOxjmobrOdp39{l!{-rAq_G0`Y$5=xgljfXhLn;NkeYH?939*D>o#}?PE++Jhb&kokyquqUM`mm+G;4DWJS zPLJCW=U}-r!w)xZR_%t*Vynm%W!>I5Z>@S-G)mc~vy$KHTaXRGz75ykw+?b*Q>tBZ zi#JCX#&`2J^|o+jv74ic9-nP>GDp%n1Q3+CW=s*RIG88;Qa9YNY*D&}5ay@a4O$gG zRfOX$3+>mOkzR;o6et*|--!}cUZWj>@tvFf?phaa=?V;hGh-pQsseXg;FSImNWZ>f zS$C=De}d4fYwBx#S#KD#B5MZ#7ODTiZTfcg#O1nQmq5;aw`Ung@<=!6f=tVdz4A1ZGdy4E+?MKT`@-U>Ws+UH z&t|Kz@>9NLZgH*+<7Eq%eSG{BaEONZsP^AEW0$1|_n+IXHofS?tx~_Pi^twn^*Shp zeDUIQ2G%#$9=AB8Z_xcnH*eIlP{0>XbUN4Vp`M854^#8h&i2>_`G2U=Jmmd*(^K_x z#8cDPt~&}KyPTkSOZ^dUfN-7&_%E9+Fn_c` zaAsHV3@_bM@F(Zmn^C^b7FBQ;EkfDq_%mf$|NLFZgJdsvbk5^|nBHku7$A;6=(=>9 zaC|<_eXVVP8f-j|w6pn;3wWra;tft(ZIm2Lu3A$kK(JQBsK5RmBaU?uyZ-(bWf23& z2I}h10HkeUvLWMdi+&{_Un{wly)@RRF5FDeXOw}mVmsCZFg&$E!)OHQ9 zT~2R`vO#^Miz6eW^tnlaSrU8)s`H=R5><((ug=YSI$Txgq9dD^RkQdiEe@Qt{fw+e ze&0-<%-10)U&=d@)!`dOKs6yhTL+>&qnZa)wCw~h_r%a(!46kh+zlJ63)2mkR1K5n z-s+9P%{iJ8AVr>Smp+4U#J~~m++_yMGh3z>y}&Ye$wzm8fEx6xiQTe!)Q8$LTvC5#JEc?MIRr`*^-{hXScFr?J-^NKpFGW@-h!UAIg!rcofZ`7q)7e)oaZB0P`Pah~weXQA?g5_^$(vFZk#02kDw@bCAc_;re@%K021Sd( zw!hp^dKu;9Z5vQd@nE3sfvvOjNl!4o9)!Q-$hhBX^d z+CBgyA~Vx1yO>VRl@2)M<{I()xA|#PQc_ukhuuv~48*9xYK7>>k8$F-Mk5DQ^xNZT zdu?GMz+!{4H&+5;BHtJrUQy$h9`@!ajWhz1X6(pnmM^Y~Z43?je%R#j_pk*DIDm_Q zct_H1*ay%?3g}R3MW3vaj@D*V(M^XKjmoFl3Q_L(U9A^_z|3PcG%_?P9iGwxviIqC z?yrJ&?{^7QxMW9`X&K~Q<{0ed6XxVjvXsDY2Q5kX-aefxv z__;w>S`i}|KOVoD!f*j4Eq|teM{8NY7bX=V_upo9=5T~0IA|9flYY;dR|m>@a<>zJ z$Mx0a1rU}<1ADa_N8+^ql*(zly2_~}&E1hW>G!D@kWa1lGsnxw{vVlj{e$1w<6JMR zxV1|+&sp!PU}7_|FU|_iaID&PW3hP|P*7gF54jDo|H9pM+lU!#P_Qw0Bft_GlaT;& zE0-lG=603R3^)6MEmJS2MB0S_*TYUH762dei?8aw8L6o_zfvfCU1DNYZ|~Nt1OYN( zB?4OEhl_#Ek1H?xl#wBf|FdBT-I-fvV7gyYA-xOZkZN21G&Zwy2>oY+Fg_!rBv8no z2oREhtne?T@P)CgZ?qpL@c};Zw`^e3up90{`%|N46O3qdDCe4!t)=mm1RT_|jp=~^ z25y_^ahpchv z&8~3^L)79GYQoK%3r((Ap?+TH_$=y=YjB2sFTV|<&(kn|?oW<NRqF4sUz3L0c^@#l}9fpk{izW4pqxe3Ztqn)Ia` zeNu(mcMLSzOrHiQBBI+v zP;KSOAW(o3KgLH76* zoxHLVZeVFr-ZrJjYUU6q1XX0Cf61MGjIjJLM&H5L@0nUB8A#YOcVak2Ci#vMq=#17 zT5s_M2E(MyX>|WjvrhErQP-H%6Vqkblc>qnCH}(uKrKU1;XFMJH03+Ocy)llHRob; zeLjDYpI9OO+8u;5ft2o`7~H4^19>h?Z%*gr;3x4pTXJq~g-Mohqsg3` z3)s7#V(9&u5y ze=(vC424(Nl@D8Wn@Cv&DoHuB39ErDum2|L`@VizM+TrTIvgx!l?@=(7G$dcW%1@a z03Tf6uzzAfu76E@k2a}w$O#Ney_rtGzfX!k8UtfRfXw2|`kX~zC~W!6{FbNmA8L#D zL4q7M#Rp8;^NAq^;6pp3Lr4Ih4BSb2GCKO?UNSlBZO*epLV2j@p#MVd5X>RseL>S~ z7oJ^;k4jqs5LTVLNA~ap8A`SQW%8)dC*^dbYeTy7?^Xcz4k1)Gl*4a-D~~Th?pZkt z0b`K8HgC#nUi!tApkkhH2j6PO{#y-qY|f|Thcnmw!LJjZPtKRV9Nu&o5MCb0!B{`$ zj}&kV>cdfiIEIk#+6K98d?3KH6klf*0vR3KPJX%PMQM~NTcUssz}|@fC7}Eq%(K!! z=bkL}G5=?Fq<)VA=%8nzF?l>ru%b*JE@nx%Y5EZUrs#uj2%*ZaFj7Q$y$ zaez29ybVNGqJ(Q5qKQgEojavZM{Fi=noDy^twqrQYC~IB_S{i6jmVT^K7wBN!12yD z;S7}1{qkUJL}@92B;kQ=q_l5$VJx63FO9$km*027*rJurczD}u&__9jNu;rl&SiJC ztwjN%BCko|jV)vczLu(I!e%0*^NqzK*Fq<1Pl_{5h`@sw40bX;q-cPXdDzX-%u@70 zwDBKHx5JrV515lkPJ3p!oO5BkW?taA>@DGe%60*phK~bT>RB56;lGZ}XRqV%G2i#t zXx9n?H+Ld1=uv$?KX5HNdgnYkYJ$((yb|He?#+DOL|v;Fn-}L2&N|IG9Z^M2$fjj) zy%a{NZV6PL^I>mM2I_xEZejbJ3){~Vm5J<TO*#B4#{O93PZAQjqMQoEMNuiTfB=_t4IU4u%TjI(@45GrbdcB%M=qnfkJ} zJd7KS^FHtGYqCnCg(t8H8C0euW?xR^=5&!beShT6$g}AZIfv+ucZQt49TY9OEsefD z`x~MB=a|UhLzq`OcsMrlGNJgRdCS&==$D{J{+o%AOhk4~uXUz1JdD{z&$f%omzo@q z?AGiTN<`WN>fP%7T9HJ!KSJ|If}e`TJX_7~PAH~=tZeLlZUmg-v-G6mhc%H<0VaG!>N8XpVD3pRBH zB@OpMXW~|`Za^L;JF?3)1*oGAw222G=pz zwb2>$!#M;OjWYz7rLYzSav5&XehZ~rz# zd&<7>Egb{^=ncJaAVqHn1^^sw=l-G~MB8J_o5ZbwpEm{w1f8R& zfl;0y;W!vnOaUot^QZDz#@Lg5tKmhy=GOLu&i&)dT>6OQD4VP7zq(ppuj`cw-qb7% z5!yxoiBQqX&Jb~v;qIfwE$3_zxHx3>N+ONuNnqLS{daVi(a<1Q%V4}Yu*JTHw2M?@ zzlBsSJcjC>@2fQB;P zzMQ}Db6c7!!Xs1dHFg@ckK8i92njDdSDMCn7ghrnL*fSv38wL;wWBX;17)-YQySC8 zd+Wn5ghvUq+=DOdAKAT@ZU5IKEc;_EBGXF9khAq(*O_O**~t4+w1)a83!g$hlY)Ob z==W?;qWQ-%eu1!6_gb}Y4*1iU<@67+zxbW8{xm_hShWy;%c@$Mg zca5I#F@1En`qC(y9e&0>E$zM$6jcv?35Kb6>%~Y=={_D9n-G;G{E8D))s*T$%Itj^p)^{Q=v9P{RzkC(f6l6sN`L8eIP<5F^jQT0LO{6h!2>*R z-_9}HUeIYkI$>rWf!A|#GW8<-mX=Kc?&Z;W6Z~|L80hdK(!Bz`<*%x5H-gII&-wpWAOxw+sj8~^#nVgG$a#Gdtl z=?saIJq$)uj^bnzJ=XVD-*7+bzKbyzeU!25!ASfWJ;7Y|41d8mY7$>`Yi@=+s{+4- zF_)PWJBRXp4QDIYSGsYqHy2fEZkG?qK0?{w`Vo_Yrsf|BnJ}tFxwB zpnMZ69&91p`{O`egHz^CZ$Cbro|C8Xw)O-YprIrZi>t}D$qC7I_p-OhH0g#;Dpoj_ zBceA_%4gDEya%;=JHj1KT>($1n$=NU4w}C)=a-%85>1p*XeSuky! zMKxN3vU|V!Q1oD4JAFz-M-WPiY4`%D4wxpk@9FO3|C4{X{b_J^$J-tB_ z*=I$})S!T@D;6mZVLmY&*bF!Y)ZP zlKN}}ZQQu;nPuN0%FQe!@=hS6+V34$a)J8M+^F=xLIyBgR^_8I06Fbg>CcPBWmTq% z)@jPM$%-S^Stc5M;V|;%d^9>7>^(2F3w51eoaTLZ{OUVb5;j7Owy+)=f{Z=VL(Hd2 zX&5>G54z2&>jdnfaIS^6xyX}(=Nj=Yl^J9{JblZYBZ}|aS9AQ20L7_UKQ9=~^jTU3 zJxlK-Vz21EV_6J22a03YMY?AS_<8n$UE&Q;5j2@rMJ-9s&5yl-h%SLiZ8!FB+?<3& z&e-%k#^{nzQGcT)HVx){KWQAaWa_jWFZgqJ?pF&)0hT^O-2Sls>plPmblcBq02uj~ zZcy16-MN~k{O!>2Z*4&X{2#uF`6h@ZF-wuLKCuH6T}gGKB&9bd`(E|k`psSZlV7+a z%;3dOQ{LwtqCdL6ED9U^)%%YXAOJ*kE=>O$8DZ11t?Z7NP%D;lkN*1NA@Ozp9-z%% z7#il^AQ#)UxnVo#93@050`He>z1v@VVN->)@2{~BS3f;BH#9>gOlX9C{-Ur zWLx5Mi0HM()2}#o&NEePa3~597AbSC>eZfUPE54UkDJhG*g* zE!!0B^dgC%B7aR5u%wopNTs!+-myn!jlxHpG`77;3@rNyfn0_GESJihJFVDfNF2?iK7=~HI6wiLn{8@@$N?I{l$D{HD#!ph6uE`3W~KK&1M z4QPdOG{!28p;u@dDcVxz1#}0kR2355!)YN* zrfQ`i5jYvCvDf>BDm= z$2e#VOpBHjre0Gid7*~&tq%#wo*8BsAwZuHW!>Zx8|9N=8qbOVn}|gHZs*pdnsBd} zc{Ns5gw;+U`axrHVr5ONCy7R9#jcb@aa3lo>XvFYg{qMDfLj3xP9z$C3<%XVg+A>0 zFqi@#hhoRBTjqz$Rq1NHoZ+Du0xUdy^IC6|tu-mybUWOw#m0lcBzfIJ&wW64NNlcF;OeQ|i%n z-@@K{$2smr(}|^8+hl3mi=^9K#SyMrp3Vnfc;9;wa1hz}*y=v#SJM-uG<-9&?$zE`2=ce%&)t+>}&ijMF%o%znIN zD96w*ME+AA3M9|{8=_SQrzKM60{ymT5_wU2o|lsQ-gh%M&v&}o7QHjbD}3df#yA(T z!Rp9hFW>MJYdN|8Yq_JiAdNccZRk1)$P9nc%A^XCsczVA3w8+|3X zAYBB{lP5p1j+@Jy_N(?5m~eA*1G)JZ%T+_5g$Rv4Az{xp24n_`0T9n&vnT659$niv z`iM7}Yg=HeepvFYe4;}?j#d}Z~oiee(`(S<~v4v z?pW#1cB~UTe{BjV60fOKmdj}s-ElQR3X?r-=%2;Fhe}lW#I13bn@AC0vYdlqDir`2 zkcc-X0?Pn^okQN)Be+27`f#!aDE(%FwzPo>29j_zW#rJujE`2 z>Cd5Zv(MtsOb821cg;kyi&7_>Mvd{PZNbH@f4sh*&(3jDQ@UlvvW+Wa+QN!yZ;?pF zS{^-A94Cw~?)~)=yfxBVOxe^I2c!hb7x~wL@8+r$>I@g$2?fLzrOW?6v3c_Ua-xp@ zb8QU}H9YQwvBN}8&Z4jraZVzmPEMHUtHwtn$z(Uj?T3XBFL*ey!^E~aC*S_Z-gKSV zZ=Z=Vj?Q68BtDLckMX2l+e_!pkB8Sw$$jddbz~=@kyH?PVD#$M!1rR03qnf9ua7^T zAGDVIB;*O1a+QBoLmrDi1z#RlbpKeQ8i#Oy-|lSv4`x*q@33QTP-mWHKdeak%!6^% z-QmkRxozF29ox2#hg(O8S+&G^akAV1H{RhmVn}3p@l<(Hq#fpn^L9+lXq~pKRI4c~ zQRC5b7-phW;c+?*8^;&)%=U z_gctJ7)NwR7=?4+KkuIZ_GQP)(*eCxatnO?o^L4RxX>ynT%sFyD-x-txSqs z4Is!88^hzcj9u7ycdmuR7#`nWMdNj=-Nzd-G@L3gI=LR!aNG4dP1v|K*J}QUNSM&^ zh}`rr(}Np*O8B5MjkPu|HCtf>!rku|nT&C7Z|f_>K602xJym7Xaj+ zSVGTxrPQ$I&jDtZZ?hn^{FwC!1)`ouW zQw8zm(qM1e^B)WL)tqaa>+v^EU<$kmWp~e`c2jZATLbGgC1GKj(D!cl6?Df7_q`dh zrfTCv`>{FIcBP+AER<<0eH)#(1LDUwh9*ZwpYTI>c&_{~&VmS(Al zLT_jKRurI1hm|J5rFy#~43TJV+~o6u8WkQRGamREE?S8% z$)c9#-$$ZQfTB0M3f;#AG(nptKV{IsT>{#4OP9B|9UZ)Mf!@qke>>2?7I-gSpLHxe znEc3LAp2CJ7cVl!0rF5-@is zADf#AjmLEjbd_F?nR5ray+3dwA}O+bf?rMR+SVg}gU6|^X*2ri45764Sx zv}ms*8N+TujD_?mt@=#dDP0Y!w3;o!=L)5b*(SL!NsTFZO`=h*^MVB-^4#fmkh#ZKCrOxxZ4xJU<~ScSXkJ3R9&q;`r<`75txzk>wTY#oOKOy%8yY6E9=X6 zYZX5&s=5nK=sna?l`3URkz{R{I;x6$H?QkrR-sNG!j&`a-wP}l;*W8m!tb$RhX$gH zZ*Sdsqf&M|I7a1^9~8!$s4G)}>I&Z4`m&51RaBKaOtQA6NC|7DCB}j3z=8Cszv66$ zC2?A4B;??n2?4076=%D!gt@f?y9mM@c!2aq+7k4YsC?|dp{`Ux1diUyNpw;?>H11< z3&5Y}pFXKdc1UiH^fU$NdUVnY-7G5W*~1;4F0MkO_O<91Ohg5pT+VY8)zq4Dy;XKFoFrx%IEw?dxQltE(=z<~&@+T@v6N zp>T^rDud@2rmN$VQzH&PJl;^6Yfcu+!X<0XF>OQh&Bp1umd^Fl9oK?SbZ?Zqc&{k6 zsQuqhBFHljRfPiS&&Zd1dq?%TaD+Ge`*ZEKEX13-gKnA(q&wJ#DpzNAcw^6Lz+(i||T@YwSiqM=VUok6BoN zsLgIur6p4(uO&N5;`hrZzpdqETK}YgHS9Zws#&Y*S=ARb6dgtzdV0ltHZndZMkfqZ zr{s?Jm}kd+8@YXZZSQh_4c)p)7+ew<(cyPptL{>&|CVO1UUhNRsC{*2R{SRkAn-3n z6l16DSr7i)o`X=dQWJ10OGd!Hx`BicS!$nGu0s@^b3D`BF13^y(a@vM1>=rp9|P0S zQjE@S3xARPPY_oojZTB49`|)8_&=JN`Mo6Sxc`3x{Tg_i1fp_vT7E#(WrQCDo z64WND>{WsP>H7DpwT~(`p-1%8Sk1UNktejW>II&}pU8AyF_>*Eg~nK|J%o@V+_m=9PCFcvG|wm+3pD4BPs7(>F-zh zjSsthXA@`y)7!O_X4{+f4T$omzyhRKRij)LHh*=JnfSwgr@xV#YX>KdB*mvC3j zHPnr+9eox5wR&f%rOClO%-6oARyw(F?n^_X)7gL6KeH-LI>cPew-JLXAIksAUq9BD zGuYozifz69OKpaLlCQhCi_9vM5v%UleBteIW@Eo-7M-4lYBHdor5Lcv>X59_F@XGkW@laheSEn5H`)z547RZ-g|*DBM* zQv~6bY_CTooQbxYbFCZSVK?M%6dQjK80*SAo&oAtF*O4i~ZtiF&`H`&Aaz72$T? z2q(}i*FzmN7D%UuCJn!4xRK4sHei3(tZ#C9NLt`g5TAZPM8H5Tp;G)BN|}Dl#z(+7 zP&Pr7ejvUen0g&v=M(b7v!|p3G7y*~L`!oW^QV;gUQ>J$d72<9uq_D7-r&2}(Z=Ru zcl=!524w-QKzz*l6fqnN_s>Gh@{?w|lpBP&2!mFmsQ&GDZAn2Q18PuhRl|8+qK$j{ z(N9$Et&HFp#`#wal;tD?w^U?d@(em zE<_J1j&wLSXvdCD{N6rh+i0RNj{Zabju@(Us9k&#Rz*&htnpn0`{DRe=|jlP%v1Sb z%Mnn<7X8$^ff=V6x4Hwro7mtsjvuId?s5AzSwW0oecrDBPQ5fS+Z<}2eCjQHMgU3O z=g#Ng()gVh;TxykMtW$x=J}l5(v04+r4*Cs6~yaygoiP?HOSg?5h=5?mG4)}Ho6>M zyzzDa-s86yhXR^XTSaqxXR<@KCMF<`#SKkQICAT!O(DSjqWJzC)zdJ?MWeN|+4P;; zYTsi>*=~J+b9X0x8Rx|JD{r9|jo>gxt3!l_Y;Ok54aa8DlpacDCe97{Xa!1|zn6Nh zJYTo@-J9*uCCFhAn6lIS-K(udD-I`c4>HQ9H&&)71H#6gkM7%QJGBWtZDJZPK-SHy zZs^KR|06_xT*z1XDTTgVrHJdHcH@iq8o5R-DsdCkZtbnEompMKCN1^Zi-l_ip-N7q zfE}O^2QVUNtb!M%MpWb$TDNT#;IIw2+p04OZ`dAp>Qt=Vc7kY!|FrgYW6gE}@>&bF zHwXka)8@@3t2{w|x7F&7fSC3KlxR_1-N*ab7niV%KT|XEXM=e)FD@)H6o26hp7gt0xsVYf;yR~&uWXROP$^CHW5~{Z@L*_GTKD+F=M&{x1nmU1A_+%C9phin6 z>d5`_dfO^UJ-OH|+grQp=Hdgj0bt3g2mweG>8|j~vdgh)27-y;him&^E*}mb{OzAM z4dAQD7T$oRUC*2AU);m@ADnr_Kc;7{1N6RQ#p>D?a=gp;E zzd;d}@GX)0M~uN4zW<)u_&1RcqaVJGaYzs<7dH9}<4|6o0}=z=EDq%_T-Yd7IwZVy z{4#H1D2ambErFt#?oWpc55paUDh3N11>yIcgnUpPyXD*lXZe0{F2=mMl6MJS?$ zTQio9iBdvU@XZLkhiN1tZbc-1tz5dHi|E(N#C17)uVnpaR)5!-t9{NfH#c9Dr*&NA zNA?P;WPwPZphV(bCkgRowKL!EA^mST%!!VX!ZNh?4F9WZoL=UZ4Y^r6Mj`tWgLxWW zLw{#Owbj+j-rK&~8v6Iv~D2D{7H%ml=t6e zX`MZL?6b8#m)>HHu+!bh7UJoRtA?Za20Ahl{))vW{IZOnY=haRjC2Y6>}@=XZ*4*H zAAl@%O^fuu!*4R;rg@PoxxX7oFGy9K&Pf?c9`h!i z9=lCE@M8orY2WyND=NzyM96s(Sf+J@-4jw5b0T*CLSf6w!i|RZFHg< z?CebJA%qrU%$YEVt_@>x=&f@%pu4{s^WVYeK@P-vAo96LL5jq)yoY1mxkdt;P$*rW z(`!x>9EpWQN5ba;%QR+VfS@L{qIYXdbxdWAYZ0CGjKBs`s{K9uq=DE`DTz3V>3+6s zTnZ5i@CzZJnBzkJU@#?y8i*kMpj0CeNdz*?z`?L~D{Rib1bvgKyS*tv|1=G2Wk+!w zo}e0_20um`8e?^rK39_wTyMXmi>+z%4Gr^+4Y8(z{2(g!13-?Adf?b@n$jpow7s)* zI`|XVGJeA6kFEm7mMx5evCyuwmhQIHD5Eio+Rba;`Q(q^MIsuPwj8TCxTZBsyaN(h zb8sql*>>e3(O1T}6}&$_u8Y_;MjVTaAG3S(aR+`@4~=!%by+5mkJzISNHq>S5X8<& z2$j%?I6lrTq=Uq`Ob0pjn#1rBER4(mbXXFi-r&0nLzllna$`R|^AomP4qy8i!KruH z05HJ@cJ+2x1`9tu%Z-J68@fztmAG`fm5VE!|0W2Kr+PDs&35xO{6Cr1Y7Jd*u%@gL)Cgp0g6S-XOL%11D&^h)E6Wf|%Y6ZPMv-BxaL6_f ztS)F`l#Xcz9LPQtH4k&p>u<3b?O2<+Ao<>EDu5N8^LTmV3D0B5h2v`k4WM35H} zRS=6{!yBi|N2oC87FJ>X$AYWJS4b%;3r-VM<=LPlU?ib(OR7A=WU)^X2In!bAw!tE^La+IPUb??}!|+GZZjC$U zf{*Q3LHcY?8lWuR;)X?S7=?2@=>$TQ0R!xNs2%aj@?EQy3gMS9aet; zVTuFui{U^MmEqxDEgvi`RR??sxQGYY?);J;bf+r8L~z0~)n0pBR_156R?m$;uA@u# zqW78&mhS3eRQ;{3U3(P+R4`i`F!6r+=QiryvmFOT0p84M6=gpD;)LoDLLp2g1C|kl zeeuwRsfzisWbx`u&LdB?tgfZkoQM7nQuApVk^)B|XTj-%>4;)-j`ey0kLzD3D6U;6Z-PBO%O&3U zL~|Lk@KW;{&3&dgSC;i`zpJIXxjC-HboLvvegJ-`tR+zVA&t7{owwXnom_0^ZP>YV z4Je}cIGcY#JU=OZj_3)`dcqZK@krTsPxy=Q3Q;P3xL~z$0O{R(DCX_g$M!ntK@CRB zCDX%4I+A*QKs@Q5dXeXPlL$zy+noIR?H(wv{A*J=I`(m{kkZ1>P zoMHu()p}QbjCS?P(3i4Xb;yV8GcYrI) zB_c)Pz|k6I>A=h&4=X8tmzZfYco=YsfZ*kL9I{a0SN)AnYeE-Nh|v&?Q&&|oCsNws zwi}Cte!2R&y5z;;Pjag$a6X?M+?iEFCvOR}UN>_n;G;y$@8U-R z^bycAP%2>5KP=p5dYjg%hF3pNwyiyaE>JfD28a|<-FZ>iT#$#-nwCTanr#@9H7lia zqBwAq=w1Od2h@g&9oIF{DkqA-6|=;c9x6x#Tct%p0VbSU6mETtpSiOwbX0aIB;4rc z?PhV&+q={@Zf8gKth>eBO8ZaFni(=(5*F-?_j*RN3QbOf=&*md!qpOCM(0V_<4H7~kuQ&so0ajxpot z60t7uP5Mk}8vb6_LXZXd$`j)vbnl^4c_c$m|0g zA`_;UYP+?D+lhdhS(>*=`as5Q17YYYzOBzH@Pm{%(2xk-<&}iq&RJ-H4EIw*n|b}L zN#!&96xvti;pF$^U9EHE%%Qlm0&05@V&f$@x-hF$X4OnZd+)8rKd}l zIAE_DB4I?@%lY%=Gptf^t%panxa+cH!4?G_iMwrGXyA9jzYl?qTcj~ch>WLZ)GgUI zDui|B~uA%L}oOcmM72-F7qZ`cYv z@Q6?7SEx^7h#viS5+WjiBXA2^?Su9+^I{{*P*l>qd7wl?Gwqw%QsC6)w|X}O2~O4K z?Y#3^;U2#vsU3LDLg@hSvYh)4v0c8^MFy}e^dQAgTW zAGf2%&gYdbrkPod*?3vN>f3tWroL^Zv3h|-50JbSsB8jZUCy0eY;rlxiN^DgiFx@@5AzxJ!W;K2o5ARTR>igS=6 zYJfuj5cA}FY+&qi5D#pBXN}C@sDWnAyhyn|7_mMu(D0#uP8KQE*JqIScDHu$$LVgg z-%Mad=UlUMl929;P^p4t99}RUs^=n(M4s#1>m2KkzHzKKqr7~?j}a+cZT*o)nq0|p zP{XzQ_E*Q0oP^32*gq6iD^ z%;p2ZbEOPut_;bhdOKPp4kT1KYSDcw#dy^Svh|o*Kj;56Gy1td=voAH~dLuDf5ou!0 z!}Pk$kQ{mRLB;iJmS3$rfchcuAJF}da3~52!$J*euDAA<1iAE0y7y3$FJzW?vx=ll zt18pEH8U)k1xs?oV5(mD>9`LPt@M(D@6{Lf)Z8oW$!|IOdr9(_;@Y>db!>0M=@;?K zcah|-=#!>Cn#$Ur41O$eNXJM+z0P7^CU0Svr9yZTwNL&|PF9N6>G!&Bek;+gZne?< zQn7ETCTY1*-HQ!wAf{ct3XGrUp`igl%X}uo*Qz2tUs_T1Qi*5tG0lpU?_M!@IS&Pa zO{~eA_kwc|dQC>E9>bpZcw7->BD;(*s)3hv>n&C97|%E5w2;#pQyS7&A5! z#$UWbxH90)FzmcWvI;tmn!LV&+!QGCcn;-ZYD>Rv9j?amnH3CGH zZ{mqcVo|L=%U}J0^TFoc!@PPoN{h*4%VfqY<`}8xs=;!<>S_9!I9k|AY6wlF)ki<< zb4^R*Y}=k}4H}H4DFE6$Y+G zMy@F^w+o`__)(5%AfBpXbI3?%wDE5-s}Gl78S6h>#@)r=8YwT%A;D9- zf_HzL4B~~IM4lVKNSb>rpD-e6qwP`bQI9ON#H%PG7lHfD#1qQzUf6D~|S)VQA)`;I*{2 z?amWNy(b5vS1--(-DcCbA^1qD-wBUmrGm;>}+gQq1#l>9o6tsB)9$D<~S>D`x_fHtynla8<3(n zx0j2}9eC4qChbf7Prfu-;V=akF9kILY3WP`eS$C5Ia^37uGmKAv&1QIWWg`1$)eE)~p@5*#&%2tkHh4%)?0M%FsvWMa` zE8VImVJzxjVTC@%8#W1qM5Qlbdq#f7CRJf+#6ovL&NZQ!%k8TwQ@c1d!;9DH#`t1D zGh=P@nb7L2K6BcjCXUjLTD~~jJ*#OFU!Y=KT>`4bt#zA?vh8Y|jGyF}%R!Eo}`Cs~FIisLGy2 z^t}j@PZGWO;nKR(*W=G#>(5wk>w-2#U_9U}<->U_C;q}cS0d^*&&G%VdxWyEboZ+N zPh<&k{{0HSPKrA>g@eJ!`NC=+NII; zMe3K^M^MtnmYG1;6wlF0#GLn%sv0TrU~8{IcmZAQ9Z%pKXR(YNfDNk^2jM2|M}8;% zXZSeUn;eab-U<88-vpH98ppnOY5v-3qvhdy|FmXb8EWRHC94+!lTZgNWJT3IEetV0 zk(%Vay}EyCPSNvZWlEEOLWYwxiihz*BcL)N<)ybxxRbRi_tt;VP>acZxNukOK` z-Xz4nD=1bf{wNyb@J_tINj=6tN&%Dvh#++Fr6Y58BTt%`NXkM443)dszVlvCef>vJ zZ(+SpZN(FLzMTw`!SWqx2V-sumu7HwpZ+lRV3Nc^+wA5#wpNEPcav_XRelR7rstr} zk7JAa3x{x>@d+yIS@fvzjfEb-jDx?b!6}G(&w1L!5$F@W|HdEND}g(4#C4}UJG)$m zwr}yh=`n!0fP5C77!0lGw7-^;z<$zbxy4r1z!(r_D|)-HCu84g=Eju1LU#&AAT)&D z7XhvaSg1+#V7d#Pc&!`5`KK91t_r6!%9jregiwQPYYLu$S!4r=vGT{|vk_fbEj8lx zxoT;a!($vV-q`+*pi?)uB~*S{r@`AGiC088(U%yUW{xN}=bu1-F>somXgf;ZrrSsF z-S*oKJOYVINQ_h0zB^^Bky{bjd*ALnarOYvhn141hB@9HRe=YA>hN~iLc_P?<72iH z3w<#wV>bBW`8Mlke5nJR)8qX#w|&)cFeDnIAA#C%x2{{evm=7C-SGHP{Em)3{;UI(wAZ#hoR+@? z5xABTOs=J0Yywz^YzrRSs-^mPXiF8)B6xjYQ$SUtw4knX^ShfO_6vPq>UX}s)tj{u1ojJtL1xzi`Q^ql zd*KN!_Jg_WGwqMN`!p4MUEwTkt?^$-qrrXAYWgMdQ zR>$*pr)})gz^z*qpWp)PDv>GHYTt_!A-&eKw-lo?6H{8V5tamD~U@$<{P^ac+ zESa5R9pMB73>;^>vo3u<`(eUH^;UTnA4HFh#ObJmZrSm)Ze?7~a!lc&2N--0|c3nZ#Wkf3m39+@v|^q~mha zi7>eK$SJOs9jC`4JQw?%GrGv!yGxb4_U>Qpux4K4o28ht3;43*oRx}GAJ!C{;{JOG)i`}xKxh^fy# z!!md8uiN_jSHt1@Pa0syb$6*t%nsKU@9)9P;19>$jgW0{hit{TMZ+Lf&sz1{8BX4{f(Z)X=DH*H0fx9fho~t?CzApto9DAir$M{3=p`UI?PUbWn1ykNCI9Ckio}H!)mGptS!2j8 zdJdF}-wYYpO|@LB8uiUrgjX`j@Z$@A^Hk`lGT5ke(f2Bb+`fY^k_yUok}?vaDoUpK zK;r>7q}9v6)@`6vG1HAt@{HPUsB<5JKplyuaAQ?CShf1vFo2~3W?V|W`Vp-mbO_W; zS&z&2o!y5P@dg1Lj9*CaYhp)|N|W1($*>kRqw2*GDayza3M!r( zV{4`?&RbM1Wi$MHC3YL>v?4b-o}4&oxRED#i0|PobkS)owa`Dw`SKS3A&YI{CtHS5 zuv%^JsNcnLtp%eEGOI+7z&q zADY%V)-XVdX~IOs98y@4a6)$%3sXBW7`IQ?IP-WSJSD?KlI@Xxz>~?xX=#hsO;tD4 zjP=)bq{py~==T?bCl>2MZj{;z1V#~<3jT3&h@Yb>R7XA_EOmkQl!dUFYQetG4TKN~ z)NwC}!zoVH7K>7Z$$?OA!A6zCvsH5mZhlMWGbG99+(E}SlTP0FfggH0i2rc#y~!au z7)e-Z%2J;~5#7b5S)HUSooe<>u`n|7nv&TM@*xrJ;joN2T_Mcm`a$+29c&FFeceY; z0KrLzU;HlDu8nxs@Ou|DojFDjKVMZjo~CCj+Tn>fzQ?3rMg06dd=r+8B28}gC zIxaFu>QrrC)Z{XT&wcPf7Ml}$KW;l1{f)V#2NelL>n_HGgto`(&>vA9z=t#<`TafX zOWlN#eg8QWi#a`}qGPQ&H9>QIj`6!}Je#1Urn+&MxVJU%)a&6J=cJ?ScJ@645RV># zR{eTPTnRyZ{+!uQv9ezo4}z^4>@vD`&^(3mRxb{OWUGsE?%WS$Lr0$QO2kt2#QWCH zq4;dweyiYR_$dvfF<%nd49F5h%l9TFYWL23ko-kkviX6iJmN)<-+$GEXXTb5$7Rf5 zlI;AaHOjW9taekLQ|$she@3K^lo48@?DU zf@r37fiKz5Fu!hib%ayrR+0RDu2K@c((k5P-onAucx>~2Rk(B@rCpDrYec?W>76ct ztzX*xUMTRbbZh~|Qnrzlz&JeYpwm5aw2*5ARj;9)VXg|}@L!HW#eECbKWK>fx5C=ZMIOCb(`RxMM~NUT?5{ukf3PseaAVrAKx z^WSa6uG(z*Rw&}aU9hQSnW1zZy3R)z!vSweTsYij;dAQWrW@jND)_8*bfg~p+A7QP zbFGuWC5nEhCt;DBe3gEHocBIOpssT2cKhk#aGFQ*=?AWDpSQJ>j~oolWgup`d0!jQ zZkC5db7T|yThGw?))RDyI)|2ZQeVQ~rI7+JQW>mVJRs$rD5d=L8R?WOr(C6BXNV<~ z93@1Z&<&@hkvfgNV=^^m()FH8An@=Df5EI%zJqgr66NFzw?>)yr?SVT9Bou7Pe68b z)K2D;&Dq>15;*%*U{C;!v6VAyFmaQjY<5=u^{CeV1?^)0SR@Y@IK+A6s>_zB z$R-8Ib++mZQus|cz}fGENdA-Mz%{$T_ve9A0s&I4V-l&TF{GXFOU)GJv*Wv~ZLaAQmT^grN!6}FfsU$*A zeHTee1VE=AePk6Z9GsloQD+nuc98vSbg=LR)?-a>ss9n)<==}*Q2A%fBmM#o!P_Ct z%ZGF2>+_88VXFAo|4jIU|0lOyrEh;eqyBj5(0BudqqZNrq5~|tPZVCG)3!+aW!f~%H=U5@vc@MmnOF--GMulEx4 zY2*=XofNujekyYJ-}D$JSz1O37JZ{M$Id$*AG;=W3Pc|hpzuXxhRXxBolV6yr_Fn2 zc^P^XoZ{F#VU!fhNR0cFmm7F0C?w=of`HxbVd2;2=kFILeGoQ6*<=N|uYS6k7CRM( zNO2y47L#7of$Thdx8vqA_*5rO--l#1KkOhG{F`MqrzxgF4_z*2G>NW(*+7t1eA(ni z#hCWAgI#$v#TN|;K~{I#($Hvxm(*{E{(KA?rl7B;Ml=6tksbRBA`TB2ri zB_9b)tOuzC*bw((1l0qJ+S@n*$|Bo*`1|%tt$O^|$51(IEe}(HCjD+s(`_`o;QN`44 z`To$6^x1SgRz;4vYwy^t%lLX8%gf2k(o&fY1KLyD@m{>^4S^7jhQzY2s4){GKbV_W z*dkHAsj1IYD|Q!lXkKsB03dyS^&TE50CjS0>dLmg@83{gPM71iog(KzO5TLDFRPgi z4@Xp`pr@nM z>wj+ad;{H9#@I+SloOgdD3JKrW10?Jg;cb{8Q3KCk(jY==j@-eULkGS2Km0bE(5OR z(xw^#>H<8=iah6MpY~cfBaAhh2LiWPBHafKo3qU{ygs=8IFny7adA%D-eb!QPPURrI z-#^7yQLMUMy>d$oNIegeFNx+`+uE*$0Fe!RaV*BT1bGH^3!QLal>dA~(@k~7(mG(M z38TEJfdGhG&o_txtx^J`vK@~xAQeF2Fc;x~1b{I_{+=T`^7k1adHkBWBm^i4FI5(X_{3m0;O_(m%&5g@6S3sA%~bMqsA6BjbpJyHwVx?kkSrDmkSUYh5>@_`zX-IeS z2KBPv#*pY#<__Wbx-0D;3EPnB#oOj0w@qg^;_aG!dAsROY2kjI z*8X5UJ^Q=R>P5R`fR+%r1UKWd!5iztbWz!dB7!jP$^{2%R(kV;^!upWC|3w)S(TWv}rL!-=RuxLtHzPf~^DcD4Mp0@jJLA3@H5`_3n#%dP zLW-hF-nJnUazfd-x!KrkAT?4^tkf?fL2&2uS5@IMIBhxEA}#ON35Q2JweeO+)$tC( z-q1|MKKedO87&!e>D=Qisf+0?`w!rxYn@3_?*`P#8!RI4(;4YIhyXLb${Dtih8v=2 zYJ8zc-V2gWcJ-mRz!1w#I-F+m#2?-yb%`w0x{c+g;zy{?H#An$q;`#;dVT%zjY8OmR#^&d=>lXgr1C3k_44;W3WPK6nipyNB zEuyRQ3qpl{`aAsQ+Nl@Wo%gxis0NuCpbZ7=YCCTAtNOnsesbo8t+CX~2w4E`IA;v9r{YHn z3?+ZazM`KaWdFMFVSafDu%K+fkL4++s_&bJhM9NM^ev{%p_UDu%_Uwk92Gv-2w(nd zO*#!;Vb^KoPBmzcuz@!lolj;kWPIKR~5=XmELnYY)*%$ZgO=!pLIa6jym| z{=4TFcVRzvW*w4*p2TlRpf;sEGWqVN{XtQmd=im{Pj%CdrkCVX8lO|rb_a!loU#>mFBvWxC#Sli`oIEVbYF@a!?z|BK+0sfyRZoT^M2c=!sqs=&aac&A|;pO5f6L76OOPfE(}PQqVRM=e94Had!Ar|dJV&y`#Qq*8Jgh_=%>E7Kfy}J5%!GW+=43lSMMpEVm|cmMN-xn` zIw2E5z^KvZ@*K$G=A@Zd#er&f3b-2BI#3P)&VIB(_YXlQ=78rv4#edfxxx!~ zTPLJ71I^E-9a`PrgtL8<-yovZJTTgcFjrd2SM!J^GfBKS4|%%y6DzCDP`NK5U_cn2 z4Ss$BnHj~$=(so%$(dLuZKes9`m&5Ze_2ezqEGa2_*6vJKDwpFu8TDI2 z1}*okCFZ-0lgVSfV`z*E`6qndbMR@)Vs|Dw?Ey*@P;F{FUnb~)+~Q(Pi6w`B;#rb}7h|CL&>s`=hz&uAcPHFmu{3IKovZ zG-VOmU8j3P3%^RA^4jr0#4?>Jt8>Wlg~|g(e5@?7ph^zs^rXr+d$`)`M7EHCPS1dA z7xUv%y7%w)&CZAD#EV}3zYs3JE>N=lrHGqVWQx-fH zEr17RZal~|ksH34za)abTFq;K;*zk2rB!FZF1gKZl7~r`e;eP-`%YA&+JP(q8h{5b zf>#AaLQt&9e55y_uqx55NnAuRqz^g2 z@G5j}4!hrnCl-{~+xL>uls`Vb0fr3x!EV>ywJ)Zbq+P8PCBsA{jSoOqmtS9J_>_Hx zKy}yCQO;l|rS zL}KSil2;D0M&sF_<{essGp$J6;2z_R7huq$b6uZBgK<3k){Wj>=cfBJ&hv7ZS_LTe z)%13z@#zbe5J(VK3$*b39lhA8a96Tv78WqQCuRkU0s&K)XF5#!4%qy6wAy)Uc>>$e z!m;;{&N1}Bf-;~`C`-RMpf%y%mba_oDDKQwi=kAqYDgbnf?*9aXe-NTM=Gp$(ugcV zDUzk$DkzkjP2+`{BrgmL{&?8!RAB2(z7Rs>9_0tyB8;Mp63N|9$7bY{Zw?`omq5z3 zX9B3Pj&ysb$Fb}5;C8dMsEYT?E>pwQ%y{*2JUjQ%{WBe~6`nJM8U>=cA;ui@OU_gy zO9xC8h-I1IC{GoMTvttTNWvAv{~SM(XzzN}F0|{N1&LlkUOl7ECd2=L0awA`rpB7? zYvN1X|7<2Y$L$csvjYrPF77x)v)p#O+gX7H*SB-3D<9we3}AQk-$PBHJRL z1hqq5)qV)C)Nc7HJU4B~?8^p{sa=guO{eo0*wR zfEK&A2TQ1(BkZG2ksH3fufTsVIH|^qUSqk+NwQo$2AA-H&zeXcy5o}D1&j)HCLlw= za@M4hef6X6D5Y1el-d`|C#cX-g9cKYESPAP#`4t57totG0gsjDrFt#Uv)dV7Fzzeg zR$RdMe;%-_n~)q>3mJu>iIP)8b#1!EUdDDt?`*_Oy_Q(%?u*e%onXjNfxq77%LOm7 zOGh&xBY_Y}Ja*ObFKe&NCPV-UZq26VPN`KU{r$UnJFG71-cc3U>~U-n?2YW=$IBQ2 z`(U-2gJhmnJR|gNyf&`#I|y{E;YtQ%g6w3_lu6iezIzz9%))a!2cj2y^GF=^rL92yzd znp8aq?ul35B~~8edk;~_@GCVsiUlkNkk9uULP6xKKFntWXYbHSIP6-lq`%ajn0>C{#}HLTw6HVKO6jYt_+?aQxv$UF!dnHg{d!+kr$$@6zDZ}{#uT%;1 z9B$9vjlLC@EAa#S?vbnVc_#U!?&T`K&HEzyZR4F5Dg-}a7jBvIFWZ#LN)D&%A2`cq%{|4l}Gfi5N%*fMgOCEGmqS3 zD>&*w5}vslA^j6W*caH1FR@Y}%7MR6r2+C% z|JtP6>7P&S(7Ei?Ru#5TA`KGl?udFo_~Md1$!-_OD(Qv1CK;`K`toOzi2Cgzx=fqh zA`}rBrjm-wi_@X=<9ce$(UPDNo&%<#O60!C+{d---NY5LU$j!dx8F`d>r>j^$MSh! zXoO#V#<^d6HaugY_vD15Ul_5V8zxZFI)BPBW$Kw<6A(1UfK&zvKuWVva?$qh!hmAj z$kQgg_hwy}YH**+=Rd6-nnLqe=zB%f6j<&bE;&mi`T}=DT+kZZ;XV8SXf1^|&~S$| zc4#4>XEfc#X}o;f2~1S-i)m(%oI27W`_WwD+}>BB$IUiqH!gLNs}k7?VFv9v=PwTm zj(=bsX|ccnZA$3YMZl*4K{H3dR=(XE@0jSfaMp3h>ZY zymc|gEvi;4yeDK@8Fm!|j(McdU0(xXfHMRzP1AudJY#AVZz$>pXa2k@2AM9<Fw=XRaVSHTXB!&}O0Rzly6hPD}(M~%C6h`#ak}63`M@c;bF>t6`&D#O^#_F+~#w@-#7v{83CVOGxO{NAg zqBWc7;p8mrhI;|Oj`FqTFnseBpqxiZ?}+A%kzQyVXez;Qc3V9FAa!*_$NQM4h3aVo ziY3K6F%Sw!L8;Huz0dpXDB-fuM}BDe+yKz#p(&z)88Yydk+3*@&PC79Fb>8UQ(|OT zFtZMLEp>_$y(u3SZKaZtAG`_@{8TPZG>PIUE0`2@c#E;pm9^=NH{P(IW_Y~5|ndh^H%(>YN z$fe|2zwvwwt+k0RLHT1*<&if5e{0EsdqXIccB?So8X#l>TpG|r+tQbtWhxzyu?;HF zZJC`<0&n8{Ge*lSrS-Rm&Ppw@jemKVYYAC-m}rR(d>C$_N@TV3j%eYs-kgM4Ro&Dh zyhyI~N>;J&0>jVru`{wkaO~>zJ4*40hi+MUl{mkdUe#wXp2>*#vX~vF46fwGe9n24 zab@3+`A7Bgh?&Hp8+-lqz^D6%^YJZJvr8|2gmIe|e6T7rRCQ>W`Qfsd2!=p6seUwGIKp9eXnCp)f3J-25+zgbR&W!jfkMo}UG z(rMK)xA$GzdG&Jf0SGY!>-!HuB4BzzWB0>Nl?7N++-H zKmK=B<3`SHJUs&cl3Ub(SRqudWZlHo-EYC9UB%Wpc<$uUYf1e1WHz)J-Z_PrfMj_q*nLVk;ne(=X`#nmQ?7Q z-@>T-8U32~_#6;cF|uFJJf4(s1G`RacL?*!9RC4UFg^=hYSXibMzD{dFM<6?cji#o z!o$a^tFC(I)d$1JPXH0(c?`&XKa65N2 z;GtcQ>h3*;=pB!6uH$ytybSTk*~R7&nDz27bfH3r!a#7jhN|RJ4Oq;;y>kIC^O?V> z_bjCtOUY+rF5h;3ZRQ}Lmh_&G{Q58{J5sC0YKuQ;e77YfvVzkwY&p+Ol4o#vH;cmf znhItcbORJ7vY2pSDW<4%MP@zL){wD9+B`*I!CJ_1w&=wKjg8hT#Gy~%7^rg183t>O z=ELbNS!uE6bS@a-*!s!@juB$F#P&Ow#~%H+)MRC*c=&NOI<@&?Hgd<3^7ic=W}x-w z=PKB%9k{XtXXz4Od;MX0-2+LaMto&hF_hqtw|Sc};*JYV_aO?1pZ)DGzMXG%v?24} zaeEW^!n(qu{gXCvhwEuAI)d--7EnK^^8&jlJ1cS>rbRl;`(HlvBgaChB=;TfxID|B$k0%B-v!1*c!S#7914%K}No31ECyN%keYM42rJASjS zF#KQfD1M$FM{a3qd;w)0d~KTAGd*#*FxPT(R|TacMV@`MyaN-h3X!H354+*9wKUa9 zS}wpWitJDU`aXQiE-6j!|h%#kvdMsMIgMUH>WWeEQ#m_pnH}_5CkK2iNzx1OjBv!6z~C;>_cC0;xy6cK^eV&xj>{s; z16Sz$@-4}8z_->k-tC-F?YRi;{1Bqbn+NUj&IWWD7wIpM&r3R6Ne%r%1q+)889-GL z1F|jkRj@MdCf6xk1~9jrWM(L%DK@Zw_!2QW;3Rsd@hARShY@^8op5Pw-byy`&bND+pKSFtm zOn3@DSl1Hu^Es#W`yF|Ut4S6m1$|eIr24g&%P&^Gq>>$fNj}R_1%jO7h`{@r-xER2 zaU#;)%VMA|o9->cGB|`R2~v&=#IKs)v1PF^?-vI-xwU$OR~!$l2BlIf1}UDBM3Dh~ z=*j`PyU5P|Je77&wZ_OKDkRKmnW6A7o82F zbU$HKZQ*halnz{aKZ(xGhG8`aak*L7{R0ms(wa1#{q3>id6eW9&&pNO-@2H{)an`g zd)4}viv7L(rCwy5a-)1}hbO0ax%MmhyL1nPt^AiA!R1CGNpk=+B(&u52Vb_>xDHYp zukvhPLjH>6rM9${ejy_}@KBzNrrDnEbBvzJ-%;bP@!B4KY}e%eqh4~!^rSayq7dDT zKl{y#f^D=_{gQv?$7wmz3Rq(6ck*R&^9uogOxrJHN9a-+IEH}Eo3DTC=CjX*$bo@3 z8~3p_9?3j70WdwzoHO7n{(+&?)icueYo_F;#LJ#Q0a67hS>!yADPP>z^Y1mqrxe~b z1hE@eCK*^A{ZX}9Y3du7Fj6rrfn%$4sESwA?6)6(`Hk zi<4HgtgD+>Mlg({8AkvB1XzP%000pG4QTT+fdiRlc?3kod7?f+q~SmiX6DA`f75N( o-IM%8o*ehr=#Iq=Q0BvwUBdJ3D + + + + + Appendix - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ +
+ +
+ + + + + + + + + + +
+
+

Appendix

+

The following sections contain reference material you may find useful in your +Solana journey.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/avalanche.html b/book/avalanche.html new file mode 100644 index 00000000000000..0e5d36f9733cbb --- /dev/null +++ b/book/avalanche.html @@ -0,0 +1,217 @@ + + + + + + Avalanche replication - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

Avalanche replication

+

The Avalance explainer video is +a conceptual overview of how a Solana leader can continuously process a gigabit +of transaction data per second and then get that same data, after being +recorded on the ledger, out to multiple validators on a single gigabit +backplane.

+

In practice, we found that just one level of the Avalanche validator tree is +sufficient for at least 150 validators. We anticipate adding the second level +to solve one of two problems:

+
    +
  1. To transmit ledger segments to slower "replicator" nodes.
  2. +
  3. To scale up the number of validators nodes.
  4. +
+

Both problems justify the additional level, but you won't find it implemented +in the reference design just yet, because Solana's gossip implementation is +currently the bottleneck on the number of nodes per Solana cluster. That work +is being actively developed here:

+

Scalable Gossip

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/ayu-highlight.css b/book/ayu-highlight.css new file mode 100644 index 00000000000000..786063fc4db925 --- /dev/null +++ b/book/ayu-highlight.css @@ -0,0 +1,71 @@ +/* +Based off of the Ayu theme +Original by Dempfi (https://github.com/dempfi/ayu) +*/ + +.hljs { + display: block; + overflow-x: auto; + background: #191f26; + color: #e6e1cf; + padding: 0.5em; +} + +.hljs-comment, +.hljs-quote, +.hljs-meta { + color: #5c6773; + font-style: italic; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-attr, +.hljs-regexp, +.hljs-link, +.hljs-selector-id, +.hljs-selector-class { + color: #ff7733; +} + +.hljs-number, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #ffee99; +} + +.hljs-string, +.hljs-bullet { + color: #b8cc52; +} + +.hljs-title, +.hljs-built_in, +.hljs-section { + color: #ffb454; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-symbol { + color: #ff7733; +} + +.hljs-name { + color: #36a3d9; +} + +.hljs-tag { + color: #00568d; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/book/bank.rs b/book/bank.rs new file mode 100644 index 00000000000000..bd95935f39f428 --- /dev/null +++ b/book/bank.rs @@ -0,0 +1,2403 @@ +//! The `bank` module tracks client accounts and the progress of smart +//! contracts. It offers a high-level API that signs transactions +//! on behalf of the caller, and a low-level API for when they have +//! already been signed and verified. + +use bincode::deserialize; +use bincode::serialize; +use bpf_loader; +use budget_program::BudgetState; +use counter::Counter; +use entry::Entry; +use itertools::Itertools; +use jsonrpc_macros::pubsub::Sink; +use leader_scheduler::LeaderScheduler; +use ledger::Block; +use log::Level; +use mint::Mint; +use native_loader; +use payment_plan::Payment; +use poh_recorder::PohRecorder; +use poh_service::NUM_TICKS_PER_SECOND; +use rayon::prelude::*; +use rpc::RpcSignatureStatus; +use signature::Keypair; +use signature::Signature; +use solana_sdk::account::{create_keyed_accounts, Account, KeyedAccount}; +use solana_sdk::hash::{hash, Hash}; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::system_instruction::SystemInstruction; +use solana_sdk::timing::{duration_as_us, timestamp}; +use std; +use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; +use std::result; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex, RwLock}; +use std::time::Instant; +use storage_program::StorageProgram; +use system_program::{Error, SystemProgram}; +use system_transaction::SystemTransaction; +use token_program; +use tokio::prelude::Future; +use transaction::Transaction; +use vote_program::VoteProgram; + +/// The number of most recent `last_id` values that the bank will track the signatures +/// of. Once the bank discards a `last_id`, it will reject any transactions that use +/// that `last_id` in a transaction. Lowering this value reduces memory consumption, +/// but requires clients to update its `last_id` more frequently. Raising the value +/// lengthens the time a client must wait to be certain a missing transaction will +/// not be processed by the network. +pub const MAX_ENTRY_IDS: usize = NUM_TICKS_PER_SECOND * 120; + +pub const VERIFY_BLOCK_SIZE: usize = 16; + +/// Reasons a transaction might be rejected. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum BankError { + /// This Pubkey is being processed in another transaction + AccountInUse, + + /// Attempt to debit from `Pubkey`, but no found no record of a prior credit. + AccountNotFound, + + /// The from `Pubkey` does not have sufficient balance to pay the fee to schedule the transaction + InsufficientFundsForFee, + + /// The bank has seen `Signature` before. This can occur under normal operation + /// when a UDP packet is duplicated, as a user error from a client not updating + /// its `last_id`, or as a double-spend attack. + DuplicateSignature, + + /// The bank has not seen the given `last_id` or the transaction is too old and + /// the `last_id` has been discarded. + LastIdNotFound, + + /// The bank has not seen a transaction with the given `Signature` or the transaction is + /// too old and has been discarded. + SignatureNotFound, + + /// A transaction with this signature has been received but not yet executed + SignatureReserved, + + /// Proof of History verification failed. + LedgerVerificationFailed, + + /// Contract's instruction token balance does not equal the balance after the instruction + UnbalancedInstruction(u8), + + /// Contract's transactions resulted in an account with a negative balance + /// The difference from InsufficientFundsForFee is that the transaction was executed by the + /// contract + ResultWithNegativeTokens(u8), + + /// Contract id is unknown + UnknownContractId(u8), + + /// Contract modified an accounts contract id + ModifiedContractId(u8), + + /// Contract spent the tokens of an account that doesn't belong to it + ExternalAccountTokenSpend(u8), + + /// The program returned an error + ProgramRuntimeError(u8), + + /// Recoding into PoH failed + RecordFailure, + + /// Loader call chain too deep + CallChainTooDeep, + + /// Transaction has a fee but has no signature present + MissingSignatureForFee, +} + +pub type Result = result::Result; +type SignatureStatusMap = HashMap>; + +#[derive(Default)] +struct ErrorCounters { + account_not_found: usize, + account_in_use: usize, + last_id_not_found: usize, + reserve_last_id: usize, + insufficient_funds: usize, + duplicate_signature: usize, +} + +pub trait Checkpoint { + /// add a checkpoint to this data at current state + fn checkpoint(&mut self); + + /// rollback to previous state, panics if no prior checkpoint + fn rollback(&mut self); + + /// cull checkpoints to depth, that is depth of zero means + /// no checkpoints, only current state + fn purge(&mut self, depth: usize); + + /// returns the number of checkpoints + fn depth(&self) -> usize; +} + +/// a record of a tick, from register_tick +#[derive(Clone)] +pub struct LastIdEntry { + /// when the id was registered, according to network time + tick_height: u64, + + /// timestamp when this id was registered, used for stats/finality + timestamp: u64, + + /// a map of signature status, used for duplicate detection + signature_status: SignatureStatusMap, +} + +pub struct LastIds { + /// A FIFO queue of `last_id` items, where each item is a set of signatures + /// that have been processed using that `last_id`. Rejected `last_id` + /// values are so old that the `last_id` has been pulled out of the queue. + + /// updated whenever an id is registered, at each tick ;) + tick_height: u64, + + /// last tick to be registered + last_id: Option, + + /// Mapping of hashes to signature sets along with timestamp and what tick_height + /// was when the id was added. The bank uses this data to + /// reject transactions with signatures it's seen before and to reject + /// transactions that are too old (nth is too small) + entries: HashMap, + + checkpoints: VecDeque<(u64, Option, HashMap)>, +} + +impl Default for LastIds { + fn default() -> Self { + LastIds { + tick_height: 0, + last_id: None, + entries: HashMap::new(), + checkpoints: VecDeque::new(), + } + } +} + +impl Checkpoint for LastIds { + fn checkpoint(&mut self) { + self.checkpoints + .push_front((self.tick_height, self.last_id, self.entries.clone())); + } + fn rollback(&mut self) { + let (tick_height, last_id, entries) = self.checkpoints.pop_front().unwrap(); + self.tick_height = tick_height; + self.last_id = last_id; + self.entries = entries; + } + fn purge(&mut self, depth: usize) { + while self.depth() > depth { + self.checkpoints.pop_back().unwrap(); + } + } + fn depth(&self) -> usize { + self.checkpoints.len() + } +} + +#[derive(Default)] +pub struct Accounts { + // TODO: implement values() or something? take this back to private + // from the voting/leader/finality code + // issue #1701 + pub accounts: HashMap, + + /// The number of transactions the bank has processed without error since the + /// start of the ledger. + transaction_count: u64, + + /// list of prior states + checkpoints: VecDeque<(HashMap, u64)>, +} + +impl Accounts { + fn load(&self, pubkey: &Pubkey) -> Option<&Account> { + if let Some(account) = self.accounts.get(pubkey) { + return Some(account); + } + + for (accounts, _) in &self.checkpoints { + if let Some(account) = accounts.get(pubkey) { + return Some(account); + } + } + None + } + + fn store(&mut self, pubkey: &Pubkey, account: &Account) { + // purge if balance is 0 and no checkpoints + if account.tokens == 0 && self.checkpoints.is_empty() { + self.accounts.remove(pubkey); + } else { + self.accounts.insert(pubkey.clone(), account.clone()); + } + } + + fn increment_transaction_count(&mut self, tx_count: usize) { + self.transaction_count += tx_count as u64; + } + fn transaction_count(&self) -> u64 { + self.transaction_count + } +} + +impl Checkpoint for Accounts { + fn checkpoint(&mut self) { + let mut accounts = HashMap::new(); + std::mem::swap(&mut self.accounts, &mut accounts); + + self.checkpoints + .push_front((accounts, self.transaction_count)); + } + fn rollback(&mut self) { + let (accounts, transaction_count) = self.checkpoints.pop_front().unwrap(); + self.accounts = accounts; + self.transaction_count = transaction_count; + } + + fn purge(&mut self, depth: usize) { + fn merge(into: &mut HashMap, purge: &mut HashMap) { + purge.retain(|pubkey, _| !into.contains_key(pubkey)); + into.extend(purge.drain()); + into.retain(|_, account| account.tokens != 0); + } + + while self.depth() > depth { + let (mut purge, _) = self.checkpoints.pop_back().unwrap(); + + if let Some((into, _)) = self.checkpoints.back_mut() { + merge(into, &mut purge); + continue; + } + merge(&mut self.accounts, &mut purge); + } + } + fn depth(&self) -> usize { + self.checkpoints.len() + } +} + +/// Manager for the state of all accounts and contracts after processing its entries. +pub struct Bank { + /// A map of account public keys to the balance in that account. + pub accounts: RwLock, + + /// FIFO queue of `last_id` items + last_ids: RwLock, + + /// set of accounts which are currently in the pipeline + account_locks: Mutex>, + + // The latest finality time for the network + finality_time: AtomicUsize, + + // Mapping of account ids to Subscriber ids and sinks to notify on userdata update + account_subscriptions: RwLock>>>, + + // Mapping of signatures to Subscriber ids and sinks to notify on confirmation + signature_subscriptions: RwLock>>>, + + /// Tracks and updates the leader schedule based on the votes and account stakes + /// processed by the bank + pub leader_scheduler: Arc>, +} + +impl Default for Bank { + fn default() -> Self { + Bank { + accounts: RwLock::new(Accounts::default()), + last_ids: RwLock::new(LastIds::default()), + account_locks: Mutex::new(HashSet::new()), + finality_time: AtomicUsize::new(std::usize::MAX), + account_subscriptions: RwLock::new(HashMap::new()), + signature_subscriptions: RwLock::new(HashMap::new()), + leader_scheduler: Arc::new(RwLock::new(LeaderScheduler::default())), + } + } +} + +impl Bank { + /// Create an Bank with built-in programs. + pub fn new_with_builtin_programs() -> Self { + let bank = Self::default(); + bank.add_builtin_programs(); + bank + } + + /// Create an Bank using a deposit. + pub fn new_from_deposits(deposits: &[Payment]) -> Self { + let bank = Self::default(); + for deposit in deposits { + let mut accounts = bank.accounts.write().unwrap(); + + let mut account = Account::default(); + account.tokens += deposit.tokens; + + accounts.store(&deposit.to, &account); + } + bank.add_builtin_programs(); + bank + } + + pub fn checkpoint(&self) { + self.accounts.write().unwrap().checkpoint(); + self.last_ids.write().unwrap().checkpoint(); + } + pub fn purge(&self, depth: usize) { + self.accounts.write().unwrap().purge(depth); + self.last_ids.write().unwrap().purge(depth); + } + + pub fn rollback(&self) { + let rolled_back_pubkeys: Vec = self + .accounts + .read() + .unwrap() + .accounts + .keys() + .cloned() + .collect(); + + self.accounts.write().unwrap().rollback(); + + rolled_back_pubkeys.iter().for_each(|pubkey| { + if let Some(account) = self.accounts.read().unwrap().load(&pubkey) { + self.check_account_subscriptions(&pubkey, account) + } + }); + + self.last_ids.write().unwrap().rollback(); + } + pub fn checkpoint_depth(&self) -> usize { + self.accounts.read().unwrap().depth() + } + + /// Create an Bank with only a Mint. Typically used by unit tests. + pub fn new(mint: &Mint) -> Self { + let mint_tokens = if mint.bootstrap_leader_id != Pubkey::default() { + mint.tokens - mint.bootstrap_leader_tokens + } else { + mint.tokens + }; + + let mint_deposit = Payment { + to: mint.pubkey(), + tokens: mint_tokens, + }; + + let deposits = if mint.bootstrap_leader_id != Pubkey::default() { + let leader_deposit = Payment { + to: mint.bootstrap_leader_id, + tokens: mint.bootstrap_leader_tokens, + }; + vec![mint_deposit, leader_deposit] + } else { + vec![mint_deposit] + }; + let bank = Self::new_from_deposits(&deposits); + bank.register_tick(&mint.last_id()); + bank + } + + fn add_builtin_programs(&self) { + let mut accounts = self.accounts.write().unwrap(); + + // Preload Bpf Loader account + accounts.store(&bpf_loader::id(), &bpf_loader::account()); + + // Preload Erc20 token program + accounts.store(&token_program::id(), &token_program::account()); + } + + /// Return the last entry ID registered. + pub fn last_id(&self) -> Hash { + self.last_ids + .read() + .unwrap() + .last_id + .expect("no last_id has been set") + } + + /// Store the given signature. The bank will reject any transaction with the same signature. + fn reserve_signature(signatures: &mut SignatureStatusMap, signature: &Signature) -> Result<()> { + if let Some(_result) = signatures.get(signature) { + return Err(BankError::DuplicateSignature); + } + signatures.insert(*signature, Err(BankError::SignatureReserved)); + Ok(()) + } + + /// Forget all signatures. Useful for benchmarking. + pub fn clear_signatures(&self) { + for entry in &mut self.last_ids.write().unwrap().entries.values_mut() { + entry.signature_status.clear(); + } + } + + /// Check if the age of the entry_id is within the max_age + /// return false for any entries with an age equal to or above max_age + fn check_entry_id_age(last_ids: &LastIds, entry_id: Hash, max_age: usize) -> bool { + let entry = last_ids.entries.get(&entry_id); + + match entry { + Some(entry) => last_ids.tick_height - entry.tick_height < max_age as u64, + _ => false, + } + } + + fn reserve_signature_with_last_id( + last_ids: &mut LastIds, + last_id: &Hash, + sig: &Signature, + ) -> Result<()> { + if let Some(entry) = last_ids.entries.get_mut(last_id) { + if last_ids.tick_height - entry.tick_height < MAX_ENTRY_IDS as u64 { + return Self::reserve_signature(&mut entry.signature_status, sig); + } + } + Err(BankError::LastIdNotFound) + } + + #[cfg(test)] + fn reserve_signature_with_last_id_test(&self, sig: &Signature, last_id: &Hash) -> Result<()> { + let mut last_ids = self.last_ids.write().unwrap(); + Self::reserve_signature_with_last_id(&mut last_ids, last_id, sig) + } + + fn update_signature_status_with_last_id( + last_ids_sigs: &mut HashMap, + signature: &Signature, + result: &Result<()>, + last_id: &Hash, + ) { + if let Some(entry) = last_ids_sigs.get_mut(last_id) { + entry.signature_status.insert(*signature, result.clone()); + } + } + + fn update_transaction_statuses(&self, txs: &[Transaction], res: &[Result<()>]) { + let mut last_ids = self.last_ids.write().unwrap(); + for (i, tx) in txs.iter().enumerate() { + Self::update_signature_status_with_last_id( + &mut last_ids.entries, + &tx.signatures[0], + &res[i], + &tx.last_id, + ); + if res[i] != Err(BankError::SignatureNotFound) { + let status = match res[i] { + Ok(_) => RpcSignatureStatus::Confirmed, + Err(BankError::AccountInUse) => RpcSignatureStatus::AccountInUse, + Err(BankError::ProgramRuntimeError(_)) => { + RpcSignatureStatus::ProgramRuntimeError + } + Err(_) => RpcSignatureStatus::GenericFailure, + }; + if status != RpcSignatureStatus::SignatureNotFound { + self.check_signature_subscriptions(&tx.signatures[0], status); + } + } + } + } + + /// Maps a tick height to a timestamp + fn tick_height_to_timestamp(last_ids: &LastIds, tick_height: u64) -> Option { + for entry in last_ids.entries.values() { + if entry.tick_height == tick_height { + return Some(entry.timestamp); + } + } + None + } + + /// Look through the last_ids and find all the valid ids + /// This is batched to avoid holding the lock for a significant amount of time + /// + /// Return a vec of tuple of (valid index, timestamp) + /// index is into the passed ids slice to avoid copying hashes + pub fn count_valid_ids(&self, ids: &[Hash]) -> Vec<(usize, u64)> { + let last_ids = self.last_ids.read().unwrap(); + let mut ret = Vec::new(); + for (i, id) in ids.iter().enumerate() { + if let Some(entry) = last_ids.entries.get(id) { + if last_ids.tick_height - entry.tick_height < MAX_ENTRY_IDS as u64 { + ret.push((i, entry.timestamp)); + } + } + } + ret + } + + /// Looks through a list of tick heights and stakes, and finds the latest + /// tick that has achieved finality + pub fn get_finality_timestamp( + &self, + ticks_and_stakes: &mut [(u64, u64)], + supermajority_stake: u64, + ) -> Option { + // Sort by tick height + ticks_and_stakes.sort_by(|a, b| a.0.cmp(&b.0)); + let last_ids = self.last_ids.read().unwrap(); + let current_tick_height = last_ids.tick_height; + let mut total = 0; + for (tick_height, stake) in ticks_and_stakes.iter() { + if ((current_tick_height - tick_height) as usize) < MAX_ENTRY_IDS { + total += stake; + if total > supermajority_stake { + return Self::tick_height_to_timestamp(&last_ids, *tick_height); + } + } + } + None + } + + /// Tell the bank which Entry IDs exist on the ledger. This function + /// assumes subsequent calls correspond to later entries, and will boot + /// the oldest ones once its internal cache is full. Once boot, the + /// bank will reject transactions using that `last_id`. + pub fn register_tick(&self, last_id: &Hash) { + let mut last_ids = self.last_ids.write().unwrap(); + + last_ids.tick_height += 1; + let tick_height = last_ids.tick_height; + + // this clean up can be deferred until sigs gets larger + // because we verify entry.nth every place we check for validity + if last_ids.entries.len() >= MAX_ENTRY_IDS as usize { + last_ids + .entries + .retain(|_, entry| tick_height - entry.tick_height <= MAX_ENTRY_IDS as u64); + } + + last_ids.entries.insert( + *last_id, + LastIdEntry { + tick_height, + timestamp: timestamp(), + signature_status: HashMap::new(), + }, + ); + + last_ids.last_id = Some(*last_id); + + inc_new_counter_info!("bank-register_tick-registered", 1); + } + + /// Process a Transaction. This is used for unit tests and simply calls the vector Bank::process_transactions method. + pub fn process_transaction(&self, tx: &Transaction) -> Result<()> { + let txs = vec![tx.clone()]; + match self.process_transactions(&txs)[0] { + Err(ref e) => { + info!("process_transaction error: {:?}", e); + Err((*e).clone()) + } + Ok(_) => Ok(()), + } + } + + fn lock_account( + account_locks: &mut HashSet, + keys: &[Pubkey], + error_counters: &mut ErrorCounters, + ) -> Result<()> { + // Copy all the accounts + for k in keys { + if account_locks.contains(k) { + error_counters.account_in_use += 1; + return Err(BankError::AccountInUse); + } + } + for k in keys { + account_locks.insert(*k); + } + Ok(()) + } + + fn unlock_account(tx: &Transaction, result: &Result<()>, account_locks: &mut HashSet) { + match result { + Err(BankError::AccountInUse) => (), + _ => { + for k in &tx.account_keys { + account_locks.remove(k); + } + } + } + } + + fn load_account( + &self, + tx: &Transaction, + accounts: &Accounts, + last_ids: &mut LastIds, + max_age: usize, + error_counters: &mut ErrorCounters, + ) -> Result> { + // Copy all the accounts + if tx.signatures.is_empty() && tx.fee != 0 { + Err(BankError::MissingSignatureForFee) + } else if accounts.load(&tx.account_keys[0]).is_none() { + error_counters.account_not_found += 1; + Err(BankError::AccountNotFound) + } else if accounts.load(&tx.account_keys[0]).unwrap().tokens < tx.fee { + error_counters.insufficient_funds += 1; + Err(BankError::InsufficientFundsForFee) + } else { + if !Self::check_entry_id_age(&last_ids, tx.last_id, max_age) { + error_counters.last_id_not_found += 1; + return Err(BankError::LastIdNotFound); + } + + // There is no way to predict what contract will execute without an error + // If a fee can pay for execution then the contract will be scheduled + let err = + Self::reserve_signature_with_last_id(last_ids, &tx.last_id, &tx.signatures[0]); + if let Err(BankError::LastIdNotFound) = err { + error_counters.reserve_last_id += 1; + } else if let Err(BankError::DuplicateSignature) = err { + error_counters.duplicate_signature += 1; + } + err?; + + let mut called_accounts: Vec = tx + .account_keys + .iter() + .map(|key| accounts.load(key).cloned().unwrap_or_default()) + .collect(); + called_accounts[0].tokens -= tx.fee; + Ok(called_accounts) + } + } + + /// This function will prevent multiple threads from modifying the same account state at the + /// same time + #[must_use] + fn lock_accounts(&self, txs: &[Transaction]) -> Vec> { + let mut account_locks = self.account_locks.lock().unwrap(); + let mut error_counters = ErrorCounters::default(); + let rv = txs + .iter() + .map(|tx| Self::lock_account(&mut account_locks, &tx.account_keys, &mut error_counters)) + .collect(); + if error_counters.account_in_use != 0 { + inc_new_counter_info!( + "bank-process_transactions-account_in_use", + error_counters.account_in_use + ); + } + rv + } + + /// Once accounts are unlocked, new transactions that modify that state can enter the pipeline + fn unlock_accounts(&self, txs: &[Transaction], results: &[Result<()>]) { + debug!("bank unlock accounts"); + let mut account_locks = self.account_locks.lock().unwrap(); + txs.iter() + .zip(results.iter()) + .for_each(|(tx, result)| Self::unlock_account(tx, result, &mut account_locks)); + } + + fn load_accounts( + &self, + txs: &[Transaction], + results: Vec>, + max_age: usize, + error_counters: &mut ErrorCounters, + ) -> Vec<(Result>)> { + let accounts = self.accounts.read().unwrap(); + let mut last_ids = self.last_ids.write().unwrap(); + txs.iter() + .zip(results.into_iter()) + .map(|etx| match etx { + (tx, Ok(())) => { + self.load_account(tx, &accounts, &mut last_ids, max_age, error_counters) + } + (_, Err(e)) => Err(e), + }).collect() + } + + pub fn verify_instruction( + instruction_index: usize, + program_id: &Pubkey, + pre_program_id: &Pubkey, + pre_tokens: u64, + account: &Account, + ) -> Result<()> { + // Verify the transaction + + // Make sure that program_id is still the same or this was just assigned by the system call contract + if *pre_program_id != account.owner && !SystemProgram::check_id(&program_id) { + return Err(BankError::ModifiedContractId(instruction_index as u8)); + } + // For accounts unassigned to the contract, the individual balance of each accounts cannot decrease. + if *program_id != account.owner && pre_tokens > account.tokens { + return Err(BankError::ExternalAccountTokenSpend( + instruction_index as u8, + )); + } + Ok(()) + } + + /// Execute a function with a subset of accounts as writable references. + /// Since the subset can point to the same references, in any order there is no way + /// for the borrow checker to track them with regards to the original set. + fn with_subset(accounts: &mut [Account], ixes: &[u8], func: F) -> A + where + F: Fn(&mut [&mut Account]) -> A, + { + let mut subset: Vec<&mut Account> = ixes + .iter() + .map(|ix| { + let ptr = &mut accounts[*ix as usize] as *mut Account; + // lifetime of this unsafe is only within the scope of the closure + // there is no way to reorder them without breaking borrow checker rules + unsafe { &mut *ptr } + }).collect(); + func(&mut subset) + } + + fn load_executable_accounts(&self, mut program_id: Pubkey) -> Result> { + let mut accounts = Vec::new(); + let mut depth = 0; + loop { + if native_loader::check_id(&program_id) { + // at the root of the chain, ready to dispatch + break; + } + + if depth >= 5 { + return Err(BankError::CallChainTooDeep); + } + depth += 1; + + let program = match self.get_account(&program_id) { + Some(program) => program, + None => return Err(BankError::AccountNotFound), + }; + if !program.executable || program.loader == Pubkey::default() { + return Err(BankError::AccountNotFound); + } + + // add loader to chain + accounts.insert(0, (program_id, program.clone())); + + program_id = program.loader; + } + Ok(accounts) + } + + /// Execute an instruction + /// This method calls the instruction's program entry pont method and verifies that the result of + /// the call does not violate the bank's accounting rules. + /// The accounts are committed back to the bank only if this function returns Ok(_). + fn execute_instruction( + &self, + tx: &Transaction, + instruction_index: usize, + program_accounts: &mut [&mut Account], + ) -> Result<()> { + let program_id = tx.program_id(instruction_index); + // TODO: the runtime should be checking read/write access to memory + // we are trusting the hard coded contracts not to clobber or allocate + let pre_total: u64 = program_accounts.iter().map(|a| a.tokens).sum(); + let pre_data: Vec<_> = program_accounts + .iter_mut() + .map(|a| (a.owner, a.tokens)) + .collect(); + + // Call the contract method + // It's up to the contract to implement its own rules on moving funds + if SystemProgram::check_id(&program_id) { + if let Err(err) = + SystemProgram::process_transaction(&tx, instruction_index, program_accounts) + { + let err = match err { + Error::ResultWithNegativeTokens(i) => BankError::ResultWithNegativeTokens(i), + _ => BankError::ProgramRuntimeError(instruction_index as u8), + }; + return Err(err); + } + } else if BudgetState::check_id(&program_id) { + if BudgetState::process_transaction(&tx, instruction_index, program_accounts).is_err() { + return Err(BankError::ProgramRuntimeError(instruction_index as u8)); + } + } else if StorageProgram::check_id(&program_id) { + if StorageProgram::process_transaction(&tx, instruction_index, program_accounts) + .is_err() + { + return Err(BankError::ProgramRuntimeError(instruction_index as u8)); + } + } else if VoteProgram::check_id(&program_id) { + if VoteProgram::process_transaction(&tx, instruction_index, program_accounts).is_err() { + return Err(BankError::ProgramRuntimeError(instruction_index as u8)); + } + } else { + let mut accounts = self.load_executable_accounts(tx.program_ids[instruction_index])?; + let mut keyed_accounts = create_keyed_accounts(&mut accounts); + let mut keyed_accounts2: Vec<_> = tx.instructions[instruction_index] + .accounts + .iter() + .map(|&index| &tx.account_keys[index as usize]) + .zip(program_accounts.iter_mut()) + .map(|(key, account)| KeyedAccount { key, account }) + .collect(); + keyed_accounts.append(&mut keyed_accounts2); + + if !native_loader::process_instruction( + &program_id, + &mut keyed_accounts, + &tx.instructions[instruction_index].userdata, + self.tick_height(), + ) { + return Err(BankError::ProgramRuntimeError(instruction_index as u8)); + } + } + + // Verify the instruction + for ((pre_program_id, pre_tokens), post_account) in + pre_data.iter().zip(program_accounts.iter()) + { + Self::verify_instruction( + instruction_index, + &program_id, + pre_program_id, + *pre_tokens, + post_account, + )?; + } + // The total sum of all the tokens in all the accounts cannot change. + let post_total: u64 = program_accounts.iter().map(|a| a.tokens).sum(); + if pre_total != post_total { + Err(BankError::UnbalancedInstruction(instruction_index as u8)) + } else { + Ok(()) + } + } + + /// Execute a transaction. + /// This method calls each instruction in the transaction over the set of loaded Accounts + /// The accounts are committed back to the bank only if every instruction succeeds + fn execute_transaction(&self, tx: &Transaction, tx_accounts: &mut [Account]) -> Result<()> { + for (instruction_index, instruction) in tx.instructions.iter().enumerate() { + Self::with_subset(tx_accounts, &instruction.accounts, |program_accounts| { + self.execute_instruction(tx, instruction_index, program_accounts) + })?; + } + Ok(()) + } + + pub fn store_accounts( + &self, + txs: &[Transaction], + res: &[Result<()>], + loaded: &[Result>], + ) { + let mut accounts = self.accounts.write().unwrap(); + for (i, racc) in loaded.iter().enumerate() { + if res[i].is_err() || racc.is_err() { + continue; + } + + let tx = &txs[i]; + let acc = racc.as_ref().unwrap(); + for (key, account) in tx.account_keys.iter().zip(acc.iter()) { + accounts.store(key, account); + } + } + } + + pub fn process_and_record_transactions( + &self, + txs: &[Transaction], + poh: &PohRecorder, + ) -> Result<()> { + let now = Instant::now(); + // Once accounts are locked, other threads cannot encode transactions that will modify the + // same account state + let locked_accounts = self.lock_accounts(txs); + let lock_time = now.elapsed(); + let now = Instant::now(); + // Use a shorter maximum age when adding transactions into the pipeline. This will reduce + // the likelyhood of any single thread getting starved and processing old ids. + // TODO: Banking stage threads should be prioritized to complete faster then this queue + // expires. + let results = + self.execute_and_commit_transactions(txs, locked_accounts, MAX_ENTRY_IDS as usize / 2); + let process_time = now.elapsed(); + let now = Instant::now(); + self.record_transactions(txs, &results, poh)?; + let record_time = now.elapsed(); + let now = Instant::now(); + // Once the accounts are unlocked new transactions can enter the pipeline to process them + self.unlock_accounts(&txs, &results); + let unlock_time = now.elapsed(); + debug!( + "lock: {}us process: {}us record: {}us unlock: {}us txs_len={}", + duration_as_us(&lock_time), + duration_as_us(&process_time), + duration_as_us(&record_time), + duration_as_us(&unlock_time), + txs.len(), + ); + Ok(()) + } + + fn record_transactions( + &self, + txs: &[Transaction], + results: &[Result<()>], + poh: &PohRecorder, + ) -> Result<()> { + let processed_transactions: Vec<_> = results + .iter() + .zip(txs.iter()) + .filter_map(|(r, x)| match r { + Ok(_) => Some(x.clone()), + Err(ref e) => { + debug!("process transaction failed {:?}", e); + None + } + }).collect(); + // unlock all the accounts with errors which are filtered by the above `filter_map` + if !processed_transactions.is_empty() { + let hash = Transaction::hash(&processed_transactions); + debug!("processed ok: {} {}", processed_transactions.len(), hash); + // record and unlock will unlock all the successfull transactions + poh.record(hash, processed_transactions).map_err(|e| { + warn!("record failure: {:?}", e); + BankError::RecordFailure + })?; + } + Ok(()) + } + + /// Process a batch of transactions. + #[must_use] + pub fn execute_and_commit_transactions( + &self, + txs: &[Transaction], + locked_accounts: Vec>, + max_age: usize, + ) -> Vec> { + debug!("processing transactions: {}", txs.len()); + let mut error_counters = ErrorCounters::default(); + let now = Instant::now(); + let mut loaded_accounts = + self.load_accounts(txs, locked_accounts.clone(), max_age, &mut error_counters); + + let load_elapsed = now.elapsed(); + let now = Instant::now(); + let executed: Vec> = loaded_accounts + .iter_mut() + .zip(txs.iter()) + .map(|(acc, tx)| match acc { + Err(e) => Err(e.clone()), + Ok(ref mut accounts) => self.execute_transaction(tx, accounts), + }).collect(); + let execution_elapsed = now.elapsed(); + let now = Instant::now(); + self.store_accounts(txs, &executed, &loaded_accounts); + + // Check account subscriptions and send notifications + self.send_account_notifications(txs, locked_accounts); + + // once committed there is no way to unroll + let write_elapsed = now.elapsed(); + debug!( + "load: {}us execute: {}us store: {}us txs_len={}", + duration_as_us(&load_elapsed), + duration_as_us(&execution_elapsed), + duration_as_us(&write_elapsed), + txs.len(), + ); + self.update_transaction_statuses(txs, &executed); + let mut tx_count = 0; + let mut err_count = 0; + for (r, tx) in executed.iter().zip(txs.iter()) { + if r.is_ok() { + tx_count += 1; + } else { + if err_count == 0 { + info!("tx error: {:?} {:?}", r, tx); + } + err_count += 1; + } + } + if err_count > 0 { + info!("{} errors of {} txs", err_count, err_count + tx_count); + inc_new_counter_info!( + "bank-process_transactions-account_not_found", + error_counters.account_not_found + ); + inc_new_counter_info!("bank-process_transactions-error_count", err_count); + } + + self.accounts + .write() + .unwrap() + .increment_transaction_count(tx_count); + + inc_new_counter_info!("bank-process_transactions-txs", tx_count); + if 0 != error_counters.last_id_not_found { + inc_new_counter_info!( + "bank-process_transactions-error-last_id_not_found", + error_counters.last_id_not_found + ); + } + if 0 != error_counters.reserve_last_id { + inc_new_counter_info!( + "bank-process_transactions-error-reserve_last_id", + error_counters.reserve_last_id + ); + } + if 0 != error_counters.duplicate_signature { + inc_new_counter_info!( + "bank-process_transactions-error-duplicate_signature", + error_counters.duplicate_signature + ); + } + if 0 != error_counters.insufficient_funds { + inc_new_counter_info!( + "bank-process_transactions-error-insufficient_funds", + error_counters.insufficient_funds + ); + } + executed + } + + #[must_use] + pub fn process_transactions(&self, txs: &[Transaction]) -> Vec> { + let locked_accounts = self.lock_accounts(txs); + let results = self.execute_and_commit_transactions(txs, locked_accounts, MAX_ENTRY_IDS); + self.unlock_accounts(txs, &results); + results + } + + pub fn process_entry(&self, entry: &Entry) -> Result<()> { + if !entry.is_tick() { + for result in self.process_transactions(&entry.transactions) { + result?; + } + } else { + self.register_tick(&entry.id); + self.leader_scheduler + .write() + .unwrap() + .update_height(self.tick_height(), self); + } + + Ok(()) + } + + /// Process an ordered list of entries. + pub fn process_entries(&self, entries: &[Entry]) -> Result<()> { + self.par_process_entries(entries) + } + + pub fn first_err(results: &[Result<()>]) -> Result<()> { + for r in results { + r.clone()?; + } + Ok(()) + } + pub fn par_execute_entries(&self, entries: &[(&Entry, Vec>)]) -> Result<()> { + inc_new_counter_info!("bank-par_execute_entries-count", entries.len()); + let results: Vec> = entries + .into_par_iter() + .map(|(e, locks)| { + let results = self.execute_and_commit_transactions( + &e.transactions, + locks.to_vec(), + MAX_ENTRY_IDS, + ); + self.unlock_accounts(&e.transactions, &results); + Self::first_err(&results) + }).collect(); + Self::first_err(&results) + } + + /// process entries in parallel + /// 1. In order lock accounts for each entry while the lock succeeds, up to a Tick entry + /// 2. Process the locked group in parallel + /// 3. Register the `Tick` if it's available, goto 1 + pub fn par_process_entries(&self, entries: &[Entry]) -> Result<()> { + // accumulator for entries that can be processed in parallel + let mut mt_group = vec![]; + for entry in entries { + if entry.is_tick() { + // if its a tick, execute the group and register the tick + self.par_execute_entries(&mt_group)?; + self.register_tick(&entry.id); + mt_group = vec![]; + continue; + } + // try to lock the accounts + let locked = self.lock_accounts(&entry.transactions); + // if any of the locks error out + // execute the current group + if Self::first_err(&locked).is_err() { + self.par_execute_entries(&mt_group)?; + mt_group = vec![]; + //reset the lock and push the entry + let locked = self.lock_accounts(&entry.transactions); + mt_group.push((entry, locked)); + } else { + // push the entry to the mt_group + mt_group.push((entry, locked)); + } + } + self.par_execute_entries(&mt_group)?; + Ok(()) + } + + /// Process an ordered list of entries, populating a circular buffer "tail" + /// as we go. + fn process_block(&self, entries: &[Entry]) -> Result<()> { + for entry in entries { + self.process_entry(entry)?; + } + + Ok(()) + } + + /// Append entry blocks to the ledger, verifying them along the way. + fn process_ledger_blocks( + &self, + start_hash: Hash, + entry_height: u64, + entries: I, + ) -> Result<(u64, Hash)> + where + I: IntoIterator, + { + // these magic numbers are from genesis of the mint, could pull them + // back out of this loop. + let mut entry_height = entry_height; + let mut last_id = start_hash; + + // Ledger verification needs to be parallelized, but we can't pull the whole + // thing into memory. We therefore chunk it. + for block in &entries.into_iter().chunks(VERIFY_BLOCK_SIZE) { + let block: Vec<_> = block.collect(); + + if !block.verify(&last_id) { + warn!("Ledger proof of history failed at entry: {}", entry_height); + return Err(BankError::LedgerVerificationFailed); + } + + self.process_block(&block)?; + + last_id = block.last().unwrap().id; + entry_height += block.len() as u64; + } + Ok((entry_height, last_id)) + } + + /// Process a full ledger. + pub fn process_ledger(&self, entries: I) -> Result<(u64, Hash)> + where + I: IntoIterator, + { + let mut entries = entries.into_iter(); + + // The first item in the ledger is required to be an entry with zero num_hashes, + // which implies its id can be used as the ledger's seed. + let entry0 = entries.next().expect("invalid ledger: empty"); + + // The second item in the ledger consists of a transaction with + // two special instructions: + // 1) The first is a special move instruction where the to and from + // fields are the same. That entry should be treated as a deposit, not a + // transfer to oneself. + // 2) The second is a move instruction that acts as a payment to the first + // leader from the mint. This bootstrap leader will stay in power during the + // bootstrapping period of the network + let entry1 = entries + .next() + .expect("invalid ledger: need at least 2 entries"); + + // genesis should conform to PoH + assert!(entry1.verify(&entry0.id)); + + { + // Process the first transaction + let tx = &entry1.transactions[0]; + assert!(SystemProgram::check_id(tx.program_id(0)), "Invalid ledger"); + assert!(SystemProgram::check_id(tx.program_id(1)), "Invalid ledger"); + let mut instruction: SystemInstruction = deserialize(tx.userdata(0)).unwrap(); + let mint_deposit = if let SystemInstruction::Move { tokens } = instruction { + Some(tokens) + } else { + None + }.expect("invalid ledger, needs to start with mint deposit"); + + instruction = deserialize(tx.userdata(1)).unwrap(); + let leader_payment = if let SystemInstruction::Move { tokens } = instruction { + Some(tokens) + } else { + None + }.expect("invalid ledger, bootstrap leader payment expected"); + + assert!(leader_payment <= mint_deposit); + assert!(leader_payment > 0); + + { + // 1) Deposit into the mint + let mut accounts = self.accounts.write().unwrap(); + + let mut account = accounts + .load(&tx.account_keys[0]) + .cloned() + .unwrap_or_default(); + account.tokens += mint_deposit - leader_payment; + accounts.store(&tx.account_keys[0], &account); + trace!( + "applied genesis payment {:?} => {:?}", + mint_deposit - leader_payment, + account + ); + + // 2) Transfer tokens to the bootstrap leader. The first two + // account keys will both be the mint (because the mint is the source + // for this transaction and the first move instruction is to the the + // mint itself), so we look at the third account key to find the first + // leader id. + let bootstrap_leader_id = tx.account_keys[2]; + let mut account = accounts + .load(&bootstrap_leader_id) + .cloned() + .unwrap_or_default(); + account.tokens += leader_payment; + accounts.store(&bootstrap_leader_id, &account); + + self.leader_scheduler.write().unwrap().bootstrap_leader = bootstrap_leader_id; + + trace!( + "applied genesis payment to bootstrap leader {:?} => {:?}", + leader_payment, + account + ); + } + } + + Ok(self.process_ledger_blocks(entry1.id, 2, entries)?) + } + + /// Create, sign, and process a Transaction from `keypair` to `to` of + /// `n` tokens where `last_id` is the last Entry ID observed by the client. + pub fn transfer( + &self, + n: u64, + keypair: &Keypair, + to: Pubkey, + last_id: Hash, + ) -> Result { + let tx = Transaction::system_new(keypair, to, n, last_id); + let signature = tx.signatures[0]; + self.process_transaction(&tx).map(|_| signature) + } + + pub fn read_balance(account: &Account) -> u64 { + if SystemProgram::check_id(&account.owner) { + SystemProgram::get_balance(account) + } else if BudgetState::check_id(&account.owner) { + BudgetState::get_balance(account) + } else { + account.tokens + } + } + /// Each contract would need to be able to introspect its own state + /// this is hard coded to the budget contract language + pub fn get_balance(&self, pubkey: &Pubkey) -> u64 { + self.get_account(pubkey) + .map(|x| Self::read_balance(&x)) + .unwrap_or(0) + } + + /// TODO: Need to implement a real staking contract to hold node stake. + /// Right now this just gets the account balances. See github issue #1655. + pub fn get_stake(&self, pubkey: &Pubkey) -> u64 { + self.get_balance(pubkey) + } + + pub fn get_account(&self, pubkey: &Pubkey) -> Option { + let accounts = self + .accounts + .read() + .expect("'accounts' read lock in get_balance"); + accounts.load(pubkey).cloned() + } + + pub fn transaction_count(&self) -> u64 { + self.accounts.read().unwrap().transaction_count() + } + + pub fn get_signature_status(&self, signature: &Signature) -> Result<()> { + let last_ids = self.last_ids.read().unwrap(); + for entry in last_ids.entries.values() { + if let Some(res) = entry.signature_status.get(signature) { + return res.clone(); + } + } + Err(BankError::SignatureNotFound) + } + + pub fn has_signature(&self, signature: &Signature) -> bool { + self.get_signature_status(signature) != Err(BankError::SignatureNotFound) + } + + pub fn get_signature(&self, last_id: &Hash, signature: &Signature) -> Option> { + self.last_ids + .read() + .unwrap() + .entries + .get(last_id) + .and_then(|entry| entry.signature_status.get(signature).cloned()) + } + + /// Hash the `accounts` HashMap. This represents a validator's interpretation + /// of the delta of the ledger since the last vote and up to now + pub fn hash_internal_state(&self) -> Hash { + let mut ordered_accounts = BTreeMap::new(); + + // only hash internal state of the part being voted upon, i.e. since last + // checkpoint + for (pubkey, account) in &self.accounts.read().unwrap().accounts { + ordered_accounts.insert(*pubkey, account.clone()); + } + + hash(&serialize(&ordered_accounts).unwrap()) + } + + pub fn finality(&self) -> usize { + self.finality_time.load(Ordering::Relaxed) + } + + pub fn set_finality(&self, finality: usize) { + self.finality_time.store(finality, Ordering::Relaxed); + } + + fn send_account_notifications(&self, txs: &[Transaction], locked_accounts: Vec>) { + let accounts = self.accounts.read().unwrap(); + txs.iter() + .zip(locked_accounts.into_iter()) + .filter(|(_, result)| result.is_ok()) + .flat_map(|(tx, _)| &tx.account_keys) + .for_each(|pubkey| { + let account = accounts.load(pubkey).cloned().unwrap_or_default(); + self.check_account_subscriptions(&pubkey, &account); + }); + } + pub fn add_account_subscription( + &self, + bank_sub_id: Pubkey, + pubkey: Pubkey, + sink: Sink, + ) { + let mut subscriptions = self.account_subscriptions.write().unwrap(); + if let Some(current_hashmap) = subscriptions.get_mut(&pubkey) { + current_hashmap.insert(bank_sub_id, sink); + return; + } + let mut hashmap = HashMap::new(); + hashmap.insert(bank_sub_id, sink); + subscriptions.insert(pubkey, hashmap); + } + + pub fn remove_account_subscription(&self, bank_sub_id: &Pubkey, pubkey: &Pubkey) -> bool { + let mut subscriptions = self.account_subscriptions.write().unwrap(); + match subscriptions.get_mut(pubkey) { + Some(ref current_hashmap) if current_hashmap.len() == 1 => {} + Some(current_hashmap) => { + return current_hashmap.remove(bank_sub_id).is_some(); + } + None => { + return false; + } + } + subscriptions.remove(pubkey).is_some() + } + + pub fn get_current_leader(&self) -> Option<(Pubkey, u64)> { + self.leader_scheduler + .read() + .unwrap() + .get_scheduled_leader(self.tick_height()) + } + + pub fn tick_height(&self) -> u64 { + self.last_ids.read().unwrap().tick_height + } + + fn check_account_subscriptions(&self, pubkey: &Pubkey, account: &Account) { + let subscriptions = self.account_subscriptions.read().unwrap(); + if let Some(hashmap) = subscriptions.get(pubkey) { + for (_bank_sub_id, sink) in hashmap.iter() { + sink.notify(Ok(account.clone())).wait().unwrap(); + } + } + } + + pub fn add_signature_subscription( + &self, + bank_sub_id: Pubkey, + signature: Signature, + sink: Sink, + ) { + let mut subscriptions = self.signature_subscriptions.write().unwrap(); + if let Some(current_hashmap) = subscriptions.get_mut(&signature) { + current_hashmap.insert(bank_sub_id, sink); + return; + } + let mut hashmap = HashMap::new(); + hashmap.insert(bank_sub_id, sink); + subscriptions.insert(signature, hashmap); + } + + pub fn remove_signature_subscription( + &self, + bank_sub_id: &Pubkey, + signature: &Signature, + ) -> bool { + let mut subscriptions = self.signature_subscriptions.write().unwrap(); + match subscriptions.get_mut(signature) { + Some(ref current_hashmap) if current_hashmap.len() == 1 => {} + Some(current_hashmap) => { + return current_hashmap.remove(bank_sub_id).is_some(); + } + None => { + return false; + } + } + subscriptions.remove(signature).is_some() + } + + fn check_signature_subscriptions(&self, signature: &Signature, status: RpcSignatureStatus) { + let mut subscriptions = self.signature_subscriptions.write().unwrap(); + if let Some(hashmap) = subscriptions.get(signature) { + for (_bank_sub_id, sink) in hashmap.iter() { + sink.notify(Ok(status)).wait().unwrap(); + } + } + subscriptions.remove(&signature); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bincode::serialize; + use budget_program::BudgetState; + use entry::next_entry; + use entry::Entry; + use jsonrpc_macros::pubsub::{Subscriber, SubscriptionId}; + use ledger; + use signature::Keypair; + use signature::{GenKeys, KeypairUtil}; + use solana_sdk::hash::hash; + use std; + use system_transaction::SystemTransaction; + use tokio::prelude::{Async, Stream}; + use transaction::Instruction; + + #[test] + fn test_bank_new() { + let mint = Mint::new(10_000); + let bank = Bank::new(&mint); + assert_eq!(bank.get_balance(&mint.pubkey()), 10_000); + } + + #[test] + fn test_bank_new_with_leader() { + let dummy_leader_id = Keypair::new().pubkey(); + let dummy_leader_tokens = 1; + let mint = Mint::new_with_leader(10_000, dummy_leader_id, dummy_leader_tokens); + let bank = Bank::new(&mint); + assert_eq!(bank.get_balance(&mint.pubkey()), 9999); + assert_eq!(bank.get_balance(&dummy_leader_id), 1); + } + + #[test] + fn test_two_payments_to_one_party() { + let mint = Mint::new(10_000); + let pubkey = Keypair::new().pubkey(); + let bank = Bank::new(&mint); + assert_eq!(bank.last_id(), mint.last_id()); + + bank.transfer(1_000, &mint.keypair(), pubkey, mint.last_id()) + .unwrap(); + assert_eq!(bank.get_balance(&pubkey), 1_000); + + bank.transfer(500, &mint.keypair(), pubkey, mint.last_id()) + .unwrap(); + assert_eq!(bank.get_balance(&pubkey), 1_500); + assert_eq!(bank.transaction_count(), 2); + } + + #[test] + fn test_one_source_two_tx_one_batch() { + let mint = Mint::new(1); + let key1 = Keypair::new().pubkey(); + let key2 = Keypair::new().pubkey(); + let bank = Bank::new(&mint); + assert_eq!(bank.last_id(), mint.last_id()); + + let t1 = Transaction::system_move(&mint.keypair(), key1, 1, mint.last_id(), 0); + let t2 = Transaction::system_move(&mint.keypair(), key2, 1, mint.last_id(), 0); + let res = bank.process_transactions(&vec![t1.clone(), t2.clone()]); + assert_eq!(res.len(), 2); + assert_eq!(res[0], Ok(())); + assert_eq!(res[1], Err(BankError::AccountInUse)); + assert_eq!(bank.get_balance(&mint.pubkey()), 0); + assert_eq!(bank.get_balance(&key1), 1); + assert_eq!(bank.get_balance(&key2), 0); + assert_eq!( + bank.get_signature(&t1.last_id, &t1.signatures[0]), + Some(Ok(())) + ); + // TODO: Transactions that fail to pay a fee could be dropped silently + assert_eq!( + bank.get_signature(&t2.last_id, &t2.signatures[0]), + Some(Err(BankError::AccountInUse)) + ); + } + + #[test] + fn test_one_tx_two_out_atomic_fail() { + let mint = Mint::new(1); + let key1 = Keypair::new().pubkey(); + let key2 = Keypair::new().pubkey(); + let bank = Bank::new(&mint); + let spend = SystemInstruction::Move { tokens: 1 }; + let instructions = vec![ + Instruction { + program_ids_index: 0, + userdata: serialize(&spend).unwrap(), + accounts: vec![0, 1], + }, + Instruction { + program_ids_index: 0, + userdata: serialize(&spend).unwrap(), + accounts: vec![0, 2], + }, + ]; + + let t1 = Transaction::new_with_instructions( + &[&mint.keypair()], + &[key1, key2], + mint.last_id(), + 0, + vec![SystemProgram::id()], + instructions, + ); + let res = bank.process_transactions(&vec![t1.clone()]); + assert_eq!(res.len(), 1); + assert_eq!(res[0], Err(BankError::ResultWithNegativeTokens(1))); + assert_eq!(bank.get_balance(&mint.pubkey()), 1); + assert_eq!(bank.get_balance(&key1), 0); + assert_eq!(bank.get_balance(&key2), 0); + assert_eq!( + bank.get_signature(&t1.last_id, &t1.signatures[0]), + Some(Err(BankError::ResultWithNegativeTokens(1))) + ); + } + + #[test] + fn test_one_tx_two_out_atomic_pass() { + let mint = Mint::new(2); + let key1 = Keypair::new().pubkey(); + let key2 = Keypair::new().pubkey(); + let bank = Bank::new(&mint); + let t1 = Transaction::system_move_many( + &mint.keypair(), + &[(key1, 1), (key2, 1)], + mint.last_id(), + 0, + ); + let res = bank.process_transactions(&vec![t1.clone()]); + assert_eq!(res.len(), 1); + assert_eq!(res[0], Ok(())); + assert_eq!(bank.get_balance(&mint.pubkey()), 0); + assert_eq!(bank.get_balance(&key1), 1); + assert_eq!(bank.get_balance(&key2), 1); + assert_eq!( + bank.get_signature(&t1.last_id, &t1.signatures[0]), + Some(Ok(())) + ); + } + + // TODO: This test demonstrates that fees are not paid when a program fails. + // See github issue 1157 (https://github.com/solana-labs/solana/issues/1157) + #[test] + fn test_detect_failed_duplicate_transactions_issue_1157() { + let mint = Mint::new(1); + let bank = Bank::new(&mint); + let dest = Keypair::new(); + + // source with 0 contract context + let tx = Transaction::system_create( + &mint.keypair(), + dest.pubkey(), + mint.last_id(), + 2, + 0, + Pubkey::default(), + 1, + ); + let signature = tx.signatures[0]; + assert!(!bank.has_signature(&signature)); + let res = bank.process_transaction(&tx); + + // Result failed, but signature is registered + assert!(res.is_err()); + assert!(bank.has_signature(&signature)); + assert_matches!( + bank.get_signature_status(&signature), + Err(BankError::ResultWithNegativeTokens(0)) + ); + + // The tokens didn't move, but the from address paid the transaction fee. + assert_eq!(bank.get_balance(&dest.pubkey()), 0); + + // BUG: This should be the original balance minus the transaction fee. + //assert_eq!(bank.get_balance(&mint.pubkey()), 0); + } + + #[test] + fn test_account_not_found() { + let mint = Mint::new(1); + let bank = Bank::new(&mint); + let keypair = Keypair::new(); + assert_eq!( + bank.transfer(1, &keypair, mint.pubkey(), mint.last_id()), + Err(BankError::AccountNotFound) + ); + assert_eq!(bank.transaction_count(), 0); + } + + #[test] + fn test_insufficient_funds() { + let mint = Mint::new(11_000); + let bank = Bank::new(&mint); + let pubkey = Keypair::new().pubkey(); + bank.transfer(1_000, &mint.keypair(), pubkey, mint.last_id()) + .unwrap(); + assert_eq!(bank.transaction_count(), 1); + assert_eq!(bank.get_balance(&pubkey), 1_000); + assert_matches!( + bank.transfer(10_001, &mint.keypair(), pubkey, mint.last_id()), + Err(BankError::ResultWithNegativeTokens(0)) + ); + assert_eq!(bank.transaction_count(), 1); + + let mint_pubkey = mint.keypair().pubkey(); + assert_eq!(bank.get_balance(&mint_pubkey), 10_000); + assert_eq!(bank.get_balance(&pubkey), 1_000); + } + + #[test] + fn test_transfer_to_newb() { + let mint = Mint::new(10_000); + let bank = Bank::new(&mint); + let pubkey = Keypair::new().pubkey(); + bank.transfer(500, &mint.keypair(), pubkey, mint.last_id()) + .unwrap(); + assert_eq!(bank.get_balance(&pubkey), 500); + } + + #[test] + fn test_duplicate_transaction_signature() { + let mint = Mint::new(1); + let bank = Bank::new(&mint); + let signature = Signature::default(); + assert_eq!( + bank.reserve_signature_with_last_id_test(&signature, &mint.last_id()), + Ok(()) + ); + assert_eq!( + bank.reserve_signature_with_last_id_test(&signature, &mint.last_id()), + Err(BankError::DuplicateSignature) + ); + } + + #[test] + fn test_clear_signatures() { + let mint = Mint::new(1); + let bank = Bank::new(&mint); + let signature = Signature::default(); + bank.reserve_signature_with_last_id_test(&signature, &mint.last_id()) + .unwrap(); + bank.clear_signatures(); + assert_eq!( + bank.reserve_signature_with_last_id_test(&signature, &mint.last_id()), + Ok(()) + ); + } + + #[test] + fn test_get_signature_status() { + let mint = Mint::new(1); + let bank = Bank::new(&mint); + let signature = Signature::default(); + bank.reserve_signature_with_last_id_test(&signature, &mint.last_id()) + .expect("reserve signature"); + assert_eq!( + bank.get_signature_status(&signature), + Err(BankError::SignatureReserved) + ); + } + + #[test] + fn test_has_signature() { + let mint = Mint::new(1); + let bank = Bank::new(&mint); + let signature = Signature::default(); + bank.reserve_signature_with_last_id_test(&signature, &mint.last_id()) + .expect("reserve signature"); + assert!(bank.has_signature(&signature)); + } + + #[test] + fn test_reject_old_last_id() { + let mint = Mint::new(1); + let bank = Bank::new(&mint); + let signature = Signature::default(); + for i in 0..MAX_ENTRY_IDS { + let last_id = hash(&serialize(&i).unwrap()); // Unique hash + bank.register_tick(&last_id); + } + // Assert we're no longer able to use the oldest entry ID. + assert_eq!( + bank.reserve_signature_with_last_id_test(&signature, &mint.last_id()), + Err(BankError::LastIdNotFound) + ); + } + + #[test] + fn test_count_valid_ids() { + let mint = Mint::new(1); + let bank = Bank::new(&mint); + let ids: Vec<_> = (0..MAX_ENTRY_IDS) + .map(|i| { + let last_id = hash(&serialize(&i).unwrap()); // Unique hash + bank.register_tick(&last_id); + last_id + }).collect(); + assert_eq!(bank.count_valid_ids(&[]).len(), 0); + assert_eq!(bank.count_valid_ids(&[mint.last_id()]).len(), 0); + for (i, id) in bank.count_valid_ids(&ids).iter().enumerate() { + assert_eq!(id.0, i); + } + } + + #[test] + fn test_debits_before_credits() { + let mint = Mint::new(2); + let bank = Bank::new(&mint); + let keypair = Keypair::new(); + let tx0 = Transaction::system_new(&mint.keypair(), keypair.pubkey(), 2, mint.last_id()); + let tx1 = Transaction::system_new(&keypair, mint.pubkey(), 1, mint.last_id()); + let txs = vec![tx0, tx1]; + let results = bank.process_transactions(&txs); + assert!(results[1].is_err()); + + // Assert bad transactions aren't counted. + assert_eq!(bank.transaction_count(), 1); + } + + #[test] + fn test_process_empty_entry_is_registered() { + let mint = Mint::new(1); + let bank = Bank::new(&mint); + let keypair = Keypair::new(); + let entry = next_entry(&mint.last_id(), 1, vec![]); + let tx = Transaction::system_new(&mint.keypair(), keypair.pubkey(), 1, entry.id); + + // First, ensure the TX is rejected because of the unregistered last ID + assert_eq!( + bank.process_transaction(&tx), + Err(BankError::LastIdNotFound) + ); + + // Now ensure the TX is accepted despite pointing to the ID of an empty entry. + bank.process_entries(&[entry]).unwrap(); + assert_eq!(bank.process_transaction(&tx), Ok(())); + } + + #[test] + fn test_process_genesis() { + let dummy_leader_id = Keypair::new().pubkey(); + let dummy_leader_tokens = 1; + let mint = Mint::new_with_leader(5, dummy_leader_id, dummy_leader_tokens); + let genesis = mint.create_entries(); + let bank = Bank::default(); + bank.process_ledger(genesis).unwrap(); + assert_eq!(bank.get_balance(&mint.pubkey()), 4); + assert_eq!(bank.get_balance(&dummy_leader_id), 1); + assert_eq!( + bank.leader_scheduler.read().unwrap().bootstrap_leader, + dummy_leader_id + ); + } + + fn create_sample_block_with_next_entries_using_keypairs( + mint: &Mint, + keypairs: &[Keypair], + ) -> impl Iterator { + let mut last_id = mint.last_id(); + let mut hash = mint.last_id(); + let mut entries: Vec = vec![]; + let num_hashes = 1; + for k in keypairs { + let txs = vec![Transaction::system_new( + &mint.keypair(), + k.pubkey(), + 1, + last_id, + )]; + let mut e = ledger::next_entries(&hash, 0, txs); + entries.append(&mut e); + hash = entries.last().unwrap().id; + let tick = Entry::new(&hash, num_hashes, vec![]); + hash = tick.id; + last_id = hash; + entries.push(tick); + } + entries.into_iter() + } + + // create a ledger with tick entries every `ticks` entries + fn create_sample_block_with_ticks( + mint: &Mint, + length: usize, + ticks: usize, + ) -> impl Iterator { + let mut entries = Vec::with_capacity(length); + let mut hash = mint.last_id(); + let mut last_id = mint.last_id(); + let num_hashes = 1; + for i in 0..length { + let keypair = Keypair::new(); + let tx = Transaction::system_new(&mint.keypair(), keypair.pubkey(), 1, last_id); + let entry = Entry::new(&hash, num_hashes, vec![tx]); + hash = entry.id; + entries.push(entry); + if (i + 1) % ticks == 0 { + let tick = Entry::new(&hash, num_hashes, vec![]); + hash = tick.id; + last_id = hash; + entries.push(tick); + } + } + entries.into_iter() + } + + fn create_sample_ledger(length: usize) -> (impl Iterator, Pubkey) { + let dummy_leader_id = Keypair::new().pubkey(); + let dummy_leader_tokens = 1; + let mint = Mint::new_with_leader( + length as u64 + 1 + dummy_leader_tokens, + dummy_leader_id, + dummy_leader_tokens, + ); + let genesis = mint.create_entries(); + let block = create_sample_block_with_ticks(&mint, length, length); + (genesis.into_iter().chain(block), mint.pubkey()) + } + + fn create_sample_ledger_with_mint_and_keypairs( + mint: &Mint, + keypairs: &[Keypair], + ) -> impl Iterator { + let genesis = mint.create_entries(); + let block = create_sample_block_with_next_entries_using_keypairs(mint, keypairs); + genesis.into_iter().chain(block) + } + + #[test] + fn test_process_ledger_simple() { + let (ledger, pubkey) = create_sample_ledger(1); + let bank = Bank::default(); + let (ledger_height, last_id) = bank.process_ledger(ledger).unwrap(); + assert_eq!(bank.get_balance(&pubkey), 1); + assert_eq!(ledger_height, 5); + assert_eq!(bank.tick_height(), 2); + assert_eq!(bank.last_id(), last_id); + } + + #[test] + fn test_hash_internal_state() { + let dummy_leader_id = Keypair::new().pubkey(); + let dummy_leader_tokens = 1; + let mint = Mint::new_with_leader(2_000, dummy_leader_id, dummy_leader_tokens); + + let seed = [0u8; 32]; + let mut rnd = GenKeys::new(seed); + let keypairs = rnd.gen_n_keypairs(5); + let ledger0 = create_sample_ledger_with_mint_and_keypairs(&mint, &keypairs); + let ledger1 = create_sample_ledger_with_mint_and_keypairs(&mint, &keypairs); + + let bank0 = Bank::default(); + bank0.process_ledger(ledger0).unwrap(); + let bank1 = Bank::default(); + bank1.process_ledger(ledger1).unwrap(); + + let initial_state = bank0.hash_internal_state(); + + assert_eq!(bank1.hash_internal_state(), initial_state); + + let pubkey = keypairs[0].pubkey(); + bank0 + .transfer(1_000, &mint.keypair(), pubkey, mint.last_id()) + .unwrap(); + assert_ne!(bank0.hash_internal_state(), initial_state); + bank1 + .transfer(1_000, &mint.keypair(), pubkey, mint.last_id()) + .unwrap(); + assert_eq!(bank0.hash_internal_state(), bank1.hash_internal_state()); + } + #[test] + fn test_finality() { + let def_bank = Bank::default(); + assert_eq!(def_bank.finality(), std::usize::MAX); + def_bank.set_finality(90); + assert_eq!(def_bank.finality(), 90); + } + #[test] + fn test_interleaving_locks() { + let mint = Mint::new(3); + let bank = Bank::new(&mint); + let alice = Keypair::new(); + let bob = Keypair::new(); + + let tx1 = Transaction::system_new(&mint.keypair(), alice.pubkey(), 1, mint.last_id()); + let pay_alice = vec![tx1]; + + let locked_alice = bank.lock_accounts(&pay_alice); + let results_alice = + bank.execute_and_commit_transactions(&pay_alice, locked_alice, MAX_ENTRY_IDS); + assert_eq!(results_alice[0], Ok(())); + + // try executing an interleaved transfer twice + assert_eq!( + bank.transfer(1, &mint.keypair(), bob.pubkey(), mint.last_id()), + Err(BankError::AccountInUse) + ); + // the second time shoudl fail as well + // this verifies that `unlock_accounts` doesn't unlock `AccountInUse` accounts + assert_eq!( + bank.transfer(1, &mint.keypair(), bob.pubkey(), mint.last_id()), + Err(BankError::AccountInUse) + ); + + bank.unlock_accounts(&pay_alice, &results_alice); + + assert_matches!( + bank.transfer(2, &mint.keypair(), bob.pubkey(), mint.last_id()), + Ok(_) + ); + } + #[test] + fn test_bank_account_subscribe() { + let mint = Mint::new(100); + let bank = Bank::new(&mint); + let alice = Keypair::new(); + let bank_sub_id = Keypair::new().pubkey(); + let last_id = bank.last_id(); + let tx = Transaction::system_create( + &mint.keypair(), + alice.pubkey(), + last_id, + 1, + 16, + BudgetState::id(), + 0, + ); + bank.process_transaction(&tx).unwrap(); + + let (subscriber, _id_receiver, mut transport_receiver) = + Subscriber::new_test("accountNotification"); + let sub_id = SubscriptionId::Number(0 as u64); + let sink = subscriber.assign_id(sub_id.clone()).unwrap(); + bank.add_account_subscription(bank_sub_id, alice.pubkey(), sink); + + assert!( + bank.account_subscriptions + .write() + .unwrap() + .contains_key(&alice.pubkey()) + ); + + let account = bank.get_account(&alice.pubkey()).unwrap(); + bank.check_account_subscriptions(&alice.pubkey(), &account); + let string = transport_receiver.poll(); + assert!(string.is_ok()); + if let Async::Ready(Some(response)) = string.unwrap() { + let expected = format!(r#"{{"jsonrpc":"2.0","method":"accountNotification","params":{{"result":{{"executable":false,"loader":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"owner":[129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"tokens":1,"userdata":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},"subscription":0}}}}"#); + assert_eq!(expected, response); + } + + bank.remove_account_subscription(&bank_sub_id, &alice.pubkey()); + assert!( + !bank + .account_subscriptions + .write() + .unwrap() + .contains_key(&alice.pubkey()) + ); + } + #[test] + fn test_bank_signature_subscribe() { + let mint = Mint::new(100); + let bank = Bank::new(&mint); + let alice = Keypair::new(); + let bank_sub_id = Keypair::new().pubkey(); + let last_id = bank.last_id(); + let tx = Transaction::system_move(&mint.keypair(), alice.pubkey(), 20, last_id, 0); + let signature = tx.signatures[0]; + bank.process_transaction(&tx).unwrap(); + + let (subscriber, _id_receiver, mut transport_receiver) = + Subscriber::new_test("signatureNotification"); + let sub_id = SubscriptionId::Number(0 as u64); + let sink = subscriber.assign_id(sub_id.clone()).unwrap(); + bank.add_signature_subscription(bank_sub_id, signature, sink); + + assert!( + bank.signature_subscriptions + .write() + .unwrap() + .contains_key(&signature) + ); + + bank.check_signature_subscriptions(&signature, RpcSignatureStatus::Confirmed); + let string = transport_receiver.poll(); + assert!(string.is_ok()); + if let Async::Ready(Some(response)) = string.unwrap() { + let expected = format!(r#"{{"jsonrpc":"2.0","method":"signatureNotification","params":{{"result":"Confirmed","subscription":0}}}}"#); + assert_eq!(expected, response); + } + + bank.remove_signature_subscription(&bank_sub_id, &signature); + assert!( + !bank + .signature_subscriptions + .write() + .unwrap() + .contains_key(&signature) + ); + } + #[test] + fn test_first_err() { + assert_eq!(Bank::first_err(&[Ok(())]), Ok(())); + assert_eq!( + Bank::first_err(&[Ok(()), Err(BankError::DuplicateSignature)]), + Err(BankError::DuplicateSignature) + ); + assert_eq!( + Bank::first_err(&[ + Ok(()), + Err(BankError::DuplicateSignature), + Err(BankError::AccountInUse) + ]), + Err(BankError::DuplicateSignature) + ); + assert_eq!( + Bank::first_err(&[ + Ok(()), + Err(BankError::AccountInUse), + Err(BankError::DuplicateSignature) + ]), + Err(BankError::AccountInUse) + ); + assert_eq!( + Bank::first_err(&[ + Err(BankError::AccountInUse), + Ok(()), + Err(BankError::DuplicateSignature) + ]), + Err(BankError::AccountInUse) + ); + } + #[test] + fn test_par_process_entries_tick() { + let mint = Mint::new(1000); + let bank = Bank::new(&mint); + + // ensure bank can process a tick + let tick = next_entry(&mint.last_id(), 1, vec![]); + assert_eq!(bank.par_process_entries(&[tick.clone()]), Ok(())); + assert_eq!(bank.last_id(), tick.id); + } + #[test] + fn test_par_process_entries_2_entries_collision() { + let mint = Mint::new(1000); + let bank = Bank::new(&mint); + let keypair1 = Keypair::new(); + let keypair2 = Keypair::new(); + + let last_id = bank.last_id(); + + // ensure bank can process 2 entries that have a common account and no tick is registered + let tx = Transaction::system_new(&mint.keypair(), keypair1.pubkey(), 2, bank.last_id()); + let entry_1 = next_entry(&last_id, 1, vec![tx]); + let tx = Transaction::system_new(&mint.keypair(), keypair2.pubkey(), 2, bank.last_id()); + let entry_2 = next_entry(&entry_1.id, 1, vec![tx]); + assert_eq!(bank.par_process_entries(&[entry_1, entry_2]), Ok(())); + assert_eq!(bank.get_balance(&keypair1.pubkey()), 2); + assert_eq!(bank.get_balance(&keypair2.pubkey()), 2); + assert_eq!(bank.last_id(), last_id); + } + #[test] + fn test_par_process_entries_2_entries_par() { + let mint = Mint::new(1000); + let bank = Bank::new(&mint); + let keypair1 = Keypair::new(); + let keypair2 = Keypair::new(); + let keypair3 = Keypair::new(); + let keypair4 = Keypair::new(); + + //load accounts + let tx = Transaction::system_new(&mint.keypair(), keypair1.pubkey(), 1, bank.last_id()); + assert_eq!(bank.process_transaction(&tx), Ok(())); + let tx = Transaction::system_new(&mint.keypair(), keypair2.pubkey(), 1, bank.last_id()); + assert_eq!(bank.process_transaction(&tx), Ok(())); + + // ensure bank can process 2 entries that do not have a common account and no tick is registered + let last_id = bank.last_id(); + let tx = Transaction::system_new(&keypair1, keypair3.pubkey(), 1, bank.last_id()); + let entry_1 = next_entry(&last_id, 1, vec![tx]); + let tx = Transaction::system_new(&keypair2, keypair4.pubkey(), 1, bank.last_id()); + let entry_2 = next_entry(&entry_1.id, 1, vec![tx]); + assert_eq!(bank.par_process_entries(&[entry_1, entry_2]), Ok(())); + assert_eq!(bank.get_balance(&keypair3.pubkey()), 1); + assert_eq!(bank.get_balance(&keypair4.pubkey()), 1); + assert_eq!(bank.last_id(), last_id); + } + #[test] + fn test_par_process_entries_2_entries_tick() { + let mint = Mint::new(1000); + let bank = Bank::new(&mint); + let keypair1 = Keypair::new(); + let keypair2 = Keypair::new(); + let keypair3 = Keypair::new(); + let keypair4 = Keypair::new(); + + //load accounts + let tx = Transaction::system_new(&mint.keypair(), keypair1.pubkey(), 1, bank.last_id()); + assert_eq!(bank.process_transaction(&tx), Ok(())); + let tx = Transaction::system_new(&mint.keypair(), keypair2.pubkey(), 1, bank.last_id()); + assert_eq!(bank.process_transaction(&tx), Ok(())); + + let last_id = bank.last_id(); + + // ensure bank can process 2 entries that do not have a common account and tick is registered + let tx = Transaction::system_new(&keypair2, keypair3.pubkey(), 1, bank.last_id()); + let entry_1 = next_entry(&last_id, 1, vec![tx]); + let new_tick = next_entry(&entry_1.id, 1, vec![]); + let tx = Transaction::system_new(&keypair1, keypair4.pubkey(), 1, new_tick.id); + let entry_2 = next_entry(&new_tick.id, 1, vec![tx]); + assert_eq!( + bank.par_process_entries(&[entry_1.clone(), new_tick.clone(), entry_2]), + Ok(()) + ); + assert_eq!(bank.get_balance(&keypair3.pubkey()), 1); + assert_eq!(bank.get_balance(&keypair4.pubkey()), 1); + assert_eq!(bank.last_id(), new_tick.id); + // ensure that errors are returned + assert_eq!( + bank.par_process_entries(&[entry_1]), + Err(BankError::AccountNotFound) + ); + } + + #[test] + fn test_program_ids() { + let system = Pubkey::new(&[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]); + let native = Pubkey::new(&[ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]); + let bpf = Pubkey::new(&[ + 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]); + let budget = Pubkey::new(&[ + 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]); + let storage = Pubkey::new(&[ + 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]); + let token = Pubkey::new(&[ + 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]); + let vote = Pubkey::new(&[ + 132, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]); + + assert_eq!(SystemProgram::id(), system); + assert_eq!(native_loader::id(), native); + assert_eq!(bpf_loader::id(), bpf); + assert_eq!(BudgetState::id(), budget); + assert_eq!(StorageProgram::id(), storage); + assert_eq!(token_program::id(), token); + assert_eq!(VoteProgram::id(), vote); + } + + #[test] + fn test_program_id_uniqueness() { + let mut unique = HashSet::new(); + let ids = vec![ + SystemProgram::id(), + native_loader::id(), + bpf_loader::id(), + BudgetState::id(), + StorageProgram::id(), + token_program::id(), + VoteProgram::id(), + ]; + assert!(ids.into_iter().all(move |id| unique.insert(id))); + } + + #[test] + fn test_bank_purge() { + let alice = Mint::new(10_000); + let bank = Bank::new(&alice); + let bob = Keypair::new(); + let charlie = Keypair::new(); + + // bob should have 500 + bank.transfer(500, &alice.keypair(), bob.pubkey(), alice.last_id()) + .unwrap(); + assert_eq!(bank.get_balance(&bob.pubkey()), 500); + + bank.transfer(500, &alice.keypair(), charlie.pubkey(), alice.last_id()) + .unwrap(); + assert_eq!(bank.get_balance(&charlie.pubkey()), 500); + + bank.checkpoint(); + bank.checkpoint(); + assert_eq!(bank.checkpoint_depth(), 2); + assert_eq!(bank.get_balance(&bob.pubkey()), 500); + assert_eq!(bank.get_balance(&alice.pubkey()), 9_000); + assert_eq!(bank.get_balance(&charlie.pubkey()), 500); + assert_eq!(bank.transaction_count(), 2); + + // transfer money back, so bob has zero + bank.transfer(500, &bob, alice.keypair().pubkey(), alice.last_id()) + .unwrap(); + // this has to be stored as zero in the top accounts hashmap ;) + assert!(bank.accounts.read().unwrap().load(&bob.pubkey()).is_some()); + assert_eq!(bank.get_balance(&bob.pubkey()), 0); + // double-checks + assert_eq!(bank.get_balance(&alice.pubkey()), 9_500); + assert_eq!(bank.get_balance(&charlie.pubkey()), 500); + assert_eq!(bank.transaction_count(), 3); + bank.purge(1); + + assert_eq!(bank.get_balance(&bob.pubkey()), 0); + // double-checks + assert_eq!(bank.get_balance(&alice.pubkey()), 9_500); + assert_eq!(bank.get_balance(&charlie.pubkey()), 500); + assert_eq!(bank.transaction_count(), 3); + assert_eq!(bank.checkpoint_depth(), 1); + + bank.purge(0); + + // bob should still have 0, alice should have 10_000 + assert_eq!(bank.get_balance(&bob.pubkey()), 0); + assert!(bank.accounts.read().unwrap().load(&bob.pubkey()).is_none()); + // double-checks + assert_eq!(bank.get_balance(&alice.pubkey()), 9_500); + assert_eq!(bank.get_balance(&charlie.pubkey()), 500); + assert_eq!(bank.transaction_count(), 3); + assert_eq!(bank.checkpoint_depth(), 0); + } + + #[test] + fn test_bank_checkpoint_rollback() { + let alice = Mint::new(10_000); + let bank = Bank::new(&alice); + let bob = Keypair::new(); + let charlie = Keypair::new(); + + // bob should have 500 + bank.transfer(500, &alice.keypair(), bob.pubkey(), alice.last_id()) + .unwrap(); + assert_eq!(bank.get_balance(&bob.pubkey()), 500); + bank.transfer(500, &alice.keypair(), charlie.pubkey(), alice.last_id()) + .unwrap(); + assert_eq!(bank.get_balance(&charlie.pubkey()), 500); + assert_eq!(bank.checkpoint_depth(), 0); + + bank.checkpoint(); + bank.checkpoint(); + assert_eq!(bank.checkpoint_depth(), 2); + assert_eq!(bank.get_balance(&bob.pubkey()), 500); + assert_eq!(bank.get_balance(&charlie.pubkey()), 500); + assert_eq!(bank.transaction_count(), 2); + + // transfer money back, so bob has zero + bank.transfer(500, &bob, alice.keypair().pubkey(), alice.last_id()) + .unwrap(); + // this has to be stored as zero in the top accounts hashmap ;) + assert_eq!(bank.get_balance(&bob.pubkey()), 0); + assert_eq!(bank.get_balance(&charlie.pubkey()), 500); + assert_eq!(bank.transaction_count(), 3); + bank.rollback(); + + // bob should have 500 again + assert_eq!(bank.get_balance(&bob.pubkey()), 500); + assert_eq!(bank.get_balance(&charlie.pubkey()), 500); + assert_eq!(bank.transaction_count(), 2); + assert_eq!(bank.checkpoint_depth(), 1); + + let signature = Signature::default(); + for i in 0..MAX_ENTRY_IDS + 1 { + let last_id = hash(&serialize(&i).unwrap()); // Unique hash + bank.register_tick(&last_id); + } + assert_eq!(bank.tick_height(), MAX_ENTRY_IDS as u64 + 2); + assert_eq!( + bank.reserve_signature_with_last_id_test(&signature, &alice.last_id()), + Err(BankError::LastIdNotFound) + ); + bank.rollback(); + assert_eq!(bank.tick_height(), 1); + assert_eq!( + bank.reserve_signature_with_last_id_test(&signature, &alice.last_id()), + Ok(()) + ); + bank.checkpoint(); + assert_eq!( + bank.reserve_signature_with_last_id_test(&signature, &alice.last_id()), + Err(BankError::DuplicateSignature) + ); + } + + #[test] + #[should_panic] + fn test_bank_rollback_panic() { + let alice = Mint::new(10_000); + let bank = Bank::new(&alice); + bank.rollback(); + } + +} diff --git a/book/banking_stage.rs b/book/banking_stage.rs new file mode 100644 index 00000000000000..b96b38c6297b85 --- /dev/null +++ b/book/banking_stage.rs @@ -0,0 +1,449 @@ +//! The `banking_stage` processes Transaction messages. It is intended to be used +//! to contruct a software pipeline. The stage uses all available CPU cores and +//! can do its processing in parallel with signature verification on the GPU. + +use bank::Bank; +use bincode::deserialize; +use compute_leader_finality_service::ComputeLeaderFinalityService; +use counter::Counter; +use entry::Entry; +use log::Level; +use packet::Packets; +use poh_recorder::{PohRecorder, PohRecorderError}; +use poh_service::{Config, PohService}; +use result::{Error, Result}; +use service::Service; +use sigverify_stage::VerifiedPackets; +use solana_sdk::hash::Hash; +use solana_sdk::timing; +use std::net::SocketAddr; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::mpsc::{channel, Receiver, RecvTimeoutError}; +use std::sync::{Arc, Mutex}; +use std::thread::{self, Builder, JoinHandle}; +use std::time::Duration; +use std::time::Instant; +use transaction::Transaction; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum BankingStageReturnType { + LeaderRotation, + ChannelDisconnected, +} + +// number of threads is 1 until mt bank is ready +pub const NUM_THREADS: usize = 10; + +/// Stores the stage's thread handle and output receiver. +pub struct BankingStage { + /// Handle to the stage's thread. + bank_thread_hdls: Vec>>, + poh_service: PohService, + compute_finality_service: ComputeLeaderFinalityService, +} + +impl BankingStage { + /// Create the stage using `bank`. Exit when `verified_receiver` is dropped. + pub fn new( + bank: &Arc, + verified_receiver: Receiver, + config: Config, + last_entry_id: &Hash, + max_tick_height: Option, + ) -> (Self, Receiver>) { + let (entry_sender, entry_receiver) = channel(); + let shared_verified_receiver = Arc::new(Mutex::new(verified_receiver)); + let poh_recorder = + PohRecorder::new(bank.clone(), entry_sender, *last_entry_id, max_tick_height); + + // Single thread to generate entries from many banks. + // This thread talks to poh_service and broadcasts the entries once they have been recorded. + // Once an entry has been recorded, its last_id is registered with the bank. + let poh_service = PohService::new(poh_recorder.clone(), config); + + // Single thread to compute finality + let compute_finality_service = + ComputeLeaderFinalityService::new(bank.clone(), poh_service.poh_exit.clone()); + + // Many banks that process transactions in parallel. + let bank_thread_hdls: Vec>> = (0..NUM_THREADS) + .map(|_| { + let thread_bank = bank.clone(); + let thread_verified_receiver = shared_verified_receiver.clone(); + let thread_poh_recorder = poh_recorder.clone(); + let thread_banking_exit = poh_service.poh_exit.clone(); + Builder::new() + .name("solana-banking-stage-tx".to_string()) + .spawn(move || { + let return_result = loop { + if let Err(e) = Self::process_packets( + &thread_bank, + &thread_verified_receiver, + &thread_poh_recorder, + ) { + debug!("got error {:?}", e); + match e { + Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), + Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => { + break Some(BankingStageReturnType::ChannelDisconnected); + } + Error::RecvError(_) => { + break Some(BankingStageReturnType::ChannelDisconnected); + } + Error::SendError => { + break Some(BankingStageReturnType::ChannelDisconnected); + } + Error::PohRecorderError(PohRecorderError::MaxHeightReached) => { + break Some(BankingStageReturnType::LeaderRotation); + } + _ => error!("solana-banking-stage-tx {:?}", e), + } + } + if thread_banking_exit.load(Ordering::Relaxed) { + break None; + } + }; + thread_banking_exit.store(true, Ordering::Relaxed); + return_result + }).unwrap() + }).collect(); + + ( + BankingStage { + bank_thread_hdls, + poh_service, + compute_finality_service, + }, + entry_receiver, + ) + } + + /// Convert the transactions from a blob of binary data to a vector of transactions and + /// an unused `SocketAddr` that could be used to send a response. + fn deserialize_transactions(p: &Packets) -> Vec> { + p.packets + .iter() + .map(|x| { + deserialize(&x.data[0..x.meta.size]) + .map(|req| (req, x.meta.addr())) + .ok() + }).collect() + } + + fn process_transactions( + bank: &Arc, + transactions: &[Transaction], + poh: &PohRecorder, + ) -> Result<()> { + debug!("transactions: {}", transactions.len()); + let mut chunk_start = 0; + while chunk_start != transactions.len() { + let chunk_end = chunk_start + Entry::num_will_fit(&transactions[chunk_start..]); + + bank.process_and_record_transactions(&transactions[chunk_start..chunk_end], poh)?; + + chunk_start = chunk_end; + } + debug!("done process_transactions"); + Ok(()) + } + + /// Process the incoming packets and send output `Signal` messages to `signal_sender`. + /// Discard packets via `packet_recycler`. + pub fn process_packets( + bank: &Arc, + verified_receiver: &Arc>>, + poh: &PohRecorder, + ) -> Result<()> { + let recv_start = Instant::now(); + let mms = verified_receiver + .lock() + .unwrap() + .recv_timeout(Duration::from_millis(100))?; + let mut reqs_len = 0; + let mms_len = mms.len(); + info!( + "@{:?} process start stalled for: {:?}ms batches: {}", + timing::timestamp(), + timing::duration_as_ms(&recv_start.elapsed()), + mms.len(), + ); + inc_new_counter_info!("banking_stage-entries_received", mms_len); + let count = mms.iter().map(|x| x.1.len()).sum(); + let proc_start = Instant::now(); + let mut new_tx_count = 0; + for (msgs, vers) in mms { + let transactions = Self::deserialize_transactions(&msgs.read().unwrap()); + reqs_len += transactions.len(); + + debug!("transactions received {}", transactions.len()); + + let transactions: Vec<_> = transactions + .into_iter() + .zip(vers) + .filter_map(|(tx, ver)| match tx { + None => None, + Some((tx, _addr)) => { + if tx.verify_refs() && ver != 0 { + Some(tx) + } else { + None + } + } + }).collect(); + debug!("verified transactions {}", transactions.len()); + Self::process_transactions(bank, &transactions, poh)?; + new_tx_count += transactions.len(); + } + + inc_new_counter_info!( + "banking_stage-time_ms", + timing::duration_as_ms(&proc_start.elapsed()) as usize + ); + let total_time_s = timing::duration_as_s(&proc_start.elapsed()); + let total_time_ms = timing::duration_as_ms(&proc_start.elapsed()); + info!( + "@{:?} done processing transaction batches: {} time: {:?}ms reqs: {} reqs/s: {}", + timing::timestamp(), + mms_len, + total_time_ms, + reqs_len, + (reqs_len as f32) / (total_time_s) + ); + inc_new_counter_info!("banking_stage-process_packets", count); + inc_new_counter_info!("banking_stage-process_transactions", new_tx_count); + Ok(()) + } +} + +impl Service for BankingStage { + type JoinReturnType = Option; + + fn join(self) -> thread::Result> { + let mut return_value = None; + + for bank_thread_hdl in self.bank_thread_hdls { + let thread_return_value = bank_thread_hdl.join()?; + if thread_return_value.is_some() { + return_value = thread_return_value; + } + } + + self.compute_finality_service.join()?; + + let poh_return_value = self.poh_service.join()?; + match poh_return_value { + Ok(_) => (), + Err(Error::PohRecorderError(PohRecorderError::MaxHeightReached)) => { + return_value = Some(BankingStageReturnType::LeaderRotation); + } + Err(Error::SendError) => { + return_value = Some(BankingStageReturnType::ChannelDisconnected); + } + Err(_) => (), + } + + Ok(return_value) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bank::Bank; + use banking_stage::BankingStageReturnType; + use ledger::Block; + use mint::Mint; + use packet::to_packets; + use signature::{Keypair, KeypairUtil}; + use std::thread::sleep; + use system_transaction::SystemTransaction; + use transaction::Transaction; + + #[test] + fn test_banking_stage_shutdown1() { + let bank = Arc::new(Bank::new(&Mint::new(2))); + let (verified_sender, verified_receiver) = channel(); + let (banking_stage, _entry_receiver) = BankingStage::new( + &bank, + verified_receiver, + Default::default(), + &bank.last_id(), + None, + ); + drop(verified_sender); + assert_eq!( + banking_stage.join().unwrap(), + Some(BankingStageReturnType::ChannelDisconnected) + ); + } + + #[test] + fn test_banking_stage_shutdown2() { + let bank = Arc::new(Bank::new(&Mint::new(2))); + let (_verified_sender, verified_receiver) = channel(); + let (banking_stage, entry_receiver) = BankingStage::new( + &bank, + verified_receiver, + Default::default(), + &bank.last_id(), + None, + ); + drop(entry_receiver); + assert_eq!( + banking_stage.join().unwrap(), + Some(BankingStageReturnType::ChannelDisconnected) + ); + } + + #[test] + fn test_banking_stage_tick() { + let bank = Arc::new(Bank::new(&Mint::new(2))); + let start_hash = bank.last_id(); + let (verified_sender, verified_receiver) = channel(); + let (banking_stage, entry_receiver) = BankingStage::new( + &bank, + verified_receiver, + Config::Sleep(Duration::from_millis(1)), + &bank.last_id(), + None, + ); + sleep(Duration::from_millis(500)); + drop(verified_sender); + + let entries: Vec<_> = entry_receiver.iter().flat_map(|x| x).collect(); + assert!(entries.len() != 0); + assert!(entries.verify(&start_hash)); + assert_eq!(entries[entries.len() - 1].id, bank.last_id()); + assert_eq!( + banking_stage.join().unwrap(), + Some(BankingStageReturnType::ChannelDisconnected) + ); + } + + #[test] + fn test_banking_stage_entries_only() { + let mint = Mint::new(2); + let bank = Arc::new(Bank::new(&mint)); + let start_hash = bank.last_id(); + let (verified_sender, verified_receiver) = channel(); + let (banking_stage, entry_receiver) = BankingStage::new( + &bank, + verified_receiver, + Default::default(), + &bank.last_id(), + None, + ); + + // good tx + let keypair = mint.keypair(); + let tx = Transaction::system_new(&keypair, keypair.pubkey(), 1, start_hash); + + // good tx, but no verify + let tx_no_ver = Transaction::system_new(&keypair, keypair.pubkey(), 1, start_hash); + + // bad tx, AccountNotFound + let keypair = Keypair::new(); + let tx_anf = Transaction::system_new(&keypair, keypair.pubkey(), 1, start_hash); + + // send 'em over + let packets = to_packets(&[tx, tx_no_ver, tx_anf]); + + // glad they all fit + assert_eq!(packets.len(), 1); + verified_sender // tx, no_ver, anf + .send(vec![(packets[0].clone(), vec![1u8, 0u8, 1u8])]) + .unwrap(); + + drop(verified_sender); + + //receive entries + ticks + let entries: Vec<_> = entry_receiver.iter().map(|x| x).collect(); + assert!(entries.len() >= 1); + + let mut last_id = start_hash; + entries.iter().for_each(|entries| { + assert_eq!(entries.len(), 1); + assert!(entries.verify(&last_id)); + last_id = entries.last().unwrap().id; + }); + drop(entry_receiver); + assert_eq!( + banking_stage.join().unwrap(), + Some(BankingStageReturnType::ChannelDisconnected) + ); + } + #[test] + fn test_banking_stage_entryfication() { + // In this attack we'll demonstrate that a verifier can interpret the ledger + // differently if either the server doesn't signal the ledger to add an + // Entry OR if the verifier tries to parallelize across multiple Entries. + let mint = Mint::new(2); + let bank = Arc::new(Bank::new(&mint)); + let (verified_sender, verified_receiver) = channel(); + let (banking_stage, entry_receiver) = BankingStage::new( + &bank, + verified_receiver, + Default::default(), + &bank.last_id(), + None, + ); + + // Process a batch that includes a transaction that receives two tokens. + let alice = Keypair::new(); + let tx = Transaction::system_new(&mint.keypair(), alice.pubkey(), 2, mint.last_id()); + + let packets = to_packets(&[tx]); + verified_sender + .send(vec![(packets[0].clone(), vec![1u8])]) + .unwrap(); + + // Process a second batch that spends one of those tokens. + let tx = Transaction::system_new(&alice, mint.pubkey(), 1, mint.last_id()); + let packets = to_packets(&[tx]); + verified_sender + .send(vec![(packets[0].clone(), vec![1u8])]) + .unwrap(); + drop(verified_sender); + assert_eq!( + banking_stage.join().unwrap(), + Some(BankingStageReturnType::ChannelDisconnected) + ); + + // Collect the ledger and feed it to a new bank. + let entries: Vec<_> = entry_receiver.iter().flat_map(|x| x).collect(); + // same assertion as running through the bank, really... + assert!(entries.len() >= 2); + + // Assert the user holds one token, not two. If the stage only outputs one + // entry, then the second transaction will be rejected, because it drives + // the account balance below zero before the credit is added. + let bank = Bank::new(&mint); + for entry in entries { + bank.process_transactions(&entry.transactions) + .iter() + .for_each(|x| assert_eq!(*x, Ok(()))); + } + assert_eq!(bank.get_balance(&alice.pubkey()), 1); + } + + // Test that when the max_tick_height is reached, the banking stage exits + // with reason BankingStageReturnType::LeaderRotation + #[test] + fn test_max_tick_height_shutdown() { + let bank = Arc::new(Bank::new(&Mint::new(2))); + let (_verified_sender_, verified_receiver) = channel(); + let max_tick_height = 10; + let (banking_stage, _entry_receiver) = BankingStage::new( + &bank, + verified_receiver, + Default::default(), + &bank.last_id(), + Some(max_tick_height), + ); + assert_eq!( + banking_stage.join().unwrap(), + Some(BankingStageReturnType::LeaderRotation) + ); + } +} diff --git a/book/bin/bench-streamer.rs b/book/bin/bench-streamer.rs new file mode 100644 index 00000000000000..149cc97d5084ea --- /dev/null +++ b/book/bin/bench-streamer.rs @@ -0,0 +1,124 @@ +extern crate clap; +extern crate solana; + +use clap::{App, Arg}; +use solana::netutil::bind_to; +use solana::packet::{Packet, SharedPackets, BLOB_SIZE, PACKET_DATA_SIZE}; +use solana::result::Result; +use solana::streamer::{receiver, PacketReceiver}; +use std::cmp::max; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::mpsc::channel; +use std::sync::Arc; +use std::thread::sleep; +use std::thread::{spawn, JoinHandle}; +use std::time::Duration; +use std::time::SystemTime; + +fn producer(addr: &SocketAddr, exit: Arc) -> JoinHandle<()> { + let send = UdpSocket::bind("0.0.0.0:0").unwrap(); + let msgs = SharedPackets::default(); + let msgs_ = msgs.clone(); + msgs.write().unwrap().packets.resize(10, Packet::default()); + for w in &mut msgs.write().unwrap().packets { + w.meta.size = PACKET_DATA_SIZE; + w.meta.set_addr(&addr); + } + spawn(move || loop { + if exit.load(Ordering::Relaxed) { + return; + } + let mut num = 0; + for p in &msgs_.read().unwrap().packets { + let a = p.meta.addr(); + assert!(p.meta.size < BLOB_SIZE); + send.send_to(&p.data[..p.meta.size], &a).unwrap(); + num += 1; + } + assert_eq!(num, 10); + }) +} + +fn sink(exit: Arc, rvs: Arc, r: PacketReceiver) -> JoinHandle<()> { + spawn(move || loop { + if exit.load(Ordering::Relaxed) { + return; + } + let timer = Duration::new(1, 0); + if let Ok(msgs) = r.recv_timeout(timer) { + rvs.fetch_add(msgs.read().unwrap().packets.len(), Ordering::Relaxed); + } + }) +} + +fn main() -> Result<()> { + let mut num_sockets = 1usize; + + let matches = App::new("solana-bench-streamer") + .arg( + Arg::with_name("num-recv-sockets") + .long("num-recv-sockets") + .value_name("NUM") + .takes_value(true) + .help("Use NUM receive sockets"), + ).get_matches(); + + if let Some(n) = matches.value_of("num-recv-sockets") { + num_sockets = max(num_sockets, n.to_string().parse().expect("integer")); + } + + let mut port = 0; + let mut addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0); + + let exit = Arc::new(AtomicBool::new(false)); + + let mut read_channels = Vec::new(); + let mut read_threads = Vec::new(); + for _ in 0..num_sockets { + let read = bind_to(port, false).unwrap(); + read.set_read_timeout(Some(Duration::new(1, 0))).unwrap(); + + addr = read.local_addr().unwrap(); + port = addr.port(); + + let (s_reader, r_reader) = channel(); + read_channels.push(r_reader); + read_threads.push(receiver( + Arc::new(read), + exit.clone(), + s_reader, + "bench-streamer", + )); + } + + let t_producer1 = producer(&addr, exit.clone()); + let t_producer2 = producer(&addr, exit.clone()); + let t_producer3 = producer(&addr, exit.clone()); + + let rvs = Arc::new(AtomicUsize::new(0)); + let sink_threads: Vec<_> = read_channels + .into_iter() + .map(|r_reader| sink(exit.clone(), rvs.clone(), r_reader)) + .collect(); + let start = SystemTime::now(); + let start_val = rvs.load(Ordering::Relaxed); + sleep(Duration::new(5, 0)); + let elapsed = start.elapsed().unwrap(); + let end_val = rvs.load(Ordering::Relaxed); + let time = elapsed.as_secs() * 10_000_000_000 + u64::from(elapsed.subsec_nanos()); + let ftime = (time as f64) / 10_000_000_000_f64; + let fcount = (end_val - start_val) as f64; + println!("performance: {:?}", fcount / ftime); + exit.store(true, Ordering::Relaxed); + for t_reader in read_threads { + t_reader.join()?; + } + t_producer1.join()?; + t_producer2.join()?; + t_producer3.join()?; + for t_sink in sink_threads { + t_sink.join()?; + } + Ok(()) +} diff --git a/book/bin/bench-tps.rs b/book/bin/bench-tps.rs new file mode 100644 index 00000000000000..7c5e5a70aa2bbc --- /dev/null +++ b/book/bin/bench-tps.rs @@ -0,0 +1,873 @@ +extern crate bincode; +#[macro_use] +extern crate clap; +extern crate rand; +extern crate rayon; +#[macro_use] +extern crate log; +extern crate serde_json; +#[macro_use] +extern crate solana; +extern crate solana_drone; +extern crate solana_metrics; +extern crate solana_sdk; + +use clap::{App, Arg}; + +use rand::{thread_rng, Rng}; +use rayon::prelude::*; +use solana::client::mk_client; +use solana::cluster_info::{ClusterInfo, NodeInfo}; +use solana::logger; +use solana::ncp::Ncp; +use solana::service::Service; +use solana::signature::{read_keypair, GenKeys, Keypair, KeypairUtil}; +use solana::system_transaction::SystemTransaction; +use solana::thin_client::{poll_gossip_for_leader, ThinClient}; +use solana::transaction::Transaction; +use solana::window::default_window; +use solana_drone::drone::{request_airdrop_transaction, DRONE_PORT}; +use solana_metrics::influxdb; +use solana_sdk::hash::Hash; +use solana_sdk::timing::timestamp; +use solana_sdk::timing::{duration_as_ms, duration_as_s}; +use std::cmp; +use std::collections::VecDeque; +use std::net::SocketAddr; +use std::process::exit; +use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering}; +use std::sync::{Arc, RwLock}; +use std::thread::sleep; +use std::thread::Builder; +use std::time::Duration; +use std::time::Instant; + +pub struct NodeStats { + pub tps: f64, // Maximum TPS reported by this node + pub tx: u64, // Total transactions reported by this node +} + +fn metrics_submit_token_balance(token_balance: u64) { + println!("Token balance: {}", token_balance); + solana_metrics::submit( + influxdb::Point::new("bench-tps") + .add_tag("op", influxdb::Value::String("token_balance".to_string())) + .add_field("balance", influxdb::Value::Integer(token_balance as i64)) + .to_owned(), + ); +} + +fn sample_tx_count( + exit_signal: &Arc, + maxes: &Arc>>, + first_tx_count: u64, + v: &NodeInfo, + sample_period: u64, +) { + let mut client = mk_client(&v); + let mut now = Instant::now(); + let mut initial_tx_count = client.transaction_count(); + let mut max_tps = 0.0; + let mut total; + + let log_prefix = format!("{:21}:", v.tpu.to_string()); + + loop { + let tx_count = client.transaction_count(); + assert!( + tx_count >= initial_tx_count, + "expected tx_count({}) >= initial_tx_count({})", + tx_count, + initial_tx_count + ); + let duration = now.elapsed(); + now = Instant::now(); + let sample = tx_count - initial_tx_count; + initial_tx_count = tx_count; + + let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos()); + let tps = (sample * 1_000_000_000) as f64 / ns as f64; + if tps > max_tps { + max_tps = tps; + } + if tx_count > first_tx_count { + total = tx_count - first_tx_count; + } else { + total = 0; + } + println!( + "{} {:9.2} TPS, Transactions: {:6}, Total transactions: {}", + log_prefix, tps, sample, total + ); + sleep(Duration::new(sample_period, 0)); + + if exit_signal.load(Ordering::Relaxed) { + println!("{} Exiting validator thread", log_prefix); + let stats = NodeStats { + tps: max_tps, + tx: total, + }; + maxes.write().unwrap().push((v.tpu, stats)); + break; + } + } +} + +/// Send loopback payment of 0 tokens and confirm the network processed it +fn send_barrier_transaction(barrier_client: &mut ThinClient, last_id: &mut Hash, id: &Keypair) { + let transfer_start = Instant::now(); + + let mut poll_count = 0; + loop { + if poll_count > 0 && poll_count % 8 == 0 { + println!( + "polling for barrier transaction confirmation, attempt {}", + poll_count + ); + } + + *last_id = barrier_client.get_last_id(); + let signature = barrier_client + .transfer(0, &id, id.pubkey(), last_id) + .expect("Unable to send barrier transaction"); + + let confirmatiom = barrier_client.poll_for_signature(&signature); + let duration_ms = duration_as_ms(&transfer_start.elapsed()); + if confirmatiom.is_ok() { + println!("barrier transaction confirmed in {} ms", duration_ms); + + solana_metrics::submit( + influxdb::Point::new("bench-tps") + .add_tag( + "op", + influxdb::Value::String("send_barrier_transaction".to_string()), + ).add_field("poll_count", influxdb::Value::Integer(poll_count)) + .add_field("duration", influxdb::Value::Integer(duration_ms as i64)) + .to_owned(), + ); + + // Sanity check that the client balance is still 1 + let balance = barrier_client + .poll_balance_with_timeout( + &id.pubkey(), + &Duration::from_millis(100), + &Duration::from_secs(10), + ).expect("Failed to get balance"); + if balance != 1 { + panic!("Expected an account balance of 1 (balance: {}", balance); + } + break; + } + + // Timeout after 3 minutes. When running a CPU-only leader+validator+drone+bench-tps on a dev + // machine, some batches of transactions can take upwards of 1 minute... + if duration_ms > 1000 * 60 * 3 { + println!("Error: Couldn't confirm barrier transaction!"); + exit(1); + } + + let new_last_id = barrier_client.get_last_id(); + if new_last_id == *last_id { + if poll_count > 0 && poll_count % 8 == 0 { + println!("last_id is not advancing, still at {:?}", *last_id); + } + } else { + *last_id = new_last_id; + } + + poll_count += 1; + } +} + +type SharedTransactions = Arc>>>; +fn generate_txs( + shared_txs: &SharedTransactions, + source: &[Keypair], + dest: &[Keypair], + threads: usize, + reclaim: bool, + leader: &NodeInfo, +) { + let mut client = mk_client(leader); + let last_id = client.get_last_id(); + info!("last_id: {} {:?}", last_id, Instant::now()); + let tx_count = source.len(); + println!("Signing transactions... {} (reclaim={})", tx_count, reclaim); + let signing_start = Instant::now(); + + let pairs: Vec<_> = if !reclaim { + source.iter().zip(dest.iter()).collect() + } else { + dest.iter().zip(source.iter()).collect() + }; + let transactions: Vec<_> = pairs + .par_iter() + .map(|(id, keypair)| { + ( + Transaction::system_new(id, keypair.pubkey(), 1, last_id), + timestamp(), + ) + }).collect(); + + let duration = signing_start.elapsed(); + let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos()); + let bsps = (tx_count) as f64 / ns as f64; + let nsps = ns as f64 / (tx_count) as f64; + println!( + "Done. {:.2} thousand signatures per second, {:.2} us per signature, {} ms total time, {}", + bsps * 1_000_000_f64, + nsps / 1_000_f64, + duration_as_ms(&duration), + last_id, + ); + solana_metrics::submit( + influxdb::Point::new("bench-tps") + .add_tag("op", influxdb::Value::String("generate_txs".to_string())) + .add_field( + "duration", + influxdb::Value::Integer(duration_as_ms(&duration) as i64), + ).to_owned(), + ); + + let sz = transactions.len() / threads; + let chunks: Vec<_> = transactions.chunks(sz).collect(); + { + let mut shared_txs_wl = shared_txs.write().unwrap(); + for chunk in chunks { + shared_txs_wl.push_back(chunk.to_vec()); + } + } +} + +fn do_tx_transfers( + exit_signal: &Arc, + shared_txs: &SharedTransactions, + leader: &NodeInfo, + shared_tx_thread_count: &Arc, + total_tx_sent_count: &Arc, +) { + let client = mk_client(&leader); + loop { + let txs; + { + let mut shared_txs_wl = shared_txs.write().unwrap(); + txs = shared_txs_wl.pop_front(); + } + if let Some(txs0) = txs { + shared_tx_thread_count.fetch_add(1, Ordering::Relaxed); + println!( + "Transferring 1 unit {} times... to {}", + txs0.len(), + leader.tpu + ); + let tx_len = txs0.len(); + let transfer_start = Instant::now(); + for tx in txs0 { + let now = timestamp(); + if now > tx.1 && now - tx.1 > 1000 * 30 { + continue; + } + client.transfer_signed(&tx.0).unwrap(); + } + shared_tx_thread_count.fetch_add(-1, Ordering::Relaxed); + total_tx_sent_count.fetch_add(tx_len, Ordering::Relaxed); + println!( + "Tx send done. {} ms {} tps", + duration_as_ms(&transfer_start.elapsed()), + tx_len as f32 / duration_as_s(&transfer_start.elapsed()), + ); + solana_metrics::submit( + influxdb::Point::new("bench-tps") + .add_tag("op", influxdb::Value::String("do_tx_transfers".to_string())) + .add_field( + "duration", + influxdb::Value::Integer(duration_as_ms(&transfer_start.elapsed()) as i64), + ).add_field("count", influxdb::Value::Integer(tx_len as i64)) + .to_owned(), + ); + } + if exit_signal.load(Ordering::Relaxed) { + break; + } + } +} + +const MAX_SPENDS_PER_TX: usize = 4; +fn verify_transfer(client: &mut ThinClient, tx: &Transaction) -> bool { + if client.poll_for_signature(&tx.signatures[0]).is_err() { + println!("no signature"); + return false; + } + for a in &tx.account_keys[1..] { + if client.poll_get_balance(a).unwrap_or(0) == 0 { + println!( + "no balance {} source bal: {} {:?}", + a, + client.poll_get_balance(&tx.account_keys[0]).unwrap_or(0), + tx + ); + return false; + } + } + true +} +/// fund the dests keys by spending all of the source keys into MAX_SPENDS_PER_TX +/// on every iteration. This allows us to replay the transfers because the source is either empty, +/// or full +fn fund_keys(client: &mut ThinClient, source: &Keypair, dests: &[Keypair], tokens: u64) { + let total = tokens * dests.len() as u64; + let mut funded: Vec<(&Keypair, u64)> = vec![(source, total)]; + let mut notfunded: Vec<&Keypair> = dests.iter().collect(); + + println!("funding keys {}", dests.len()); + while !notfunded.is_empty() { + let mut new_funded: Vec<(&Keypair, u64)> = vec![]; + let mut to_fund = vec![]; + println!("creating from... {}", funded.len()); + for f in &mut funded { + let max_units = cmp::min(notfunded.len(), MAX_SPENDS_PER_TX); + if max_units == 0 { + break; + } + let start = notfunded.len() - max_units; + let per_unit = f.1 / (max_units as u64); + let moves: Vec<_> = notfunded[start..] + .iter() + .map(|k| (k.pubkey(), per_unit)) + .collect(); + notfunded[start..] + .iter() + .for_each(|k| new_funded.push((k, per_unit))); + notfunded.truncate(start); + if !moves.is_empty() { + to_fund.push((f.0, moves)); + } + } + println!("sending... {}", to_fund.len()); + // try to transfer a few at a time with recent last_id + to_fund.chunks(10_000).for_each(|chunk| { + loop { + let last_id = client.get_last_id(); + println!("generating... {} {}", chunk.len(), last_id); + let mut to_fund_txs: Vec<_> = chunk + .par_iter() + .map(|(k, m)| Transaction::system_move_many(k, &m, last_id, 0)) + .collect(); + // with randomly distributed the failures + // most of the account pairs should have some funding in one of the pairs + // durring generate_tx step + thread_rng().shuffle(&mut to_fund_txs); + println!("transfering... {}", chunk.len()); + to_fund_txs.iter().for_each(|tx| { + let _ = client.transfer_signed(&tx).expect("transfer"); + }); + // randomly sample some of the transfers + thread_rng().shuffle(&mut to_fund_txs); + let max = cmp::min(10, to_fund_txs.len()); + if to_fund_txs[..max] + .iter() + .all(|tx| verify_transfer(client, tx)) + { + break; + } + } + }); + println!("funded: {} left: {}", new_funded.len(), notfunded.len()); + funded = new_funded; + } +} + +fn airdrop_tokens(client: &mut ThinClient, drone_addr: &SocketAddr, id: &Keypair, tx_count: u64) { + let starting_balance = client.poll_get_balance(&id.pubkey()).unwrap_or(0); + metrics_submit_token_balance(starting_balance); + println!("starting balance {}", starting_balance); + + if starting_balance < tx_count { + let airdrop_amount = tx_count - starting_balance; + println!( + "Airdropping {:?} tokens from {} for {}", + airdrop_amount, + drone_addr, + id.pubkey(), + ); + + let last_id = client.get_last_id(); + match request_airdrop_transaction(&drone_addr, &id.pubkey(), airdrop_amount, last_id) { + Ok(transaction) => { + let signature = client.transfer_signed(&transaction).unwrap(); + client.poll_for_signature(&signature).unwrap(); + } + Err(err) => { + panic!( + "Error requesting airdrop: {:?} to addr: {:?} amount: {}", + err, drone_addr, airdrop_amount + ); + } + }; + + let current_balance = client.poll_get_balance(&id.pubkey()).unwrap_or_else(|e| { + println!("airdrop error {}", e); + starting_balance + }); + println!("current balance {}...", current_balance); + + metrics_submit_token_balance(current_balance); + if current_balance - starting_balance != airdrop_amount { + println!( + "Airdrop failed! {} {} {}", + id.pubkey(), + current_balance, + starting_balance + ); + exit(1); + } + } +} + +fn compute_and_report_stats( + maxes: &Arc>>, + sample_period: u64, + tx_send_elapsed: &Duration, + total_tx_send_count: usize, +) { + // Compute/report stats + let mut max_of_maxes = 0.0; + let mut max_tx_count = 0; + let mut nodes_with_zero_tps = 0; + let mut total_maxes = 0.0; + println!(" Node address | Max TPS | Total Transactions"); + println!("---------------------+---------------+--------------------"); + + for (sock, stats) in maxes.read().unwrap().iter() { + let maybe_flag = match stats.tx { + 0 => "!!!!!", + _ => "", + }; + + println!( + "{:20} | {:13.2} | {} {}", + (*sock).to_string(), + stats.tps, + stats.tx, + maybe_flag + ); + + if stats.tps == 0.0 { + nodes_with_zero_tps += 1; + } + total_maxes += stats.tps; + + if stats.tps > max_of_maxes { + max_of_maxes = stats.tps; + } + if stats.tx > max_tx_count { + max_tx_count = stats.tx; + } + } + + if total_maxes > 0.0 { + let num_nodes_with_tps = maxes.read().unwrap().len() - nodes_with_zero_tps; + let average_max = total_maxes / num_nodes_with_tps as f64; + println!( + "\nAverage max TPS: {:.2}, {} nodes had 0 TPS", + average_max, nodes_with_zero_tps + ); + } + + println!( + "\nHighest TPS: {:.2} sampling period {}s max transactions: {} clients: {} drop rate: {:.2}", + max_of_maxes, + sample_period, + max_tx_count, + maxes.read().unwrap().len(), + (total_tx_send_count as u64 - max_tx_count) as f64 / total_tx_send_count as f64, + ); + println!( + "\tAverage TPS: {}", + max_tx_count as f32 / duration_as_s(tx_send_elapsed) + ); +} + +// First transfer 3/4 of the tokens to the dest accounts +// then ping-pong 1/4 of the tokens back to the other account +// this leaves 1/4 token buffer in each account +fn should_switch_directions(num_tokens_per_account: u64, i: u64) -> bool { + i % (num_tokens_per_account / 4) == 0 && (i >= (3 * num_tokens_per_account) / 4) +} + +fn main() { + logger::setup(); + solana_metrics::set_panic_hook("bench-tps"); + + let matches = App::new("solana-bench-tps") + .version(crate_version!()) + .arg( + Arg::with_name("network") + .short("n") + .long("network") + .value_name("HOST:PORT") + .takes_value(true) + .help("Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001"), + ) + .arg( + Arg::with_name("drone") + .short("d") + .long("drone") + .value_name("HOST:PORT") + .takes_value(true) + .help("Location of the drone; defaults to network:DRONE_PORT"), + ) + .arg( + Arg::with_name("identity") + .short("i") + .long("identity") + .value_name("PATH") + .takes_value(true) + .required(true) + .help("File containing a client identity (keypair)"), + ) + .arg( + Arg::with_name("num-nodes") + .short("N") + .long("num-nodes") + .value_name("NUM") + .takes_value(true) + .help("Wait for NUM nodes to converge"), + ) + .arg( + Arg::with_name("reject-extra-nodes") + .long("reject-extra-nodes") + .help("Require exactly `num-nodes` on convergence. Appropriate only for internal networks"), + ) + .arg( + Arg::with_name("threads") + .short("t") + .long("threads") + .value_name("NUM") + .takes_value(true) + .help("Number of threads"), + ) + .arg( + Arg::with_name("duration") + .long("duration") + .value_name("SECS") + .takes_value(true) + .help("Seconds to run benchmark, then exit; default is forever"), + ) + .arg( + Arg::with_name("converge-only") + .long("converge-only") + .help("Exit immediately after converging"), + ) + .arg( + Arg::with_name("sustained") + .long("sustained") + .help("Use sustained performance mode vs. peak mode. This overlaps the tx generation with transfers."), + ) + .arg( + Arg::with_name("tx_count") + .long("tx_count") + .value_name("NUM") + .takes_value(true) + .help("Number of transactions to send per batch") + ) + .get_matches(); + + let network = if let Some(addr) = matches.value_of("network") { + addr.parse().unwrap_or_else(|e| { + eprintln!("failed to parse network: {}", e); + exit(1) + }) + } else { + socketaddr!("127.0.0.1:8001") + }; + + let drone_addr = if let Some(addr) = matches.value_of("drone") { + addr.parse().unwrap_or_else(|e| { + eprintln!("failed to parse drone address: {}", e); + exit(1) + }) + } else { + let mut addr = network; + addr.set_port(DRONE_PORT); + addr + }; + + let id = + read_keypair(matches.value_of("identity").unwrap()).expect("can't read client identity"); + + let threads = if let Some(t) = matches.value_of("threads") { + t.to_string().parse().expect("can't parse threads") + } else { + 4usize + }; + + let num_nodes = if let Some(n) = matches.value_of("num-nodes") { + n.to_string().parse().expect("can't parse num-nodes") + } else { + 1usize + }; + + let duration = if let Some(s) = matches.value_of("duration") { + Duration::new(s.to_string().parse().expect("can't parse duration"), 0) + } else { + Duration::new(std::u64::MAX, 0) + }; + + let tx_count = if let Some(s) = matches.value_of("tx_count") { + s.to_string().parse().expect("can't parse tx_count") + } else { + 500_000 + }; + + let sustained = matches.is_present("sustained"); + + println!("Looking for leader at {:?}", network); + let leader = poll_gossip_for_leader(network, None).expect("unable to find leader on network"); + + let exit_signal = Arc::new(AtomicBool::new(false)); + let (nodes, leader, ncp) = converge(&leader, &exit_signal, num_nodes); + + if nodes.len() < num_nodes { + println!( + "Error: Insufficient nodes discovered. Expecting {} or more", + num_nodes + ); + exit(1); + } + if matches.is_present("reject-extra-nodes") && nodes.len() > num_nodes { + println!( + "Error: Extra nodes discovered. Expecting exactly {}", + num_nodes + ); + exit(1); + } + + if leader.is_none() { + println!("no leader"); + exit(1); + } + + if matches.is_present("converge-only") { + return; + } + + let leader = leader.unwrap(); + + println!("leader RPC is at {} {}", leader.rpc, leader.id); + let mut client = mk_client(&leader); + let mut barrier_client = mk_client(&leader); + + let mut seed = [0u8; 32]; + seed.copy_from_slice(&id.public_key_bytes()[..32]); + let mut rnd = GenKeys::new(seed); + + println!("Creating {} keypairs...", tx_count * 2); + let mut total_keys = 0; + let mut target = tx_count * 2; + while target > 0 { + total_keys += target; + target /= MAX_SPENDS_PER_TX; + } + let gen_keypairs = rnd.gen_n_keypairs(total_keys as u64); + let barrier_id = rnd.gen_n_keypairs(1).pop().unwrap(); + + println!("Get tokens..."); + let num_tokens_per_account = 20; + + // Sample the first keypair, see if it has tokens, if so then resume + // to avoid token loss + let keypair0_balance = client + .poll_get_balance(&gen_keypairs.last().unwrap().pubkey()) + .unwrap_or(0); + + if num_tokens_per_account > keypair0_balance { + let extra = num_tokens_per_account - keypair0_balance; + let total = extra * (gen_keypairs.len() as u64); + airdrop_tokens(&mut client, &drone_addr, &id, total); + println!("adding more tokens {}", extra); + fund_keys(&mut client, &id, &gen_keypairs, extra); + } + let start = gen_keypairs.len() - (tx_count * 2) as usize; + let keypairs = &gen_keypairs[start..]; + airdrop_tokens(&mut barrier_client, &drone_addr, &barrier_id, 1); + + println!("Get last ID..."); + let mut last_id = client.get_last_id(); + println!("Got last ID {:?}", last_id); + + let first_tx_count = client.transaction_count(); + println!("Initial transaction count {}", first_tx_count); + + // Setup a thread per validator to sample every period + // collect the max transaction rate and total tx count seen + let maxes = Arc::new(RwLock::new(Vec::new())); + let sample_period = 1; // in seconds + println!("Sampling TPS every {} second...", sample_period); + let v_threads: Vec<_> = nodes + .into_iter() + .map(|v| { + let exit_signal = exit_signal.clone(); + let maxes = maxes.clone(); + Builder::new() + .name("solana-client-sample".to_string()) + .spawn(move || { + sample_tx_count(&exit_signal, &maxes, first_tx_count, &v, sample_period); + }).unwrap() + }).collect(); + + let shared_txs: SharedTransactions = Arc::new(RwLock::new(VecDeque::new())); + + let shared_tx_active_thread_count = Arc::new(AtomicIsize::new(0)); + let total_tx_sent_count = Arc::new(AtomicUsize::new(0)); + + let s_threads: Vec<_> = (0..threads) + .map(|_| { + let exit_signal = exit_signal.clone(); + let shared_txs = shared_txs.clone(); + let leader = leader.clone(); + let shared_tx_active_thread_count = shared_tx_active_thread_count.clone(); + let total_tx_sent_count = total_tx_sent_count.clone(); + Builder::new() + .name("solana-client-sender".to_string()) + .spawn(move || { + do_tx_transfers( + &exit_signal, + &shared_txs, + &leader, + &shared_tx_active_thread_count, + &total_tx_sent_count, + ); + }).unwrap() + }).collect(); + + // generate and send transactions for the specified duration + let start = Instant::now(); + let mut reclaim_tokens_back_to_source_account = false; + let mut i = keypair0_balance; + while start.elapsed() < duration { + let balance = client.poll_get_balance(&id.pubkey()).unwrap_or(0); + metrics_submit_token_balance(balance); + + // ping-pong between source and destination accounts for each loop iteration + // this seems to be faster than trying to determine the balance of individual + // accounts + let len = tx_count as usize; + generate_txs( + &shared_txs, + &keypairs[..len], + &keypairs[len..], + threads, + reclaim_tokens_back_to_source_account, + &leader, + ); + // In sustained mode overlap the transfers with generation + // this has higher average performance but lower peak performance + // in tested environments. + if !sustained { + while shared_tx_active_thread_count.load(Ordering::Relaxed) > 0 { + sleep(Duration::from_millis(100)); + } + } + // It's not feasible (would take too much time) to confirm each of the `tx_count / 2` + // transactions sent by `generate_txs()` so instead send and confirm a single transaction + // to validate the network is still functional. + send_barrier_transaction(&mut barrier_client, &mut last_id, &barrier_id); + + i += 1; + if should_switch_directions(num_tokens_per_account, i) { + reclaim_tokens_back_to_source_account = !reclaim_tokens_back_to_source_account; + } + } + + // Stop the sampling threads so it will collect the stats + exit_signal.store(true, Ordering::Relaxed); + + println!("Waiting for validator threads..."); + for t in v_threads { + if let Err(err) = t.join() { + println!(" join() failed with: {:?}", err); + } + } + + // join the tx send threads + println!("Waiting for transmit threads..."); + for t in s_threads { + if let Err(err) = t.join() { + println!(" join() failed with: {:?}", err); + } + } + + let balance = client.poll_get_balance(&id.pubkey()).unwrap_or(0); + metrics_submit_token_balance(balance); + + compute_and_report_stats( + &maxes, + sample_period, + &start.elapsed(), + total_tx_sent_count.load(Ordering::Relaxed), + ); + + // join the cluster_info client threads + ncp.join().unwrap(); +} + +fn converge( + leader: &NodeInfo, + exit_signal: &Arc, + num_nodes: usize, +) -> (Vec, Option, Ncp) { + //lets spy on the network + let (node, gossip_socket) = ClusterInfo::spy_node(); + let mut spy_cluster_info = ClusterInfo::new(node); + spy_cluster_info.insert_info(leader.clone()); + spy_cluster_info.set_leader(leader.id); + let spy_ref = Arc::new(RwLock::new(spy_cluster_info)); + let window = Arc::new(RwLock::new(default_window())); + let ncp = Ncp::new(&spy_ref, window, None, gossip_socket, exit_signal.clone()); + let mut v: Vec = vec![]; + // wait for the network to converge, 30 seconds should be plenty + for _ in 0..30 { + { + let spy_ref = spy_ref.read().unwrap(); + + println!("{}", spy_ref.node_info_trace()); + + if spy_ref.leader_data().is_some() { + v = spy_ref.rpc_peers(); + if v.len() >= num_nodes { + println!("CONVERGED!"); + break; + } else { + println!( + "{} node(s) discovered (looking for {} or more)", + v.len(), + num_nodes + ); + } + } + } + sleep(Duration::new(1, 0)); + } + let leader = spy_ref.read().unwrap().leader_data().cloned(); + (v, leader, ncp) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_switch_directions() { + assert_eq!(should_switch_directions(20, 0), false); + assert_eq!(should_switch_directions(20, 1), false); + assert_eq!(should_switch_directions(20, 14), false); + assert_eq!(should_switch_directions(20, 15), true); + assert_eq!(should_switch_directions(20, 16), false); + assert_eq!(should_switch_directions(20, 19), false); + assert_eq!(should_switch_directions(20, 20), true); + assert_eq!(should_switch_directions(20, 21), false); + assert_eq!(should_switch_directions(20, 99), false); + assert_eq!(should_switch_directions(20, 100), true); + assert_eq!(should_switch_directions(20, 101), false); + } +} diff --git a/book/bin/fullnode-config.rs b/book/bin/fullnode-config.rs new file mode 100644 index 00000000000000..26ad46e352c584 --- /dev/null +++ b/book/bin/fullnode-config.rs @@ -0,0 +1,81 @@ +#[macro_use] +extern crate clap; +extern crate dirs; +extern crate ring; +extern crate serde_json; +extern crate solana; + +use clap::{App, Arg}; +use ring::rand::SystemRandom; +use ring::signature::Ed25519KeyPair; +use solana::cluster_info::FULLNODE_PORT_RANGE; +use solana::fullnode::Config; +use solana::logger; +use solana::netutil::{get_ip_addr, get_public_ip_addr, parse_port_or_addr}; +use solana::signature::read_pkcs8; +use std::io; +use std::net::SocketAddr; + +fn main() { + logger::setup(); + let matches = App::new("fullnode-config") + .version(crate_version!()) + .arg( + Arg::with_name("local") + .short("l") + .long("local") + .takes_value(false) + .help("Detect network address from local machine configuration"), + ).arg( + Arg::with_name("keypair") + .short("k") + .long("keypair") + .value_name("PATH") + .takes_value(true) + .help("/path/to/id.json"), + ).arg( + Arg::with_name("public") + .short("p") + .long("public") + .takes_value(false) + .help("Detect public network address using public servers"), + ).arg( + Arg::with_name("bind") + .short("b") + .long("bind") + .value_name("PORT") + .takes_value(true) + .help("Bind to port or address"), + ).get_matches(); + + let bind_addr: SocketAddr = { + let mut bind_addr = parse_port_or_addr(matches.value_of("bind"), FULLNODE_PORT_RANGE.0); + if matches.is_present("local") { + let ip = get_ip_addr().unwrap(); + bind_addr.set_ip(ip); + } + if matches.is_present("public") { + let ip = get_public_ip_addr().unwrap(); + bind_addr.set_ip(ip); + } + bind_addr + }; + + let mut path = dirs::home_dir().expect("home directory"); + let id_path = if matches.is_present("keypair") { + matches.value_of("keypair").unwrap() + } else { + path.extend(&[".config", "solana", "id.json"]); + path.to_str().unwrap() + }; + let pkcs8 = read_pkcs8(id_path).expect("client keypair"); + + let rnd = SystemRandom::new(); + let vote_account_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rnd).unwrap(); + + // we need all the receiving sockets to be bound within the expected + // port range that we open on aws + let config = Config::new(&bind_addr, pkcs8, vote_account_pkcs8.to_vec()); + let stdout = io::stdout(); + serde_json::to_writer(stdout, &config).expect("serialize"); +} diff --git a/book/bin/fullnode.rs b/book/bin/fullnode.rs new file mode 100644 index 00000000000000..67af9371dbf995 --- /dev/null +++ b/book/bin/fullnode.rs @@ -0,0 +1,213 @@ +#[macro_use] +extern crate clap; +extern crate getopts; +#[macro_use] +extern crate log; +extern crate serde_json; +#[macro_use] +extern crate solana; +extern crate solana_metrics; + +use clap::{App, Arg}; +use solana::client::mk_client; +use solana::cluster_info::{Node, FULLNODE_PORT_RANGE}; +use solana::fullnode::{Config, Fullnode, FullnodeReturnType}; +use solana::leader_scheduler::LeaderScheduler; +use solana::logger; +use solana::netutil::find_available_port_in_range; +use solana::signature::{Keypair, KeypairUtil}; +use solana::thin_client::poll_gossip_for_leader; +use solana::vote_program::VoteProgram; +use solana::vote_transaction::VoteTransaction; +use std::fs::File; +use std::net::{Ipv4Addr, SocketAddr}; +use std::process::exit; +use std::sync::Arc; +use std::thread::sleep; +use std::time::Duration; + +fn main() { + logger::setup(); + solana_metrics::set_panic_hook("fullnode"); + let matches = App::new("fullnode") + .version(crate_version!()) + .arg( + Arg::with_name("identity") + .short("i") + .long("identity") + .value_name("PATH") + .takes_value(true) + .help("Run with the identity found in FILE"), + ).arg( + Arg::with_name("network") + .short("n") + .long("network") + .value_name("HOST:PORT") + .takes_value(true) + .help("Rendezvous with the network at this gossip entry point"), + ).arg( + Arg::with_name("ledger") + .short("l") + .long("ledger") + .value_name("DIR") + .takes_value(true) + .required(true) + .help("Use DIR as persistent ledger location"), + ).arg( + Arg::with_name("rpc") + .long("rpc") + .value_name("PORT") + .takes_value(true) + .help("Custom RPC port for this node"), + ).get_matches(); + + let (keypair, vote_account_keypair, ncp) = if let Some(i) = matches.value_of("identity") { + let path = i.to_string(); + if let Ok(file) = File::open(path.clone()) { + let parse: serde_json::Result = serde_json::from_reader(file); + if let Ok(data) = parse { + ( + data.keypair(), + data.vote_account_keypair(), + data.node_info.ncp, + ) + } else { + eprintln!("failed to parse {}", path); + exit(1); + } + } else { + eprintln!("failed to read {}", path); + exit(1); + } + } else { + (Keypair::new(), Keypair::new(), socketaddr!(0, 8000)) + }; + + let ledger_path = matches.value_of("ledger").unwrap(); + + // socketaddr that is initial pointer into the network's gossip (ncp) + let network = matches + .value_of("network") + .map(|network| network.parse().expect("failed to parse network address")); + + let node = Node::new_with_external_ip(keypair.pubkey(), &ncp); + + // save off some stuff for airdrop + let mut node_info = node.info.clone(); + + let vote_account_keypair = Arc::new(vote_account_keypair); + let vote_account_id = vote_account_keypair.pubkey(); + let keypair = Arc::new(keypair); + let pubkey = keypair.pubkey(); + + let mut leader_scheduler = LeaderScheduler::default(); + + // Remove this line to enable leader rotation + leader_scheduler.use_only_bootstrap_leader = true; + + let rpc_port = if let Some(port) = matches.value_of("rpc") { + let port_number = port.to_string().parse().expect("integer"); + if port_number == 0 { + eprintln!("Invalid RPC port requested: {:?}", port); + exit(1); + } + Some(port_number) + } else { + match find_available_port_in_range(FULLNODE_PORT_RANGE) { + Ok(port) => Some(port), + Err(_) => None, + } + }; + + let leader = match network { + Some(network) => { + poll_gossip_for_leader(network, None).expect("can't find leader on network") + } + None => { + //self = leader + if rpc_port.is_some() { + node_info.rpc.set_port(rpc_port.unwrap()); + node_info.rpc_pubsub.set_port(rpc_port.unwrap() + 1); + } + node_info + } + }; + + let mut fullnode = Fullnode::new( + node, + ledger_path, + keypair.clone(), + vote_account_keypair.clone(), + network, + false, + leader_scheduler, + rpc_port, + ); + let mut client = mk_client(&leader); + + let balance = client.poll_get_balance(&pubkey).unwrap_or(0); + info!("balance is {}", balance); + if balance < 1 { + error!("insufficient tokens"); + exit(1); + } + + // Create the vote account if necessary + if client.poll_get_balance(&vote_account_id).unwrap_or(0) == 0 { + // Need at least two tokens as one token will be spent on a vote_account_new() transaction + if balance < 2 { + error!("insufficient tokens"); + exit(1); + } + loop { + let last_id = client.get_last_id(); + let transaction = + VoteTransaction::vote_account_new(&keypair, vote_account_id, last_id, 1); + if client.transfer_signed(&transaction).is_err() { + sleep(Duration::from_secs(2)); + continue; + } + + let balance = client.poll_get_balance(&vote_account_id).unwrap_or(0); + if balance > 0 { + break; + } + sleep(Duration::from_secs(2)); + } + } + + // Register the vote account to this node + loop { + let last_id = client.get_last_id(); + let transaction = + VoteTransaction::vote_account_register(&keypair, vote_account_id, last_id, 0); + if client.transfer_signed(&transaction).is_err() { + sleep(Duration::from_secs(2)); + continue; + } + + let account_user_data = client.get_account_userdata(&vote_account_id); + if let Ok(Some(account_user_data)) = account_user_data { + if let Ok(vote_state) = VoteProgram::deserialize(&account_user_data) { + if vote_state.node_id == pubkey { + break; + } + } + } + + sleep(Duration::from_secs(2)); + } + + loop { + let status = fullnode.handle_role_transition(); + match status { + Ok(Some(FullnodeReturnType::LeaderToValidatorRotation)) => (), + Ok(Some(FullnodeReturnType::ValidatorToLeaderRotation)) => (), + _ => { + // Fullnode tpu/tvu exited for some unexpected + // reason, so exit + exit(1); + } + } + } +} diff --git a/book/bin/genesis.rs b/book/bin/genesis.rs new file mode 100644 index 00000000000000..e299b94ae4dce1 --- /dev/null +++ b/book/bin/genesis.rs @@ -0,0 +1,85 @@ +//! A command-line executable for generating the chain's genesis block. + +extern crate atty; +#[macro_use] +extern crate clap; +extern crate serde_json; +extern crate solana; +extern crate untrusted; + +use clap::{App, Arg}; +use solana::fullnode::Config; +use solana::ledger::LedgerWriter; +use solana::mint::Mint; +use solana::signature::KeypairUtil; +use std::error; +use std::fs::File; +use std::path::Path; + +/** + * Bootstrap leader gets two tokens: + * - one token to create an instance of the vote_program with + * - one second token to keep the node identity public key valid + */ +pub const BOOTSTRAP_LEADER_TOKENS: u64 = 2; + +fn main() -> Result<(), Box> { + let matches = App::new("solana-genesis") + .version(crate_version!()) + .arg( + Arg::with_name("num_tokens") + .short("t") + .long("num_tokens") + .value_name("TOKENS") + .takes_value(true) + .required(true) + .help("Number of tokens to create in the mint"), + ).arg( + Arg::with_name("mint") + .short("m") + .long("mint") + .value_name("MINT") + .takes_value(true) + .required(true) + .help("Path to file containing keys of the mint"), + ).arg( + Arg::with_name("bootstrap_leader") + .short("b") + .long("bootstrap_leader") + .value_name("BOOTSTRAP LEADER") + .takes_value(true) + .required(true) + .help("Path to file containing keys of the bootstrap leader"), + ).arg( + Arg::with_name("ledger") + .short("l") + .long("ledger") + .value_name("DIR") + .takes_value(true) + .required(true) + .help("Use directory as persistent ledger location"), + ).get_matches(); + + // Parse the input leader configuration + let file = File::open(Path::new(&matches.value_of("bootstrap_leader").unwrap())).unwrap(); + let leader_config: Config = serde_json::from_reader(file).unwrap(); + let leader_keypair = leader_config.keypair(); + + // Parse the input mint configuration + let num_tokens = value_t_or_exit!(matches, "num_tokens", u64); + let file = File::open(Path::new(&matches.value_of("mint").unwrap())).unwrap(); + let pkcs8: Vec = serde_json::from_reader(&file)?; + let mint = Mint::new_with_pkcs8( + num_tokens, + pkcs8, + leader_keypair.pubkey(), + BOOTSTRAP_LEADER_TOKENS, + ); + + // Write the ledger entries + let ledger_path = matches.value_of("ledger").unwrap(); + let mut ledger_writer = LedgerWriter::open(&ledger_path, true)?; + ledger_writer.write_entries(&mint.create_entries())?; + + Ok(()) +} diff --git a/book/bin/keygen.rs b/book/bin/keygen.rs new file mode 100644 index 00000000000000..10a49ca48b5210 --- /dev/null +++ b/book/bin/keygen.rs @@ -0,0 +1,37 @@ +#[macro_use] +extern crate clap; +extern crate dirs; +extern crate ring; +extern crate serde_json; +extern crate solana; + +use clap::{App, Arg}; +use solana::wallet::gen_keypair_file; +use std::error; + +fn main() -> Result<(), Box> { + let matches = App::new("solana-keygen") + .version(crate_version!()) + .arg( + Arg::with_name("outfile") + .short("o") + .long("outfile") + .value_name("PATH") + .takes_value(true) + .help("Path to generated file"), + ).get_matches(); + + let mut path = dirs::home_dir().expect("home directory"); + let outfile = if matches.is_present("outfile") { + matches.value_of("outfile").unwrap() + } else { + path.extend(&[".config", "solana", "id.json"]); + path.to_str().unwrap() + }; + + let serialized_keypair = gen_keypair_file(outfile.to_string())?; + if outfile == "-" { + println!("{}", serialized_keypair); + } + Ok(()) +} diff --git a/book/bin/ledger-tool.rs b/book/bin/ledger-tool.rs new file mode 100644 index 00000000000000..541915a2b2eaa8 --- /dev/null +++ b/book/bin/ledger-tool.rs @@ -0,0 +1,162 @@ +#[macro_use] +extern crate clap; +extern crate serde_json; +extern crate solana; + +use clap::{App, Arg, SubCommand}; +use solana::bank::Bank; +use solana::ledger::{read_ledger, verify_ledger}; +use solana::logger; +use std::io::{stdout, Write}; +use std::process::exit; + +fn main() { + logger::setup(); + let matches = App::new("ledger-tool") + .version(crate_version!()) + .arg( + Arg::with_name("ledger") + .short("l") + .long("ledger") + .value_name("DIR") + .takes_value(true) + .required(true) + .help("Use directory for ledger location"), + ) + .arg( + Arg::with_name("head") + .short("n") + .long("head") + .value_name("NUM") + .takes_value(true) + .help("Limit to at most the first NUM entries in ledger\n (only applies to verify, print, json commands)"), + ) + .arg( + Arg::with_name("precheck") + .short("p") + .long("precheck") + .help("Use ledger_verify() to check internal ledger consistency before proceeding"), + ) + .arg( + Arg::with_name("continue") + .short("c") + .long("continue") + .help("Continue verify even if verification fails"), + ) + .subcommand(SubCommand::with_name("print").about("Print the ledger")) + .subcommand(SubCommand::with_name("json").about("Print the ledger in JSON format")) + .subcommand(SubCommand::with_name("verify").about("Verify the ledger's PoH")) + .get_matches(); + + let ledger_path = matches.value_of("ledger").unwrap(); + + if matches.is_present("precheck") { + if let Err(e) = verify_ledger(&ledger_path) { + eprintln!("ledger precheck failed, error: {:?} ", e); + exit(1); + } + } + + let entries = match read_ledger(ledger_path, true) { + Ok(entries) => entries, + Err(err) => { + eprintln!("Failed to open ledger at {}: {}", ledger_path, err); + exit(1); + } + }; + + let head = match matches.value_of("head") { + Some(head) => head.parse().expect("please pass a number for --head"), + None => ::max_value(), + }; + + match matches.subcommand() { + ("print", _) => { + let entries = match read_ledger(ledger_path, true) { + Ok(entries) => entries, + Err(err) => { + eprintln!("Failed to open ledger at {}: {}", ledger_path, err); + exit(1); + } + }; + for (i, entry) in entries.enumerate() { + if i >= head { + break; + } + let entry = entry.unwrap(); + println!("{:?}", entry); + } + } + ("json", _) => { + stdout().write_all(b"{\"ledger\":[\n").expect("open array"); + for (i, entry) in entries.enumerate() { + if i >= head { + break; + } + let entry = entry.unwrap(); + serde_json::to_writer(stdout(), &entry).expect("serialize"); + stdout().write_all(b",\n").expect("newline"); + } + stdout().write_all(b"\n]}\n").expect("close array"); + } + ("verify", _) => { + const NUM_GENESIS_ENTRIES: usize = 3; + if head < NUM_GENESIS_ENTRIES { + eprintln!( + "verify requires at least {} entries to run", + NUM_GENESIS_ENTRIES + ); + exit(1); + } + let bank = Bank::default(); + { + let genesis = match read_ledger(ledger_path, true) { + Ok(entries) => entries, + Err(err) => { + eprintln!("Failed to open ledger at {}: {}", ledger_path, err); + exit(1); + } + }; + + let genesis = genesis.take(NUM_GENESIS_ENTRIES).map(|e| e.unwrap()); + if let Err(e) = bank.process_ledger(genesis) { + eprintln!("verify failed at genesis err: {:?}", e); + if !matches.is_present("continue") { + exit(1); + } + } + } + let entries = entries.map(|e| e.unwrap()); + + let head = head - NUM_GENESIS_ENTRIES; + + let mut last_id = bank.last_id(); + + for (i, entry) in entries.skip(NUM_GENESIS_ENTRIES).enumerate() { + if i >= head { + break; + } + + if !entry.verify(&last_id) { + eprintln!("entry.verify() failed at entry[{}]", i + 2); + if !matches.is_present("continue") { + exit(1); + } + } + last_id = entry.id; + + if let Err(e) = bank.process_entry(&entry) { + eprintln!("verify failed at entry[{}], err: {:?}", i + 2, e); + if !matches.is_present("continue") { + exit(1); + } + } + } + } + ("", _) => { + eprintln!("{}", matches.usage()); + exit(1); + } + _ => unreachable!(), + }; +} diff --git a/book/bin/replicator.rs b/book/bin/replicator.rs new file mode 100644 index 00000000000000..165fa1c89b8227 --- /dev/null +++ b/book/bin/replicator.rs @@ -0,0 +1,153 @@ +#[macro_use] +extern crate clap; +extern crate getopts; +extern crate serde_json; +#[macro_use] +extern crate solana; +extern crate solana_drone; + +use clap::{App, Arg}; +use solana::chacha::{chacha_cbc_encrypt_file, CHACHA_BLOCK_SIZE}; +use solana::client::mk_client; +use solana::cluster_info::Node; +use solana::fullnode::Config; +use solana::ledger::LEDGER_DATA_FILE; +use solana::logger; +use solana::replicator::{sample_file, Replicator}; +use solana::signature::{Keypair, KeypairUtil}; +use solana::storage_transaction::StorageTransaction; +use solana::transaction::Transaction; +use solana_drone::drone::{request_airdrop_transaction, DRONE_PORT}; +use std::fs::File; +use std::net::{Ipv4Addr, SocketAddr}; +use std::path::Path; +use std::process::exit; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread::sleep; +use std::time::Duration; + +fn main() { + logger::setup(); + + let matches = App::new("replicator") + .version(crate_version!()) + .arg( + Arg::with_name("identity") + .short("i") + .long("identity") + .value_name("PATH") + .takes_value(true) + .help("Run with the identity found in FILE"), + ).arg( + Arg::with_name("network") + .short("n") + .long("network") + .value_name("HOST:PORT") + .takes_value(true) + .help("Rendezvous with the network at this gossip entry point"), + ).arg( + Arg::with_name("ledger") + .short("l") + .long("ledger") + .value_name("DIR") + .takes_value(true) + .required(true) + .help("use DIR as persistent ledger location"), + ).get_matches(); + + let ledger_path = matches.value_of("ledger"); + + let (keypair, ncp) = if let Some(i) = matches.value_of("identity") { + let path = i.to_string(); + if let Ok(file) = File::open(path.clone()) { + let parse: serde_json::Result = serde_json::from_reader(file); + if let Ok(data) = parse { + (data.keypair(), data.node_info.ncp) + } else { + eprintln!("failed to parse {}", path); + exit(1); + } + } else { + eprintln!("failed to read {}", path); + exit(1); + } + } else { + (Keypair::new(), socketaddr!([127, 0, 0, 1], 8700)) + }; + + let node = Node::new_with_external_ip(keypair.pubkey(), &ncp); + + println!( + "replicating the data with keypair: {:?} ncp:{:?}", + keypair.pubkey(), + ncp + ); + + let exit = Arc::new(AtomicBool::new(false)); + let done = Arc::new(AtomicBool::new(false)); + + let network_addr = matches + .value_of("network") + .map(|network| network.parse().expect("failed to parse network address")); + + // TODO: ask network what slice we should store + let entry_height = 0; + + let (replicator, leader_info) = Replicator::new( + entry_height, + 5, + &exit, + ledger_path, + node, + network_addr, + done.clone(), + ); + + while !done.load(Ordering::Relaxed) { + sleep(Duration::from_millis(100)); + } + + println!("Done downloading ledger"); + + let ledger_path = Path::new(ledger_path.unwrap()); + let ledger_data_file = ledger_path.join(LEDGER_DATA_FILE); + let ledger_data_file_encrypted = ledger_path.join(format!("{}.enc", LEDGER_DATA_FILE)); + let mut ivec = [0u8; CHACHA_BLOCK_SIZE]; + ivec[0..4].copy_from_slice(&[2, 3, 4, 5]); + + if let Err(e) = + chacha_cbc_encrypt_file(&ledger_data_file, &ledger_data_file_encrypted, &mut ivec) + { + println!("Error while encrypting ledger: {:?}", e); + return; + } + + println!("Done encrypting the ledger"); + + let sampling_offsets = [0, 1, 2, 3]; + + let mut client = mk_client(&leader_info); + + let mut drone_addr = leader_info.tpu; + drone_addr.set_port(DRONE_PORT); + let airdrop_amount = 5; + let last_id = client.get_last_id(); + let transaction = + request_airdrop_transaction(&drone_addr, &keypair.pubkey(), airdrop_amount, last_id) + .unwrap(); + let signature = client.transfer_signed(&transaction).unwrap(); + client.poll_for_signature(&signature).unwrap(); + + match sample_file(&ledger_data_file_encrypted, &sampling_offsets) { + Ok(hash) => { + let last_id = client.get_last_id(); + println!("sampled hash: {}", hash); + let tx = Transaction::storage_new_mining_proof(&keypair, hash, last_id); + client.transfer_signed(&tx).expect("transfer didn't work!"); + } + Err(e) => println!("Error occurred while sampling: {:?}", e), + } + + replicator.join(); +} diff --git a/book/bin/upload-perf.rs b/book/bin/upload-perf.rs new file mode 100644 index 00000000000000..e658476e5bbae6 --- /dev/null +++ b/book/bin/upload-perf.rs @@ -0,0 +1,116 @@ +extern crate serde_json; +extern crate solana; +extern crate solana_metrics; + +use serde_json::Value; +use solana_metrics::influxdb; +use std::collections::HashMap; +use std::env; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::process::Command; + +fn get_last_metrics(metric: &str, db: &str, name: &str, branch: &str) -> Result { + let query = format!( + r#"SELECT last("{}") FROM "{}"."autogen"."{}" WHERE "branch"='{}'"#, + metric, db, name, branch + ); + + let response = solana_metrics::query(&query)?; + + match serde_json::from_str(&response) { + Result::Ok(v) => { + let v: Value = v; + let data = &v["results"][0]["series"][0]["values"][0][1]; + if data.is_null() { + return Result::Err("Key not found".to_string()); + } + Result::Ok(data.to_string()) + } + Result::Err(err) => Result::Err(err.to_string()), + } +} + +fn main() { + let args: Vec = env::args().collect(); + // Open the path in read-only mode, returns `io::Result` + let fname = &args[1]; + let file = match File::open(fname) { + Err(why) => panic!("couldn't open {}: {:?}", fname, why), + Ok(file) => file, + }; + + let branch = &args[2]; + let upload_metrics = args.len() > 2; + + let git_output = Command::new("git") + .args(&["rev-parse", "HEAD"]) + .output() + .expect("failed to execute git rev-parse"); + let git_commit_hash = String::from_utf8_lossy(&git_output.stdout); + let trimmed_hash = git_commit_hash.trim().to_string(); + + let mut last_commit = None; + let mut results = HashMap::new(); + + let db = env::var("INFLUX_DATABASE").unwrap_or_else(|_| "scratch".to_string()); + + for line in BufReader::new(file).lines() { + if let Ok(v) = serde_json::from_str(&line.unwrap()) { + let v: Value = v; + if v["type"] == "bench" { + let name = v["name"].as_str().unwrap().trim_matches('\"').to_string(); + + last_commit = match get_last_metrics(&"commit".to_string(), &db, &name, &branch) { + Result::Ok(v) => Some(v), + Result::Err(_) => None, + }; + + let median = v["median"].to_string().parse().unwrap(); + let deviation = v["deviation"].to_string().parse().unwrap(); + if upload_metrics { + solana_metrics::submit( + influxdb::Point::new(&v["name"].as_str().unwrap().trim_matches('\"')) + .add_tag("test", influxdb::Value::String("bench".to_string())) + .add_tag("branch", influxdb::Value::String(branch.to_string())) + .add_field("median", influxdb::Value::Integer(median)) + .add_field("deviation", influxdb::Value::Integer(deviation)) + .add_field( + "commit", + influxdb::Value::String(git_commit_hash.trim().to_string()), + ).to_owned(), + ); + } + let last_median = get_last_metrics(&"median".to_string(), &db, &name, &branch) + .unwrap_or_default(); + let last_deviation = + get_last_metrics(&"deviation".to_string(), &db, &name, &branch) + .unwrap_or_default(); + + results.insert(name, (median, deviation, last_median, last_deviation)); + } + } + } + + if let Some(commit) = last_commit { + println!( + "Comparing current commits: {} against baseline {} on {} branch", + trimmed_hash, commit, branch + ); + println!("bench_name, median, last_median, deviation, last_deviation"); + for (entry, values) in results { + println!( + "{}, {}, {}, {}, {}", + entry, values.0, values.2, values.1, values.3 + ); + } + } else { + println!("No previous results found for {} branch", branch); + println!("hash: {}", trimmed_hash); + println!("bench_name, median, deviation"); + for (entry, values) in results { + println!("{}, {}, {}", entry, values.0, values.1); + } + } + solana_metrics::flush(); +} diff --git a/book/bin/wallet.rs b/book/bin/wallet.rs new file mode 100644 index 00000000000000..1d806fa8d6eb47 --- /dev/null +++ b/book/bin/wallet.rs @@ -0,0 +1,235 @@ +#[macro_use] +extern crate clap; +extern crate dirs; +#[macro_use] +extern crate solana; + +use clap::{App, Arg, ArgMatches, SubCommand}; +use solana::logger; +use solana::signature::{read_keypair, KeypairUtil}; +use solana::wallet::{gen_keypair_file, parse_command, process_command, WalletConfig, WalletError}; +use std::error; +use std::net::SocketAddr; + +pub fn parse_args(matches: &ArgMatches) -> Result> { + let network = if let Some(addr) = matches.value_of("network") { + addr.parse().or_else(|_| { + Err(WalletError::BadParameter( + "Invalid network location".to_string(), + )) + })? + } else { + socketaddr!("127.0.0.1:8001") + }; + let timeout = if let Some(secs) = matches.value_of("timeout") { + Some(secs.to_string().parse().expect("integer")) + } else { + None + }; + + let proxy = matches.value_of("proxy").map(|proxy| proxy.to_string()); + + let mut path = dirs::home_dir().expect("home directory"); + let id_path = if matches.is_present("keypair") { + matches.value_of("keypair").unwrap() + } else { + path.extend(&[".config", "solana", "id.json"]); + if !path.exists() { + gen_keypair_file(path.to_str().unwrap().to_string())?; + println!("New keypair generated at: {:?}", path.to_str().unwrap()); + } + + path.to_str().unwrap() + }; + let id = read_keypair(id_path).or_else(|err| { + Err(WalletError::BadParameter(format!( + "{}: Unable to open keypair file: {}", + err, id_path + ))) + })?; + + let command = parse_command(id.pubkey(), &matches)?; + + Ok(WalletConfig { + id, + command, + network, + timeout, + proxy, + drone_port: None, + }) +} + +fn main() -> Result<(), Box> { + logger::setup(); + let matches = App::new("solana-wallet") + .version(crate_version!()) + .arg( + Arg::with_name("network") + .short("n") + .long("network") + .value_name("HOST:PORT") + .takes_value(true) + .help("Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001"), + ).arg( + Arg::with_name("keypair") + .short("k") + .long("keypair") + .value_name("PATH") + .takes_value(true) + .help("/path/to/id.json"), + ).arg( + Arg::with_name("timeout") + .long("timeout") + .value_name("SECS") + .takes_value(true) + .help("Max seconds to wait to get necessary gossip from the network"), + ).arg( + Arg::with_name("proxy") + .long("proxy") + .takes_value(true) + .value_name("URL") + .help("Address of TLS proxy") + .conflicts_with("rpc-port") + ).subcommand(SubCommand::with_name("address").about("Get your public key")) + .subcommand( + SubCommand::with_name("airdrop") + .about("Request a batch of tokens") + .arg( + Arg::with_name("tokens") + .index(1) + .value_name("NUM") + .takes_value(true) + .required(true) + .help("The number of tokens to request"), + ), + ).subcommand(SubCommand::with_name("balance").about("Get your balance")) + .subcommand( + SubCommand::with_name("cancel") + .about("Cancel a transfer") + .arg( + Arg::with_name("process-id") + .index(1) + .value_name("PROCESS_ID") + .takes_value(true) + .required(true) + .help("The process id of the transfer to cancel"), + ), + ).subcommand( + SubCommand::with_name("confirm") + .about("Confirm transaction by signature") + .arg( + Arg::with_name("signature") + .index(1) + .value_name("SIGNATURE") + .takes_value(true) + .required(true) + .help("The transaction signature to confirm"), + ), + ).subcommand( + SubCommand::with_name("deploy") + .about("Deploy a program") + .arg( + Arg::with_name("program-location") + .index(1) + .value_name("PATH") + .takes_value(true) + .required(true) + .help("/path/to/program.o"), + ) + // TODO: Add "loader" argument; current default is bpf_loader + ).subcommand( + SubCommand::with_name("get-transaction-count") + .about("Get current transaction count") + ).subcommand( + SubCommand::with_name("pay") + .about("Send a payment") + .arg( + Arg::with_name("to") + .index(1) + .value_name("PUBKEY") + .takes_value(true) + .required(true) + .help("The pubkey of recipient"), + ).arg( + Arg::with_name("tokens") + .index(2) + .value_name("NUM") + .takes_value(true) + .required(true) + .help("The number of tokens to send"), + ).arg( + Arg::with_name("timestamp") + .long("after") + .value_name("DATETIME") + .takes_value(true) + .help("A timestamp after which transaction will execute"), + ).arg( + Arg::with_name("timestamp-pubkey") + .long("require-timestamp-from") + .value_name("PUBKEY") + .takes_value(true) + .requires("timestamp") + .help("Require timestamp from this third party"), + ).arg( + Arg::with_name("witness") + .long("require-signature-from") + .value_name("PUBKEY") + .takes_value(true) + .multiple(true) + .use_delimiter(true) + .help("Any third party signatures required to unlock the tokens"), + ).arg( + Arg::with_name("cancelable") + .long("cancelable") + .takes_value(false), + ), + ).subcommand( + SubCommand::with_name("send-signature") + .about("Send a signature to authorize a transfer") + .arg( + Arg::with_name("to") + .index(1) + .value_name("PUBKEY") + .takes_value(true) + .required(true) + .help("The pubkey of recipient"), + ).arg( + Arg::with_name("process-id") + .index(2) + .value_name("PROCESS_ID") + .takes_value(true) + .required(true) + .help("The process id of the transfer to authorize") + ) + ).subcommand( + SubCommand::with_name("send-timestamp") + .about("Send a timestamp to unlock a transfer") + .arg( + Arg::with_name("to") + .index(1) + .value_name("PUBKEY") + .takes_value(true) + .required(true) + .help("The pubkey of recipient"), + ).arg( + Arg::with_name("process-id") + .index(2) + .value_name("PROCESS_ID") + .takes_value(true) + .required(true) + .help("The process id of the transfer to unlock") + ).arg( + Arg::with_name("datetime") + .long("date") + .value_name("DATETIME") + .takes_value(true) + .help("Optional arbitrary timestamp to apply") + ) + ).get_matches(); + + let config = parse_args(&matches)?; + let result = process_command(&config)?; + println!("{}", result); + Ok(()) +} diff --git a/book/blob_fetch_stage.rs b/book/blob_fetch_stage.rs new file mode 100644 index 00000000000000..54badfa50315db --- /dev/null +++ b/book/blob_fetch_stage.rs @@ -0,0 +1,47 @@ +//! The `blob_fetch_stage` pulls blobs from UDP sockets and sends it to a channel. + +use service::Service; +use std::net::UdpSocket; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc::channel; +use std::sync::Arc; +use std::thread::{self, JoinHandle}; +use streamer::{self, BlobReceiver}; + +pub struct BlobFetchStage { + exit: Arc, + thread_hdls: Vec>, +} + +impl BlobFetchStage { + pub fn new(socket: Arc, exit: Arc) -> (Self, BlobReceiver) { + Self::new_multi_socket(vec![socket], exit) + } + pub fn new_multi_socket( + sockets: Vec>, + exit: Arc, + ) -> (Self, BlobReceiver) { + let (sender, receiver) = channel(); + let thread_hdls: Vec<_> = sockets + .into_iter() + .map(|socket| streamer::blob_receiver(socket, exit.clone(), sender.clone())) + .collect(); + + (BlobFetchStage { exit, thread_hdls }, receiver) + } + + pub fn close(&self) { + self.exit.store(true, Ordering::Relaxed); + } +} + +impl Service for BlobFetchStage { + type JoinReturnType = (); + + fn join(self) -> thread::Result<()> { + for thread_hdl in self.thread_hdls { + thread_hdl.join()?; + } + Ok(()) + } +} diff --git a/book/bloom.rs b/book/bloom.rs new file mode 100644 index 00000000000000..2f74a2f3e05e01 --- /dev/null +++ b/book/bloom.rs @@ -0,0 +1,105 @@ +//! Simple Bloom Filter +use bv::BitVec; +use rand::{self, Rng}; +use std::cmp; +use std::marker::PhantomData; + +/// Generate a stable hash of `self` for each `hash_index` +/// Best effort can be made for uniqueness of each hash. +pub trait BloomHashIndex { + fn hash(&self, hash_index: u64) -> u64; +} + +#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)] +pub struct Bloom { + pub keys: Vec, + pub bits: BitVec, + _phantom: PhantomData, +} + +impl Bloom { + /// create filter optimal for num size given the `false_rate` + /// the keys are randomized for picking data out of a collision resistant hash of size + /// `keysize` bytes + /// https://hur.st/bloomfilter/ + pub fn random(num: usize, false_rate: f64, max_bits: usize) -> Self { + let min_num_bits = ((num as f64 * false_rate.log(2f64)) + / (1f64 / 2f64.powf(2f64.log(2f64))).log(2f64)).ceil() + as usize; + let num_bits = cmp::max(1, cmp::min(min_num_bits, max_bits)); + let num_keys = ((num_bits as f64 / num as f64) * 2f64.log(2f64)).round() as usize; + let keys: Vec = (0..num_keys).map(|_| rand::thread_rng().gen()).collect(); + let bits = BitVec::new_fill(false, num_bits as u64); + Bloom { + keys, + bits, + _phantom: Default::default(), + } + } + fn pos(&self, key: &T, k: u64) -> u64 { + key.hash(k) % self.bits.len() + } + pub fn add(&mut self, key: &T) { + for k in &self.keys { + let pos = self.pos(key, *k); + self.bits.set(pos, true); + } + } + pub fn contains(&mut self, key: &T) -> bool { + for k in &self.keys { + let pos = self.pos(key, *k); + if !self.bits.get(pos) { + return false; + } + } + true + } +} + +#[cfg(test)] +mod test { + use super::*; + use solana_sdk::hash::{hash, Hash}; + + #[test] + fn test_bloom_filter() { + //empty + let bloom: Bloom = Bloom::random(0, 0.1, 100); + assert_eq!(bloom.keys.len(), 0); + assert_eq!(bloom.bits.len(), 1); + + //normal + let bloom: Bloom = Bloom::random(10, 0.1, 100); + assert_eq!(bloom.keys.len(), 3); + assert_eq!(bloom.bits.len(), 34); + + //saturated + let bloom: Bloom = Bloom::random(100, 0.1, 100); + assert_eq!(bloom.keys.len(), 1); + assert_eq!(bloom.bits.len(), 100); + } + #[test] + fn test_add_contains() { + let mut bloom: Bloom = Bloom::random(100, 0.1, 100); + //known keys to avoid false positives in the test + bloom.keys = vec![0, 1, 2, 3]; + + let key = hash(b"hello"); + assert!(!bloom.contains(&key)); + bloom.add(&key); + assert!(bloom.contains(&key)); + + let key = hash(b"world"); + assert!(!bloom.contains(&key)); + bloom.add(&key); + assert!(bloom.contains(&key)); + } + #[test] + fn test_random() { + let mut b1: Bloom = Bloom::random(10, 0.1, 100); + let mut b2: Bloom = Bloom::random(10, 0.1, 100); + b1.keys.sort(); + b2.keys.sort(); + assert_ne!(b1.keys, b2.keys); + } +} diff --git a/book/book.js b/book/book.js new file mode 100644 index 00000000000000..c9c2f4d63c2653 --- /dev/null +++ b/book/book.js @@ -0,0 +1,600 @@ +"use strict"; + +// Fix back button cache problem +window.onunload = function () { }; + +// Global variable, shared between modules +function playpen_text(playpen) { + let code_block = playpen.querySelector("code"); + + if (window.ace && code_block.classList.contains("editable")) { + let editor = window.ace.edit(code_block); + return editor.getValue(); + } else { + return code_block.textContent; + } +} + +(function codeSnippets() { + // Hide Rust code lines prepended with a specific character + var hiding_character = "#"; + + function fetch_with_timeout(url, options, timeout = 6000) { + return Promise.race([ + fetch(url, options), + new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout)) + ]); + } + + var playpens = Array.from(document.querySelectorAll(".playpen")); + if (playpens.length > 0) { + fetch_with_timeout("https://play.rust-lang.org/meta/crates", { + headers: { + 'Content-Type': "application/json", + }, + method: 'POST', + mode: 'cors', + }) + .then(response => response.json()) + .then(response => { + // get list of crates available in the rust playground + let playground_crates = response.crates.map(item => item["id"]); + playpens.forEach(block => handle_crate_list_update(block, playground_crates)); + }); + } + + function handle_crate_list_update(playpen_block, playground_crates) { + // update the play buttons after receiving the response + update_play_button(playpen_block, playground_crates); + + // and install on change listener to dynamically update ACE editors + if (window.ace) { + let code_block = playpen_block.querySelector("code"); + if (code_block.classList.contains("editable")) { + let editor = window.ace.edit(code_block); + editor.addEventListener("change", function (e) { + update_play_button(playpen_block, playground_crates); + }); + } + } + } + + // updates the visibility of play button based on `no_run` class and + // used crates vs ones available on http://play.rust-lang.org + function update_play_button(pre_block, playground_crates) { + var play_button = pre_block.querySelector(".play-button"); + + // skip if code is `no_run` + if (pre_block.querySelector('code').classList.contains("no_run")) { + play_button.classList.add("hidden"); + return; + } + + // get list of `extern crate`'s from snippet + var txt = playpen_text(pre_block); + var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g; + var snippet_crates = []; + var item; + while (item = re.exec(txt)) { + snippet_crates.push(item[1]); + } + + // check if all used crates are available on play.rust-lang.org + var all_available = snippet_crates.every(function (elem) { + return playground_crates.indexOf(elem) > -1; + }); + + if (all_available) { + play_button.classList.remove("hidden"); + } else { + play_button.classList.add("hidden"); + } + } + + function run_rust_code(code_block) { + var result_block = code_block.querySelector(".result"); + if (!result_block) { + result_block = document.createElement('code'); + result_block.className = 'result hljs language-bash'; + + code_block.append(result_block); + } + + let text = playpen_text(code_block); + + var params = { + version: "stable", + optimize: "0", + code: text + }; + + if (text.indexOf("#![feature") !== -1) { + params.version = "nightly"; + } + + result_block.innerText = "Running..."; + + fetch_with_timeout("https://play.rust-lang.org/evaluate.json", { + headers: { + 'Content-Type': "application/json", + }, + method: 'POST', + mode: 'cors', + body: JSON.stringify(params) + }) + .then(response => response.json()) + .then(response => result_block.innerText = response.result) + .catch(error => result_block.innerText = "Playground Communication: " + error.message); + } + + // Syntax highlighting Configuration + hljs.configure({ + tabReplace: ' ', // 4 spaces + languages: [], // Languages used for auto-detection + }); + + if (window.ace) { + // language-rust class needs to be removed for editable + // blocks or highlightjs will capture events + Array + .from(document.querySelectorAll('code.editable')) + .forEach(function (block) { block.classList.remove('language-rust'); }); + + Array + .from(document.querySelectorAll('code:not(.editable)')) + .forEach(function (block) { hljs.highlightBlock(block); }); + } else { + Array + .from(document.querySelectorAll('code')) + .forEach(function (block) { hljs.highlightBlock(block); }); + } + + // Adding the hljs class gives code blocks the color css + // even if highlighting doesn't apply + Array + .from(document.querySelectorAll('code')) + .forEach(function (block) { block.classList.add('hljs'); }); + + Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) { + + var code_block = block; + var pre_block = block.parentNode; + // hide lines + var lines = code_block.innerHTML.split("\n"); + var first_non_hidden_line = false; + var lines_hidden = false; + var trimmed_line = ""; + + for (var n = 0; n < lines.length; n++) { + trimmed_line = lines[n].trim(); + if (trimmed_line[0] == hiding_character && trimmed_line[1] != hiding_character) { + if (first_non_hidden_line) { + lines[n] = "" + "\n" + lines[n].replace(/(\s*)# ?/, "$1") + ""; + } + else { + lines[n] = "" + lines[n].replace(/(\s*)# ?/, "$1") + "\n" + ""; + } + lines_hidden = true; + } + else if (first_non_hidden_line) { + lines[n] = "\n" + lines[n]; + } + else { + first_non_hidden_line = true; + } + if (trimmed_line[0] == hiding_character && trimmed_line[1] == hiding_character) { + lines[n] = lines[n].replace("##", "#") + } + } + code_block.innerHTML = lines.join(""); + + // If no lines were hidden, return + if (!lines_hidden) { return; } + + var buttons = document.createElement('div'); + buttons.className = 'buttons'; + buttons.innerHTML = ""; + + // add expand button + pre_block.insertBefore(buttons, pre_block.firstChild); + + pre_block.querySelector('.buttons').addEventListener('click', function (e) { + if (e.target.classList.contains('fa-expand')) { + var lines = pre_block.querySelectorAll('span.hidden'); + + e.target.classList.remove('fa-expand'); + e.target.classList.add('fa-compress'); + e.target.title = 'Hide lines'; + e.target.setAttribute('aria-label', e.target.title); + + Array.from(lines).forEach(function (line) { + line.classList.remove('hidden'); + line.classList.add('unhidden'); + }); + } else if (e.target.classList.contains('fa-compress')) { + var lines = pre_block.querySelectorAll('span.unhidden'); + + e.target.classList.remove('fa-compress'); + e.target.classList.add('fa-expand'); + e.target.title = 'Show hidden lines'; + e.target.setAttribute('aria-label', e.target.title); + + Array.from(lines).forEach(function (line) { + line.classList.remove('unhidden'); + line.classList.add('hidden'); + }); + } + }); + }); + + Array.from(document.querySelectorAll('pre code')).forEach(function (block) { + var pre_block = block.parentNode; + if (!pre_block.classList.contains('playpen')) { + var buttons = pre_block.querySelector(".buttons"); + if (!buttons) { + buttons = document.createElement('div'); + buttons.className = 'buttons'; + pre_block.insertBefore(buttons, pre_block.firstChild); + } + + var clipButton = document.createElement('button'); + clipButton.className = 'fa fa-copy clip-button'; + clipButton.title = 'Copy to clipboard'; + clipButton.setAttribute('aria-label', clipButton.title); + clipButton.innerHTML = ''; + + buttons.insertBefore(clipButton, buttons.firstChild); + } + }); + + // Process playpen code blocks + Array.from(document.querySelectorAll(".playpen")).forEach(function (pre_block) { + // Add play button + var buttons = pre_block.querySelector(".buttons"); + if (!buttons) { + buttons = document.createElement('div'); + buttons.className = 'buttons'; + pre_block.insertBefore(buttons, pre_block.firstChild); + } + + var runCodeButton = document.createElement('button'); + runCodeButton.className = 'fa fa-play play-button'; + runCodeButton.hidden = true; + runCodeButton.title = 'Run this code'; + runCodeButton.setAttribute('aria-label', runCodeButton.title); + + var copyCodeClipboardButton = document.createElement('button'); + copyCodeClipboardButton.className = 'fa fa-copy clip-button'; + copyCodeClipboardButton.innerHTML = ''; + copyCodeClipboardButton.title = 'Copy to clipboard'; + copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title); + + buttons.insertBefore(runCodeButton, buttons.firstChild); + buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild); + + runCodeButton.addEventListener('click', function (e) { + run_rust_code(pre_block); + }); + + let code_block = pre_block.querySelector("code"); + if (window.ace && code_block.classList.contains("editable")) { + var undoChangesButton = document.createElement('button'); + undoChangesButton.className = 'fa fa-history reset-button'; + undoChangesButton.title = 'Undo changes'; + undoChangesButton.setAttribute('aria-label', undoChangesButton.title); + + buttons.insertBefore(undoChangesButton, buttons.firstChild); + + undoChangesButton.addEventListener('click', function () { + let editor = window.ace.edit(code_block); + editor.setValue(editor.originalCode); + editor.clearSelection(); + }); + } + }); +})(); + +(function themes() { + var html = document.querySelector('html'); + var themeToggleButton = document.getElementById('theme-toggle'); + var themePopup = document.getElementById('theme-list'); + var themeColorMetaTag = document.querySelector('meta[name="theme-color"]'); + var stylesheets = { + ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"), + tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"), + highlight: document.querySelector("[href$='highlight.css']"), + }; + + function showThemes() { + themePopup.style.display = 'block'; + themeToggleButton.setAttribute('aria-expanded', true); + themePopup.querySelector("button#" + document.body.className).focus(); + } + + function hideThemes() { + themePopup.style.display = 'none'; + themeToggleButton.setAttribute('aria-expanded', false); + themeToggleButton.focus(); + } + + function set_theme(theme) { + let ace_theme; + + if (theme == 'coal' || theme == 'navy') { + stylesheets.ayuHighlight.disabled = true; + stylesheets.tomorrowNight.disabled = false; + stylesheets.highlight.disabled = true; + + ace_theme = "ace/theme/tomorrow_night"; + } else if (theme == 'ayu') { + stylesheets.ayuHighlight.disabled = false; + stylesheets.tomorrowNight.disabled = true; + stylesheets.highlight.disabled = true; + + ace_theme = "ace/theme/tomorrow_night"; + } else { + stylesheets.ayuHighlight.disabled = true; + stylesheets.tomorrowNight.disabled = true; + stylesheets.highlight.disabled = false; + + ace_theme = "ace/theme/dawn"; + } + + setTimeout(function () { + themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor; + }, 1); + + if (window.ace && window.editors) { + window.editors.forEach(function (editor) { + editor.setTheme(ace_theme); + }); + } + + var previousTheme; + try { previousTheme = localStorage.getItem('mdbook-theme'); } catch (e) { } + if (previousTheme === null || previousTheme === undefined) { previousTheme = 'light'; } + + try { localStorage.setItem('mdbook-theme', theme); } catch (e) { } + + document.body.className = theme; + html.classList.remove(previousTheme); + html.classList.add(theme); + } + + // Set theme + var theme; + try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { } + if (theme === null || theme === undefined) { theme = 'light'; } + + set_theme(theme); + + themeToggleButton.addEventListener('click', function () { + if (themePopup.style.display === 'block') { + hideThemes(); + } else { + showThemes(); + } + }); + + themePopup.addEventListener('click', function (e) { + var theme = e.target.id || e.target.parentElement.id; + set_theme(theme); + }); + + themePopup.addEventListener('focusout', function(e) { + // e.relatedTarget is null in Safari and Firefox on macOS (see workaround below) + if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) { + hideThemes(); + } + }); + + // Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang-nursery/mdBook/issues/628 + document.addEventListener('click', function(e) { + if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) { + hideThemes(); + } + }); + + document.addEventListener('keydown', function (e) { + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } + if (!themePopup.contains(e.target)) { return; } + + switch (e.key) { + case 'Escape': + e.preventDefault(); + hideThemes(); + break; + case 'ArrowUp': + e.preventDefault(); + var li = document.activeElement.parentElement; + if (li && li.previousElementSibling) { + li.previousElementSibling.querySelector('button').focus(); + } + break; + case 'ArrowDown': + e.preventDefault(); + var li = document.activeElement.parentElement; + if (li && li.nextElementSibling) { + li.nextElementSibling.querySelector('button').focus(); + } + break; + case 'Home': + e.preventDefault(); + themePopup.querySelector('li:first-child button').focus(); + break; + case 'End': + e.preventDefault(); + themePopup.querySelector('li:last-child button').focus(); + break; + } + }); +})(); + +(function sidebar() { + var html = document.querySelector("html"); + var sidebar = document.getElementById("sidebar"); + var sidebarLinks = document.querySelectorAll('#sidebar a'); + var sidebarToggleButton = document.getElementById("sidebar-toggle"); + var firstContact = null; + + function showSidebar() { + html.classList.remove('sidebar-hidden') + html.classList.add('sidebar-visible'); + Array.from(sidebarLinks).forEach(function (link) { + link.setAttribute('tabIndex', 0); + }); + sidebarToggleButton.setAttribute('aria-expanded', true); + sidebar.setAttribute('aria-hidden', false); + try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { } + } + + function hideSidebar() { + html.classList.remove('sidebar-visible') + html.classList.add('sidebar-hidden'); + Array.from(sidebarLinks).forEach(function (link) { + link.setAttribute('tabIndex', -1); + }); + sidebarToggleButton.setAttribute('aria-expanded', false); + sidebar.setAttribute('aria-hidden', true); + try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { } + } + + // Toggle sidebar + sidebarToggleButton.addEventListener('click', function sidebarToggle() { + if (html.classList.contains("sidebar-hidden")) { + showSidebar(); + } else if (html.classList.contains("sidebar-visible")) { + hideSidebar(); + } else { + if (getComputedStyle(sidebar)['transform'] === 'none') { + hideSidebar(); + } else { + showSidebar(); + } + } + }); + + document.addEventListener('touchstart', function (e) { + firstContact = { + x: e.touches[0].clientX, + time: Date.now() + }; + }, { passive: true }); + + document.addEventListener('touchmove', function (e) { + if (!firstContact) + return; + + var curX = e.touches[0].clientX; + var xDiff = curX - firstContact.x, + tDiff = Date.now() - firstContact.time; + + if (tDiff < 250 && Math.abs(xDiff) >= 150) { + if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300)) + showSidebar(); + else if (xDiff < 0 && curX < 300) + hideSidebar(); + + firstContact = null; + } + }, { passive: true }); + + // Scroll sidebar to current active section + var activeSection = sidebar.querySelector(".active"); + if (activeSection) { + sidebar.scrollTop = activeSection.offsetTop; + } +})(); + +(function chapterNavigation() { + document.addEventListener('keydown', function (e) { + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } + if (window.search && window.search.hasFocus()) { return; } + + switch (e.key) { + case 'ArrowRight': + e.preventDefault(); + var nextButton = document.querySelector('.nav-chapters.next'); + if (nextButton) { + window.location.href = nextButton.href; + } + break; + case 'ArrowLeft': + e.preventDefault(); + var previousButton = document.querySelector('.nav-chapters.previous'); + if (previousButton) { + window.location.href = previousButton.href; + } + break; + } + }); +})(); + +(function clipboard() { + var clipButtons = document.querySelectorAll('.clip-button'); + + function hideTooltip(elem) { + elem.firstChild.innerText = ""; + elem.className = 'fa fa-copy clip-button'; + } + + function showTooltip(elem, msg) { + elem.firstChild.innerText = msg; + elem.className = 'fa fa-copy tooltipped'; + } + + var clipboardSnippets = new Clipboard('.clip-button', { + text: function (trigger) { + hideTooltip(trigger); + let playpen = trigger.closest("pre"); + return playpen_text(playpen); + } + }); + + Array.from(clipButtons).forEach(function (clipButton) { + clipButton.addEventListener('mouseout', function (e) { + hideTooltip(e.currentTarget); + }); + }); + + clipboardSnippets.on('success', function (e) { + e.clearSelection(); + showTooltip(e.trigger, "Copied!"); + }); + + clipboardSnippets.on('error', function (e) { + showTooltip(e.trigger, "Clipboard error!"); + }); +})(); + +(function scrollToTop () { + var menuTitle = document.querySelector('.menu-title'); + + menuTitle.addEventListener('click', function () { + document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' }); + }); +})(); + +(function autoHideMenu() { + var menu = document.getElementById('menu-bar'); + + var previousScrollTop = document.scrollingElement.scrollTop; + + document.addEventListener('scroll', function () { + if (menu.classList.contains('folded') && document.scrollingElement.scrollTop < previousScrollTop) { + menu.classList.remove('folded'); + } else if (!menu.classList.contains('folded') && document.scrollingElement.scrollTop > previousScrollTop) { + menu.classList.add('folded'); + } + + if (!menu.classList.contains('bordered') && document.scrollingElement.scrollTop > 0) { + menu.classList.add('bordered'); + } + + if (menu.classList.contains('bordered') && document.scrollingElement.scrollTop === 0) { + menu.classList.remove('bordered'); + } + + previousScrollTop = document.scrollingElement.scrollTop; + }, { passive: true }); +})(); diff --git a/book/bpf_loader.rs b/book/bpf_loader.rs new file mode 100644 index 00000000000000..156c2110cd634a --- /dev/null +++ b/book/bpf_loader.rs @@ -0,0 +1,24 @@ +//! BPF loader +use native_loader; +use solana_sdk::account::Account; +use solana_sdk::pubkey::Pubkey; + +const BPF_LOADER_NAME: &str = "solana_bpf_loader"; +const BPF_LOADER_PROGRAM_ID: [u8; 32] = [ + 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +]; + +pub fn id() -> Pubkey { + Pubkey::new(&BPF_LOADER_PROGRAM_ID) +} + +pub fn account() -> Account { + Account { + tokens: 1, + owner: id(), + userdata: BPF_LOADER_NAME.as_bytes().to_vec(), + executable: true, + loader: native_loader::id(), + } +} diff --git a/book/broadcast_stage.rs b/book/broadcast_stage.rs new file mode 100644 index 00000000000000..010fd58f3b025e --- /dev/null +++ b/book/broadcast_stage.rs @@ -0,0 +1,299 @@ +//! The `broadcast_stage` broadcasts data from a leader node to validators +//! +use cluster_info::{ClusterInfo, ClusterInfoError, NodeInfo}; +use counter::Counter; +use entry::Entry; +#[cfg(feature = "erasure")] +use erasure; + +use ledger::Block; +use log::Level; +use packet::{index_blobs, SharedBlobs}; +use rayon::prelude::*; +use result::{Error, Result}; +use service::Service; +use solana_metrics::{influxdb, submit}; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::timing::duration_as_ms; +use std::net::UdpSocket; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::mpsc::{Receiver, RecvTimeoutError}; +use std::sync::{Arc, RwLock}; +use std::thread::{self, Builder, JoinHandle}; +use std::time::{Duration, Instant}; +use window::{SharedWindow, WindowIndex, WindowUtil}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum BroadcastStageReturnType { + LeaderRotation, + ChannelDisconnected, +} + +#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] +fn broadcast( + max_tick_height: Option, + tick_height: &mut u64, + leader_id: Pubkey, + node_info: &NodeInfo, + broadcast_table: &[NodeInfo], + window: &SharedWindow, + receiver: &Receiver>, + sock: &UdpSocket, + transmit_index: &mut WindowIndex, + receive_index: &mut u64, + leader_slot: u64, +) -> Result<()> { + let id = node_info.id; + let timer = Duration::new(1, 0); + let entries = receiver.recv_timeout(timer)?; + let now = Instant::now(); + let mut num_entries = entries.len(); + let mut ventries = Vec::new(); + ventries.push(entries); + while let Ok(entries) = receiver.try_recv() { + num_entries += entries.len(); + ventries.push(entries); + } + inc_new_counter_info!("broadcast_stage-entries_received", num_entries); + + let to_blobs_start = Instant::now(); + let num_ticks: u64 = ventries + .iter() + .flatten() + .map(|entry| (entry.is_tick()) as u64) + .sum(); + + *tick_height += num_ticks; + + let dq: SharedBlobs = ventries + .into_par_iter() + .flat_map(|p| p.to_blobs()) + .collect(); + + let to_blobs_elapsed = duration_as_ms(&to_blobs_start.elapsed()); + + // flatten deque to vec + let blobs_vec: SharedBlobs = dq.into_iter().collect(); + + let blobs_chunking = Instant::now(); + // We could receive more blobs than window slots so + // break them up into window-sized chunks to process + let window_size = window.read().unwrap().window_size(); + let blobs_chunked = blobs_vec.chunks(window_size as usize).map(|x| x.to_vec()); + let chunking_elapsed = duration_as_ms(&blobs_chunking.elapsed()); + + let broadcast_start = Instant::now(); + for mut blobs in blobs_chunked { + let blobs_len = blobs.len(); + trace!("{}: broadcast blobs.len: {}", id, blobs_len); + + index_blobs(&blobs, &node_info.id, *receive_index, leader_slot); + + // keep the cache of blobs that are broadcast + inc_new_counter_info!("streamer-broadcast-sent", blobs.len()); + { + let mut win = window.write().unwrap(); + assert!(blobs.len() <= win.len()); + for b in &blobs { + let ix = b.read().unwrap().index().expect("blob index"); + let pos = (ix % window_size) as usize; + if let Some(x) = win[pos].data.take() { + trace!( + "{} popped {} at {}", + id, + x.read().unwrap().index().unwrap(), + pos + ); + } + if let Some(x) = win[pos].coding.take() { + trace!( + "{} popped {} at {}", + id, + x.read().unwrap().index().unwrap(), + pos + ); + } + + trace!("{} null {}", id, pos); + } + for b in &blobs { + let ix = b.read().unwrap().index().expect("blob index"); + let pos = (ix % window_size) as usize; + trace!("{} caching {} at {}", id, ix, pos); + assert!(win[pos].data.is_none()); + win[pos].data = Some(b.clone()); + } + } + + // Fill in the coding blob data from the window data blobs + #[cfg(feature = "erasure")] + { + erasure::generate_coding( + &id, + &mut window.write().unwrap(), + *receive_index, + blobs_len, + &mut transmit_index.coding, + )?; + } + + *receive_index += blobs_len as u64; + + // Send blobs out from the window + ClusterInfo::broadcast( + Some(*tick_height) == max_tick_height, + leader_id, + &node_info, + &broadcast_table, + &window, + &sock, + transmit_index, + *receive_index, + )?; + } + let broadcast_elapsed = duration_as_ms(&broadcast_start.elapsed()); + + inc_new_counter_info!( + "broadcast_stage-time_ms", + duration_as_ms(&now.elapsed()) as usize + ); + info!( + "broadcast: {} entries, blob time {} chunking time {} broadcast time {}", + num_entries, to_blobs_elapsed, chunking_elapsed, broadcast_elapsed + ); + + submit( + influxdb::Point::new("broadcast-stage") + .add_field( + "transmit-index", + influxdb::Value::Integer(transmit_index.data as i64), + ).to_owned(), + ); + + Ok(()) +} + +// Implement a destructor for the BroadcastStage thread to signal it exited +// even on panics +struct Finalizer { + exit_sender: Arc, +} + +impl Finalizer { + fn new(exit_sender: Arc) -> Self { + Finalizer { exit_sender } + } +} +// Implement a destructor for Finalizer. +impl Drop for Finalizer { + fn drop(&mut self) { + self.exit_sender.clone().store(true, Ordering::Relaxed); + } +} + +pub struct BroadcastStage { + thread_hdl: JoinHandle, +} + +impl BroadcastStage { + fn run( + sock: &UdpSocket, + cluster_info: &Arc>, + window: &SharedWindow, + entry_height: u64, + leader_slot: u64, + receiver: &Receiver>, + max_tick_height: Option, + tick_height: u64, + ) -> BroadcastStageReturnType { + let mut transmit_index = WindowIndex { + data: entry_height, + coding: entry_height, + }; + let mut receive_index = entry_height; + let me = cluster_info.read().unwrap().my_data().clone(); + let mut tick_height_ = tick_height; + loop { + let broadcast_table = cluster_info.read().unwrap().tvu_peers(); + let leader_id = cluster_info.read().unwrap().leader_id(); + if let Err(e) = broadcast( + max_tick_height, + &mut tick_height_, + leader_id, + &me, + &broadcast_table, + &window, + &receiver, + &sock, + &mut transmit_index, + &mut receive_index, + leader_slot, + ) { + match e { + Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => { + return BroadcastStageReturnType::ChannelDisconnected + } + Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), + Error::ClusterInfoError(ClusterInfoError::NoPeers) => (), // TODO: Why are the unit-tests throwing hundreds of these? + _ => { + inc_new_counter_info!("streamer-broadcaster-error", 1, 1); + error!("broadcaster error: {:?}", e); + } + } + } + } + } + + /// Service to broadcast messages from the leader to layer 1 nodes. + /// See `cluster_info` for network layer definitions. + /// # Arguments + /// * `sock` - Socket to send from. + /// * `exit` - Boolean to signal system exit. + /// * `cluster_info` - ClusterInfo structure + /// * `window` - Cache of blobs that we have broadcast + /// * `receiver` - Receive channel for blobs to be retransmitted to all the layer 1 nodes. + /// * `exit_sender` - Set to true when this stage exits, allows rest of Tpu to exit cleanly. Otherwise, + /// when a Tpu stage closes, it only closes the stages that come after it. The stages + /// that come before could be blocked on a receive, and never notice that they need to + /// exit. Now, if any stage of the Tpu closes, it will lead to closing the WriteStage (b/c + /// WriteStage is the last stage in the pipeline), which will then close Broadcast stage, + /// which will then close FetchStage in the Tpu, and then the rest of the Tpu, + /// completing the cycle. + pub fn new( + sock: UdpSocket, + cluster_info: Arc>, + window: SharedWindow, + entry_height: u64, + leader_slot: u64, + receiver: Receiver>, + max_tick_height: Option, + tick_height: u64, + exit_sender: Arc, + ) -> Self { + let thread_hdl = Builder::new() + .name("solana-broadcaster".to_string()) + .spawn(move || { + let _exit = Finalizer::new(exit_sender); + Self::run( + &sock, + &cluster_info, + &window, + entry_height, + leader_slot, + &receiver, + max_tick_height, + tick_height, + ) + }).unwrap(); + + BroadcastStage { thread_hdl } + } +} + +impl Service for BroadcastStage { + type JoinReturnType = BroadcastStageReturnType; + + fn join(self) -> thread::Result { + self.thread_hdl.join() + } +} diff --git a/book/budget_expr.rs b/book/budget_expr.rs new file mode 100644 index 00000000000000..ef1a1fbebd81be --- /dev/null +++ b/book/budget_expr.rs @@ -0,0 +1,232 @@ +//! The `budget_expr` module provides a domain-specific language for payment plans. Users create BudgetExpr objects that +//! are given to an interpreter. The interpreter listens for `Witness` transactions, +//! which it uses to reduce the payment plan. When the budget is reduced to a +//! `Payment`, the payment is executed. + +use chrono::prelude::*; +use payment_plan::{Payment, Witness}; +use solana_sdk::pubkey::Pubkey; +use std::mem; + +/// A data type representing a `Witness` that the payment plan is waiting on. +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub enum Condition { + /// Wait for a `Timestamp` `Witness` at or after the given `DateTime`. + Timestamp(DateTime, Pubkey), + + /// Wait for a `Signature` `Witness` from `Pubkey`. + Signature(Pubkey), +} + +impl Condition { + /// Return true if the given Witness satisfies this Condition. + pub fn is_satisfied(&self, witness: &Witness, from: &Pubkey) -> bool { + match (self, witness) { + (Condition::Signature(pubkey), Witness::Signature) => pubkey == from, + (Condition::Timestamp(dt, pubkey), Witness::Timestamp(last_time)) => { + pubkey == from && dt <= last_time + } + _ => false, + } + } +} + +/// A data type representing a payment plan. +#[repr(C)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub enum BudgetExpr { + /// Make a payment. + Pay(Payment), + + /// Make a payment after some condition. + After(Condition, Payment), + + /// Either make a payment after one condition or a different payment after another + /// condition, which ever condition is satisfied first. + Or((Condition, Payment), (Condition, Payment)), + + /// Make a payment after both of two conditions are satisfied + And(Condition, Condition, Payment), +} + +impl BudgetExpr { + /// Create the simplest budget - one that pays `tokens` to Pubkey. + pub fn new_payment(tokens: u64, to: Pubkey) -> Self { + BudgetExpr::Pay(Payment { tokens, to }) + } + + /// Create a budget that pays `tokens` to `to` after being witnessed by `from`. + pub fn new_authorized_payment(from: Pubkey, tokens: u64, to: Pubkey) -> Self { + BudgetExpr::After(Condition::Signature(from), Payment { tokens, to }) + } + + /// Create a budget that pays tokens` to `to` after being witnessed by 2x `from`s + pub fn new_2_2_multisig_payment(from0: Pubkey, from1: Pubkey, tokens: u64, to: Pubkey) -> Self { + BudgetExpr::And( + Condition::Signature(from0), + Condition::Signature(from1), + Payment { tokens, to }, + ) + } + + /// Create a budget that pays `tokens` to `to` after the given DateTime. + pub fn new_future_payment(dt: DateTime, from: Pubkey, tokens: u64, to: Pubkey) -> Self { + BudgetExpr::After(Condition::Timestamp(dt, from), Payment { tokens, to }) + } + + /// Create a budget that pays `tokens` to `to` after the given DateTime + /// unless cancelled by `from`. + pub fn new_cancelable_future_payment( + dt: DateTime, + from: Pubkey, + tokens: u64, + to: Pubkey, + ) -> Self { + BudgetExpr::Or( + (Condition::Timestamp(dt, from), Payment { tokens, to }), + (Condition::Signature(from), Payment { tokens, to: from }), + ) + } + + /// Return Payment if the budget requires no additional Witnesses. + pub fn final_payment(&self) -> Option { + match self { + BudgetExpr::Pay(payment) => Some(payment.clone()), + _ => None, + } + } + + /// Return true if the budget spends exactly `spendable_tokens`. + pub fn verify(&self, spendable_tokens: u64) -> bool { + match self { + BudgetExpr::Pay(payment) + | BudgetExpr::After(_, payment) + | BudgetExpr::And(_, _, payment) => payment.tokens == spendable_tokens, + BudgetExpr::Or(a, b) => { + a.1.tokens == spendable_tokens && b.1.tokens == spendable_tokens + } + } + } + + /// Apply a witness to the budget to see if the budget can be reduced. + /// If so, modify the budget in-place. + pub fn apply_witness(&mut self, witness: &Witness, from: &Pubkey) { + let new_expr = match self { + BudgetExpr::After(cond, payment) if cond.is_satisfied(witness, from) => { + Some(BudgetExpr::Pay(payment.clone())) + } + BudgetExpr::Or((cond, payment), _) if cond.is_satisfied(witness, from) => { + Some(BudgetExpr::Pay(payment.clone())) + } + BudgetExpr::Or(_, (cond, payment)) if cond.is_satisfied(witness, from) => { + Some(BudgetExpr::Pay(payment.clone())) + } + BudgetExpr::And(cond0, cond1, payment) => { + if cond0.is_satisfied(witness, from) { + Some(BudgetExpr::After(cond1.clone(), payment.clone())) + } else if cond1.is_satisfied(witness, from) { + Some(BudgetExpr::After(cond0.clone(), payment.clone())) + } else { + None + } + } + _ => None, + }; + if let Some(expr) = new_expr { + mem::replace(self, expr); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use signature::{Keypair, KeypairUtil}; + + #[test] + fn test_signature_satisfied() { + let from = Pubkey::default(); + assert!(Condition::Signature(from).is_satisfied(&Witness::Signature, &from)); + } + + #[test] + fn test_timestamp_satisfied() { + let dt1 = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); + let dt2 = Utc.ymd(2014, 11, 14).and_hms(10, 9, 8); + let from = Pubkey::default(); + assert!(Condition::Timestamp(dt1, from).is_satisfied(&Witness::Timestamp(dt1), &from)); + assert!(Condition::Timestamp(dt1, from).is_satisfied(&Witness::Timestamp(dt2), &from)); + assert!(!Condition::Timestamp(dt2, from).is_satisfied(&Witness::Timestamp(dt1), &from)); + } + + #[test] + fn test_verify() { + let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); + let from = Pubkey::default(); + let to = Pubkey::default(); + assert!(BudgetExpr::new_payment(42, to).verify(42)); + assert!(BudgetExpr::new_authorized_payment(from, 42, to).verify(42)); + assert!(BudgetExpr::new_future_payment(dt, from, 42, to).verify(42)); + assert!(BudgetExpr::new_cancelable_future_payment(dt, from, 42, to).verify(42)); + } + + #[test] + fn test_authorized_payment() { + let from = Pubkey::default(); + let to = Pubkey::default(); + + let mut expr = BudgetExpr::new_authorized_payment(from, 42, to); + expr.apply_witness(&Witness::Signature, &from); + assert_eq!(expr, BudgetExpr::new_payment(42, to)); + } + + #[test] + fn test_future_payment() { + let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); + let from = Keypair::new().pubkey(); + let to = Keypair::new().pubkey(); + + let mut expr = BudgetExpr::new_future_payment(dt, from, 42, to); + expr.apply_witness(&Witness::Timestamp(dt), &from); + assert_eq!(expr, BudgetExpr::new_payment(42, to)); + } + + #[test] + fn test_unauthorized_future_payment() { + // Ensure timestamp will only be acknowledged if it came from the + // whitelisted public key. + let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); + let from = Keypair::new().pubkey(); + let to = Keypair::new().pubkey(); + + let mut expr = BudgetExpr::new_future_payment(dt, from, 42, to); + let orig_expr = expr.clone(); + expr.apply_witness(&Witness::Timestamp(dt), &to); // <-- Attack! + assert_eq!(expr, orig_expr); + } + + #[test] + fn test_cancelable_future_payment() { + let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); + let from = Pubkey::default(); + let to = Pubkey::default(); + + let mut expr = BudgetExpr::new_cancelable_future_payment(dt, from, 42, to); + expr.apply_witness(&Witness::Timestamp(dt), &from); + assert_eq!(expr, BudgetExpr::new_payment(42, to)); + + let mut expr = BudgetExpr::new_cancelable_future_payment(dt, from, 42, to); + expr.apply_witness(&Witness::Signature, &from); + assert_eq!(expr, BudgetExpr::new_payment(42, from)); + } + #[test] + fn test_2_2_multisig_payment() { + let from0 = Keypair::new().pubkey(); + let from1 = Keypair::new().pubkey(); + let to = Pubkey::default(); + + let mut expr = BudgetExpr::new_2_2_multisig_payment(from0, from1, 42, to); + expr.apply_witness(&Witness::Signature, &from0); + assert_eq!(expr, BudgetExpr::new_authorized_payment(from1, 42, to)); + } +} diff --git a/book/budget_instruction.rs b/book/budget_instruction.rs new file mode 100644 index 00000000000000..78ca94e2ac358b --- /dev/null +++ b/book/budget_instruction.rs @@ -0,0 +1,24 @@ +use budget_expr::BudgetExpr; +use chrono::prelude::{DateTime, Utc}; + +/// A smart contract. +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub struct Contract { + /// The number of tokens allocated to the `BudgetExpr` and any transaction fees. + pub tokens: u64, + pub budget_expr: BudgetExpr, +} + +/// An instruction to progress the smart contract. +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub enum Instruction { + /// Declare and instantiate `BudgetExpr`. + NewBudget(BudgetExpr), + + /// Tell a payment plan acknowledge the given `DateTime` has past. + ApplyTimestamp(DateTime), + + /// Tell the budget that the `NewBudget` with `Signature` has been + /// signed by the containing transaction's `Pubkey`. + ApplySignature, +} diff --git a/book/budget_program.rs b/book/budget_program.rs new file mode 100644 index 00000000000000..16603ddfcbd59f --- /dev/null +++ b/book/budget_program.rs @@ -0,0 +1,641 @@ +//! budget program +use bincode::{self, deserialize, serialize_into, serialized_size}; +use budget_expr::BudgetExpr; +use budget_instruction::Instruction; +use chrono::prelude::{DateTime, Utc}; +use payment_plan::Witness; +use solana_sdk::account::Account; +use solana_sdk::pubkey::Pubkey; +use std::io; +use transaction::Transaction; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub enum BudgetError { + InsufficientFunds, + ContractAlreadyExists, + ContractNotPending, + SourceIsPendingContract, + UninitializedContract, + NegativeTokens, + DestinationMissing, + FailedWitness, + UserdataTooSmall, + UserdataDeserializeFailure, + UnsignedKey, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] +pub struct BudgetState { + pub initialized: bool, + pub pending_budget: Option, +} + +const BUDGET_PROGRAM_ID: [u8; 32] = [ + 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +]; + +impl BudgetState { + fn is_pending(&self) -> bool { + self.pending_budget != None + } + pub fn id() -> Pubkey { + Pubkey::new(&BUDGET_PROGRAM_ID) + } + pub fn check_id(program_id: &Pubkey) -> bool { + program_id.as_ref() == BUDGET_PROGRAM_ID + } + + /// Process a Witness Signature. Any payment plans waiting on this signature + /// will progress one step. + fn apply_signature( + &mut self, + tx: &Transaction, + instruction_index: usize, + accounts: &mut [&mut Account], + ) -> Result<(), BudgetError> { + let mut final_payment = None; + if let Some(ref mut expr) = self.pending_budget { + let key = match tx.signed_key(instruction_index, 0) { + None => return Err(BudgetError::UnsignedKey), + Some(key) => key, + }; + expr.apply_witness(&Witness::Signature, key); + final_payment = expr.final_payment(); + } + + if let Some(payment) = final_payment { + if Some(&payment.to) != tx.key(instruction_index, 2) { + trace!("destination missing"); + return Err(BudgetError::DestinationMissing); + } + self.pending_budget = None; + accounts[1].tokens -= payment.tokens; + accounts[2].tokens += payment.tokens; + } + Ok(()) + } + + /// Process a Witness Timestamp. Any payment plans waiting on this timestamp + /// will progress one step. + fn apply_timestamp( + &mut self, + tx: &Transaction, + instruction_index: usize, + accounts: &mut [&mut Account], + dt: DateTime, + ) -> Result<(), BudgetError> { + // Check to see if any timelocked transactions can be completed. + let mut final_payment = None; + + if let Some(ref mut expr) = self.pending_budget { + let key = match tx.signed_key(instruction_index, 0) { + None => return Err(BudgetError::UnsignedKey), + Some(key) => key, + }; + expr.apply_witness(&Witness::Timestamp(dt), key); + final_payment = expr.final_payment(); + } + + if let Some(payment) = final_payment { + if Some(&payment.to) != tx.key(instruction_index, 2) { + trace!("destination missing"); + return Err(BudgetError::DestinationMissing); + } + self.pending_budget = None; + accounts[1].tokens -= payment.tokens; + accounts[2].tokens += payment.tokens; + } + Ok(()) + } + + fn apply_debits_to_budget_state( + tx: &Transaction, + instruction_index: usize, + accounts: &mut [&mut Account], + instruction: &Instruction, + ) -> Result<(), BudgetError> { + if !accounts[0].userdata.is_empty() { + trace!("source is pending"); + return Err(BudgetError::SourceIsPendingContract); + } + match instruction { + Instruction::NewBudget(expr) => { + let expr = expr.clone(); + if let Some(payment) = expr.final_payment() { + accounts[1].tokens += payment.tokens; + Ok(()) + } else { + let existing = Self::deserialize(&accounts[1].userdata).ok(); + if Some(true) == existing.map(|x| x.initialized) { + trace!("contract already exists"); + Err(BudgetError::ContractAlreadyExists) + } else { + let mut state = BudgetState::default(); + state.pending_budget = Some(expr); + accounts[1].tokens += accounts[0].tokens; + accounts[0].tokens = 0; + state.initialized = true; + state.serialize(&mut accounts[1].userdata) + } + } + } + Instruction::ApplyTimestamp(dt) => { + if let Ok(mut state) = Self::deserialize(&accounts[1].userdata) { + if !state.is_pending() { + Err(BudgetError::ContractNotPending) + } else if !state.initialized { + trace!("contract is uninitialized"); + Err(BudgetError::UninitializedContract) + } else { + trace!("apply timestamp"); + state.apply_timestamp(tx, instruction_index, accounts, *dt)?; + trace!("apply timestamp committed"); + state.serialize(&mut accounts[1].userdata) + } + } else { + Err(BudgetError::UninitializedContract) + } + } + Instruction::ApplySignature => { + if let Ok(mut state) = Self::deserialize(&accounts[1].userdata) { + if !state.is_pending() { + Err(BudgetError::ContractNotPending) + } else if !state.initialized { + trace!("contract is uninitialized"); + Err(BudgetError::UninitializedContract) + } else { + trace!("apply signature"); + state.apply_signature(tx, instruction_index, accounts)?; + trace!("apply signature committed"); + state.serialize(&mut accounts[1].userdata) + } + } else { + Err(BudgetError::UninitializedContract) + } + } + } + } + fn serialize(&self, output: &mut [u8]) -> Result<(), BudgetError> { + let len = serialized_size(self).unwrap() as u64; + if output.len() < len as usize { + warn!( + "{} bytes required to serialize, only have {} bytes", + len, + output.len() + ); + return Err(BudgetError::UserdataTooSmall); + } + { + let writer = io::BufWriter::new(&mut output[..8]); + serialize_into(writer, &len).unwrap(); + } + + { + let writer = io::BufWriter::new(&mut output[8..8 + len as usize]); + serialize_into(writer, self).unwrap(); + } + Ok(()) + } + + pub fn deserialize(input: &[u8]) -> bincode::Result { + if input.len() < 8 { + return Err(Box::new(bincode::ErrorKind::SizeLimit)); + } + let len: u64 = deserialize(&input[..8]).unwrap(); + if len < 2 { + return Err(Box::new(bincode::ErrorKind::SizeLimit)); + } + if input.len() < 8 + len as usize { + return Err(Box::new(bincode::ErrorKind::SizeLimit)); + } + deserialize(&input[8..8 + len as usize]) + } + + /// Budget DSL contract interface + /// * tx - the transaction + /// * accounts[0] - The source of the tokens + /// * accounts[1] - The contract context. Once the contract has been completed, the tokens can + /// be spent from this account . + pub fn process_transaction( + tx: &Transaction, + instruction_index: usize, + accounts: &mut [&mut Account], + ) -> Result<(), BudgetError> { + if let Ok(instruction) = deserialize(tx.userdata(instruction_index)) { + trace!("process_transaction: {:?}", instruction); + Self::apply_debits_to_budget_state(tx, instruction_index, accounts, &instruction) + } else { + info!( + "Invalid transaction userdata: {:?}", + tx.userdata(instruction_index) + ); + Err(BudgetError::UserdataDeserializeFailure) + } + } + + //TODO the contract needs to provide a "get_balance" introspection call of the userdata + pub fn get_balance(account: &Account) -> u64 { + if let Ok(state) = deserialize(&account.userdata) { + let state: BudgetState = state; + if state.is_pending() { + 0 + } else { + account.tokens + } + } else { + account.tokens + } + } +} +#[cfg(test)] +mod test { + use bincode::serialize; + use budget_program::{BudgetError, BudgetState}; + use budget_transaction::BudgetTransaction; + use chrono::prelude::{DateTime, NaiveDate, Utc}; + use signature::{GenKeys, Keypair, KeypairUtil}; + use solana_sdk::account::Account; + use solana_sdk::hash::Hash; + use solana_sdk::pubkey::Pubkey; + use transaction::Transaction; + + fn process_transaction(tx: &Transaction, accounts: &mut [Account]) -> Result<(), BudgetError> { + let mut refs: Vec<&mut Account> = accounts.iter_mut().collect(); + BudgetState::process_transaction(&tx, 0, &mut refs[..]) + } + #[test] + fn test_serializer() { + let mut a = Account::new(0, 512, BudgetState::id()); + let b = BudgetState::default(); + b.serialize(&mut a.userdata).unwrap(); + let buf = serialize(&b).unwrap(); + assert_eq!(a.userdata[8..8 + buf.len()], buf[0..]); + let c = BudgetState::deserialize(&a.userdata).unwrap(); + assert_eq!(b, c); + } + + #[test] + fn test_serializer_userdata_too_small() { + let mut a = Account::new(0, 1, BudgetState::id()); + let b = BudgetState::default(); + assert_eq!( + b.serialize(&mut a.userdata), + Err(BudgetError::UserdataTooSmall) + ); + } + #[test] + fn test_invalid_instruction() { + let mut accounts = vec![ + Account::new(1, 0, BudgetState::id()), + Account::new(0, 512, BudgetState::id()), + ]; + let from = Keypair::new(); + let contract = Keypair::new(); + let userdata = (1u8, 2u8, 3u8); + let tx = Transaction::new( + &from, + &[contract.pubkey()], + BudgetState::id(), + &userdata, + Hash::default(), + 0, + ); + assert!(process_transaction(&tx, &mut accounts).is_err()); + } + + #[test] + fn test_unsigned_witness_key() { + let mut accounts = vec![ + Account::new(1, 0, BudgetState::id()), + Account::new(0, 512, BudgetState::id()), + Account::new(0, 0, BudgetState::id()), + ]; + + // Initialize BudgetState + let from = Keypair::new(); + let contract = Keypair::new().pubkey(); + let to = Keypair::new().pubkey(); + let witness = Keypair::new().pubkey(); + let tx = Transaction::budget_new_when_signed( + &from, + to, + contract, + witness, + None, + 1, + Hash::default(), + ); + process_transaction(&tx, &mut accounts).unwrap(); + + // Attack! Part 1: Sign a witness transaction with a random key. + let rando = Keypair::new(); + let mut tx = Transaction::budget_new_signature(&rando, contract, to, Hash::default()); + + // Attack! Part 2: Point the instruction to the expected, but unsigned, key. + tx.account_keys.push(from.pubkey()); + tx.instructions[0].accounts[0] = 3; + + // Ensure the transaction fails because of the unsigned key. + assert_eq!( + process_transaction(&tx, &mut accounts), + Err(BudgetError::UnsignedKey) + ); + } + + #[test] + fn test_unsigned_timestamp() { + let mut accounts = vec![ + Account::new(1, 0, BudgetState::id()), + Account::new(0, 512, BudgetState::id()), + Account::new(0, 0, BudgetState::id()), + ]; + + // Initialize BudgetState + let from = Keypair::new(); + let contract = Keypair::new().pubkey(); + let to = Keypair::new().pubkey(); + let dt = Utc::now(); + let tx = Transaction::budget_new_on_date( + &from, + to, + contract, + dt, + from.pubkey(), + None, + 1, + Hash::default(), + ); + process_transaction(&tx, &mut accounts).unwrap(); + + // Attack! Part 1: Sign a timestamp transaction with a random key. + let rando = Keypair::new(); + let mut tx = Transaction::budget_new_timestamp(&rando, contract, to, dt, Hash::default()); + + // Attack! Part 2: Point the instruction to the expected, but unsigned, key. + tx.account_keys.push(from.pubkey()); + tx.instructions[0].accounts[0] = 3; + + // Ensure the transaction fails because of the unsigned key. + assert_eq!( + process_transaction(&tx, &mut accounts), + Err(BudgetError::UnsignedKey) + ); + } + + #[test] + fn test_transfer_on_date() { + let mut accounts = vec![ + Account::new(1, 0, BudgetState::id()), + Account::new(0, 512, BudgetState::id()), + Account::new(0, 0, BudgetState::id()), + ]; + let from_account = 0; + let contract_account = 1; + let to_account = 2; + let from = Keypair::new(); + let contract = Keypair::new(); + let to = Keypair::new(); + let rando = Keypair::new(); + let dt = Utc::now(); + let tx = Transaction::budget_new_on_date( + &from, + to.pubkey(), + contract.pubkey(), + dt, + from.pubkey(), + None, + 1, + Hash::default(), + ); + process_transaction(&tx, &mut accounts).unwrap(); + assert_eq!(accounts[from_account].tokens, 0); + assert_eq!(accounts[contract_account].tokens, 1); + let state = BudgetState::deserialize(&accounts[contract_account].userdata).unwrap(); + assert!(state.is_pending()); + + // Attack! Try to payout to a rando key + let tx = Transaction::budget_new_timestamp( + &from, + contract.pubkey(), + rando.pubkey(), + dt, + Hash::default(), + ); + assert_eq!( + process_transaction(&tx, &mut accounts), + Err(BudgetError::DestinationMissing) + ); + assert_eq!(accounts[from_account].tokens, 0); + assert_eq!(accounts[contract_account].tokens, 1); + assert_eq!(accounts[to_account].tokens, 0); + + let state = BudgetState::deserialize(&accounts[contract_account].userdata).unwrap(); + assert!(state.is_pending()); + + // Now, acknowledge the time in the condition occurred and + // that pubkey's funds are now available. + let tx = Transaction::budget_new_timestamp( + &from, + contract.pubkey(), + to.pubkey(), + dt, + Hash::default(), + ); + process_transaction(&tx, &mut accounts).unwrap(); + assert_eq!(accounts[from_account].tokens, 0); + assert_eq!(accounts[contract_account].tokens, 0); + assert_eq!(accounts[to_account].tokens, 1); + + let state = BudgetState::deserialize(&accounts[contract_account].userdata).unwrap(); + assert!(!state.is_pending()); + + // try to replay the timestamp contract + assert_eq!( + process_transaction(&tx, &mut accounts), + Err(BudgetError::ContractNotPending) + ); + assert_eq!(accounts[from_account].tokens, 0); + assert_eq!(accounts[contract_account].tokens, 0); + assert_eq!(accounts[to_account].tokens, 1); + } + #[test] + fn test_cancel_transfer() { + let mut accounts = vec![ + Account::new(1, 0, BudgetState::id()), + Account::new(0, 512, BudgetState::id()), + Account::new(0, 0, BudgetState::id()), + ]; + let from_account = 0; + let contract_account = 1; + let pay_account = 2; + let from = Keypair::new(); + let contract = Keypair::new(); + let to = Keypair::new(); + let dt = Utc::now(); + let tx = Transaction::budget_new_on_date( + &from, + to.pubkey(), + contract.pubkey(), + dt, + from.pubkey(), + Some(from.pubkey()), + 1, + Hash::default(), + ); + process_transaction(&tx, &mut accounts).unwrap(); + assert_eq!(accounts[from_account].tokens, 0); + assert_eq!(accounts[contract_account].tokens, 1); + let state = BudgetState::deserialize(&accounts[contract_account].userdata).unwrap(); + assert!(state.is_pending()); + + // Attack! try to put the tokens into the wrong account with cancel + let tx = + Transaction::budget_new_signature(&to, contract.pubkey(), to.pubkey(), Hash::default()); + // unit test hack, the `from account` is passed instead of the `to` account to avoid + // creating more account vectors + process_transaction(&tx, &mut accounts).unwrap(); + // nothing should be changed because apply witness didn't finalize a payment + assert_eq!(accounts[from_account].tokens, 0); + assert_eq!(accounts[contract_account].tokens, 1); + // this would be the `to.pubkey()` account + assert_eq!(accounts[pay_account].tokens, 0); + + // Now, cancel the transaction. from gets her funds back + let tx = Transaction::budget_new_signature( + &from, + contract.pubkey(), + from.pubkey(), + Hash::default(), + ); + process_transaction(&tx, &mut accounts).unwrap(); + assert_eq!(accounts[from_account].tokens, 0); + assert_eq!(accounts[contract_account].tokens, 0); + assert_eq!(accounts[pay_account].tokens, 1); + + // try to replay the signature contract + let tx = Transaction::budget_new_signature( + &from, + contract.pubkey(), + from.pubkey(), + Hash::default(), + ); + assert_eq!( + process_transaction(&tx, &mut accounts), + Err(BudgetError::ContractNotPending) + ); + assert_eq!(accounts[from_account].tokens, 0); + assert_eq!(accounts[contract_account].tokens, 0); + assert_eq!(accounts[pay_account].tokens, 1); + } + + #[test] + fn test_userdata_too_small() { + let mut accounts = vec![ + Account::new(1, 0, BudgetState::id()), + Account::new(1, 0, BudgetState::id()), // <== userdata is 0, which is not enough + Account::new(1, 0, BudgetState::id()), + ]; + let from = Keypair::new(); + let contract = Keypair::new(); + let to = Keypair::new(); + let tx = Transaction::budget_new_on_date( + &from, + to.pubkey(), + contract.pubkey(), + Utc::now(), + from.pubkey(), + None, + 1, + Hash::default(), + ); + + assert!(process_transaction(&tx, &mut accounts).is_err()); + assert!(BudgetState::deserialize(&accounts[1].userdata).is_err()); + + let tx = Transaction::budget_new_timestamp( + &from, + contract.pubkey(), + to.pubkey(), + Utc::now(), + Hash::default(), + ); + assert!(process_transaction(&tx, &mut accounts).is_err()); + assert!(BudgetState::deserialize(&accounts[1].userdata).is_err()); + + // Success if there was no panic... + } + + /// Detect binary changes in the serialized contract userdata, which could have a downstream + /// affect on SDKs and DApps + #[test] + fn test_sdk_serialize() { + let keypair = &GenKeys::new([0u8; 32]).gen_n_keypairs(1)[0]; + let to = Pubkey::new(&[ + 1, 1, 1, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 8, 7, 6, 5, 4, + 1, 1, 1, + ]); + let contract = Pubkey::new(&[ + 2, 2, 2, 4, 5, 6, 7, 8, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 8, 7, 6, 5, 4, + 2, 2, 2, + ]); + let date = + DateTime::::from_utc(NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11), Utc); + let date_iso8601 = "2016-07-08T09:10:11Z"; + + let tx = Transaction::budget_new(&keypair, to, 192, Hash::default()); + assert_eq!( + tx.userdata(0).to_vec(), + vec![2, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0] + ); + assert_eq!( + tx.userdata(1).to_vec(), + vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 4, 5, 6, 7, 8, 9, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 8, 7, 6, 5, 4, 1, 1, 1 + ] + ); + + let tx = Transaction::budget_new_on_date( + &keypair, + to, + contract, + date, + keypair.pubkey(), + Some(keypair.pubkey()), + 192, + Hash::default(), + ); + assert_eq!( + tx.userdata(0).to_vec(), + vec![ + 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 50, 48, 49, 54, 45, + 48, 55, 45, 48, 56, 84, 48, 57, 58, 49, 48, 58, 49, 49, 90, 32, 253, 186, 201, 177, + 11, 117, 135, 187, 167, 181, 188, 22, 59, 206, 105, 231, 150, 215, 30, 78, 212, 76, + 16, 252, 180, 72, 134, 137, 247, 161, 68, 192, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 4, 5, + 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 8, 7, 6, 5, 4, 1, 1, 1, 1, + 0, 0, 0, 32, 253, 186, 201, 177, 11, 117, 135, 187, 167, 181, 188, 22, 59, 206, + 105, 231, 150, 215, 30, 78, 212, 76, 16, 252, 180, 72, 134, 137, 247, 161, 68, 192, + 0, 0, 0, 0, 0, 0, 0, 32, 253, 186, 201, 177, 11, 117, 135, 187, 167, 181, 188, 22, + 59, 206, 105, 231, 150, 215, 30, 78, 212, 76, 16, 252, 180, 72, 134, 137, 247, 161, + 68 + ] + ); + + // ApplyTimestamp(date) + let tx = Transaction::budget_new_timestamp( + &keypair, + keypair.pubkey(), + to, + date, + Hash::default(), + ); + let mut expected_userdata = vec![1, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0]; + expected_userdata.extend(date_iso8601.as_bytes()); + assert_eq!(tx.userdata(0).to_vec(), expected_userdata); + + // ApplySignature + let tx = Transaction::budget_new_signature(&keypair, keypair.pubkey(), to, Hash::default()); + assert_eq!(tx.userdata(0).to_vec(), vec![2, 0, 0, 0]); + } +} diff --git a/book/budget_transaction.rs b/book/budget_transaction.rs new file mode 100644 index 00000000000000..7f664890cd574d --- /dev/null +++ b/book/budget_transaction.rs @@ -0,0 +1,346 @@ +//! The `budget_transaction` module provides functionality for creating Budget transactions. + +use bincode::deserialize; +use budget_expr::{BudgetExpr, Condition}; +use budget_instruction::Instruction; +use budget_program::BudgetState; +use chrono::prelude::*; +use payment_plan::Payment; +use signature::{Keypair, KeypairUtil}; +use solana_sdk::hash::Hash; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::system_instruction::{SystemInstruction, SYSTEM_PROGRAM_ID}; +use transaction::{self, Transaction}; + +pub trait BudgetTransaction { + fn budget_new_taxed( + from_keypair: &Keypair, + to: Pubkey, + tokens: u64, + fee: u64, + last_id: Hash, + ) -> Self; + + fn budget_new(from_keypair: &Keypair, to: Pubkey, tokens: u64, last_id: Hash) -> Self; + + fn budget_new_timestamp( + from_keypair: &Keypair, + contract: Pubkey, + to: Pubkey, + dt: DateTime, + last_id: Hash, + ) -> Self; + + fn budget_new_signature( + from_keypair: &Keypair, + contract: Pubkey, + to: Pubkey, + last_id: Hash, + ) -> Self; + + fn budget_new_on_date( + from_keypair: &Keypair, + to: Pubkey, + contract: Pubkey, + dt: DateTime, + dt_pubkey: Pubkey, + cancelable: Option, + tokens: u64, + last_id: Hash, + ) -> Self; + + fn budget_new_when_signed( + from_keypair: &Keypair, + to: Pubkey, + contract: Pubkey, + witness: Pubkey, + cancelable: Option, + tokens: u64, + last_id: Hash, + ) -> Self; + + fn instruction(&self, program_index: usize) -> Option; + fn system_instruction(&self, program_index: usize) -> Option; + + fn verify_plan(&self) -> bool; +} + +impl BudgetTransaction for Transaction { + /// Create and sign a new Transaction. Used for unit-testing. + fn budget_new_taxed( + from_keypair: &Keypair, + to: Pubkey, + tokens: u64, + fee: u64, + last_id: Hash, + ) -> Self { + let contract = Keypair::new().pubkey(); + let keys = vec![from_keypair.pubkey(), contract]; + + let system_instruction = SystemInstruction::Move { tokens }; + + let payment = Payment { + tokens: tokens - fee, + to, + }; + let budget_instruction = Instruction::NewBudget(BudgetExpr::Pay(payment)); + + let program_ids = vec![Pubkey::new(&SYSTEM_PROGRAM_ID), BudgetState::id()]; + + let instructions = vec![ + transaction::Instruction::new(0, &system_instruction, vec![0, 1]), + transaction::Instruction::new(1, &budget_instruction, vec![1]), + ]; + + Self::new_with_instructions( + &[from_keypair], + &keys, + last_id, + fee, + program_ids, + instructions, + ) + } + + /// Create and sign a new Transaction. Used for unit-testing. + fn budget_new(from_keypair: &Keypair, to: Pubkey, tokens: u64, last_id: Hash) -> Self { + Self::budget_new_taxed(from_keypair, to, tokens, 0, last_id) + } + + /// Create and sign a new Witness Timestamp. Used for unit-testing. + fn budget_new_timestamp( + from_keypair: &Keypair, + contract: Pubkey, + to: Pubkey, + dt: DateTime, + last_id: Hash, + ) -> Self { + let instruction = Instruction::ApplyTimestamp(dt); + Self::new( + from_keypair, + &[contract, to], + BudgetState::id(), + &instruction, + last_id, + 0, + ) + } + + /// Create and sign a new Witness Signature. Used for unit-testing. + fn budget_new_signature( + from_keypair: &Keypair, + contract: Pubkey, + to: Pubkey, + last_id: Hash, + ) -> Self { + let instruction = Instruction::ApplySignature; + Self::new( + from_keypair, + &[contract, to], + BudgetState::id(), + &instruction, + last_id, + 0, + ) + } + + /// Create and sign a postdated Transaction. Used for unit-testing. + fn budget_new_on_date( + from_keypair: &Keypair, + to: Pubkey, + contract: Pubkey, + dt: DateTime, + dt_pubkey: Pubkey, + cancelable: Option, + tokens: u64, + last_id: Hash, + ) -> Self { + let expr = if let Some(from) = cancelable { + BudgetExpr::Or( + (Condition::Timestamp(dt, dt_pubkey), Payment { tokens, to }), + (Condition::Signature(from), Payment { tokens, to: from }), + ) + } else { + BudgetExpr::After(Condition::Timestamp(dt, dt_pubkey), Payment { tokens, to }) + }; + let instruction = Instruction::NewBudget(expr); + Self::new( + from_keypair, + &[contract], + BudgetState::id(), + &instruction, + last_id, + 0, + ) + } + /// Create and sign a multisig Transaction. + fn budget_new_when_signed( + from_keypair: &Keypair, + to: Pubkey, + contract: Pubkey, + witness: Pubkey, + cancelable: Option, + tokens: u64, + last_id: Hash, + ) -> Self { + let expr = if let Some(from) = cancelable { + BudgetExpr::Or( + (Condition::Signature(witness), Payment { tokens, to }), + (Condition::Signature(from), Payment { tokens, to: from }), + ) + } else { + BudgetExpr::After(Condition::Signature(witness), Payment { tokens, to }) + }; + let instruction = Instruction::NewBudget(expr); + Self::new( + from_keypair, + &[contract], + BudgetState::id(), + &instruction, + last_id, + 0, + ) + } + + fn instruction(&self, instruction_index: usize) -> Option { + deserialize(&self.userdata(instruction_index)).ok() + } + + fn system_instruction(&self, instruction_index: usize) -> Option { + deserialize(&self.userdata(instruction_index)).ok() + } + + /// Verify only the payment plan. + fn verify_plan(&self) -> bool { + if let Some(SystemInstruction::Move { tokens }) = self.system_instruction(0) { + if let Some(Instruction::NewBudget(expr)) = self.instruction(1) { + if !(self.fee <= tokens && expr.verify(tokens - self.fee)) { + return false; + } + } + } + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bincode::{deserialize, serialize}; + use signature::KeypairUtil; + use transaction; + + #[test] + fn test_claim() { + let keypair = Keypair::new(); + let zero = Hash::default(); + let tx0 = Transaction::budget_new(&keypair, keypair.pubkey(), 42, zero); + assert!(tx0.verify_plan()); + } + + #[test] + fn test_transfer() { + let zero = Hash::default(); + let keypair0 = Keypair::new(); + let keypair1 = Keypair::new(); + let pubkey1 = keypair1.pubkey(); + let tx0 = Transaction::budget_new(&keypair0, pubkey1, 42, zero); + assert!(tx0.verify_plan()); + } + + #[test] + fn test_transfer_with_fee() { + let zero = Hash::default(); + let keypair0 = Keypair::new(); + let pubkey1 = Keypair::new().pubkey(); + assert!(Transaction::budget_new_taxed(&keypair0, pubkey1, 1, 1, zero).verify_plan()); + } + + #[test] + fn test_serialize_claim() { + let expr = BudgetExpr::Pay(Payment { + tokens: 0, + to: Default::default(), + }); + let instruction = Instruction::NewBudget(expr); + let instructions = vec![transaction::Instruction::new(0, &instruction, vec![])]; + let claim0 = Transaction { + account_keys: vec![], + last_id: Default::default(), + signatures: vec![], + program_ids: vec![], + instructions, + fee: 0, + }; + let buf = serialize(&claim0).unwrap(); + let claim1: Transaction = deserialize(&buf).unwrap(); + assert_eq!(claim1, claim0); + } + + #[test] + fn test_token_attack() { + let zero = Hash::default(); + let keypair = Keypair::new(); + let pubkey = keypair.pubkey(); + let mut tx = Transaction::budget_new(&keypair, pubkey, 42, zero); + let mut system_instruction = tx.system_instruction(0).unwrap(); + if let SystemInstruction::Move { ref mut tokens } = system_instruction { + *tokens = 1_000_000; // <-- attack, part 1! + let mut instruction = tx.instruction(1).unwrap(); + if let Instruction::NewBudget(ref mut expr) = instruction { + if let BudgetExpr::Pay(ref mut payment) = expr { + payment.tokens = *tokens; // <-- attack, part 2! + } + } + tx.instructions[1].userdata = serialize(&instruction).unwrap(); + } + tx.instructions[0].userdata = serialize(&system_instruction).unwrap(); + assert!(tx.verify_plan()); + assert!(!tx.verify_signature()); + } + + #[test] + fn test_hijack_attack() { + let keypair0 = Keypair::new(); + let keypair1 = Keypair::new(); + let thief_keypair = Keypair::new(); + let pubkey1 = keypair1.pubkey(); + let zero = Hash::default(); + let mut tx = Transaction::budget_new(&keypair0, pubkey1, 42, zero); + let mut instruction = tx.instruction(1); + if let Some(Instruction::NewBudget(ref mut expr)) = instruction { + if let BudgetExpr::Pay(ref mut payment) = expr { + payment.to = thief_keypair.pubkey(); // <-- attack! + } + } + tx.instructions[1].userdata = serialize(&instruction).unwrap(); + assert!(tx.verify_plan()); + assert!(!tx.verify_signature()); + } + + #[test] + fn test_overspend_attack() { + let keypair0 = Keypair::new(); + let keypair1 = Keypair::new(); + let zero = Hash::default(); + let mut tx = Transaction::budget_new(&keypair0, keypair1.pubkey(), 1, zero); + let mut instruction = tx.instruction(1).unwrap(); + if let Instruction::NewBudget(ref mut expr) = instruction { + if let BudgetExpr::Pay(ref mut payment) = expr { + payment.tokens = 2; // <-- attack! + } + } + tx.instructions[1].userdata = serialize(&instruction).unwrap(); + assert!(!tx.verify_plan()); + + // Also, ensure all branchs of the plan spend all tokens + let mut instruction = tx.instruction(1).unwrap(); + if let Instruction::NewBudget(ref mut expr) = instruction { + if let BudgetExpr::Pay(ref mut payment) = expr { + payment.tokens = 0; // <-- whoops! + } + } + tx.instructions[1].userdata = serialize(&instruction).unwrap(); + assert!(!tx.verify_plan()); + } +} diff --git a/book/chacha.rs b/book/chacha.rs new file mode 100644 index 00000000000000..896a7d06f146ff --- /dev/null +++ b/book/chacha.rs @@ -0,0 +1,92 @@ +use std::fs::File; +use std::io; +use std::io::Read; +use std::io::Write; +use std::io::{BufReader, BufWriter}; +use std::path::Path; + +pub const CHACHA_BLOCK_SIZE: usize = 64; +pub const CHACHA_KEY_SIZE: usize = 32; + +#[link(name = "cpu-crypt")] +extern "C" { + fn chacha20_cbc_encrypt( + input: *const u8, + output: *mut u8, + in_len: usize, + key: *const u8, + ivec: *mut u8, + ); +} + +pub fn chacha_cbc_encrypt(input: &[u8], output: &mut [u8], key: &[u8], ivec: &mut [u8]) { + unsafe { + chacha20_cbc_encrypt( + input.as_ptr(), + output.as_mut_ptr(), + input.len(), + key.as_ptr(), + ivec.as_mut_ptr(), + ); + } +} + +pub fn chacha_cbc_encrypt_file( + in_path: &Path, + out_path: &Path, + ivec: &mut [u8; CHACHA_BLOCK_SIZE], +) -> io::Result<()> { + let mut in_file = BufReader::new(File::open(in_path).expect("Can't open ledger data file")); + let mut out_file = + BufWriter::new(File::create(out_path).expect("Can't open ledger encrypted data file")); + let mut buffer = [0; 4 * 1024]; + let mut encrypted_buffer = [0; 4 * 1024]; + let key = [0; CHACHA_KEY_SIZE]; + + while let Ok(size) = in_file.read(&mut buffer) { + debug!("read {} bytes", size); + if size == 0 { + break; + } + chacha_cbc_encrypt(&buffer[..size], &mut encrypted_buffer[..size], &key, ivec); + if let Err(res) = out_file.write(&encrypted_buffer[..size]) { + println!("Error writing file! {:?}", res); + return Err(res); + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use chacha::chacha_cbc_encrypt_file; + use std::fs::remove_file; + use std::fs::File; + use std::io::Read; + use std::io::Write; + use std::path::Path; + + #[test] + fn test_encrypt_file() { + let in_path = Path::new("test_chacha_encrypt_file_input.txt"); + let out_path = Path::new("test_chacha_encrypt_file_output.txt.enc"); + { + let mut in_file = File::create(in_path).unwrap(); + in_file.write("123456foobar".as_bytes()).unwrap(); + } + let mut key = hex!( + "abcd1234abcd1234abcd1234abcd1234 abcd1234abcd1234abcd1234abcd1234 + abcd1234abcd1234abcd1234abcd1234 abcd1234abcd1234abcd1234abcd1234" + ); + assert!(chacha_cbc_encrypt_file(in_path, out_path, &mut key).is_ok()); + let mut out_file = File::open(out_path).unwrap(); + let mut buf = vec![]; + let size = out_file.read_to_end(&mut buf).unwrap(); + assert_eq!( + buf[..size], + [66, 54, 56, 212, 142, 110, 105, 158, 116, 82, 120, 53] + ); + remove_file(in_path).unwrap(); + remove_file(out_path).unwrap(); + } +} diff --git a/book/chacha_cuda.rs b/book/chacha_cuda.rs new file mode 100644 index 00000000000000..b9dfb310423308 --- /dev/null +++ b/book/chacha_cuda.rs @@ -0,0 +1,212 @@ +use chacha::{CHACHA_BLOCK_SIZE, CHACHA_KEY_SIZE}; +use ledger::LedgerWindow; +use sigverify::{chacha_cbc_encrypt_many_sample, chacha_end_sha_state, chacha_init_sha_state}; +use solana_sdk::hash::Hash; +use std::io; +use std::mem::size_of; + +use storage_stage::ENTRIES_PER_SLICE; + +// Encrypt a file with multiple starting IV states, determined by ivecs.len() +// +// Then sample each block at the offsets provided by samples argument with sha256 +// and return the vec of sha states +pub fn chacha_cbc_encrypt_file_many_keys( + in_path: &str, + slice: u64, + ivecs: &mut [u8], + samples: &[u64], +) -> io::Result> { + if ivecs.len() % CHACHA_BLOCK_SIZE != 0 { + return Err(io::Error::new( + io::ErrorKind::Other, + format!( + "bad IV length({}) not divisible by {} ", + ivecs.len(), + CHACHA_BLOCK_SIZE, + ), + )); + } + + let mut ledger_window = LedgerWindow::open(in_path)?; + let mut buffer = [0; 8 * 1024]; + let num_keys = ivecs.len() / CHACHA_BLOCK_SIZE; + let mut sha_states = vec![0; num_keys * size_of::()]; + let mut int_sha_states = vec![0; num_keys * 112]; + let keys: Vec = vec![0; num_keys * CHACHA_KEY_SIZE]; // keys not used ATM, uniqueness comes from IV + let mut entry = slice; + let mut total_entries = 0; + let mut total_entry_len = 0; + let mut time: f32 = 0.0; + unsafe { + chacha_init_sha_state(int_sha_states.as_mut_ptr(), num_keys as u32); + } + loop { + match ledger_window.get_entries_bytes(entry, ENTRIES_PER_SLICE - total_entries, &mut buffer) + { + Ok((num_entries, entry_len)) => { + info!( + "encrypting slice: {} num_entries: {} entry_len: {}", + slice, num_entries, entry_len + ); + let entry_len_usz = entry_len as usize; + unsafe { + chacha_cbc_encrypt_many_sample( + buffer[..entry_len_usz].as_ptr(), + int_sha_states.as_mut_ptr(), + entry_len_usz, + keys.as_ptr(), + ivecs.as_mut_ptr(), + num_keys as u32, + samples.as_ptr(), + samples.len() as u32, + total_entry_len, + &mut time, + ); + } + + total_entry_len += entry_len; + total_entries += num_entries; + entry += num_entries; + debug!( + "total entries: {} entry: {} slice: {} entries_per_slice: {}", + total_entries, entry, slice, ENTRIES_PER_SLICE + ); + if (entry - slice) >= ENTRIES_PER_SLICE { + break; + } + } + Err(e) => { + info!("Error encrypting file: {:?}", e); + break; + } + } + } + unsafe { + chacha_end_sha_state( + int_sha_states.as_ptr(), + sha_states.as_mut_ptr(), + num_keys as u32, + ); + } + let mut res = Vec::new(); + for x in 0..num_keys { + let start = x * size_of::(); + let end = start + size_of::(); + res.push(Hash::new(&sha_states[start..end])); + } + Ok(res) +} + +#[cfg(test)] +mod tests { + use chacha::chacha_cbc_encrypt_file; + use chacha_cuda::chacha_cbc_encrypt_file_many_keys; + use ledger::LedgerWriter; + use ledger::{get_tmp_ledger_path, make_tiny_test_entries, LEDGER_DATA_FILE}; + use replicator::sample_file; + use solana_sdk::hash::Hash; + use std::fs::{remove_dir_all, remove_file}; + use std::path::Path; + + #[test] + fn test_encrypt_file_many_keys_single() { + use logger; + logger::setup(); + + let entries = make_tiny_test_entries(32); + let ledger_dir = "test_encrypt_file_many_keys_single"; + let ledger_path = get_tmp_ledger_path(ledger_dir); + { + let mut writer = LedgerWriter::open(&ledger_path, true).unwrap(); + writer.write_entries(&entries).unwrap(); + } + + let out_path = Path::new("test_chacha_encrypt_file_many_keys_single_output.txt.enc"); + + let samples = [0]; + let mut ivecs = hex!( + "abcd1234abcd1234abcd1234abcd1234 abcd1234abcd1234abcd1234abcd1234 + abcd1234abcd1234abcd1234abcd1234 abcd1234abcd1234abcd1234abcd1234" + ); + + let mut cpu_iv = ivecs.clone(); + assert!( + chacha_cbc_encrypt_file( + &Path::new(&ledger_path).join(LEDGER_DATA_FILE), + out_path, + &mut cpu_iv, + ).is_ok() + ); + + let ref_hash = sample_file(&out_path, &samples).unwrap(); + + let hashes = + chacha_cbc_encrypt_file_many_keys(&ledger_path, 0, &mut ivecs, &samples).unwrap(); + + assert_eq!(hashes[0], ref_hash); + + let _ignored = remove_dir_all(&ledger_path); + let _ignored = remove_file(out_path); + } + + #[test] + fn test_encrypt_file_many_keys_multiple_keys() { + use logger; + logger::setup(); + + let entries = make_tiny_test_entries(32); + let ledger_dir = "test_encrypt_file_many_keys_multiple"; + let ledger_path = get_tmp_ledger_path(ledger_dir); + { + let mut writer = LedgerWriter::open(&ledger_path, true).unwrap(); + writer.write_entries(&entries).unwrap(); + } + + let out_path = Path::new("test_chacha_encrypt_file_many_keys_multiple_output.txt.enc"); + + let samples = [0, 1, 3, 4, 5, 150]; + let mut ivecs = Vec::new(); + let mut ref_hashes: Vec = vec![]; + for i in 0..2 { + let mut ivec = hex!( + "abc123abc123abc123abc123abc123abc123abababababababababababababab + abc123abc123abc123abc123abc123abc123abababababababababababababab" + ); + ivec[0] = i; + ivecs.extend(ivec.clone().iter()); + assert!( + chacha_cbc_encrypt_file( + &Path::new(&ledger_path).join(LEDGER_DATA_FILE), + out_path, + &mut ivec, + ).is_ok() + ); + + ref_hashes.push(sample_file(&out_path, &samples).unwrap()); + info!( + "ivec: {:?} hash: {:?} ivecs: {:?}", + ivec.to_vec(), + ref_hashes.last(), + ivecs + ); + } + + let hashes = + chacha_cbc_encrypt_file_many_keys(&ledger_path, 0, &mut ivecs, &samples).unwrap(); + + assert_eq!(hashes, ref_hashes); + + let _ignored = remove_dir_all(&ledger_path); + let _ignored = remove_file(out_path); + } + + #[test] + fn test_encrypt_file_many_keys_bad_key_length() { + let mut keys = hex!("abc123"); + let ledger_dir = "test_encrypt_file_many_keys_bad_key_length"; + let ledger_path = get_tmp_ledger_path(ledger_dir); + let samples = [0]; + assert!(chacha_cbc_encrypt_file_many_keys(&ledger_path, 0, &mut keys, &samples,).is_err()); + } +} diff --git a/book/client.rs b/book/client.rs new file mode 100644 index 00000000000000..7d278eb3f56952 --- /dev/null +++ b/book/client.rs @@ -0,0 +1,8 @@ +use cluster_info::{NodeInfo, FULLNODE_PORT_RANGE}; +use netutil::bind_in_range; +use thin_client::ThinClient; + +pub fn mk_client(r: &NodeInfo) -> ThinClient { + let (_, transactions_socket) = bind_in_range(FULLNODE_PORT_RANGE).unwrap(); + ThinClient::new(r.rpc, r.tpu, transactions_socket) +} diff --git a/book/clipboard.min.js b/book/clipboard.min.js new file mode 100644 index 00000000000000..1993676f992881 --- /dev/null +++ b/book/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v1.6.1 + * https://zenorocha.github.io/clipboard.js + * + * Licensed MIT © Zeno Rocha + */ +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.Clipboard=e()}}(function(){var e,t,n;return function e(t,n,o){function i(a,c){if(!n[a]){if(!t[a]){var l="function"==typeof require&&require;if(!c&&l)return l(a,!0);if(r)return r(a,!0);var u=new Error("Cannot find module '"+a+"'");throw u.code="MODULE_NOT_FOUND",u}var s=n[a]={exports:{}};t[a][0].call(s.exports,function(e){var n=t[a][1][e];return i(n?n:e)},s,s.exports,e,t,n,o)}return n[a].exports}for(var r="function"==typeof require&&require,a=0;a0&&void 0!==arguments[0]?arguments[0]:{};this.action=t.action,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function e(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function e(){var t=this,n="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=document.body.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[n?"right":"left"]="-9999px";var o=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=o+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,document.body.appendChild(this.fakeElem),this.selectedText=(0,i.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function e(){this.fakeHandler&&(document.body.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(document.body.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function e(){this.selectedText=(0,i.default)(this.target),this.copyText()}},{key:"copyText",value:function e(){var t=void 0;try{t=document.execCommand(this.action)}catch(e){t=!1}this.handleResult(t)}},{key:"handleResult",value:function e(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function e(){this.target&&this.target.blur(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function e(){this.removeFake()}},{key:"action",set:function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function e(){return this._action}},{key:"target",set:function e(t){if(void 0!==t){if(!t||"object"!==("undefined"==typeof t?"undefined":r(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function e(){return this._target}}]),e}();e.exports=c})},{select:5}],8:[function(t,n,o){!function(i,r){if("function"==typeof e&&e.amd)e(["module","./clipboard-action","tiny-emitter","good-listener"],r);else if("undefined"!=typeof o)r(n,t("./clipboard-action"),t("tiny-emitter"),t("good-listener"));else{var a={exports:{}};r(a,i.clipboardAction,i.tinyEmitter,i.goodListener),i.clipboard=a.exports}}(this,function(e,t,n,o){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function c(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function l(e,t){var n="data-clipboard-"+e;if(t.hasAttribute(n))return t.getAttribute(n)}var u=i(t),s=i(n),f=i(o),d=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText}},{key:"listenClick",value:function e(t){var n=this;this.listener=(0,f.default)(t,"click",function(e){return n.onClick(e)})}},{key:"onClick",value:function e(t){var n=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new u.default({action:this.action(n),target:this.target(n),text:this.text(n),trigger:n,emitter:this})}},{key:"defaultAction",value:function e(t){return l("action",t)}},{key:"defaultTarget",value:function e(t){var n=l("target",t);if(n)return document.querySelector(n)}},{key:"defaultText",value:function e(t){return l("text",t)}},{key:"destroy",value:function e(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],n="string"==typeof t?[t]:t,o=!!document.queryCommandSupported;return n.forEach(function(e){o=o&&!!document.queryCommandSupported(e)}),o}}]),t}(s.default);e.exports=h})},{"./clipboard-action":7,"good-listener":4,"tiny-emitter":6}]},{},[8])(8)}); \ No newline at end of file diff --git a/book/cluster_info.rs b/book/cluster_info.rs new file mode 100644 index 00000000000000..82e2922e13b74b --- /dev/null +++ b/book/cluster_info.rs @@ -0,0 +1,1346 @@ +//! The `cluster_info` module defines a data structure that is shared by all the nodes in the network over +//! a gossip control plane. The goal is to share small bits of off-chain information and detect and +//! repair partitions. +//! +//! This CRDT only supports a very limited set of types. A map of Pubkey -> Versioned Struct. +//! The last version is always picked during an update. +//! +//! The network is arranged in layers: +//! +//! * layer 0 - Leader. +//! * layer 1 - As many nodes as we can fit +//! * layer 2 - Everyone else, if layer 1 is `2^10`, layer 2 should be able to fit `2^20` number of nodes. +//! +//! Bank needs to provide an interface for us to query the stake weight +use bincode::{deserialize, serialize}; +use bloom::Bloom; +use contact_info::ContactInfo; +use counter::Counter; +use crds_gossip::CrdsGossip; +use crds_gossip_pull::CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS; +use crds_value::{CrdsValue, CrdsValueLabel, LeaderId}; +use ledger::LedgerWindow; +use log::Level; +use netutil::{bind_in_range, bind_to, find_available_port_in_range, multi_bind_in_range}; +use packet::{to_blob, Blob, SharedBlob, BLOB_SIZE}; +use rand::{thread_rng, Rng}; +use rayon::prelude::*; +use result::Result; +use rpc::RPC_PORT; +use signature::{Keypair, KeypairUtil}; +use solana_sdk::hash::Hash; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::timing::{duration_as_ms, timestamp}; +use std::collections::HashMap; +use std::io; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::{Arc, RwLock}; +use std::thread::{sleep, Builder, JoinHandle}; +use std::time::{Duration, Instant}; +use streamer::{BlobReceiver, BlobSender}; +use window::{SharedWindow, WindowIndex}; + +pub type NodeInfo = ContactInfo; + +pub const FULLNODE_PORT_RANGE: (u16, u16) = (8000, 10_000); + +/// milliseconds we sleep for between gossip requests +const GOSSIP_SLEEP_MILLIS: u64 = 100; + +#[derive(Debug, PartialEq, Eq)] +pub enum ClusterInfoError { + NoPeers, + NoLeader, + BadContactInfo, + BadNodeInfo, + BadGossipAddress, +} + +pub struct ClusterInfo { + /// The network + pub gossip: CrdsGossip, +} + +// TODO These messages should be signed, and go through the gpu pipeline for spam filtering +#[derive(Serialize, Deserialize, Debug)] +#[cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))] +enum Protocol { + /// Gosisp protocol messages + PullRequest(Bloom, CrdsValue), + PullResponse(Pubkey, Vec), + PushMessage(Pubkey, Vec), + PruneMessage(Pubkey, Vec), + + /// Window protocol messages + /// TODO: move this message to a different module + RequestWindowIndex(NodeInfo, u64), +} + +impl ClusterInfo { + pub fn new(node_info: NodeInfo) -> Self { + let mut me = ClusterInfo { + gossip: CrdsGossip::default(), + }; + let id = node_info.id; + me.gossip.set_self(id); + me.insert_info(node_info); + me.push_self(); + me + } + pub fn push_self(&mut self) { + let mut my_data = self.my_data(); + let now = timestamp(); + my_data.wallclock = now; + let entry = CrdsValue::ContactInfo(my_data); + self.gossip.refresh_push_active_set(); + self.gossip.process_push_message(&[entry], now); + } + pub fn insert_info(&mut self, node_info: NodeInfo) { + let value = CrdsValue::ContactInfo(node_info); + let _ = self.gossip.crds.insert(value, timestamp()); + } + pub fn id(&self) -> Pubkey { + self.gossip.id + } + pub fn lookup(&self, id: Pubkey) -> Option<&NodeInfo> { + let entry = CrdsValueLabel::ContactInfo(id); + self.gossip + .crds + .lookup(&entry) + .and_then(|x| x.contact_info()) + } + pub fn my_data(&self) -> NodeInfo { + self.lookup(self.id()).cloned().unwrap() + } + pub fn leader_id(&self) -> Pubkey { + let entry = CrdsValueLabel::LeaderId(self.id()); + self.gossip + .crds + .lookup(&entry) + .and_then(|v| v.leader_id()) + .map(|x| x.leader_id) + .unwrap_or_default() + } + pub fn leader_data(&self) -> Option<&NodeInfo> { + let leader_id = self.leader_id(); + if leader_id == Pubkey::default() { + return None; + } + self.lookup(leader_id) + } + pub fn node_info_trace(&self) -> String { + let leader_id = self.leader_id(); + let nodes: Vec<_> = self + .rpc_peers() + .into_iter() + .map(|node| { + format!( + " ncp: {:20} | {}{}\n \ + tpu: {:20} |\n \ + rpc: {:20} |\n", + node.ncp.to_string(), + node.id, + if node.id == leader_id { + " <==== leader" + } else { + "" + }, + node.tpu.to_string(), + node.rpc.to_string() + ) + }).collect(); + + format!( + " NodeInfo.contact_info | Node identifier\n\ + ---------------------------+------------------\n\ + {}\n \ + Nodes: {}", + nodes.join(""), + nodes.len() + ) + } + + pub fn set_leader(&mut self, key: Pubkey) -> () { + let prev = self.leader_id(); + let self_id = self.gossip.id; + let now = timestamp(); + let leader = LeaderId { + id: self_id, + leader_id: key, + wallclock: now, + }; + let entry = CrdsValue::LeaderId(leader); + warn!("{}: LEADER_UPDATE TO {} from {}", self_id, key, prev); + self.gossip.process_push_message(&[entry], now); + } + + pub fn purge(&mut self, now: u64) { + self.gossip.purge(now); + } + pub fn convergence(&self) -> usize { + self.ncp_peers().len() + 1 + } + pub fn rpc_peers(&self) -> Vec { + let me = self.my_data().id; + self.gossip + .crds + .table + .values() + .filter_map(|x| x.value.contact_info()) + .filter(|x| x.id != me) + .filter(|x| ContactInfo::is_valid_address(&x.rpc)) + .cloned() + .collect() + } + + pub fn ncp_peers(&self) -> Vec { + let me = self.my_data().id; + self.gossip + .crds + .table + .values() + .filter_map(|x| x.value.contact_info()) + .filter(|x| x.id != me) + .filter(|x| ContactInfo::is_valid_address(&x.ncp)) + .cloned() + .collect() + } + + /// compute broadcast table + pub fn tvu_peers(&self) -> Vec { + let me = self.my_data().id; + self.gossip + .crds + .table + .values() + .filter_map(|x| x.value.contact_info()) + .filter(|x| x.id != me) + .filter(|x| ContactInfo::is_valid_address(&x.tvu)) + .cloned() + .collect() + } + + /// compute broadcast table + pub fn tpu_peers(&self) -> Vec { + let me = self.my_data().id; + self.gossip + .crds + .table + .values() + .filter_map(|x| x.value.contact_info()) + .filter(|x| x.id != me) + .filter(|x| ContactInfo::is_valid_address(&x.tpu)) + .cloned() + .collect() + } + + /// broadcast messages from the leader to layer 1 nodes + /// # Remarks + /// We need to avoid having obj locked while doing any io, such as the `send_to` + pub fn broadcast( + contains_last_tick: bool, + leader_id: Pubkey, + me: &NodeInfo, + broadcast_table: &[NodeInfo], + window: &SharedWindow, + s: &UdpSocket, + transmit_index: &mut WindowIndex, + received_index: u64, + ) -> Result<()> { + if broadcast_table.is_empty() { + debug!("{}:not enough peers in cluster_info table", me.id); + inc_new_counter_info!("cluster_info-broadcast-not_enough_peers_error", 1); + Err(ClusterInfoError::NoPeers)?; + } + trace!( + "{} transmit_index: {:?} received_index: {} broadcast_len: {}", + me.id, + *transmit_index, + received_index, + broadcast_table.len() + ); + + let old_transmit_index = transmit_index.data; + + let orders = Self::create_broadcast_orders( + contains_last_tick, + window, + broadcast_table, + transmit_index, + received_index, + me, + ); + trace!("broadcast orders table {}", orders.len()); + + let errs = Self::send_orders(s, orders, me, leader_id); + + for e in errs { + if let Err(e) = &e { + trace!("broadcast result {:?}", e); + } + e?; + if transmit_index.data < received_index { + transmit_index.data += 1; + } + } + inc_new_counter_info!( + "cluster_info-broadcast-max_idx", + (transmit_index.data - old_transmit_index) as usize + ); + transmit_index.coding = transmit_index.data; + + Ok(()) + } + + /// retransmit messages from the leader to layer 1 nodes + /// # Remarks + /// We need to avoid having obj locked while doing any io, such as the `send_to` + pub fn retransmit(obj: &Arc>, blob: &SharedBlob, s: &UdpSocket) -> Result<()> { + let (me, orders): (NodeInfo, Vec) = { + // copy to avoid locking during IO + let s = obj.read().expect("'obj' read lock in pub fn retransmit"); + (s.my_data().clone(), s.tvu_peers()) + }; + blob.write() + .unwrap() + .set_id(&me.id) + .expect("set_id in pub fn retransmit"); + let rblob = blob.read().unwrap(); + trace!("retransmit orders {}", orders.len()); + let errs: Vec<_> = orders + .par_iter() + .map(|v| { + debug!( + "{}: retransmit blob {} to {} {}", + me.id, + rblob.index().unwrap(), + v.id, + v.tvu, + ); + //TODO profile this, may need multiple sockets for par_iter + assert!(rblob.meta.size <= BLOB_SIZE); + s.send_to(&rblob.data[..rblob.meta.size], &v.tvu) + }).collect(); + for e in errs { + if let Err(e) = &e { + inc_new_counter_info!("cluster_info-retransmit-send_to_error", 1, 1); + error!("retransmit result {:?}", e); + } + e?; + } + Ok(()) + } + + fn send_orders( + s: &UdpSocket, + orders: Vec<(Option, Vec<&NodeInfo>)>, + me: &NodeInfo, + leader_id: Pubkey, + ) -> Vec> { + orders + .into_iter() + .flat_map(|(b, vs)| { + // only leader should be broadcasting + assert!(vs.iter().find(|info| info.id == leader_id).is_none()); + let bl = b.unwrap(); + let blob = bl.read().unwrap(); + //TODO profile this, may need multiple sockets for par_iter + let ids_and_tvus = if log_enabled!(Level::Trace) { + let v_ids = vs.iter().map(|v| v.id); + let tvus = vs.iter().map(|v| v.tvu); + let ids_and_tvus = v_ids.zip(tvus).collect(); + + trace!( + "{}: BROADCAST idx: {} sz: {} to {:?} coding: {}", + me.id, + blob.index().unwrap(), + blob.meta.size, + ids_and_tvus, + blob.is_coding() + ); + + ids_and_tvus + } else { + vec![] + }; + + assert!(blob.meta.size <= BLOB_SIZE); + let send_errs_for_blob: Vec<_> = vs + .iter() + .map(move |v| { + let e = s.send_to(&blob.data[..blob.meta.size], &v.tvu); + trace!( + "{}: done broadcast {} to {:?}", + me.id, + blob.meta.size, + ids_and_tvus + ); + e + }).collect(); + send_errs_for_blob + }).collect() + } + + fn create_broadcast_orders<'a>( + contains_last_tick: bool, + window: &SharedWindow, + broadcast_table: &'a [NodeInfo], + transmit_index: &mut WindowIndex, + received_index: u64, + me: &NodeInfo, + ) -> Vec<(Option, Vec<&'a NodeInfo>)> { + // enumerate all the blobs in the window, those are the indices + // transmit them to nodes, starting from a different node. + let mut orders = Vec::with_capacity((received_index - transmit_index.data) as usize); + let window_l = window.read().unwrap(); + let mut br_idx = transmit_index.data as usize % broadcast_table.len(); + + for idx in transmit_index.data..received_index { + let w_idx = idx as usize % window_l.len(); + + trace!( + "{} broadcast order data w_idx {} br_idx {}", + me.id, + w_idx, + br_idx + ); + + // Broadcast the last tick to everyone on the network so it doesn't get dropped + // (Need to maximize probability the next leader in line sees this handoff tick + // despite packet drops) + let target = if idx == received_index - 1 && contains_last_tick { + // If we see a tick at max_tick_height, then we know it must be the last + // Blob in the window, at index == received_index. There cannot be an entry + // that got sent after the last tick, guaranteed by the PohService). + assert!(window_l[w_idx].data.is_some()); + ( + window_l[w_idx].data.clone(), + broadcast_table.iter().collect(), + ) + } else { + (window_l[w_idx].data.clone(), vec![&broadcast_table[br_idx]]) + }; + + orders.push(target); + br_idx += 1; + br_idx %= broadcast_table.len(); + } + + for idx in transmit_index.coding..received_index { + let w_idx = idx as usize % window_l.len(); + + // skip over empty slots + if window_l[w_idx].coding.is_none() { + continue; + } + + trace!( + "{} broadcast order coding w_idx: {} br_idx :{}", + me.id, + w_idx, + br_idx, + ); + + orders.push(( + window_l[w_idx].coding.clone(), + vec![&broadcast_table[br_idx]], + )); + br_idx += 1; + br_idx %= broadcast_table.len(); + } + + orders + } + + pub fn window_index_request(&self, ix: u64) -> Result<(SocketAddr, Vec)> { + // find a peer that appears to be accepting replication, as indicated + // by a valid tvu port location + let valid: Vec<_> = self.tvu_peers(); + if valid.is_empty() { + Err(ClusterInfoError::NoPeers)?; + } + let n = thread_rng().gen::() % valid.len(); + let addr = valid[n].ncp; // send the request to the peer's gossip port + let req = Protocol::RequestWindowIndex(self.my_data().clone(), ix); + let out = serialize(&req)?; + Ok((addr, out)) + } + fn new_pull_requests(&mut self) -> Vec<(SocketAddr, Protocol)> { + let now = timestamp(); + let pulls: Vec<_> = self.gossip.new_pull_request(now).ok().into_iter().collect(); + + let pr: Vec<_> = pulls + .into_iter() + .filter_map(|(peer, filter, self_info)| { + let peer_label = CrdsValueLabel::ContactInfo(peer); + self.gossip + .crds + .lookup(&peer_label) + .and_then(|v| v.contact_info()) + .map(|peer_info| (peer, filter, peer_info.ncp, self_info)) + }).collect(); + pr.into_iter() + .map(|(peer, filter, ncp, self_info)| { + self.gossip.mark_pull_request_creation_time(peer, now); + (ncp, Protocol::PullRequest(filter, self_info)) + }).collect() + } + fn new_push_requests(&mut self) -> Vec<(SocketAddr, Protocol)> { + let self_id = self.gossip.id; + let (_, peers, msgs) = self.gossip.new_push_messages(timestamp()); + peers + .into_iter() + .filter_map(|p| { + let peer_label = CrdsValueLabel::ContactInfo(p); + self.gossip + .crds + .lookup(&peer_label) + .and_then(|v| v.contact_info()) + .map(|p| p.ncp) + }).map(|peer| (peer, Protocol::PushMessage(self_id, msgs.clone()))) + .collect() + } + + fn gossip_request(&mut self) -> Vec<(SocketAddr, Protocol)> { + let pulls: Vec<_> = self.new_pull_requests(); + let pushes: Vec<_> = self.new_push_requests(); + vec![pulls, pushes].into_iter().flat_map(|x| x).collect() + } + + /// At random pick a node and try to get updated changes from them + fn run_gossip(obj: &Arc>, blob_sender: &BlobSender) -> Result<()> { + let reqs = obj.write().unwrap().gossip_request(); + let blobs = reqs + .into_iter() + .filter_map(|(remote_gossip_addr, req)| to_blob(req, remote_gossip_addr).ok()) + .collect(); + blob_sender.send(blobs)?; + Ok(()) + } + + pub fn get_gossip_top_leader(&self) -> Option<&NodeInfo> { + let mut table = HashMap::new(); + let def = Pubkey::default(); + let cur = self + .gossip + .crds + .table + .values() + .filter_map(|x| x.value.leader_id()) + .filter(|x| x.leader_id != def); + for v in cur { + let cnt = table.entry(&v.leader_id).or_insert(0); + *cnt += 1; + trace!("leader {} {}", v.leader_id, *cnt); + } + let mut sorted: Vec<(&Pubkey, usize)> = table.into_iter().collect(); + for x in &sorted { + trace!("{}: sorted leaders {} votes: {}", self.gossip.id, x.0, x.1); + } + sorted.sort_by_key(|a| a.1); + let top_leader = sorted.last().map(|a| *a.0); + + top_leader + .and_then(|x| { + let leader_label = CrdsValueLabel::ContactInfo(x); + self.gossip.crds.lookup(&leader_label) + }).and_then(|x| x.contact_info()) + } + + /// randomly pick a node and ask them for updates asynchronously + pub fn gossip( + obj: Arc>, + blob_sender: BlobSender, + exit: Arc, + ) -> JoinHandle<()> { + Builder::new() + .name("solana-gossip".to_string()) + .spawn(move || { + let mut last_push = timestamp(); + loop { + let start = timestamp(); + let _ = Self::run_gossip(&obj, &blob_sender); + if exit.load(Ordering::Relaxed) { + return; + } + obj.write().unwrap().purge(timestamp()); + //TODO: possibly tune this parameter + //we saw a deadlock passing an obj.read().unwrap().timeout into sleep + if start - last_push > CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS / 2 { + obj.write().unwrap().push_self(); + last_push = timestamp(); + } + let elapsed = timestamp() - start; + if GOSSIP_SLEEP_MILLIS > elapsed { + let time_left = GOSSIP_SLEEP_MILLIS - elapsed; + sleep(Duration::from_millis(time_left)); + } + } + }).unwrap() + } + fn run_window_request( + from: &NodeInfo, + from_addr: &SocketAddr, + window: &SharedWindow, + ledger_window: &mut Option<&mut LedgerWindow>, + me: &NodeInfo, + leader_id: Pubkey, + ix: u64, + ) -> Vec { + let pos = (ix as usize) % window.read().unwrap().len(); + if let Some(ref mut blob) = &mut window.write().unwrap()[pos].data { + let mut wblob = blob.write().unwrap(); + let blob_ix = wblob.index().expect("run_window_request index"); + if blob_ix == ix { + let num_retransmits = wblob.meta.num_retransmits; + wblob.meta.num_retransmits += 1; + // Setting the sender id to the requester id + // prevents the requester from retransmitting this response + // to other peers + let mut sender_id = from.id; + + // Allow retransmission of this response if the node + // is the leader and the number of repair requests equals + // a power of two + if leader_id == me.id && (num_retransmits == 0 || num_retransmits.is_power_of_two()) + { + sender_id = me.id + } + + let out = SharedBlob::default(); + + // copy to avoid doing IO inside the lock + { + let mut outblob = out.write().unwrap(); + let sz = wblob.meta.size; + outblob.meta.size = sz; + outblob.data[..sz].copy_from_slice(&wblob.data[..sz]); + outblob.meta.set_addr(from_addr); + outblob.set_id(&sender_id).expect("blob set_id"); + } + inc_new_counter_info!("cluster_info-window-request-pass", 1); + + return vec![out]; + } else { + inc_new_counter_info!("cluster_info-window-request-outside", 1); + trace!( + "requested ix {} != blob_ix {}, outside window!", + ix, + blob_ix + ); + // falls through to checking window_ledger + } + } + + if let Some(ledger_window) = ledger_window { + if let Ok(entry) = ledger_window.get_entry(ix) { + inc_new_counter_info!("cluster_info-window-request-ledger", 1); + + let out = entry.to_blob( + Some(ix), + Some(me.id), // causes retransmission if I'm the leader + Some(from_addr), + ); + + return vec![out]; + } + } + + inc_new_counter_info!("cluster_info-window-request-fail", 1); + trace!( + "{}: failed RequestWindowIndex {} {} {}", + me.id, + from.id, + ix, + pos, + ); + + vec![] + } + + //TODO we should first coalesce all the requests + fn handle_blob( + obj: &Arc>, + window: &SharedWindow, + ledger_window: &mut Option<&mut LedgerWindow>, + blob: &Blob, + ) -> Vec { + deserialize(&blob.data[..blob.meta.size]) + .into_iter() + .flat_map(|request| { + ClusterInfo::handle_protocol(obj, &blob.meta.addr(), request, window, ledger_window) + }).collect() + } + fn handle_pull_request( + me: &Arc>, + filter: Bloom, + caller: CrdsValue, + from_addr: &SocketAddr, + ) -> Vec { + let self_id = me.read().unwrap().gossip.id; + inc_new_counter_info!("cluster_info-pull_request", 1); + if caller.contact_info().is_none() { + return vec![]; + } + let mut from = caller.contact_info().cloned().unwrap(); + if from.id == self_id { + warn!( + "PullRequest ignored, I'm talking to myself: me={} remoteme={}", + self_id, from.id + ); + inc_new_counter_info!("cluster_info-window-request-loopback", 1); + return vec![]; + } + let now = timestamp(); + let data = me + .write() + .unwrap() + .gossip + .process_pull_request(caller, filter, now); + let len = data.len(); + trace!("get updates since response {}", len); + if data.is_empty() { + trace!("no updates me {}", self_id); + vec![] + } else { + let rsp = Protocol::PullResponse(self_id, data); + // the remote side may not know his public IP:PORT, record what he looks like to us + // this may or may not be correct for everybody but it's better than leaving him with + // an unspecified address in our table + if from.ncp.ip().is_unspecified() { + inc_new_counter_info!("cluster_info-window-request-updates-unspec-ncp", 1); + from.ncp = *from_addr; + } + inc_new_counter_info!("cluster_info-pull_request-rsp", len); + to_blob(rsp, from.ncp).ok().into_iter().collect() + } + } + fn handle_pull_response(me: &Arc>, from: Pubkey, data: Vec) { + let len = data.len(); + let now = Instant::now(); + let self_id = me.read().unwrap().gossip.id; + trace!("PullResponse me: {} len={}", self_id, len); + me.write() + .unwrap() + .gossip + .process_pull_response(from, data, timestamp()); + inc_new_counter_info!("cluster_info-pull_request_response", 1); + inc_new_counter_info!("cluster_info-pull_request_response-size", len); + + report_time_spent("ReceiveUpdates", &now.elapsed(), &format!(" len: {}", len)); + } + fn handle_push_message( + me: &Arc>, + from: Pubkey, + data: &[CrdsValue], + ) -> Vec { + let self_id = me.read().unwrap().gossip.id; + inc_new_counter_info!("cluster_info-push_message", 1); + let prunes: Vec<_> = me + .write() + .unwrap() + .gossip + .process_push_message(&data, timestamp()); + if !prunes.is_empty() { + let mut wme = me.write().unwrap(); + inc_new_counter_info!("cluster_info-push_message-prunes", prunes.len()); + let rsp = Protocol::PruneMessage(self_id, prunes); + let ci = wme.lookup(from).cloned(); + let pushes: Vec<_> = wme.new_push_requests(); + inc_new_counter_info!("cluster_info-push_message-pushes", pushes.len()); + let mut rsp: Vec<_> = ci + .and_then(|ci| to_blob(rsp, ci.ncp).ok()) + .into_iter() + .collect(); + let mut blobs: Vec<_> = pushes + .into_iter() + .filter_map(|(remote_gossip_addr, req)| to_blob(req, remote_gossip_addr).ok()) + .collect(); + rsp.append(&mut blobs); + rsp + } else { + vec![] + } + } + fn handle_request_window_index( + me: &Arc>, + from: &ContactInfo, + ix: u64, + from_addr: &SocketAddr, + window: &SharedWindow, + ledger_window: &mut Option<&mut LedgerWindow>, + ) -> Vec { + let now = Instant::now(); + + //TODO this doesn't depend on cluster_info module, could be moved + //but we are using the listen thread to service these request + //TODO verify from is signed + + let self_id = me.read().unwrap().gossip.id; + if from.id == me.read().unwrap().gossip.id { + warn!( + "{}: Ignored received RequestWindowIndex from ME {} {} ", + self_id, from.id, ix, + ); + inc_new_counter_info!("cluster_info-window-request-address-eq", 1); + return vec![]; + } + + me.write().unwrap().insert_info(from.clone()); + let leader_id = me.read().unwrap().leader_id(); + let my_info = me.read().unwrap().my_data().clone(); + inc_new_counter_info!("cluster_info-window-request-recv", 1); + trace!( + "{}: received RequestWindowIndex {} {} ", + self_id, + from.id, + ix, + ); + let res = Self::run_window_request( + &from, + &from_addr, + &window, + ledger_window, + &my_info, + leader_id, + ix, + ); + report_time_spent( + "RequestWindowIndex", + &now.elapsed(), + &format!(" ix: {}", ix), + ); + res + } + fn handle_protocol( + me: &Arc>, + from_addr: &SocketAddr, + request: Protocol, + window: &SharedWindow, + ledger_window: &mut Option<&mut LedgerWindow>, + ) -> Vec { + match request { + // TODO sigverify these + Protocol::PullRequest(filter, caller) => { + Self::handle_pull_request(me, filter, caller, from_addr) + } + Protocol::PullResponse(from, data) => { + Self::handle_pull_response(me, from, data); + vec![] + } + Protocol::PushMessage(from, data) => Self::handle_push_message(me, from, &data), + Protocol::PruneMessage(from, data) => { + inc_new_counter_info!("cluster_info-prune_message", 1); + inc_new_counter_info!("cluster_info-prune_message-size", data.len()); + me.write().unwrap().gossip.process_prune_msg(from, &data); + vec![] + } + Protocol::RequestWindowIndex(from, ix) => { + Self::handle_request_window_index(me, &from, ix, from_addr, window, ledger_window) + } + } + } + + /// Process messages from the network + fn run_listen( + obj: &Arc>, + window: &SharedWindow, + ledger_window: &mut Option<&mut LedgerWindow>, + requests_receiver: &BlobReceiver, + response_sender: &BlobSender, + ) -> Result<()> { + //TODO cache connections + let timeout = Duration::new(1, 0); + let mut reqs = requests_receiver.recv_timeout(timeout)?; + while let Ok(mut more) = requests_receiver.try_recv() { + reqs.append(&mut more); + } + let mut resps = Vec::new(); + for req in reqs { + let mut resp = Self::handle_blob(obj, window, ledger_window, &req.read().unwrap()); + resps.append(&mut resp); + } + response_sender.send(resps)?; + Ok(()) + } + pub fn listen( + me: Arc>, + window: SharedWindow, + ledger_path: Option<&str>, + requests_receiver: BlobReceiver, + response_sender: BlobSender, + exit: Arc, + ) -> JoinHandle<()> { + let mut ledger_window = ledger_path.map(|p| LedgerWindow::open(p).unwrap()); + + Builder::new() + .name("solana-listen".to_string()) + .spawn(move || loop { + let e = Self::run_listen( + &me, + &window, + &mut ledger_window.as_mut(), + &requests_receiver, + &response_sender, + ); + if exit.load(Ordering::Relaxed) { + return; + } + if e.is_err() { + let me = me.read().unwrap(); + debug!( + "{}: run_listen timeout, table size: {}", + me.gossip.id, + me.gossip.crds.table.len() + ); + } + }).unwrap() + } + + pub fn spy_node() -> (NodeInfo, UdpSocket) { + let (_, gossip_socket) = bind_in_range(FULLNODE_PORT_RANGE).unwrap(); + let pubkey = Keypair::new().pubkey(); + let daddr = socketaddr_any!(); + + let node = NodeInfo::new( + pubkey, + daddr, + daddr, + daddr, + daddr, + daddr, + daddr, + timestamp(), + ); + (node, gossip_socket) + } +} + +#[derive(Debug)] +pub struct Sockets { + pub gossip: UdpSocket, + pub replicate: Vec, + pub transaction: Vec, + pub broadcast: UdpSocket, + pub repair: UdpSocket, + pub retransmit: UdpSocket, +} + +#[derive(Debug)] +pub struct Node { + pub info: NodeInfo, + pub sockets: Sockets, +} + +impl Node { + pub fn new_localhost() -> Self { + let pubkey = Keypair::new().pubkey(); + Self::new_localhost_with_pubkey(pubkey) + } + pub fn new_localhost_with_pubkey(pubkey: Pubkey) -> Self { + let transaction = UdpSocket::bind("127.0.0.1:0").unwrap(); + let gossip = UdpSocket::bind("127.0.0.1:0").unwrap(); + let replicate = UdpSocket::bind("127.0.0.1:0").unwrap(); + let repair = UdpSocket::bind("127.0.0.1:0").unwrap(); + let rpc_port = find_available_port_in_range((1024, 65535)).unwrap(); + let rpc_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), rpc_port); + let rpc_pubsub_port = find_available_port_in_range((1024, 65535)).unwrap(); + let rpc_pubsub_addr = + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), rpc_pubsub_port); + + let broadcast = UdpSocket::bind("0.0.0.0:0").unwrap(); + let retransmit = UdpSocket::bind("0.0.0.0:0").unwrap(); + let storage = UdpSocket::bind("0.0.0.0:0").unwrap(); + let info = NodeInfo::new( + pubkey, + gossip.local_addr().unwrap(), + replicate.local_addr().unwrap(), + transaction.local_addr().unwrap(), + storage.local_addr().unwrap(), + rpc_addr, + rpc_pubsub_addr, + timestamp(), + ); + Node { + info, + sockets: Sockets { + gossip, + replicate: vec![replicate], + transaction: vec![transaction], + broadcast, + repair, + retransmit, + }, + } + } + pub fn new_with_external_ip(pubkey: Pubkey, ncp: &SocketAddr) -> Node { + fn bind() -> (u16, UdpSocket) { + bind_in_range(FULLNODE_PORT_RANGE).expect("Failed to bind") + }; + + let (gossip_port, gossip) = if ncp.port() != 0 { + (ncp.port(), bind_to(ncp.port(), false).expect("ncp bind")) + } else { + bind() + }; + + let (replicate_port, replicate_sockets) = + multi_bind_in_range(FULLNODE_PORT_RANGE, 8).expect("tvu multi_bind"); + + let (transaction_port, transaction_sockets) = + multi_bind_in_range(FULLNODE_PORT_RANGE, 32).expect("tpu multi_bind"); + + let (_, repair) = bind(); + let (_, broadcast) = bind(); + let (_, retransmit) = bind(); + let (storage_port, _) = bind(); + + let info = NodeInfo::new( + pubkey, + SocketAddr::new(ncp.ip(), gossip_port), + SocketAddr::new(ncp.ip(), replicate_port), + SocketAddr::new(ncp.ip(), transaction_port), + SocketAddr::new(ncp.ip(), storage_port), + SocketAddr::new(ncp.ip(), RPC_PORT), + SocketAddr::new(ncp.ip(), RPC_PORT + 1), + 0, + ); + trace!("new NodeInfo: {:?}", info); + + Node { + info, + sockets: Sockets { + gossip, + replicate: replicate_sockets, + transaction: transaction_sockets, + broadcast, + repair, + retransmit, + }, + } + } +} + +fn report_time_spent(label: &str, time: &Duration, extra: &str) { + let count = duration_as_ms(time); + if count > 5 { + info!("{} took: {} ms {}", label, count, extra); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crds_value::CrdsValueLabel; + use entry::Entry; + use ledger::{get_tmp_ledger_path, LedgerWindow, LedgerWriter}; + use logger; + use packet::SharedBlob; + use result::Error; + use signature::{Keypair, KeypairUtil}; + use solana_sdk::hash::{hash, Hash}; + use std::fs::remove_dir_all; + use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use std::sync::{Arc, RwLock}; + use window::default_window; + + #[test] + fn test_cluster_spy_gossip() { + //check that gossip doesn't try to push to invalid addresses + let node = Node::new_localhost(); + let (spy, _) = ClusterInfo::spy_node(); + let cluster_info = Arc::new(RwLock::new(ClusterInfo::new(node.info))); + cluster_info.write().unwrap().insert_info(spy); + cluster_info + .write() + .unwrap() + .gossip + .refresh_push_active_set(); + let reqs = cluster_info.write().unwrap().gossip_request(); + //assert none of the addrs are invalid. + reqs.iter().all(|(addr, _)| { + let res = ContactInfo::is_valid_address(addr); + assert!(res); + res + }); + } + + #[test] + fn test_cluster_info_new() { + let d = NodeInfo::new_localhost(Keypair::new().pubkey(), timestamp()); + let cluster_info = ClusterInfo::new(d.clone()); + assert_eq!(d.id, cluster_info.my_data().id); + } + + #[test] + fn insert_info_test() { + let d = NodeInfo::new_localhost(Keypair::new().pubkey(), timestamp()); + let mut cluster_info = ClusterInfo::new(d); + let d = NodeInfo::new_localhost(Keypair::new().pubkey(), timestamp()); + let label = CrdsValueLabel::ContactInfo(d.id); + cluster_info.insert_info(d); + assert!(cluster_info.gossip.crds.lookup(&label).is_some()); + } + #[test] + fn window_index_request() { + let me = NodeInfo::new_localhost(Keypair::new().pubkey(), timestamp()); + let mut cluster_info = ClusterInfo::new(me); + let rv = cluster_info.window_index_request(0); + assert_matches!(rv, Err(Error::ClusterInfoError(ClusterInfoError::NoPeers))); + + let ncp = socketaddr!([127, 0, 0, 1], 1234); + let nxt = NodeInfo::new( + Keypair::new().pubkey(), + ncp, + socketaddr!([127, 0, 0, 1], 1235), + socketaddr!([127, 0, 0, 1], 1236), + socketaddr!([127, 0, 0, 1], 1237), + socketaddr!([127, 0, 0, 1], 1238), + socketaddr!([127, 0, 0, 1], 1239), + 0, + ); + cluster_info.insert_info(nxt.clone()); + let rv = cluster_info.window_index_request(0).unwrap(); + assert_eq!(nxt.ncp, ncp); + assert_eq!(rv.0, nxt.ncp); + + let ncp2 = socketaddr!([127, 0, 0, 2], 1234); + let nxt = NodeInfo::new( + Keypair::new().pubkey(), + ncp2, + socketaddr!([127, 0, 0, 1], 1235), + socketaddr!([127, 0, 0, 1], 1236), + socketaddr!([127, 0, 0, 1], 1237), + socketaddr!([127, 0, 0, 1], 1238), + socketaddr!([127, 0, 0, 1], 1239), + 0, + ); + cluster_info.insert_info(nxt); + let mut one = false; + let mut two = false; + while !one || !two { + //this randomly picks an option, so eventually it should pick both + let rv = cluster_info.window_index_request(0).unwrap(); + if rv.0 == ncp { + one = true; + } + if rv.0 == ncp2 { + two = true; + } + } + assert!(one && two); + } + + /// test window requests respond with the right blob, and do not overrun + #[test] + fn run_window_request() { + logger::setup(); + let window = Arc::new(RwLock::new(default_window())); + let me = NodeInfo::new( + Keypair::new().pubkey(), + socketaddr!("127.0.0.1:1234"), + socketaddr!("127.0.0.1:1235"), + socketaddr!("127.0.0.1:1236"), + socketaddr!("127.0.0.1:1237"), + socketaddr!("127.0.0.1:1238"), + socketaddr!("127.0.0.1:1239"), + 0, + ); + let leader_id = me.id; + let rv = ClusterInfo::run_window_request( + &me, + &socketaddr_any!(), + &window, + &mut None, + &me, + leader_id, + 0, + ); + assert!(rv.is_empty()); + let out = SharedBlob::default(); + out.write().unwrap().meta.size = 200; + window.write().unwrap()[0].data = Some(out); + let rv = ClusterInfo::run_window_request( + &me, + &socketaddr_any!(), + &window, + &mut None, + &me, + leader_id, + 0, + ); + assert!(!rv.is_empty()); + let v = rv[0].clone(); + //test we copied the blob + assert_eq!(v.read().unwrap().meta.size, 200); + let len = window.read().unwrap().len() as u64; + let rv = ClusterInfo::run_window_request( + &me, + &socketaddr_any!(), + &window, + &mut None, + &me, + leader_id, + len, + ); + assert!(rv.is_empty()); + + fn tmp_ledger(name: &str) -> String { + let path = get_tmp_ledger_path(name); + + let mut writer = LedgerWriter::open(&path, true).unwrap(); + let zero = Hash::default(); + let one = hash(&zero.as_ref()); + writer + .write_entries( + &vec![ + Entry::new_tick(&zero, 0, &zero), + Entry::new_tick(&one, 0, &one), + ].to_vec(), + ).unwrap(); + path + } + + let ledger_path = tmp_ledger("run_window_request"); + let mut ledger_window = LedgerWindow::open(&ledger_path).unwrap(); + + let rv = ClusterInfo::run_window_request( + &me, + &socketaddr_any!(), + &window, + &mut Some(&mut ledger_window), + &me, + leader_id, + 1, + ); + assert!(!rv.is_empty()); + + remove_dir_all(ledger_path).unwrap(); + } + + /// test window requests respond with the right blob, and do not overrun + #[test] + fn run_window_request_with_backoff() { + let window = Arc::new(RwLock::new(default_window())); + + let me = NodeInfo::new_with_socketaddr(&socketaddr!("127.0.0.1:1234")); + let leader_id = me.id; + + let mock_peer = NodeInfo::new_with_socketaddr(&socketaddr!("127.0.0.1:1234")); + + // Simulate handling a repair request from mock_peer + let rv = ClusterInfo::run_window_request( + &mock_peer, + &socketaddr_any!(), + &window, + &mut None, + &me, + leader_id, + 0, + ); + assert!(rv.is_empty()); + let blob = SharedBlob::default(); + let blob_size = 200; + blob.write().unwrap().meta.size = blob_size; + window.write().unwrap()[0].data = Some(blob); + + let num_requests: u32 = 64; + for i in 0..num_requests { + let shared_blob = ClusterInfo::run_window_request( + &mock_peer, + &socketaddr_any!(), + &window, + &mut None, + &me, + leader_id, + 0, + )[0].clone(); + let blob = shared_blob.read().unwrap(); + // Test we copied the blob + assert_eq!(blob.meta.size, blob_size); + + let id = if i == 0 || i.is_power_of_two() { + me.id + } else { + mock_peer.id + }; + assert_eq!(blob.id().unwrap(), id); + } + } + + #[test] + fn test_default_leader() { + logger::setup(); + let node_info = NodeInfo::new_localhost(Keypair::new().pubkey(), 0); + let mut cluster_info = ClusterInfo::new(node_info); + let network_entry_point = NodeInfo::new_entry_point(&socketaddr!("127.0.0.1:1239")); + cluster_info.insert_info(network_entry_point); + assert!(cluster_info.leader_data().is_none()); + } + + #[test] + fn new_with_external_ip_test_random() { + let ip = Ipv4Addr::from(0); + let node = Node::new_with_external_ip(Keypair::new().pubkey(), &socketaddr!(ip, 0)); + assert_eq!(node.sockets.gossip.local_addr().unwrap().ip(), ip); + assert!(node.sockets.replicate.len() > 1); + for tx_socket in node.sockets.replicate.iter() { + assert_eq!(tx_socket.local_addr().unwrap().ip(), ip); + } + assert!(node.sockets.transaction.len() > 1); + for tx_socket in node.sockets.transaction.iter() { + assert_eq!(tx_socket.local_addr().unwrap().ip(), ip); + } + assert_eq!(node.sockets.repair.local_addr().unwrap().ip(), ip); + + assert!(node.sockets.gossip.local_addr().unwrap().port() >= FULLNODE_PORT_RANGE.0); + assert!(node.sockets.gossip.local_addr().unwrap().port() < FULLNODE_PORT_RANGE.1); + let tx_port = node.sockets.replicate[0].local_addr().unwrap().port(); + assert!(tx_port >= FULLNODE_PORT_RANGE.0); + assert!(tx_port < FULLNODE_PORT_RANGE.1); + for tx_socket in node.sockets.replicate.iter() { + assert_eq!(tx_socket.local_addr().unwrap().port(), tx_port); + } + let tx_port = node.sockets.transaction[0].local_addr().unwrap().port(); + assert!(tx_port >= FULLNODE_PORT_RANGE.0); + assert!(tx_port < FULLNODE_PORT_RANGE.1); + for tx_socket in node.sockets.transaction.iter() { + assert_eq!(tx_socket.local_addr().unwrap().port(), tx_port); + } + assert!(node.sockets.repair.local_addr().unwrap().port() >= FULLNODE_PORT_RANGE.0); + assert!(node.sockets.repair.local_addr().unwrap().port() < FULLNODE_PORT_RANGE.1); + } + + #[test] + fn new_with_external_ip_test_gossip() { + let ip = IpAddr::V4(Ipv4Addr::from(0)); + let node = Node::new_with_external_ip(Keypair::new().pubkey(), &socketaddr!(0, 8050)); + assert_eq!(node.sockets.gossip.local_addr().unwrap().ip(), ip); + assert!(node.sockets.replicate.len() > 1); + for tx_socket in node.sockets.replicate.iter() { + assert_eq!(tx_socket.local_addr().unwrap().ip(), ip); + } + assert!(node.sockets.transaction.len() > 1); + for tx_socket in node.sockets.transaction.iter() { + assert_eq!(tx_socket.local_addr().unwrap().ip(), ip); + } + assert_eq!(node.sockets.repair.local_addr().unwrap().ip(), ip); + + assert_eq!(node.sockets.gossip.local_addr().unwrap().port(), 8050); + let tx_port = node.sockets.replicate[0].local_addr().unwrap().port(); + assert!(tx_port >= FULLNODE_PORT_RANGE.0); + assert!(tx_port < FULLNODE_PORT_RANGE.1); + for tx_socket in node.sockets.replicate.iter() { + assert_eq!(tx_socket.local_addr().unwrap().port(), tx_port); + } + let tx_port = node.sockets.transaction[0].local_addr().unwrap().port(); + assert!(tx_port >= FULLNODE_PORT_RANGE.0); + assert!(tx_port < FULLNODE_PORT_RANGE.1); + for tx_socket in node.sockets.transaction.iter() { + assert_eq!(tx_socket.local_addr().unwrap().port(), tx_port); + } + assert!(node.sockets.repair.local_addr().unwrap().port() >= FULLNODE_PORT_RANGE.0); + assert!(node.sockets.repair.local_addr().unwrap().port() < FULLNODE_PORT_RANGE.1); + } +} diff --git a/book/compute_leader_finality_service.rs b/book/compute_leader_finality_service.rs new file mode 100644 index 00000000000000..0ab54e76e34abc --- /dev/null +++ b/book/compute_leader_finality_service.rs @@ -0,0 +1,205 @@ +//! The `compute_leader_finality_service` module implements the tools necessary +//! to generate a thread which regularly calculates the last finality times +//! observed by the leader + +use bank::Bank; + +use service::Service; +use solana_metrics::{influxdb, submit}; +use solana_sdk::timing; +use std::result; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread::sleep; +use std::thread::{self, Builder, JoinHandle}; +use std::time::Duration; +use vote_program::VoteProgram; + +#[derive(Debug, PartialEq, Eq)] +pub enum FinalityError { + NoValidSupermajority, +} + +pub const COMPUTE_FINALITY_MS: u64 = 1000; + +pub struct ComputeLeaderFinalityService { + compute_finality_thread: JoinHandle<()>, +} + +impl ComputeLeaderFinalityService { + fn get_last_supermajority_timestamp( + bank: &Arc, + now: u64, + last_valid_validator_timestamp: u64, + ) -> result::Result { + let mut total_stake = 0; + + let mut ticks_and_stakes: Vec<(u64, u64)> = { + let bank_accounts = bank.accounts.read().unwrap(); + // TODO: Doesn't account for duplicates since a single validator could potentially register + // multiple vote accounts. Once that is no longer possible (see the TODO in vote_program.rs, + // process_transaction(), case VoteInstruction::RegisterAccount), this will be more accurate. + // See github issue 1654. + bank_accounts + .accounts + .values() + .filter_map(|account| { + // Filter out any accounts that don't belong to the VoteProgram + // by returning None + if VoteProgram::check_id(&account.owner) { + if let Ok(vote_state) = VoteProgram::deserialize(&account.userdata) { + let validator_stake = bank.get_stake(&vote_state.node_id); + total_stake += validator_stake; + // Filter out any validators that don't have at least one vote + // by returning None + return vote_state + .votes + .back() + .map(|vote| (vote.tick_height, validator_stake)); + } + } + + None + }).collect() + }; + + let super_majority_stake = (2 * total_stake) / 3; + + if let Some(last_valid_validator_timestamp) = + bank.get_finality_timestamp(&mut ticks_and_stakes, super_majority_stake) + { + return Ok(last_valid_validator_timestamp); + } + + if last_valid_validator_timestamp != 0 { + submit( + influxdb::Point::new(&"leader-finality") + .add_field( + "duration_ms", + influxdb::Value::Integer((now - last_valid_validator_timestamp) as i64), + ).to_owned(), + ); + } + + Err(FinalityError::NoValidSupermajority) + } + + pub fn compute_finality(bank: &Arc, last_valid_validator_timestamp: &mut u64) { + let now = timing::timestamp(); + if let Ok(super_majority_timestamp) = + Self::get_last_supermajority_timestamp(bank, now, *last_valid_validator_timestamp) + { + let finality_ms = now - super_majority_timestamp; + + *last_valid_validator_timestamp = super_majority_timestamp; + bank.set_finality((now - *last_valid_validator_timestamp) as usize); + + submit( + influxdb::Point::new(&"leader-finality") + .add_field("duration_ms", influxdb::Value::Integer(finality_ms as i64)) + .to_owned(), + ); + } + } + + /// Create a new ComputeLeaderFinalityService for computing finality. + pub fn new(bank: Arc, exit: Arc) -> Self { + let compute_finality_thread = Builder::new() + .name("solana-leader-finality-stage".to_string()) + .spawn(move || { + let mut last_valid_validator_timestamp = 0; + loop { + if exit.load(Ordering::Relaxed) { + break; + } + Self::compute_finality(&bank, &mut last_valid_validator_timestamp); + sleep(Duration::from_millis(COMPUTE_FINALITY_MS)); + } + }).unwrap(); + + (ComputeLeaderFinalityService { + compute_finality_thread, + }) + } +} + +impl Service for ComputeLeaderFinalityService { + type JoinReturnType = (); + + fn join(self) -> thread::Result<()> { + self.compute_finality_thread.join() + } +} + +#[cfg(test)] +pub mod tests { + use bank::Bank; + use bincode::serialize; + use compute_leader_finality_service::ComputeLeaderFinalityService; + use logger; + use mint::Mint; + use signature::{Keypair, KeypairUtil}; + use solana_sdk::hash::hash; + use std::sync::Arc; + use std::thread::sleep; + use std::time::Duration; + use transaction::Transaction; + use vote_program::Vote; + use vote_transaction::{create_vote_account, VoteTransaction}; + + #[test] + fn test_compute_finality() { + logger::setup(); + + let mint = Mint::new(1234); + let bank = Arc::new(Bank::new(&mint)); + // generate 10 validators, but only vote for the first 6 validators + let ids: Vec<_> = (0..10) + .map(|i| { + let last_id = hash(&serialize(&i).unwrap()); // Unique hash + bank.register_tick(&last_id); + // sleep to get a different timestamp in the bank + sleep(Duration::from_millis(1)); + last_id + }).collect(); + + // Create a total of 10 vote accounts, each will have a balance of 1 (after giving 1 to + // their vote account), for a total staking pool of 10 tokens. + let vote_accounts: Vec<_> = (0..10) + .map(|i| { + // Create new validator to vote + let validator_keypair = Keypair::new(); + let last_id = ids[i]; + + // Give the validator some tokens + bank.transfer(2, &mint.keypair(), validator_keypair.pubkey(), last_id) + .unwrap(); + let vote_account = create_vote_account(&validator_keypair, &bank, 1, last_id) + .expect("Expected successful creation of account"); + + if i < 6 { + let vote = Vote { + tick_height: (i + 1) as u64, + }; + let vote_tx = Transaction::vote_new(&vote_account, vote, last_id, 0); + bank.process_transaction(&vote_tx).unwrap(); + } + vote_account + }).collect(); + + // There isn't 2/3 consensus, so the bank's finality value should be the default + let mut last_finality_time = 0; + ComputeLeaderFinalityService::compute_finality(&bank, &mut last_finality_time); + assert_eq!(bank.finality(), std::usize::MAX); + + // Get another validator to vote, so we now have 2/3 consensus + let vote_account = &vote_accounts[7]; + let vote = Vote { tick_height: 7 }; + let vote_tx = Transaction::vote_new(&vote_account, vote, ids[6], 0); + bank.process_transaction(&vote_tx).unwrap(); + + ComputeLeaderFinalityService::compute_finality(&bank, &mut last_finality_time); + assert!(bank.finality() != std::usize::MAX); + assert!(last_finality_time > 0); + } +} diff --git a/book/contact_info.rs b/book/contact_info.rs new file mode 100644 index 00000000000000..56512031c924a0 --- /dev/null +++ b/book/contact_info.rs @@ -0,0 +1,237 @@ +use rpc::RPC_PORT; +use signature::{Keypair, KeypairUtil}; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::timing::timestamp; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + +/// Structure representing a node on the network +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct ContactInfo { + pub id: Pubkey, + /// gossip address + pub ncp: SocketAddr, + /// address to connect to for replication + pub tvu: SocketAddr, + /// transactions address + pub tpu: SocketAddr, + /// storage data address + pub storage_addr: SocketAddr, + /// address to which to send JSON-RPC requests + pub rpc: SocketAddr, + /// websocket for JSON-RPC push notifications + pub rpc_pubsub: SocketAddr, + /// latest wallclock picked + pub wallclock: u64, +} + +#[macro_export] +macro_rules! socketaddr { + ($ip:expr, $port:expr) => { + SocketAddr::from((Ipv4Addr::from($ip), $port)) + }; + ($str:expr) => {{ + let a: SocketAddr = $str.parse().unwrap(); + a + }}; +} +#[macro_export] +macro_rules! socketaddr_any { + () => { + socketaddr!(0, 0) + }; +} + +impl Default for ContactInfo { + fn default() -> Self { + ContactInfo { + id: Pubkey::default(), + ncp: socketaddr_any!(), + tvu: socketaddr_any!(), + tpu: socketaddr_any!(), + storage_addr: socketaddr_any!(), + rpc: socketaddr_any!(), + rpc_pubsub: socketaddr_any!(), + wallclock: 0, + } + } +} + +impl ContactInfo { + pub fn new( + id: Pubkey, + ncp: SocketAddr, + tvu: SocketAddr, + tpu: SocketAddr, + storage_addr: SocketAddr, + rpc: SocketAddr, + rpc_pubsub: SocketAddr, + now: u64, + ) -> Self { + ContactInfo { + id, + ncp, + tvu, + tpu, + storage_addr, + rpc, + rpc_pubsub, + wallclock: now, + } + } + + pub fn new_localhost(id: Pubkey, now: u64) -> Self { + Self::new( + id, + socketaddr!("127.0.0.1:1234"), + socketaddr!("127.0.0.1:1235"), + socketaddr!("127.0.0.1:1236"), + socketaddr!("127.0.0.1:1237"), + socketaddr!("127.0.0.1:1238"), + socketaddr!("127.0.0.1:1239"), + now, + ) + } + + #[cfg(test)] + /// ContactInfo with multicast addresses for adversarial testing. + pub fn new_multicast() -> Self { + let addr = socketaddr!("224.0.1.255:1000"); + assert!(addr.ip().is_multicast()); + Self::new( + Keypair::new().pubkey(), + addr, + addr, + addr, + addr, + addr, + addr, + 0, + ) + } + fn next_port(addr: &SocketAddr, nxt: u16) -> SocketAddr { + let mut nxt_addr = *addr; + nxt_addr.set_port(addr.port() + nxt); + nxt_addr + } + pub fn new_with_pubkey_socketaddr(pubkey: Pubkey, bind_addr: &SocketAddr) -> Self { + let transactions_addr = *bind_addr; + let gossip_addr = Self::next_port(&bind_addr, 1); + let replicate_addr = Self::next_port(&bind_addr, 2); + let rpc_addr = SocketAddr::new(bind_addr.ip(), RPC_PORT); + let rpc_pubsub_addr = SocketAddr::new(bind_addr.ip(), RPC_PORT + 1); + ContactInfo::new( + pubkey, + gossip_addr, + replicate_addr, + transactions_addr, + "0.0.0.0:0".parse().unwrap(), + rpc_addr, + rpc_pubsub_addr, + timestamp(), + ) + } + pub fn new_with_socketaddr(bind_addr: &SocketAddr) -> Self { + let keypair = Keypair::new(); + Self::new_with_pubkey_socketaddr(keypair.pubkey(), bind_addr) + } + // + pub fn new_entry_point(gossip_addr: &SocketAddr) -> Self { + let daddr: SocketAddr = socketaddr!("0.0.0.0:0"); + ContactInfo::new( + Pubkey::default(), + *gossip_addr, + daddr, + daddr, + daddr, + daddr, + daddr, + timestamp(), + ) + } + fn is_valid_ip(addr: IpAddr) -> bool { + !(addr.is_unspecified() || addr.is_multicast()) + // || (addr.is_loopback() && !cfg_test)) + // TODO: boot loopback in production networks + } + /// port must not be 0 + /// ip must be specified and not mulitcast + /// loopback ip is only allowed in tests + pub fn is_valid_address(addr: &SocketAddr) -> bool { + (addr.port() != 0) && Self::is_valid_ip(addr.ip()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_valid_address() { + assert!(cfg!(test)); + let bad_address_port = socketaddr!("127.0.0.1:0"); + assert!(!ContactInfo::is_valid_address(&bad_address_port)); + let bad_address_unspecified = socketaddr!(0, 1234); + assert!(!ContactInfo::is_valid_address(&bad_address_unspecified)); + let bad_address_multicast = socketaddr!([224, 254, 0, 0], 1234); + assert!(!ContactInfo::is_valid_address(&bad_address_multicast)); + let loopback = socketaddr!("127.0.0.1:1234"); + assert!(ContactInfo::is_valid_address(&loopback)); + // assert!(!ContactInfo::is_valid_ip_internal(loopback.ip(), false)); + } + #[test] + fn test_default() { + let ci = ContactInfo::default(); + assert!(ci.ncp.ip().is_unspecified()); + assert!(ci.tvu.ip().is_unspecified()); + assert!(ci.rpc.ip().is_unspecified()); + assert!(ci.rpc_pubsub.ip().is_unspecified()); + assert!(ci.tpu.ip().is_unspecified()); + assert!(ci.storage_addr.ip().is_unspecified()); + } + #[test] + fn test_multicast() { + let ci = ContactInfo::new_multicast(); + assert!(ci.ncp.ip().is_multicast()); + assert!(ci.tvu.ip().is_multicast()); + assert!(ci.rpc.ip().is_multicast()); + assert!(ci.rpc_pubsub.ip().is_multicast()); + assert!(ci.tpu.ip().is_multicast()); + assert!(ci.storage_addr.ip().is_multicast()); + } + #[test] + fn test_entry_point() { + let addr = socketaddr!("127.0.0.1:10"); + let ci = ContactInfo::new_entry_point(&addr); + assert_eq!(ci.ncp, addr); + assert!(ci.tvu.ip().is_unspecified()); + assert!(ci.rpc.ip().is_unspecified()); + assert!(ci.rpc_pubsub.ip().is_unspecified()); + assert!(ci.tpu.ip().is_unspecified()); + assert!(ci.storage_addr.ip().is_unspecified()); + } + #[test] + fn test_socketaddr() { + let addr = socketaddr!("127.0.0.1:10"); + let ci = ContactInfo::new_with_socketaddr(&addr); + assert_eq!(ci.tpu, addr); + assert_eq!(ci.ncp.port(), 11); + assert_eq!(ci.tvu.port(), 12); + assert_eq!(ci.rpc.port(), 8899); + assert_eq!(ci.rpc_pubsub.port(), 8900); + assert!(ci.storage_addr.ip().is_unspecified()); + } + #[test] + fn replicated_data_new_with_socketaddr_with_pubkey() { + let keypair = Keypair::new(); + let d1 = ContactInfo::new_with_pubkey_socketaddr( + keypair.pubkey().clone(), + &socketaddr!("127.0.0.1:1234"), + ); + assert_eq!(d1.id, keypair.pubkey()); + assert_eq!(d1.ncp, socketaddr!("127.0.0.1:1235")); + assert_eq!(d1.tvu, socketaddr!("127.0.0.1:1236")); + assert_eq!(d1.tpu, socketaddr!("127.0.0.1:1234")); + assert_eq!(d1.rpc, socketaddr!("127.0.0.1:8899")); + assert_eq!(d1.rpc_pubsub, socketaddr!("127.0.0.1:8900")); + } +} diff --git a/book/counter.rs b/book/counter.rs new file mode 100644 index 00000000000000..fa6145ff004b25 --- /dev/null +++ b/book/counter.rs @@ -0,0 +1,183 @@ +use solana_metrics::{influxdb, submit}; +use solana_sdk::timing; +use std::env; +use std::sync::atomic::{AtomicUsize, Ordering}; + +const DEFAULT_LOG_RATE: usize = 1000; + +pub struct Counter { + pub name: &'static str, + /// total accumulated value + pub counts: AtomicUsize, + pub times: AtomicUsize, + /// last accumulated value logged + pub lastlog: AtomicUsize, + pub lograte: AtomicUsize, +} + +macro_rules! create_counter { + ($name:expr, $lograte:expr) => { + Counter { + name: $name, + counts: AtomicUsize::new(0), + times: AtomicUsize::new(0), + lastlog: AtomicUsize::new(0), + lograte: AtomicUsize::new($lograte), + } + }; +} + +macro_rules! inc_counter { + ($name:expr, $level:expr, $count:expr) => { + unsafe { $name.inc($level, $count) }; + }; +} + +macro_rules! inc_new_counter_info { + ($name:expr, $count:expr) => {{ + inc_new_counter!($name, $count, Level::Info, 0); + }}; + ($name:expr, $count:expr, $lograte:expr) => {{ + inc_new_counter!($name, $count, Level::Info, $lograte); + }}; +} + +macro_rules! inc_new_counter { + ($name:expr, $count:expr, $level:expr, $lograte:expr) => {{ + static mut INC_NEW_COUNTER: Counter = create_counter!($name, $lograte); + inc_counter!(INC_NEW_COUNTER, $level, $count); + }}; +} + +impl Counter { + fn default_log_rate() -> usize { + let v = env::var("SOLANA_DEFAULT_LOG_RATE") + .map(|x| x.parse().unwrap_or(DEFAULT_LOG_RATE)) + .unwrap_or(DEFAULT_LOG_RATE); + if v == 0 { + DEFAULT_LOG_RATE + } else { + v + } + } + pub fn inc(&mut self, level: log::Level, events: usize) { + let counts = self.counts.fetch_add(events, Ordering::Relaxed); + let times = self.times.fetch_add(1, Ordering::Relaxed); + let mut lograte = self.lograte.load(Ordering::Relaxed); + if lograte == 0 { + lograte = Counter::default_log_rate(); + self.lograte.store(lograte, Ordering::Relaxed); + } + if times % lograte == 0 && times > 0 && log_enabled!(level) { + info!( + "COUNTER:{{\"name\": \"{}\", \"counts\": {}, \"samples\": {}, \"now\": {}, \"events\": {}}}", + self.name, + counts + events, + times, + timing::timestamp(), + events, + ); + } + let lastlog = self.lastlog.load(Ordering::Relaxed); + let prev = self + .lastlog + .compare_and_swap(lastlog, counts, Ordering::Relaxed); + if prev == lastlog { + submit( + influxdb::Point::new(&format!("counter-{}", self.name)) + .add_field( + "count", + influxdb::Value::Integer(counts as i64 - lastlog as i64), + ).to_owned(), + ); + } + } +} +#[cfg(test)] +mod tests { + use counter::{Counter, DEFAULT_LOG_RATE}; + use log::Level; + use std::env; + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::{Once, RwLock, ONCE_INIT}; + + fn get_env_lock() -> &'static RwLock<()> { + static mut ENV_LOCK: Option> = None; + static INIT_HOOK: Once = ONCE_INIT; + + unsafe { + INIT_HOOK.call_once(|| { + ENV_LOCK = Some(RwLock::new(())); + }); + &ENV_LOCK.as_ref().unwrap() + } + } + + #[test] + fn test_counter() { + let _readlock = get_env_lock().read(); + static mut COUNTER: Counter = create_counter!("test", 1000); + let count = 1; + inc_counter!(COUNTER, Level::Info, count); + unsafe { + assert_eq!(COUNTER.counts.load(Ordering::Relaxed), 1); + assert_eq!(COUNTER.times.load(Ordering::Relaxed), 1); + assert_eq!(COUNTER.lograte.load(Ordering::Relaxed), 1000); + assert_eq!(COUNTER.lastlog.load(Ordering::Relaxed), 0); + assert_eq!(COUNTER.name, "test"); + } + for _ in 0..199 { + inc_counter!(COUNTER, Level::Info, 2); + } + unsafe { + assert_eq!(COUNTER.lastlog.load(Ordering::Relaxed), 397); + } + inc_counter!(COUNTER, Level::Info, 2); + unsafe { + assert_eq!(COUNTER.lastlog.load(Ordering::Relaxed), 399); + } + } + #[test] + fn test_inc_new_counter() { + let _readlock = get_env_lock().read(); + //make sure that macros are syntactically correct + //the variable is internal to the macro scope so there is no way to introspect it + inc_new_counter_info!("counter-1", 1); + inc_new_counter_info!("counter-2", 1, 2); + } + #[test] + fn test_lograte() { + let _readlock = get_env_lock().read(); + assert_eq!( + Counter::default_log_rate(), + DEFAULT_LOG_RATE, + "default_log_rate() is {}, expected {}, SOLANA_DEFAULT_LOG_RATE environment variable set?", + Counter::default_log_rate(), + DEFAULT_LOG_RATE, + ); + static mut COUNTER: Counter = create_counter!("test_lograte", 0); + inc_counter!(COUNTER, Level::Info, 2); + unsafe { + assert_eq!(COUNTER.lograte.load(Ordering::Relaxed), DEFAULT_LOG_RATE); + } + } + + #[test] + fn test_lograte_env() { + assert_ne!(DEFAULT_LOG_RATE, 0); + let _writelock = get_env_lock().write(); + static mut COUNTER: Counter = create_counter!("test_lograte_env", 0); + env::set_var("SOLANA_DEFAULT_LOG_RATE", "50"); + inc_counter!(COUNTER, Level::Info, 2); + unsafe { + assert_eq!(COUNTER.lograte.load(Ordering::Relaxed), 50); + } + + static mut COUNTER2: Counter = create_counter!("test_lograte_env", 0); + env::set_var("SOLANA_DEFAULT_LOG_RATE", "0"); + inc_counter!(COUNTER2, Level::Info, 2); + unsafe { + assert_eq!(COUNTER2.lograte.load(Ordering::Relaxed), DEFAULT_LOG_RATE); + } + } +} diff --git a/book/crds.rs b/book/crds.rs new file mode 100644 index 00000000000000..82e40320e012fd --- /dev/null +++ b/book/crds.rs @@ -0,0 +1,351 @@ +//! This module implements Cluster Replicated Data Store for +//! asynchronous updates in a distributed network. +//! +//! Data is stored in the CrdsValue type, each type has a specific +//! CrdsValueLabel. Labels are semantically grouped into a single record +//! that is identified by a Pubkey. +//! * 1 Pubkey maps many CrdsValueLabels +//! * 1 CrdsValueLabel maps to 1 CrdsValue +//! The Label, the record Pubkey, and all the record labels can be derived +//! from a single CrdsValue. +//! +//! The actual data is stored in a single map of +//! `CrdsValueLabel(Pubkey) -> CrdsValue` This allows for partial record +//! updates to be propagated through the network. +//! +//! This means that full `Record` updates are not atomic. +//! +//! Additional labels can be added by appending them to the CrdsValueLabel, +//! CrdsValue enums. +//! +//! Merge strategy is implemented in: +//! impl PartialOrd for VersionedCrdsValue +//! +//! A value is updated to a new version if the labels match, and the value +//! wallclock is later, or the value hash is greater. + +use bincode::serialize; +use crds_value::{CrdsValue, CrdsValueLabel}; +use indexmap::map::IndexMap; +use solana_sdk::hash::{hash, Hash}; +use solana_sdk::pubkey::Pubkey; +use std::cmp; + +pub struct Crds { + /// Stores the map of labels and values + pub table: IndexMap, +} + +#[derive(PartialEq, Debug)] +pub enum CrdsError { + InsertFailed, +} + +/// This structure stores some local metadata assosciated with the CrdsValue +/// The implementation of PartialOrd ensures that the "highest" version is always picked to be +/// stored in the Crds +#[derive(PartialEq, Debug)] +pub struct VersionedCrdsValue { + pub value: CrdsValue, + /// local time when inserted + pub insert_timestamp: u64, + /// local time when updated + pub local_timestamp: u64, + /// value hash + pub value_hash: Hash, +} + +impl PartialOrd for VersionedCrdsValue { + fn partial_cmp(&self, other: &VersionedCrdsValue) -> Option { + if self.value.label() != other.value.label() { + None + } else if self.value.wallclock() == other.value.wallclock() { + Some(self.value_hash.cmp(&other.value_hash)) + } else { + Some(self.value.wallclock().cmp(&other.value.wallclock())) + } + } +} +impl VersionedCrdsValue { + pub fn new(local_timestamp: u64, value: CrdsValue) -> Self { + let value_hash = hash(&serialize(&value).unwrap()); + VersionedCrdsValue { + value, + insert_timestamp: local_timestamp, + local_timestamp, + value_hash, + } + } +} + +impl Default for Crds { + fn default() -> Self { + Crds { + table: IndexMap::new(), + } + } +} + +impl Crds { + /// must be called atomically with `insert_versioned` + pub fn new_versioned(&self, local_timestamp: u64, value: CrdsValue) -> VersionedCrdsValue { + VersionedCrdsValue::new(local_timestamp, value) + } + /// insert the new value, returns the old value if insert succeeds + pub fn insert_versioned( + &mut self, + new_value: VersionedCrdsValue, + ) -> Result, CrdsError> { + let label = new_value.value.label(); + let wallclock = new_value.value.wallclock(); + let do_insert = self + .table + .get(&label) + .map(|current| new_value > *current) + .unwrap_or(true); + if do_insert { + let old = self.table.insert(label, new_value); + Ok(old) + } else { + trace!("INSERT FAILED data: {} new.wallclock: {}", label, wallclock,); + Err(CrdsError::InsertFailed) + } + } + pub fn insert( + &mut self, + value: CrdsValue, + local_timestamp: u64, + ) -> Result, CrdsError> { + let new_value = self.new_versioned(local_timestamp, value); + self.insert_versioned(new_value) + } + pub fn lookup(&self, label: &CrdsValueLabel) -> Option<&CrdsValue> { + self.table.get(label).map(|x| &x.value) + } + + pub fn lookup_versioned(&self, label: &CrdsValueLabel) -> Option<&VersionedCrdsValue> { + self.table.get(label) + } + + fn update_label_timestamp(&mut self, id: &CrdsValueLabel, now: u64) { + if let Some(e) = self.table.get_mut(id) { + e.local_timestamp = cmp::max(e.local_timestamp, now); + } + } + + /// Update the timestamp's of all the labels that are assosciated with Pubkey + pub fn update_record_timestamp(&mut self, pubkey: Pubkey, now: u64) { + for label in &CrdsValue::record_labels(pubkey) { + self.update_label_timestamp(label, now); + } + } + + /// find all the keys that are older or equal to min_ts + pub fn find_old_labels(&self, min_ts: u64) -> Vec { + self.table + .iter() + .filter_map(|(k, v)| { + if v.local_timestamp <= min_ts { + Some(k) + } else { + None + } + }).cloned() + .collect() + } + + pub fn remove(&mut self, key: &CrdsValueLabel) { + self.table.remove(key); + } +} + +#[cfg(test)] +mod test { + use super::*; + use contact_info::ContactInfo; + use crds_value::LeaderId; + use signature::{Keypair, KeypairUtil}; + + #[test] + fn test_insert() { + let mut crds = Crds::default(); + let val = CrdsValue::LeaderId(LeaderId::default()); + assert_eq!(crds.insert(val.clone(), 0).ok(), Some(None)); + assert_eq!(crds.table.len(), 1); + assert!(crds.table.contains_key(&val.label())); + assert_eq!(crds.table[&val.label()].local_timestamp, 0); + } + #[test] + fn test_update_old() { + let mut crds = Crds::default(); + let val = CrdsValue::LeaderId(LeaderId::default()); + assert_eq!(crds.insert(val.clone(), 0), Ok(None)); + assert_eq!(crds.insert(val.clone(), 1), Err(CrdsError::InsertFailed)); + assert_eq!(crds.table[&val.label()].local_timestamp, 0); + } + #[test] + fn test_update_new() { + let mut crds = Crds::default(); + let original = CrdsValue::LeaderId(LeaderId::default()); + assert_matches!(crds.insert(original.clone(), 0), Ok(_)); + let val = CrdsValue::LeaderId(LeaderId { + id: Pubkey::default(), + leader_id: Pubkey::default(), + wallclock: 1, + }); + assert_eq!( + crds.insert(val.clone(), 1).unwrap().unwrap().value, + original + ); + assert_eq!(crds.table[&val.label()].local_timestamp, 1); + } + #[test] + fn test_update_timestsamp() { + let mut crds = Crds::default(); + let val = CrdsValue::LeaderId(LeaderId::default()); + assert_eq!(crds.insert(val.clone(), 0), Ok(None)); + + crds.update_label_timestamp(&val.label(), 1); + assert_eq!(crds.table[&val.label()].local_timestamp, 1); + assert_eq!(crds.table[&val.label()].insert_timestamp, 0); + + let val2 = CrdsValue::ContactInfo(ContactInfo::default()); + assert_eq!(val2.label().pubkey(), val.label().pubkey()); + assert_matches!(crds.insert(val2.clone(), 0), Ok(None)); + + crds.update_record_timestamp(val.label().pubkey(), 2); + assert_eq!(crds.table[&val.label()].local_timestamp, 2); + assert_eq!(crds.table[&val.label()].insert_timestamp, 0); + assert_eq!(crds.table[&val2.label()].local_timestamp, 2); + assert_eq!(crds.table[&val2.label()].insert_timestamp, 0); + + crds.update_record_timestamp(val.label().pubkey(), 1); + assert_eq!(crds.table[&val.label()].local_timestamp, 2); + assert_eq!(crds.table[&val.label()].insert_timestamp, 0); + + let mut ci = ContactInfo::default(); + ci.wallclock += 1; + let val3 = CrdsValue::ContactInfo(ci); + assert_matches!(crds.insert(val3.clone(), 3), Ok(Some(_))); + assert_eq!(crds.table[&val2.label()].local_timestamp, 3); + assert_eq!(crds.table[&val2.label()].insert_timestamp, 3); + } + #[test] + fn test_find_old_records() { + let mut crds = Crds::default(); + let val = CrdsValue::LeaderId(LeaderId::default()); + assert_eq!(crds.insert(val.clone(), 1), Ok(None)); + + assert!(crds.find_old_labels(0).is_empty()); + assert_eq!(crds.find_old_labels(1), vec![val.label()]); + assert_eq!(crds.find_old_labels(2), vec![val.label()]); + } + #[test] + fn test_remove() { + let mut crds = Crds::default(); + let val = CrdsValue::LeaderId(LeaderId::default()); + assert_matches!(crds.insert(val.clone(), 1), Ok(_)); + + assert_eq!(crds.find_old_labels(1), vec![val.label()]); + crds.remove(&val.label()); + assert!(crds.find_old_labels(1).is_empty()); + } + #[test] + fn test_equal() { + let key = Keypair::new(); + let v1 = VersionedCrdsValue::new( + 1, + CrdsValue::LeaderId(LeaderId { + id: key.pubkey(), + leader_id: Pubkey::default(), + wallclock: 0, + }), + ); + let v2 = VersionedCrdsValue::new( + 1, + CrdsValue::LeaderId(LeaderId { + id: key.pubkey(), + leader_id: Pubkey::default(), + wallclock: 0, + }), + ); + assert!(!(v1 != v2)); + assert!(v1 == v2); + } + #[test] + fn test_hash_order() { + let key = Keypair::new(); + let v1 = VersionedCrdsValue::new( + 1, + CrdsValue::LeaderId(LeaderId { + id: key.pubkey(), + leader_id: Pubkey::default(), + wallclock: 0, + }), + ); + let v2 = VersionedCrdsValue::new( + 1, + CrdsValue::LeaderId(LeaderId { + id: key.pubkey(), + leader_id: key.pubkey(), + wallclock: 0, + }), + ); + assert!(v1 != v2); + assert!(!(v1 == v2)); + if v1 > v2 { + assert!(v2 < v1) + } else { + assert!(v2 > v1) + } + } + #[test] + fn test_wallclock_order() { + let key = Keypair::new(); + let v1 = VersionedCrdsValue::new( + 1, + CrdsValue::LeaderId(LeaderId { + id: key.pubkey(), + leader_id: Pubkey::default(), + wallclock: 1, + }), + ); + let v2 = VersionedCrdsValue::new( + 1, + CrdsValue::LeaderId(LeaderId { + id: key.pubkey(), + leader_id: Pubkey::default(), + wallclock: 0, + }), + ); + assert!(v1 > v2); + assert!(!(v1 < v2)); + assert!(v1 != v2); + assert!(!(v1 == v2)); + } + #[test] + fn test_label_order() { + let v1 = VersionedCrdsValue::new( + 1, + CrdsValue::LeaderId(LeaderId { + id: Keypair::new().pubkey(), + leader_id: Pubkey::default(), + wallclock: 0, + }), + ); + let v2 = VersionedCrdsValue::new( + 1, + CrdsValue::LeaderId(LeaderId { + id: Keypair::new().pubkey(), + leader_id: Pubkey::default(), + wallclock: 0, + }), + ); + assert!(v1 != v2); + assert!(!(v1 == v2)); + assert!(!(v1 < v2)); + assert!(!(v1 > v2)); + assert!(!(v2 < v1)); + assert!(!(v2 > v1)); + } +} diff --git a/book/crds_gossip.rs b/book/crds_gossip.rs new file mode 100644 index 00000000000000..57fb21bbe17c71 --- /dev/null +++ b/book/crds_gossip.rs @@ -0,0 +1,486 @@ +//! Crds Gossip +//! This module ties together Crds and the push and pull gossip overlays. The interface is +//! designed to run with a simulator or over a UDP network connection with messages up to a +//! packet::BLOB_DATA_SIZE size. + +use bloom::Bloom; +use crds::Crds; +use crds_gossip_error::CrdsGossipError; +use crds_gossip_pull::CrdsGossipPull; +use crds_gossip_push::{CrdsGossipPush, CRDS_GOSSIP_NUM_ACTIVE}; +use crds_value::CrdsValue; +use solana_sdk::hash::Hash; +use solana_sdk::pubkey::Pubkey; + +pub struct CrdsGossip { + pub crds: Crds, + pub id: Pubkey, + push: CrdsGossipPush, + pull: CrdsGossipPull, +} + +impl Default for CrdsGossip { + fn default() -> Self { + CrdsGossip { + crds: Crds::default(), + id: Pubkey::default(), + push: CrdsGossipPush::default(), + pull: CrdsGossipPull::default(), + } + } +} + +impl CrdsGossip { + pub fn set_self(&mut self, id: Pubkey) { + self.id = id; + } + /// process a push message to the network + pub fn process_push_message(&mut self, values: &[CrdsValue], now: u64) -> Vec { + let results: Vec<_> = values + .iter() + .map(|val| { + self.push + .process_push_message(&mut self.crds, val.clone(), now) + }).collect(); + results + .into_iter() + .zip(values) + .filter_map(|(r, d)| { + if r == Err(CrdsGossipError::PushMessagePrune) { + Some(d.label().pubkey()) + } else if let Ok(Some(val)) = r { + self.pull + .record_old_hash(val.value_hash, val.local_timestamp); + None + } else { + None + } + }).collect() + } + + pub fn new_push_messages(&mut self, now: u64) -> (Pubkey, Vec, Vec) { + let (peers, values) = self.push.new_push_messages(&self.crds, now); + (self.id, peers, values) + } + + /// add the `from` to the peer's filter of nodes + pub fn process_prune_msg(&mut self, peer: Pubkey, origin: &[Pubkey]) { + self.push.process_prune_msg(peer, origin) + } + + /// refresh the push active set + /// * ratio - number of actives to rotate + pub fn refresh_push_active_set(&mut self) { + self.push.refresh_push_active_set( + &self.crds, + self.id, + self.pull.pull_request_time.len(), + CRDS_GOSSIP_NUM_ACTIVE, + ) + } + + /// generate a random request + pub fn new_pull_request( + &self, + now: u64, + ) -> Result<(Pubkey, Bloom, CrdsValue), CrdsGossipError> { + self.pull.new_pull_request(&self.crds, self.id, now) + } + + /// time when a request to `from` was initiated + /// This is used for weighted random selection durring `new_pull_request` + /// It's important to use the local nodes request creation time as the weight + /// instaad of the response received time otherwise failed nodes will increase their weight. + pub fn mark_pull_request_creation_time(&mut self, from: Pubkey, now: u64) { + self.pull.mark_pull_request_creation_time(from, now) + } + /// process a pull request and create a response + pub fn process_pull_request( + &mut self, + caller: CrdsValue, + filter: Bloom, + now: u64, + ) -> Vec { + self.pull + .process_pull_request(&mut self.crds, caller, filter, now) + } + /// process a pull response + pub fn process_pull_response( + &mut self, + from: Pubkey, + response: Vec, + now: u64, + ) -> usize { + self.pull + .process_pull_response(&mut self.crds, from, response, now) + } + pub fn purge(&mut self, now: u64) { + if now > self.push.msg_timeout { + let min = now - self.push.msg_timeout; + self.push.purge_old_pending_push_messages(&self.crds, min); + } + if now > 5 * self.push.msg_timeout { + let min = now - 5 * self.push.msg_timeout; + self.push.purge_old_pushed_once_messages(min); + } + if now > self.pull.crds_timeout { + let min = now - self.pull.crds_timeout; + self.pull.purge_active(&mut self.crds, self.id, min); + } + if now > 5 * self.pull.crds_timeout { + let min = now - 5 * self.pull.crds_timeout; + self.pull.purge_purged(min); + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use bincode::serialized_size; + use contact_info::ContactInfo; + use crds_gossip_push::CRDS_GOSSIP_PUSH_MSG_TIMEOUT_MS; + use crds_value::CrdsValueLabel; + use rayon::prelude::*; + use signature::{Keypair, KeypairUtil}; + use std::collections::HashMap; + use std::sync::{Arc, Mutex}; + + type Node = Arc>; + type Network = HashMap; + fn star_network_create(num: usize) -> Network { + let entry = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + let mut network: HashMap<_, _> = (1..num) + .map(|_| { + let new = + CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + let id = new.label().pubkey(); + let mut node = CrdsGossip::default(); + node.crds.insert(new.clone(), 0).unwrap(); + node.crds.insert(entry.clone(), 0).unwrap(); + node.set_self(id); + (new.label().pubkey(), Arc::new(Mutex::new(node))) + }).collect(); + let mut node = CrdsGossip::default(); + let id = entry.label().pubkey(); + node.crds.insert(entry.clone(), 0).unwrap(); + node.set_self(id); + network.insert(id, Arc::new(Mutex::new(node))); + network + } + + fn rstar_network_create(num: usize) -> Network { + let entry = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + let mut origin = CrdsGossip::default(); + let id = entry.label().pubkey(); + origin.crds.insert(entry.clone(), 0).unwrap(); + origin.set_self(id); + let mut network: HashMap<_, _> = (1..num) + .map(|_| { + let new = + CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + let id = new.label().pubkey(); + let mut node = CrdsGossip::default(); + node.crds.insert(new.clone(), 0).unwrap(); + origin.crds.insert(new.clone(), 0).unwrap(); + node.set_self(id); + (new.label().pubkey(), Arc::new(Mutex::new(node))) + }).collect(); + network.insert(id, Arc::new(Mutex::new(origin))); + network + } + + fn ring_network_create(num: usize) -> Network { + let mut network: HashMap<_, _> = (0..num) + .map(|_| { + let new = + CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + let id = new.label().pubkey(); + let mut node = CrdsGossip::default(); + node.crds.insert(new.clone(), 0).unwrap(); + node.set_self(id); + (new.label().pubkey(), Arc::new(Mutex::new(node))) + }).collect(); + let keys: Vec = network.keys().cloned().collect(); + for k in 0..keys.len() { + let start_info = { + let start = &network[&keys[k]]; + let start_id = start.lock().unwrap().id.clone(); + start + .lock() + .unwrap() + .crds + .lookup(&CrdsValueLabel::ContactInfo(start_id)) + .unwrap() + .clone() + }; + let end = network.get_mut(&keys[(k + 1) % keys.len()]).unwrap(); + end.lock().unwrap().crds.insert(start_info, 0).unwrap(); + } + network + } + + fn network_simulator_pull_only(network: &mut Network) { + let num = network.len(); + let (converged, bytes_tx) = network_run_pull(network, 0, num * 2, 0.9); + trace!( + "network_simulator_pull_{}: converged: {} total_bytes: {}", + num, + converged, + bytes_tx + ); + assert!(converged >= 0.9); + } + + fn network_simulator(network: &mut Network) { + let num = network.len(); + // run for a small amount of time + let (converged, bytes_tx) = network_run_pull(network, 0, 10, 1.0); + trace!("network_simulator_push_{}: converged: {}", num, converged); + // make sure there is someone in the active set + let network_values: Vec = network.values().cloned().collect(); + network_values.par_iter().for_each(|node| { + node.lock().unwrap().refresh_push_active_set(); + }); + let mut total_bytes = bytes_tx; + for second in 1..num { + let start = second * 10; + let end = (second + 1) * 10; + let now = (start * 100) as u64; + // push a message to the network + network_values.par_iter().for_each(|locked_node| { + let node = &mut locked_node.lock().unwrap(); + let mut m = node + .crds + .lookup(&CrdsValueLabel::ContactInfo(node.id)) + .and_then(|v| v.contact_info().cloned()) + .unwrap(); + m.wallclock = now; + node.process_push_message(&[CrdsValue::ContactInfo(m.clone())], now); + }); + // push for a bit + let (queue_size, bytes_tx) = network_run_push(network, start, end); + total_bytes += bytes_tx; + trace!( + "network_simulator_push_{}: queue_size: {} bytes: {}", + num, + queue_size, + bytes_tx + ); + // pull for a bit + let (converged, bytes_tx) = network_run_pull(network, start, end, 1.0); + total_bytes += bytes_tx; + trace!( + "network_simulator_push_{}: converged: {} bytes: {} total_bytes: {}", + num, + converged, + bytes_tx, + total_bytes + ); + if converged > 0.9 { + break; + } + } + } + + fn network_run_push(network: &mut Network, start: usize, end: usize) -> (usize, usize) { + let mut bytes: usize = 0; + let mut num_msgs: usize = 0; + let mut total: usize = 0; + let num = network.len(); + let mut prunes: usize = 0; + let mut delivered: usize = 0; + let network_values: Vec = network.values().cloned().collect(); + for t in start..end { + let now = t as u64 * 100; + let requests: Vec<_> = network_values + .par_iter() + .map(|node| { + node.lock().unwrap().purge(now); + node.lock().unwrap().new_push_messages(now) + }).collect(); + let transfered: Vec<_> = requests + .par_iter() + .map(|(from, peers, msgs)| { + let mut bytes: usize = 0; + let mut delivered: usize = 0; + let mut num_msgs: usize = 0; + let mut prunes: usize = 0; + for to in peers { + bytes += serialized_size(msgs).unwrap() as usize; + num_msgs += 1; + let rsps = network + .get(&to) + .map(|node| node.lock().unwrap().process_push_message(&msgs, now)) + .unwrap(); + bytes += serialized_size(&rsps).unwrap() as usize; + prunes += rsps.len(); + network + .get(&from) + .map(|node| node.lock().unwrap().process_prune_msg(*to, &rsps)) + .unwrap(); + delivered += rsps.is_empty() as usize; + } + (bytes, delivered, num_msgs, prunes) + }).collect(); + for (b, d, m, p) in transfered { + bytes += b; + delivered += d; + num_msgs += m; + prunes += p; + } + if now % CRDS_GOSSIP_PUSH_MSG_TIMEOUT_MS == 0 && now > 0 { + network_values.par_iter().for_each(|node| { + node.lock().unwrap().refresh_push_active_set(); + }); + } + total = network_values + .par_iter() + .map(|v| v.lock().unwrap().push.num_pending()) + .sum(); + trace!( + "network_run_push_{}: now: {} queue: {} bytes: {} num_msgs: {} prunes: {} delivered: {}", + num, + now, + total, + bytes, + num_msgs, + prunes, + delivered, + ); + } + (total, bytes) + } + + fn network_run_pull( + network: &mut Network, + start: usize, + end: usize, + max_convergance: f64, + ) -> (f64, usize) { + let mut bytes: usize = 0; + let mut msgs: usize = 0; + let mut overhead: usize = 0; + let mut convergance = 0f64; + let num = network.len(); + let network_values: Vec = network.values().cloned().collect(); + for t in start..end { + let now = t as u64 * 100; + let mut requests: Vec<_> = { + network_values + .par_iter() + .filter_map(|from| from.lock().unwrap().new_pull_request(now).ok()) + .collect() + }; + let transfered: Vec<_> = requests + .into_par_iter() + .map(|(to, request, caller_info)| { + let mut bytes: usize = 0; + let mut msgs: usize = 0; + let mut overhead: usize = 0; + let from = caller_info.label().pubkey(); + bytes += request.keys.len(); + bytes += (request.bits.len() / 8) as usize; + bytes += serialized_size(&caller_info).unwrap() as usize; + let rsp = network + .get(&to) + .map(|node| { + node.lock() + .unwrap() + .process_pull_request(caller_info, request, now) + }).unwrap(); + bytes += serialized_size(&rsp).unwrap() as usize; + msgs += rsp.len(); + network.get(&from).map(|node| { + node.lock() + .unwrap() + .mark_pull_request_creation_time(from, now); + overhead += node.lock().unwrap().process_pull_response(from, rsp, now); + }); + (bytes, msgs, overhead) + }).collect(); + for (b, m, o) in transfered { + bytes += b; + msgs += m; + overhead += o; + } + let total: usize = network_values + .par_iter() + .map(|v| v.lock().unwrap().crds.table.len()) + .sum(); + convergance = total as f64 / ((num * num) as f64); + if convergance > max_convergance { + break; + } + trace!( + "network_run_pull_{}: now: {} connections: {} convergance: {} bytes: {} msgs: {} overhead: {}", + num, + now, + total, + convergance, + bytes, + msgs, + overhead + ); + } + (convergance, bytes) + } + + #[test] + fn test_star_network_pull_50() { + let mut network = star_network_create(50); + network_simulator_pull_only(&mut network); + } + #[test] + fn test_star_network_pull_100() { + let mut network = star_network_create(100); + network_simulator_pull_only(&mut network); + } + #[test] + fn test_star_network_push_star_200() { + let mut network = star_network_create(200); + network_simulator(&mut network); + } + #[test] + fn test_star_network_push_rstar_200() { + let mut network = rstar_network_create(200); + network_simulator(&mut network); + } + #[test] + fn test_star_network_push_ring_200() { + let mut network = ring_network_create(200); + network_simulator(&mut network); + } + #[test] + #[ignore] + fn test_star_network_large_pull() { + use logger; + logger::setup(); + let mut network = star_network_create(2000); + network_simulator_pull_only(&mut network); + } + #[test] + #[ignore] + fn test_rstar_network_large_push() { + use logger; + logger::setup(); + let mut network = rstar_network_create(4000); + network_simulator(&mut network); + } + #[test] + #[ignore] + fn test_ring_network_large_push() { + use logger; + logger::setup(); + let mut network = ring_network_create(4001); + network_simulator(&mut network); + } + #[test] + #[ignore] + fn test_star_network_large_push() { + use logger; + logger::setup(); + let mut network = star_network_create(4002); + network_simulator(&mut network); + } +} diff --git a/book/crds_gossip_error.rs b/book/crds_gossip_error.rs new file mode 100644 index 00000000000000..d9d00ce77c043f --- /dev/null +++ b/book/crds_gossip_error.rs @@ -0,0 +1,7 @@ +#[derive(PartialEq, Debug)] +pub enum CrdsGossipError { + NoPeers, + PushMessageTimeout, + PushMessagePrune, + PushMessageOldVersion, +} diff --git a/book/crds_gossip_pull.rs b/book/crds_gossip_pull.rs new file mode 100644 index 00000000000000..2b77fbfa1bb1f9 --- /dev/null +++ b/book/crds_gossip_pull.rs @@ -0,0 +1,378 @@ +//! Crds Gossip Pull overlay +//! This module implements the anti-entropy protocol for the network. +//! +//! The basic strategy is as follows: +//! 1. Construct a bloom filter of the local data set +//! 2. Randomly ask a node on the network for data that is not contained in the bloom filter. +//! +//! Bloom filters have a false positive rate. Each requests uses a different bloom filter +//! with random hash functions. So each subsequent request will have a different distribution +//! of false positives. + +use bincode::serialized_size; +use bloom::Bloom; +use crds::Crds; +use crds_gossip_error::CrdsGossipError; +use crds_value::{CrdsValue, CrdsValueLabel}; +use packet::BLOB_DATA_SIZE; +use rand; +use rand::distributions::{Distribution, Weighted, WeightedChoice}; +use solana_sdk::hash::Hash; +use solana_sdk::pubkey::Pubkey; +use std::cmp; +use std::collections::HashMap; +use std::collections::VecDeque; + +pub const CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS: u64 = 15000; + +pub struct CrdsGossipPull { + /// timestamp of last request + pub pull_request_time: HashMap, + /// hash and insert time + purged_values: VecDeque<(Hash, u64)>, + /// max bytes per message + pub max_bytes: usize, + pub crds_timeout: u64, +} + +impl Default for CrdsGossipPull { + fn default() -> Self { + Self { + purged_values: VecDeque::new(), + pull_request_time: HashMap::new(), + max_bytes: BLOB_DATA_SIZE, + crds_timeout: CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS, + } + } +} +impl CrdsGossipPull { + /// generate a random request + pub fn new_pull_request( + &self, + crds: &Crds, + self_id: Pubkey, + now: u64, + ) -> Result<(Pubkey, Bloom, CrdsValue), CrdsGossipError> { + let mut options: Vec<_> = crds + .table + .values() + .filter_map(|v| v.value.contact_info()) + .filter(|v| { + v.id != self_id && !v.ncp.ip().is_unspecified() && !v.ncp.ip().is_multicast() + }).map(|item| { + let req_time: u64 = *self.pull_request_time.get(&item.id).unwrap_or(&0); + let weight = cmp::max( + 1, + cmp::min(u64::from(u16::max_value()) - 1, (now - req_time) / 1024) as u32, + ); + Weighted { weight, item } + }).collect(); + if options.is_empty() { + return Err(CrdsGossipError::NoPeers); + } + let filter = self.build_crds_filter(crds); + let random = WeightedChoice::new(&mut options).sample(&mut rand::thread_rng()); + let self_info = crds + .lookup(&CrdsValueLabel::ContactInfo(self_id)) + .unwrap_or_else(|| panic!("self_id invalid {}", self_id)); + Ok((random.id, filter, self_info.clone())) + } + + /// time when a request to `from` was initiated + /// This is used for weighted random selection during `new_pull_request` + /// It's important to use the local nodes request creation time as the weight + /// instead of the response received time otherwise failed nodes will increase their weight. + pub fn mark_pull_request_creation_time(&mut self, from: Pubkey, now: u64) { + self.pull_request_time.insert(from, now); + } + + /// Store an old hash in the purged values set + pub fn record_old_hash(&mut self, hash: Hash, timestamp: u64) { + self.purged_values.push_back((hash, timestamp)) + } + + /// process a pull request and create a response + pub fn process_pull_request( + &mut self, + crds: &mut Crds, + caller: CrdsValue, + mut filter: Bloom, + now: u64, + ) -> Vec { + let rv = self.filter_crds_values(crds, &mut filter); + let key = caller.label().pubkey(); + let old = crds.insert(caller, now); + if let Some(val) = old.ok().and_then(|opt| opt) { + self.purged_values + .push_back((val.value_hash, val.local_timestamp)) + } + crds.update_record_timestamp(key, now); + rv + } + /// process a pull response + pub fn process_pull_response( + &mut self, + crds: &mut Crds, + from: Pubkey, + response: Vec, + now: u64, + ) -> usize { + let mut failed = 0; + for r in response { + let owner = r.label().pubkey(); + let old = crds.insert(r, now); + failed += old.is_err() as usize; + old.ok().map(|opt| { + crds.update_record_timestamp(owner, now); + opt.map(|val| { + self.purged_values + .push_back((val.value_hash, val.local_timestamp)) + }) + }); + } + crds.update_record_timestamp(from, now); + failed + } + /// build a filter of the current crds table + fn build_crds_filter(&self, crds: &Crds) -> Bloom { + let num = crds.table.values().count() + self.purged_values.len(); + let mut bloom = Bloom::random(num, 0.1, 4 * 1024 * 8 - 1); + for v in crds.table.values() { + bloom.add(&v.value_hash); + } + for (value_hash, _insert_timestamp) in &self.purged_values { + bloom.add(value_hash); + } + bloom + } + /// filter values that fail the bloom filter up to max_bytes + fn filter_crds_values(&self, crds: &Crds, filter: &mut Bloom) -> Vec { + let mut max_bytes = self.max_bytes as isize; + let mut ret = vec![]; + for v in crds.table.values() { + if filter.contains(&v.value_hash) { + continue; + } + max_bytes -= serialized_size(&v.value).unwrap() as isize; + if max_bytes < 0 { + break; + } + ret.push(v.value.clone()); + } + ret + } + /// Purge values from the crds that are older then `active_timeout` + /// The value_hash of an active item is put into self.purged_values queue + pub fn purge_active(&mut self, crds: &mut Crds, self_id: Pubkey, min_ts: u64) { + let old = crds.find_old_labels(min_ts); + let mut purged: VecDeque<_> = old + .iter() + .filter(|label| label.pubkey() != self_id) + .filter_map(|label| { + let rv = crds + .lookup_versioned(label) + .map(|val| (val.value_hash, val.local_timestamp)); + crds.remove(label); + rv + }).collect(); + self.purged_values.append(&mut purged); + } + /// Purge values from the `self.purged_values` queue that are older then purge_timeout + pub fn purge_purged(&mut self, min_ts: u64) { + let cnt = self + .purged_values + .iter() + .take_while(|v| v.1 < min_ts) + .count(); + self.purged_values.drain(..cnt); + } +} +#[cfg(test)] +mod test { + use super::*; + use contact_info::ContactInfo; + use crds_value::LeaderId; + use signature::{Keypair, KeypairUtil}; + + #[test] + fn test_new_pull_request() { + let mut crds = Crds::default(); + let entry = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + let id = entry.label().pubkey(); + let node = CrdsGossipPull::default(); + assert_eq!( + node.new_pull_request(&crds, id, 0), + Err(CrdsGossipError::NoPeers) + ); + + crds.insert(entry.clone(), 0).unwrap(); + assert_eq!( + node.new_pull_request(&crds, id, 0), + Err(CrdsGossipError::NoPeers) + ); + + let new = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + crds.insert(new.clone(), 0).unwrap(); + let req = node.new_pull_request(&crds, id, 0); + let (to, _, self_info) = req.unwrap(); + assert_eq!(to, new.label().pubkey()); + assert_eq!(self_info, entry); + } + + #[test] + fn test_new_mark_creation_time() { + let mut crds = Crds::default(); + let entry = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + let node_id = entry.label().pubkey(); + let mut node = CrdsGossipPull::default(); + crds.insert(entry.clone(), 0).unwrap(); + let old = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + crds.insert(old.clone(), 0).unwrap(); + let new = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + crds.insert(new.clone(), 0).unwrap(); + + // set request creation time to max_value + node.mark_pull_request_creation_time(new.label().pubkey(), u64::max_value()); + + // odds of getting the other request should be 1 in u64::max_value() + for _ in 0..10 { + let req = node.new_pull_request(&crds, node_id, u64::max_value()); + let (to, _, self_info) = req.unwrap(); + assert_eq!(to, old.label().pubkey()); + assert_eq!(self_info, entry); + } + } + + #[test] + fn test_process_pull_request() { + let mut node_crds = Crds::default(); + let entry = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + let node_id = entry.label().pubkey(); + let node = CrdsGossipPull::default(); + node_crds.insert(entry.clone(), 0).unwrap(); + let new = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + node_crds.insert(new.clone(), 0).unwrap(); + let req = node.new_pull_request(&node_crds, node_id, 0); + + let mut dest_crds = Crds::default(); + let mut dest = CrdsGossipPull::default(); + let (_, filter, caller) = req.unwrap(); + let rsp = dest.process_pull_request(&mut dest_crds, caller.clone(), filter, 1); + assert!(rsp.is_empty()); + assert!(dest_crds.lookup(&caller.label()).is_some()); + assert_eq!( + dest_crds + .lookup_versioned(&caller.label()) + .unwrap() + .insert_timestamp, + 1 + ); + assert_eq!( + dest_crds + .lookup_versioned(&caller.label()) + .unwrap() + .local_timestamp, + 1 + ); + } + #[test] + fn test_process_pull_request_response() { + let mut node_crds = Crds::default(); + let entry = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + let node_id = entry.label().pubkey(); + let mut node = CrdsGossipPull::default(); + node_crds.insert(entry.clone(), 0).unwrap(); + let new = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + node_crds.insert(new.clone(), 0).unwrap(); + + let mut dest = CrdsGossipPull::default(); + let mut dest_crds = Crds::default(); + let new = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + dest_crds.insert(new.clone(), 0).unwrap(); + + // node contains a key from the dest node, but at an older local timestamp + let dest_id = new.label().pubkey(); + let same_key = CrdsValue::LeaderId(LeaderId { + id: dest_id, + leader_id: dest_id, + wallclock: 1, + }); + node_crds.insert(same_key.clone(), 0).unwrap(); + assert_eq!( + node_crds + .lookup_versioned(&same_key.label()) + .unwrap() + .local_timestamp, + 0 + ); + let mut done = false; + for _ in 0..30 { + // there is a chance of a false positive with bloom filters + let req = node.new_pull_request(&node_crds, node_id, 0); + let (_, filter, caller) = req.unwrap(); + let rsp = dest.process_pull_request(&mut dest_crds, caller, filter, 0); + // if there is a false positive this is empty + // prob should be around 0.1 per iteration + if rsp.is_empty() { + continue; + } + + assert_eq!(rsp.len(), 1); + let failed = node.process_pull_response(&mut node_crds, node_id, rsp, 1); + assert_eq!(failed, 0); + assert_eq!( + node_crds + .lookup_versioned(&new.label()) + .unwrap() + .local_timestamp, + 1 + ); + // verify that the whole record was updated for dest since this is a response from dest + assert_eq!( + node_crds + .lookup_versioned(&same_key.label()) + .unwrap() + .local_timestamp, + 1 + ); + done = true; + break; + } + assert!(done); + } + #[test] + fn test_gossip_purge() { + let mut node_crds = Crds::default(); + let entry = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + let node_label = entry.label(); + let node_id = node_label.pubkey(); + let mut node = CrdsGossipPull::default(); + node_crds.insert(entry.clone(), 0).unwrap(); + let old = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + node_crds.insert(old.clone(), 0).unwrap(); + let value_hash = node_crds.lookup_versioned(&old.label()).unwrap().value_hash; + + //verify self is valid + assert_eq!(node_crds.lookup(&node_label).unwrap().label(), node_label); + + // purge + node.purge_active(&mut node_crds, node_id, 1); + + //verify self is still valid after purge + assert_eq!(node_crds.lookup(&node_label).unwrap().label(), node_label); + + assert_eq!(node_crds.lookup_versioned(&old.label()), None); + assert_eq!(node.purged_values.len(), 1); + for _ in 0..30 { + // there is a chance of a false positive with bloom filters + // assert that purged value is still in the set + // chance of 30 consecutive false positives is 0.1^30 + let mut filter = node.build_crds_filter(&node_crds); + assert!(filter.contains(&value_hash)); + } + + // purge the value + node.purge_purged(1); + assert_eq!(node.purged_values.len(), 0); + } +} diff --git a/book/crds_gossip_push.rs b/book/crds_gossip_push.rs new file mode 100644 index 00000000000000..00f50ba630c3b6 --- /dev/null +++ b/book/crds_gossip_push.rs @@ -0,0 +1,459 @@ +//! Crds Gossip Push overlay +//! This module is used to propagate recently created CrdsValues across the network +//! Eager push strategy is based on Plumtree +//! http://asc.di.fct.unl.pt/~jleitao/pdf/srds07-leitao.pdf +//! +//! Main differences are: +//! 1. There is no `max hop`. Messages are signed with a local wallclock. If they are outside of +//! the local nodes wallclock window they are drooped silently. +//! 2. The prune set is stored in a Bloom filter. + +use bincode::serialized_size; +use bloom::Bloom; +use contact_info::ContactInfo; +use crds::{Crds, VersionedCrdsValue}; +use crds_gossip_error::CrdsGossipError; +use crds_value::{CrdsValue, CrdsValueLabel}; +use indexmap::map::IndexMap; +use packet::BLOB_DATA_SIZE; +use rand::{self, Rng}; +use solana_sdk::hash::Hash; +use solana_sdk::pubkey::Pubkey; +use std::cmp; +use std::collections::HashMap; + +pub const CRDS_GOSSIP_NUM_ACTIVE: usize = 30; +pub const CRDS_GOSSIP_PUSH_FANOUT: usize = 6; +pub const CRDS_GOSSIP_PUSH_MSG_TIMEOUT_MS: u64 = 5000; + +pub struct CrdsGossipPush { + /// max bytes per message + pub max_bytes: usize, + /// active set of validators for push + active_set: IndexMap>, + /// push message queue + push_messages: HashMap, + pushed_once: HashMap, + pub num_active: usize, + pub push_fanout: usize, + pub msg_timeout: u64, +} + +impl Default for CrdsGossipPush { + fn default() -> Self { + Self { + max_bytes: BLOB_DATA_SIZE, + active_set: IndexMap::new(), + push_messages: HashMap::new(), + pushed_once: HashMap::new(), + num_active: CRDS_GOSSIP_NUM_ACTIVE, + push_fanout: CRDS_GOSSIP_PUSH_FANOUT, + msg_timeout: CRDS_GOSSIP_PUSH_MSG_TIMEOUT_MS, + } + } +} +impl CrdsGossipPush { + pub fn num_pending(&self) -> usize { + self.push_messages.len() + } + /// process a push message to the network + pub fn process_push_message( + &mut self, + crds: &mut Crds, + value: CrdsValue, + now: u64, + ) -> Result, CrdsGossipError> { + if now > value.wallclock() + self.msg_timeout { + return Err(CrdsGossipError::PushMessageTimeout); + } + if now + self.msg_timeout < value.wallclock() { + return Err(CrdsGossipError::PushMessageTimeout); + } + let label = value.label(); + + let new_value = crds.new_versioned(now, value); + let value_hash = new_value.value_hash; + if self.pushed_once.get(&value_hash).is_some() { + return Err(CrdsGossipError::PushMessagePrune); + } + let old = crds.insert_versioned(new_value); + if old.is_err() { + return Err(CrdsGossipError::PushMessageOldVersion); + } + self.push_messages.insert(label, value_hash); + self.pushed_once.insert(value_hash, now); + Ok(old.ok().and_then(|opt| opt)) + } + + /// New push message to broadcast to peers. + /// Returns a list of Pubkeys for the selected peers and a list of values to send to all the + /// peers. + /// The list of push messages is created such that all the randomly selected peers have not + /// pruned the source addresses. + pub fn new_push_messages(&mut self, crds: &Crds, now: u64) -> (Vec, Vec) { + let max = self.active_set.len(); + let mut nodes: Vec<_> = (0..max).collect(); + rand::thread_rng().shuffle(&mut nodes); + let peers: Vec = nodes + .into_iter() + .filter_map(|n| self.active_set.get_index(n)) + .take(self.push_fanout) + .map(|n| *n.0) + .collect(); + let mut total_bytes: usize = 0; + let mut values = vec![]; + for (label, hash) in &self.push_messages { + let mut failed = false; + for p in &peers { + let filter = self.active_set.get_mut(p); + failed |= filter.is_none() || filter.unwrap().contains(&label.pubkey()); + } + if failed { + continue; + } + let res = crds.lookup_versioned(label); + if res.is_none() { + continue; + } + let version = res.unwrap(); + if version.value_hash != *hash { + continue; + } + let value = &version.value; + if value.wallclock() > now || value.wallclock() + self.msg_timeout < now { + continue; + } + total_bytes += serialized_size(value).unwrap() as usize; + if total_bytes > self.max_bytes { + break; + } + values.push(value.clone()); + } + for v in &values { + self.push_messages.remove(&v.label()); + } + (peers, values) + } + + /// add the `from` to the peer's filter of nodes + pub fn process_prune_msg(&mut self, peer: Pubkey, origins: &[Pubkey]) { + for origin in origins { + if let Some(p) = self.active_set.get_mut(&peer) { + p.add(origin) + } + } + } + + fn compute_need(num_active: usize, active_set_len: usize, ratio: usize) -> usize { + let num = active_set_len / ratio; + cmp::min(num_active, (num_active - active_set_len) + num) + } + + /// refresh the push active set + /// * ratio - active_set.len()/ratio is the number of actives to rotate + pub fn refresh_push_active_set( + &mut self, + crds: &Crds, + self_id: Pubkey, + network_size: usize, + ratio: usize, + ) { + let need = Self::compute_need(self.num_active, self.active_set.len(), ratio); + let mut new_items = HashMap::new(); + let mut ixs: Vec<_> = (0..crds.table.len()).collect(); + rand::thread_rng().shuffle(&mut ixs); + + for ix in ixs { + let item = crds.table.get_index(ix); + if item.is_none() { + continue; + } + let val = item.unwrap(); + if val.0.pubkey() == self_id { + continue; + } + if self.active_set.get(&val.0.pubkey()).is_some() { + continue; + } + if new_items.get(&val.0.pubkey()).is_some() { + continue; + } + if let Some(contact) = val.1.value.contact_info() { + if !ContactInfo::is_valid_address(&contact.ncp) { + continue; + } + } + let bloom = Bloom::random(network_size, 0.1, 1024 * 8 * 4); + new_items.insert(val.0.pubkey(), bloom); + if new_items.len() == need { + break; + } + } + let mut keys: Vec = self.active_set.keys().cloned().collect(); + rand::thread_rng().shuffle(&mut keys); + let num = keys.len() / ratio; + for k in &keys[..num] { + self.active_set.remove(k); + } + for (k, v) in new_items { + self.active_set.insert(k, v); + } + } + + /// purge old pending push messages + pub fn purge_old_pending_push_messages(&mut self, crds: &Crds, min_time: u64) { + let old_msgs: Vec = self + .push_messages + .iter() + .filter_map(|(k, hash)| { + if let Some(versioned) = crds.lookup_versioned(k) { + if versioned.value.wallclock() < min_time || versioned.value_hash != *hash { + Some(k) + } else { + None + } + } else { + Some(k) + } + }).cloned() + .collect(); + for k in old_msgs { + self.push_messages.remove(&k); + } + } + /// purge old pushed_once messages + pub fn purge_old_pushed_once_messages(&mut self, min_time: u64) { + let old_msgs: Vec = self + .pushed_once + .iter() + .filter_map(|(k, v)| if *v < min_time { Some(k) } else { None }) + .cloned() + .collect(); + for k in old_msgs { + self.pushed_once.remove(&k); + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use contact_info::ContactInfo; + use signature::{Keypair, KeypairUtil}; + #[test] + fn test_process_push() { + let mut crds = Crds::default(); + let mut push = CrdsGossipPush::default(); + let value = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + let label = value.label(); + // push a new message + assert_eq!( + push.process_push_message(&mut crds, value.clone(), 0), + Ok(None) + ); + assert_eq!(crds.lookup(&label), Some(&value)); + + // push it again + assert_eq!( + push.process_push_message(&mut crds, value.clone(), 0), + Err(CrdsGossipError::PushMessagePrune) + ); + } + #[test] + fn test_process_push_old_version() { + let mut crds = Crds::default(); + let mut push = CrdsGossipPush::default(); + let mut ci = ContactInfo::new_localhost(Keypair::new().pubkey(), 0); + ci.wallclock = 1; + let value = CrdsValue::ContactInfo(ci.clone()); + + // push a new message + assert_eq!(push.process_push_message(&mut crds, value, 0), Ok(None)); + + // push an old version + ci.wallclock = 0; + let value = CrdsValue::ContactInfo(ci.clone()); + assert_eq!( + push.process_push_message(&mut crds, value, 0), + Err(CrdsGossipError::PushMessageOldVersion) + ); + } + #[test] + fn test_process_push_timeout() { + let mut crds = Crds::default(); + let mut push = CrdsGossipPush::default(); + let timeout = push.msg_timeout; + let mut ci = ContactInfo::new_localhost(Keypair::new().pubkey(), 0); + + // push a version to far in the future + ci.wallclock = timeout + 1; + let value = CrdsValue::ContactInfo(ci.clone()); + assert_eq!( + push.process_push_message(&mut crds, value, 0), + Err(CrdsGossipError::PushMessageTimeout) + ); + + // push a version to far in the past + ci.wallclock = 0; + let value = CrdsValue::ContactInfo(ci.clone()); + assert_eq!( + push.process_push_message(&mut crds, value, timeout + 1), + Err(CrdsGossipError::PushMessageTimeout) + ); + } + #[test] + fn test_process_push_update() { + let mut crds = Crds::default(); + let mut push = CrdsGossipPush::default(); + let mut ci = ContactInfo::new_localhost(Keypair::new().pubkey(), 0); + ci.wallclock = 0; + let value_old = CrdsValue::ContactInfo(ci.clone()); + + // push a new message + assert_eq!( + push.process_push_message(&mut crds, value_old.clone(), 0), + Ok(None) + ); + + // push an old version + ci.wallclock = 1; + let value = CrdsValue::ContactInfo(ci.clone()); + assert_eq!( + push.process_push_message(&mut crds, value, 0) + .unwrap() + .unwrap() + .value, + value_old + ); + } + #[test] + fn test_compute_need() { + assert_eq!(CrdsGossipPush::compute_need(30, 0, 10), 30); + assert_eq!(CrdsGossipPush::compute_need(30, 1, 10), 29); + assert_eq!(CrdsGossipPush::compute_need(30, 30, 10), 3); + assert_eq!(CrdsGossipPush::compute_need(30, 29, 10), 3); + } + #[test] + fn test_refresh_active_set() { + use logger; + logger::setup(); + let mut crds = Crds::default(); + let mut push = CrdsGossipPush::default(); + let value1 = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + + assert_eq!(crds.insert(value1.clone(), 0), Ok(None)); + push.refresh_push_active_set(&crds, Pubkey::default(), 1, 1); + + assert!(push.active_set.get(&value1.label().pubkey()).is_some()); + let value2 = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + assert!(push.active_set.get(&value2.label().pubkey()).is_none()); + assert_eq!(crds.insert(value2.clone(), 0), Ok(None)); + for _ in 0..30 { + push.refresh_push_active_set(&crds, Pubkey::default(), 1, 1); + if push.active_set.get(&value2.label().pubkey()).is_some() { + break; + } + } + assert!(push.active_set.get(&value2.label().pubkey()).is_some()); + + for _ in 0..push.num_active { + let value2 = + CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + assert_eq!(crds.insert(value2.clone(), 0), Ok(None)); + } + push.refresh_push_active_set(&crds, Pubkey::default(), 1, 1); + assert_eq!(push.active_set.len(), push.num_active); + } + #[test] + fn test_new_push_messages() { + let mut crds = Crds::default(); + let mut push = CrdsGossipPush::default(); + let peer = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + assert_eq!(crds.insert(peer.clone(), 0), Ok(None)); + push.refresh_push_active_set(&crds, Pubkey::default(), 1, 1); + + let new_msg = + CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + assert_eq!( + push.process_push_message(&mut crds, new_msg.clone(), 0), + Ok(None) + ); + assert_eq!(push.active_set.len(), 1); + assert_eq!( + push.new_push_messages(&crds, 0), + (vec![peer.label().pubkey()], vec![new_msg]) + ); + } + #[test] + fn test_process_prune() { + let mut crds = Crds::default(); + let mut push = CrdsGossipPush::default(); + let peer = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + assert_eq!(crds.insert(peer.clone(), 0), Ok(None)); + push.refresh_push_active_set(&crds, Pubkey::default(), 1, 1); + + let new_msg = + CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + assert_eq!( + push.process_push_message(&mut crds, new_msg.clone(), 0), + Ok(None) + ); + push.process_prune_msg(peer.label().pubkey(), &[new_msg.label().pubkey()]); + assert_eq!( + push.new_push_messages(&crds, 0), + (vec![peer.label().pubkey()], vec![]) + ); + } + #[test] + fn test_purge_old_pending_push_messages() { + let mut crds = Crds::default(); + let mut push = CrdsGossipPush::default(); + let peer = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); + assert_eq!(crds.insert(peer.clone(), 0), Ok(None)); + push.refresh_push_active_set(&crds, Pubkey::default(), 1, 1); + + let mut ci = ContactInfo::new_localhost(Keypair::new().pubkey(), 0); + ci.wallclock = 1; + let new_msg = CrdsValue::ContactInfo(ci.clone()); + assert_eq!( + push.process_push_message(&mut crds, new_msg.clone(), 1), + Ok(None) + ); + push.purge_old_pending_push_messages(&crds, 0); + assert_eq!( + push.new_push_messages(&crds, 0), + (vec![peer.label().pubkey()], vec![]) + ); + } + + #[test] + fn test_purge_old_pushed_once_messages() { + let mut crds = Crds::default(); + let mut push = CrdsGossipPush::default(); + let mut ci = ContactInfo::new_localhost(Keypair::new().pubkey(), 0); + ci.wallclock = 0; + let value = CrdsValue::ContactInfo(ci.clone()); + let label = value.label(); + // push a new message + assert_eq!( + push.process_push_message(&mut crds, value.clone(), 0), + Ok(None) + ); + assert_eq!(crds.lookup(&label), Some(&value)); + + // push it again + assert_eq!( + push.process_push_message(&mut crds, value.clone(), 0), + Err(CrdsGossipError::PushMessagePrune) + ); + + // purge the old pushed + push.purge_old_pushed_once_messages(1); + + // push it again + assert_eq!( + push.process_push_message(&mut crds, value.clone(), 0), + Err(CrdsGossipError::PushMessageOldVersion) + ); + } +} diff --git a/book/crds_traits_impls.rs b/book/crds_traits_impls.rs new file mode 100644 index 00000000000000..382a8d50f6b004 --- /dev/null +++ b/book/crds_traits_impls.rs @@ -0,0 +1,26 @@ +use bloom::BloomHashIndex; +use solana_sdk::hash::Hash; +use solana_sdk::pubkey::Pubkey; + +fn slice_hash(slice: &[u8], hash_index: u64) -> u64 { + let len = slice.len(); + assert!(len < 256); + let mut rv = 0u64; + for i in 0..8 { + let pos = (hash_index >> i) & 0xff; + rv |= u64::from(slice[pos as usize % len]) << i; + } + rv +} + +impl BloomHashIndex for Pubkey { + fn hash(&self, hash_index: u64) -> u64 { + slice_hash(self.as_ref(), hash_index) + } +} + +impl BloomHashIndex for Hash { + fn hash(&self, hash_index: u64) -> u64 { + slice_hash(self.as_ref(), hash_index) + } +} diff --git a/book/crds_value.rs b/book/crds_value.rs new file mode 100644 index 00000000000000..512d490a4105dd --- /dev/null +++ b/book/crds_value.rs @@ -0,0 +1,147 @@ +use contact_info::ContactInfo; +use solana_sdk::pubkey::Pubkey; +use std::fmt; +use transaction::Transaction; + +/// CrdsValue that is replicated across the cluster +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub enum CrdsValue { + /// * Merge Strategy - Latest wallclock is picked + ContactInfo(ContactInfo), + /// TODO, Votes need a height potentially in the userdata + /// * Merge Strategy - Latest height is picked + Vote(Vote), + /// * Merge Strategy - Latest wallclock is picked + LeaderId(LeaderId), +} + +#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)] +pub struct LeaderId { + pub id: Pubkey, + pub leader_id: Pubkey, + pub wallclock: u64, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct Vote { + pub transaction: Transaction, + pub height: u64, + pub wallclock: u64, +} + +/// Type of the replicated value +/// These are labels for values in a record that is assosciated with `Pubkey` +#[derive(PartialEq, Hash, Eq, Clone, Debug)] +pub enum CrdsValueLabel { + ContactInfo(Pubkey), + Vote(Pubkey), + LeaderId(Pubkey), +} + +impl fmt::Display for CrdsValueLabel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + CrdsValueLabel::ContactInfo(_) => write!(f, "ContactInfo({})", self.pubkey()), + CrdsValueLabel::Vote(_) => write!(f, "Vote({})", self.pubkey()), + CrdsValueLabel::LeaderId(_) => write!(f, "LeaderId({})", self.pubkey()), + } + } +} + +impl CrdsValueLabel { + pub fn pubkey(&self) -> Pubkey { + match self { + CrdsValueLabel::ContactInfo(p) => *p, + CrdsValueLabel::Vote(p) => *p, + CrdsValueLabel::LeaderId(p) => *p, + } + } +} + +impl CrdsValue { + /// Totally unsecure unverfiable wallclock of the node that generatd this message + /// Latest wallclock is always picked. + /// This is used to time out push messages. + pub fn wallclock(&self) -> u64 { + match self { + CrdsValue::ContactInfo(contact_info) => contact_info.wallclock, + CrdsValue::Vote(vote) => vote.wallclock, + CrdsValue::LeaderId(leader_id) => leader_id.wallclock, + } + } + pub fn label(&self) -> CrdsValueLabel { + match self { + CrdsValue::ContactInfo(contact_info) => CrdsValueLabel::ContactInfo(contact_info.id), + CrdsValue::Vote(vote) => CrdsValueLabel::Vote(vote.transaction.account_keys[0]), + CrdsValue::LeaderId(leader_id) => CrdsValueLabel::LeaderId(leader_id.id), + } + } + pub fn contact_info(&self) -> Option<&ContactInfo> { + match self { + CrdsValue::ContactInfo(contact_info) => Some(contact_info), + _ => None, + } + } + pub fn leader_id(&self) -> Option<&LeaderId> { + match self { + CrdsValue::LeaderId(leader_id) => Some(leader_id), + _ => None, + } + } + pub fn vote(&self) -> Option<&Vote> { + match self { + CrdsValue::Vote(vote) => Some(vote), + _ => None, + } + } + /// Return all the possible labels for a record identified by Pubkey. + pub fn record_labels(key: Pubkey) -> [CrdsValueLabel; 3] { + [ + CrdsValueLabel::ContactInfo(key), + CrdsValueLabel::Vote(key), + CrdsValueLabel::LeaderId(key), + ] + } +} +#[cfg(test)] +mod test { + use super::*; + use contact_info::ContactInfo; + use system_transaction::test_tx; + + #[test] + fn test_labels() { + let mut hits = [false; 3]; + // this method should cover all the possible labels + for v in &CrdsValue::record_labels(Pubkey::default()) { + match v { + CrdsValueLabel::ContactInfo(_) => hits[0] = true, + CrdsValueLabel::Vote(_) => hits[1] = true, + CrdsValueLabel::LeaderId(_) => hits[2] = true, + } + } + assert!(hits.iter().all(|x| *x)); + } + #[test] + fn test_keys_and_values() { + let v = CrdsValue::LeaderId(LeaderId::default()); + let key = v.clone().leader_id().unwrap().id; + assert_eq!(v.wallclock(), 0); + assert_eq!(v.label(), CrdsValueLabel::LeaderId(key)); + + let v = CrdsValue::ContactInfo(ContactInfo::default()); + assert_eq!(v.wallclock(), 0); + let key = v.clone().contact_info().unwrap().id; + assert_eq!(v.label(), CrdsValueLabel::ContactInfo(key)); + + let v = CrdsValue::Vote(Vote { + transaction: test_tx(), + height: 1, + wallclock: 0, + }); + assert_eq!(v.wallclock(), 0); + let key = v.clone().vote().unwrap().transaction.account_keys[0]; + assert_eq!(v.label(), CrdsValueLabel::Vote(key)); + } + +} diff --git a/book/css/chrome.css b/book/css/chrome.css new file mode 100644 index 00000000000000..82883e6b9a73a2 --- /dev/null +++ b/book/css/chrome.css @@ -0,0 +1,417 @@ +/* CSS for UI elements (a.k.a. chrome) */ + +@import 'variables.css'; + +::-webkit-scrollbar { + background: var(--bg); +} +::-webkit-scrollbar-thumb { + background: var(--scrollbar); +} + +#searchresults a, +.content a:link, +a:visited, +a > .hljs { + color: var(--links); +} + +/* Menu Bar */ + +#menu-bar { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 101; + margin: auto calc(0px - var(--page-padding)); +} +#menu-bar > #menu-bar-sticky-container { + display: flex; + flex-wrap: wrap; + background-color: var(--bg); + border-bottom-color: var(--bg); + border-bottom-width: 1px; + border-bottom-style: solid; +} +.js #menu-bar > #menu-bar-sticky-container { + transition: transform 0.3s; +} +#menu-bar.bordered > #menu-bar-sticky-container { + border-bottom-color: var(--table-border-color); +} +#menu-bar i, #menu-bar .icon-button { + position: relative; + padding: 0 8px; + z-index: 10; + line-height: 50px; + cursor: pointer; + transition: color 0.5s; +} +@media only screen and (max-width: 420px) { + #menu-bar i, #menu-bar .icon-button { + padding: 0 5px; + } +} + +.icon-button { + border: none; + background: none; + padding: 0; + color: inherit; +} +.icon-button i { + margin: 0; +} + +#print-button { + margin: 0 15px; +} + +html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-container { + transform: translateY(-60px); +} + +.left-buttons { + display: flex; + margin: 0 5px; +} +.no-js .left-buttons { + display: none; +} + +.menu-title { + display: inline-block; + font-weight: 200; + font-size: 20px; + line-height: 50px; + text-align: center; + margin: 0; + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.js .menu-title { + cursor: pointer; +} + +.menu-bar, +.menu-bar:visited, +.nav-chapters, +.nav-chapters:visited, +.mobile-nav-chapters, +.mobile-nav-chapters:visited, +.menu-bar .icon-button, +.menu-bar a i { + color: var(--icons); +} + +.menu-bar i:hover, +.menu-bar .icon-button:hover, +.nav-chapters:hover, +.mobile-nav-chapters i:hover { + color: var(--icons-hover); +} + +/* Nav Icons */ + +.nav-chapters { + font-size: 2.5em; + text-align: center; + text-decoration: none; + + position: fixed; + top: 50px; /* Height of menu-bar */ + bottom: 0; + margin: 0; + max-width: 150px; + min-width: 90px; + + display: flex; + justify-content: center; + align-content: center; + flex-direction: column; + + transition: color 0.5s; +} + +.nav-chapters:hover { text-decoration: none; } + +.nav-wrapper { + margin-top: 50px; + display: none; +} + +.mobile-nav-chapters { + font-size: 2.5em; + text-align: center; + text-decoration: none; + width: 90px; + border-radius: 5px; + background-color: var(--sidebar-bg); +} + +.previous { + float: left; +} + +.next { + float: right; + right: var(--page-padding); +} + +@media only screen and (max-width: 1080px) { + .nav-wide-wrapper { display: none; } + .nav-wrapper { display: block; } +} + +@media only screen and (max-width: 1380px) { + .sidebar-visible .nav-wide-wrapper { display: none; } + .sidebar-visible .nav-wrapper { display: block; } +} + +/* Inline code */ + +:not(pre) > .hljs { + display: inline-block; + vertical-align: middle; + padding: 0.1em 0.3em; + border-radius: 3px; + color: var(--inline-code-color); +} + +a:hover > .hljs { + text-decoration: underline; +} + +pre { + position: relative; +} +pre > .buttons { + position: absolute; + z-index: 100; + right: 5px; + top: 5px; + + color: var(--sidebar-fg); + cursor: pointer; +} +pre > .buttons :hover { + color: var(--sidebar-active); +} +pre > .buttons i { + margin-left: 8px; +} +pre > .buttons button { + color: inherit; + background: transparent; + border: none; + cursor: inherit; +} +pre > .result { + margin-top: 10px; +} + +/* Search */ + +#searchresults a { + text-decoration: none; +} + +mark { + border-radius: 2px; + padding: 0 3px 1px 3px; + margin: 0 -3px -1px -3px; + background-color: var(--search-mark-bg); + transition: background-color 300ms linear; + cursor: pointer; +} + +mark.fade-out { + background-color: rgba(0,0,0,0) !important; + cursor: auto; +} + +.searchbar-outer { + margin-left: auto; + margin-right: auto; + max-width: var(--content-max-width); +} + +#searchbar { + width: 100%; + margin: 5px auto 0px auto; + padding: 10px 16px; + transition: box-shadow 300ms ease-in-out; + border: 1px solid var(--searchbar-border-color); + border-radius: 3px; + background-color: var(--searchbar-bg); + color: var(--searchbar-fg); +} +#searchbar:focus, +#searchbar.active { + box-shadow: 0 0 3px var(--searchbar-shadow-color); +} + +.searchresults-header { + font-weight: bold; + font-size: 1em; + padding: 18px 0 0 5px; + color: var(--searchresults-header-fg); +} + +.searchresults-outer { + margin-left: auto; + margin-right: auto; + max-width: var(--content-max-width); + border-bottom: 1px dashed var(--searchresults-border-color); +} + +ul#searchresults { + list-style: none; + padding-left: 20px; +} +ul#searchresults li { + margin: 10px 0px; + padding: 2px; + border-radius: 2px; +} +ul#searchresults li.focus { + background-color: var(--searchresults-li-bg); +} +ul#searchresults span.teaser { + display: block; + clear: both; + margin: 5px 0 0 20px; + font-size: 0.8em; +} +ul#searchresults span.teaser em { + font-weight: bold; + font-style: normal; +} + +/* Sidebar */ + +.sidebar { + position: fixed; + left: 0; + top: 0; + bottom: 0; + width: var(--sidebar-width); + overflow-y: auto; + padding: 10px 10px; + font-size: 0.875em; + box-sizing: border-box; + -webkit-overflow-scrolling: touch; + overscroll-behavior-y: contain; + background-color: var(--sidebar-bg); + color: var(--sidebar-fg); +} +.js .sidebar { + transition: transform 0.3s; /* Animation: slide away */ +} +.sidebar code { + line-height: 2em; +} +.sidebar-hidden .sidebar { + transform: translateX(calc(0px - var(--sidebar-width))); +} +.sidebar::-webkit-scrollbar { + background: var(--sidebar-bg); +} +.sidebar::-webkit-scrollbar-thumb { + background: var(--scrollbar); +} + +.sidebar-visible .page-wrapper { + transform: translateX(var(--sidebar-width)); +} +@media only screen and (min-width: 620px) { + .sidebar-visible .page-wrapper { + transform: none; + margin-left: var(--sidebar-width); + } +} + +.chapter { + list-style: none outside none; + padding-left: 0; + line-height: 2.2em; +} +.chapter li { + color: var(--sidebar-non-existant); +} +.chapter li a { + color: var(--sidebar-fg); + display: block; + padding: 0; + text-decoration: none; +} +.chapter li a:hover { text-decoration: none } +.chapter li .active, +a:hover { + /* Animate color change */ + color: var(--sidebar-active); +} + +.spacer { + width: 100%; + height: 3px; + margin: 5px 0px; +} +.chapter .spacer { + background-color: var(--sidebar-spacer); +} + +@media (-moz-touch-enabled: 1), (pointer: coarse) { + .chapter li a { padding: 5px 0; } + .spacer { margin: 10px 0; } +} + +.section { + list-style: none outside none; + padding-left: 20px; + line-height: 1.9em; +} + +/* Theme Menu Popup */ + +.theme-popup { + position: absolute; + left: 10px; + top: 50px; + z-index: 1000; + border-radius: 4px; + font-size: 0.7em; + color: var(--fg); + background: var(--theme-popup-bg); + border: 1px solid var(--theme-popup-border); + margin: 0; + padding: 0; + list-style: none; + display: none; +} +.theme-popup .default { + color: var(--icons); +} +.theme-popup .theme { + width: 100%; + border: 0; + margin: 0; + padding: 2px 10px; + line-height: 25px; + white-space: nowrap; + text-align: left; + cursor: pointer; + color: inherit; + background: inherit; + font-size: inherit; +} +.theme-popup .theme:hover { + background-color: var(--theme-hover); +} +.theme-popup .theme:hover:first-child, +.theme-popup .theme:hover:last-child { + border-top-left-radius: inherit; + border-top-right-radius: inherit; +} diff --git a/book/css/general.css b/book/css/general.css new file mode 100644 index 00000000000000..aedfb332b7458b --- /dev/null +++ b/book/css/general.css @@ -0,0 +1,144 @@ +/* Base styles and content styles */ + +@import 'variables.css'; + +html { + font-family: "Open Sans", sans-serif; + color: var(--fg); + background-color: var(--bg); + text-size-adjust: none; +} + +body { + margin: 0; + font-size: 1rem; + overflow-x: hidden; +} + +code { + font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace; + font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */ +} + +.left { float: left; } +.right { float: right; } +.hidden { display: none; } +.play-button.hidden { display: none; } + +h2, h3 { margin-top: 2.5em; } +h4, h5 { margin-top: 2em; } + +.header + .header h3, +.header + .header h4, +.header + .header h5 { + margin-top: 1em; +} + +a.header:target h1:before, +a.header:target h2:before, +a.header:target h3:before, +a.header:target h4:before { + display: inline-block; + content: "»"; + margin-left: -30px; + width: 30px; +} + +.page { + outline: 0; + padding: 0 var(--page-padding); +} +.page-wrapper { + box-sizing: border-box; +} +.js .page-wrapper { + transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ +} + +.content { + overflow-y: auto; + padding: 0 15px; + padding-bottom: 50px; +} +.content main { + margin-left: auto; + margin-right: auto; + max-width: var(--content-max-width); +} +.content a { text-decoration: none; } +.content a:hover { text-decoration: underline; } +.content img { max-width: 100%; } +.content .header:link, +.content .header:visited { + color: var(--fg); +} +.content .header:link, +.content .header:visited:hover { + text-decoration: none; +} + +table { + margin: 0 auto; + border-collapse: collapse; +} +table td { + padding: 3px 20px; + border: 1px var(--table-border-color) solid; +} +table thead { + background: var(--table-header-bg); +} +table thead td { + font-weight: 700; + border: none; +} +table thead tr { + border: 1px var(--table-header-bg) solid; +} +/* Alternate background colors for rows */ +table tbody tr:nth-child(2n) { + background: var(--table-alternate-bg); +} + + +blockquote { + margin: 20px 0; + padding: 0 20px; + color: var(--fg); + background-color: var(--quote-bg); + border-top: .1em solid var(--quote-border); + border-bottom: .1em solid var(--quote-border); +} + + +:not(.footnote-definition) + .footnote-definition, +.footnote-definition + :not(.footnote-definition) { + margin-top: 2em; +} +.footnote-definition { + font-size: 0.9em; + margin: 0.5em 0; +} +.footnote-definition p { + display: inline; +} + +.tooltiptext { + position: absolute; + visibility: hidden; + color: #fff; + background-color: #333; + transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ + left: -8px; /* Half of the width of the icon */ + top: -35px; + font-size: 0.8em; + text-align: center; + border-radius: 6px; + padding: 5px 8px; + margin: 5px; + z-index: 1000; +} +.tooltipped .tooltiptext { + visibility: visible; +} + \ No newline at end of file diff --git a/book/css/print.css b/book/css/print.css new file mode 100644 index 00000000000000..5e690f7559943d --- /dev/null +++ b/book/css/print.css @@ -0,0 +1,54 @@ + +#sidebar, +#menu-bar, +.nav-chapters, +.mobile-nav-chapters { + display: none; +} + +#page-wrapper.page-wrapper { + transform: none; + margin-left: 0px; + overflow-y: initial; +} + +#content { + max-width: none; + margin: 0; + padding: 0; +} + +.page { + overflow-y: initial; +} + +code { + background-color: #666666; + border-radius: 5px; + + /* Force background to be printed in Chrome */ + -webkit-print-color-adjust: exact; +} + +pre > .buttons { + z-index: 2; +} + +a, a:visited, a:active, a:hover { + color: #4183c4; + text-decoration: none; +} + +h1, h2, h3, h4, h5, h6 { + page-break-inside: avoid; + page-break-after: avoid; +} + +pre, code { + page-break-inside: avoid; + white-space: pre-wrap; +} + +.fa { + display: none !important; +} diff --git a/book/css/variables.css b/book/css/variables.css new file mode 100644 index 00000000000000..29daa07293793e --- /dev/null +++ b/book/css/variables.css @@ -0,0 +1,210 @@ + +/* Globals */ + +:root { + --sidebar-width: 300px; + --page-padding: 15px; + --content-max-width: 750px; +} + +/* Themes */ + +.ayu { + --bg: hsl(210, 25%, 8%); + --fg: #c5c5c5; + + --sidebar-bg: #14191f; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #5c6773; + --sidebar-active: #ffb454; + --sidebar-spacer: #2d334f; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #b7b9cc; + + --links: #0096cf; + + --inline-code-color: #ffb454; + + --theme-popup-bg: #14191f; + --theme-popup-border: #5c6773; + --theme-hover: #191f26; + + --quote-bg: hsl(226, 15%, 17%); + --quote-border: hsl(226, 15%, 22%); + + --table-border-color: hsl(210, 25%, 13%); + --table-header-bg: hsl(210, 25%, 28%); + --table-alternate-bg: hsl(210, 25%, 11%); + + --searchbar-border-color: #848484; + --searchbar-bg: #424242; + --searchbar-fg: #fff; + --searchbar-shadow-color: #d4c89f; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #252932; + --search-mark-bg: #e3b171; +} + +.coal { + --bg: hsl(200, 7%, 8%); + --fg: #98a3ad; + + --sidebar-bg: #292c2f; + --sidebar-fg: #a1adb8; + --sidebar-non-existant: #505254; + --sidebar-active: #3473ad; + --sidebar-spacer: #393939; + + --scrollbar: var(--sidebar-fg); + + --icons: #43484d; + --icons-hover: #b3c0cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6;; + + --theme-popup-bg: #141617; + --theme-popup-border: #43484d; + --theme-hover: #1f2124; + + --quote-bg: hsl(234, 21%, 18%); + --quote-border: hsl(234, 21%, 23%); + + --table-border-color: hsl(200, 7%, 13%); + --table-header-bg: hsl(200, 7%, 28%); + --table-alternate-bg: hsl(200, 7%, 11%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #b7b7b7; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #98a3ad; + --searchresults-li-bg: #2b2b2f; + --search-mark-bg: #355c7d; +} + +.light { + --bg: hsl(0, 0%, 100%); + --fg: #333333; + + --sidebar-bg: #fafafa; + --sidebar-fg: #364149; + --sidebar-non-existant: #aaaaaa; + --sidebar-active: #008cff; + --sidebar-spacer: #f4f4f4; + + --scrollbar: #cccccc; + + --icons: #cccccc; + --icons-hover: #333333; + + --links: #4183c4; + + --inline-code-color: #6e6b5e; + + --theme-popup-bg: #fafafa; + --theme-popup-border: #cccccc; + --theme-hover: #e6e6e6; + + --quote-bg: hsl(197, 37%, 96%); + --quote-border: hsl(197, 37%, 91%); + + --table-border-color: hsl(0, 0%, 95%); + --table-header-bg: hsl(0, 0%, 80%); + --table-alternate-bg: hsl(0, 0%, 97%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #fafafa; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #e4f2fe; + --search-mark-bg: #a2cff5; +} + +.navy { + --bg: hsl(226, 23%, 11%); + --fg: #bcbdd0; + + --sidebar-bg: #282d3f; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #505274; + --sidebar-active: #2b79a2; + --sidebar-spacer: #2d334f; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #b7b9cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6;; + + --theme-popup-bg: #161923; + --theme-popup-border: #737480; + --theme-hover: #282e40; + + --quote-bg: hsl(226, 15%, 17%); + --quote-border: hsl(226, 15%, 22%); + + --table-border-color: hsl(226, 23%, 16%); + --table-header-bg: hsl(226, 23%, 31%); + --table-alternate-bg: hsl(226, 23%, 14%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #aeaec6; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #5f5f71; + --searchresults-border-color: #5c5c68; + --searchresults-li-bg: #242430; + --search-mark-bg: #a2cff5; +} + +.rust { + --bg: hsl(60, 9%, 87%); + --fg: #262625; + + --sidebar-bg: #3b2e2a; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #505254; + --sidebar-active: #e69f67; + --sidebar-spacer: #45373a; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #262625; + + --links: #2b79a2; + + --inline-code-color: #6e6b5e; + + --theme-popup-bg: #e1e1db; + --theme-popup-border: #b38f6b; + --theme-hover: #99908a; + + --quote-bg: hsl(60, 5%, 75%); + --quote-border: hsl(60, 5%, 70%); + + --table-border-color: hsl(60, 9%, 82%); + --table-header-bg: #b3a497; + --table-alternate-bg: hsl(60, 9%, 84%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #fafafa; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #dec2a2; + --search-mark-bg: #e69f67; +} diff --git a/book/db_ledger.rs b/book/db_ledger.rs new file mode 100644 index 00000000000000..15d8183d45a7b9 --- /dev/null +++ b/book/db_ledger.rs @@ -0,0 +1,634 @@ +//! The `ledger` module provides functions for parallel verification of the +//! Proof of History ledger as well as iterative read, append write, and random +//! access read to a persistent file-based ledger. + +use bincode::{deserialize, serialize}; +use byteorder::{ByteOrder, LittleEndian, ReadBytesExt}; +use entry::Entry; +use ledger::Block; +use packet::{Blob, SharedBlob, BLOB_HEADER_SIZE}; +use result::{Error, Result}; +use rocksdb::{ColumnFamily, Options, WriteBatch, DB}; +use serde::de::DeserializeOwned; +use serde::Serialize; +use std::io; + +pub const DB_LEDGER_DIRECTORY: &str = "db_ledger"; + +#[derive(Debug, PartialEq, Eq)] +pub enum DbLedgerError { + BlobForIndexExists, + InvalidBlobData, +} + +pub trait LedgerColumnFamily { + type ValueType: DeserializeOwned + Serialize; + + fn get(&self, db: &DB, key: &[u8]) -> Result> { + let data_bytes = db.get_cf(self.handle(db), key)?; + + if let Some(raw) = data_bytes { + let result: Self::ValueType = deserialize(&raw)?; + Ok(Some(result)) + } else { + Ok(None) + } + } + + fn get_bytes(&self, db: &DB, key: &[u8]) -> Result>> { + let data_bytes = db.get_cf(self.handle(db), key)?; + Ok(data_bytes.map(|x| x.to_vec())) + } + + fn put_bytes(&self, db: &DB, key: &[u8], serialized_value: &[u8]) -> Result<()> { + db.put_cf(self.handle(db), &key, &serialized_value)?; + Ok(()) + } + + fn put(&self, db: &DB, key: &[u8], value: &Self::ValueType) -> Result<()> { + let serialized = serialize(value)?; + db.put_cf(self.handle(db), &key, &serialized)?; + Ok(()) + } + + fn delete(&self, db: &DB, key: &[u8]) -> Result<()> { + db.delete_cf(self.handle(db), &key)?; + Ok(()) + } + + fn handle(&self, db: &DB) -> ColumnFamily; +} + +pub trait LedgerColumnFamilyRaw { + fn get(&self, db: &DB, key: &[u8]) -> Result>> { + let data_bytes = db.get_cf(self.handle(db), key)?; + Ok(data_bytes.map(|x| x.to_vec())) + } + + fn put(&self, db: &DB, key: &[u8], serialized_value: &[u8]) -> Result<()> { + db.put_cf(self.handle(db), &key, &serialized_value)?; + Ok(()) + } + + fn delete(&self, db: &DB, key: &[u8]) -> Result<()> { + db.delete_cf(self.handle(db), &key)?; + Ok(()) + } + + fn handle(&self, db: &DB) -> ColumnFamily; +} + +#[derive(Debug, Default, Deserialize, Serialize, Eq, PartialEq)] +// The Meta column family +pub struct SlotMeta { + // The total number of consecutive blob starting from index 0 + // we have received for this slot. + pub consumed: u64, + // The entry height of the highest blob received for this slot. + pub received: u64, +} + +impl SlotMeta { + fn new() -> Self { + SlotMeta { + consumed: 0, + received: 0, + } + } +} + +#[derive(Default)] +pub struct MetaCf {} + +impl MetaCf { + pub fn key(slot_height: u64) -> Vec { + let mut key = vec![0u8; 8]; + LittleEndian::write_u64(&mut key[0..8], slot_height); + key + } +} + +impl LedgerColumnFamily for MetaCf { + type ValueType = SlotMeta; + + fn handle(&self, db: &DB) -> ColumnFamily { + db.cf_handle(META_CF).unwrap() + } +} + +// The data column family +#[derive(Default)] +pub struct DataCf {} + +impl DataCf { + pub fn get_by_slot_index( + &self, + db: &DB, + slot_height: u64, + index: u64, + ) -> Result>> { + let key = Self::key(slot_height, index); + self.get(db, &key) + } + + pub fn put_by_slot_index( + &self, + db: &DB, + slot_height: u64, + index: u64, + serialized_value: &[u8], + ) -> Result<()> { + let key = Self::key(slot_height, index); + self.put(db, &key, serialized_value) + } + + pub fn key(slot_height: u64, index: u64) -> Vec { + let mut key = vec![0u8; 16]; + LittleEndian::write_u64(&mut key[0..8], slot_height); + LittleEndian::write_u64(&mut key[8..16], index); + key + } + + pub fn slot_height_from_key(key: &[u8]) -> Result { + let mut rdr = io::Cursor::new(&key[0..8]); + let height = rdr.read_u64::()?; + Ok(height) + } + + pub fn index_from_key(key: &[u8]) -> Result { + let mut rdr = io::Cursor::new(&key[8..16]); + let index = rdr.read_u64::()?; + Ok(index) + } +} + +impl LedgerColumnFamilyRaw for DataCf { + fn handle(&self, db: &DB) -> ColumnFamily { + db.cf_handle(DATA_CF).unwrap() + } +} + +// The erasure column family +#[derive(Default)] +pub struct ErasureCf {} + +impl ErasureCf { + pub fn get_by_slot_index( + &self, + db: &DB, + slot_height: u64, + index: u64, + ) -> Result>> { + let key = Self::key(slot_height, index); + self.get(db, &key) + } + + pub fn put_by_slot_index( + &self, + db: &DB, + slot_height: u64, + index: u64, + serialized_value: &[u8], + ) -> Result<()> { + let key = Self::key(slot_height, index); + self.put(db, &key, serialized_value) + } + + pub fn key(slot_height: u64, index: u64) -> Vec { + DataCf::key(slot_height, index) + } + + pub fn index_from_key(key: &[u8]) -> Result { + DataCf::index_from_key(key) + } +} + +impl LedgerColumnFamilyRaw for ErasureCf { + fn handle(&self, db: &DB) -> ColumnFamily { + db.cf_handle(ERASURE_CF).unwrap() + } +} + +// ledger window +pub struct DbLedger { + // Underlying database is automatically closed in the Drop implementation of DB + pub db: DB, + pub meta_cf: MetaCf, + pub data_cf: DataCf, + pub erasure_cf: ErasureCf, +} + +// TODO: Once we support a window that knows about different leader +// slots, change functions where this is used to take slot height +// as a variable argument +pub const DEFAULT_SLOT_HEIGHT: u64 = 0; +// Column family for metadata about a leader slot +pub const META_CF: &str = "meta"; +// Column family for the data in a leader slot +pub const DATA_CF: &str = "data"; +// Column family for erasure data +pub const ERASURE_CF: &str = "erasure"; + +impl DbLedger { + // Opens a Ledger in directory, provides "infinite" window of blobs + pub fn open(ledger_path: &str) -> Result { + // Use default database options + let mut options = Options::default(); + options.create_if_missing(true); + options.create_missing_column_families(true); + + // Column family names + let cfs = vec![META_CF, DATA_CF, ERASURE_CF]; + + // Open the database + let db = DB::open_cf(&options, ledger_path, &cfs)?; + + // Create the metadata column family + let meta_cf = MetaCf::default(); + + // Create the data column family + let data_cf = DataCf::default(); + + // Create the erasure column family + let erasure_cf = ErasureCf::default(); + + Ok(DbLedger { + db, + meta_cf, + data_cf, + erasure_cf, + }) + } + + pub fn write_shared_blobs(&mut self, slot: u64, shared_blobs: &[SharedBlob]) -> Result<()> { + let blob_locks: Vec<_> = shared_blobs.iter().map(|b| b.read().unwrap()).collect(); + let blobs: Vec<&Blob> = blob_locks.iter().map(|b| &**b).collect(); + self.write_blobs(slot, &blobs) + } + + pub fn write_blobs<'a, I>(&mut self, slot: u64, blobs: I) -> Result<()> + where + I: IntoIterator, + { + for blob in blobs.into_iter() { + let index = blob.index()?; + let key = DataCf::key(slot, index); + self.insert_data_blob(&key, blob)?; + } + Ok(()) + } + + pub fn write_entries(&mut self, slot: u64, entries: &[Entry]) -> Result<()> { + let shared_blobs = entries.to_blobs(); + let blob_locks: Vec<_> = shared_blobs.iter().map(|b| b.read().unwrap()).collect(); + let blobs: Vec<&Blob> = blob_locks.iter().map(|b| &**b).collect(); + self.write_blobs(slot, &blobs)?; + Ok(()) + } + + pub fn insert_data_blob(&self, key: &[u8], new_blob: &Blob) -> Result> { + let slot_height = DataCf::slot_height_from_key(key)?; + let meta_key = MetaCf::key(slot_height); + + let mut should_write_meta = false; + + let mut meta = { + if let Some(meta) = self.db.get_cf(self.meta_cf.handle(&self.db), &meta_key)? { + deserialize(&meta)? + } else { + should_write_meta = true; + SlotMeta::new() + } + }; + + let mut index = DataCf::index_from_key(key)?; + + // TODO: Handle if leader sends different blob for same index when the index > consumed + // The old window implementation would just replace that index. + if index < meta.consumed { + return Err(Error::DbLedgerError(DbLedgerError::BlobForIndexExists)); + } + + // Index is zero-indexed, while the "received" height starts from 1, + // so received = index + 1 for the same blob. + if index >= meta.received { + meta.received = index + 1; + should_write_meta = true; + } + + let mut consumed_queue = vec![]; + + if meta.consumed == index { + // Add the new blob to the consumed queue + let serialized_entry_data = + &new_blob.data[BLOB_HEADER_SIZE..BLOB_HEADER_SIZE + new_blob.size()?]; + // Verify entries can actually be reconstructed + let entry: Entry = deserialize(serialized_entry_data) + .expect("Blob made it past validation, so must be deserializable at this point"); + should_write_meta = true; + meta.consumed += 1; + consumed_queue.push(entry); + // Find the next consecutive block of blobs. + // TODO: account for consecutive blocks that + // span multiple slots + loop { + index += 1; + let key = DataCf::key(slot_height, index); + if let Some(blob_data) = self.data_cf.get(&self.db, &key)? { + let serialized_entry_data = &blob_data[BLOB_HEADER_SIZE..]; + let entry: Entry = deserialize(serialized_entry_data) + .expect("Ledger should only contain well formed data"); + consumed_queue.push(entry); + meta.consumed += 1; + } else { + break; + } + } + } + + // Commit Step: Atomic write both the metadata and the data + let mut batch = WriteBatch::default(); + if should_write_meta { + batch.put_cf(self.meta_cf.handle(&self.db), &meta_key, &serialize(&meta)?)?; + } + + let serialized_blob_data = &new_blob.data[..BLOB_HEADER_SIZE + new_blob.size()?]; + batch.put_cf(self.data_cf.handle(&self.db), key, serialized_blob_data)?; + self.db.write(batch)?; + Ok(consumed_queue) + } + + // Fill 'buf' with num_blobs or most number of consecutive + // whole blobs that fit into buf.len() + // + // Return tuple of (number of blob read, total size of blobs read) + pub fn get_blob_bytes( + &mut self, + start_index: u64, + num_blobs: u64, + buf: &mut [u8], + ) -> Result<(u64, u64)> { + let start_key = DataCf::key(DEFAULT_SLOT_HEIGHT, start_index); + let mut db_iterator = self.db.raw_iterator_cf(self.data_cf.handle(&self.db))?; + db_iterator.seek(&start_key); + let mut total_blobs = 0; + let mut total_current_size = 0; + for expected_index in start_index..start_index + num_blobs { + if !db_iterator.valid() { + if expected_index == start_index { + return Err(Error::IO(io::Error::new( + io::ErrorKind::NotFound, + "Blob at start_index not found", + ))); + } else { + break; + } + } + + // Check key is the next sequential key based on + // blob index + let key = &db_iterator.key().expect("Expected valid key"); + let index = DataCf::index_from_key(key)?; + if index != expected_index { + break; + } + + // Get the blob data + let value = &db_iterator.value(); + + if value.is_none() { + break; + } + + let value = value.as_ref().unwrap(); + let blob_data_len = value.len(); + + if total_current_size + blob_data_len > buf.len() { + break; + } + + buf[total_current_size..total_current_size + value.len()].copy_from_slice(value); + total_current_size += blob_data_len; + total_blobs += 1; + + // TODO: Change this logic to support looking for data + // that spans multiple leader slots, once we support + // a window that knows about different leader slots + db_iterator.next(); + } + + Ok((total_blobs, total_current_size as u64)) + } +} + +pub fn write_entries_to_ledger(ledger_paths: &[String], entries: &[Entry]) { + for ledger_path in ledger_paths { + let mut db_ledger = + DbLedger::open(ledger_path).expect("Expected to be able to open database ledger"); + db_ledger + .write_entries(DEFAULT_SLOT_HEIGHT, &entries) + .expect("Expected successful write of genesis entries"); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ledger::{get_tmp_ledger_path, make_tiny_test_entries, Block}; + use rocksdb::{Options, DB}; + + #[test] + fn test_put_get_simple() { + let ledger_path = get_tmp_ledger_path("test_put_get_simple"); + let ledger = DbLedger::open(&ledger_path).unwrap(); + + // Test meta column family + let meta = SlotMeta::new(); + let meta_key = MetaCf::key(0); + ledger.meta_cf.put(&ledger.db, &meta_key, &meta).unwrap(); + let result = ledger + .meta_cf + .get(&ledger.db, &meta_key) + .unwrap() + .expect("Expected meta object to exist"); + + assert_eq!(result, meta); + + // Test erasure column family + let erasure = vec![1u8; 16]; + let erasure_key = ErasureCf::key(DEFAULT_SLOT_HEIGHT, 0); + ledger + .erasure_cf + .put(&ledger.db, &erasure_key, &erasure) + .unwrap(); + + let result = ledger + .erasure_cf + .get(&ledger.db, &erasure_key) + .unwrap() + .expect("Expected erasure object to exist"); + + assert_eq!(result, erasure); + + // Test data column family + let data = vec![2u8; 16]; + let data_key = DataCf::key(DEFAULT_SLOT_HEIGHT, 0); + ledger.data_cf.put(&ledger.db, &data_key, &data).unwrap(); + + let result = ledger + .data_cf + .get(&ledger.db, &data_key) + .unwrap() + .expect("Expected data object to exist"); + + assert_eq!(result, data); + + // Destroying database without closing it first is undefined behavior + drop(ledger); + DB::destroy(&Options::default(), &ledger_path) + .expect("Expected successful database destruction"); + } + + #[test] + fn test_get_blobs_bytes() { + let shared_blobs = make_tiny_test_entries(10).to_blobs(); + let blob_locks: Vec<_> = shared_blobs.iter().map(|b| b.read().unwrap()).collect(); + let blobs: Vec<&Blob> = blob_locks.iter().map(|b| &**b).collect(); + + let ledger_path = get_tmp_ledger_path("test_get_blobs_bytes"); + let mut ledger = DbLedger::open(&ledger_path).unwrap(); + ledger.write_blobs(DEFAULT_SLOT_HEIGHT, &blobs).unwrap(); + + let mut buf = [0; 1024]; + let (num_blobs, bytes) = ledger.get_blob_bytes(0, 1, &mut buf).unwrap(); + let bytes = bytes as usize; + assert_eq!(num_blobs, 1); + { + let blob_data = &buf[..bytes]; + assert_eq!(blob_data, &blobs[0].data[..bytes]); + } + + let (num_blobs, bytes2) = ledger.get_blob_bytes(0, 2, &mut buf).unwrap(); + let bytes2 = bytes2 as usize; + assert_eq!(num_blobs, 2); + assert!(bytes2 > bytes); + { + let blob_data_1 = &buf[..bytes]; + assert_eq!(blob_data_1, &blobs[0].data[..bytes]); + + let blob_data_2 = &buf[bytes..bytes2]; + assert_eq!(blob_data_2, &blobs[1].data[..bytes2 - bytes]); + } + + // buf size part-way into blob[1], should just return blob[0] + let mut buf = vec![0; bytes + 1]; + let (num_blobs, bytes3) = ledger.get_blob_bytes(0, 2, &mut buf).unwrap(); + assert_eq!(num_blobs, 1); + let bytes3 = bytes3 as usize; + assert_eq!(bytes3, bytes); + + let mut buf = vec![0; bytes2 - 1]; + let (num_blobs, bytes4) = ledger.get_blob_bytes(0, 2, &mut buf).unwrap(); + assert_eq!(num_blobs, 1); + let bytes4 = bytes4 as usize; + assert_eq!(bytes4, bytes); + + let mut buf = vec![0; bytes * 2]; + let (num_blobs, bytes6) = ledger.get_blob_bytes(9, 1, &mut buf).unwrap(); + assert_eq!(num_blobs, 1); + let bytes6 = bytes6 as usize; + + { + let blob_data = &buf[..bytes6]; + assert_eq!(blob_data, &blobs[9].data[..bytes6]); + } + + // Read out of range + assert!(ledger.get_blob_bytes(20, 2, &mut buf).is_err()); + + // Destroying database without closing it first is undefined behavior + drop(ledger); + DB::destroy(&Options::default(), &ledger_path) + .expect("Expected successful database destruction"); + } + + #[test] + fn test_insert_data_blobs_basic() { + let entries = make_tiny_test_entries(2); + let shared_blobs = entries.to_blobs(); + let blob_locks: Vec<_> = shared_blobs.iter().map(|b| b.read().unwrap()).collect(); + let blobs: Vec<&Blob> = blob_locks.iter().map(|b| &**b).collect(); + + let ledger_path = get_tmp_ledger_path("test_insert_data_blobs_basic"); + let ledger = DbLedger::open(&ledger_path).unwrap(); + + // Insert second blob, we're missing the first blob, so should return nothing + let result = ledger + .insert_data_blob(&DataCf::key(DEFAULT_SLOT_HEIGHT, 1), blobs[1]) + .unwrap(); + + assert!(result.len() == 0); + let meta = ledger + .meta_cf + .get(&ledger.db, &MetaCf::key(DEFAULT_SLOT_HEIGHT)) + .unwrap() + .expect("Expected new metadata object to be created"); + assert!(meta.consumed == 0 && meta.received == 2); + + // Insert first blob, check for consecutive returned entries + let result = ledger + .insert_data_blob(&DataCf::key(DEFAULT_SLOT_HEIGHT, 0), blobs[0]) + .unwrap(); + + assert_eq!(result, entries); + + let meta = ledger + .meta_cf + .get(&ledger.db, &MetaCf::key(DEFAULT_SLOT_HEIGHT)) + .unwrap() + .expect("Expected new metadata object to exist"); + assert!(meta.consumed == 2 && meta.received == 2); + + // Destroying database without closing it first is undefined behavior + drop(ledger); + DB::destroy(&Options::default(), &ledger_path) + .expect("Expected successful database destruction"); + } + + #[test] + fn test_insert_data_blobs_multiple() { + let num_blobs = 10; + let entries = make_tiny_test_entries(num_blobs); + let shared_blobs = entries.to_blobs(); + let blob_locks: Vec<_> = shared_blobs.iter().map(|b| b.read().unwrap()).collect(); + let blobs: Vec<&Blob> = blob_locks.iter().map(|b| &**b).collect(); + + let ledger_path = get_tmp_ledger_path("test_insert_data_blobs_multiple"); + let ledger = DbLedger::open(&ledger_path).unwrap(); + + // Insert first blob, check for consecutive returned blobs + for i in (0..num_blobs).rev() { + let result = ledger + .insert_data_blob(&DataCf::key(DEFAULT_SLOT_HEIGHT, i as u64), blobs[i]) + .unwrap(); + + let meta = ledger + .meta_cf + .get(&ledger.db, &MetaCf::key(DEFAULT_SLOT_HEIGHT)) + .unwrap() + .expect("Expected metadata object to exist"); + if i != 0 { + assert_eq!(result.len(), 0); + assert!(meta.consumed == 0 && meta.received == num_blobs as u64); + } else { + assert_eq!(result, entries); + assert!(meta.consumed == num_blobs as u64 && meta.received == num_blobs as u64); + } + } + + // Destroying database without closing it first is undefined behavior + drop(ledger); + DB::destroy(&Options::default(), &ledger_path) + .expect("Expected successful database destruction"); + } +} diff --git a/book/db_window.rs b/book/db_window.rs new file mode 100644 index 00000000000000..caec3cc06aa812 --- /dev/null +++ b/book/db_window.rs @@ -0,0 +1,578 @@ +//! Set of functions for emulating windowing functions from a database ledger implementation +use cluster_info::ClusterInfo; +use counter::Counter; +use db_ledger::*; +use entry::Entry; +use leader_scheduler::LeaderScheduler; +use log::Level; +use packet::{SharedBlob, BLOB_HEADER_SIZE}; +use result::Result; +use rocksdb::DBRawIterator; +use solana_metrics::{influxdb, submit}; +use solana_sdk::pubkey::Pubkey; +use std::cmp; +use std::net::SocketAddr; +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, RwLock}; +use streamer::BlobSender; + +pub fn repair( + slot: u64, + db_ledger: &DbLedger, + cluster_info: &Arc>, + id: &Pubkey, + times: usize, + tick_height: u64, + max_entry_height: u64, + leader_scheduler_option: &Arc>, +) -> Result)>> { + let rcluster_info = cluster_info.read().unwrap(); + let mut is_next_leader = false; + let meta = db_ledger.meta_cf.get(&db_ledger.db, &MetaCf::key(slot))?; + if meta.is_none() { + return Ok(vec![]); + } + let meta = meta.unwrap(); + + let consumed = meta.consumed; + let received = meta.received; + + // Repair should only be called when received > consumed, enforced in window_service + assert!(received > consumed); + { + let ls_lock = leader_scheduler_option.read().unwrap(); + if !ls_lock.use_only_bootstrap_leader { + // Calculate the next leader rotation height and check if we are the leader + if let Some(next_leader_rotation_height) = ls_lock.max_height_for_leader(tick_height) { + match ls_lock.get_scheduled_leader(next_leader_rotation_height) { + Some((leader_id, _)) if leader_id == *id => is_next_leader = true, + // In the case that we are not in the current scope of the leader schedule + // window then either: + // + // 1) The replicate stage hasn't caught up to the "consumed" entries we sent, + // in which case it will eventually catch up + // + // 2) We are on the border between seed_rotation_intervals, so the + // schedule won't be known until the entry on that cusp is received + // by the replicate stage (which comes after this stage). Hence, the next + // leader at the beginning of that next epoch will not know they are the + // leader until they receive that last "cusp" entry. The leader also won't ask for repairs + // for that entry because "is_next_leader" won't be set here. In this case, + // everybody will be blocking waiting for that "cusp" entry instead of repairing, + // until the leader hits "times" >= the max times in calculate_max_repair_entry_height(). + // The impact of this, along with the similar problem from broadcast for the transitioning + // leader, can be observed in the multinode test, test_full_leader_validator_network(), + None => (), + _ => (), + } + } + } + } + + let num_peers = rcluster_info.tvu_peers().len() as u64; + + // Check if there's a max_entry_height limitation + let max_repair_entry_height = if max_entry_height == 0 { + calculate_max_repair_entry_height(num_peers, consumed, received, times, is_next_leader) + } else { + max_entry_height + 2 + }; + + let idxs = find_missing_data_indexes(slot, db_ledger, consumed, max_repair_entry_height - 1); + + let reqs: Vec<_> = idxs + .into_iter() + .filter_map(|pix| rcluster_info.window_index_request(pix).ok()) + .collect(); + + drop(rcluster_info); + + inc_new_counter_info!("streamer-repair_window-repair", reqs.len()); + + if log_enabled!(Level::Trace) { + trace!( + "{}: repair_window counter times: {} consumed: {} received: {} max_repair_entry_height: {} missing: {}", + id, + times, + consumed, + received, + max_repair_entry_height, + reqs.len() + ); + for (to, _) in &reqs { + trace!("{}: repair_window request to {}", id, to); + } + } + + Ok(reqs) +} + +// Given a start and end entry index, find all the missing +// indexes in the ledger in the range [start_index, end_index) +pub fn find_missing_indexes( + db_iterator: &mut DBRawIterator, + slot: u64, + start_index: u64, + end_index: u64, + key: &Fn(u64, u64) -> Vec, + index_from_key: &Fn(&[u8]) -> Result, +) -> Vec { + if start_index >= end_index { + return vec![]; + } + + let mut missing_indexes = vec![]; + + // Seek to the first blob with index >= start_index + db_iterator.seek(&key(slot, start_index)); + + // The index of the first missing blob in the slot + let mut prev_index = start_index; + loop { + if !db_iterator.valid() { + break; + } + let current_key = db_iterator.key().expect("Expect a valid key"); + let current_index = + index_from_key(¤t_key).expect("Expect to be able to parse index from valid key"); + let upper_index = cmp::min(current_index, end_index); + for i in prev_index..upper_index { + missing_indexes.push(i); + } + if current_index >= end_index { + break; + } + + prev_index = current_index + 1; + db_iterator.next(); + } + + missing_indexes +} + +pub fn find_missing_data_indexes( + slot: u64, + db_ledger: &DbLedger, + start_index: u64, + end_index: u64, +) -> Vec { + let mut db_iterator = db_ledger + .db + .raw_iterator_cf(db_ledger.data_cf.handle(&db_ledger.db)) + .expect("Expected to be able to open database iterator"); + + find_missing_indexes( + &mut db_iterator, + slot, + start_index, + end_index, + &DataCf::key, + &DataCf::index_from_key, + ) +} + +pub fn find_missing_coding_indexes( + slot: u64, + db_ledger: &DbLedger, + start_index: u64, + end_index: u64, +) -> Vec { + let mut db_iterator = db_ledger + .db + .raw_iterator_cf(db_ledger.erasure_cf.handle(&db_ledger.db)) + .expect("Expected to be able to open database iterator"); + + find_missing_indexes( + &mut db_iterator, + slot, + start_index, + end_index, + &ErasureCf::key, + &ErasureCf::index_from_key, + ) +} + +pub fn retransmit_all_leader_blocks( + dq: &[SharedBlob], + leader_scheduler: &LeaderScheduler, + retransmit: &BlobSender, +) -> Result<()> { + let mut retransmit_queue: Vec = Vec::new(); + for b in dq { + // Check if the blob is from the scheduled leader for its slot. If so, + // add to the retransmit_queue + let slot = b.read().unwrap().slot()?; + if let Some(leader_id) = leader_scheduler.get_leader_for_slot(slot) { + add_blob_to_retransmit_queue(b, leader_id, &mut retransmit_queue); + } + } + + submit( + influxdb::Point::new("retransmit-queue") + .add_field( + "count", + influxdb::Value::Integer(retransmit_queue.len() as i64), + ).to_owned(), + ); + + if !retransmit_queue.is_empty() { + inc_new_counter_info!("streamer-recv_window-retransmit", retransmit_queue.len()); + retransmit.send(retransmit_queue)?; + } + Ok(()) +} + +pub fn add_blob_to_retransmit_queue( + b: &SharedBlob, + leader_id: Pubkey, + retransmit_queue: &mut Vec, +) { + let p = b.read().unwrap(); + if p.id().expect("get_id in fn add_block_to_retransmit_queue") == leader_id { + let nv = SharedBlob::default(); + { + let mut mnv = nv.write().unwrap(); + let sz = p.meta.size; + mnv.meta.size = sz; + mnv.data[..sz].copy_from_slice(&p.data[..sz]); + } + retransmit_queue.push(nv); + } +} + +/// Process a blob: Add blob to the ledger window. If a continuous set of blobs +/// starting from consumed is thereby formed, add that continuous +/// range of blobs to a queue to be sent on to the next stage. +pub fn process_blob( + leader_scheduler: &LeaderScheduler, + db_ledger: &mut DbLedger, + blob: &SharedBlob, + max_ix: u64, + pix: u64, + consume_queue: &mut Vec, + tick_height: &mut u64, + done: &Arc, +) -> Result<()> { + let is_coding = blob.read().unwrap().is_coding(); + + // Check if the blob is in the range of our known leaders. If not, we return. + let slot = blob.read().unwrap().slot()?; + let leader = leader_scheduler.get_leader_for_slot(slot); + + if leader.is_none() { + return Ok(()); + } + + // Insert the new blob into the window + let mut consumed_entries = if is_coding { + let erasure_key = ErasureCf::key(slot, pix); + let rblob = &blob.read().unwrap(); + let size = rblob.size()?; + db_ledger.erasure_cf.put( + &db_ledger.db, + &erasure_key, + &rblob.data[..BLOB_HEADER_SIZE + size], + )?; + vec![] + } else { + let data_key = ErasureCf::key(slot, pix); + db_ledger.insert_data_blob(&data_key, &blob.read().unwrap())? + }; + + // TODO: Once erasure is fixed, readd that logic here + + for entry in &consumed_entries { + *tick_height += entry.is_tick() as u64; + } + + // For downloading storage blobs, + // we only want up to a certain index + // then stop + if max_ix != 0 && !consumed_entries.is_empty() { + let meta = db_ledger + .meta_cf + .get(&db_ledger.db, &MetaCf::key(slot))? + .expect("Expect metadata to exist if consumed entries is nonzero"); + + let consumed = meta.consumed; + + // Check if we ran over the last wanted entry + if consumed > max_ix { + let extra_unwanted_entries_len = consumed - (max_ix + 1); + let consumed_entries_len = consumed_entries.len(); + consumed_entries.truncate(consumed_entries_len - extra_unwanted_entries_len as usize); + done.store(true, Ordering::Relaxed); + } + } + + consume_queue.extend(consumed_entries); + Ok(()) +} + +pub fn calculate_max_repair_entry_height( + num_peers: u64, + consumed: u64, + received: u64, + times: usize, + is_next_leader: bool, +) -> u64 { + // Calculate the highest blob index that this node should have already received + // via avalanche. The avalanche splits data stream into nodes and each node retransmits + // the data to their peer nodes. So there's a possibility that a blob (with index lower + // than current received index) is being retransmitted by a peer node. + if times >= 8 || is_next_leader { + // if repair backoff is getting high, or if we are the next leader, + // don't wait for avalanche. received - 1 is the index of the highest blob. + received + } else { + cmp::max(consumed, received.saturating_sub(num_peers)) + } +} + +#[cfg(test)] +mod test { + use super::*; + use ledger::{get_tmp_ledger_path, make_tiny_test_entries, Block}; + use packet::{Blob, Packet, Packets, SharedBlob, PACKET_DATA_SIZE}; + use rocksdb::{Options, DB}; + use signature::{Keypair, KeypairUtil}; + use std::io; + use std::io::Write; + use std::net::UdpSocket; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::mpsc::channel; + use std::sync::Arc; + use std::time::Duration; + use streamer::{receiver, responder, PacketReceiver}; + + fn get_msgs(r: PacketReceiver, num: &mut usize) { + for _t in 0..5 { + let timer = Duration::new(1, 0); + match r.recv_timeout(timer) { + Ok(m) => *num += m.read().unwrap().packets.len(), + e => info!("error {:?}", e), + } + if *num == 10 { + break; + } + } + } + #[test] + pub fn streamer_debug() { + write!(io::sink(), "{:?}", Packet::default()).unwrap(); + write!(io::sink(), "{:?}", Packets::default()).unwrap(); + write!(io::sink(), "{:?}", Blob::default()).unwrap(); + } + + #[test] + pub fn streamer_send_test() { + let read = UdpSocket::bind("127.0.0.1:0").expect("bind"); + read.set_read_timeout(Some(Duration::new(1, 0))).unwrap(); + + let addr = read.local_addr().unwrap(); + let send = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let exit = Arc::new(AtomicBool::new(false)); + let (s_reader, r_reader) = channel(); + let t_receiver = receiver( + Arc::new(read), + exit.clone(), + s_reader, + "window-streamer-test", + ); + let t_responder = { + let (s_responder, r_responder) = channel(); + let t_responder = responder("streamer_send_test", Arc::new(send), r_responder); + let mut msgs = Vec::new(); + for i in 0..10 { + let mut b = SharedBlob::default(); + { + let mut w = b.write().unwrap(); + w.data[0] = i as u8; + w.meta.size = PACKET_DATA_SIZE; + w.meta.set_addr(&addr); + } + msgs.push(b); + } + s_responder.send(msgs).expect("send"); + t_responder + }; + + let mut num = 0; + get_msgs(r_reader, &mut num); + assert_eq!(num, 10); + exit.store(true, Ordering::Relaxed); + t_receiver.join().expect("join"); + t_responder.join().expect("join"); + } + + #[test] + pub fn test_calculate_max_repair_entry_height() { + assert_eq!(calculate_max_repair_entry_height(20, 4, 11, 0, false), 4); + assert_eq!(calculate_max_repair_entry_height(0, 10, 90, 0, false), 90); + assert_eq!(calculate_max_repair_entry_height(15, 10, 90, 32, false), 90); + assert_eq!(calculate_max_repair_entry_height(15, 10, 90, 0, false), 75); + assert_eq!(calculate_max_repair_entry_height(90, 10, 90, 0, false), 10); + assert_eq!(calculate_max_repair_entry_height(90, 10, 50, 0, false), 10); + assert_eq!(calculate_max_repair_entry_height(90, 10, 99, 0, false), 10); + assert_eq!(calculate_max_repair_entry_height(90, 10, 101, 0, false), 11); + assert_eq!(calculate_max_repair_entry_height(90, 10, 101, 0, true), 101); + assert_eq!( + calculate_max_repair_entry_height(90, 10, 101, 30, true), + 101 + ); + } + + #[test] + pub fn test_retransmit() { + let leader = Keypair::new().pubkey(); + let nonleader = Keypair::new().pubkey(); + let leader_scheduler = LeaderScheduler::from_bootstrap_leader(leader); + let blob = SharedBlob::default(); + + let (blob_sender, blob_receiver) = channel(); + + // Expect blob from leader to be retransmitted + blob.write().unwrap().set_id(&leader).unwrap(); + retransmit_all_leader_blocks(&vec![blob.clone()], &leader_scheduler, &blob_sender) + .expect("Expect successful retransmit"); + let output_blob = blob_receiver + .try_recv() + .expect("Expect input blob to be retransmitted"); + + // Retransmitted blob should be missing the leader id + assert_ne!(*output_blob[0].read().unwrap(), *blob.read().unwrap()); + // Set the leader in the retransmitted blob, should now match the original + output_blob[0].write().unwrap().set_id(&leader).unwrap(); + assert_eq!(*output_blob[0].read().unwrap(), *blob.read().unwrap()); + + // Expect blob from nonleader to not be retransmitted + blob.write().unwrap().set_id(&nonleader).unwrap(); + retransmit_all_leader_blocks(&vec![blob], &leader_scheduler, &blob_sender) + .expect("Expect successful retransmit"); + assert!(blob_receiver.try_recv().is_err()); + } + + #[test] + pub fn test_find_missing_data_indexes_sanity() { + let slot = 0; + + // Create RocksDb ledger + let db_ledger_path = get_tmp_ledger_path("test_find_missing_data_indexes_sanity"); + let mut db_ledger = DbLedger::open(&db_ledger_path).unwrap(); + + // Early exit conditions + let empty: Vec = vec![]; + assert_eq!(find_missing_data_indexes(slot, &db_ledger, 0, 0), empty); + assert_eq!(find_missing_data_indexes(slot, &db_ledger, 5, 5), empty); + assert_eq!(find_missing_data_indexes(slot, &db_ledger, 4, 3), empty); + + let shared_blob = &make_tiny_test_entries(1).to_blobs()[0]; + let first_index = 10; + { + let mut bl = shared_blob.write().unwrap(); + bl.set_index(10).unwrap(); + } + + // Insert one blob at index = first_index + db_ledger + .write_blobs(slot, &vec![&*shared_blob.read().unwrap()]) + .unwrap(); + + // The first blob has index = first_index. Thus, for i < first_index, + // given the input range of [i, first_index], the missing indexes should be + // [i, first_index - 1] + for i in 0..first_index { + let result = find_missing_data_indexes(slot, &db_ledger, i, first_index); + let expected: Vec = (i..first_index).collect(); + + assert_eq!(result, expected); + } + + drop(db_ledger); + DB::destroy(&Options::default(), &db_ledger_path) + .expect("Expected successful database destruction"); + } + + #[test] + pub fn test_find_missing_data_indexes() { + let slot = 0; + // Create RocksDb ledger + let db_ledger_path = get_tmp_ledger_path("test_find_missing_data_indexes"); + let mut db_ledger = DbLedger::open(&db_ledger_path).unwrap(); + + // Write entries + let gap = 10; + let num_entries = 10; + let shared_blobs = make_tiny_test_entries(num_entries).to_blobs(); + for (b, i) in shared_blobs.iter().zip(0..shared_blobs.len() as u64) { + b.write().unwrap().set_index(i * gap).unwrap(); + } + let blob_locks: Vec<_> = shared_blobs.iter().map(|b| b.read().unwrap()).collect(); + let blobs: Vec<&Blob> = blob_locks.iter().map(|b| &**b).collect(); + db_ledger.write_blobs(slot, &blobs).unwrap(); + + // Index of the first blob is 0 + // Index of the second blob is "gap" + // Thus, the missing indexes should then be [1, gap - 1] for the input index + // range of [0, gap) + let expected: Vec = (1..gap).collect(); + assert_eq!( + find_missing_data_indexes(slot, &db_ledger, 0, gap), + expected + ); + assert_eq!( + find_missing_data_indexes(slot, &db_ledger, 1, gap), + expected, + ); + assert_eq!( + find_missing_data_indexes(slot, &db_ledger, 0, gap - 1), + &expected[..expected.len() - 1], + ); + + for i in 0..num_entries as u64 { + for j in 0..i { + let expected: Vec = (j..i) + .flat_map(|k| { + let begin = k * gap + 1; + let end = (k + 1) * gap; + (begin..end) + }).collect(); + assert_eq!( + find_missing_data_indexes(slot, &db_ledger, j * gap, i * gap), + expected, + ); + } + } + + drop(db_ledger); + DB::destroy(&Options::default(), &db_ledger_path) + .expect("Expected successful database destruction"); + } + + #[test] + pub fn test_no_missing_blob_indexes() { + let slot = 0; + // Create RocksDb ledger + let db_ledger_path = get_tmp_ledger_path("test_find_missing_data_indexes"); + let mut db_ledger = DbLedger::open(&db_ledger_path).unwrap(); + + // Write entries + let num_entries = 10; + let shared_blobs = make_tiny_test_entries(num_entries).to_blobs(); + let blob_locks: Vec<_> = shared_blobs.iter().map(|b| b.read().unwrap()).collect(); + let blobs: Vec<&Blob> = blob_locks.iter().map(|b| &**b).collect(); + db_ledger.write_blobs(slot, &blobs).unwrap(); + + let empty: Vec = vec![]; + for i in 0..num_entries as u64 { + for j in 0..i { + assert_eq!(find_missing_data_indexes(slot, &db_ledger, j, i), empty); + } + } + + drop(db_ledger); + DB::destroy(&Options::default(), &db_ledger_path) + .expect("Expected successful database destruction"); + } +} diff --git a/book/elasticlunr.min.js b/book/elasticlunr.min.js new file mode 100644 index 00000000000000..94b20dd2ef4609 --- /dev/null +++ b/book/elasticlunr.min.js @@ -0,0 +1,10 @@ +/** + * elasticlunr - http://weixsong.github.io + * Lightweight full-text search engine in Javascript for browser search and offline search. - 0.9.5 + * + * Copyright (C) 2017 Oliver Nightingale + * Copyright (C) 2017 Wei Song + * MIT Licensed + * @license + */ +!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o>; +pub type EntryReceiver = Receiver>; + +/// Each Entry contains three pieces of data. The `num_hashes` field is the number +/// of hashes performed since the previous entry. The `id` field is the result +/// of hashing `id` from the previous entry `num_hashes` times. The `transactions` +/// field points to Transactions that took place shortly before `id` was generated. +/// +/// If you divide `num_hashes` by the amount of time it takes to generate a new hash, you +/// get a duration estimate since the last Entry. Since processing power increases +/// over time, one should expect the duration `num_hashes` represents to decrease proportionally. +/// An upper bound on Duration can be estimated by assuming each hash was generated by the +/// world's fastest processor at the time the entry was recorded. Or said another way, it +/// is physically not possible for a shorter duration to have occurred if one assumes the +/// hash was computed by the world's fastest processor at that time. The hash chain is both +/// a Verifiable Delay Function (VDF) and a Proof of Work (not to be confused with Proof of +/// Work consensus!) + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub struct Entry { + /// The the previous Entry ID. + pub prev_id: Hash, + + /// The number of hashes since the previous Entry ID. + pub num_hashes: u64, + + /// The SHA-256 hash `num_hashes` after the previous Entry ID. + pub id: Hash, + + /// An unordered list of transactions that were observed before the Entry ID was + /// generated. They may have been observed before a previous Entry ID but were + /// pushed back into this list to ensure deterministic interpretation of the ledger. + pub transactions: Vec, +} + +impl Entry { + /// Creates the next Entry `num_hashes` after `start_hash`. + pub fn new(prev_id: &Hash, num_hashes: u64, transactions: Vec) -> Self { + let entry = { + if num_hashes == 0 && transactions.is_empty() { + Entry { + prev_id: *prev_id, + num_hashes: 0, + id: *prev_id, + transactions, + } + } else if num_hashes == 0 { + // If you passed in transactions, but passed in num_hashes == 0, then + // next_hash will generate the next hash and set num_hashes == 1 + let id = next_hash(prev_id, 1, &transactions); + Entry { + prev_id: *prev_id, + num_hashes: 1, + id, + transactions, + } + } else { + // Otherwise, the next Entry `num_hashes` after `start_hash`. + // If you wanted a tick for instance, then pass in num_hashes = 1 + // and transactions = empty + let id = next_hash(prev_id, num_hashes, &transactions); + Entry { + prev_id: *prev_id, + num_hashes, + id, + transactions, + } + } + }; + + let size = serialized_size(&entry).unwrap(); + if size > BLOB_DATA_SIZE as u64 { + panic!( + "Serialized entry size too large: {} ({} transactions):", + size, + entry.transactions.len() + ); + } + entry + } + + pub fn to_blob( + &self, + idx: Option, + id: Option, + addr: Option<&SocketAddr>, + ) -> SharedBlob { + let blob = SharedBlob::default(); + { + let mut blob_w = blob.write().unwrap(); + let pos = { + let mut out = Cursor::new(blob_w.data_mut()); + serialize_into(&mut out, &self).expect("failed to serialize output"); + out.position() as usize + }; + blob_w.set_size(pos); + + if let Some(idx) = idx { + blob_w.set_index(idx).expect("set_index()"); + } + if let Some(id) = id { + blob_w.set_id(&id).expect("set_id()"); + } + if let Some(addr) = addr { + blob_w.meta.set_addr(addr); + } + blob_w.set_flags(0).unwrap(); + } + blob + } + + /// Estimate serialized_size of Entry without creating an Entry. + pub fn serialized_size(transactions: &[Transaction]) -> u64 { + let txs_size = serialized_size(transactions).unwrap(); + + // num_hashes + id + prev_id + txs + + (size_of::() + 2 * size_of::()) as u64 + txs_size + } + + pub fn num_will_fit(transactions: &[Transaction]) -> usize { + if transactions.is_empty() { + return 0; + } + let mut num = transactions.len(); + let mut upper = transactions.len(); + let mut lower = 1; // if one won't fit, we have a lot of TODOs + let mut next = transactions.len(); // optimistic + loop { + debug!( + "num {}, upper {} lower {} next {} transactions.len() {}", + num, + upper, + lower, + next, + transactions.len() + ); + if Self::serialized_size(&transactions[..num]) <= BLOB_DATA_SIZE as u64 { + next = (upper + num) / 2; + lower = num; + debug!("num {} fits, maybe too well? trying {}", num, next); + } else { + next = (lower + num) / 2; + upper = num; + debug!("num {} doesn't fit! trying {}", num, next); + } + // same as last time + if next == num { + debug!("converged on num {}", num); + break; + } + num = next; + } + num + } + + /// Creates the next Tick Entry `num_hashes` after `start_hash`. + pub fn new_mut( + start_hash: &mut Hash, + num_hashes: &mut u64, + transactions: Vec, + ) -> Self { + let entry = Self::new(start_hash, *num_hashes, transactions); + *start_hash = entry.id; + *num_hashes = 0; + assert!(serialized_size(&entry).unwrap() <= BLOB_DATA_SIZE as u64); + entry + } + + /// Creates a Entry from the number of hashes `num_hashes` since the previous transaction + /// and that resulting `id`. + pub fn new_tick(prev_id: &Hash, num_hashes: u64, id: &Hash) -> Self { + Entry { + prev_id: *prev_id, + num_hashes, + id: *id, + transactions: vec![], + } + } + + pub fn verify_self(&self) -> bool { + self.id == next_hash(&self.prev_id, self.num_hashes, &self.transactions) + } + + /// Verifies self.id is the result of hashing a `start_hash` `self.num_hashes` times. + /// If the transaction is not a Tick, then hash that as well. + pub fn verify(&self, start_hash: &Hash) -> bool { + let ref_hash = next_hash(start_hash, self.num_hashes, &self.transactions); + if self.id != ref_hash { + warn!( + "next_hash is invalid expected: {:?} actual: {:?}", + self.id, ref_hash + ); + return false; + } + true + } + + pub fn is_tick(&self) -> bool { + self.transactions.is_empty() + } +} + +/// Creates the hash `num_hashes` after `start_hash`. If the transaction contains +/// a signature, the final hash will be a hash of both the previous ID and +/// the signature. If num_hashes is zero and there's no transaction data, +/// start_hash is returned. +fn next_hash(start_hash: &Hash, num_hashes: u64, transactions: &[Transaction]) -> Hash { + if num_hashes == 0 && transactions.is_empty() { + return *start_hash; + } + + let mut poh = Poh::new(*start_hash, 0); + + for _ in 1..num_hashes { + poh.hash(); + } + + if transactions.is_empty() { + poh.tick().id + } else { + poh.record(Transaction::hash(transactions)).id + } +} + +/// Creates the next Tick or Transaction Entry `num_hashes` after `start_hash`. +pub fn next_entry(prev_id: &Hash, num_hashes: u64, transactions: Vec) -> Entry { + assert!(num_hashes > 0 || transactions.is_empty()); + Entry { + prev_id: *prev_id, + num_hashes, + id: next_hash(prev_id, num_hashes, &transactions), + transactions, + } +} + +pub fn reconstruct_entries_from_blobs(blobs: Vec) -> Result<(Vec, u64)> { + let mut entries: Vec = Vec::with_capacity(blobs.len()); + let mut num_ticks = 0; + + for blob in blobs { + let entry: Entry = { + let msg = blob.read().unwrap(); + let msg_size = msg.size()?; + deserialize(&msg.data()[..msg_size]).expect("Error reconstructing entry") + }; + + if entry.is_tick() { + num_ticks += 1 + } + entries.push(entry) + } + Ok((entries, num_ticks)) +} + +#[cfg(test)] +mod tests { + use super::*; + use budget_transaction::BudgetTransaction; + use chrono::prelude::*; + use entry::Entry; + use signature::{Keypair, KeypairUtil}; + use solana_sdk::hash::hash; + use system_transaction::SystemTransaction; + use transaction::Transaction; + + #[test] + fn test_entry_verify() { + let zero = Hash::default(); + let one = hash(&zero.as_ref()); + assert!(Entry::new_tick(&zero, 0, &zero).verify(&zero)); // base case, never used + assert!(!Entry::new_tick(&zero, 0, &zero).verify(&one)); // base case, bad + assert!(next_entry(&zero, 1, vec![]).verify(&zero)); // inductive step + assert!(next_entry(&zero, 1, vec![]).verify_self()); // also inductive step + assert!(!next_entry(&zero, 1, vec![]).verify(&one)); // inductive step, bad + } + + #[test] + fn test_transaction_reorder_attack() { + let zero = Hash::default(); + + // First, verify entries + let keypair = Keypair::new(); + let tx0 = Transaction::system_new(&keypair, keypair.pubkey(), 0, zero); + let tx1 = Transaction::system_new(&keypair, keypair.pubkey(), 1, zero); + let mut e0 = Entry::new(&zero, 0, vec![tx0.clone(), tx1.clone()]); + assert!(e0.verify(&zero)); + + // Next, swap two transactions and ensure verification fails. + e0.transactions[0] = tx1; // <-- attack + e0.transactions[1] = tx0; + assert!(!e0.verify(&zero)); + } + + #[test] + fn test_witness_reorder_attack() { + let zero = Hash::default(); + + // First, verify entries + let keypair = Keypair::new(); + let tx0 = Transaction::budget_new_timestamp( + &keypair, + keypair.pubkey(), + keypair.pubkey(), + Utc::now(), + zero, + ); + let tx1 = + Transaction::budget_new_signature(&keypair, keypair.pubkey(), keypair.pubkey(), zero); + let mut e0 = Entry::new(&zero, 0, vec![tx0.clone(), tx1.clone()]); + assert!(e0.verify(&zero)); + + // Next, swap two witness transactions and ensure verification fails. + e0.transactions[0] = tx1; // <-- attack + e0.transactions[1] = tx0; + assert!(!e0.verify(&zero)); + } + + #[test] + fn test_next_entry() { + let zero = Hash::default(); + let tick = next_entry(&zero, 1, vec![]); + assert_eq!(tick.num_hashes, 1); + assert_ne!(tick.id, zero); + + let tick = next_entry(&zero, 0, vec![]); + assert_eq!(tick.num_hashes, 0); + assert_eq!(tick.id, zero); + + let keypair = Keypair::new(); + let tx0 = Transaction::budget_new_timestamp( + &keypair, + keypair.pubkey(), + keypair.pubkey(), + Utc::now(), + zero, + ); + let entry0 = next_entry(&zero, 1, vec![tx0.clone()]); + assert_eq!(entry0.num_hashes, 1); + assert_eq!(entry0.id, next_hash(&zero, 1, &vec![tx0])); + } + + #[test] + #[should_panic] + fn test_next_entry_panic() { + let zero = Hash::default(); + let keypair = Keypair::new(); + let tx = Transaction::system_new(&keypair, keypair.pubkey(), 0, zero); + next_entry(&zero, 0, vec![tx]); + } + + #[test] + fn test_serialized_size() { + let zero = Hash::default(); + let keypair = Keypair::new(); + let tx = Transaction::system_new(&keypair, keypair.pubkey(), 0, zero); + let entry = next_entry(&zero, 1, vec![tx.clone()]); + assert_eq!( + Entry::serialized_size(&[tx]), + serialized_size(&entry).unwrap() + ); + } +} diff --git a/book/erasure.rs b/book/erasure.rs new file mode 100644 index 00000000000000..bdd7f6381ef689 --- /dev/null +++ b/book/erasure.rs @@ -0,0 +1,943 @@ +// Support erasure coding +use packet::{SharedBlob, BLOB_DATA_SIZE, BLOB_HEADER_SIZE}; +use solana_sdk::pubkey::Pubkey; +use std::cmp; +use std::mem; +use std::result; +use window::WindowSlot; + +//TODO(sakridge) pick these values +pub const NUM_DATA: usize = 16; // number of data blobs +pub const NUM_CODING: usize = 4; // number of coding blobs, also the maximum number that can go missing +pub const ERASURE_SET_SIZE: usize = NUM_DATA + NUM_CODING; // total number of blobs in an erasure set, includes data and coding blobs + +pub const JERASURE_ALIGN: usize = 4; // data size has to be a multiple of 4 bytes + +macro_rules! align { + ($x:expr, $align:expr) => { + $x + ($align - 1) & !($align - 1) + }; +} + +#[derive(Debug, PartialEq, Eq)] +pub enum ErasureError { + NotEnoughBlocksToDecode, + DecodeError, + EncodeError, + InvalidBlockSize, +} + +pub type Result = result::Result; + +// k = number of data devices +// m = number of coding devices +// w = word size + +extern "C" { + fn jerasure_matrix_encode( + k: i32, + m: i32, + w: i32, + matrix: *const i32, + data_ptrs: *const *const u8, + coding_ptrs: *const *mut u8, + size: i32, + ); + fn jerasure_matrix_decode( + k: i32, + m: i32, + w: i32, + matrix: *const i32, + row_k_ones: i32, + erasures: *const i32, + data_ptrs: *const *mut u8, + coding_ptrs: *const *mut u8, + size: i32, + ) -> i32; + fn galois_single_divide(a: i32, b: i32, w: i32) -> i32; +} + +fn get_matrix(m: i32, k: i32, w: i32) -> Vec { + let mut matrix = vec![0; (m * k) as usize]; + for i in 0..m { + for j in 0..k { + unsafe { + matrix[(i * k + j) as usize] = galois_single_divide(1, i ^ (m + j), w); + } + } + } + matrix +} + +pub const ERASURE_W: i32 = 32; + +// Generate coding blocks into coding +// There are some alignment restrictions, blocks should be aligned by 16 bytes +// which means their size should be >= 16 bytes +pub fn generate_coding_blocks(coding: &mut [&mut [u8]], data: &[&[u8]]) -> Result<()> { + if data.is_empty() { + return Ok(()); + } + let k = data.len() as i32; + let m = coding.len() as i32; + let block_len = data[0].len() as i32; + let matrix: Vec = get_matrix(m, k, ERASURE_W); + let mut data_arg = Vec::with_capacity(data.len()); + for block in data { + if block_len != block.len() as i32 { + error!( + "data block size incorrect {} expected {}", + block.len(), + block_len + ); + return Err(ErasureError::InvalidBlockSize); + } + data_arg.push(block.as_ptr()); + } + let mut coding_arg = Vec::with_capacity(coding.len()); + for mut block in coding { + if block_len != block.len() as i32 { + error!( + "coding block size incorrect {} expected {}", + block.len(), + block_len + ); + return Err(ErasureError::InvalidBlockSize); + } + coding_arg.push(block.as_mut_ptr()); + } + + unsafe { + jerasure_matrix_encode( + k, + m, + ERASURE_W, + matrix.as_ptr(), + data_arg.as_ptr(), + coding_arg.as_ptr(), + block_len, + ); + } + Ok(()) +} + +// Recover data + coding blocks into data blocks +// data: array of blocks to recover into +// coding: arry of coding blocks +// erasures: list of indices in data where blocks should be recovered +pub fn decode_blocks( + data: &mut [&mut [u8]], + coding: &mut [&mut [u8]], + erasures: &[i32], +) -> Result<()> { + if data.is_empty() { + return Ok(()); + } + let block_len = data[0].len(); + let matrix: Vec = get_matrix(coding.len() as i32, data.len() as i32, ERASURE_W); + + // generate coding pointers, blocks should be the same size + let mut coding_arg: Vec<*mut u8> = Vec::new(); + for x in coding.iter_mut() { + if x.len() != block_len { + return Err(ErasureError::InvalidBlockSize); + } + coding_arg.push(x.as_mut_ptr()); + } + + // generate data pointers, blocks should be the same size + let mut data_arg: Vec<*mut u8> = Vec::new(); + for x in data.iter_mut() { + if x.len() != block_len { + return Err(ErasureError::InvalidBlockSize); + } + data_arg.push(x.as_mut_ptr()); + } + let ret = unsafe { + jerasure_matrix_decode( + data.len() as i32, + coding.len() as i32, + ERASURE_W, + matrix.as_ptr(), + 0, + erasures.as_ptr(), + data_arg.as_ptr(), + coding_arg.as_ptr(), + data[0].len() as i32, + ) + }; + trace!("jerasure_matrix_decode ret: {}", ret); + for x in data[erasures[0] as usize][0..8].iter() { + trace!("{} ", x) + } + trace!(""); + if ret < 0 { + return Err(ErasureError::DecodeError); + } + Ok(()) +} + +// Generate coding blocks in window starting from start_idx, +// for num_blobs.. For each block place the coding blobs +// at the end of the block like so: +// +// block-size part of a Window, with each element a WindowSlot.. +// |<======================= NUM_DATA ==============================>| +// |<==== NUM_CODING ===>| +// +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +// | D | | D | | D | | D | | D | | D | | D | | D | | D | | D | +// +---+ +---+ +---+ +---+ +---+ . . . +---+ +---+ +---+ +---+ +---+ +// | | | | | | | | | | | | | C | | C | | C | | C | +// +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +// +// blob structure for coding, recover +// +// + ------- meta is set and used by transport, meta.size is actual length +// | of data in the byte array blob.data +// | +// | + -- data is stuff shipped over the wire, and has an included +// | | header +// V V +// +----------+------------------------------------------------------------+ +// | meta | data | +// |+---+-- |+---+---+---+---+------------------------------------------+| +// || s | . || i | | f | s | || +// || i | . || n | i | l | i | || +// || z | . || d | d | a | z | blob.data(), or blob.data_mut() || +// || e | || e | | g | e | || +// |+---+-- || x | | s | | || +// | |+---+---+---+---+------------------------------------------+| +// +----------+------------------------------------------------------------+ +// | |<=== coding blob part for "coding" =======>| +// | | +// |<============== data blob part for "coding" ==============>| +// +// +// +pub fn generate_coding( + id: &Pubkey, + window: &mut [WindowSlot], + receive_index: u64, + num_blobs: usize, + transmit_index_coding: &mut u64, +) -> Result<()> { + // beginning of the coding blobs of the block that receive_index points into + let coding_index_start = + receive_index - (receive_index % NUM_DATA as u64) + (NUM_DATA - NUM_CODING) as u64; + + let start_idx = receive_index as usize % window.len(); + let mut block_start = start_idx - (start_idx % NUM_DATA); + + loop { + let block_end = block_start + NUM_DATA; + if block_end > (start_idx + num_blobs) { + break; + } + info!( + "generate_coding {} start: {} end: {} start_idx: {} num_blobs: {}", + id, block_start, block_end, start_idx, num_blobs + ); + + let mut max_data_size = 0; + + // find max_data_size, maybe bail if not all the data is here + for i in block_start..block_end { + let n = i % window.len(); + trace!("{} window[{}] = {:?}", id, n, window[n].data); + + if let Some(b) = &window[n].data { + max_data_size = cmp::max(b.read().unwrap().meta.size, max_data_size); + } else { + trace!("{} data block is null @ {}", id, n); + return Ok(()); + } + } + + // round up to the nearest jerasure alignment + max_data_size = align!(max_data_size, JERASURE_ALIGN); + + trace!("{} max_data_size: {}", id, max_data_size); + + let mut data_blobs = Vec::with_capacity(NUM_DATA); + for i in block_start..block_end { + let n = i % window.len(); + + if let Some(b) = &window[n].data { + // make sure extra bytes in each blob are zero-d out for generation of + // coding blobs + let mut b_wl = b.write().unwrap(); + for i in b_wl.meta.size..max_data_size { + b_wl.data[i] = 0; + } + data_blobs.push(b); + } + } + + // getting ready to do erasure coding, means that we're potentially + // going back in time, tell our caller we've inserted coding blocks + // starting at coding_index_start + *transmit_index_coding = cmp::min(*transmit_index_coding, coding_index_start); + + let mut coding_blobs = Vec::with_capacity(NUM_CODING); + let coding_start = block_end - NUM_CODING; + for i in coding_start..block_end { + let n = i % window.len(); + assert!(window[n].coding.is_none()); + + window[n].coding = Some(SharedBlob::default()); + + let coding = window[n].coding.clone().unwrap(); + let mut coding_wl = coding.write().unwrap(); + for i in 0..max_data_size { + coding_wl.data[i] = 0; + } + // copy index and id from the data blob + if let Some(data) = &window[n].data { + let data_rl = data.read().unwrap(); + + let index = data_rl.index().unwrap(); + let slot = data_rl.slot().unwrap(); + let id = data_rl.id().unwrap(); + + trace!( + "{} copying index {} id {:?} from data to coding", + id, + index, + id + ); + coding_wl.set_index(index).unwrap(); + coding_wl.set_slot(slot).unwrap(); + coding_wl.set_id(&id).unwrap(); + } + coding_wl.set_size(max_data_size); + if coding_wl.set_coding().is_err() { + return Err(ErasureError::EncodeError); + } + + coding_blobs.push(coding.clone()); + } + + let data_locks: Vec<_> = data_blobs.iter().map(|b| b.read().unwrap()).collect(); + + let data_ptrs: Vec<_> = data_locks + .iter() + .enumerate() + .map(|(i, l)| { + trace!("{} i: {} data: {}", id, i, l.data[0]); + &l.data[..max_data_size] + }).collect(); + + let mut coding_locks: Vec<_> = coding_blobs.iter().map(|b| b.write().unwrap()).collect(); + + let mut coding_ptrs: Vec<_> = coding_locks + .iter_mut() + .enumerate() + .map(|(i, l)| { + trace!("{} i: {} coding: {}", id, i, l.data[0],); + &mut l.data_mut()[..max_data_size] + }).collect(); + + generate_coding_blocks(coding_ptrs.as_mut_slice(), &data_ptrs)?; + debug!( + "{} start_idx: {} data: {}:{} coding: {}:{}", + id, start_idx, block_start, block_end, coding_start, block_end + ); + block_start = block_end; + } + Ok(()) +} + +// examine the window slot at idx returns +// true if slot is empty +// true if slot is stale (i.e. has the wrong index), old blob is flushed +// false if slot has a blob with the right index +fn is_missing(id: &Pubkey, idx: u64, window_slot: &mut Option, c_or_d: &str) -> bool { + if let Some(blob) = window_slot.take() { + let blob_idx = blob.read().unwrap().index().unwrap(); + if blob_idx == idx { + trace!("recover {}: idx: {} good {}", id, idx, c_or_d); + // put it back + mem::replace(window_slot, Some(blob)); + false + } else { + trace!( + "recover {}: idx: {} old {} {}, recycling", + id, + idx, + c_or_d, + blob_idx, + ); + true + } + } else { + trace!("recover {}: idx: {} None {}", id, idx, c_or_d); + // nothing there + true + } +} + +// examine the window beginning at block_start for missing or +// stale (based on block_start_idx) blobs +// if a blob is stale, remove it from the window slot +// side effect: block will be cleaned of old blobs +fn find_missing( + id: &Pubkey, + block_start_idx: u64, + block_start: usize, + window: &mut [WindowSlot], +) -> (usize, usize) { + let mut data_missing = 0; + let mut coding_missing = 0; + let block_end = block_start + NUM_DATA; + let coding_start = block_start + NUM_DATA - NUM_CODING; + + // count missing blobs in the block + for i in block_start..block_end { + let idx = (i - block_start) as u64 + block_start_idx; + let n = i % window.len(); + + if is_missing(id, idx, &mut window[n].data, "data") { + data_missing += 1; + } + + if i >= coding_start && is_missing(id, idx, &mut window[n].coding, "coding") { + coding_missing += 1; + } + } + (data_missing, coding_missing) +} + +// Recover a missing block into window +// missing blocks should be None or old... +// If not enough coding or data blocks are present to restore +// any of the blocks, the block is skipped. +// Side effect: old blobs in a block are None'd +pub fn recover(id: &Pubkey, window: &mut [WindowSlot], start_idx: u64, start: usize) -> Result<()> { + let block_start = start - (start % NUM_DATA); + let block_start_idx = start_idx - (start_idx % NUM_DATA as u64); + + debug!("start: {} block_start: {}", start, block_start); + + let coding_start = block_start + NUM_DATA - NUM_CODING; + let block_end = block_start + NUM_DATA; + trace!( + "recover {}: block_start_idx: {} block_start: {} coding_start: {} block_end: {}", + id, + block_start_idx, + block_start, + coding_start, + block_end + ); + + let (data_missing, coding_missing) = find_missing(id, block_start_idx, block_start, window); + + // if we're not missing data, or if we have too much missin but have enough coding + if data_missing == 0 { + // nothing to do... + return Ok(()); + } + + if (data_missing + coding_missing) > NUM_CODING { + trace!( + "recover {}: start: {} skipping recovery data: {} coding: {}", + id, + block_start, + data_missing, + coding_missing + ); + // nothing to do... + return Err(ErasureError::NotEnoughBlocksToDecode); + } + + trace!( + "recover {}: recovering: data: {} coding: {}", + id, + data_missing, + coding_missing + ); + let mut blobs: Vec = Vec::with_capacity(NUM_DATA + NUM_CODING); + let mut locks = Vec::with_capacity(NUM_DATA + NUM_CODING); + let mut erasures: Vec = Vec::with_capacity(NUM_CODING); + let mut meta = None; + let mut size = None; + + // add the data blobs we have into recovery blob vector + for i in block_start..block_end { + let j = i % window.len(); + + if let Some(b) = window[j].data.clone() { + if meta.is_none() { + meta = Some(b.read().unwrap().meta.clone()); + trace!("recover {} meta at {} {:?}", id, j, meta); + } + blobs.push(b); + } else { + let n = SharedBlob::default(); + window[j].data = Some(n.clone()); + // mark the missing memory + blobs.push(n); + erasures.push((i - block_start) as i32); + } + } + for i in coding_start..block_end { + let j = i % window.len(); + if let Some(b) = window[j].coding.clone() { + if size.is_none() { + size = Some(b.read().unwrap().meta.size - BLOB_HEADER_SIZE); + trace!( + "{} recover size {} from {}", + id, + size.unwrap(), + i as u64 + block_start_idx + ); + } + blobs.push(b); + } else { + let n = SharedBlob::default(); + window[j].coding = Some(n.clone()); + //mark the missing memory + blobs.push(n); + erasures.push(((i - coding_start) + NUM_DATA) as i32); + } + } + + // now that we have size (from coding), zero out data blob tails + let size = size.unwrap(); + for i in block_start..block_end { + let j = i % window.len(); + + if let Some(b) = &window[j].data { + let mut b_wl = b.write().unwrap(); + for i in b_wl.meta.size..size { + b_wl.data[i] = 0; + } + } + } + + // marks end of erasures + erasures.push(-1); + trace!("erasures[]: {} {:?} data_size: {}", id, erasures, size,); + //lock everything for write + for b in &blobs { + locks.push(b.write().unwrap()); + } + + { + let mut coding_ptrs: Vec<&mut [u8]> = Vec::with_capacity(NUM_CODING); + let mut data_ptrs: Vec<&mut [u8]> = Vec::with_capacity(NUM_DATA); + for (i, l) in locks.iter_mut().enumerate() { + if i < NUM_DATA { + trace!("{} pushing data: {}", id, i); + data_ptrs.push(&mut l.data[..size]); + } else { + trace!("{} pushing coding: {}", id, i); + coding_ptrs.push(&mut l.data_mut()[..size]); + } + } + trace!( + "{} coding_ptrs.len: {} data_ptrs.len {}", + id, + coding_ptrs.len(), + data_ptrs.len() + ); + decode_blocks( + data_ptrs.as_mut_slice(), + coding_ptrs.as_mut_slice(), + &erasures, + )?; + } + + let meta = meta.unwrap(); + let mut corrupt = false; + // repopulate header data size from recovered blob contents + for i in &erasures[..erasures.len() - 1] { + let n = *i as usize; + let mut idx = n as u64 + block_start_idx; + + let mut data_size; + if n < NUM_DATA { + data_size = locks[n].data_size().unwrap() as usize; + data_size -= BLOB_HEADER_SIZE; + if data_size > BLOB_DATA_SIZE { + error!("{} corrupt data blob[{}] data_size: {}", id, idx, data_size); + corrupt = true; + } + } else { + data_size = size; + idx -= NUM_CODING as u64; + locks[n].set_index(idx).unwrap(); + + if data_size - BLOB_HEADER_SIZE > BLOB_DATA_SIZE { + error!( + "{} corrupt coding blob[{}] data_size: {}", + id, idx, data_size + ); + corrupt = true; + } + } + + locks[n].meta = meta.clone(); + locks[n].set_size(data_size); + trace!( + "{} erasures[{}] ({}) size: {} data[0]: {}", + id, + *i, + idx, + data_size, + locks[n].data()[0] + ); + } + assert!(!corrupt, " {} ", id); + + Ok(()) +} + +#[cfg(test)] +mod test { + use erasure; + use logger; + use packet::{index_blobs, SharedBlob, BLOB_DATA_SIZE, BLOB_HEADER_SIZE, BLOB_SIZE}; + use rand::{thread_rng, Rng}; + use signature::{Keypair, KeypairUtil}; + use solana_sdk::pubkey::Pubkey; + // use std::sync::{Arc, RwLock}; + use window::WindowSlot; + + #[test] + pub fn test_coding() { + let zero_vec = vec![0; 16]; + let mut vs: Vec> = (0..4).map(|i| (i..(16 + i)).collect()).collect(); + let v_orig: Vec = vs[0].clone(); + + let m = 2; + let mut coding_blocks: Vec<_> = (0..m).map(|_| vec![0u8; 16]).collect(); + + { + let mut coding_blocks_slices: Vec<_> = + coding_blocks.iter_mut().map(|x| x.as_mut_slice()).collect(); + let v_slices: Vec<_> = vs.iter().map(|x| x.as_slice()).collect(); + + assert!( + erasure::generate_coding_blocks( + coding_blocks_slices.as_mut_slice(), + v_slices.as_slice(), + ).is_ok() + ); + } + trace!("coding blocks:"); + for b in &coding_blocks { + trace!("{:?}", b); + } + let erasure: i32 = 1; + let erasures = vec![erasure, -1]; + // clear an entry + vs[erasure as usize].copy_from_slice(zero_vec.as_slice()); + + { + let mut coding_blocks_slices: Vec<_> = + coding_blocks.iter_mut().map(|x| x.as_mut_slice()).collect(); + let mut v_slices: Vec<_> = vs.iter_mut().map(|x| x.as_mut_slice()).collect(); + + assert!( + erasure::decode_blocks( + v_slices.as_mut_slice(), + coding_blocks_slices.as_mut_slice(), + erasures.as_slice(), + ).is_ok() + ); + } + + trace!("vs:"); + for v in &vs { + trace!("{:?}", v); + } + assert_eq!(v_orig, vs[0]); + } + + fn print_window(window: &[WindowSlot]) { + for (i, w) in window.iter().enumerate() { + print!("window({:>w$}): ", i, w = 2); + if w.data.is_some() { + let window_l1 = w.data.clone().unwrap(); + let window_l2 = window_l1.read().unwrap(); + print!( + "data index: {:?} meta.size: {} data: ", + window_l2.index(), + window_l2.meta.size + ); + for i in 0..64 { + print!("{:>w$} ", window_l2.data()[i], w = 3); + } + } else { + print!("data null "); + } + println!(); + print!("window({:>w$}): ", i, w = 2); + if w.coding.is_some() { + let window_l1 = w.coding.clone().unwrap(); + let window_l2 = window_l1.read().unwrap(); + print!( + "coding index: {:?} meta.size: {} data: ", + window_l2.index(), + window_l2.meta.size + ); + for i in 0..8 { + print!("{:>w$} ", window_l2.data()[i], w = 3); + } + } else { + print!("coding null"); + } + println!(); + } + } + + const WINDOW_SIZE: usize = 64; + fn generate_window(offset: usize, num_blobs: usize) -> Vec { + let mut window = vec![ + WindowSlot { + data: None, + coding: None, + leader_unknown: false, + }; + WINDOW_SIZE + ]; + let mut blobs = Vec::with_capacity(num_blobs); + for i in 0..num_blobs { + let b = SharedBlob::default(); + let b_ = b.clone(); + let mut w = b.write().unwrap(); + // generate a random length, multiple of 4 between 8 and 32 + let data_len = if i == 3 { + BLOB_DATA_SIZE + } else { + (thread_rng().gen_range(2, 8) * 4) + 1 + }; + + eprintln!("data_len of {} is {}", i, data_len); + w.set_size(data_len); + + for k in 0..data_len { + w.data_mut()[k] = (k + i) as u8; + } + + // overfill, simulates re-used blobs + for i in BLOB_HEADER_SIZE + data_len..BLOB_SIZE { + w.data[i] = thread_rng().gen(); + } + + blobs.push(b_); + } + + index_blobs(&blobs, &Keypair::new().pubkey(), offset as u64, 13); + for b in blobs { + let idx = b.read().unwrap().index().unwrap() as usize % WINDOW_SIZE; + + window[idx].data = Some(b); + } + window + } + + fn scramble_window_tails(window: &mut [WindowSlot], num_blobs: usize) { + for i in 0..num_blobs { + if let Some(b) = &window[i].data { + let size = { + let b_l = b.read().unwrap(); + b_l.meta.size + } as usize; + + let mut b_l = b.write().unwrap(); + for i in size..BLOB_SIZE { + b_l.data[i] = thread_rng().gen(); + } + } + } + } + + #[test] + pub fn test_window_recover_basic() { + logger::setup(); + // Generate a window + let offset = 0; + let num_blobs = erasure::NUM_DATA + 2; + let mut window = generate_window(WINDOW_SIZE, num_blobs); + + for slot in &window { + if let Some(blob) = &slot.data { + let blob_r = blob.read().unwrap(); + assert!(!blob_r.is_coding()); + } + } + + println!("** after-gen-window:"); + print_window(&window); + + // Generate the coding blocks + let mut index = (erasure::NUM_DATA + 2) as u64; + let id = Pubkey::default(); + assert!( + erasure::generate_coding(&id, &mut window, offset as u64, num_blobs, &mut index) + .is_ok() + ); + assert_eq!(index, (erasure::NUM_DATA - erasure::NUM_CODING) as u64); + + println!("** after-gen-coding:"); + print_window(&window); + + println!("** whack data block:"); + // test erasing a data block + let erase_offset = offset; + // Create a hole in the window + let refwindow = window[erase_offset].data.clone(); + window[erase_offset].data = None; + print_window(&window); + + // put junk in the tails, simulates re-used blobs + scramble_window_tails(&mut window, num_blobs); + + // Recover it from coding + assert!(erasure::recover(&id, &mut window, (offset + WINDOW_SIZE) as u64, offset,).is_ok()); + println!("** after-recover:"); + print_window(&window); + + { + // Check the result, block is here to drop locks + + let window_l = window[erase_offset].data.clone().unwrap(); + let window_l2 = window_l.read().unwrap(); + let ref_l = refwindow.clone().unwrap(); + let ref_l2 = ref_l.read().unwrap(); + + assert_eq!(window_l2.meta.size, ref_l2.meta.size); + assert_eq!( + window_l2.data[..window_l2.meta.size], + ref_l2.data[..window_l2.meta.size] + ); + assert_eq!(window_l2.meta.addr, ref_l2.meta.addr); + assert_eq!(window_l2.meta.port, ref_l2.meta.port); + assert_eq!(window_l2.meta.v6, ref_l2.meta.v6); + assert_eq!( + window_l2.index().unwrap(), + (erase_offset + WINDOW_SIZE) as u64 + ); + } + + println!("** whack coding block and data block"); + // tests erasing a coding block and a data block + let erase_offset = offset + erasure::NUM_DATA - erasure::NUM_CODING; + // Create a hole in the window + let refwindow = window[erase_offset].data.clone(); + window[erase_offset].data = None; + window[erase_offset].coding = None; + + print_window(&window); + + // Recover it from coding + assert!(erasure::recover(&id, &mut window, (offset + WINDOW_SIZE) as u64, offset,).is_ok()); + println!("** after-recover:"); + print_window(&window); + + { + // Check the result, block is here to drop locks + let window_l = window[erase_offset].data.clone().unwrap(); + let window_l2 = window_l.read().unwrap(); + let ref_l = refwindow.clone().unwrap(); + let ref_l2 = ref_l.read().unwrap(); + assert_eq!(window_l2.meta.size, ref_l2.meta.size); + assert_eq!( + window_l2.data[..window_l2.meta.size], + ref_l2.data[..window_l2.meta.size] + ); + assert_eq!(window_l2.meta.addr, ref_l2.meta.addr); + assert_eq!(window_l2.meta.port, ref_l2.meta.port); + assert_eq!(window_l2.meta.v6, ref_l2.meta.v6); + assert_eq!( + window_l2.index().unwrap(), + (erase_offset + WINDOW_SIZE) as u64 + ); + } + + println!("** make stale data block index"); + // tests erasing a coding block + let erase_offset = offset; + // Create a hole in the window by making the blob's index stale + let refwindow = window[offset].data.clone(); + if let Some(blob) = &window[erase_offset].data { + blob.write() + .unwrap() + .set_index(erase_offset as u64) + .unwrap(); // this also writes to refwindow... + } + print_window(&window); + + // Recover it from coding + assert!(erasure::recover(&id, &mut window, (offset + WINDOW_SIZE) as u64, offset,).is_ok()); + println!("** after-recover:"); + print_window(&window); + + // fix refwindow, we wrote to it above... + if let Some(blob) = &refwindow { + blob.write() + .unwrap() + .set_index((erase_offset + WINDOW_SIZE) as u64) + .unwrap(); // this also writes to refwindow... + } + + { + // Check the result, block is here to drop locks + let window_l = window[erase_offset].data.clone().unwrap(); + let window_l2 = window_l.read().unwrap(); + let ref_l = refwindow.clone().unwrap(); + let ref_l2 = ref_l.read().unwrap(); + assert_eq!(window_l2.meta.size, ref_l2.meta.size); + assert_eq!( + window_l2.data[..window_l2.meta.size], + ref_l2.data[..window_l2.meta.size] + ); + assert_eq!(window_l2.index().unwrap(), ref_l2.index().unwrap()); + assert_eq!(window_l2.slot().unwrap(), ref_l2.slot().unwrap()); + assert_eq!(window_l2.meta.addr, ref_l2.meta.addr); + assert_eq!(window_l2.meta.port, ref_l2.meta.port); + assert_eq!(window_l2.meta.v6, ref_l2.meta.v6); + assert_eq!( + window_l2.index().unwrap(), + (erase_offset + WINDOW_SIZE) as u64 + ); + } + } + + // //TODO This needs to be reworked + // #[test] + // #[ignore] + // pub fn test_window_recover() { + // logger::setup(); + // let offset = 4; + // let data_len = 16; + // let num_blobs = erasure::NUM_DATA + 2; + // let (mut window, blobs_len) = generate_window(data_len, offset, num_blobs); + // println!("** after-gen:"); + // print_window(&window); + // assert!(erasure::generate_coding(&mut window, offset, blobs_len).is_ok()); + // println!("** after-coding:"); + // print_window(&window); + // let refwindow = window[offset + 1].clone(); + // window[offset + 1] = None; + // window[offset + 2] = None; + // window[offset + erasure::SET_SIZE + 3] = None; + // window[offset + (2 * erasure::SET_SIZE) + 0] = None; + // window[offset + (2 * erasure::SET_SIZE) + 1] = None; + // window[offset + (2 * erasure::SET_SIZE) + 2] = None; + // let window_l0 = &(window[offset + (3 * erasure::SET_SIZE)]).clone().unwrap(); + // window_l0.write().unwrap().data[0] = 55; + // println!("** after-nulling:"); + // print_window(&window); + // assert!(erasure::recover(&mut window, offset, offset + blobs_len).is_ok()); + // println!("** after-restore:"); + // print_window(&window); + // let window_l = window[offset + 1].clone().unwrap(); + // let ref_l = refwindow.clone().unwrap(); + // assert_eq!( + // window_l.read().unwrap().data()[..data_len], + // ref_l.read().unwrap().data()[..data_len] + // ); + // } +} diff --git a/book/favicon.png b/book/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..a5b1aa16c4dcb6c872cb5af799bfc9b5552c7b9e GIT binary patch literal 5679 zcmaKwcQhN&+sD<65fVyMrKCtvVz>54LX99&YDJBr_J}>&8nH)>5R@84MNz9ITCG*P zX0<44?^;#6_4>Wr#V&J8m1AkprJ&CCG60hiIs8wiCL z#DZiT`@6@!`1|?!W$KnZXnX^2+=d_hxD?}hboJ~h?Z3p^&%c2CYBPHWm3|C7?4S)S z<=8>rwHV}maohw}kN+bB?OodAZp_b^;z<(fft-=472pgWXh~>V9TV@+f@jN9eBDb8 z7XHu2>hath5j`z|b;Ad2K;UB)cLZ_o3=_`cn$3dBvrK8=_c@&)Co@h@Pv4py{4-VG zZ5jW&6>DrRtr<|%Fe?ncBXZ&-%N-M8DAk#!lh2}pj>hTyRLw-Y@j^l@;vBq_lsS75 z)qdTT8D@D}Dii_QlsRP&RSJqu0GI`03S)>yR&aVKQRp}j!xeZZtz;w73{iCK&bLCE z+>)g!H+&K+40SZ~GUg@L=NTM^3f#ws*h)fa+1^TLfkKX0cOC&h^&$#734jCMlkosy zmd-3tD>m+NOhmzi8_WaR-6=gM$YT}*bmNV`pA@5OxA3<=Q1`U?J!Hn_;zv{eIRg zOFf{KJb(ijx*?h43sdXmvg??24dL#E3BQSwKa7K16Vfe7C{X5}#}S=h395Gr8tI#4 zrrXi%UzXQ+xVq%dG=XgJJ;R{?RK?)mMzlW`d>wIS2I$CY#jkO8)qKV{zRyiyxA_TK zpQ2lmzVdp}WSg;LaU?Luyp_y8A4v1Y8KL-9=|}S1(8e%kW&{6^S&9Y3fBcboDg50- z`Qj6Q;g!Y|dsX7SLNWgRDu$7&lu2dOtHL?%0QHIW&DjDT7KgaL<8ddx_`;gAe(ws7 z-bYcVLmTf4KO~Ht8r;G~ib2Ktq~*M&J~r~gyFUF|ZM9V2>rcS^ptfS351 zr^~D87q>{pza=Os8TRei`It?ZT)3}ex4)PPl1_Fehm)dZIwaonoF|^0CiXot?0#U$ zd5Adv*yoKC^y4PFMxCOA8ulM`^RI8#3wBzVU{j!kLT|MECdCNN=aDDAG_rx=3hu6$ zi1)J%CH^R5KRB~a|6bd=WO0y&cZNbYs->zXoc}LP!BQ$^+m^Jzm!h)A`YihVcA;wi z9)s{xe;%b;ebpeVKBKl|>mpk5joFL|HXZ1lp&kaPeVQf5Uxs>C2@2177dFce>so`9xUBtGlx&g~%@+&~Zm z@e1S@Iyo>1YR`3y$<2*6aqWJ=_`%-{&?=mt1OC;1V`%O z&^8C?*X0=-A0g8~BgayHpBjPJ@y^*uIMl0q)cyV>`%Di24jwgadCc9rVl~j*gM{0T zA6q8Os&y!=+Ev&iHf)^A4kIzqR`#Iipif;;qX(qHz*h@6-1BOEG}}%)RqL(K`(wU( z#l|@c@~WaG&qonFH#Xmn)&X{HF^~K${Bvu7O%P{j@wSZ%`@#=Gh|SEJU2tFr+F2(T zcrL`|{;d7=CtrnsD!a=V4*u3FAu6u7vt-@Y{H*ryGs3Cc9o>ee1e~Jw37ZeVJZ4z9Zd6pnbZ!bP z%|#tEI7|PX`)tc=y8Z|1hx2t6>5t@BxO(N2%fGmGJnc@n8R|tJtE&99CWQ1pUr=7m z=ow1W8xJZMjpKK|FCE81i=Fy;W9lwx05p1+WRbNnY-#w;JUJsx{?}3MPQIJPwg8FC z$d0DFq^mT&;v-*W#(x z-c4x23zgd^)`AE}lH}(uS|;!)9oKu(VJvy5+iI=m4;#juwwYH`YjzcVBs$$xA10z{y@6Kl86p_m*8kBhNqam&f=36^8dj+ARQYpWvJvTX|g z6o*}_2y=Dr5qcHHpAjp@1eJ}kWJrV&674HNfzCflqP!Gqm`kE|ELr1UFHE)F`9|Ss zx2IBLY|M1uvna)hF~d3Y4C8=FTFAb_=$R*!c~eBm+a6pSE2tVx&hb#-g2Iss`rIP9_fcL1t-Z ze6D|FAb+TAsvDKg@}y{4addJZvYIdHzQ+YmAmLFHfwa~I)|0&tqQdDUC|||%0(39F zfrg&C3I-_NCGCKWw-j==dqor;ka|kgY1|V8>5>2cM1fF{-sOt5W z{Eh1on(hz1l~!@<9-@1gIz2oHJN>S_ZI)7f?TYFm^wG|E2GjS5!Z{C)$Sle>#18MQ z+_t%oPijKw)uHoTsN1XbG#jln9|uWn(rFrn_Y9VzSPR~7BfE`dQj5*MsiH_Wm%(IU zL%FUDj8?#}%!4pOzck@IR)0I2y=`d!Akki`Y{Rph{`Z<$Q9l5D`&wNj=2dK%*$L&54NIbgN=U^o3BtI_6evC!BD z?bbZ-D}OVqX8fpVwk1DYyRv0t@1{Af5eQLy5|}z(aIFfuA8}m&bDBj`;>jIw1FqaG z^KI)+KtMzFp~vf+&cac7Pye)I2EhJD{x8BevfmnOP;fIv^3W@0B>%Qqf4GR*Ih%BOR=&zaPoU8z6w_x9PvK;)>#KEj~a~X4Dv!a{ee` zV0)d>M46CLep<5_RqAb4Qhc(b@g11dkpj`sArRP;h5S3=yfoYm2#;J-7(-RiP#?O4 zbg%gIx9;@IzmAoe*-9Tp&Fr)}HjzNjX#jdsuOIPsjm zo0I99MoF-G!yPA%Jck#tBV;caGv1YQib1!l)c=`76%XAmV4l_hp&&KyG z+kU33`F{q&hQZUx)ra{H*aH|uHy1nKoa9DgfupniH??CgW;L(#*9<0TG1B*X;g!R{ zFkH^9eJxU(@~Oz&r^j1cRBemNAPBJXB=4dpWHW#Z*}-dm^GW{Tp}so&?J2uPv)Zy- zUU4V=bP|T3Ri0f|H%gWtGoE=~eV8}1{yQ`|UC0BiQ>(J>Gl15KrR#X{`anyV8~dJX zC92B+V0Bj%N%1ue!<7pfA0|@W{pg^<{WUmSHu88rE6c@lUnP^xxiTf zzi!h%l#Pp{Ck!RIekpB9SokK)OzogpDOA;(==E2}jNw-j##lySot{-1`eA7#Pc?s# zqi3M@C#OxG1*@D~F^q&tY_E(1BRt;fU3#WL8C>z<`Ku#+xLJ84aDSCL0#7bq!jxpW zz)TQM(kjd>4BbL39ZDi|FZMa}2ffP<(RQI(XsNOOK%Ul6gN~2aBeb=)UlE#@TU_S` zaL&ArLdNGRwX!nhY9htiE{^Mx`p{va8nHj|FEJ;2#sE0-{-66 z7TG`O9aPjU&;AO~ut})k^7^tTpqejGV`xz3fh#)c3iA=g)}|tQuN5Xj3;}t>rK_{% zwr?Jfuk9*m<@9EOq? zC~SHvu*vE3q!9)BVQ1R=;_Gb$R0Du`sizUw%^Hr^rT$~LRgzDi`&$!uY(sm){^0({ zpBD`ekM$vvNi88E2IJXs9~@(({8wec=QErQ%t}i6d|s_XCJ{5;SmMN*LF$N>Fxdj2 z&znWpNwbJ$Zw>vfu5fEMR|sp6DuBGq_%hq2G;=Yj!|p#isk8WbuD{;;hBQltyj*FM zr0$<5k zwWt5ZJ)0rzW4?BpPF4Pv(B4+W-a9z^dR+Hb=8~cZ~q&eZkbYsEQnGId%v2raT11iS>D&<#Db z9dk%6P$rs>A0G{<8&!O=5>`V<`PlAqV;d+0iVpVFBdZdv$xwbB}zcmDY!XN#! z)oU{{s`)@SGxFyyUzIHIF#oF-C zdVsun511^=T35BSjB%RVCO)R#LTF#{keUnxsJBknytTSZ_HgCS#!#}cFUoNZn(BGm5(Vf`; zn!+nt)Gd^b{er3mjVMY&Qn|?&difi0fdIfUIQC$&qYI2ZqBYi@7p*79kpYtPU`P~B z`r7e!bdsPQGM)sI(m8po`hcrz zlRf$`Q@+iO2-l!suX2WAw1p}Q5Gg$&uj139v*-bjdgqdhTfzWDI#QWlLsT<(`@$x{ zrq25LV=RQuVUe=1xyOg$4y(^jkfr~dpQ=B86}$vKBhPPo;dYUizZtlKBT}DhJqvl5 z*wd*uB=jIstOa1AN5G`x=JftS#ctecT_jpSA!nF{`!bL7B zr7;#NX8gSM&>Zr)hSeg3HAf!6p&eUTSXiFB#^NfZxClok&YLkTsW3RqM=;_EDP^Mn zw&J(8wt#LTOt!oj(X~wlr$x|XVMKSXa(etHtMC^O&3p*~E1vL&U3WiZNjbxB zPRi5++1NZ6OC7~7d5P@WWxsrV7d3U`(#+}c>hrXlw8?VFLCJo70{9YyYBIY7$=e4n z_FTPA74839$pPh*_!lO@h^YmMhrLW(-co+j%%Umn^vlz|BFd@o!JEUfej6D`tYh88 z!xOp88&kL_omR|hhQy%VV570%z31uE7nsb&=9lx0f~QVs}&QZli(7C+4WInF(c~1G?Ay}@=Js6#Ta&S*M8tzG+=nyvS4C!u0HG7 zKX=aXY38nuJz&^FN?mu3@F1#E%R_S9N%lmfUjlL$z@X6N1%x{Wxw=n$=IRLiRFDx) zC-B0x)S*v13dEu{-17fX(EmoH?UHAPVV9_q-f;^!OLHAu5MU}DO#@UF!Y1N>0Q+#1 A;{X5v literal 0 HcmV?d00001 diff --git a/book/fetch_stage.rs b/book/fetch_stage.rs new file mode 100644 index 00000000000000..34d6dc3f073434 --- /dev/null +++ b/book/fetch_stage.rs @@ -0,0 +1,48 @@ +//! The `fetch_stage` batches input from a UDP socket and sends it to a channel. + +use service::Service; +use std::net::UdpSocket; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc::channel; +use std::sync::Arc; +use std::thread::{self, JoinHandle}; +use streamer::{self, PacketReceiver}; + +pub struct FetchStage { + exit: Arc, + thread_hdls: Vec>, +} + +impl FetchStage { + pub fn new(sockets: Vec, exit: Arc) -> (Self, PacketReceiver) { + let tx_sockets = sockets.into_iter().map(Arc::new).collect(); + Self::new_multi_socket(tx_sockets, exit) + } + pub fn new_multi_socket( + sockets: Vec>, + exit: Arc, + ) -> (Self, PacketReceiver) { + let (sender, receiver) = channel(); + let thread_hdls: Vec<_> = sockets + .into_iter() + .map(|socket| streamer::receiver(socket, exit.clone(), sender.clone(), "fetch-stage")) + .collect(); + + (FetchStage { exit, thread_hdls }, receiver) + } + + pub fn close(&self) { + self.exit.store(true, Ordering::Relaxed); + } +} + +impl Service for FetchStage { + type JoinReturnType = (); + + fn join(self) -> thread::Result<()> { + for thread_hdl in self.thread_hdls { + thread_hdl.join()?; + } + Ok(()) + } +} diff --git a/book/fullnode.html b/book/fullnode.html new file mode 100644 index 00000000000000..950d6f1aa4a6f4 --- /dev/null +++ b/book/fullnode.html @@ -0,0 +1,223 @@ + + + + + + Fullnode - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

Fullnode

+

Fullnode block diagrams

+

Pipelining

+

The fullnodes make extensive use of an optimization common in CPU design, +called pipelining. Pipelining is the right tool for the job when there's a +stream of input data that needs to be processed by a sequence of steps, and +there's different hardware responsible for each. The quintessential example is +using a washer and dryer to wash/dry/fold several loads of laundry. Washing +must occur before drying and drying before folding, but each of the three +operations is performed by a separate unit. To maximize efficiency, one creates +a pipeline of stages. We'll call the washer one stage, the dryer another, and +the folding process a third. To run the pipeline, one adds a second load of +laundry to the washer just after the first load is added to the dryer. +Likewise, the third load is added to the washer after the second is in the +dryer and the first is being folded. In this way, one can make progress on +three loads of laundry simultaneously. Given infinite loads, the pipeline will +consistently complete a load at the rate of the slowest stage in the pipeline.

+

Pipelining in the fullnode

+

The fullnode contains two pipelined processes, one used in leader mode called +the Tpu and one used in validator mode called the Tvu. In both cases, the +hardware being pipelined is the same, the network input, the GPU cards, the CPU +cores, writes to disk, and the network output. What it does with that hardware +is different. The Tpu exists to create ledger entries whereas the Tvu exists +to validate them.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/fullnode.rs b/book/fullnode.rs new file mode 100644 index 00000000000000..4feddd3b2ddd8f --- /dev/null +++ b/book/fullnode.rs @@ -0,0 +1,1040 @@ +//! The `fullnode` module hosts all the fullnode microservices. + +use bank::Bank; +use broadcast_stage::BroadcastStage; +use cluster_info::{ClusterInfo, Node, NodeInfo}; +use leader_scheduler::LeaderScheduler; +use ledger::read_ledger; +use ncp::Ncp; +use rpc::JsonRpcService; +use rpc_pubsub::PubSubService; +use service::Service; +use signature::{Keypair, KeypairUtil}; +use solana_sdk::hash::Hash; +use solana_sdk::timing::timestamp; +use std::net::UdpSocket; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, RwLock}; +use std::thread::Result; +use tpu::{Tpu, TpuReturnType}; +use tpu_forwarder::TpuForwarder; +use tvu::{Tvu, TvuReturnType}; +use untrusted::Input; +use window::{new_window, SharedWindow}; + +pub enum NodeRole { + Leader(LeaderServices), + Validator(ValidatorServices), +} + +pub struct LeaderServices { + tpu: Tpu, + broadcast_stage: BroadcastStage, +} + +impl LeaderServices { + fn new(tpu: Tpu, broadcast_stage: BroadcastStage) -> Self { + LeaderServices { + tpu, + broadcast_stage, + } + } + + pub fn join(self) -> Result> { + self.broadcast_stage.join()?; + self.tpu.join() + } + + pub fn is_exited(&self) -> bool { + self.tpu.is_exited() + } + + pub fn exit(&self) -> () { + self.tpu.exit(); + } +} + +pub struct ValidatorServices { + tvu: Tvu, + tpu_forwarder: TpuForwarder, +} + +impl ValidatorServices { + fn new(tvu: Tvu, tpu_forwarder: TpuForwarder) -> Self { + ValidatorServices { tvu, tpu_forwarder } + } + + pub fn join(self) -> Result> { + let ret = self.tvu.join(); // TVU calls the shots, we wait for it to shut down + self.tpu_forwarder.join()?; + ret + } + + pub fn is_exited(&self) -> bool { + self.tvu.is_exited() + } + + pub fn exit(&self) -> () { + self.tvu.exit() + } +} + +#[derive(Debug)] +pub enum FullnodeReturnType { + LeaderToValidatorRotation, + ValidatorToLeaderRotation, +} + +pub struct Fullnode { + pub node_role: Option, + keypair: Arc, + vote_account_keypair: Arc, + exit: Arc, + rpc_service: Option, + rpc_pubsub_service: Option, + ncp: Ncp, + bank: Arc, + cluster_info: Arc>, + ledger_path: String, + sigverify_disabled: bool, + shared_window: SharedWindow, + replicate_socket: Vec, + repair_socket: UdpSocket, + retransmit_socket: UdpSocket, + transaction_sockets: Vec, + broadcast_socket: UdpSocket, + rpc_addr: SocketAddr, + rpc_pubsub_addr: SocketAddr, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "PascalCase")] +/// Fullnode configuration to be stored in file +pub struct Config { + pub node_info: NodeInfo, + pkcs8: Vec, + vote_account_pkcs8: Vec, +} + +impl Config { + pub fn new(bind_addr: &SocketAddr, pkcs8: Vec, vote_account_pkcs8: Vec) -> Self { + let keypair = + Keypair::from_pkcs8(Input::from(&pkcs8)).expect("from_pkcs8 in fullnode::Config new"); + let pubkey = keypair.pubkey(); + let node_info = NodeInfo::new_with_pubkey_socketaddr(pubkey, bind_addr); + Config { + node_info, + pkcs8, + vote_account_pkcs8, + } + } + pub fn keypair(&self) -> Keypair { + Keypair::from_pkcs8(Input::from(&self.pkcs8)) + .expect("from_pkcs8 in fullnode::Config keypair") + } + pub fn vote_account_keypair(&self) -> Keypair { + Keypair::from_pkcs8(Input::from(&self.vote_account_pkcs8)) + .expect("from_pkcs8 in fullnode::Config vote_account_keypair") + } +} + +impl Fullnode { + pub fn new( + node: Node, + ledger_path: &str, + keypair: Arc, + vote_account_keypair: Arc, + leader_addr: Option, + sigverify_disabled: bool, + leader_scheduler: LeaderScheduler, + rpc_port: Option, + ) -> Self { + let leader_scheduler = Arc::new(RwLock::new(leader_scheduler)); + + info!("creating bank..."); + + let (bank, entry_height, last_entry_id) = + Self::new_bank_from_ledger(ledger_path, leader_scheduler); + + info!("creating networking stack..."); + let local_gossip_addr = node.sockets.gossip.local_addr().unwrap(); + + info!( + "starting... local gossip address: {} (advertising {})", + local_gossip_addr, node.info.ncp + ); + let mut rpc_addr = node.info.rpc; + if let Some(port) = rpc_port { + rpc_addr.set_port(port); + } + + let leader_info = leader_addr.map(|i| NodeInfo::new_entry_point(&i)); + let server = Self::new_with_bank( + keypair, + vote_account_keypair, + bank, + entry_height, + &last_entry_id, + node, + leader_info.as_ref(), + ledger_path, + sigverify_disabled, + rpc_port, + ); + + match leader_addr { + Some(leader_addr) => { + info!( + "validator ready... rpc address: {}, connected to: {}", + rpc_addr, leader_addr + ); + } + None => { + info!("leader ready... rpc address: {}", rpc_addr); + } + } + + server + } + + /// Create a fullnode instance acting as a leader or validator. + #[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] + pub fn new_with_bank( + keypair: Arc, + vote_account_keypair: Arc, + bank: Bank, + entry_height: u64, + last_entry_id: &Hash, + mut node: Node, + bootstrap_leader_info_option: Option<&NodeInfo>, + ledger_path: &str, + sigverify_disabled: bool, + rpc_port: Option, + ) -> Self { + let mut rpc_addr = node.info.rpc; + let mut rpc_pubsub_addr = node.info.rpc_pubsub; + // Use custom RPC port, if provided (`Some(port)`) + // RPC port may be any valid open port on the node + // If rpc_port == `None`, node will listen on the ports set in NodeInfo + if let Some(port) = rpc_port { + rpc_addr.set_port(port); + node.info.rpc = rpc_addr; + rpc_pubsub_addr.set_port(port + 1); + node.info.rpc_pubsub = rpc_pubsub_addr; + } + + let exit = Arc::new(AtomicBool::new(false)); + let bank = Arc::new(bank); + + let window = new_window(32 * 1024); + let shared_window = Arc::new(RwLock::new(window)); + node.info.wallclock = timestamp(); + let cluster_info = Arc::new(RwLock::new(ClusterInfo::new(node.info))); + + let (rpc_service, rpc_pubsub_service) = + Self::startup_rpc_services(rpc_addr, rpc_pubsub_addr, &bank, &cluster_info); + + let ncp = Ncp::new( + &cluster_info, + shared_window.clone(), + Some(ledger_path), + node.sockets.gossip, + exit.clone(), + ); + + // Insert the bootstrap leader info, should only be None if this node + // is the bootstrap leader + if let Some(bootstrap_leader_info) = bootstrap_leader_info_option { + cluster_info + .write() + .unwrap() + .insert_info(bootstrap_leader_info.clone()); + } + + // Get the scheduled leader + let (scheduled_leader, leader_slot) = bank + .get_current_leader() + .expect("Leader not known after processing bank"); + + cluster_info.write().unwrap().set_leader(scheduled_leader); + let node_role = if scheduled_leader != keypair.pubkey() { + // Start in validator mode. + let tvu = Tvu::new( + keypair.clone(), + vote_account_keypair.clone(), + &bank, + entry_height, + *last_entry_id, + cluster_info.clone(), + shared_window.clone(), + node.sockets + .replicate + .iter() + .map(|s| s.try_clone().expect("Failed to clone replicate sockets")) + .collect(), + node.sockets + .repair + .try_clone() + .expect("Failed to clone repair socket"), + node.sockets + .retransmit + .try_clone() + .expect("Failed to clone retransmit socket"), + Some(ledger_path), + ); + let tpu_forwarder = TpuForwarder::new( + node.sockets + .transaction + .iter() + .map(|s| s.try_clone().expect("Failed to clone transaction sockets")) + .collect(), + cluster_info.clone(), + ); + + let validator_state = ValidatorServices::new(tvu, tpu_forwarder); + Some(NodeRole::Validator(validator_state)) + } else { + let max_tick_height = { + let ls_lock = bank.leader_scheduler.read().unwrap(); + ls_lock.max_height_for_leader(bank.tick_height()) + }; + // Start in leader mode. + let (tpu, entry_receiver, tpu_exit) = Tpu::new( + &bank, + Default::default(), + node.sockets + .transaction + .iter() + .map(|s| s.try_clone().expect("Failed to clone transaction sockets")) + .collect(), + ledger_path, + sigverify_disabled, + max_tick_height, + last_entry_id, + ); + + let broadcast_stage = BroadcastStage::new( + node.sockets + .broadcast + .try_clone() + .expect("Failed to clone broadcast socket"), + cluster_info.clone(), + shared_window.clone(), + entry_height, + leader_slot, + entry_receiver, + max_tick_height, + bank.tick_height(), + tpu_exit, + ); + let leader_state = LeaderServices::new(tpu, broadcast_stage); + Some(NodeRole::Leader(leader_state)) + }; + + Fullnode { + keypair, + vote_account_keypair, + cluster_info, + shared_window, + bank, + sigverify_disabled, + ncp, + rpc_service: Some(rpc_service), + rpc_pubsub_service: Some(rpc_pubsub_service), + node_role, + ledger_path: ledger_path.to_owned(), + exit, + replicate_socket: node.sockets.replicate, + repair_socket: node.sockets.repair, + retransmit_socket: node.sockets.retransmit, + transaction_sockets: node.sockets.transaction, + broadcast_socket: node.sockets.broadcast, + rpc_addr, + rpc_pubsub_addr, + } + } + + fn leader_to_validator(&mut self) -> Result<()> { + // Close down any services that could have a reference to the bank + if self.rpc_service.is_some() { + let old_rpc_service = self.rpc_service.take().unwrap(); + old_rpc_service.close()?; + } + + if self.rpc_pubsub_service.is_some() { + let old_rpc_pubsub_service = self.rpc_pubsub_service.take().unwrap(); + old_rpc_pubsub_service.close()?; + } + + // Correctness check: Ensure that references to the bank and leader scheduler are no + // longer held by any running thread + let mut new_leader_scheduler = self.bank.leader_scheduler.read().unwrap().clone(); + + // Clear the leader scheduler + new_leader_scheduler.reset(); + + let (new_bank, scheduled_leader, entry_height, last_entry_id) = { + // TODO: We can avoid building the bank again once RecordStage is + // integrated with BankingStage + let (new_bank, entry_height, last_id) = Self::new_bank_from_ledger( + &self.ledger_path, + Arc::new(RwLock::new(new_leader_scheduler)), + ); + + let new_bank = Arc::new(new_bank); + let (scheduled_leader, _) = new_bank + .get_current_leader() + .expect("Scheduled leader should exist after rebuilding bank"); + + (new_bank, scheduled_leader, entry_height, last_id) + }; + + self.cluster_info + .write() + .unwrap() + .set_leader(scheduled_leader); + + // Spin up new versions of all the services that relied on the bank, passing in the + // new bank + let (rpc_service, rpc_pubsub_service) = Self::startup_rpc_services( + self.rpc_addr, + self.rpc_pubsub_addr, + &new_bank, + &self.cluster_info, + ); + self.rpc_service = Some(rpc_service); + self.rpc_pubsub_service = Some(rpc_pubsub_service); + self.bank = new_bank; + + // In the rare case that the leader exited on a multiple of seed_rotation_interval + // when the new leader schedule was being generated, and there are no other validators + // in the active set, then the leader scheduler will pick the same leader again, so + // check for that + if scheduled_leader == self.keypair.pubkey() { + let tick_height = self.bank.tick_height(); + self.validator_to_leader(tick_height, entry_height, last_entry_id); + Ok(()) + } else { + let tvu = Tvu::new( + self.keypair.clone(), + self.vote_account_keypair.clone(), + &self.bank, + entry_height, + last_entry_id, + self.cluster_info.clone(), + self.shared_window.clone(), + self.replicate_socket + .iter() + .map(|s| s.try_clone().expect("Failed to clone replicate sockets")) + .collect(), + self.repair_socket + .try_clone() + .expect("Failed to clone repair socket"), + self.retransmit_socket + .try_clone() + .expect("Failed to clone retransmit socket"), + Some(&self.ledger_path), + ); + let tpu_forwarder = TpuForwarder::new( + self.transaction_sockets + .iter() + .map(|s| s.try_clone().expect("Failed to clone transaction sockets")) + .collect(), + self.cluster_info.clone(), + ); + + let validator_state = ValidatorServices::new(tvu, tpu_forwarder); + self.node_role = Some(NodeRole::Validator(validator_state)); + Ok(()) + } + } + + fn validator_to_leader(&mut self, tick_height: u64, entry_height: u64, last_id: Hash) { + self.cluster_info + .write() + .unwrap() + .set_leader(self.keypair.pubkey()); + + let max_tick_height = { + let ls_lock = self.bank.leader_scheduler.read().unwrap(); + ls_lock.max_height_for_leader(tick_height) + }; + + let (tpu, blob_receiver, tpu_exit) = Tpu::new( + &self.bank, + Default::default(), + self.transaction_sockets + .iter() + .map(|s| s.try_clone().expect("Failed to clone transaction sockets")) + .collect(), + &self.ledger_path, + self.sigverify_disabled, + max_tick_height, + // We pass the last_entry_id from the replicate stage because we can't trust that + // the window didn't overwrite the slot at for the last entry that the replicate stage + // processed. We also want to avoid reading processing the ledger for the last id. + &last_id, + ); + + let broadcast_stage = BroadcastStage::new( + self.broadcast_socket + .try_clone() + .expect("Failed to clone broadcast socket"), + self.cluster_info.clone(), + self.shared_window.clone(), + entry_height, + 0, // TODO: get real leader slot from leader_scheduler + blob_receiver, + max_tick_height, + tick_height, + tpu_exit, + ); + let leader_state = LeaderServices::new(tpu, broadcast_stage); + self.node_role = Some(NodeRole::Leader(leader_state)); + } + + pub fn check_role_exited(&self) -> bool { + match self.node_role { + Some(NodeRole::Leader(ref leader_services)) => leader_services.is_exited(), + Some(NodeRole::Validator(ref validator_services)) => validator_services.is_exited(), + None => false, + } + } + + pub fn handle_role_transition(&mut self) -> Result> { + let node_role = self.node_role.take(); + match node_role { + Some(NodeRole::Leader(leader_services)) => match leader_services.join()? { + Some(TpuReturnType::LeaderRotation) => { + self.leader_to_validator()?; + Ok(Some(FullnodeReturnType::LeaderToValidatorRotation)) + } + _ => Ok(None), + }, + Some(NodeRole::Validator(validator_services)) => match validator_services.join()? { + Some(TvuReturnType::LeaderRotation(tick_height, entry_height, last_entry_id)) => { + //TODO: Fix this to return actual poh height. + self.validator_to_leader(tick_height, entry_height, last_entry_id); + Ok(Some(FullnodeReturnType::ValidatorToLeaderRotation)) + } + _ => Ok(None), + }, + None => Ok(None), + } + } + + //used for notifying many nodes in parallel to exit + pub fn exit(&self) { + self.exit.store(true, Ordering::Relaxed); + if let Some(ref rpc_service) = self.rpc_service { + rpc_service.exit(); + } + if let Some(ref rpc_pubsub_service) = self.rpc_pubsub_service { + rpc_pubsub_service.exit(); + } + match self.node_role { + Some(NodeRole::Leader(ref leader_services)) => leader_services.exit(), + Some(NodeRole::Validator(ref validator_services)) => validator_services.exit(), + _ => (), + } + } + + pub fn close(self) -> Result<(Option)> { + self.exit(); + self.join() + } + + pub fn new_bank_from_ledger( + ledger_path: &str, + leader_scheduler: Arc>, + ) -> (Bank, u64, Hash) { + let mut bank = Bank::new_with_builtin_programs(); + bank.leader_scheduler = leader_scheduler; + let entries = read_ledger(ledger_path, true).expect("opening ledger"); + let entries = entries + .map(|e| e.unwrap_or_else(|err| panic!("failed to parse entry. error: {}", err))); + info!("processing ledger..."); + + let (entry_height, last_entry_id) = bank.process_ledger(entries).expect("process_ledger"); + // entry_height is the network-wide agreed height of the ledger. + // initialize it from the input ledger + info!("processed {} ledger...", entry_height); + (bank, entry_height, last_entry_id) + } + + pub fn get_leader_scheduler(&self) -> &Arc> { + &self.bank.leader_scheduler + } + + fn startup_rpc_services( + rpc_addr: SocketAddr, + rpc_pubsub_addr: SocketAddr, + bank: &Arc, + cluster_info: &Arc>, + ) -> (JsonRpcService, PubSubService) { + let rpc_port = rpc_addr.port(); + let rpc_pubsub_port = rpc_pubsub_addr.port(); + // TODO: The RPC service assumes that there is a drone running on the leader + // Drone location/id will need to be handled a different way as soon as leader rotation begins + ( + JsonRpcService::new( + bank, + cluster_info, + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), rpc_port), + ), + PubSubService::new( + bank, + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), rpc_pubsub_port), + ), + ) + } +} + +impl Service for Fullnode { + type JoinReturnType = Option; + + fn join(self) -> Result> { + if let Some(rpc_service) = self.rpc_service { + rpc_service.join()?; + } + if let Some(rpc_pubsub_service) = self.rpc_pubsub_service { + rpc_pubsub_service.join()?; + } + + self.ncp.join()?; + + match self.node_role { + Some(NodeRole::Validator(validator_service)) => { + if let Some(TvuReturnType::LeaderRotation(_, _, _)) = validator_service.join()? { + return Ok(Some(FullnodeReturnType::ValidatorToLeaderRotation)); + } + } + Some(NodeRole::Leader(leader_service)) => { + if let Some(TpuReturnType::LeaderRotation) = leader_service.join()? { + return Ok(Some(FullnodeReturnType::LeaderToValidatorRotation)); + } + } + _ => (), + } + + Ok(None) + } +} + +#[cfg(test)] +mod tests { + use bank::Bank; + use cluster_info::Node; + use fullnode::{Fullnode, FullnodeReturnType, NodeRole, TvuReturnType}; + use leader_scheduler::{make_active_set_entries, LeaderScheduler, LeaderSchedulerConfig}; + use ledger::{create_tmp_genesis, create_tmp_sample_ledger, LedgerWriter}; + use packet::make_consecutive_blobs; + use service::Service; + use signature::{Keypair, KeypairUtil}; + use std::cmp; + use std::fs::remove_dir_all; + use std::net::UdpSocket; + use std::sync::mpsc::channel; + use std::sync::{Arc, RwLock}; + use streamer::responder; + + #[test] + fn validator_exit() { + let keypair = Keypair::new(); + let tn = Node::new_localhost_with_pubkey(keypair.pubkey()); + let (mint, validator_ledger_path) = + create_tmp_genesis("validator_exit", 10_000, keypair.pubkey(), 1000); + let mut bank = Bank::new(&mint); + let entry = tn.info.clone(); + let genesis_entries = &mint.create_entries(); + let entry_height = genesis_entries.len() as u64; + + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + entry.id, + ))); + bank.leader_scheduler = leader_scheduler; + + let last_id = bank.last_id(); + let v = Fullnode::new_with_bank( + Arc::new(keypair), + Arc::new(Keypair::new()), + bank, + entry_height, + &last_id, + tn, + Some(&entry), + &validator_ledger_path, + false, + None, + ); + v.close().unwrap(); + remove_dir_all(validator_ledger_path).unwrap(); + } + + #[test] + fn validator_parallel_exit() { + let mut ledger_paths = vec![]; + let vals: Vec = (0..2) + .map(|i| { + let keypair = Keypair::new(); + let tn = Node::new_localhost_with_pubkey(keypair.pubkey()); + let (mint, validator_ledger_path) = create_tmp_genesis( + &format!("validator_parallel_exit_{}", i), + 10_000, + keypair.pubkey(), + 1000, + ); + ledger_paths.push(validator_ledger_path.clone()); + let mut bank = Bank::new(&mint); + let entry = tn.info.clone(); + + let leader_scheduler = Arc::new(RwLock::new( + LeaderScheduler::from_bootstrap_leader(entry.id), + )); + bank.leader_scheduler = leader_scheduler; + + let entry_height = mint.create_entries().len() as u64; + let last_id = bank.last_id(); + Fullnode::new_with_bank( + Arc::new(keypair), + Arc::new(Keypair::new()), + bank, + entry_height, + &last_id, + tn, + Some(&entry), + &validator_ledger_path, + false, + None, + ) + }).collect(); + + //each validator can exit in parallel to speed many sequential calls to `join` + vals.iter().for_each(|v| v.exit()); + //while join is called sequentially, the above exit call notified all the + //validators to exit from all their threads + vals.into_iter().for_each(|v| { + v.join().unwrap(); + }); + + for path in ledger_paths { + remove_dir_all(path).unwrap(); + } + } + + #[test] + fn test_leader_to_leader_transition() { + // Create the leader node information + let bootstrap_leader_keypair = Keypair::new(); + let bootstrap_leader_node = + Node::new_localhost_with_pubkey(bootstrap_leader_keypair.pubkey()); + let bootstrap_leader_info = bootstrap_leader_node.info.clone(); + + // Make a mint and a genesis entries for leader ledger + let num_ending_ticks = 1; + let (_, bootstrap_leader_ledger_path, genesis_entries) = create_tmp_sample_ledger( + "test_leader_to_leader_transition", + 10_000, + num_ending_ticks, + bootstrap_leader_keypair.pubkey(), + 500, + ); + + let initial_tick_height = genesis_entries + .iter() + .skip(2) + .fold(0, |tick_count, entry| tick_count + entry.is_tick() as u64); + + // Create the common leader scheduling configuration + let num_slots_per_epoch = 3; + let leader_rotation_interval = 5; + let seed_rotation_interval = num_slots_per_epoch * leader_rotation_interval; + let active_window_length = 5; + + // Set the bootstrap height to be bigger than the initial tick height. + // Once the leader hits the bootstrap height ticks, because there are no other + // choices in the active set, this leader will remain the leader in the next + // epoch. In the next epoch, check that the same leader knows to shut down and + // restart as a leader again. + let bootstrap_height = initial_tick_height + 1; + let leader_scheduler_config = LeaderSchedulerConfig::new( + Some(bootstrap_height as u64), + Some(leader_rotation_interval), + Some(seed_rotation_interval), + Some(active_window_length), + ); + + // Start up the leader + let mut bootstrap_leader = Fullnode::new( + bootstrap_leader_node, + &bootstrap_leader_ledger_path, + Arc::new(bootstrap_leader_keypair), + Arc::new(Keypair::new()), + Some(bootstrap_leader_info.ncp), + false, + LeaderScheduler::new(&leader_scheduler_config), + None, + ); + + // Wait for the leader to transition, ticks should cause the leader to + // reach the height for leader rotation + match bootstrap_leader.handle_role_transition().unwrap() { + Some(FullnodeReturnType::LeaderToValidatorRotation) => (), + _ => { + panic!("Expected a leader transition"); + } + } + + match bootstrap_leader.node_role { + Some(NodeRole::Leader(_)) => (), + _ => { + panic!("Expected bootstrap leader to be a leader"); + } + } + } + + #[test] + fn test_wrong_role_transition() { + // Create the leader node information + let bootstrap_leader_keypair = Arc::new(Keypair::new()); + let bootstrap_leader_node = + Node::new_localhost_with_pubkey(bootstrap_leader_keypair.pubkey()); + let bootstrap_leader_info = bootstrap_leader_node.info.clone(); + + // Create the validator node information + let validator_keypair = Keypair::new(); + let validator_node = Node::new_localhost_with_pubkey(validator_keypair.pubkey()); + + // Make a common mint and a genesis entry for both leader + validator's ledgers + let num_ending_ticks = 1; + let (mint, bootstrap_leader_ledger_path, genesis_entries) = create_tmp_sample_ledger( + "test_wrong_role_transition", + 10_000, + num_ending_ticks, + bootstrap_leader_keypair.pubkey(), + 500, + ); + + let last_id = genesis_entries + .last() + .expect("expected at least one genesis entry") + .id; + + // Write the entries to the ledger that will cause leader rotation + // after the bootstrap height + let mut ledger_writer = LedgerWriter::open(&bootstrap_leader_ledger_path, false).unwrap(); + let (active_set_entries, validator_vote_account_keypair) = make_active_set_entries( + &validator_keypair, + &mint.keypair(), + &last_id, + &last_id, + num_ending_ticks, + ); + + let genesis_tick_height = genesis_entries + .iter() + .skip(2) + .fold(0, |tick_count, entry| tick_count + entry.is_tick() as u64) + + num_ending_ticks as u64; + ledger_writer.write_entries(&active_set_entries).unwrap(); + + // Create the common leader scheduling configuration + let num_slots_per_epoch = 3; + let leader_rotation_interval = 5; + let seed_rotation_interval = num_slots_per_epoch * leader_rotation_interval; + + // Set the bootstrap height exactly the current tick height, so that we can + // test if the bootstrap leader knows to immediately transition to a validator + // after parsing the ledger during startup + let bootstrap_height = genesis_tick_height; + let leader_scheduler_config = LeaderSchedulerConfig::new( + Some(bootstrap_height), + Some(leader_rotation_interval), + Some(seed_rotation_interval), + Some(genesis_tick_height), + ); + + // Test that a node knows to transition to a validator based on parsing the ledger + let leader_vote_account_keypair = Arc::new(Keypair::new()); + let bootstrap_leader = Fullnode::new( + bootstrap_leader_node, + &bootstrap_leader_ledger_path, + bootstrap_leader_keypair, + leader_vote_account_keypair, + Some(bootstrap_leader_info.ncp), + false, + LeaderScheduler::new(&leader_scheduler_config), + None, + ); + + match bootstrap_leader.node_role { + Some(NodeRole::Validator(_)) => (), + _ => { + panic!("Expected bootstrap leader to be a validator"); + } + } + + // Test that a node knows to transition to a leader based on parsing the ledger + let validator = Fullnode::new( + validator_node, + &bootstrap_leader_ledger_path, + Arc::new(validator_keypair), + Arc::new(validator_vote_account_keypair), + Some(bootstrap_leader_info.ncp), + false, + LeaderScheduler::new(&leader_scheduler_config), + None, + ); + + match validator.node_role { + Some(NodeRole::Leader(_)) => (), + _ => { + panic!("Expected node to be the leader"); + } + } + let _ignored = remove_dir_all(&bootstrap_leader_ledger_path); + } + + #[test] + fn test_validator_to_leader_transition() { + // Make a leader identity + let leader_keypair = Keypair::new(); + let leader_node = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); + let leader_id = leader_node.info.id; + let leader_ncp = leader_node.info.ncp; + + // Create validator identity + let num_ending_ticks = 1; + let (mint, validator_ledger_path, genesis_entries) = create_tmp_sample_ledger( + "test_validator_to_leader_transition", + 10_000, + num_ending_ticks, + leader_id, + 500, + ); + + let validator_keypair = Keypair::new(); + let validator_node = Node::new_localhost_with_pubkey(validator_keypair.pubkey()); + let validator_info = validator_node.info.clone(); + + let mut last_id = genesis_entries + .last() + .expect("expected at least one genesis entry") + .id; + + // Write two entries so that the validator is in the active set: + // + // 1) Give the validator a nonzero number of tokens + // Write the bootstrap entries to the ledger that will cause leader rotation + // after the bootstrap height + // + // 2) A vote from the validator + let mut ledger_writer = LedgerWriter::open(&validator_ledger_path, false).unwrap(); + let (active_set_entries, validator_vote_account_keypair) = + make_active_set_entries(&validator_keypair, &mint.keypair(), &last_id, &last_id, 0); + let initial_tick_height = genesis_entries + .iter() + .skip(2) + .fold(0, |tick_count, entry| tick_count + entry.is_tick() as u64); + let initial_non_tick_height = genesis_entries.len() as u64 - initial_tick_height; + let active_set_entries_len = active_set_entries.len() as u64; + last_id = active_set_entries.last().unwrap().id; + ledger_writer.write_entries(&active_set_entries).unwrap(); + let ledger_initial_len = genesis_entries.len() as u64 + active_set_entries_len; + + // Set the leader scheduler for the validator + let leader_rotation_interval = 10; + let num_bootstrap_slots = 2; + let bootstrap_height = num_bootstrap_slots * leader_rotation_interval; + + let leader_scheduler_config = LeaderSchedulerConfig::new( + Some(bootstrap_height), + Some(leader_rotation_interval), + Some(leader_rotation_interval * 2), + Some(bootstrap_height), + ); + + // Start the validator + let mut validator = Fullnode::new( + validator_node, + &validator_ledger_path, + Arc::new(validator_keypair), + Arc::new(validator_vote_account_keypair), + Some(leader_ncp), + false, + LeaderScheduler::new(&leader_scheduler_config), + None, + ); + + // Send blobs to the validator from our mock leader + let t_responder = { + let (s_responder, r_responder) = channel(); + let blob_sockets: Vec> = leader_node + .sockets + .replicate + .into_iter() + .map(Arc::new) + .collect(); + + let t_responder = responder( + "test_validator_to_leader_transition", + blob_sockets[0].clone(), + r_responder, + ); + + // Send the blobs out of order, in reverse. Also send an extra + // "extra_blobs" number of blobs to make sure the window stops in the right place. + let extra_blobs = cmp::max(leader_rotation_interval / 3, 1); + let total_blobs_to_send = bootstrap_height + extra_blobs; + let tvu_address = &validator_info.tvu; + let msgs = make_consecutive_blobs( + leader_id, + total_blobs_to_send, + ledger_initial_len, + last_id, + &tvu_address, + ).into_iter() + .rev() + .collect(); + s_responder.send(msgs).expect("send"); + t_responder + }; + + // Wait for validator to shut down tvu + let node_role = validator.node_role.take(); + match node_role { + Some(NodeRole::Validator(validator_services)) => { + let join_result = validator_services + .join() + .expect("Expected successful validator join"); + if let Some(TvuReturnType::LeaderRotation(tick_height, _, _)) = join_result { + assert_eq!(tick_height, bootstrap_height); + } else { + panic!("Expected validator to have exited due to leader rotation"); + } + } + _ => panic!("Role should not be leader"), + } + + // Check the validator ledger for the correct entry + tick heights, we should've + // transitioned after tick_height = bootstrap_height. + let (bank, entry_height, _) = Fullnode::new_bank_from_ledger( + &validator_ledger_path, + Arc::new(RwLock::new(LeaderScheduler::new(&leader_scheduler_config))), + ); + + assert_eq!(bank.tick_height(), bootstrap_height); + assert_eq!( + entry_height, + // Only the first genesis entry has num_hashes = 0, every other entry + // had num_hashes = 1 + bootstrap_height + active_set_entries_len + initial_non_tick_height, + ); + + // Shut down + t_responder.join().expect("responder thread join"); + validator.close().unwrap(); + remove_dir_all(&validator_ledger_path).unwrap(); + } +} diff --git a/book/highlight.css b/book/highlight.css new file mode 100644 index 00000000000000..c667e3a04449a3 --- /dev/null +++ b/book/highlight.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Dune Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Dune Comment */ +.hljs-comment, +.hljs-quote { + color: #AAA; +} + +/* Atelier-Dune Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #d73737; +} + +/* Atelier-Dune Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #b65611; +} + +/* Atelier-Dune Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #60ac39; +} + +/* Atelier-Dune Blue */ +.hljs-title, +.hljs-section { + color: #6684e1; +} + +/* Atelier-Dune Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #b854d4; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f1f1f1; + color: #6e6b5e; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/book/highlight.js b/book/highlight.js new file mode 100644 index 00000000000000..bb48d118ddd921 --- /dev/null +++ b/book/highlight.js @@ -0,0 +1,2 @@ +/*! highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */ +!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){s+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"
":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("diff",function(e){return{aliases:["patch"],c:[{cN:"meta",r:10,v:[{b:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"comment",v:[{b:/Index: /,e:/$/},{b:/={3,}/,e:/$/},{b:/^\-{3}/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+{3}/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"addition",b:"^\\!",e:"$"}]}});hljs.registerLanguage("nginx",function(e){var r={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+e.UIR}]},b={eW:!0,l:"[a-z/_]+",k:{literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[e.HCM,{cN:"string",c:[e.BE,r],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[r]},{cN:"regexp",c:[e.BE,r],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},r]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s+{",rB:!0,e:"{",c:[{cN:"section",b:e.UIR}],r:0},{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"attribute",b:e.UIR,starts:b}],r:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("objectivec",function(e){var t={cN:"built_in",b:"\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+"},_={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},i=/[a-zA-Z@][a-zA-Z0-9_]*/,n="@interface @class @protocol @implementation";return{aliases:["mm","objc","obj-c"],k:_,l:i,i:""}]}]},{cN:"class",b:"("+n.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:n,l:i,c:[e.UTM]},{b:"\\."+e.UIR,r:0}]}});hljs.registerLanguage("xml",function(s){var e="[A-Za-z0-9\\._:-]+",t={eW:!0,i:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0}]},{cN:"tag",b:"|$)",e:">",k:{name:"style"},c:[t],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"|$)",e:">",k:{name:"script"},c:[t],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}});hljs.registerLanguage("handlebars",function(e){var a={"builtin-name":"each in with if else unless bindattr action collection debugger log outlet template unbound view yield"};return{aliases:["hbs","html.hbs","html.handlebars"],cI:!0,sL:"xml",c:[e.C("{{!(--)?","(--)?}}"),{cN:"template-tag",b:/\{\{[#\/]/,e:/\}\}/,c:[{cN:"name",b:/[a-zA-Z\.-]+/,k:a,starts:{eW:!0,r:0,c:[e.QSM]}}]},{cN:"template-variable",b:/\{\{/,e:/\}\}/,k:a}]}});hljs.registerLanguage("ini",function(e){var b={cN:"string",c:[e.BE],v:[{b:"'''",e:"'''",r:10},{b:'"""',e:'"""',r:10},{b:'"',e:'"'},{b:"'",e:"'"}]};return{aliases:["toml"],cI:!0,i:/\S/,c:[e.C(";","$"),e.HCM,{cN:"section",b:/^\s*\[+/,e:/\]+/},{b:/^[a-z0-9\[\]_-]+\s*=\s*/,e:"$",rB:!0,c:[{cN:"attr",b:/[a-z0-9\[\]_-]+/},{b:/=/,eW:!0,r:0,c:[{cN:"literal",b:/\bon|off|true|false|yes|no\b/},{cN:"variable",v:[{b:/\$[\w\d"][\w\d_]*/},{b:/\$\{(.*?)}/}]},b,{cN:"number",b:/([\+\-]+)?[\d]+_[\d_]+/},e.NM]}]}]}});hljs.registerLanguage("javascript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},c={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,c,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:t,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,c,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:r+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:r,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+r+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:r},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:s}]}]},{b://,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:r}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("python",function(e){var r={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},b={cN:"meta",b:/^(>>>|\.\.\.) /},c={cN:"subst",b:/\{/,e:/\}/,k:r,i:/#/},a={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[b],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[b],r:10},{b:/(fr|rf|f)'''/,e:/'''/,c:[b,c]},{b:/(fr|rf|f)"""/,e:/"""/,c:[b,c]},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},{b:/(fr|rf|f)'/,e:/'/,c:[c]},{b:/(fr|rf|f)"/,e:/"/,c:[c]},e.ASM,e.QSM]},s={cN:"number",r:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},i={cN:"params",b:/\(/,e:/\)/,c:["self",b,s,a]};return c.c=[a,s,b],{aliases:["py","gyp"],k:r,i:/(<\/|->|\?)|=>/,c:[b,s,a,e.HCM,{v:[{cN:"function",bK:"def"},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,i,{b:/->/,eW:!0,k:"None"}]},{cN:"meta",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}});hljs.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"section",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"quote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"^```w*s*$",e:"^```s*$"},{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"string",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"symbol",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:/^\[[^\n]+\]:/,rB:!0,c:[{cN:"symbol",b:/\[/,e:/\]/,eB:!0,eE:!0},{cN:"link",b:/:\s*/,e:/$/,eB:!0}]}]}});hljs.registerLanguage("php",function(e){var c={b:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},i={cN:"meta",b:/<\?(php)?|\?>/},t={cN:"string",c:[e.BE,i],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},a={v:[e.BNM,e.CNM]};return{aliases:["php3","php4","php5","php6"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[e.HCM,e.C("//","$",{c:[i]}),e.C("/\\*","\\*/",{c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:/<<<['"]?\w+['"]?$/,e:/^\w+;?$/,c:[e.BE,{cN:"subst",v:[{b:/\$\w+/},{b:/\{\$/,e:/\}/}]}]},i,{cN:"keyword",b:/\$this\b/},c,{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",c,e.CBCM,t,a]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},t,a]}});hljs.registerLanguage("d",function(e){var t={keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},r="(0|[1-9][\\d_]*)",a="(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)",i="0[bB][01_]+",n="([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)",_="0[xX]"+n,c="([eE][+-]?"+a+")",d="("+a+"(\\.\\d*|"+c+")|\\d+\\."+a+a+"|\\."+r+c+"?)",o="(0[xX]("+n+"\\."+n+"|\\.?"+n+")[pP][+-]?"+a+")",s="("+r+"|"+i+"|"+_+")",l="("+o+"|"+d+")",u="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",b={cN:"number",b:"\\b"+s+"(L|u|U|Lu|LU|uL|UL)?",r:0},f={cN:"number",b:"\\b("+l+"([fF]|L|i|[fF]i|Li)?|"+s+"(i|[fF]i|Li))",r:0},g={cN:"string",b:"'("+u+"|.)",e:"'",i:"."},h={b:u,r:0},p={cN:"string",b:'"',c:[h],e:'"[cwd]?'},m={cN:"string",b:'[rq]"',e:'"[cwd]?',r:5},w={cN:"string",b:"`",e:"`[cwd]?"},N={cN:"string",b:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',r:10},A={cN:"string",b:'q"\\{',e:'\\}"'},F={cN:"meta",b:"^#!",e:"$",r:5},y={cN:"meta",b:"#(line)",e:"$",r:5},L={cN:"keyword",b:"@[a-zA-Z_][a-zA-Z_\\d]*"},v=e.C("\\/\\+","\\+\\/",{c:["self"],r:10});return{l:e.UIR,k:t,c:[e.CLCM,e.CBCM,v,N,p,m,w,A,f,b,g,F,y,L]}});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:n,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})],i:"\\S"},c={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return n.splice(n.length,0,t,c),{c:n,k:i,i:"\\S"}});hljs.registerLanguage("go",function(e){var t={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{aliases:["golang"],k:t,i:"{",e:"}"},n={v:[{b:/\$\d/},{b:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{b:/[\$%@][^\s\w{]/,r:0}]},i=[e.BE,r,n],o=[n,e.HCM,e.C("^\\=\\w","\\=cut",{eW:!0}),s,{cN:"string",c:i,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",r:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",r:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",r:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",r:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",r:5},{b:"qw\\s+q",e:"q",r:5},{b:"'",e:"'",c:[e.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[e.BE]},{b:"{\\w+}",c:[],r:0},{b:"-?\\w+\\s*\\=\\>",c:[],r:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\/\\/|"+e.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",r:0,c:[e.HCM,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",r:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[e.BE],r:0}]},{cN:"function",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",eE:!0,r:5,c:[e.TM]},{b:"-\\w\\b",r:0},{b:"^__DATA__$",e:"^__END__$",sL:"mojolicious",c:[{b:"^@@.*",e:"$",cN:"comment"}]}];return r.c=o,s.c=o,{aliases:["pl","pm"],l:/[\w\.]+/,k:t,c:o}});hljs.registerLanguage("rust",function(e){var t="([ui](8|16|32|64|128|size)|f(32|64))?",r="alignof as be box break const continue crate do else enum extern false fn for if impl in let loop match mod mut offsetof once priv proc pub pure ref return self Self sizeof static struct super trait true type typeof unsafe unsized use virtual while where yield move default",n="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{aliases:["rs"],k:{keyword:r,literal:"true false Some None Ok Err",built_in:n},l:e.IR+"!?",i:""}]}});hljs.registerLanguage("ruby",function(e){var b="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},c={cN:"doctag",b:"@[A-Za-z]+"},a={b:"#<",e:">"},s=[e.C("#","$",{c:[c]}),e.C("^\\=begin","^\\=end",{c:[c],r:10}),e.C("^__END__","\\n$")],n={cN:"subst",b:"#\\{",e:"}",k:r},t={cN:"string",c:[e.BE,n],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{b:/<<(-?)\w+$/,e:/^\s*\w+$/}]},i={cN:"params",b:"\\(",e:"\\)",endsParent:!0,k:r},d=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{b:"<\\s*",c:[{b:"("+e.IR+"::)?"+e.IR}]}].concat(s)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:b}),i].concat(s)},{b:e.IR+"::"},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":(?!\\s)",c:[t,{b:b}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{cN:"params",b:/\|/,e:/\|/,k:r},{b:"("+e.RSR+"|unless)\\s*",k:"unless",c:[a,{cN:"regexp",c:[e.BE,n],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(s),r:0}].concat(s);n.c=d,i.c=d;var l="[>?]>",o="[\\w#]+\\(\\w+\\):\\d+:\\d+>",u="(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>",w=[{b:/^\s*=>/,starts:{e:"$",c:d}},{cN:"meta",b:"^("+l+"|"+o+"|"+u+")",starts:{e:"$",c:d}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,i:/\/\*/,c:s.concat(w).concat(d)}});hljs.registerLanguage("makefile",function(e){var i={cN:"variable",v:[{b:"\\$\\("+e.UIR+"\\)",c:[e.BE]},{b:/\$[@%] *$",rE:!0,c:l.c,e:t.v[0].b},{b:"<%[%=-]?",e:"[%-]?%>",sL:"ruby",eB:!0,eE:!0,r:0},{cN:"type",b:"!!"+e.UIR},{cN:"meta",b:"&"+e.UIR+"$"},{cN:"meta",b:"\\*"+e.UIR+"$"},{cN:"bullet",b:"^ *-",r:0},e.HCM,{bK:b,k:{literal:b}},e.CNM,l]}});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",t={b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/},{b:/\(/,e:/\)/,c:[e.ASM,e.QSM]}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{b:"@",e:"[{;]",i:/:/,c:[{cN:"keyword",b:/\w+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:c,r:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,t]}]}});hljs.registerLanguage("java",function(e){var a="[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",t=a+"(<"+a+"(\\s*,\\s*"+a+")*>)?",r="false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",s="\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",c={cN:"number",b:s,r:0};return{aliases:["jsp"],k:r,i:/<\/|#/,c:[e.C("/\\*\\*","\\*/",{r:0,c:[{b:/\w+@/,r:0},{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return else",r:0},{cN:"function",b:"("+t+"\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:r,c:[{b:e.UIR+"\\s*\\(",rB:!0,r:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:r,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},c,{cN:"meta",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("armasm",function(s){return{cI:!0,aliases:["arm"],l:"\\.?"+s.IR,k:{meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .arm .thumb .code16 .code32 .force_thumb .thumb_func .ltorg ALIAS ALIGN ARM AREA ASSERT ATTR CN CODE CODE16 CODE32 COMMON CP DATA DCB DCD DCDU DCDO DCFD DCFDU DCI DCQ DCQU DCW DCWU DN ELIF ELSE END ENDFUNC ENDIF ENDP ENTRY EQU EXPORT EXPORTAS EXTERN FIELD FILL FUNCTION GBLA GBLL GBLS GET GLOBAL IF IMPORT INCBIN INCLUDE INFO KEEP LCLA LCLL LCLS LTORG MACRO MAP MEND MEXIT NOFP OPT PRESERVE8 PROC QN READONLY RELOC REQUIRE REQUIRE8 RLIST FN ROUT SETA SETL SETS SN SPACE SUBT THUMB THUMBX TTL WHILE WEND ",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 pc lr sp ip sl sb fp a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 f0 f1 f2 f3 f4 f5 f6 f7 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 q0 q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 cpsr_c cpsr_x cpsr_s cpsr_f cpsr_cx cpsr_cxs cpsr_xs cpsr_xsf cpsr_sf cpsr_cxsf spsr_c spsr_x spsr_s spsr_f spsr_cx spsr_cxs spsr_xs spsr_xsf spsr_sf spsr_cxsf s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s30 s31 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 d16 d17 d18 d19 d20 d21 d22 d23 d24 d25 d26 d27 d28 d29 d30 d31 {PC} {VAR} {TRUE} {FALSE} {OPT} {CONFIG} {ENDIAN} {CODESIZE} {CPU} {FPU} {ARCHITECTURE} {PCSTOREOFFSET} {ARMASM_VERSION} {INTER} {ROPI} {RWPI} {SWST} {NOSWST} . @"},c:[{cN:"keyword",b:"\\b(adc|(qd?|sh?|u[qh]?)?add(8|16)?|usada?8|(q|sh?|u[qh]?)?(as|sa)x|and|adrl?|sbc|rs[bc]|asr|b[lx]?|blx|bxj|cbn?z|tb[bh]|bic|bfc|bfi|[su]bfx|bkpt|cdp2?|clz|clrex|cmp|cmn|cpsi[ed]|cps|setend|dbg|dmb|dsb|eor|isb|it[te]{0,3}|lsl|lsr|ror|rrx|ldm(([id][ab])|f[ds])?|ldr((s|ex)?[bhd])?|movt?|mvn|mra|mar|mul|[us]mull|smul[bwt][bt]|smu[as]d|smmul|smmla|mla|umlaal|smlal?([wbt][bt]|d)|mls|smlsl?[ds]|smc|svc|sev|mia([bt]{2}|ph)?|mrr?c2?|mcrr2?|mrs|msr|orr|orn|pkh(tb|bt)|rbit|rev(16|sh)?|sel|[su]sat(16)?|nop|pop|push|rfe([id][ab])?|stm([id][ab])?|str(ex)?[bhd]?|(qd?)?sub|(sh?|q|u[qh]?)?sub(8|16)|[su]xt(a?h|a?b(16)?)|srs([id][ab])?|swpb?|swi|smi|tst|teq|wfe|wfi|yield)(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo)?[sptrx]?",e:"\\s"},s.C("[;@]","$",{r:0}),s.CBCM,s.QSM,{cN:"string",b:"'",e:"[^\\\\]'",r:0},{cN:"title",b:"\\|",e:"\\|",i:"\\n",r:0},{cN:"number",v:[{b:"[#$=]?0x[0-9a-f]+"},{b:"[#$=]?0b[01]+"},{b:"[#$=]\\d+"},{b:"\\b\\d+"}],r:0},{cN:"symbol",v:[{b:"^[a-z_\\.\\$][a-z0-9_\\.\\$]+"},{b:"^\\s*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{b:"[=#]\\w+"}],r:0}]}});hljs.registerLanguage("swift",function(e){var i={keyword:"__COLUMN__ __FILE__ __FUNCTION__ __LINE__ as as! as? associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},t={cN:"type",b:"\\b[A-Z][\\wÀ-ʸ']*",r:0},n=e.C("/\\*","\\*/",{c:["self"]}),r={cN:"subst",b:/\\\(/,e:"\\)",k:i,c:[]},a={cN:"number",b:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",r:0},o=e.inherit(e.QSM,{c:[r,e.BE]});return r.c=[a],{k:i,c:[o,e.CLCM,n,t,a,{cN:"function",bK:"func",e:"{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{b://},{cN:"params",b:/\(/,e:/\)/,endsParent:!0,k:i,c:["self",a,o,e.CBCM,{b:":"}],i:/["']/}],i:/\[|%/},{cN:"class",bK:"struct protocol class extension enum",k:i,e:"\\{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{cN:"meta",b:"(@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain)"},{bK:"import",e:/$/,c:[e.CLCM,n]}]}});hljs.registerLanguage("cpp",function(t){var e={cN:"keyword",b:"\\b[a-z\\d_]*_t\\b"},r={cN:"string",v:[{b:'(u8?|U)?L?"',e:'"',i:"\\n",c:[t.BE]},{b:'(u8?|U)?R"',e:'"',c:[t.BE]},{b:"'\\\\?.",e:"'",i:"."}]},s={cN:"number",v:[{b:"\\b(0b[01']+)"},{b:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{b:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],r:0},i={cN:"meta",b:/#\s*[a-z]+\b/,e:/$/,k:{"meta-keyword":"if else elif endif define undef warning error line pragma ifdef ifndef include"},c:[{b:/\\\n/,r:0},t.inherit(r,{cN:"meta-string"}),{cN:"meta-string",b:/<[^\n>]*>/,e:/$/,i:"\\n"},t.CLCM,t.CBCM]},a=t.IR+"\\s*\\(",c={keyword:"int float while private char catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignof constexpr decltype noexcept static_assert thread_local restrict _Bool complex _Complex _Imaginary atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and or not",built_in:"std string cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr",literal:"true false nullptr NULL"},n=[e,t.CLCM,t.CBCM,s,r];return{aliases:["c","cc","h","c++","h++","hpp"],k:c,i:"",k:c,c:["self",e]},{b:t.IR+"::",k:c},{v:[{b:/=/,e:/;/},{b:/\(/,e:/\)/},{bK:"new throw return else",e:/;/}],k:c,c:n.concat([{b:/\(/,e:/\)/,k:c,c:n.concat(["self"]),r:0}]),r:0},{cN:"function",b:"("+t.IR+"[\\*&\\s]+)+"+a,rB:!0,e:/[{;=]/,eE:!0,k:c,i:/[^\w\s\*&]/,c:[{b:a,rB:!0,c:[t.TM],r:0},{cN:"params",b:/\(/,e:/\)/,k:c,r:0,c:[t.CLCM,t.CBCM,r,s,e]},t.CLCM,t.CBCM,i]},{cN:"class",bK:"class struct",e:/[{;:]/,c:[{b://,c:["self"]},t.TM]}]),exports:{preprocessor:i,strings:r,k:c}}});hljs.registerLanguage("x86asm",function(s){return{cI:!0,l:"[.%]?"+s.IR,k:{keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",built_in:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr",meta:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},c:[s.C(";","$",{r:0}),{cN:"number",v:[{b:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",r:0},{b:"\\$[0-9][0-9A-Fa-f]*",r:0},{b:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{b:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"}]},s.QSM,{cN:"string",v:[{b:"'",e:"[^\\\\]'"},{b:"`",e:"[^\\\\]`"}],r:0},{cN:"symbol",v:[{b:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{b:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],r:0},{cN:"subst",b:"%[0-9]+",r:0},{cN:"subst",b:"%!S+",r:0},{cN:"meta",b:/^\s*\.[\w_-]+/}]}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},s={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/\b-?[a-z\._]+\b/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,s,a,t]}});hljs.registerLanguage("shell",function(s){return{aliases:["console"],c:[{cN:"meta",b:"^\\s{0,3}[\\w\\d\\[\\]()@-]*[>%$#]",starts:{e:"$",sL:"bash"}}]}});hljs.registerLanguage("http",function(e){var t="HTTP/[0-9\\.]+";return{aliases:["https"],i:"\\S",c:[{b:"^"+t,e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{b:"^[A-Z]+ (.*?) "+t+"$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0},{b:t},{cN:"keyword",b:"[A-Z]+"}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{e:"$",r:0}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}});hljs.registerLanguage("cs",function(e){var i={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long nameof object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let on orderby partial remove select set value var where yield",literal:"null false true"},t={cN:"string",b:'@"',e:'"',c:[{b:'""'}]},r=e.inherit(t,{i:/\n/}),a={cN:"subst",b:"{",e:"}",k:i},c=e.inherit(a,{i:/\n/}),n={cN:"string",b:/\$"/,e:'"',i:/\n/,c:[{b:"{{"},{b:"}}"},e.BE,c]},s={cN:"string",b:/\$@"/,e:'"',c:[{b:"{{"},{b:"}}"},{b:'""'},a]},o=e.inherit(s,{i:/\n/,c:[{b:"{{"},{b:"}}"},{b:'""'},c]});a.c=[s,n,t,e.ASM,e.QSM,e.CNM,e.CBCM],c.c=[o,n,r,e.ASM,e.QSM,e.CNM,e.inherit(e.CBCM,{i:/\n/})];var l={v:[s,n,t,e.ASM,e.QSM]},b=e.IR+"(<"+e.IR+"(\\s*,\\s*"+e.IR+")*>)?(\\[\\])?";return{aliases:["csharp"],k:i,i:/::/,c:[e.C("///","$",{rB:!0,c:[{cN:"doctag",v:[{b:"///",r:0},{b:""},{b:""}]}]}),e.CLCM,e.CBCM,{cN:"meta",b:"#",e:"$",k:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},l,e.CNM,{bK:"class interface",e:/[{;=]/,i:/[^\s:]/,c:[e.TM,e.CLCM,e.CBCM]},{bK:"namespace",e:/[{;=]/,i:/[^\s:]/,c:[e.inherit(e.TM,{b:"[a-zA-Z](\\.?\\w)*"}),e.CLCM,e.CBCM]},{cN:"meta",b:"^\\s*\\[",eB:!0,e:"\\]",eE:!0,c:[{cN:"meta-string",b:/"/,e:/"/}]},{bK:"new return throw await else",r:0},{cN:"function",b:"("+b+"\\s+)+"+e.IR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:i,c:[{b:e.IR+"\\s*\\(",rB:!0,c:[e.TM],r:0},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:i,r:0,c:[l,e.CNM,e.CBCM]},e.CLCM,e.CBCM]}]}});hljs.registerLanguage("coffeescript",function(e){var c={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super yield import export from as default await then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",built_in:"npm require console print module global window document"},n="[A-Za-z$_][0-9A-Za-z$_]*",r={cN:"subst",b:/#\{/,e:/}/,k:c},i=[e.BNM,e.inherit(e.CNM,{starts:{e:"(\\s*/)?",r:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/"""/,e:/"""/,c:[e.BE,r]},{b:/"/,e:/"/,c:[e.BE,r]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[r,e.HCM]},{b:"//[gim]*",r:0},{b:/\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/}]},{b:"@"+n},{sL:"javascript",eB:!0,eE:!0,v:[{b:"```",e:"```"},{b:"`",e:"`"}]}];r.c=i;var s=e.inherit(e.TM,{b:n}),t="(\\(.*\\))?\\s*\\B[-=]>",o={cN:"params",b:"\\([^\\(]",rB:!0,c:[{b:/\(/,e:/\)/,k:c,c:["self"].concat(i)}]};return{aliases:["coffee","cson","iced"],k:c,i:/\/\*/,c:i.concat([e.C("###","###"),e.HCM,{cN:"function",b:"^\\s*"+n+"\\s*=\\s*"+t,e:"[-=]>",rB:!0,c:[s,o]},{b:/[:\(,=]\s*/,r:0,c:[{cN:"function",b:t,e:"[-=]>",rB:!0,c:[o]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[s]},s]},{b:n+":",e:":",rB:!0,rE:!0,r:0}])}});hljs.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>{}*#]/,c:[{bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment",e:/;/,eW:!0,l:/[\w\.]+/,k:{keyword:"abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second section securefile security seed segment select self sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null",built_in:"array bigint binary bit blob boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text varchar varying void"},c:[{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[e.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[e.BE]},e.CNM,e.CBCM,t]},e.CBCM,t]}});hljs.registerLanguage("apache",function(e){var r={cN:"number",b:"[\\$%]\\d+"};return{aliases:["apacheconf"],cI:!0,c:[e.HCM,{cN:"section",b:""},{cN:"attribute",b:/\w+/,r:0,k:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,r:0,k:{literal:"on off all"},c:[{cN:"meta",b:"\\s\\[",e:"\\]$"},{cN:"variable",b:"[\\$%]\\{",e:"\\}",c:["self",r]},r,e.QSM]}}],i:/\S/}});hljs.registerLanguage("haskell",function(e){var i={v:[e.C("--","$"),e.C("{-","-}",{c:["self"]})]},a={cN:"meta",b:"{-#",e:"#-}"},l={cN:"meta",b:"^#",e:"$"},c={cN:"type",b:"\\b[A-Z][\\w']*",r:0},n={b:"\\(",e:"\\)",i:'"',c:[a,l,{cN:"type",b:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TM,{b:"[_a-z][\\w']*"}),i]},s={b:"{",e:"}",c:n.c};return{aliases:["hs"],k:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",c:[{bK:"module",e:"where",k:"module where",c:[n,i],i:"\\W\\.|;"},{b:"\\bimport\\b",e:"$",k:"import qualified as hiding",c:[n,i],i:"\\W\\.|;"},{cN:"class",b:"^(\\s*)?(class|instance)\\b",e:"where",k:"class family instance where",c:[c,n,i]},{cN:"class",b:"\\b(data|(new)?type)\\b",e:"$",k:"data family type newtype deriving",c:[a,c,n,s,i]},{bK:"default",e:"$",c:[c,n,i]},{bK:"infix infixl infixr",e:"$",c:[e.CNM,i]},{b:"\\bforeign\\b",e:"$",k:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",c:[c,e.QSM,i]},{cN:"meta",b:"#!\\/usr\\/bin\\/env runhaskell",e:"$"},a,l,e.QSM,e.CNM,c,e.inherit(e.TM,{b:"^[_a-z][\\w']*"}),i,{b:"->|<-"}]}});hljs.registerLanguage("scala",function(e){var t={cN:"meta",b:"@[A-Za-z]+"},a={cN:"subst",v:[{b:"\\$[A-Za-z0-9_]+"},{b:"\\${",e:"}"}]},r={cN:"string",v:[{b:'"',e:'"',i:"\\n",c:[e.BE]},{b:'"""',e:'"""',r:10},{b:'[a-z]+"',e:'"',i:"\\n",c:[e.BE,a]},{cN:"string",b:'[a-z]+"""',e:'"""',c:[a],r:10}]},c={cN:"symbol",b:"'\\w[\\w\\d_]*(?!')"},i={cN:"type",b:"\\b[A-Z][A-Za-z0-9_]*",r:0},s={cN:"title",b:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,r:0},n={cN:"class",bK:"class object trait type",e:/[:={\[\n;]/,eE:!0,c:[{bK:"extends with",r:10},{b:/\[/,e:/\]/,eB:!0,eE:!0,r:0,c:[i]},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,r:0,c:[i]},s]},l={cN:"function",bK:"def",e:/[:={\[(\n;]/,eE:!0,c:[s]};return{k:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},c:[e.CLCM,e.CBCM,r,c,i,l,n,e.CNM,t]}}); \ No newline at end of file diff --git a/book/img/fullnode.svg b/book/img/fullnode.svg new file mode 100644 index 00000000000000..bed26722138085 --- /dev/null +++ b/book/img/fullnode.svg @@ -0,0 +1,278 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Client + + + + +Fullnode + + + + +JsonRpcService + + + + +Bank + + + + +Tpu + + + + +Ncp + + + + +Tvu + + + + +Validators + + + diff --git a/book/img/leader-scheduler.svg b/book/img/leader-scheduler.svg new file mode 100644 index 00000000000000..3d13d7549b728a --- /dev/null +++ b/book/img/leader-scheduler.svg @@ -0,0 +1,330 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +time + + + + +L1 + + + + +L2 + + + + +L3 + + + + +L4 + + + + +L5 + + + + +x + + + + +xx + + + + +E3 + + + + +xx + + + + +E2 + + + + +E4 + + + + +xx + + + + +x + + + + +E5 + + + + +E1 + + + + +xx + + + + +E3' + + + + +xx + + + + +x + + + + +xx + + + + +xx + + + + +validator + + + + +vote(E1) + + + + +vote(E2) + + + + +slash(E3) + + + + +vote(E4) + + + + +hang + + + + +on + + + + +to + + + + +action + + + + +E4 + + + + +and + + + + +E5 + + + + +for + + + + +more... + + + diff --git a/book/img/runtime.svg b/book/img/runtime.svg new file mode 100644 index 00000000000000..3e823946d93895 --- /dev/null +++ b/book/img/runtime.svg @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +sigverify + + + + +load + + + + +data + + + + +lock + + + + +memory + + + + +execute + + + + +validate + + + + +commit + + + + +data + + + + +fee + + + + +unlock + + + + +allocate + + + + +memory + + + + +accounts + + + diff --git a/book/img/sdk-tools.svg b/book/img/sdk-tools.svg new file mode 100644 index 00000000000000..629a3feaa35012 --- /dev/null +++ b/book/img/sdk-tools.svg @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Client + + + + +Verifier + + + + +Loader + + + + +Solana + + + + +LoadAccounts + + + + +Runtime + + + + +Interpreter + + + + +Accounts + + + diff --git a/book/img/tpu.svg b/book/img/tpu.svg new file mode 100644 index 00000000000000..794b8c9cd29351 --- /dev/null +++ b/book/img/tpu.svg @@ -0,0 +1,323 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Clients + + + + +Tpu + + + + +Fetch + + + + +Stage + + + + +SigVerify + + + + +Stage + + + + +PohService + + + + +Banking + + + + +Stage + + + + +Bank + + + + +Write + + + + +Stage + + + + +Ledger + + + + +Validators + + + diff --git a/book/img/tvu.svg b/book/img/tvu.svg new file mode 100644 index 00000000000000..6080e033bcaf4e --- /dev/null +++ b/book/img/tvu.svg @@ -0,0 +1,323 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Leader + + + + +Tvu + + + + +Blob + + + + +Fetch + + + + +Stage + + + + +Validators + + + + +Retransmit + + + + +Stage + + + + +Replicate + + + + +Stage + + + + +Bank + + + + +Ledger + + + + +Write + + + + +Stage + + + + +Storage + + + + +Stage + + + diff --git a/book/index.html b/book/index.html new file mode 100644 index 00000000000000..cfe0b3f80c3e3d --- /dev/null +++ b/book/index.html @@ -0,0 +1,215 @@ + + + + + + Introduction - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

Disclaimer

+

All claims, content, designs, algorithms, estimates, roadmaps, specifications, +and performance measurements described in this project are done with the +author's best effort. It is up to the reader to check and validate their +accuracy and truthfulness. Furthermore nothing in this project constitutes a +solicitation for investment.

+

Introduction

+

This document defines the architecture of Solana, a blockchain built from the +ground up for scale. The goal of the architecture is to demonstrate there +exists a set of software algorithms that in combination, removes software as a +performance bottleneck, allowing transaction throughput to scale proportionally +with network bandwidth. The architecture goes on to satisfy all three desirable +properties of a proper blockchain, that it not only be scalable, but that it is +also secure and decentralized.

+

With this architecture, we calculate a theoretical upper bound of 710 thousand +transactions per second (tps) on a standard gigabit network and 28.4 million +tps on 40 gigabit. In practice, our focus has been on gigabit. We soak-tested +a 150 node permissioned testnet and it is able to maintain a mean transaction +throughput of approximately 200 thousand tps with peaks over 400 thousand.

+

Furthermore, we have found high throughput extends beyond simple payments, and +that this architecture is also able to perform safe, concurrent execution of +programs authored in a general purpose programming language, such as C. We feel +the extension warrants industry focus on an additional performance metric +already common in the CPU industry, millions of instructions per second or +mips. By measuring mips, we see that batching instructions within a transaction +amortizes the cost of signature verification, lifting the maximum theoretical +instruction throughput up to almost exactly that of centralized databases.

+

Lastly, we discuss the relationships between high throughput, security and +transaction fees. Solana's efficient use hardware drives transaction fees into +the ballpark of 1/1000th of a cent. The drop in fees in turn makes certain +denial of service attacks cheaper. We discuss what these attacks look like and +Solana's techniques to defend against them.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/introduction.html b/book/introduction.html new file mode 100644 index 00000000000000..215ec423b6c36b --- /dev/null +++ b/book/introduction.html @@ -0,0 +1,223 @@ + + + + + + Introduction - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

Disclaimer

+

All claims, content, designs, algorithms, estimates, roadmaps, specifications, +and performance measurements described in this project are done with the +author's best effort. It is up to the reader to check and validate their +accuracy and truthfulness. Furthermore nothing in this project constitutes a +solicitation for investment.

+

Introduction

+

This document defines the architecture of Solana, a blockchain built from the +ground up for scale. The goal of the architecture is to demonstrate there +exists a set of software algorithms that in combination, removes software as a +performance bottleneck, allowing transaction throughput to scale proportionally +with network bandwidth. The architecture goes on to satisfy all three desirable +properties of a proper blockchain, that it not only be scalable, but that it is +also secure and decentralized.

+

With this architecture, we calculate a theoretical upper bound of 710 thousand +transactions per second (tps) on a standard gigabit network and 28.4 million +tps on 40 gigabit. In practice, our focus has been on gigabit. We soak-tested +a 150 node permissioned testnet and it is able to maintain a mean transaction +throughput of approximately 200 thousand tps with peaks over 400 thousand.

+

Furthermore, we have found high throughput extends beyond simple payments, and +that this architecture is also able to perform safe, concurrent execution of +programs authored in a general purpose programming language, such as C. We feel +the extension warrants industry focus on an additional performance metric +already common in the CPU industry, millions of instructions per second or +mips. By measuring mips, we see that batching instructions within a transaction +amortizes the cost of signature verification, lifting the maximum theoretical +instruction throughput up to almost exactly that of centralized databases.

+

Lastly, we discuss the relationships between high throughput, security and +transaction fees. Solana's efficient use hardware drives transaction fees into +the ballpark of 1/1000th of a cent. The drop in fees in turn makes certain +denial of service attacks cheaper. We discuss what these attacks look like and +Solana's techniques to defend against them.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/jsonrpc-api.html b/book/jsonrpc-api.html new file mode 100644 index 00000000000000..fe8d97b100d1dc --- /dev/null +++ b/book/jsonrpc-api.html @@ -0,0 +1,514 @@ + + + + + + JSON RPC API - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

JSON RPC API

+

Solana nodes accept HTTP requests using the JSON-RPC 2.0 specification.

+

To interact with a Solana node inside a JavaScript application, use the solana-web3.js library, which gives a convenient interface for the RPC methods.

+

RPC HTTP Endpoint

+

Default port: 8899 +eg. http://localhost:8899, http://192.168.1.88:8899

+

RPC PubSub WebSocket Endpoint

+

Default port: 8900 +eg. ws://localhost:8900, http://192.168.1.88:8900

+

Methods

+ +

Request Formatting

+

To make a JSON-RPC request, send an HTTP POST request with a Content-Type: application/json header. The JSON request data should contain 4 fields:

+
    +
  • jsonrpc, set to "2.0"
  • +
  • id, a unique client-generated identifying integer
  • +
  • method, a string containing the method to be invoked
  • +
  • params, a JSON array of ordered parameter values
  • +
+

Example using curl:

+
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' 192.168.1.88:8899
+
+

The response output will be a JSON object with the following fields:

+
    +
  • jsonrpc, matching the request specification
  • +
  • id, matching the request identifier
  • +
  • result, requested data or success confirmation
  • +
+

Requests can be sent in batches by sending an array of JSON-RPC request objects as the data for a single POST.

+

Definitions

+
    +
  • Hash: A SHA-256 hash of a chunk of data.
  • +
  • Pubkey: The public key of a Ed25519 key-pair.
  • +
  • Signature: An Ed25519 signature of a chunk of data.
  • +
  • Transaction: A Solana instruction signed by a client key-pair.
  • +
+

JSON RPC API Reference

+

confirmTransaction

+

Returns a transaction receipt

+
Parameters:
+
    +
  • string - Signature of Transaction to confirm, as base-58 encoded string
  • +
+
Results:
+
    +
  • boolean - Transaction status, true if Transaction is confirmed
  • +
+
Example:
+
// Request
+curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"confirmTransaction", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
+
+// Result
+{"jsonrpc":"2.0","result":true,"id":1}
+
+
+

getBalance

+

Returns the balance of the account of provided Pubkey

+
Parameters:
+
    +
  • string - Pubkey of account to query, as base-58 encoded string
  • +
+
Results:
+
    +
  • integer - quantity, as a signed 64-bit integer
  • +
+
Example:
+
// Request
+curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' http://localhost:8899
+
+// Result
+{"jsonrpc":"2.0","result":0,"id":1}
+
+
+

getAccountInfo

+

Returns all information associated with the account of provided Pubkey

+
Parameters:
+
    +
  • string - Pubkey of account to query, as base-58 encoded string
  • +
+
Results:
+

The result field will be a JSON object with the following sub fields:

+
    +
  • tokens, number of tokens assigned to this account, as a signed 64-bit integer
  • +
  • owner, array of 32 bytes representing the program this account has been assigned to
  • +
  • userdata, array of bytes representing any userdata associated with the account
  • +
  • executable, boolean indicating if the account contains a program (and is strictly read-only)
  • +
  • loader, array of 32 bytes representing the loader for this program (if executable), otherwise all
  • +
+
Example:
+
// Request
+curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"]}' http://localhost:8899
+
+// Result
+{"jsonrpc":"2.0","result":{"executable":false,"loader":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"tokens":1,"userdata":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"id":1}
+
+
+

getLastId

+

Returns the last entry ID from the ledger

+
Parameters:
+

None

+
Results:
+
    +
  • string - the ID of last entry, a Hash as base-58 encoded string
  • +
+
Example:
+
// Request
+curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getLastId"}' http://localhost:8899
+
+// Result
+{"jsonrpc":"2.0","result":"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC","id":1}
+
+
+

getSignatureStatus

+

Returns the status of a given signature. This method is similar to +confirmTransaction but provides more resolution for error +events.

+
Parameters:
+
    +
  • string - Signature of Transaction to confirm, as base-58 encoded string
  • +
+
Results:
+
    +
  • string - Transaction status: +
      +
    • Confirmed - Transaction was successful
    • +
    • SignatureNotFound - Unknown transaction
    • +
    • ProgramRuntimeError - An error occurred in the program that processed this Transaction
    • +
    • AccountInUse - Another Transaction had a write lock one of the Accounts specified in this Transaction. The Transaction may succeed if retried
    • +
    • GenericFailure - Some other error occurred. Note: In the future new Transaction statuses may be added to this list. It's safe to assume that all new statuses will be more specific error conditions that previously presented as GenericFailure
    • +
    +
  • +
+
Example:
+
// Request
+curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatus", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
+
+// Result
+{"jsonrpc":"2.0","result":"SignatureNotFound","id":1}
+
+
+

getTransactionCount

+

Returns the current Transaction count from the ledger

+
Parameters:
+

None

+
Results:
+
    +
  • integer - count, as unsigned 64-bit integer
  • +
+
Example:
+
// Request
+curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://localhost:8899
+
+// Result
+{"jsonrpc":"2.0","result":268,"id":1}
+
+
+

requestAirdrop

+

Requests an airdrop of tokens to a Pubkey

+
Parameters:
+
    +
  • string - Pubkey of account to receive tokens, as base-58 encoded string
  • +
  • integer - token quantity, as a signed 64-bit integer
  • +
+
Results:
+
    +
  • string - Transaction Signature of airdrop, as base-58 encoded string
  • +
+
Example:
+
// Request
+curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"requestAirdrop", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri", 50]}' http://localhost:8899
+
+// Result
+{"jsonrpc":"2.0","result":"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW","id":1}
+
+
+

sendTransaction

+

Creates new transaction

+
Parameters:
+
    +
  • array - array of octets containing a fully-signed Transaction
  • +
+
Results:
+
    +
  • string - Transaction Signature, as base-58 encoded string
  • +
+
Example:
+
// Request
+curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"sendTransaction", "params":[[61, 98, 55, 49, 15, 187, 41, 215, 176, 49, 234, 229, 228, 77, 129, 221, 239, 88, 145, 227, 81, 158, 223, 123, 14, 229, 235, 247, 191, 115, 199, 71, 121, 17, 32, 67, 63, 209, 239, 160, 161, 2, 94, 105, 48, 159, 235, 235, 93, 98, 172, 97, 63, 197, 160, 164, 192, 20, 92, 111, 57, 145, 251, 6, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 13, 39, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 11, 12, 106, 49, 74, 226, 201, 16, 161, 192, 28, 84, 124, 97, 190, 201, 171, 186, 6, 18, 70, 142, 89, 185, 176, 154, 115, 61, 26, 163, 77, 1, 88, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]}' http://localhost:8899
+
+// Result
+{"jsonrpc":"2.0","result":"2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b","id":1}
+
+
+

Subscription Websocket

+

After connect to the RPC PubSub websocket at ws://<ADDRESS>/:

+
    +
  • Submit subscription requests to the websocket using the methods below
  • +
  • Multiple subscriptions may be active at once
  • +
+
+

accountSubscribe

+

Subscribe to an account to receive notifications when the userdata for a given account public key changes

+
Parameters:
+
    +
  • string - account Pubkey, as base-58 encoded string
  • +
+
Results:
+
    +
  • integer - Subscription id (needed to unsubscribe)
  • +
+
Example:
+
// Request
+{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"]}
+
+// Result
+{"jsonrpc": "2.0","result": 0,"id": 1}
+
+
Notification Format:
+
{"jsonrpc": "2.0","method": "accountNotification", "params": {"result": {"executable":false,"loader":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"tokens":1,"userdata":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"subscription":0}}
+
+
+

accountUnsubscribe

+

Unsubscribe from account userdata change notifications

+
Parameters:
+
    +
  • integer - id of account Subscription to cancel
  • +
+
Results:
+
    +
  • bool - unsubscribe success message
  • +
+
Example:
+
// Request
+{"jsonrpc":"2.0", "id":1, "method":"accountUnsubscribe", "params":[0]}
+
+// Result
+{"jsonrpc": "2.0","result": true,"id": 1}
+
+
+

signatureSubscribe

+

Subscribe to a transaction signature to receive notification when the transaction is confirmed +On signatureNotification, the subscription is automatically cancelled

+
Parameters:
+
    +
  • string - Transaction Signature, as base-58 encoded string
  • +
+
Results:
+
    +
  • integer - subscription id (needed to unsubscribe)
  • +
+
Example:
+
// Request
+{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b"]}
+
+// Result
+{"jsonrpc": "2.0","result": 0,"id": 1}
+
+
Notification Format:
+
{"jsonrpc": "2.0","method": "signatureNotification", "params": {"result": "Confirmed","subscription":0}}
+
+
+

signatureUnsubscribe

+

Unsubscribe from account userdata change notifications

+
Parameters:
+
    +
  • integer - id of account subscription to cancel
  • +
+
Results:
+
    +
  • bool - unsubscribe success message
  • +
+
Example:
+
// Request
+{"jsonrpc":"2.0", "id":1, "method":"signatureUnsubscribe", "params":[0]}
+
+// Result
+{"jsonrpc": "2.0","result": true,"id": 1}
+
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/jsonrpc-service.html b/book/jsonrpc-service.html new file mode 100644 index 00000000000000..640aa8680ac051 --- /dev/null +++ b/book/jsonrpc-service.html @@ -0,0 +1,200 @@ + + + + + + JsonRpcService - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

JsonRpcService

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/leader-scheduler.html b/book/leader-scheduler.html new file mode 100644 index 00000000000000..0a5c7540b88edf --- /dev/null +++ b/book/leader-scheduler.html @@ -0,0 +1,286 @@ + + + + + + Leader Rotation - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

Leader Rotation

+

A property of any permissionless blockchain is that the entity choosing the next block is randomly selected. In proof of stake systems, +that entity is typically called the "leader" or "block producer." In Solana, we call it the leader. Under the hood, a leader is +simply a mode of the fullnode. A fullnode runs as either a leader or validator. In this chapter, we describe how a fullnode determines +what node is the leader, how that mechanism may choose different leaders at the same time, and if so, how the system converges in response.

+

Leader Seed Generation

+

Leader selection is decided via a random seed. The process is as follows:

+
    +
  1. Periodically, at a specific PoH tick count, select the signatures of the votes that made up the last supermajority
  2. +
  3. Concatenate the signatures
  4. +
  5. Hash the resulting string for N counts
  6. +
  7. The resulting hash is the random seed for M counts, M leader slots, where M > N
  8. +
+

Leader Rotation

+
    +
  1. The leader is chosen via a random seed generated from stake weights and votes (the leader schedule)
  2. +
  3. The leader is rotated every T PoH ticks (leader slot), according to the leader schedule
  4. +
  5. The schedule is applicable for M voting rounds
  6. +
+

Leader's transmit for a count of T PoH ticks. When T is reached all the validators should switch to the next scheduled leader. To schedule leaders, the supermajority + M nodes are shuffled using the above calculated random seed.

+

All T ticks must be observed from the current leader for that part of PoH to be accepted by the network. If T ticks (and any intervening transactions) are not observed, the network optimistically fills in the T ticks, and continues with PoH from the next leader.

+

Partitions, Forks

+

Forks can arise at PoH tick counts that correspond to leader rotations, because leader nodes may or may not have observed the previous leader's data. These empty ticks are generated by all nodes in the network at a network-specified rate for hashes-per-tick Z.

+

There are only two possible versions of the PoH during a voting round: PoH with T ticks and entries generated by the current leader, or PoH with just ticks. The "just ticks" version of the PoH can be thought of as a virtual ledger, one that all nodes in the network can derive from the last tick in the previous slot.

+

Validators can ignore forks at other points (e.g. from the wrong leader), or slash the leader responsible for the fork.

+

Validators vote on the longest chain that contains their previous vote, or a longer chain if the lockout on their previous vote has expired.

+

Validator's View

+
Time Progression
+

The diagram below represents a validator's view of the PoH stream with possible forks over time. L1, L2, etc. are leader slots, and Es represent entries from that leader during that leader's slot. The xs represent ticks only, and time flows downwards in the diagram.

+

Leader scheduler

+

Note that an E appearing on 2 branches at the same slot is a slashable condition, so a validator observing L3 and L3' can slash L3 and safely choose x for that slot. Once a validator observes a supermajority vote on any branch, other branches can be discarded below that tick count. For any slot, validators need only consider a single "has entries" chain or a "ticks only" chain.

+
Time Division
+

It's useful to consider leader rotation over PoH tick count as time division of the job of encoding state for the network. The following table presents the above tree of forks as a time-divided ledger.

+ + + +
leader slot L1 L2 L3 L4 L5
data E1 E2 E3 E4 E5
ticks since prev x xx
+

Note that only data from leader L3 will be accepted during leader slot +L3. Data from L3 may include "catchup" ticks back to a slot other than +L2 if L3 did not observe L2's data. L4 and L5's transmissions +include the "ticks since prev" PoH entries.

+

This arrangement of the network data streams permits nodes to save exactly this +to the ledger for replay, restart, and checkpoints.

+

Leader's View

+

When a new leader begins a slot, it must first transmit any PoH (ticks) +required to link the new slot with the most recently observed and voted +slot.

+

Examples

+

Small Partition

+
    +
  1. Network partition M occurs for 10% of the nodes
  2. +
  3. The larger partition K, with 90% of the stake weight continues to operate as +normal
  4. +
  5. M cycles through the ranks until one of them is leader, generating ticks for +slots where the leader is in K.
  6. +
  7. M validators observe 10% of the vote pool, finality is not reached.
  8. +
  9. M and K reconnect.
  10. +
  11. M validators cancel their votes on M, which has not reached finality, and +re-cast on K (after their vote lockout on M).
  12. +
+

Leader Timeout

+
    +
  1. Next rank leader node V observes a timeout from current leader A, fills in +A's slot with virtual ticks and starts sending out entries.
  2. +
  3. Nodes observing both streams keep track of the forks, waiting for: +
      +
    • their vote on leader A to expire in order to be able to vote on B
    • +
    • a supermajority on A's slot
    • +
    +
  4. +
  5. If the first case occurs, leader B's slot is filled with ticks. if the +second case occurs, A's slot is filled with ticks
  6. +
  7. Partition is resolved just like in the Small Partition +above
  8. +
+

Network Variables

+

A - name of a node

+

B - name of a node

+

K - number of nodes in the supermajority to whom leaders broadcast their +PoH hash for validation

+

M - number of nodes outside the supermajority to whom leaders broadcast their +PoH hash for validation

+

N - number of voting rounds for which a leader schedule is considered before +a new leader schedule is used

+

T - number of PoH ticks per leader slot (also voting round)

+

V - name of a node that will create virtual ticks

+

Z - number of hashes per PoH tick

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/leader_scheduler.rs b/book/leader_scheduler.rs new file mode 100644 index 00000000000000..fb78ff1dee30a7 --- /dev/null +++ b/book/leader_scheduler.rs @@ -0,0 +1,1407 @@ +//! The `leader_scheduler` module implements a structure and functions for tracking and +//! managing the schedule for leader rotation + +use bank::Bank; + +use bincode::serialize; +use byteorder::{LittleEndian, ReadBytesExt}; +use entry::Entry; +use ledger::create_ticks; +use signature::{Keypair, KeypairUtil}; +use solana_sdk::hash::{hash, Hash}; +use solana_sdk::pubkey::Pubkey; +use std::collections::HashSet; +use std::io::Cursor; +use system_transaction::SystemTransaction; +use transaction::Transaction; +use vote_program::{Vote, VoteProgram}; +use vote_transaction::VoteTransaction; + +pub const DEFAULT_BOOTSTRAP_HEIGHT: u64 = 1000; +pub const DEFAULT_LEADER_ROTATION_INTERVAL: u64 = 100; +pub const DEFAULT_SEED_ROTATION_INTERVAL: u64 = 1000; +pub const DEFAULT_ACTIVE_WINDOW_LENGTH: u64 = 1000; + +pub struct LeaderSchedulerConfig { + // The interval at which to rotate the leader, should be much less than + // seed_rotation_interval + pub leader_rotation_interval_option: Option, + + // The interval at which to generate the seed used for ranking the validators + pub seed_rotation_interval_option: Option, + + // The last height at which the bootstrap_leader will be in power before + // the leader rotation process begins to pick future leaders + pub bootstrap_height_option: Option, + + // The length of the acceptable window for determining live validators + pub active_window_length_option: Option, +} + +// Used to toggle leader rotation in fullnode so that tests that don't +// need leader rotation don't break +impl LeaderSchedulerConfig { + pub fn new( + bootstrap_height_option: Option, + leader_rotation_interval_option: Option, + seed_rotation_interval_option: Option, + active_window_length_option: Option, + ) -> Self { + LeaderSchedulerConfig { + bootstrap_height_option, + leader_rotation_interval_option, + seed_rotation_interval_option, + active_window_length_option, + } + } +} + +#[derive(Clone, Debug)] +pub struct LeaderScheduler { + // Set to true if we want the default implementation of the LeaderScheduler, + // where ony the bootstrap leader is used + pub use_only_bootstrap_leader: bool, + + // The interval at which to rotate the leader, should be much less than + // seed_rotation_interval + pub leader_rotation_interval: u64, + + // The interval at which to generate the seed used for ranking the validators + pub seed_rotation_interval: u64, + + // The first leader who will bootstrap the network + pub bootstrap_leader: Pubkey, + + // The last height at which the bootstrap_leader will be in power before + // the leader rotation process begins to pick future leaders + pub bootstrap_height: u64, + + // The last height at which the seed + schedule was generated + pub last_seed_height: Option, + + // The length of time in ticks for which a vote qualifies a candidate for leader + // selection + pub active_window_length: u64, + + // Round-robin ordering for the validators + leader_schedule: Vec, + + // The seed used to determine the round robin order of leaders + seed: u64, +} + +// The LeaderScheduler implements a schedule for leaders as follows: +// +// 1) During the bootstrapping period of bootstrap_height PoH counts, the +// leader is hard-coded to the bootstrap_leader that is read from the genesis block. +// +// 2) After the first seed is generated, this signals the beginning of actual leader rotation. +// From this point on, every seed_rotation_interval PoH counts we generate the seed based +// on the PoH height, and use it to do a weighted sample from the set +// of validators based on current stake weight. This gets you the bootstrap leader A for +// the next leader_rotation_interval PoH counts. On the same PoH count we generate the seed, +// we also order the validators based on their current stake weight, and starting +// from leader A, we then pick the next leader sequentially every leader_rotation_interval +// PoH counts based on this fixed ordering, so the next +// seed_rotation_interval / leader_rotation_interval leaders are determined. +// +// 3) When we we hit the next seed rotation PoH height, step 2) is executed again to +// calculate the leader schedule for the upcoming seed_rotation_interval PoH counts. +impl LeaderScheduler { + pub fn from_bootstrap_leader(bootstrap_leader: Pubkey) -> Self { + let config = LeaderSchedulerConfig::new(None, None, None, None); + let mut leader_scheduler = LeaderScheduler::new(&config); + leader_scheduler.use_only_bootstrap_leader = true; + leader_scheduler.bootstrap_leader = bootstrap_leader; + leader_scheduler + } + + pub fn new(config: &LeaderSchedulerConfig) -> Self { + let mut bootstrap_height = DEFAULT_BOOTSTRAP_HEIGHT; + if let Some(input) = config.bootstrap_height_option { + bootstrap_height = input; + } + + let mut leader_rotation_interval = DEFAULT_LEADER_ROTATION_INTERVAL; + if let Some(input) = config.leader_rotation_interval_option { + leader_rotation_interval = input; + } + + let mut seed_rotation_interval = DEFAULT_SEED_ROTATION_INTERVAL; + if let Some(input) = config.seed_rotation_interval_option { + seed_rotation_interval = input; + } + + let mut active_window_length = DEFAULT_ACTIVE_WINDOW_LENGTH; + if let Some(input) = config.active_window_length_option { + active_window_length = input; + } + + // Enforced invariants + assert!(seed_rotation_interval >= leader_rotation_interval); + assert!(bootstrap_height > 0); + assert!(seed_rotation_interval % leader_rotation_interval == 0); + + LeaderScheduler { + use_only_bootstrap_leader: false, + leader_rotation_interval, + seed_rotation_interval, + leader_schedule: Vec::new(), + last_seed_height: None, + bootstrap_leader: Pubkey::default(), + bootstrap_height, + active_window_length, + seed: 0, + } + } + + pub fn is_leader_rotation_height(&self, height: u64) -> bool { + if self.use_only_bootstrap_leader { + return false; + } + + if height < self.bootstrap_height { + return false; + } + + (height - self.bootstrap_height) % self.leader_rotation_interval == 0 + } + + pub fn count_until_next_leader_rotation(&self, height: u64) -> Option { + if self.use_only_bootstrap_leader { + return None; + } + + if height < self.bootstrap_height { + Some(self.bootstrap_height - height) + } else { + Some( + self.leader_rotation_interval + - ((height - self.bootstrap_height) % self.leader_rotation_interval), + ) + } + } + + // Let Leader X be the leader at the input tick height. This function returns the + // the PoH height at which Leader X's slot ends. + pub fn max_height_for_leader(&self, height: u64) -> Option { + if self.use_only_bootstrap_leader || self.get_scheduled_leader(height).is_none() { + return None; + } + + let result = { + if height < self.bootstrap_height || self.leader_schedule.len() > 1 { + // Two cases to consider: + // + // 1) If height is less than the bootstrap height, then the current leader's + // slot ends when PoH height = bootstrap_height + // + // 2) Otherwise, if height >= bootstrap height, then we have generated a schedule. + // If this leader is not the only one in the schedule, then they will + // only be leader until the end of this slot (someone else is then guaranteed + // to take over) + // + // Both above cases are calculated by the function: + // count_until_next_leader_rotation() + height + self.count_until_next_leader_rotation(height).expect( + "Should return some value when not using default implementation + of LeaderScheduler", + ) + height + } else { + // If the height is greater than bootstrap_height and this leader is + // the only leader in the schedule, then that leader will be in power + // for every slot until the next epoch, which is seed_rotation_interval + // PoH counts from the beginning of the last epoch. + self.last_seed_height.expect( + "If height >= bootstrap height, then we expect + a seed has been generated", + ) + self.seed_rotation_interval + } + }; + + Some(result) + } + + pub fn reset(&mut self) { + self.last_seed_height = None; + } + + pub fn update_height(&mut self, height: u64, bank: &Bank) { + if self.use_only_bootstrap_leader { + return; + } + + if height < self.bootstrap_height { + return; + } + + if let Some(last_seed_height) = self.last_seed_height { + if height <= last_seed_height { + return; + } + } + + if (height - self.bootstrap_height) % self.seed_rotation_interval == 0 { + self.generate_schedule(height, bank); + } + } + + // Uses the schedule generated by the last call to generate_schedule() to return the + // leader for a given PoH height in round-robin fashion + pub fn get_scheduled_leader(&self, height: u64) -> Option<(Pubkey, u64)> { + if self.use_only_bootstrap_leader { + return Some((self.bootstrap_leader, 0)); + } + + // This covers cases where the schedule isn't yet generated. + if self.last_seed_height == None { + if height < self.bootstrap_height { + return Some((self.bootstrap_leader, 0)); + } else { + // If there's been no schedule generated yet before we reach the end of the + // bootstrapping period, then the leader is unknown + return None; + } + } + + // If we have a schedule, then just check that we are within the bounds of that + // schedule [last_seed_height, last_seed_height + seed_rotation_interval). + // Leaders outside of this bound are undefined. + let last_seed_height = self.last_seed_height.unwrap(); + if height >= last_seed_height + self.seed_rotation_interval || height < last_seed_height { + return None; + } + + // Find index into the leader_schedule that this PoH height maps to + let leader_slot = (height - self.bootstrap_height) / self.leader_rotation_interval + 1; + let index = (height - last_seed_height) / self.leader_rotation_interval; + let validator_index = index as usize % self.leader_schedule.len(); + Some((self.leader_schedule[validator_index], leader_slot)) + } + + pub fn get_leader_for_slot(&self, slot_height: u64) -> Option { + let tick_height = self.slot_height_to_first_tick_height(slot_height); + self.get_scheduled_leader(tick_height).map(|(id, _)| id) + } + + // Maps the nth slot (where n == slot_height) to the tick height of + // the first tick for that slot + fn slot_height_to_first_tick_height(&self, slot_height: u64) -> u64 { + if slot_height == 0 { + 0 + } else { + (slot_height - 1) * self.leader_rotation_interval + self.bootstrap_height + } + } + + // TODO: We use a HashSet for now because a single validator could potentially register + // multiple vote account. Once that is no longer possible (see the TODO in vote_program.rs, + // process_transaction(), case VoteInstruction::RegisterAccount), we can use a vector. + fn get_active_set(&mut self, height: u64, bank: &Bank) -> HashSet { + let upper_bound = height; + let lower_bound = height.saturating_sub(self.active_window_length); + + { + let accounts = bank.accounts.read().unwrap(); + + // TODO: iterate through checkpoints, too + accounts + .accounts + .values() + .filter_map(|account| { + if VoteProgram::check_id(&account.owner) { + if let Ok(vote_state) = VoteProgram::deserialize(&account.userdata) { + return vote_state + .votes + .back() + .filter(|vote| { + vote.tick_height > lower_bound + && vote.tick_height <= upper_bound + }).map(|_| vote_state.node_id); + } + } + + None + }).collect() + } + } + + // Called every seed_rotation_interval entries, generates the leader schedule + // for the range of entries: [height, height + seed_rotation_interval) + fn generate_schedule(&mut self, height: u64, bank: &Bank) { + assert!(height >= self.bootstrap_height); + assert!((height - self.bootstrap_height) % self.seed_rotation_interval == 0); + let seed = Self::calculate_seed(height); + self.seed = seed; + let active_set = self.get_active_set(height, &bank); + let ranked_active_set = Self::rank_active_set(bank, active_set.iter()); + + // Handle case where there are no active validators with + // non-zero stake. In this case, use the bootstrap leader for + // the upcoming rounds + if ranked_active_set.is_empty() { + self.last_seed_height = Some(height); + self.leader_schedule = vec![self.bootstrap_leader]; + self.last_seed_height = Some(height); + return; + } + + let (mut validator_rankings, total_stake) = ranked_active_set.iter().fold( + (Vec::with_capacity(ranked_active_set.len()), 0), + |(mut ids, total_stake), (pk, stake)| { + ids.push(**pk); + (ids, total_stake + stake) + }, + ); + + // Choose the validator that will be the first to be the leader in this new + // schedule + let ordered_account_stake = ranked_active_set.into_iter().map(|(_, stake)| stake); + let start_index = Self::choose_account(ordered_account_stake, self.seed, total_stake); + validator_rankings.rotate_left(start_index); + + // There are only seed_rotation_interval / self.leader_rotation_interval slots, so + // we only need to keep at most that many validators in the schedule + let slots_per_epoch = self.seed_rotation_interval / self.leader_rotation_interval; + + // If possible, try to avoid having the same leader twice in a row, but + // if there's only one leader to choose from, then we have no other choice + if validator_rankings.len() > 1 { + let (old_epoch_last_leader, _) = self + .get_scheduled_leader(height - 1) + .expect("Previous leader schedule should still exist"); + let new_epoch_start_leader = validator_rankings[0]; + + if old_epoch_last_leader == new_epoch_start_leader { + if slots_per_epoch == 1 { + // If there is only one slot per epoch, and the same leader as the last slot + // of the previous epoch was chosen, then pick the next leader in the + // rankings instead + validator_rankings[0] = validator_rankings[1]; + } else { + // If there is more than one leader in the schedule, truncate and set the most + // recent leader to the back of the line. This way that node will still remain + // in the rotation, just at a later slot. + validator_rankings.truncate(slots_per_epoch as usize); + validator_rankings.rotate_left(1); + } + } + } + + self.leader_schedule = validator_rankings; + self.last_seed_height = Some(height); + } + + fn rank_active_set<'a, I>(bank: &Bank, active: I) -> Vec<(&'a Pubkey, u64)> + where + I: Iterator, + { + let mut active_accounts: Vec<(&'a Pubkey, u64)> = active + .filter_map(|pk| { + let stake = bank.get_stake(pk); + if stake > 0 { + Some((pk, stake as u64)) + } else { + None + } + }).collect(); + + active_accounts.sort_by( + |(pk1, t1), (pk2, t2)| { + if t1 == t2 { + pk1.cmp(&pk2) + } else { + t1.cmp(&t2) + } + }, + ); + active_accounts + } + + fn calculate_seed(height: u64) -> u64 { + let hash = hash(&serialize(&height).unwrap()); + let bytes = hash.as_ref(); + let mut rdr = Cursor::new(bytes); + rdr.read_u64::().unwrap() + } + + fn choose_account(stakes: I, seed: u64, total_stake: u64) -> usize + where + I: IntoIterator, + { + let mut total = 0; + let mut chosen_account = 0; + let seed = seed % total_stake; + for (i, s) in stakes.into_iter().enumerate() { + // We should have filtered out all accounts with zero stake in + // rank_active_set() + assert!(s != 0); + total += s; + if total > seed { + chosen_account = i; + break; + } + } + + chosen_account + } +} + +impl Default for LeaderScheduler { + // Create a dummy leader scheduler + fn default() -> Self { + let id = Pubkey::default(); + Self::from_bootstrap_leader(id) + } +} + +// Create two entries so that the node with keypair == active_keypair +// is in the active set for leader selection: +// 1) Give the node a nonzero number of tokens, +// 2) A vote from the validator +pub fn make_active_set_entries( + active_keypair: &Keypair, + token_source: &Keypair, + last_entry_id: &Hash, + last_tick_id: &Hash, + num_ending_ticks: usize, +) -> (Vec, Keypair) { + // 1) Create transfer token entry + let transfer_tx = + Transaction::system_new(&token_source, active_keypair.pubkey(), 2, *last_tick_id); + let transfer_entry = Entry::new(last_entry_id, 1, vec![transfer_tx]); + let mut last_entry_id = transfer_entry.id; + + // 2) Create the vote account + let vote_account = Keypair::new(); + let create_vote_account_tx = + Transaction::vote_account_new(active_keypair, vote_account.pubkey(), *last_tick_id, 1); + + let create_vote_account_entry = Entry::new(&last_entry_id, 1, vec![create_vote_account_tx]); + last_entry_id = create_vote_account_entry.id; + + // 3) Register the vote account + let register_vote_account_tx = + Transaction::vote_account_register(active_keypair, vote_account.pubkey(), *last_tick_id, 0); + + let register_vote_account_entry = Entry::new(&last_entry_id, 1, vec![register_vote_account_tx]); + last_entry_id = register_vote_account_entry.id; + + // 4) Create vote entry + let vote = Vote { tick_height: 1 }; + let vote_tx = Transaction::vote_new(&vote_account, vote, *last_tick_id, 0); + let vote_entry = Entry::new(&last_entry_id, 1, vec![vote_tx]); + last_entry_id = vote_entry.id; + + // 5) Create the ending empty ticks + let mut txs = vec![ + transfer_entry, + create_vote_account_entry, + register_vote_account_entry, + vote_entry, + ]; + let empty_ticks = create_ticks(num_ending_ticks, last_entry_id); + txs.extend(empty_ticks); + (txs, vote_account) +} + +#[cfg(test)] +mod tests { + use bank::Bank; + use leader_scheduler::{ + LeaderScheduler, LeaderSchedulerConfig, DEFAULT_BOOTSTRAP_HEIGHT, + DEFAULT_LEADER_ROTATION_INTERVAL, DEFAULT_SEED_ROTATION_INTERVAL, + }; + use mint::Mint; + use signature::{Keypair, KeypairUtil}; + use solana_sdk::hash::Hash; + use solana_sdk::pubkey::Pubkey; + use std::collections::HashSet; + use std::hash::Hash as StdHash; + use std::iter::FromIterator; + use transaction::Transaction; + use vote_program::Vote; + use vote_transaction::{create_vote_account, VoteTransaction}; + + fn to_hashset_owned(slice: &[T]) -> HashSet + where + T: Eq + StdHash + Clone, + { + HashSet::from_iter(slice.iter().cloned()) + } + + fn push_vote(vote_account: &Keypair, bank: &Bank, height: u64, last_id: Hash) { + let vote = Vote { + tick_height: height, + }; + + let new_vote_tx = Transaction::vote_new(vote_account, vote, last_id, 0); + + bank.process_transaction(&new_vote_tx).unwrap(); + } + + fn run_scheduler_test( + num_validators: usize, + bootstrap_height: u64, + leader_rotation_interval: u64, + seed_rotation_interval: u64, + ) { + // Allow the validators to be in the active window for the entire test + let active_window_length = seed_rotation_interval + bootstrap_height; + + // Set up the LeaderScheduler struct + let bootstrap_leader_id = Keypair::new().pubkey(); + let leader_scheduler_config = LeaderSchedulerConfig::new( + Some(bootstrap_height), + Some(leader_rotation_interval), + Some(seed_rotation_interval), + Some(active_window_length), + ); + + let mut leader_scheduler = LeaderScheduler::new(&leader_scheduler_config); + leader_scheduler.bootstrap_leader = bootstrap_leader_id; + + // Create the bank and validators, which are inserted in order of account balance + let num_vote_account_tokens = 1; + let mint = Mint::new( + (((num_validators + 1) / 2) * (num_validators + 1) + + num_vote_account_tokens * num_validators) as u64, + ); + let bank = Bank::new(&mint); + let mut validators = vec![]; + let last_id = mint + .create_entries() + .last() + .expect("Mint should not create empty genesis entries") + .id; + for i in 0..num_validators { + let new_validator = Keypair::new(); + let new_pubkey = new_validator.pubkey(); + validators.push(new_pubkey); + // Give the validator some tokens + bank.transfer( + (i + 1 + num_vote_account_tokens) as u64, + &mint.keypair(), + new_pubkey, + last_id, + ).unwrap(); + + // Create a vote account + let new_vote_account = create_vote_account( + &new_validator, + &bank, + num_vote_account_tokens as u64, + mint.last_id(), + ).unwrap(); + // Vote to make the validator part of the active set for the entire test + // (we made the active_window_length large enough at the beginning of the test) + push_vote(&new_vote_account, &bank, 1, mint.last_id()); + } + + // The scheduled leader during the bootstrapping period (assuming a seed + schedule + // haven't been generated, otherwise that schedule takes precendent) should always + // be the bootstrap leader + assert_eq!( + leader_scheduler.get_scheduled_leader(0), + Some((bootstrap_leader_id, 0)) + ); + assert_eq!( + leader_scheduler.get_scheduled_leader(bootstrap_height - 1), + Some((bootstrap_leader_id, 0)) + ); + assert_eq!( + leader_scheduler.get_scheduled_leader(bootstrap_height), + None + ); + + // Generate the schedule at the end of the bootstrapping period, should be the + // same leader for the next leader_rotation_interval entries + leader_scheduler.generate_schedule(bootstrap_height, &bank); + + // The leader outside of the newly generated schedule window: + // [bootstrap_height, bootstrap_height + seed_rotation_interval) + // should be undefined + assert_eq!( + leader_scheduler.get_scheduled_leader(bootstrap_height - 1), + None, + ); + + assert_eq!( + leader_scheduler.get_scheduled_leader(bootstrap_height + seed_rotation_interval), + None, + ); + + // For the next seed_rotation_interval entries, call get_scheduled_leader every + // leader_rotation_interval entries, and the next leader should be the next validator + // in order of stake + + // Note: seed_rotation_interval must be divisible by leader_rotation_interval, enforced + // by the LeaderScheduler constructor + let num_rounds = seed_rotation_interval / leader_rotation_interval; + let mut start_leader_index = None; + for i in 0..num_rounds { + let begin_height = bootstrap_height + i * leader_rotation_interval; + let (current_leader, slot) = leader_scheduler + .get_scheduled_leader(begin_height) + .expect("Expected a leader from scheduler"); + + // Note: The "validators" vector is already sorted by stake, so the expected order + // for the leader schedule can be derived by just iterating through the vector + // in order. The only excpetion is for the bootstrap leader in the schedule, we need to + // find the index into the "validators" vector where the schedule begins. + if None == start_leader_index { + start_leader_index = Some( + validators + .iter() + .position(|v| *v == current_leader) + .unwrap(), + ); + } + + let expected_leader = + validators[(start_leader_index.unwrap() + i as usize) % num_validators]; + assert_eq!(current_leader, expected_leader); + assert_eq!(slot, i + 1); + // Check that the same leader is in power for the next leader_rotation_interval entries + assert_eq!( + leader_scheduler.get_scheduled_leader(begin_height + leader_rotation_interval - 1), + Some((current_leader, slot)) + ); + } + } + + #[test] + fn test_active_set() { + let leader_id = Keypair::new().pubkey(); + let active_window_length = 1000; + let mint = Mint::new_with_leader(10000, leader_id, 500); + let bank = Bank::new(&mint); + + let leader_scheduler_config = + LeaderSchedulerConfig::new(Some(100), Some(100), Some(100), Some(active_window_length)); + + let mut leader_scheduler = LeaderScheduler::new(&leader_scheduler_config); + leader_scheduler.bootstrap_leader = leader_id; + + // Insert a bunch of votes at height "start_height" + let start_height = 3; + let num_old_ids = 20; + let mut old_ids = HashSet::new(); + for _ in 0..num_old_ids { + let new_keypair = Keypair::new(); + let pk = new_keypair.pubkey(); + old_ids.insert(pk.clone()); + + // Give the account some stake + bank.transfer(5, &mint.keypair(), pk, mint.last_id()) + .unwrap(); + + // Create a vote account + let new_vote_account = + create_vote_account(&new_keypair, &bank, 1, mint.last_id()).unwrap(); + + // Push a vote for the account + push_vote(&new_vote_account, &bank, start_height, mint.last_id()); + } + + // Insert a bunch of votes at height "start_height + active_window_length" + let num_new_ids = 10; + let mut new_ids = HashSet::new(); + for _ in 0..num_new_ids { + let new_keypair = Keypair::new(); + let pk = new_keypair.pubkey(); + new_ids.insert(pk); + // Give the account some stake + bank.transfer(5, &mint.keypair(), pk, mint.last_id()) + .unwrap(); + + // Create a vote account + let new_vote_account = + create_vote_account(&new_keypair, &bank, 1, mint.last_id()).unwrap(); + + push_vote( + &new_vote_account, + &bank, + start_height + active_window_length, + mint.last_id(), + ); + } + + // Queries for the active set + let result = + leader_scheduler.get_active_set(active_window_length + start_height - 1, &bank); + assert_eq!(result, old_ids); + + let result = leader_scheduler.get_active_set(active_window_length + start_height, &bank); + assert_eq!(result, new_ids); + + let result = + leader_scheduler.get_active_set(2 * active_window_length + start_height - 1, &bank); + assert_eq!(result, new_ids); + + let result = + leader_scheduler.get_active_set(2 * active_window_length + start_height, &bank); + assert!(result.is_empty()); + } + + #[test] + fn test_seed() { + // Check that num_seeds different seeds are generated + let num_seeds = 1000; + let mut old_seeds = HashSet::new(); + for i in 0..num_seeds { + let seed = LeaderScheduler::calculate_seed(i); + assert!(!old_seeds.contains(&seed)); + old_seeds.insert(seed); + } + } + + #[test] + fn test_rank_active_set() { + let num_validators: usize = 101; + // Give mint sum(1..num_validators) tokens + let mint = Mint::new((((num_validators + 1) / 2) * (num_validators + 1)) as u64); + let bank = Bank::new(&mint); + let mut validators = vec![]; + let last_id = mint + .create_entries() + .last() + .expect("Mint should not create empty genesis entries") + .id; + for i in 0..num_validators { + let new_validator = Keypair::new(); + let new_pubkey = new_validator.pubkey(); + validators.push(new_validator); + bank.transfer( + (num_validators - i) as u64, + &mint.keypair(), + new_pubkey, + last_id, + ).unwrap(); + } + + let validators_pk: Vec = validators.iter().map(Keypair::pubkey).collect(); + let result = LeaderScheduler::rank_active_set(&bank, validators_pk.iter()); + + assert_eq!(result.len(), validators.len()); + + // Expect the result to be the reverse of the list we passed into the rank_active_set() + for (i, (pk, stake)) in result.into_iter().enumerate() { + assert_eq!(stake, i as u64 + 1); + assert_eq!(*pk, validators[num_validators - i - 1].pubkey()); + } + + // Transfer all the tokens to a new set of validators, old validators should now + // have balance of zero and get filtered out of the rankings + let mut new_validators = vec![]; + for i in 0..num_validators { + let new_validator = Keypair::new(); + let new_pubkey = new_validator.pubkey(); + new_validators.push(new_validator); + bank.transfer( + (num_validators - i) as u64, + &validators[i], + new_pubkey, + last_id, + ).unwrap(); + } + + let all_validators: Vec = validators + .iter() + .chain(new_validators.iter()) + .map(Keypair::pubkey) + .collect(); + let result = LeaderScheduler::rank_active_set(&bank, all_validators.iter()); + assert_eq!(result.len(), new_validators.len()); + + for (i, (pk, balance)) in result.into_iter().enumerate() { + assert_eq!(balance, i as u64 + 1); + assert_eq!(*pk, new_validators[num_validators - i - 1].pubkey()); + } + + // Break ties between validators with the same balances using public key + let mint = Mint::new(num_validators as u64); + let bank = Bank::new(&mint); + let mut tied_validators_pk = vec![]; + let last_id = mint + .create_entries() + .last() + .expect("Mint should not create empty genesis entries") + .id; + + for _ in 0..num_validators { + let new_validator = Keypair::new(); + let new_pubkey = new_validator.pubkey(); + tied_validators_pk.push(new_pubkey); + bank.transfer(1, &mint.keypair(), new_pubkey, last_id) + .unwrap(); + } + + let result = LeaderScheduler::rank_active_set(&bank, tied_validators_pk.iter()); + let mut sorted: Vec<&Pubkey> = tied_validators_pk.iter().map(|x| x).collect(); + sorted.sort_by(|pk1, pk2| pk1.cmp(pk2)); + assert_eq!(result.len(), tied_validators_pk.len()); + for (i, (pk, s)) in result.into_iter().enumerate() { + assert_eq!(s, 1); + assert_eq!(*pk, *sorted[i]); + } + } + + #[test] + fn test_choose_account() { + let tokens = vec![10, 30, 50, 5, 1]; + let total_tokens = tokens.iter().sum(); + let mut seed = tokens[0]; + assert_eq!( + LeaderScheduler::choose_account(tokens.clone(), seed, total_tokens), + 1 + ); + + seed = tokens[0] - 1; + assert_eq!( + LeaderScheduler::choose_account(tokens.clone(), seed, total_tokens), + 0 + ); + + seed = 0; + assert_eq!( + LeaderScheduler::choose_account(tokens.clone(), seed, total_tokens), + 0 + ); + + seed = total_tokens; + assert_eq!( + LeaderScheduler::choose_account(tokens.clone(), seed, total_tokens), + 0 + ); + + seed = total_tokens - 1; + assert_eq!( + LeaderScheduler::choose_account(tokens.clone(), seed, total_tokens), + tokens.len() - 1 + ); + + seed = tokens[0..3].iter().sum(); + assert_eq!( + LeaderScheduler::choose_account(tokens.clone(), seed, total_tokens), + 3 + ); + } + + #[test] + fn test_scheduler() { + // Test when the number of validators equals + // seed_rotation_interval / leader_rotation_interval, so each validator + // is selected once + let mut num_validators = 100; + let mut bootstrap_height = 500; + let mut leader_rotation_interval = 100; + let mut seed_rotation_interval = leader_rotation_interval * num_validators; + run_scheduler_test( + num_validators, + bootstrap_height, + leader_rotation_interval as u64, + seed_rotation_interval as u64, + ); + + // Test when there are fewer validators than + // seed_rotation_interval / leader_rotation_interval, so each validator + // is selected multiple times + num_validators = 3; + bootstrap_height = 500; + leader_rotation_interval = 100; + seed_rotation_interval = 1000; + run_scheduler_test( + num_validators, + bootstrap_height, + leader_rotation_interval as u64, + seed_rotation_interval as u64, + ); + + // Test when there are fewer number of validators than + // seed_rotation_interval / leader_rotation_interval, so each validator + // may not be selected + num_validators = 10; + bootstrap_height = 500; + leader_rotation_interval = 100; + seed_rotation_interval = 200; + run_scheduler_test( + num_validators, + bootstrap_height, + leader_rotation_interval as u64, + seed_rotation_interval as u64, + ); + + // Test when seed_rotation_interval == leader_rotation_interval, + // only one validator should be selected + num_validators = 10; + bootstrap_height = 1; + leader_rotation_interval = 1; + seed_rotation_interval = 1; + run_scheduler_test( + num_validators, + bootstrap_height, + leader_rotation_interval as u64, + seed_rotation_interval as u64, + ); + } + + #[test] + fn test_scheduler_active_window() { + let num_validators = 10; + let num_vote_account_tokens = 1; + // Set up the LeaderScheduler struct + let bootstrap_leader_id = Keypair::new().pubkey(); + let bootstrap_height = 500; + let leader_rotation_interval = 100; + // Make sure seed_rotation_interval is big enough so we select all the + // validators as part of the schedule each time (we need to check the active window + // is the cause of validators being truncated later) + let seed_rotation_interval = leader_rotation_interval * num_validators; + let active_window_length = seed_rotation_interval; + + let leader_scheduler_config = LeaderSchedulerConfig::new( + Some(bootstrap_height), + Some(leader_rotation_interval), + Some(seed_rotation_interval), + Some(active_window_length), + ); + + let mut leader_scheduler = LeaderScheduler::new(&leader_scheduler_config); + leader_scheduler.bootstrap_leader = bootstrap_leader_id; + + // Create the bank and validators + let mint = Mint::new( + ((((num_validators + 1) / 2) * (num_validators + 1)) + + (num_vote_account_tokens * num_validators)) as u64, + ); + let bank = Bank::new(&mint); + let mut validators = vec![]; + let last_id = mint + .create_entries() + .last() + .expect("Mint should not create empty genesis entries") + .id; + for i in 0..num_validators { + let new_validator = Keypair::new(); + let new_pubkey = new_validator.pubkey(); + validators.push(new_pubkey); + // Give the validator some tokens + bank.transfer( + (i + 1 + num_vote_account_tokens) as u64, + &mint.keypair(), + new_pubkey, + last_id, + ).unwrap(); + + // Create a vote account + let new_vote_account = create_vote_account( + &new_validator, + &bank, + num_vote_account_tokens as u64, + mint.last_id(), + ).unwrap(); + + // Vote at height i * active_window_length for validator i + push_vote( + &new_vote_account, + &bank, + i * active_window_length + bootstrap_height, + mint.last_id(), + ); + } + + // Generate schedule every active_window_length entries and check that + // validators are falling out of the rotation as they fall out of the + // active set + for i in 0..=num_validators { + leader_scheduler.generate_schedule(i * active_window_length + bootstrap_height, &bank); + let result = &leader_scheduler.leader_schedule; + let expected = if i == num_validators { + bootstrap_leader_id + } else { + validators[i as usize] + }; + + assert_eq!(vec![expected], *result); + } + } + + #[test] + fn test_multiple_vote() { + let leader_keypair = Keypair::new(); + let leader_id = leader_keypair.pubkey(); + let active_window_length = 1000; + let mint = Mint::new_with_leader(10000, leader_id, 500); + let bank = Bank::new(&mint); + + let leader_scheduler_config = + LeaderSchedulerConfig::new(Some(100), Some(100), Some(100), Some(active_window_length)); + + let mut leader_scheduler = LeaderScheduler::new(&leader_scheduler_config); + leader_scheduler.bootstrap_leader = leader_id; + + // Check that a node that votes twice in a row will get included in the active + // window + let initial_vote_height = 1; + + // Create a vote account + let new_vote_account = + create_vote_account(&leader_keypair, &bank, 1, mint.last_id()).unwrap(); + + // Vote twice + push_vote( + &new_vote_account, + &bank, + initial_vote_height, + mint.last_id(), + ); + push_vote( + &new_vote_account, + &bank, + initial_vote_height + 1, + mint.last_id(), + ); + + let result = + leader_scheduler.get_active_set(initial_vote_height + active_window_length, &bank); + assert_eq!(result, to_hashset_owned(&vec![leader_id])); + let result = + leader_scheduler.get_active_set(initial_vote_height + active_window_length + 1, &bank); + assert!(result.is_empty()); + } + + #[test] + fn test_update_height() { + let bootstrap_leader_id = Keypair::new().pubkey(); + let bootstrap_height = 500; + let leader_rotation_interval = 100; + // Make sure seed_rotation_interval is big enough so we select all the + // validators as part of the schedule each time (we need to check the active window + // is the cause of validators being truncated later) + let seed_rotation_interval = leader_rotation_interval; + let active_window_length = 1; + + let leader_scheduler_config = LeaderSchedulerConfig::new( + Some(bootstrap_height), + Some(leader_rotation_interval), + Some(seed_rotation_interval), + Some(active_window_length), + ); + + let mut leader_scheduler = LeaderScheduler::new(&leader_scheduler_config); + leader_scheduler.bootstrap_leader = bootstrap_leader_id; + + // Check that the generate_schedule() function is being called by the + // update_height() function at the correct entry heights. + let bank = Bank::default(); + leader_scheduler.update_height(bootstrap_height - 1, &bank); + assert_eq!(leader_scheduler.last_seed_height, None); + leader_scheduler.update_height(bootstrap_height, &bank); + assert_eq!(leader_scheduler.last_seed_height, Some(bootstrap_height)); + leader_scheduler.update_height(bootstrap_height + seed_rotation_interval - 1, &bank); + assert_eq!(leader_scheduler.last_seed_height, Some(bootstrap_height)); + leader_scheduler.update_height(bootstrap_height + seed_rotation_interval, &bank); + assert_eq!( + leader_scheduler.last_seed_height, + Some(bootstrap_height + seed_rotation_interval) + ); + } + + #[test] + fn test_constructors() { + let bootstrap_leader_id = Keypair::new().pubkey(); + + // Check defaults for LeaderScheduler + let leader_scheduler_config = LeaderSchedulerConfig::new(None, None, None, None); + + let leader_scheduler = LeaderScheduler::new(&leader_scheduler_config); + + assert_eq!(leader_scheduler.bootstrap_leader, Pubkey::default()); + + assert_eq!(leader_scheduler.bootstrap_height, DEFAULT_BOOTSTRAP_HEIGHT); + + assert_eq!( + leader_scheduler.leader_rotation_interval, + DEFAULT_LEADER_ROTATION_INTERVAL + ); + assert_eq!( + leader_scheduler.seed_rotation_interval, + DEFAULT_SEED_ROTATION_INTERVAL + ); + + // Check actual arguments for LeaderScheduler + let bootstrap_height = 500; + let leader_rotation_interval = 100; + let seed_rotation_interval = 200; + let active_window_length = 1; + + let leader_scheduler_config = LeaderSchedulerConfig::new( + Some(bootstrap_height), + Some(leader_rotation_interval), + Some(seed_rotation_interval), + Some(active_window_length), + ); + + let mut leader_scheduler = LeaderScheduler::new(&leader_scheduler_config); + leader_scheduler.bootstrap_leader = bootstrap_leader_id; + + assert_eq!(leader_scheduler.bootstrap_height, bootstrap_height); + + assert_eq!( + leader_scheduler.leader_rotation_interval, + leader_rotation_interval + ); + assert_eq!( + leader_scheduler.seed_rotation_interval, + seed_rotation_interval + ); + } + + fn run_consecutive_leader_test(num_slots_per_epoch: u64, add_validator: bool) { + let bootstrap_leader_keypair = Keypair::new(); + let bootstrap_leader_id = bootstrap_leader_keypair.pubkey(); + let bootstrap_height = 500; + let leader_rotation_interval = 100; + let seed_rotation_interval = num_slots_per_epoch * leader_rotation_interval; + let active_window_length = bootstrap_height + seed_rotation_interval; + + let leader_scheduler_config = LeaderSchedulerConfig::new( + Some(bootstrap_height), + Some(leader_rotation_interval), + Some(seed_rotation_interval), + Some(active_window_length), + ); + + let mut leader_scheduler = LeaderScheduler::new(&leader_scheduler_config); + leader_scheduler.bootstrap_leader = bootstrap_leader_id; + + // Create mint and bank + let mint = Mint::new_with_leader(10000, bootstrap_leader_id, 0); + let bank = Bank::new(&mint); + let last_id = mint + .create_entries() + .last() + .expect("Mint should not create empty genesis entries") + .id; + let initial_vote_height = 1; + + // Create and add validator to the active set + let validator_keypair = Keypair::new(); + let validator_id = validator_keypair.pubkey(); + if add_validator { + bank.transfer(5, &mint.keypair(), validator_id, last_id) + .unwrap(); + // Create a vote account + let new_vote_account = + create_vote_account(&validator_keypair, &bank, 1, mint.last_id()).unwrap(); + push_vote( + &new_vote_account, + &bank, + initial_vote_height, + mint.last_id(), + ); + } + + // Make sure the bootstrap leader, not the validator, is picked again on next slot + // Depending on the seed, we make the leader stake either 2, or 3. Because the + // validator stake is always 1, then the rankings will always be + // [(validator, 1), (leader, leader_stake)]. Thus we just need to make sure that + // seed % (leader_stake + 1) > 0 to make sure that the leader is picked again. + let seed = LeaderScheduler::calculate_seed(bootstrap_height); + let leader_stake = { + if seed % 3 == 0 { + 3 + } else { + 2 + } + }; + + let vote_account_tokens = 1; + bank.transfer( + leader_stake + vote_account_tokens, + &mint.keypair(), + bootstrap_leader_id, + last_id, + ).unwrap(); + + // Create a vote account + let new_vote_account = create_vote_account( + &bootstrap_leader_keypair, + &bank, + vote_account_tokens, + mint.last_id(), + ).unwrap(); + + // Add leader to the active set + push_vote( + &new_vote_account, + &bank, + initial_vote_height, + mint.last_id(), + ); + + leader_scheduler.generate_schedule(bootstrap_height, &bank); + + // Make sure the validator, not the leader is selected on the first slot of the + // next epoch + if add_validator { + assert!(leader_scheduler.leader_schedule[0] == validator_id); + } else { + assert!(leader_scheduler.leader_schedule[0] == bootstrap_leader_id); + } + } + + #[test] + fn test_avoid_consecutive_leaders() { + // Test when there is both a leader + validator in the active set + run_consecutive_leader_test(1, true); + run_consecutive_leader_test(2, true); + run_consecutive_leader_test(10, true); + + // Test when there is only one node in the active set + run_consecutive_leader_test(1, false); + run_consecutive_leader_test(2, false); + run_consecutive_leader_test(10, false); + } + + #[test] + fn test_max_height_for_leader() { + let bootstrap_leader_keypair = Keypair::new(); + let bootstrap_leader_id = bootstrap_leader_keypair.pubkey(); + let bootstrap_height = 500; + let leader_rotation_interval = 100; + let seed_rotation_interval = 2 * leader_rotation_interval; + let active_window_length = bootstrap_height + seed_rotation_interval; + + let leader_scheduler_config = LeaderSchedulerConfig::new( + Some(bootstrap_height), + Some(leader_rotation_interval), + Some(seed_rotation_interval), + Some(active_window_length), + ); + + let mut leader_scheduler = LeaderScheduler::new(&leader_scheduler_config); + leader_scheduler.bootstrap_leader = bootstrap_leader_id; + + // Create mint and bank + let mint = Mint::new_with_leader(10000, bootstrap_leader_id, 500); + let bank = Bank::new(&mint); + let last_id = mint + .create_entries() + .last() + .expect("Mint should not create empty genesis entries") + .id; + let initial_vote_height = 1; + + // No schedule generated yet, so for all heights < bootstrap height, the + // max height will be bootstrap leader + assert_eq!( + leader_scheduler.max_height_for_leader(0), + Some(bootstrap_height) + ); + assert_eq!( + leader_scheduler.max_height_for_leader(bootstrap_height - 1), + Some(bootstrap_height) + ); + assert_eq!( + leader_scheduler.max_height_for_leader(bootstrap_height), + None + ); + + // Test when the active set == 1 node + + // Generate schedule where the bootstrap leader will be the only + // choice because the active set is empty. Thus if the schedule + // was generated on PoH height bootstrap_height + n * seed_rotation_interval, + // then the same leader will be in power until PoH height + // bootstrap_height + (n + 1) * seed_rotation_interval + leader_scheduler.generate_schedule(bootstrap_height, &bank); + assert_eq!( + leader_scheduler.max_height_for_leader(bootstrap_height), + Some(bootstrap_height + seed_rotation_interval) + ); + assert_eq!( + leader_scheduler.max_height_for_leader(bootstrap_height - 1), + None + ); + leader_scheduler.generate_schedule(bootstrap_height + seed_rotation_interval, &bank); + assert_eq!( + leader_scheduler.max_height_for_leader(bootstrap_height + seed_rotation_interval), + Some(bootstrap_height + 2 * seed_rotation_interval) + ); + assert_eq!( + leader_scheduler.max_height_for_leader(bootstrap_height + seed_rotation_interval - 1), + None + ); + + leader_scheduler.reset(); + + // Now test when the active set > 1 node + + // Create and add validator to the active set + let validator_keypair = Keypair::new(); + let validator_id = validator_keypair.pubkey(); + + // Create a vote account for the validator + bank.transfer(5, &mint.keypair(), validator_id, last_id) + .unwrap(); + let new_validator_vote_account = + create_vote_account(&validator_keypair, &bank, 1, mint.last_id()).unwrap(); + push_vote( + &new_validator_vote_account, + &bank, + initial_vote_height, + mint.last_id(), + ); + + // Create a vote account for the leader + bank.transfer(5, &mint.keypair(), bootstrap_leader_id, last_id) + .unwrap(); + let new_leader_vote_account = + create_vote_account(&bootstrap_leader_keypair, &bank, 1, mint.last_id()).unwrap(); + + // Add leader to the active set + push_vote( + &new_leader_vote_account, + &bank, + initial_vote_height, + mint.last_id(), + ); + + // Generate the schedule + leader_scheduler.generate_schedule(bootstrap_height, &bank); + + assert_eq!( + leader_scheduler.max_height_for_leader(bootstrap_height), + Some(bootstrap_height + leader_rotation_interval) + ); + assert_eq!( + leader_scheduler.max_height_for_leader(bootstrap_height - 1), + None + ); + assert_eq!( + leader_scheduler.max_height_for_leader(bootstrap_height + leader_rotation_interval), + Some(bootstrap_height + 2 * leader_rotation_interval) + ); + assert_eq!( + leader_scheduler.max_height_for_leader(bootstrap_height + seed_rotation_interval), + None, + ); + + leader_scheduler.generate_schedule(bootstrap_height + seed_rotation_interval, &bank); + + assert_eq!( + leader_scheduler.max_height_for_leader(bootstrap_height + seed_rotation_interval), + Some(bootstrap_height + seed_rotation_interval + leader_rotation_interval) + ); + assert_eq!( + leader_scheduler.max_height_for_leader(bootstrap_height + seed_rotation_interval - 1), + None + ); + assert_eq!( + leader_scheduler.max_height_for_leader(bootstrap_height + 2 * seed_rotation_interval), + None + ); + } +} diff --git a/book/ledger.html b/book/ledger.html new file mode 100644 index 00000000000000..507c63ca61c5a1 --- /dev/null +++ b/book/ledger.html @@ -0,0 +1,200 @@ + + + + + + Ledger format - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

Ledger format

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/ledger.rs b/book/ledger.rs new file mode 100644 index 00000000000000..e37314dc2abed8 --- /dev/null +++ b/book/ledger.rs @@ -0,0 +1,1007 @@ +//! The `ledger` module provides functions for parallel verification of the +//! Proof of History ledger as well as iterative read, append write, and random +//! access read to a persistent file-based ledger. + +use bincode::{self, deserialize_from, serialize_into, serialized_size}; +#[cfg(test)] +use budget_transaction::BudgetTransaction; +#[cfg(test)] +use chrono::prelude::Utc; +use entry::Entry; +use log::Level::Trace; +use mint::Mint; +use packet::{SharedBlob, BLOB_DATA_SIZE}; +use rayon::prelude::*; +use signature::{Keypair, KeypairUtil}; +#[cfg(test)] +use solana_sdk::hash::hash; +use solana_sdk::hash::Hash; +use solana_sdk::pubkey::Pubkey; +use std::fs::{create_dir_all, remove_dir_all, File, OpenOptions}; +use std::io::prelude::*; +use std::io::{self, BufReader, BufWriter, Seek, SeekFrom}; +use std::mem::size_of; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::path::Path; +use transaction::Transaction; +use vote_program::Vote; +use vote_transaction::VoteTransaction; + +// +// A persistent ledger is 2 files: +// ledger_path/ --+ +// +-- index <== an array of u64 offsets into data, +// | each offset points to the first bytes +// | of a u64 that contains the length of +// | the entry. To make the code smaller, +// | index[0] is set to 0, TODO: this field +// | could later be used for other stuff... +// +-- data <== concatenated instances of +// u64 length +// entry data +// +// When opening a ledger, we have the ability to "audit" it, which means we need +// to pick which file to use as "truth", and correct the other file as +// necessary, if possible. +// +// The protocol for writing the ledger is to append to the data file first, the +// index file 2nd. If the writing node is interupted while appending to the +// ledger, there are some possibilities we need to cover: +// +// 1. a partial write of data, which might be a partial write of length +// or a partial write entry data +// 2. a partial or missing write to index for that entry +// +// There is also the possibility of "unsynchronized" reading of the ledger +// during transfer across nodes via rsync (or whatever). In this case, if the +// transfer of the data file is done before the transfer of the index file, +// it's likely that the index file will be far ahead of the data file in time. +// +// The quickest and most reliable strategy for recovery is therefore to treat +// the data file as nearest to the "truth". +// +// The logic for "recovery/audit" is to open index and read backwards from the +// last u64-aligned entry to get to where index and data agree (i.e. where a +// successful deserialization of an entry can be performed), then truncate +// both files to this syncrhonization point. +// + +// ledger window +#[derive(Debug)] +pub struct LedgerWindow { + index: BufReader, + data: BufReader, +} + +pub const LEDGER_DATA_FILE: &str = "data"; +const LEDGER_INDEX_FILE: &str = "index"; + +// use a CONST because there's a cast, and we don't want "sizeof:: as u64"... +const SIZEOF_U64: u64 = size_of::() as u64; + +#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] +fn err_bincode_to_io(e: Box) -> io::Error { + io::Error::new(io::ErrorKind::Other, e.to_string()) +} + +fn read_entry(file: &mut A, len: u64) -> io::Result { + deserialize_from(file.take(len)).map_err(err_bincode_to_io) +} + +fn entry_at(file: &mut A, at: u64) -> io::Result { + let len = u64_at(file, at)?; + + read_entry(file, len) +} + +fn next_entry(file: &mut A) -> io::Result { + let len = deserialize_from(file.take(SIZEOF_U64)).map_err(err_bincode_to_io)?; + read_entry(file, len) +} + +fn u64_at(file: &mut A, at: u64) -> io::Result { + file.seek(SeekFrom::Start(at))?; + deserialize_from(file.take(SIZEOF_U64)).map_err(err_bincode_to_io) +} + +impl LedgerWindow { + // opens a Ledger in directory, provides "infinite" window + // + pub fn open(ledger_path: &str) -> io::Result { + let ledger_path = Path::new(&ledger_path); + + let index = File::open(ledger_path.join(LEDGER_INDEX_FILE))?; + let index = BufReader::new(index); + let data = File::open(ledger_path.join(LEDGER_DATA_FILE))?; + let data = BufReader::with_capacity(BLOB_DATA_SIZE, data); + + Ok(LedgerWindow { index, data }) + } + + pub fn get_entry(&mut self, index: u64) -> io::Result { + let offset = self.get_entry_offset(index)?; + entry_at(&mut self.data, offset) + } + + // Fill 'buf' with num_entries or most number of whole entries that fit into buf.len() + // + // Return tuple of (number of entries read, total size of entries read) + pub fn get_entries_bytes( + &mut self, + start_index: u64, + num_entries: u64, + buf: &mut [u8], + ) -> io::Result<(u64, u64)> { + let start_offset = self.get_entry_offset(start_index)?; + let mut total_entries = 0; + let mut end_offset = 0; + for i in 0..num_entries { + let offset = self.get_entry_offset(start_index + i)?; + let len = u64_at(&mut self.data, offset)?; + let cur_end_offset = offset + len + SIZEOF_U64; + if (cur_end_offset - start_offset) > buf.len() as u64 { + break; + } + end_offset = cur_end_offset; + total_entries += 1; + } + + if total_entries == 0 { + return Ok((0, 0)); + } + + let read_len = end_offset - start_offset; + self.data.seek(SeekFrom::Start(start_offset))?; + let fread_len = self.data.read(&mut buf[..read_len as usize])? as u64; + if fread_len != read_len { + return Err(io::Error::new( + io::ErrorKind::Other, + format!( + "entry read_len({}) doesn't match expected ({})", + fread_len, read_len + ), + )); + } + Ok((total_entries, read_len)) + } + + fn get_entry_offset(&mut self, index: u64) -> io::Result { + u64_at(&mut self.index, index * SIZEOF_U64) + } +} + +pub fn verify_ledger(ledger_path: &str) -> io::Result<()> { + let ledger_path = Path::new(&ledger_path); + + let index = File::open(ledger_path.join(LEDGER_INDEX_FILE))?; + + let index_len = index.metadata()?.len(); + + if index_len % SIZEOF_U64 != 0 { + Err(io::Error::new( + io::ErrorKind::Other, + format!("index is not a multiple of {} bytes long", SIZEOF_U64), + ))?; + } + let mut index = BufReader::new(index); + + let data = File::open(ledger_path.join(LEDGER_DATA_FILE))?; + let mut data = BufReader::with_capacity(BLOB_DATA_SIZE, data); + + let mut last_data_offset = 0; + let mut index_offset = 0; + let mut data_read = 0; + let mut last_len = 0; + let mut i = 0; + + while index_offset < index_len { + let data_offset = u64_at(&mut index, index_offset)?; + + if last_data_offset + last_len != data_offset { + Err(io::Error::new( + io::ErrorKind::Other, + format!( + "at entry[{}], a gap or an overlap last_offset {} offset {} last_len {}", + i, last_data_offset, data_offset, last_len + ), + ))?; + } + + match entry_at(&mut data, data_offset) { + Err(e) => Err(io::Error::new( + io::ErrorKind::Other, + format!( + "entry[{}] deserialize() failed at offset {}, err: {}", + index_offset / SIZEOF_U64, + data_offset, + e.to_string(), + ), + ))?, + Ok(entry) => { + last_len = serialized_size(&entry).map_err(err_bincode_to_io)? + SIZEOF_U64 + } + } + + last_data_offset = data_offset; + data_read += last_len; + index_offset += SIZEOF_U64; + i += 1; + } + let data = data.into_inner(); + if data_read != data.metadata()?.len() { + Err(io::Error::new( + io::ErrorKind::Other, + "garbage on end of data file", + ))?; + } + Ok(()) +} + +fn recover_ledger(ledger_path: &str) -> io::Result<()> { + let ledger_path = Path::new(ledger_path); + let mut index = OpenOptions::new() + .write(true) + .read(true) + .open(ledger_path.join(LEDGER_INDEX_FILE))?; + + let mut data = OpenOptions::new() + .write(true) + .read(true) + .open(ledger_path.join(LEDGER_DATA_FILE))?; + + // first, truncate to a multiple of SIZEOF_U64 + let len = index.metadata()?.len(); + + if len % SIZEOF_U64 != 0 { + trace!("recover: trimming index len to {}", len - len % SIZEOF_U64); + index.set_len(len - (len % SIZEOF_U64))?; + } + + // next, pull index offsets off one at a time until the last one points + // to a valid entry deserialization offset... + loop { + let len = index.metadata()?.len(); + trace!("recover: index len:{}", len); + + // should never happen + if len < SIZEOF_U64 { + trace!("recover: error index len {} too small", len); + + Err(io::Error::new(io::ErrorKind::Other, "empty ledger index"))?; + } + + let offset = u64_at(&mut index, len - SIZEOF_U64)?; + trace!("recover: offset[{}]: {}", (len / SIZEOF_U64) - 1, offset); + + match entry_at(&mut data, offset) { + Ok(entry) => { + trace!("recover: entry[{}]: {:?}", (len / SIZEOF_U64) - 1, entry); + + let entry_len = serialized_size(&entry).map_err(err_bincode_to_io)?; + + trace!("recover: entry_len: {}", entry_len); + + // now trim data file to size... + data.set_len(offset + SIZEOF_U64 + entry_len)?; + + trace!( + "recover: trimmed data file to {}", + offset + SIZEOF_U64 + entry_len + ); + + break; // all good + } + Err(_err) => { + trace!( + "recover: no entry recovered at {} {}", + offset, + _err.to_string() + ); + index.set_len(len - SIZEOF_U64)?; + } + } + } + if log_enabled!(Trace) { + let num_entries = index.metadata()?.len() / SIZEOF_U64; + trace!("recover: done. {} entries", num_entries); + } + + // flush everything to disk... + index.sync_all()?; + data.sync_all() +} + +// TODO?? ... we could open the files on demand to support [], but today +// LedgerWindow needs "&mut self" +// +//impl Index for LedgerWindow { +// type Output = io::Result; +// +// fn index(&mut self, index: u64) -> &io::Result { +// match u64_at(&mut self.index, index * SIZEOF_U64) { +// Ok(offset) => &entry_at(&mut self.data, offset), +// Err(e) => &Err(e), +// } +// } +//} + +#[derive(Debug)] +pub struct LedgerWriter { + index: BufWriter, + data: BufWriter, +} + +impl LedgerWriter { + // recover and open the ledger for writing + pub fn recover(ledger_path: &str) -> io::Result { + recover_ledger(ledger_path)?; + LedgerWriter::open(ledger_path, false) + } + + // opens or creates a LedgerWriter in ledger_path directory + pub fn open(ledger_path: &str, create: bool) -> io::Result { + let ledger_path = Path::new(&ledger_path); + + if create { + let _ignored = remove_dir_all(ledger_path); + create_dir_all(ledger_path)?; + } + + let index = OpenOptions::new() + .create(create) + .append(true) + .open(ledger_path.join(LEDGER_INDEX_FILE))?; + + if log_enabled!(Trace) { + let len = index.metadata()?.len(); + trace!("LedgerWriter::new: index fp:{}", len); + } + let index = BufWriter::new(index); + + let data = OpenOptions::new() + .create(create) + .append(true) + .open(ledger_path.join(LEDGER_DATA_FILE))?; + + if log_enabled!(Trace) { + let len = data.metadata()?.len(); + trace!("LedgerWriter::new: data fp:{}", len); + } + let data = BufWriter::new(data); + + Ok(LedgerWriter { index, data }) + } + + fn write_entry_noflush(&mut self, entry: &Entry) -> io::Result<()> { + let len = serialized_size(&entry).map_err(err_bincode_to_io)?; + + serialize_into(&mut self.data, &len).map_err(err_bincode_to_io)?; + if log_enabled!(Trace) { + let offset = self.data.seek(SeekFrom::Current(0))?; + trace!("write_entry: after len data fp:{}", offset); + } + + serialize_into(&mut self.data, &entry).map_err(err_bincode_to_io)?; + if log_enabled!(Trace) { + let offset = self.data.seek(SeekFrom::Current(0))?; + trace!("write_entry: after entry data fp:{}", offset); + } + + let offset = self.data.seek(SeekFrom::Current(0))? - len - SIZEOF_U64; + trace!("write_entry: offset:{} len:{}", offset, len); + + serialize_into(&mut self.index, &offset).map_err(err_bincode_to_io)?; + + if log_enabled!(Trace) { + let offset = self.index.seek(SeekFrom::Current(0))?; + trace!("write_entry: end index fp:{}", offset); + } + Ok(()) + } + + pub fn write_entry(&mut self, entry: &Entry) -> io::Result<()> { + self.write_entry_noflush(&entry)?; + self.index.flush()?; + self.data.flush()?; + Ok(()) + } + + pub fn write_entries<'a, I>(&mut self, entries: I) -> io::Result<()> + where + I: IntoIterator, + { + for entry in entries { + self.write_entry_noflush(&entry)?; + } + self.index.flush()?; + self.data.flush()?; + Ok(()) + } +} + +#[derive(Debug)] +pub struct LedgerReader { + data: BufReader, +} + +impl Iterator for LedgerReader { + type Item = io::Result; + + fn next(&mut self) -> Option> { + match next_entry(&mut self.data) { + Ok(entry) => Some(Ok(entry)), + Err(_) => None, + } + } +} + +/// Return an iterator for all the entries in the given file. +pub fn read_ledger( + ledger_path: &str, + recover: bool, +) -> io::Result>> { + if recover { + recover_ledger(ledger_path)?; + } + + let ledger_path = Path::new(&ledger_path); + let data = File::open(ledger_path.join(LEDGER_DATA_FILE))?; + let data = BufReader::new(data); + + Ok(LedgerReader { data }) +} + +// a Block is a slice of Entries +pub trait Block { + /// Verifies the hashes and counts of a slice of transactions are all consistent. + fn verify(&self, start_hash: &Hash) -> bool; + fn to_blobs(&self) -> Vec; + fn to_blobs_with_id(&self, id: Pubkey, start_id: u64, addr: &SocketAddr) -> Vec; + fn votes(&self) -> Vec<(Pubkey, Vote, Hash)>; +} + +impl Block for [Entry] { + fn verify(&self, start_hash: &Hash) -> bool { + let genesis = [Entry::new_tick(start_hash, 0, start_hash)]; + let entry_pairs = genesis.par_iter().chain(self).zip(self); + entry_pairs.all(|(x0, x1)| { + let r = x1.verify(&x0.id); + if !r { + warn!( + "entry invalid!: x0: {:?}, x1: {:?} num txs: {}", + x0.id, + x1.id, + x1.transactions.len() + ); + } + r + }) + } + + fn to_blobs_with_id(&self, id: Pubkey, start_idx: u64, addr: &SocketAddr) -> Vec { + self.iter() + .enumerate() + .map(|(i, entry)| entry.to_blob(Some(start_idx + i as u64), Some(id), Some(&addr))) + .collect() + } + + fn to_blobs(&self) -> Vec { + let default_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0); + self.to_blobs_with_id(Pubkey::default(), 0, &default_addr) + } + + fn votes(&self) -> Vec<(Pubkey, Vote, Hash)> { + self.iter() + .flat_map(|entry| { + entry + .transactions + .iter() + .flat_map(VoteTransaction::get_votes) + }).collect() + } +} + +/// Creates the next entries for given transactions, outputs +/// updates start_hash to id of last Entry, sets num_hashes to 0 +pub fn next_entries_mut( + start_hash: &mut Hash, + num_hashes: &mut u64, + transactions: Vec, +) -> Vec { + // TODO: ?? find a number that works better than |? + // V + if transactions.is_empty() || transactions.len() == 1 { + vec![Entry::new_mut(start_hash, num_hashes, transactions)] + } else { + let mut chunk_start = 0; + let mut entries = Vec::new(); + + while chunk_start < transactions.len() { + let mut chunk_end = transactions.len(); + let mut upper = chunk_end; + let mut lower = chunk_start; + let mut next = chunk_end; // be optimistic that all will fit + + // binary search for how many transactions will fit in an Entry (i.e. a BLOB) + loop { + debug!( + "chunk_end {}, upper {} lower {} next {} transactions.len() {}", + chunk_end, + upper, + lower, + next, + transactions.len() + ); + if Entry::serialized_size(&transactions[chunk_start..chunk_end]) + <= BLOB_DATA_SIZE as u64 + { + next = (upper + chunk_end) / 2; + lower = chunk_end; + debug!( + "chunk_end {} fits, maybe too well? trying {}", + chunk_end, next + ); + } else { + next = (lower + chunk_end) / 2; + upper = chunk_end; + debug!("chunk_end {} doesn't fit! trying {}", chunk_end, next); + } + // same as last time + if next == chunk_end { + debug!("converged on chunk_end {}", chunk_end); + break; + } + chunk_end = next; + } + entries.push(Entry::new_mut( + start_hash, + num_hashes, + transactions[chunk_start..chunk_end].to_vec(), + )); + chunk_start = chunk_end; + } + + entries + } +} + +/// Creates the next Entries for given transactions +pub fn next_entries( + start_hash: &Hash, + num_hashes: u64, + transactions: Vec, +) -> Vec { + let mut id = *start_hash; + let mut num_hashes = num_hashes; + next_entries_mut(&mut id, &mut num_hashes, transactions) +} + +pub fn get_tmp_ledger_path(name: &str) -> String { + use std::env; + let out_dir = env::var("OUT_DIR").unwrap_or_else(|_| "target".to_string()); + let keypair = Keypair::new(); + + let path = format!("{}/tmp/ledger-{}-{}", out_dir, name, keypair.pubkey()); + + // whack any possible collision + let _ignored = remove_dir_all(&path); + + path +} + +pub fn create_tmp_ledger_with_mint(name: &str, mint: &Mint) -> String { + let path = get_tmp_ledger_path(name); + + let mut writer = LedgerWriter::open(&path, true).unwrap(); + writer.write_entries(&mint.create_entries()).unwrap(); + + path +} + +pub fn create_tmp_genesis( + name: &str, + num: u64, + bootstrap_leader_id: Pubkey, + bootstrap_leader_tokens: u64, +) -> (Mint, String) { + let mint = Mint::new_with_leader(num, bootstrap_leader_id, bootstrap_leader_tokens); + let path = create_tmp_ledger_with_mint(name, &mint); + + (mint, path) +} + +pub fn create_ticks(num_ticks: usize, mut hash: Hash) -> Vec { + let mut ticks = Vec::with_capacity(num_ticks as usize); + for _ in 0..num_ticks { + let new_tick = Entry::new(&hash, 1, vec![]); + hash = new_tick.id; + ticks.push(new_tick); + } + + ticks +} + +pub fn create_tmp_sample_ledger( + name: &str, + num_tokens: u64, + num_ending_ticks: usize, + bootstrap_leader_id: Pubkey, + bootstrap_leader_tokens: u64, +) -> (Mint, String, Vec) { + let mint = Mint::new_with_leader(num_tokens, bootstrap_leader_id, bootstrap_leader_tokens); + let path = get_tmp_ledger_path(name); + + // Create the entries + let mut genesis = mint.create_entries(); + let ticks = create_ticks(num_ending_ticks, mint.last_id()); + genesis.extend(ticks); + + let mut writer = LedgerWriter::open(&path, true).unwrap(); + writer.write_entries(&genesis.clone()).unwrap(); + + (mint, path, genesis) +} + +#[cfg(test)] +pub fn make_tiny_test_entries(num: usize) -> Vec { + let zero = Hash::default(); + let one = hash(&zero.as_ref()); + let keypair = Keypair::new(); + + let mut id = one; + let mut num_hashes = 0; + (0..num) + .map(|_| { + Entry::new_mut( + &mut id, + &mut num_hashes, + vec![Transaction::budget_new_timestamp( + &keypair, + keypair.pubkey(), + keypair.pubkey(), + Utc::now(), + one, + )], + ) + }).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use bincode::{deserialize, serialized_size}; + use budget_transaction::BudgetTransaction; + use entry::{next_entry, reconstruct_entries_from_blobs, Entry}; + use packet::{to_blobs, BLOB_DATA_SIZE, PACKET_DATA_SIZE}; + use signature::{Keypair, KeypairUtil}; + use solana_sdk::hash::hash; + use std; + use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use transaction::Transaction; + use vote_program::Vote; + + #[test] + fn test_verify_slice() { + use logger; + logger::setup(); + let zero = Hash::default(); + let one = hash(&zero.as_ref()); + assert!(vec![][..].verify(&zero)); // base case + assert!(vec![Entry::new_tick(&zero, 0, &zero)][..].verify(&zero)); // singleton case 1 + assert!(!vec![Entry::new_tick(&zero, 0, &zero)][..].verify(&one)); // singleton case 2, bad + assert!(vec![next_entry(&zero, 0, vec![]); 2][..].verify(&zero)); // inductive step + + let mut bad_ticks = vec![next_entry(&zero, 0, vec![]); 2]; + bad_ticks[1].id = one; + assert!(!bad_ticks.verify(&zero)); // inductive step, bad + } + + fn make_test_entries() -> Vec { + let zero = Hash::default(); + let one = hash(&zero.as_ref()); + let keypair = Keypair::new(); + let vote_account = Keypair::new(); + let tx0 = Transaction::vote_new(&vote_account, Vote { tick_height: 1 }, one, 1); + let tx1 = Transaction::budget_new_timestamp( + &keypair, + keypair.pubkey(), + keypair.pubkey(), + Utc::now(), + one, + ); + // + // TODO: this magic number and the mix of transaction types + // is designed to fill up a Blob more or less exactly, + // to get near enough the the threshold that + // deserialization falls over if it uses the wrong size() + // parameter to index into blob.data() + // + // magic numbers -----------------+ + // | + // V + let mut transactions = vec![tx0; 362]; + transactions.extend(vec![tx1; 100]); + next_entries(&zero, 0, transactions) + } + + #[test] + fn test_entries_to_blobs() { + use logger; + logger::setup(); + let entries = make_test_entries(); + + let blob_q = entries.to_blobs(); + + assert_eq!(reconstruct_entries_from_blobs(blob_q).unwrap().0, entries); + } + + #[test] + fn test_bad_blobs_attack() { + use logger; + logger::setup(); + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8000); + let blobs_q = to_blobs(vec![(0, addr)]).unwrap(); // <-- attack! + assert!(reconstruct_entries_from_blobs(blobs_q).is_err()); + } + + #[test] + fn test_next_entries() { + use logger; + logger::setup(); + let id = Hash::default(); + let next_id = hash(&id.as_ref()); + let keypair = Keypair::new(); + let vote_account = Keypair::new(); + let tx_small = Transaction::vote_new(&vote_account, Vote { tick_height: 1 }, next_id, 2); + let tx_large = Transaction::budget_new(&keypair, keypair.pubkey(), 1, next_id); + + let tx_small_size = serialized_size(&tx_small).unwrap() as usize; + let tx_large_size = serialized_size(&tx_large).unwrap() as usize; + let entry_size = serialized_size(&Entry { + prev_id: Hash::default(), + num_hashes: 0, + id: Hash::default(), + transactions: vec![], + }).unwrap() as usize; + assert!(tx_small_size < tx_large_size); + assert!(tx_large_size < PACKET_DATA_SIZE); + + let threshold = (BLOB_DATA_SIZE - entry_size) / tx_small_size; + + // verify no split + let transactions = vec![tx_small.clone(); threshold]; + let entries0 = next_entries(&id, 0, transactions.clone()); + assert_eq!(entries0.len(), 1); + assert!(entries0.verify(&id)); + + // verify the split with uniform transactions + let transactions = vec![tx_small.clone(); threshold * 2]; + let entries0 = next_entries(&id, 0, transactions.clone()); + assert_eq!(entries0.len(), 2); + assert!(entries0.verify(&id)); + + // verify the split with small transactions followed by large + // transactions + let mut transactions = vec![tx_small.clone(); BLOB_DATA_SIZE / tx_small_size]; + let large_transactions = vec![tx_large.clone(); BLOB_DATA_SIZE / tx_large_size]; + + transactions.extend(large_transactions); + + let entries0 = next_entries(&id, 0, transactions.clone()); + assert!(entries0.len() >= 2); + assert!(entries0.verify(&id)); + } + + #[test] + fn test_ledger_reader_writer() { + use logger; + logger::setup(); + let ledger_path = get_tmp_ledger_path("test_ledger_reader_writer"); + let entries = make_tiny_test_entries(10); + + { + let mut writer = LedgerWriter::open(&ledger_path, true).unwrap(); + writer.write_entries(&entries.clone()).unwrap(); + // drops writer, flushes buffers + } + verify_ledger(&ledger_path).unwrap(); + + let mut read_entries = vec![]; + for x in read_ledger(&ledger_path, true).unwrap() { + let entry = x.unwrap(); + trace!("entry... {:?}", entry); + read_entries.push(entry); + } + assert_eq!(read_entries, entries); + + let mut window = LedgerWindow::open(&ledger_path).unwrap(); + + for (i, entry) in entries.iter().enumerate() { + let read_entry = window.get_entry(i as u64).unwrap(); + assert_eq!(*entry, read_entry); + } + assert!(window.get_entry(100).is_err()); + + std::fs::remove_file(Path::new(&ledger_path).join(LEDGER_DATA_FILE)).unwrap(); + // empty data file should fall over + assert!(LedgerWindow::open(&ledger_path).is_err()); + assert!(read_ledger(&ledger_path, false).is_err()); + + std::fs::remove_dir_all(ledger_path).unwrap(); + } + + fn truncated_last_entry(ledger_path: &str, entries: Vec) { + let len = { + let mut writer = LedgerWriter::open(&ledger_path, true).unwrap(); + writer.write_entries(&entries).unwrap(); + writer.data.seek(SeekFrom::Current(0)).unwrap() + }; + verify_ledger(&ledger_path).unwrap(); + + let data = OpenOptions::new() + .write(true) + .open(Path::new(&ledger_path).join(LEDGER_DATA_FILE)) + .unwrap(); + data.set_len(len - 4).unwrap(); + } + + fn garbage_on_data(ledger_path: &str, entries: Vec) { + let mut writer = LedgerWriter::open(&ledger_path, true).unwrap(); + writer.write_entries(&entries).unwrap(); + writer.data.write_all(b"hi there!").unwrap(); + } + + fn read_ledger_check(ledger_path: &str, entries: Vec, len: usize) { + let read_entries = read_ledger(&ledger_path, true).unwrap(); + let mut i = 0; + + for entry in read_entries { + assert_eq!(entry.unwrap(), entries[i]); + i += 1; + } + assert_eq!(i, len); + } + + fn ledger_window_check(ledger_path: &str, entries: Vec, len: usize) { + let mut window = LedgerWindow::open(&ledger_path).unwrap(); + for i in 0..len { + let entry = window.get_entry(i as u64); + assert_eq!(entry.unwrap(), entries[i]); + } + } + + #[test] + fn test_recover_ledger() { + use logger; + logger::setup(); + + let entries = make_tiny_test_entries(10); + let ledger_path = get_tmp_ledger_path("test_recover_ledger"); + + // truncate data file, tests recover inside read_ledger_check() + truncated_last_entry(&ledger_path, entries.clone()); + read_ledger_check(&ledger_path, entries.clone(), entries.len() - 1); + + // truncate data file, tests recover inside LedgerWindow::new() + truncated_last_entry(&ledger_path, entries.clone()); + ledger_window_check(&ledger_path, entries.clone(), entries.len() - 1); + + // restore last entry, tests recover_ledger() inside LedgerWriter::new() + truncated_last_entry(&ledger_path, entries.clone()); + // verify should fail at first + assert!(verify_ledger(&ledger_path).is_err()); + { + let mut writer = LedgerWriter::recover(&ledger_path).unwrap(); + writer.write_entry(&entries[entries.len() - 1]).unwrap(); + } + // and be fine after recover() + verify_ledger(&ledger_path).unwrap(); + + read_ledger_check(&ledger_path, entries.clone(), entries.len()); + ledger_window_check(&ledger_path, entries.clone(), entries.len()); + + // make it look like data is newer in time, check reader... + garbage_on_data(&ledger_path, entries.clone()); + read_ledger_check(&ledger_path, entries.clone(), entries.len()); + + // make it look like data is newer in time, check window... + garbage_on_data(&ledger_path, entries.clone()); + ledger_window_check(&ledger_path, entries.clone(), entries.len()); + + // make it look like data is newer in time, check writer... + garbage_on_data(&ledger_path, entries[..entries.len() - 1].to_vec()); + assert!(verify_ledger(&ledger_path).is_err()); + { + let mut writer = LedgerWriter::recover(&ledger_path).unwrap(); + writer.write_entry(&entries[entries.len() - 1]).unwrap(); + } + verify_ledger(&ledger_path).unwrap(); + read_ledger_check(&ledger_path, entries.clone(), entries.len()); + ledger_window_check(&ledger_path, entries.clone(), entries.len()); + let _ignored = remove_dir_all(&ledger_path); + } + + #[test] + fn test_verify_ledger() { + use logger; + logger::setup(); + + let entries = make_tiny_test_entries(10); + let ledger_path = get_tmp_ledger_path("test_verify_ledger"); + { + let mut writer = LedgerWriter::open(&ledger_path, true).unwrap(); + writer.write_entries(&entries).unwrap(); + } + // TODO more cases that make ledger_verify() fail + // assert!(verify_ledger(&ledger_path).is_err()); + + assert!(verify_ledger(&ledger_path).is_ok()); + let _ignored = remove_dir_all(&ledger_path); + } + + #[test] + fn test_get_entries_bytes() { + use logger; + logger::setup(); + let entries = make_tiny_test_entries(10); + let ledger_path = get_tmp_ledger_path("test_raw_entries"); + { + let mut writer = LedgerWriter::open(&ledger_path, true).unwrap(); + writer.write_entries(&entries).unwrap(); + } + + let mut window = LedgerWindow::open(&ledger_path).unwrap(); + let mut buf = [0; 1024]; + let (num_entries, bytes) = window.get_entries_bytes(0, 1, &mut buf).unwrap(); + let bytes = bytes as usize; + assert_eq!(num_entries, 1); + let entry: Entry = deserialize(&buf[size_of::()..bytes]).unwrap(); + assert_eq!(entry, entries[0]); + + let (num_entries, bytes2) = window.get_entries_bytes(0, 2, &mut buf).unwrap(); + let bytes2 = bytes2 as usize; + assert_eq!(num_entries, 2); + assert!(bytes2 > bytes); + for (i, ref entry) in entries.iter().enumerate() { + info!("entry[{}] = {:?}", i, entry.id); + } + + let entry: Entry = deserialize(&buf[size_of::()..bytes]).unwrap(); + assert_eq!(entry, entries[0]); + + let entry: Entry = deserialize(&buf[bytes + size_of::()..bytes2]).unwrap(); + assert_eq!(entry, entries[1]); + + // buf size part-way into entry[1], should just return entry[0] + let mut buf = vec![0; bytes + size_of::() + 1]; + let (num_entries, bytes3) = window.get_entries_bytes(0, 2, &mut buf).unwrap(); + assert_eq!(num_entries, 1); + let bytes3 = bytes3 as usize; + assert_eq!(bytes3, bytes); + + let mut buf = vec![0; bytes2 - 1]; + let (num_entries, bytes4) = window.get_entries_bytes(0, 2, &mut buf).unwrap(); + assert_eq!(num_entries, 1); + let bytes4 = bytes4 as usize; + assert_eq!(bytes4, bytes); + + let mut buf = vec![0; bytes + size_of::() - 1]; + let (num_entries, bytes5) = window.get_entries_bytes(0, 2, &mut buf).unwrap(); + assert_eq!(num_entries, 1); + let bytes5 = bytes5 as usize; + assert_eq!(bytes5, bytes); + + let mut buf = vec![0; bytes * 2]; + let (num_entries, bytes6) = window.get_entries_bytes(9, 1, &mut buf).unwrap(); + assert_eq!(num_entries, 1); + let bytes6 = bytes6 as usize; + + let entry: Entry = deserialize(&buf[size_of::()..bytes6]).unwrap(); + assert_eq!(entry, entries[9]); + + // Read out of range + assert!(window.get_entries_bytes(20, 2, &mut buf).is_err()); + + let _ignored = remove_dir_all(&ledger_path); + } +} diff --git a/book/ledger_write_stage.rs b/book/ledger_write_stage.rs new file mode 100644 index 00000000000000..e41f75f8a66b80 --- /dev/null +++ b/book/ledger_write_stage.rs @@ -0,0 +1,92 @@ +//! The `ledger_write_stage` module implements the ledger write stage. It +//! writes entries to the given writer, which is typically a file + +use counter::Counter; +use entry::{EntryReceiver, EntrySender}; +use ledger::LedgerWriter; +use log::Level; +use result::{Error, Result}; +use service::Service; +use solana_sdk::timing::duration_as_ms; +use std::sync::atomic::AtomicUsize; +use std::sync::mpsc::{channel, RecvTimeoutError}; +use std::thread::{self, Builder, JoinHandle}; +use std::time::{Duration, Instant}; + +pub struct LedgerWriteStage { + write_thread: JoinHandle<()>, +} + +impl LedgerWriteStage { + pub fn write( + ledger_writer: Option<&mut LedgerWriter>, + entry_receiver: &EntryReceiver, + entry_sender: &EntrySender, + ) -> Result<()> { + let mut ventries = Vec::new(); + let mut received_entries = entry_receiver.recv_timeout(Duration::new(1, 0))?; + let mut num_new_entries = 0; + let now = Instant::now(); + + loop { + num_new_entries += received_entries.len(); + ventries.push(received_entries); + + if let Ok(n) = entry_receiver.try_recv() { + received_entries = n; + } else { + break; + } + } + + if let Some(ledger_writer) = ledger_writer { + ledger_writer.write_entries(ventries.iter().flatten())?; + } + + inc_new_counter_info!("ledger_writer_stage-entries_received", num_new_entries); + for entries in ventries { + entry_sender.send(entries)?; + } + inc_new_counter_info!( + "ledger_writer_stage-time_ms", + duration_as_ms(&now.elapsed()) as usize + ); + Ok(()) + } + + pub fn new(ledger_path: Option<&str>, entry_receiver: EntryReceiver) -> (Self, EntryReceiver) { + let mut ledger_writer = ledger_path.map(|p| LedgerWriter::open(p, false).unwrap()); + + let (entry_sender, entry_forwarder) = channel(); + let write_thread = Builder::new() + .name("solana-ledger-writer".to_string()) + .spawn(move || loop { + if let Err(e) = Self::write(ledger_writer.as_mut(), &entry_receiver, &entry_sender) + { + match e { + Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => { + break; + } + Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), + _ => { + inc_new_counter_info!( + "ledger_writer_stage-write_and_send_entries-error", + 1 + ); + error!("{:?}", e); + } + } + }; + }).unwrap(); + + (LedgerWriteStage { write_thread }, entry_forwarder) + } +} + +impl Service for LedgerWriteStage { + type JoinReturnType = (); + + fn join(self) -> thread::Result<()> { + self.write_thread.join() + } +} diff --git a/book/lib.rs b/book/lib.rs new file mode 100644 index 00000000000000..60ecef26a67aa7 --- /dev/null +++ b/book/lib.rs @@ -0,0 +1,143 @@ +//! The `solana` library implements the Solana high-performance blockchain architecture. +//! It includes a full Rust implementation of the architecture (see +//! [Fullnode](server/struct.Fullnode.html)) as well as hooks to GPU implementations of its most +//! paralellizable components (i.e. [SigVerify](sigverify/index.html)). It also includes +//! command-line tools to spin up fullnodes and a Rust library +//! (see [ThinClient](thin_client/struct.ThinClient.html)) to interact with them. +//! + +#![cfg_attr(feature = "unstable", feature(test))] +#[macro_use] +pub mod counter; +pub mod bank; +pub mod banking_stage; +pub mod blob_fetch_stage; +pub mod bloom; +pub mod bpf_loader; +pub mod broadcast_stage; +pub mod budget_expr; +pub mod budget_instruction; +pub mod budget_transaction; +#[cfg(feature = "chacha")] +pub mod chacha; +#[cfg(all(feature = "chacha", feature = "cuda"))] +pub mod chacha_cuda; +pub mod client; +pub mod crds; +pub mod crds_gossip; +pub mod crds_gossip_error; +pub mod crds_gossip_pull; +pub mod crds_gossip_push; +pub mod crds_traits_impls; +pub mod crds_value; +#[macro_use] +pub mod contact_info; +pub mod budget_program; +pub mod cluster_info; +pub mod compute_leader_finality_service; +pub mod db_ledger; +pub mod db_window; +pub mod entry; +#[cfg(feature = "erasure")] +pub mod erasure; +pub mod fetch_stage; +pub mod fullnode; +pub mod leader_scheduler; +pub mod ledger; +pub mod ledger_write_stage; +pub mod loader_transaction; +pub mod logger; +pub mod mint; +pub mod native_loader; +pub mod ncp; +pub mod netutil; +pub mod packet; +pub mod payment_plan; +pub mod poh; +pub mod poh_recorder; +pub mod poh_service; +pub mod recvmmsg; +pub mod replicate_stage; +pub mod replicator; +pub mod result; +pub mod retransmit_stage; +pub mod rpc; +pub mod rpc_pubsub; +pub mod rpc_request; +pub mod service; +pub mod signature; +pub mod sigverify; +pub mod sigverify_stage; +pub mod storage_program; +pub mod storage_stage; +pub mod storage_transaction; +pub mod store_ledger_stage; +pub mod streamer; +pub mod system_program; +pub mod system_transaction; +pub mod thin_client; +pub mod token_program; +pub mod tpu; +pub mod tpu_forwarder; +pub mod transaction; +pub mod tvu; +pub mod vote_program; +pub mod vote_stage; +pub mod vote_transaction; +pub mod wallet; +pub mod window; +pub mod window_service; +extern crate bincode; +extern crate bs58; +extern crate bv; +extern crate byteorder; +extern crate bytes; +extern crate chrono; +extern crate clap; +extern crate dirs; +extern crate elf; +extern crate generic_array; +#[cfg(test)] +#[cfg(any(feature = "chacha", feature = "cuda"))] +#[macro_use] +extern crate hex_literal; +extern crate indexmap; +extern crate ipnetwork; +extern crate itertools; +extern crate libc; +extern crate libloading; +#[macro_use] +extern crate log; +extern crate nix; +extern crate pnet_datalink; +extern crate rayon; +extern crate reqwest; +extern crate ring; +extern crate rocksdb; +extern crate serde; +#[macro_use] +extern crate serde_derive; +#[macro_use] +extern crate serde_json; +extern crate serde_cbor; +extern crate sha2; +extern crate socket2; +extern crate solana_drone; +extern crate solana_jsonrpc_core as jsonrpc_core; +extern crate solana_jsonrpc_http_server as jsonrpc_http_server; +#[macro_use] +extern crate solana_jsonrpc_macros as jsonrpc_macros; +extern crate solana_jsonrpc_pubsub as jsonrpc_pubsub; +extern crate solana_jsonrpc_ws_server as jsonrpc_ws_server; +extern crate solana_metrics; +extern crate solana_sdk; +extern crate sys_info; +extern crate tokio; +extern crate tokio_codec; +extern crate untrusted; + +#[cfg(test)] +#[macro_use] +extern crate matches; + +extern crate rand; diff --git a/book/loader_transaction.rs b/book/loader_transaction.rs new file mode 100644 index 00000000000000..10bc3eecb9e4b6 --- /dev/null +++ b/book/loader_transaction.rs @@ -0,0 +1,49 @@ +//! The `dynamic_transaction` module provides functionality for loading and calling a program + +use signature::{Keypair, KeypairUtil}; +use solana_sdk::hash::Hash; +use solana_sdk::loader_instruction::LoaderInstruction; +use solana_sdk::pubkey::Pubkey; +use transaction::Transaction; + +pub trait LoaderTransaction { + fn loader_write( + from_keypair: &Keypair, + loader: Pubkey, + offset: u32, + bytes: Vec, + last_id: Hash, + fee: u64, + ) -> Self; + + fn loader_finalize(from_keypair: &Keypair, loader: Pubkey, last_id: Hash, fee: u64) -> Self; +} + +impl LoaderTransaction for Transaction { + fn loader_write( + from_keypair: &Keypair, + loader: Pubkey, + offset: u32, + bytes: Vec, + last_id: Hash, + fee: u64, + ) -> Self { + trace!( + "LoaderTransaction::Write() program {:?} offset {} length {}", + from_keypair.pubkey(), + offset, + bytes.len() + ); + let instruction = LoaderInstruction::Write { offset, bytes }; + Transaction::new(from_keypair, &[], loader, &instruction, last_id, fee) + } + + fn loader_finalize(from_keypair: &Keypair, loader: Pubkey, last_id: Hash, fee: u64) -> Self { + trace!( + "LoaderTransaction::Finalize() program {:?}", + from_keypair.pubkey(), + ); + let instruction = LoaderInstruction::Finalize; + Transaction::new(from_keypair, &[], loader, &instruction, last_id, fee) + } +} diff --git a/book/logger.rs b/book/logger.rs new file mode 100644 index 00000000000000..08375adf57e32a --- /dev/null +++ b/book/logger.rs @@ -0,0 +1,16 @@ +//! The `logger` module provides a setup function for `env_logger`. Its only function, +//! `setup()` may be called multiple times. + +use std::sync::{Once, ONCE_INIT}; +extern crate env_logger; + +static INIT: Once = ONCE_INIT; + +/// Setup function that is only run once, even if called multiple times. +pub fn setup() { + INIT.call_once(|| { + env_logger::Builder::from_default_env() + .default_format_timestamp_nanos(true) + .init(); + }); +} diff --git a/book/mark.min.js b/book/mark.min.js new file mode 100644 index 00000000000000..16362318834726 --- /dev/null +++ b/book/mark.min.js @@ -0,0 +1,7 @@ +/*!*************************************************** +* mark.js v8.11.1 +* https://markjs.io/ +* Copyright (c) 2014–2018, Julian Kühnel +* Released under the MIT license https://git.io/vwTVl +*****************************************************/ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Mark=t()}(this,function(){"use strict";var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},n=function(){function e(e,t){for(var n=0;n1&&void 0!==arguments[1])||arguments[1],i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:5e3;t(this,e),this.ctx=n,this.iframes=r,this.exclude=i,this.iframesTimeout=o}return n(e,[{key:"getContexts",value:function(){var e=[];return(void 0!==this.ctx&&this.ctx?NodeList.prototype.isPrototypeOf(this.ctx)?Array.prototype.slice.call(this.ctx):Array.isArray(this.ctx)?this.ctx:"string"==typeof this.ctx?Array.prototype.slice.call(document.querySelectorAll(this.ctx)):[this.ctx]:[]).forEach(function(t){var n=e.filter(function(e){return e.contains(t)}).length>0;-1!==e.indexOf(t)||n||e.push(t)}),e}},{key:"getIframeContents",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){},r=void 0;try{var i=e.contentWindow;if(r=i.document,!i||!r)throw new Error("iframe inaccessible")}catch(e){n()}r&&t(r)}},{key:"isIframeBlank",value:function(e){var t="about:blank",n=e.getAttribute("src").trim();return e.contentWindow.location.href===t&&n!==t&&n}},{key:"observeIframeLoad",value:function(e,t,n){var r=this,i=!1,o=null,a=function a(){if(!i){i=!0,clearTimeout(o);try{r.isIframeBlank(e)||(e.removeEventListener("load",a),r.getIframeContents(e,t,n))}catch(e){n()}}};e.addEventListener("load",a),o=setTimeout(a,this.iframesTimeout)}},{key:"onIframeReady",value:function(e,t,n){try{"complete"===e.contentWindow.document.readyState?this.isIframeBlank(e)?this.observeIframeLoad(e,t,n):this.getIframeContents(e,t,n):this.observeIframeLoad(e,t,n)}catch(e){n()}}},{key:"waitForIframes",value:function(e,t){var n=this,r=0;this.forEachIframe(e,function(){return!0},function(e){r++,n.waitForIframes(e.querySelector("html"),function(){--r||t()})},function(e){e||t()})}},{key:"forEachIframe",value:function(t,n,r){var i=this,o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},a=t.querySelectorAll("iframe"),s=a.length,c=0;a=Array.prototype.slice.call(a);var u=function(){--s<=0&&o(c)};s||u(),a.forEach(function(t){e.matches(t,i.exclude)?u():i.onIframeReady(t,function(e){n(t)&&(c++,r(e)),u()},u)})}},{key:"createIterator",value:function(e,t,n){return document.createNodeIterator(e,t,n,!1)}},{key:"createInstanceOnIframe",value:function(t){return new e(t.querySelector("html"),this.iframes)}},{key:"compareNodeIframe",value:function(e,t,n){if(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_PRECEDING){if(null===t)return!0;if(t.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)return!0}return!1}},{key:"getIteratorNode",value:function(e){var t=e.previousNode();return{prevNode:t,node:null===t?e.nextNode():e.nextNode()&&e.nextNode()}}},{key:"checkIframeFilter",value:function(e,t,n,r){var i=!1,o=!1;return r.forEach(function(e,t){e.val===n&&(i=t,o=e.handled)}),this.compareNodeIframe(e,t,n)?(!1!==i||o?!1===i||o||(r[i].handled=!0):r.push({val:n,handled:!0}),!0):(!1===i&&r.push({val:n,handled:!1}),!1)}},{key:"handleOpenIframes",value:function(e,t,n,r){var i=this;e.forEach(function(e){e.handled||i.getIframeContents(e.val,function(e){i.createInstanceOnIframe(e).forEachNode(t,n,r)})})}},{key:"iterateThroughNodes",value:function(e,t,n,r,i){for(var o,a=this,s=this.createIterator(t,e,r),c=[],u=[],l=void 0,h=void 0;void 0,o=a.getIteratorNode(s),h=o.prevNode,l=o.node;)this.iframes&&this.forEachIframe(t,function(e){return a.checkIframeFilter(l,h,e,c)},function(t){a.createInstanceOnIframe(t).forEachNode(e,function(e){return u.push(e)},r)}),u.push(l);u.forEach(function(e){n(e)}),this.iframes&&this.handleOpenIframes(c,e,n,r),i()}},{key:"forEachNode",value:function(e,t,n){var r=this,i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},o=this.getContexts(),a=o.length;a||i(),o.forEach(function(o){var s=function(){r.iterateThroughNodes(e,o,t,n,function(){--a<=0&&i()})};r.iframes?r.waitForIframes(o,s):s()})}}],[{key:"matches",value:function(e,t){var n="string"==typeof t?[t]:t,r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector;if(r){var i=!1;return n.every(function(t){return!r.call(e,t)||(i=!0,!1)}),i}return!1}}]),e}(),o=function(){function e(n){t(this,e),this.opt=r({},{diacritics:!0,synonyms:{},accuracy:"partially",caseSensitive:!1,ignoreJoiners:!1,ignorePunctuation:[],wildcards:"disabled"},n)}return n(e,[{key:"create",value:function(e){return"disabled"!==this.opt.wildcards&&(e=this.setupWildcardsRegExp(e)),e=this.escapeStr(e),Object.keys(this.opt.synonyms).length&&(e=this.createSynonymsRegExp(e)),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),this.opt.diacritics&&(e=this.createDiacriticsRegExp(e)),e=this.createMergedBlanksRegExp(e),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.createJoinersRegExp(e)),"disabled"!==this.opt.wildcards&&(e=this.createWildcardsRegExp(e)),e=this.createAccuracyRegExp(e),new RegExp(e,"gm"+(this.opt.caseSensitive?"":"i"))}},{key:"escapeStr",value:function(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}},{key:"createSynonymsRegExp",value:function(e){var t=this.opt.synonyms,n=this.opt.caseSensitive?"":"i",r=this.opt.ignoreJoiners||this.opt.ignorePunctuation.length?"\0":"";for(var i in t)if(t.hasOwnProperty(i)){var o=t[i],a="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(i):this.escapeStr(i),s="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(o):this.escapeStr(o);""!==a&&""!==s&&(e=e.replace(new RegExp("("+this.escapeStr(a)+"|"+this.escapeStr(s)+")","gm"+n),r+"("+this.processSynonyms(a)+"|"+this.processSynonyms(s)+")"+r))}return e}},{key:"processSynonyms",value:function(e){return(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),e}},{key:"setupWildcardsRegExp",value:function(e){return(e=e.replace(/(?:\\)*\?/g,function(e){return"\\"===e.charAt(0)?"?":""})).replace(/(?:\\)*\*/g,function(e){return"\\"===e.charAt(0)?"*":""})}},{key:"createWildcardsRegExp",value:function(e){var t="withSpaces"===this.opt.wildcards;return e.replace(/\u0001/g,t?"[\\S\\s]?":"\\S?").replace(/\u0002/g,t?"[\\S\\s]*?":"\\S*")}},{key:"setupIgnoreJoinersRegExp",value:function(e){return e.replace(/[^(|)\\]/g,function(e,t,n){var r=n.charAt(t+1);return/[(|)\\]/.test(r)||""===r?e:e+"\0"})}},{key:"createJoinersRegExp",value:function(e){var t=[],n=this.opt.ignorePunctuation;return Array.isArray(n)&&n.length&&t.push(this.escapeStr(n.join(""))),this.opt.ignoreJoiners&&t.push("\\u00ad\\u200b\\u200c\\u200d"),t.length?e.split(/\u0000+/).join("["+t.join("")+"]*"):e}},{key:"createDiacriticsRegExp",value:function(e){var t=this.opt.caseSensitive?"":"i",n=this.opt.caseSensitive?["aàáảãạăằắẳẵặâầấẩẫậäåāą","AÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćč","CÇĆČ","dđď","DĐĎ","eèéẻẽẹêềếểễệëěēę","EÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïī","IÌÍỈĨỊÎÏĪ","lł","LŁ","nñňń","NÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøō","OÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rř","RŘ","sšśșş","SŠŚȘŞ","tťțţ","TŤȚŢ","uùúủũụưừứửữựûüůū","UÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿ","YÝỲỶỸỴŸ","zžżź","ZŽŻŹ"]:["aàáảãạăằắẳẵặâầấẩẫậäåāąAÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćčCÇĆČ","dđďDĐĎ","eèéẻẽẹêềếểễệëěēęEÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïīIÌÍỈĨỊÎÏĪ","lłLŁ","nñňńNÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøōOÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rřRŘ","sšśșşSŠŚȘŞ","tťțţTŤȚŢ","uùúủũụưừứửữựûüůūUÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿYÝỲỶỸỴŸ","zžżźZŽŻŹ"],r=[];return e.split("").forEach(function(i){n.every(function(n){if(-1!==n.indexOf(i)){if(r.indexOf(n)>-1)return!1;e=e.replace(new RegExp("["+n+"]","gm"+t),"["+n+"]"),r.push(n)}return!0})}),e}},{key:"createMergedBlanksRegExp",value:function(e){return e.replace(/[\s]+/gim,"[\\s]+")}},{key:"createAccuracyRegExp",value:function(e){var t=this,n=this.opt.accuracy,r="string"==typeof n?n:n.value,i="";switch(("string"==typeof n?[]:n.limiters).forEach(function(e){i+="|"+t.escapeStr(e)}),r){case"partially":default:return"()("+e+")";case"complementary":return"()([^"+(i="\\s"+(i||this.escapeStr("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~¡¿")))+"]*"+e+"[^"+i+"]*)";case"exactly":return"(^|\\s"+i+")("+e+")(?=$|\\s"+i+")"}}}]),e}(),a=function(){function a(e){t(this,a),this.ctx=e,this.ie=!1;var n=window.navigator.userAgent;(n.indexOf("MSIE")>-1||n.indexOf("Trident")>-1)&&(this.ie=!0)}return n(a,[{key:"log",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"debug",r=this.opt.log;this.opt.debug&&"object"===(void 0===r?"undefined":e(r))&&"function"==typeof r[n]&&r[n]("mark.js: "+t)}},{key:"getSeparatedKeywords",value:function(e){var t=this,n=[];return e.forEach(function(e){t.opt.separateWordSearch?e.split(" ").forEach(function(e){e.trim()&&-1===n.indexOf(e)&&n.push(e)}):e.trim()&&-1===n.indexOf(e)&&n.push(e)}),{keywords:n.sort(function(e,t){return t.length-e.length}),length:n.length}}},{key:"isNumeric",value:function(e){return Number(parseFloat(e))==e}},{key:"checkRanges",value:function(e){var t=this;if(!Array.isArray(e)||"[object Object]"!==Object.prototype.toString.call(e[0]))return this.log("markRanges() will only accept an array of objects"),this.opt.noMatch(e),[];var n=[],r=0;return e.sort(function(e,t){return e.start-t.start}).forEach(function(e){var i=t.callNoMatchOnInvalidRanges(e,r),o=i.start,a=i.end;i.valid&&(e.start=o,e.length=a-o,n.push(e),r=a)}),n}},{key:"callNoMatchOnInvalidRanges",value:function(e,t){var n=void 0,r=void 0,i=!1;return e&&void 0!==e.start?(r=(n=parseInt(e.start,10))+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&r-t>0&&r-n>0?i=!0:(this.log("Ignoring invalid or overlapping range: "+JSON.stringify(e)),this.opt.noMatch(e))):(this.log("Ignoring invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:n,end:r,valid:i}}},{key:"checkWhitespaceRanges",value:function(e,t,n){var r=void 0,i=!0,o=n.length,a=t-o,s=parseInt(e.start,10)-a;return(r=(s=s>o?o:s)+parseInt(e.length,10))>o&&(r=o,this.log("End range automatically set to the max value of "+o)),s<0||r-s<0||s>o||r>o?(i=!1,this.log("Invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)):""===n.substring(s,r).replace(/\s+/g,"")&&(i=!1,this.log("Skipping whitespace only range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:s,end:r,valid:i}}},{key:"getTextNodes",value:function(e){var t=this,n="",r=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,function(e){r.push({start:n.length,end:(n+=e.textContent).length,node:e})},function(e){return t.matchesExclude(e.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT},function(){e({value:n,nodes:r})})}},{key:"matchesExclude",value:function(e){return i.matches(e,this.opt.exclude.concat(["script","style","title","head","html"]))}},{key:"wrapRangeInTextNode",value:function(e,t,n){var r=this.opt.element?this.opt.element:"mark",i=e.splitText(t),o=i.splitText(n-t),a=document.createElement(r);return a.setAttribute("data-markjs","true"),this.opt.className&&a.setAttribute("class",this.opt.className),a.textContent=i.textContent,i.parentNode.replaceChild(a,i),o}},{key:"wrapRangeInMappedTextNode",value:function(e,t,n,r,i){var o=this;e.nodes.every(function(a,s){var c=e.nodes[s+1];if(void 0===c||c.start>t){if(!r(a.node))return!1;var u=t-a.start,l=(n>a.end?a.end:n)-a.start,h=e.value.substr(0,a.start),f=e.value.substr(l+a.start);if(a.node=o.wrapRangeInTextNode(a.node,u,l),e.value=h+f,e.nodes.forEach(function(t,n){n>=s&&(e.nodes[n].start>0&&n!==s&&(e.nodes[n].start-=l),e.nodes[n].end-=l)}),n-=l,i(a.node.previousSibling,a.start),!(n>a.end))return!1;t=a.end}return!0})}},{key:"wrapGroups",value:function(e,t,n,r){return r((e=this.wrapRangeInTextNode(e,t,t+n)).previousSibling),e}},{key:"separateGroups",value:function(e,t,n,r,i){for(var o=t.length,a=1;a-1&&r(t[a],e)&&(e=this.wrapGroups(e,s,t[a].length,i))}return e}},{key:"wrapMatches",value:function(e,t,n,r,i){var o=this,a=0===t?0:t+1;this.getTextNodes(function(t){t.nodes.forEach(function(t){t=t.node;for(var i=void 0;null!==(i=e.exec(t.textContent))&&""!==i[a];){if(o.opt.separateGroups)t=o.separateGroups(t,i,a,n,r);else{if(!n(i[a],t))continue;var s=i.index;if(0!==a)for(var c=1;c, + pubkey: Pubkey, + pub tokens: u64, + pub bootstrap_leader_id: Pubkey, + pub bootstrap_leader_tokens: u64, +} + +impl Mint { + pub fn new_with_pkcs8( + tokens: u64, + pkcs8: Vec, + bootstrap_leader_id: Pubkey, + bootstrap_leader_tokens: u64, + ) -> Self { + let keypair = + Keypair::from_pkcs8(Input::from(&pkcs8)).expect("from_pkcs8 in mint pub fn new"); + let pubkey = keypair.pubkey(); + Mint { + pkcs8, + pubkey, + tokens, + bootstrap_leader_id, + bootstrap_leader_tokens, + } + } + + pub fn new_with_leader( + tokens: u64, + bootstrap_leader: Pubkey, + bootstrap_leader_tokens: u64, + ) -> Self { + let rnd = SystemRandom::new(); + let pkcs8 = Keypair::generate_pkcs8(&rnd) + .expect("generate_pkcs8 in mint pub fn new") + .to_vec(); + Self::new_with_pkcs8(tokens, pkcs8, bootstrap_leader, bootstrap_leader_tokens) + } + + pub fn new(tokens: u64) -> Self { + let rnd = SystemRandom::new(); + let pkcs8 = Keypair::generate_pkcs8(&rnd) + .expect("generate_pkcs8 in mint pub fn new") + .to_vec(); + Self::new_with_pkcs8(tokens, pkcs8, Pubkey::default(), 0) + } + + pub fn seed(&self) -> Hash { + hash(&self.pkcs8) + } + + pub fn last_id(&self) -> Hash { + self.create_entries().last().unwrap().id + } + + pub fn keypair(&self) -> Keypair { + Keypair::from_pkcs8(Input::from(&self.pkcs8)).expect("from_pkcs8 in mint pub fn keypair") + } + + pub fn pubkey(&self) -> Pubkey { + self.pubkey + } + + pub fn create_transaction(&self) -> Vec { + let keypair = self.keypair(); + // Check if creating the leader genesis entries is necessary + if self.bootstrap_leader_id == Pubkey::default() { + let tx = Transaction::system_move(&keypair, self.pubkey(), self.tokens, self.seed(), 0); + vec![tx] + } else { + // Create moves from mint to itself (deposit), and then a move from the mint + // to the bootstrap leader + let moves = vec![ + (self.pubkey(), self.tokens), + (self.bootstrap_leader_id, self.bootstrap_leader_tokens), + ]; + vec![Transaction::system_move_many( + &keypair, + &moves, + self.seed(), + 0, + )] + } + } + + pub fn create_entries(&self) -> Vec { + let e0 = Entry::new(&self.seed(), 0, vec![]); + let e1 = Entry::new(&e0.id, 1, self.create_transaction()); + let e2 = Entry::new(&e1.id, 1, vec![]); // include a tick + vec![e0, e1, e2] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bincode::deserialize; + use ledger::Block; + use solana_sdk::system_instruction::SystemInstruction; + use system_program::SystemProgram; + + #[test] + fn test_create_transactions() { + let mut transactions = Mint::new(100).create_transaction().into_iter(); + let tx = transactions.next().unwrap(); + assert_eq!(tx.instructions.len(), 1); + assert!(SystemProgram::check_id(tx.program_id(0))); + let instruction: SystemInstruction = deserialize(tx.userdata(0)).unwrap(); + if let SystemInstruction::Move { tokens } = instruction { + assert_eq!(tokens, 100); + } + assert_eq!(transactions.next(), None); + } + + #[test] + fn test_create_leader_transactions() { + let dummy_leader_id = Keypair::new().pubkey(); + let dummy_leader_tokens = 1; + let mut transactions = Mint::new_with_leader(100, dummy_leader_id, dummy_leader_tokens) + .create_transaction() + .into_iter(); + let tx = transactions.next().unwrap(); + assert_eq!(tx.instructions.len(), 2); + assert!(SystemProgram::check_id(tx.program_id(0))); + assert!(SystemProgram::check_id(tx.program_id(1))); + let instruction: SystemInstruction = deserialize(tx.userdata(0)).unwrap(); + if let SystemInstruction::Move { tokens } = instruction { + assert_eq!(tokens, 100); + } + let instruction: SystemInstruction = deserialize(tx.userdata(1)).unwrap(); + if let SystemInstruction::Move { tokens } = instruction { + assert_eq!(tokens, 1); + } + assert_eq!(transactions.next(), None); + } + + #[test] + fn test_verify_entries() { + let entries = Mint::new(100).create_entries(); + assert!(entries[..].verify(&entries[0].id)); + } + + #[test] + fn test_verify_leader_entries() { + let dummy_leader_id = Keypair::new().pubkey(); + let dummy_leader_tokens = 1; + let entries = + Mint::new_with_leader(100, dummy_leader_id, dummy_leader_tokens).create_entries(); + assert!(entries[..].verify(&entries[0].id)); + } +} diff --git a/book/native_loader.rs b/book/native_loader.rs new file mode 100644 index 00000000000000..27885990e8e26a --- /dev/null +++ b/book/native_loader.rs @@ -0,0 +1,127 @@ +//! Native loader +use bincode::deserialize; +use libc; +#[cfg(unix)] +use libloading::os::unix::*; +#[cfg(windows)] +use libloading::os::windows::*; +use solana_sdk::account::KeyedAccount; +use solana_sdk::loader_instruction::LoaderInstruction; +use solana_sdk::native_program; +use solana_sdk::pubkey::Pubkey; +use std::env; +use std::path::PathBuf; +use std::str; + +/// Dynamic link library prefixs +#[cfg(unix)] +const PLATFORM_FILE_PREFIX_NATIVE: &str = "lib"; +#[cfg(windows)] +const PLATFORM_FILE_PREFIX_NATIVE: &str = ""; + +/// Dynamic link library file extension specific to the platform +#[cfg(any(target_os = "macos", target_os = "ios"))] +const PLATFORM_FILE_EXTENSION_NATIVE: &str = "dylib"; +/// Dynamic link library file extension specific to the platform +#[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))] +const PLATFORM_FILE_EXTENSION_NATIVE: &str = "so"; +/// Dynamic link library file extension specific to the platform +#[cfg(windows)] +const PLATFORM_FILE_EXTENSION_NATIVE: &str = "dll"; + +fn create_path(name: &str) -> PathBuf { + let pathbuf = { + let current_exe = env::current_exe().unwrap(); + PathBuf::from(current_exe.parent().unwrap()) + }; + + pathbuf.join( + PathBuf::from(PLATFORM_FILE_PREFIX_NATIVE.to_string() + name) + .with_extension(PLATFORM_FILE_EXTENSION_NATIVE), + ) +} + +const NATIVE_LOADER_PROGRAM_ID: [u8; 32] = [ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +pub fn check_id(program_id: &Pubkey) -> bool { + program_id.as_ref() == NATIVE_LOADER_PROGRAM_ID +} + +pub fn id() -> Pubkey { + Pubkey::new(&NATIVE_LOADER_PROGRAM_ID) +} + +pub fn process_instruction( + program_id: &Pubkey, + keyed_accounts: &mut [KeyedAccount], + ix_userdata: &[u8], + tick_height: u64, +) -> bool { + if keyed_accounts[0].account.executable { + // dispatch it + let name = keyed_accounts[0].account.userdata.clone(); + let name = match str::from_utf8(&name) { + Ok(v) => v, + Err(e) => { + warn!("Invalid UTF-8 sequence: {}", e); + return false; + } + }; + trace!("Call native {:?}", name); + let path = create_path(&name); + // TODO linux tls bug can cause crash on dlclose(), workaround by never unloading + match Library::open(Some(&path), libc::RTLD_NODELETE | libc::RTLD_NOW) { + Ok(library) => unsafe { + let entrypoint: Symbol = + match library.get(native_program::ENTRYPOINT.as_bytes()) { + Ok(s) => s, + Err(e) => { + warn!( + "{:?}: Unable to find {:?} in program", + e, + native_program::ENTRYPOINT + ); + return false; + } + }; + return entrypoint( + program_id, + &mut keyed_accounts[1..], + ix_userdata, + tick_height, + ); + }, + Err(e) => { + warn!("Unable to load: {:?}", e); + return false; + } + } + } else if let Ok(instruction) = deserialize(ix_userdata) { + match instruction { + LoaderInstruction::Write { offset, bytes } => { + trace!("NativeLoader::Write offset {} bytes {:?}", offset, bytes); + let offset = offset as usize; + if keyed_accounts[0].account.userdata.len() < offset + bytes.len() { + warn!( + "Error: Overflow, {} < {}", + keyed_accounts[0].account.userdata.len(), + offset + bytes.len() + ); + return false; + } + // native loader takes a name and we assume it all comes in at once + keyed_accounts[0].account.userdata = bytes; + } + + LoaderInstruction::Finalize => { + keyed_accounts[0].account.executable = true; + trace!("NativeLoader::Finalize prog: {:?}", keyed_accounts[0].key); + } + } + } else { + warn!("Invalid userdata in instruction: {:?}", ix_userdata); + } + true +} diff --git a/book/ncp.html b/book/ncp.html new file mode 100644 index 00000000000000..8743b66e17dd3e --- /dev/null +++ b/book/ncp.html @@ -0,0 +1,201 @@ + + + + + + Ncp - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

Ncp

+

The Network Control Plane implements a gossip network between all nodes on in the cluster.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/ncp.rs b/book/ncp.rs new file mode 100644 index 00000000000000..738534d25112f6 --- /dev/null +++ b/book/ncp.rs @@ -0,0 +1,86 @@ +//! The `ncp` module implements the network control plane. + +use cluster_info::ClusterInfo; +use service::Service; +use std::net::UdpSocket; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc::channel; +use std::sync::{Arc, RwLock}; +use std::thread::{self, JoinHandle}; +use streamer; +use window::SharedWindow; + +pub struct Ncp { + exit: Arc, + thread_hdls: Vec>, +} + +impl Ncp { + pub fn new( + cluster_info: &Arc>, + window: SharedWindow, + ledger_path: Option<&str>, + gossip_socket: UdpSocket, + exit: Arc, + ) -> Self { + let (request_sender, request_receiver) = channel(); + let gossip_socket = Arc::new(gossip_socket); + trace!( + "Ncp: id: {}, listening on: {:?}", + &cluster_info.read().unwrap().my_data().id, + gossip_socket.local_addr().unwrap() + ); + let t_receiver = + streamer::blob_receiver(gossip_socket.clone(), exit.clone(), request_sender); + let (response_sender, response_receiver) = channel(); + let t_responder = streamer::responder("ncp", gossip_socket, response_receiver); + let t_listen = ClusterInfo::listen( + cluster_info.clone(), + window, + ledger_path, + request_receiver, + response_sender.clone(), + exit.clone(), + ); + let t_gossip = ClusterInfo::gossip(cluster_info.clone(), response_sender, exit.clone()); + let thread_hdls = vec![t_receiver, t_responder, t_listen, t_gossip]; + Ncp { exit, thread_hdls } + } + + pub fn close(self) -> thread::Result<()> { + self.exit.store(true, Ordering::Relaxed); + self.join() + } +} + +impl Service for Ncp { + type JoinReturnType = (); + + fn join(self) -> thread::Result<()> { + for thread_hdl in self.thread_hdls { + thread_hdl.join()?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use cluster_info::{ClusterInfo, Node}; + use ncp::Ncp; + use std::sync::atomic::AtomicBool; + use std::sync::{Arc, RwLock}; + + #[test] + #[ignore] + // test that stage will exit when flag is set + fn test_exit() { + let exit = Arc::new(AtomicBool::new(false)); + let tn = Node::new_localhost(); + let cluster_info = ClusterInfo::new(tn.info.clone()); + let c = Arc::new(RwLock::new(cluster_info)); + let w = Arc::new(RwLock::new(vec![])); + let d = Ncp::new(&c, w, None, tn.sockets.gossip, exit.clone()); + d.close().expect("thread join"); + } +} diff --git a/book/netutil.rs b/book/netutil.rs new file mode 100644 index 00000000000000..133482bc54f806 --- /dev/null +++ b/book/netutil.rs @@ -0,0 +1,304 @@ +//! The `netutil` module assists with networking + +use nix::sys::socket::setsockopt; +use nix::sys::socket::sockopt::{ReuseAddr, ReusePort}; +use pnet_datalink as datalink; +use rand::{thread_rng, Rng}; +use reqwest; +use socket2::{Domain, SockAddr, Socket, Type}; +use std::io; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, UdpSocket}; +use std::os::unix::io::AsRawFd; + +/// A data type representing a public Udp socket +pub struct UdpSocketPair { + pub addr: SocketAddr, // Public address of the socket + pub receiver: UdpSocket, // Locally bound socket that can receive from the public address + pub sender: UdpSocket, // Locally bound socket to send via public address +} + +/// Tries to determine the public IP address of this machine +pub fn get_public_ip_addr() -> Result { + let body = reqwest::get("http://ifconfig.co/ip") + .map_err(|err| err.to_string())? + .text() + .map_err(|err| err.to_string())?; + + match body.lines().next() { + Some(ip) => Result::Ok(ip.parse().unwrap()), + None => Result::Err("Empty response body".to_string()), + } +} + +pub fn parse_port_or_addr(optstr: Option<&str>, default_port: u16) -> SocketAddr { + let daddr = SocketAddr::from(([0, 0, 0, 0], default_port)); + + if let Some(addrstr) = optstr { + if let Ok(port) = addrstr.parse() { + let mut addr = daddr; + addr.set_port(port); + addr + } else if let Ok(addr) = addrstr.parse() { + addr + } else { + daddr + } + } else { + daddr + } +} + +fn find_eth0ish_ip_addr(ifaces: &mut Vec) -> Option { + // put eth0 and wifi0, etc. up front of our list of candidates + ifaces.sort_by(|a, b| { + a.name + .chars() + .last() + .unwrap() + .cmp(&b.name.chars().last().unwrap()) + }); + + for iface in ifaces.clone() { + trace!("get_ip_addr considering iface {}", iface.name); + for p in iface.ips { + trace!(" ip {}", p); + if p.ip().is_loopback() { + trace!(" loopback"); + continue; + } + if p.ip().is_multicast() { + trace!(" multicast"); + continue; + } + match p.ip() { + IpAddr::V4(addr) => { + if addr.is_link_local() { + trace!(" link local"); + continue; + } + trace!(" picked {}", p.ip()); + return Some(p.ip()); + } + IpAddr::V6(_addr) => { + // Select an ipv6 address if the config is selected + #[cfg(feature = "ipv6")] + { + return Some(p.ip()); + } + } + } + } + } + None +} + +pub fn get_ip_addr() -> Option { + let mut ifaces = datalink::interfaces(); + + find_eth0ish_ip_addr(&mut ifaces) +} + +fn udp_socket(reuseaddr: bool) -> io::Result { + let sock = Socket::new(Domain::ipv4(), Type::dgram(), None)?; + let sock_fd = sock.as_raw_fd(); + + if reuseaddr { + // best effort, i.e. ignore errors here, we'll get the failure in caller + setsockopt(sock_fd, ReusePort, &true).ok(); + setsockopt(sock_fd, ReuseAddr, &true).ok(); + } + + Ok(sock) +} + +pub fn bind_in_range(range: (u16, u16)) -> io::Result<(u16, UdpSocket)> { + let sock = udp_socket(false)?; + + let (start, end) = range; + let mut tries_left = end - start; + let mut rand_port = thread_rng().gen_range(start, end); + loop { + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), rand_port); + + match sock.bind(&SockAddr::from(addr)) { + Ok(_) => { + let sock = sock.into_udp_socket(); + break Result::Ok((sock.local_addr().unwrap().port(), sock)); + } + Err(err) => { + if tries_left == 0 { + return Err(err); + } + } + } + rand_port += 1; + if rand_port == end { + rand_port = start; + } + tries_left -= 1; + } +} + +// binds many sockets to the same port in a range +pub fn multi_bind_in_range(range: (u16, u16), num: usize) -> io::Result<(u16, Vec)> { + let mut sockets = Vec::with_capacity(num); + + let port = { + let (port, _) = bind_in_range(range)?; + port + }; // drop the probe, port should be available... briefly. + + for _ in 0..num { + sockets.push(bind_to(port, true)?); + } + Ok((port, sockets)) +} + +pub fn bind_to(port: u16, reuseaddr: bool) -> io::Result { + let sock = udp_socket(reuseaddr)?; + + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), port); + + match sock.bind(&SockAddr::from(addr)) { + Ok(_) => Result::Ok(sock.into_udp_socket()), + Err(err) => Err(err), + } +} + +pub fn find_available_port_in_range(range: (u16, u16)) -> io::Result { + let (start, end) = range; + let mut tries_left = end - start; + let mut rand_port = thread_rng().gen_range(start, end); + loop { + match TcpListener::bind(SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), + rand_port, + )) { + Ok(_) => { + break Ok(rand_port); + } + Err(err) => { + if tries_left == 0 { + return Err(err); + } + } + } + rand_port += 1; + if rand_port == end { + rand_port = start; + } + tries_left -= 1; + } +} + +#[cfg(test)] +mod tests { + use ipnetwork::IpNetwork; + use logger; + use netutil::*; + use pnet_datalink as datalink; + + #[test] + fn test_find_eth0ish_ip_addr() { + logger::setup(); + + macro_rules! mock_interface { + ($name:ident, $ip_mask:expr) => { + datalink::NetworkInterface { + name: stringify!($name).to_string(), + index: 0, + mac: None, + ips: vec![IpNetwork::V4($ip_mask.parse().unwrap())], + flags: 0, + } + }; + } + + // loopback bad + assert_eq!( + find_eth0ish_ip_addr(&mut vec![mock_interface!(lo, "127.0.0.1/24")]), + None + ); + // multicast bad + assert_eq!( + find_eth0ish_ip_addr(&mut vec![mock_interface!(eth0, "224.0.1.5/24")]), + None + ); + + // finds "wifi0" + assert_eq!( + find_eth0ish_ip_addr(&mut vec![ + mock_interface!(eth0, "224.0.1.5/24"), + mock_interface!(eth2, "192.168.137.1/8"), + mock_interface!(eth3, "10.0.75.1/8"), + mock_interface!(eth4, "172.22.140.113/4"), + mock_interface!(lo, "127.0.0.1/24"), + mock_interface!(wifi0, "192.168.1.184/8"), + ]), + Some(mock_interface!(wifi0, "192.168.1.184/8").ips[0].ip()) + ); + // finds "wifi0" in the middle + assert_eq!( + find_eth0ish_ip_addr(&mut vec![ + mock_interface!(eth0, "224.0.1.5/24"), + mock_interface!(eth2, "192.168.137.1/8"), + mock_interface!(eth3, "10.0.75.1/8"), + mock_interface!(wifi0, "192.168.1.184/8"), + mock_interface!(eth4, "172.22.140.113/4"), + mock_interface!(lo, "127.0.0.1/24"), + ]), + Some(mock_interface!(wifi0, "192.168.1.184/8").ips[0].ip()) + ); + // picks "eth2", is lowest valid "eth" + assert_eq!( + find_eth0ish_ip_addr(&mut vec![ + mock_interface!(eth0, "224.0.1.5/24"), + mock_interface!(eth2, "192.168.137.1/8"), + mock_interface!(eth3, "10.0.75.1/8"), + mock_interface!(eth4, "172.22.140.113/4"), + mock_interface!(lo, "127.0.0.1/24"), + ]), + Some(mock_interface!(eth2, "192.168.137.1/8").ips[0].ip()) + ); + } + + #[test] + fn test_parse_port_or_addr() { + let p1 = parse_port_or_addr(Some("9000"), 1); + assert_eq!(p1.port(), 9000); + let p2 = parse_port_or_addr(Some("127.0.0.1:7000"), 1); + assert_eq!(p2.port(), 7000); + let p2 = parse_port_or_addr(Some("hi there"), 1); + assert_eq!(p2.port(), 1); + let p3 = parse_port_or_addr(None, 1); + assert_eq!(p3.port(), 1); + } + + #[test] + fn test_bind() { + assert_eq!(bind_in_range((2000, 2001)).unwrap().0, 2000); + let x = bind_to(2002, true).unwrap(); + let y = bind_to(2002, true).unwrap(); + assert_eq!( + x.local_addr().unwrap().port(), + y.local_addr().unwrap().port() + ); + let (port, v) = multi_bind_in_range((2010, 2110), 10).unwrap(); + for sock in &v { + assert_eq!(port, sock.local_addr().unwrap().port()); + } + } + + #[test] + #[should_panic] + fn test_bind_in_range_nil() { + let _ = bind_in_range((2000, 2000)); + } + + #[test] + fn test_find_available_port_in_range() { + assert_eq!(find_available_port_in_range((3000, 3001)).unwrap(), 3000); + let port = find_available_port_in_range((3000, 3050)).unwrap(); + assert!(3000 <= port && port < 3050); + } +} diff --git a/book/packet.rs b/book/packet.rs new file mode 100644 index 00000000000000..e134308698300d --- /dev/null +++ b/book/packet.rs @@ -0,0 +1,569 @@ +//! The `packet` module defines data structures and methods to pull data from the network. +use bincode::{deserialize, serialize}; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use counter::Counter; +#[cfg(test)] +use entry::Entry; +#[cfg(test)] +use ledger::Block; +use log::Level; +use recvmmsg::{recv_mmsg, NUM_RCVMMSGS}; +use result::{Error, Result}; +use serde::Serialize; +#[cfg(test)] +use solana_sdk::hash::Hash; +pub use solana_sdk::packet::PACKET_DATA_SIZE; +use solana_sdk::pubkey::Pubkey; +use std::fmt; +use std::io; +use std::mem::size_of; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket}; +use std::sync::atomic::AtomicUsize; +use std::sync::{Arc, RwLock}; + +pub type SharedPackets = Arc>; +pub type SharedBlob = Arc>; +pub type SharedBlobs = Vec; + +pub const NUM_PACKETS: usize = 1024 * 8; +pub const BLOB_SIZE: usize = (64 * 1024 - 128); // wikipedia says there should be 20b for ipv4 headers +pub const BLOB_DATA_SIZE: usize = BLOB_SIZE - (BLOB_HEADER_SIZE * 2); +pub const NUM_BLOBS: usize = (NUM_PACKETS * PACKET_DATA_SIZE) / BLOB_SIZE; + +#[derive(Clone, Default, Debug, PartialEq)] +#[repr(C)] +pub struct Meta { + pub size: usize, + pub num_retransmits: u64, + pub addr: [u16; 8], + pub port: u16, + pub v6: bool, +} + +#[derive(Clone)] +#[repr(C)] +pub struct Packet { + pub data: [u8; PACKET_DATA_SIZE], + pub meta: Meta, +} + +impl fmt::Debug for Packet { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Packet {{ size: {:?}, addr: {:?} }}", + self.meta.size, + self.meta.addr() + ) + } +} + +impl Default for Packet { + fn default() -> Packet { + Packet { + data: [0u8; PACKET_DATA_SIZE], + meta: Meta::default(), + } + } +} + +impl Meta { + pub fn addr(&self) -> SocketAddr { + if !self.v6 { + let addr = [ + self.addr[0] as u8, + self.addr[1] as u8, + self.addr[2] as u8, + self.addr[3] as u8, + ]; + let ipv4: Ipv4Addr = From::<[u8; 4]>::from(addr); + SocketAddr::new(IpAddr::V4(ipv4), self.port) + } else { + let ipv6: Ipv6Addr = From::<[u16; 8]>::from(self.addr); + SocketAddr::new(IpAddr::V6(ipv6), self.port) + } + } + + pub fn set_addr(&mut self, a: &SocketAddr) { + match *a { + SocketAddr::V4(v4) => { + let ip = v4.ip().octets(); + self.addr[0] = u16::from(ip[0]); + self.addr[1] = u16::from(ip[1]); + self.addr[2] = u16::from(ip[2]); + self.addr[3] = u16::from(ip[3]); + self.addr[4] = 0; + self.addr[5] = 0; + self.addr[6] = 0; + self.addr[7] = 0; + self.v6 = false; + } + SocketAddr::V6(v6) => { + self.addr = v6.ip().segments(); + self.v6 = true; + } + } + self.port = a.port(); + } +} + +#[derive(Debug)] +pub struct Packets { + pub packets: Vec, +} + +//auto derive doesn't support large arrays +impl Default for Packets { + fn default() -> Packets { + Packets { + packets: vec![Packet::default(); NUM_PACKETS], + } + } +} + +#[derive(Clone)] +pub struct Blob { + pub data: [u8; BLOB_SIZE], + pub meta: Meta, +} + +impl PartialEq for Blob { + fn eq(&self, other: &Blob) -> bool { + self.data.iter().zip(other.data.iter()).all(|(a, b)| a == b) && self.meta == other.meta + } +} + +impl fmt::Debug for Blob { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Blob {{ size: {:?}, addr: {:?} }}", + self.meta.size, + self.meta.addr() + ) + } +} + +//auto derive doesn't support large arrays +impl Default for Blob { + fn default() -> Blob { + Blob { + data: [0u8; BLOB_SIZE], + meta: Meta::default(), + } + } +} + +#[derive(Debug)] +pub enum BlobError { + /// the Blob's meta and data are not self-consistent + BadState, + /// Blob verification failed + VerificationFailed, +} + +impl Packets { + fn run_read_from(&mut self, socket: &UdpSocket) -> Result { + self.packets.resize(NUM_PACKETS, Packet::default()); + let mut i = 0; + //DOCUMENTED SIDE-EFFECT + //Performance out of the IO without poll + // * block on the socket until it's readable + // * set the socket to non blocking + // * read until it fails + // * set it back to blocking before returning + socket.set_nonblocking(false)?; + trace!("receiving on {}", socket.local_addr().unwrap()); + loop { + match recv_mmsg(socket, &mut self.packets[i..]) { + Err(_) if i > 0 => { + inc_new_counter_info!("packets-recv_count", i); + debug!("got {:?} messages on {}", i, socket.local_addr().unwrap()); + socket.set_nonblocking(true)?; + return Ok(i); + } + Err(e) => { + trace!("recv_from err {:?}", e); + return Err(Error::IO(e)); + } + Ok(npkts) => { + trace!("got {} packets", npkts); + i += npkts; + if npkts != NUM_RCVMMSGS { + socket.set_nonblocking(true)?; + inc_new_counter_info!("packets-recv_count", i); + return Ok(i); + } + } + } + } + } + pub fn recv_from(&mut self, socket: &UdpSocket) -> Result<()> { + let sz = self.run_read_from(socket)?; + self.packets.resize(sz, Packet::default()); + debug!("recv_from: {}", sz); + Ok(()) + } + pub fn send_to(&self, socket: &UdpSocket) -> Result<()> { + for p in &self.packets { + let a = p.meta.addr(); + socket.send_to(&p.data[..p.meta.size], &a)?; + } + Ok(()) + } +} + +pub fn to_packets_chunked(xs: &[T], chunks: usize) -> Vec { + let mut out = vec![]; + for x in xs.chunks(chunks) { + let mut p = SharedPackets::default(); + p.write() + .unwrap() + .packets + .resize(x.len(), Default::default()); + for (i, o) in x.iter().zip(p.write().unwrap().packets.iter_mut()) { + let v = serialize(&i).expect("serialize request"); + let len = v.len(); + o.data[..len].copy_from_slice(&v); + o.meta.size = len; + } + out.push(p); + } + out +} + +pub fn to_packets(xs: &[T]) -> Vec { + to_packets_chunked(xs, NUM_PACKETS) +} + +pub fn to_blob(resp: T, rsp_addr: SocketAddr) -> Result { + let blob = SharedBlob::default(); + { + let mut b = blob.write().unwrap(); + let v = serialize(&resp)?; + let len = v.len(); + assert!(len <= BLOB_SIZE); + b.data[..len].copy_from_slice(&v); + b.meta.size = len; + b.meta.set_addr(&rsp_addr); + } + Ok(blob) +} + +pub fn to_blobs(rsps: Vec<(T, SocketAddr)>) -> Result { + let mut blobs = Vec::new(); + for (resp, rsp_addr) in rsps { + blobs.push(to_blob(resp, rsp_addr)?); + } + Ok(blobs) +} + +const BLOB_SLOT_END: usize = size_of::(); +const BLOB_INDEX_END: usize = BLOB_SLOT_END + size_of::(); +const BLOB_ID_END: usize = BLOB_INDEX_END + size_of::(); +const BLOB_FLAGS_END: usize = BLOB_ID_END + size_of::(); +const BLOB_SIZE_END: usize = BLOB_FLAGS_END + size_of::(); + +macro_rules! align { + ($x:expr, $align:expr) => { + $x + ($align - 1) & !($align - 1) + }; +} + +pub const BLOB_FLAG_IS_CODING: u32 = 0x1; +pub const BLOB_HEADER_SIZE: usize = align!(BLOB_SIZE_END, 64); + +impl Blob { + pub fn slot(&self) -> Result { + let mut rdr = io::Cursor::new(&self.data[0..BLOB_SLOT_END]); + let r = rdr.read_u64::()?; + Ok(r) + } + pub fn set_slot(&mut self, ix: u64) -> Result<()> { + let mut wtr = vec![]; + wtr.write_u64::(ix)?; + self.data[..BLOB_SLOT_END].clone_from_slice(&wtr); + Ok(()) + } + pub fn index(&self) -> Result { + let mut rdr = io::Cursor::new(&self.data[BLOB_SLOT_END..BLOB_INDEX_END]); + let r = rdr.read_u64::()?; + Ok(r) + } + pub fn set_index(&mut self, ix: u64) -> Result<()> { + let mut wtr = vec![]; + wtr.write_u64::(ix)?; + self.data[BLOB_SLOT_END..BLOB_INDEX_END].clone_from_slice(&wtr); + Ok(()) + } + /// sender id, we use this for identifying if its a blob from the leader that we should + /// retransmit. eventually blobs should have a signature that we can use ffor spam filtering + pub fn id(&self) -> Result { + let e = deserialize(&self.data[BLOB_INDEX_END..BLOB_ID_END])?; + Ok(e) + } + + pub fn set_id(&mut self, id: &Pubkey) -> Result<()> { + let wtr = serialize(id)?; + self.data[BLOB_INDEX_END..BLOB_ID_END].clone_from_slice(&wtr); + Ok(()) + } + + pub fn flags(&self) -> Result { + let mut rdr = io::Cursor::new(&self.data[BLOB_ID_END..BLOB_FLAGS_END]); + let r = rdr.read_u32::()?; + Ok(r) + } + + pub fn set_flags(&mut self, ix: u32) -> Result<()> { + let mut wtr = vec![]; + wtr.write_u32::(ix)?; + self.data[BLOB_ID_END..BLOB_FLAGS_END].clone_from_slice(&wtr); + Ok(()) + } + + pub fn is_coding(&self) -> bool { + (self.flags().unwrap() & BLOB_FLAG_IS_CODING) != 0 + } + + pub fn set_coding(&mut self) -> Result<()> { + let flags = self.flags().unwrap(); + self.set_flags(flags | BLOB_FLAG_IS_CODING) + } + + pub fn data_size(&self) -> Result { + let mut rdr = io::Cursor::new(&self.data[BLOB_FLAGS_END..BLOB_SIZE_END]); + let r = rdr.read_u64::()?; + Ok(r) + } + + pub fn set_data_size(&mut self, ix: u64) -> Result<()> { + let mut wtr = vec![]; + wtr.write_u64::(ix)?; + self.data[BLOB_FLAGS_END..BLOB_SIZE_END].clone_from_slice(&wtr); + Ok(()) + } + + pub fn data(&self) -> &[u8] { + &self.data[BLOB_HEADER_SIZE..] + } + pub fn data_mut(&mut self) -> &mut [u8] { + &mut self.data[BLOB_HEADER_SIZE..] + } + pub fn size(&self) -> Result { + let size = self.data_size()? as usize; + if self.meta.size == size { + Ok(size - BLOB_HEADER_SIZE) + } else { + Err(Error::BlobError(BlobError::BadState)) + } + } + pub fn set_size(&mut self, size: usize) { + let new_size = size + BLOB_HEADER_SIZE; + self.meta.size = new_size; + self.set_data_size(new_size as u64).unwrap(); + } + + pub fn recv_blob(socket: &UdpSocket, r: &SharedBlob) -> io::Result<()> { + let mut p = r.write().unwrap(); + trace!("receiving on {}", socket.local_addr().unwrap()); + + let (nrecv, from) = socket.recv_from(&mut p.data)?; + p.meta.size = nrecv; + p.meta.set_addr(&from); + trace!("got {} bytes from {}", nrecv, from); + Ok(()) + } + + pub fn recv_from(socket: &UdpSocket) -> Result { + let mut v = Vec::new(); + //DOCUMENTED SIDE-EFFECT + //Performance out of the IO without poll + // * block on the socket until it's readable + // * set the socket to non blocking + // * read until it fails + // * set it back to blocking before returning + socket.set_nonblocking(false)?; + for i in 0..NUM_BLOBS { + let r = SharedBlob::default(); + + match Blob::recv_blob(socket, &r) { + Err(_) if i > 0 => { + trace!("got {:?} messages on {}", i, socket.local_addr().unwrap()); + break; + } + Err(e) => { + if e.kind() != io::ErrorKind::WouldBlock { + info!("recv_from err {:?}", e); + } + return Err(Error::IO(e)); + } + Ok(()) => { + if i == 0 { + socket.set_nonblocking(true)?; + } + } + } + v.push(r); + } + Ok(v) + } + pub fn send_to(socket: &UdpSocket, v: SharedBlobs) -> Result<()> { + for r in v { + { + let p = r.read().unwrap(); + let a = p.meta.addr(); + if let Err(e) = socket.send_to(&p.data[..p.meta.size], &a) { + warn!( + "error sending {} byte packet to {:?}: {:?}", + p.meta.size, a, e + ); + Err(e)?; + } + } + } + Ok(()) + } +} + +pub fn index_blobs(blobs: &[SharedBlob], id: &Pubkey, mut index: u64, slot: u64) { + // enumerate all the blobs, those are the indices + for b in blobs { + let mut blob = b.write().unwrap(); + + blob.set_index(index).expect("set_index"); + blob.set_slot(slot).expect("set_slot"); + blob.set_id(id).expect("set_id"); + blob.set_flags(0).unwrap(); + + index += 1; + } +} + +#[cfg(test)] +pub fn make_consecutive_blobs( + me_id: Pubkey, + num_blobs_to_make: u64, + start_height: u64, + start_hash: Hash, + addr: &SocketAddr, +) -> SharedBlobs { + let mut last_hash = start_hash; + let num_hashes = 1; + let mut all_entries = Vec::with_capacity(num_blobs_to_make as usize); + for _ in 0..num_blobs_to_make { + let entry = Entry::new(&last_hash, num_hashes, vec![]); + last_hash = entry.id; + all_entries.push(entry); + } + let mut new_blobs = all_entries.to_blobs_with_id(me_id, start_height, addr); + new_blobs.truncate(num_blobs_to_make as usize); + new_blobs +} + +#[cfg(test)] +mod tests { + use packet::{ + to_packets, Blob, Meta, Packet, Packets, SharedBlob, SharedPackets, NUM_PACKETS, + PACKET_DATA_SIZE, + }; + use signature::{Keypair, KeypairUtil}; + use solana_sdk::hash::Hash; + use std::io; + use std::io::Write; + use std::net::UdpSocket; + use system_transaction::SystemTransaction; + use transaction::Transaction; + + #[test] + pub fn packet_send_recv() { + let reader = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let addr = reader.local_addr().unwrap(); + let sender = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let saddr = sender.local_addr().unwrap(); + let p = SharedPackets::default(); + p.write().unwrap().packets.resize(10, Packet::default()); + for m in p.write().unwrap().packets.iter_mut() { + m.meta.set_addr(&addr); + m.meta.size = PACKET_DATA_SIZE; + } + p.read().unwrap().send_to(&sender).unwrap(); + p.write().unwrap().recv_from(&reader).unwrap(); + for m in p.write().unwrap().packets.iter_mut() { + assert_eq!(m.meta.size, PACKET_DATA_SIZE); + assert_eq!(m.meta.addr(), saddr); + } + } + + #[test] + fn test_to_packets() { + let keypair = Keypair::new(); + let hash = Hash::new(&[1; 32]); + let tx = Transaction::system_new(&keypair, keypair.pubkey(), 1, hash); + let rv = to_packets(&vec![tx.clone(); 1]); + assert_eq!(rv.len(), 1); + assert_eq!(rv[0].read().unwrap().packets.len(), 1); + + let rv = to_packets(&vec![tx.clone(); NUM_PACKETS]); + assert_eq!(rv.len(), 1); + assert_eq!(rv[0].read().unwrap().packets.len(), NUM_PACKETS); + + let rv = to_packets(&vec![tx.clone(); NUM_PACKETS + 1]); + assert_eq!(rv.len(), 2); + assert_eq!(rv[0].read().unwrap().packets.len(), NUM_PACKETS); + assert_eq!(rv[1].read().unwrap().packets.len(), 1); + } + + #[test] + pub fn blob_send_recv() { + trace!("start"); + let reader = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let addr = reader.local_addr().unwrap(); + let sender = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let p = SharedBlob::default(); + p.write().unwrap().meta.set_addr(&addr); + p.write().unwrap().meta.size = 1024; + let v = vec![p]; + Blob::send_to(&sender, v).unwrap(); + trace!("send_to"); + let rv = Blob::recv_from(&reader).unwrap(); + trace!("recv_from"); + assert_eq!(rv.len(), 1); + assert_eq!(rv[0].read().unwrap().meta.size, 1024); + } + + #[cfg(all(feature = "ipv6", test))] + #[test] + pub fn blob_ipv6_send_recv() { + let reader = UdpSocket::bind("[::1]:0").expect("bind"); + let addr = reader.local_addr().unwrap(); + let sender = UdpSocket::bind("[::1]:0").expect("bind"); + let p = SharedBlob::default(); + p.as_mut().unwrap().meta.set_addr(&addr); + p.as_mut().unwrap().meta.size = 1024; + let mut v = VecDeque::default(); + v.push_back(p); + Blob::send_to(&r, &sender, &mut v).unwrap(); + let mut rv = Blob::recv_from(&reader).unwrap(); + let rp = rv.pop_front().unwrap(); + assert_eq!(rp.as_mut().meta.size, 1024); + } + + #[test] + pub fn debug_trait() { + write!(io::sink(), "{:?}", Packet::default()).unwrap(); + write!(io::sink(), "{:?}", Packets::default()).unwrap(); + write!(io::sink(), "{:?}", Blob::default()).unwrap(); + } + #[test] + pub fn blob_test() { + let mut b = Blob::default(); + b.set_index(::max_value()).unwrap(); + assert_eq!(b.index().unwrap(), ::max_value()); + b.data_mut()[0] = 1; + assert_eq!(b.data()[0], 1); + assert_eq!(b.index().unwrap(), ::max_value()); + assert_eq!(b.meta, Meta::default()); + } + +} diff --git a/book/payment_plan.rs b/book/payment_plan.rs new file mode 100644 index 00000000000000..c0f5f1e651ab63 --- /dev/null +++ b/book/payment_plan.rs @@ -0,0 +1,27 @@ +//! The `plan` module provides a domain-specific language for payment plans. Users create BudgetExpr objects that +//! are given to an interpreter. The interpreter listens for `Witness` transactions, +//! which it uses to reduce the payment plan. When the plan is reduced to a +//! `Payment`, the payment is executed. + +use chrono::prelude::*; +use solana_sdk::pubkey::Pubkey; + +/// The types of events a payment plan can process. +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub enum Witness { + /// The current time. + Timestamp(DateTime), + + /// A signature from Pubkey. + Signature, +} + +/// Some amount of tokens that should be sent to the `to` `Pubkey`. +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub struct Payment { + /// Amount to be paid. + pub tokens: u64, + + /// The `Pubkey` that `tokens` should be paid to. + pub to: Pubkey, +} diff --git a/book/poh.html b/book/poh.html new file mode 100644 index 00000000000000..60948bb29f1ef2 --- /dev/null +++ b/book/poh.html @@ -0,0 +1,235 @@ + + + + + + Proof of History - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

Proof of History

+

Proof of History overview

+

Relationship to consensus mechanisms

+

Most confusingly, a Proof of History (PoH) is more similar to a Verifiable +Delay Function (VDF) than a Proof of Work or Proof of Stake consensus +mechanism. The name unfortunately requires some historical context to +understand. Proof of History was developed by Anatoly Yakovenko in November of +2017, roughly 6 months before we saw a paper using the term +VDF. At that time, it was commonplace to +publish new proofs of some desirable property used to build most any blockchain +component. Some time shortly after, the crypto community began charting out all +the different consensus mechanisms and because most of them started with "Proof +of", the prefix became synonymous with a "consensus" suffix. Proof of History +is not a consensus mechanism, but it is used to improve the performance of +Solana's Proof of Stake consensus. It is also used to improve the performance +of the replication and storage protocols. To minimize confusion, Solana may +rebrand PoH to some flavor of the term VDF.

+

Relationship to VDFs

+

A desirable property of a VDF is that verification time is very fast. Solana's +approach to verifying its delay function is proportional to the time it took to +create it. Split over a 4000 core GPU, it is sufficiently fast for Solana's +needs, but if you asked the authors the paper cited above, they might tell you +(and have) that Solana's approach is algorithmically slow it shouldn't be +called a VDF. We argue the term VDF should represent the category of verifiable +delay functions and not just the subset with certain performance +characteristics. Until that's resolved, Solana will likely continue using the +term PoH for its application-specific VDF.

+

Another difference between PoH and VDFs used only for tracking duration, is +that PoH's hash chain includes hashes of any data the application observed. +That data is a double-edged sword. On one side, the data "proves history" - +that the data most certainly existed before hashes after it. On the side, it +means the application can manipulate the hash chain by changing when the data +is hashed. The PoH chain therefore does not serve as a good source of +randomness whereas a VDF without that data could. Solana's leader selection +algorithm (TODO: add link), for example, is derived only from the VDF height +and not its hash at that height.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/poh.rs b/book/poh.rs new file mode 100644 index 00000000000000..1b1a908b98a39a --- /dev/null +++ b/book/poh.rs @@ -0,0 +1,116 @@ +//! The `Poh` module provides an object for generating a Proof of History. +//! It records Hashes items on behalf of its users. +use solana_sdk::hash::{hash, hashv, Hash}; + +pub struct Poh { + prev_id: Hash, + id: Hash, + num_hashes: u64, + pub tick_height: u64, +} + +#[derive(Debug)] +pub struct PohEntry { + pub prev_id: Hash, + pub num_hashes: u64, + pub id: Hash, + pub mixin: Option, +} + +impl Poh { + pub fn new(prev_id: Hash, tick_height: u64) -> Self { + Poh { + prev_id, + num_hashes: 0, + id: prev_id, + tick_height, + } + } + + pub fn hash(&mut self) { + self.id = hash(&self.id.as_ref()); + self.num_hashes += 1; + } + + pub fn record(&mut self, mixin: Hash) -> PohEntry { + self.id = hashv(&[&self.id.as_ref(), &mixin.as_ref()]); + + let prev_id = self.prev_id; + self.prev_id = self.id; + + let num_hashes = self.num_hashes + 1; + self.num_hashes = 0; + + PohEntry { + prev_id, + num_hashes, + id: self.id, + mixin: Some(mixin), + } + } + + // emissions of Ticks (i.e. PohEntries without a mixin) allows + // validators to parallelize the work of catching up + pub fn tick(&mut self) -> PohEntry { + self.hash(); + + let num_hashes = self.num_hashes; + self.num_hashes = 0; + + let prev_id = self.prev_id; + self.prev_id = self.id; + + self.tick_height += 1; + + PohEntry { + prev_id, + num_hashes, + id: self.id, + mixin: None, + } + } +} + +#[cfg(test)] +pub fn verify(initial: Hash, entries: &[PohEntry]) -> bool { + let mut prev_id = initial; + + for entry in entries { + assert!(entry.num_hashes != 0); + assert!(prev_id == entry.prev_id); + + for _ in 1..entry.num_hashes { + prev_id = hash(&prev_id.as_ref()); + } + prev_id = match entry.mixin { + Some(mixin) => hashv(&[&prev_id.as_ref(), &mixin.as_ref()]), + None => hash(&prev_id.as_ref()), + }; + if prev_id != entry.id { + return false; + } + } + + true +} + +#[cfg(test)] +mod tests { + use poh::{self, PohEntry}; + use solana_sdk::hash::Hash; + + #[test] + #[should_panic] + fn test_poh_verify_assert() { + poh::verify( + Hash::default(), + &[PohEntry { + prev_id: Hash::default(), + num_hashes: 0, + id: Hash::default(), + mixin: None, + }], + ); + } + +} diff --git a/book/poh_recorder.rs b/book/poh_recorder.rs new file mode 100644 index 00000000000000..0ac484c8c0c9f6 --- /dev/null +++ b/book/poh_recorder.rs @@ -0,0 +1,149 @@ +//! The `poh_recorder` module provides an object for synchronizing with Proof of History. +//! It synchronizes PoH, bank's register_tick and the ledger +//! +use bank::Bank; +use entry::Entry; +use poh::Poh; +use result::{Error, Result}; +use solana_sdk::hash::Hash; +use std::sync::mpsc::Sender; +use std::sync::{Arc, Mutex}; +use transaction::Transaction; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum PohRecorderError { + InvalidCallingObject, + MaxHeightReached, +} + +#[derive(Clone)] +pub struct PohRecorder { + poh: Arc>, + bank: Arc, + sender: Sender>, + max_tick_height: Option, +} + +impl PohRecorder { + pub fn hash(&self) -> Result<()> { + // TODO: amortize the cost of this lock by doing the loop in here for + // some min amount of hashes + let mut poh = self.poh.lock().unwrap(); + if self.is_max_tick_height_reached(&poh) { + Err(Error::PohRecorderError(PohRecorderError::MaxHeightReached)) + } else { + poh.hash(); + Ok(()) + } + } + + pub fn tick(&mut self) -> Result<()> { + // Register and send the entry out while holding the lock if the max PoH height + // hasn't been reached. + // This guarantees PoH order and Entry production and banks LastId queue is the same + let mut poh = self.poh.lock().unwrap(); + if self.is_max_tick_height_reached(&poh) { + Err(Error::PohRecorderError(PohRecorderError::MaxHeightReached)) + } else { + self.register_and_send_tick(&mut *poh)?; + Ok(()) + } + } + + pub fn record(&self, mixin: Hash, txs: Vec) -> Result<()> { + // Register and send the entry out while holding the lock. + // This guarantees PoH order and Entry production and banks LastId queue is the same. + let mut poh = self.poh.lock().unwrap(); + if self.is_max_tick_height_reached(&poh) { + Err(Error::PohRecorderError(PohRecorderError::MaxHeightReached)) + } else { + self.record_and_send_txs(&mut *poh, mixin, txs)?; + Ok(()) + } + } + + /// A recorder to synchronize PoH with the following data structures + /// * bank - the LastId's queue is updated on `tick` and `record` events + /// * sender - the Entry channel that outputs to the ledger + pub fn new( + bank: Arc, + sender: Sender>, + last_entry_id: Hash, + max_tick_height: Option, + ) -> Self { + let poh = Arc::new(Mutex::new(Poh::new(last_entry_id, bank.tick_height()))); + PohRecorder { + poh, + bank, + sender, + max_tick_height, + } + } + + fn is_max_tick_height_reached(&self, poh: &Poh) -> bool { + if let Some(max_tick_height) = self.max_tick_height { + poh.tick_height >= max_tick_height + } else { + false + } + } + + fn record_and_send_txs(&self, poh: &mut Poh, mixin: Hash, txs: Vec) -> Result<()> { + let entry = poh.record(mixin); + assert!(!txs.is_empty(), "Entries without transactions are used to track real-time passing in the ledger and can only be generated with PohRecorder::tick function"); + let entry = Entry { + prev_id: entry.prev_id, + num_hashes: entry.num_hashes, + id: entry.id, + transactions: txs, + }; + self.sender.send(vec![entry])?; + Ok(()) + } + + fn register_and_send_tick(&self, poh: &mut Poh) -> Result<()> { + let tick = poh.tick(); + let tick = Entry { + prev_id: tick.prev_id, + num_hashes: tick.num_hashes, + id: tick.id, + transactions: vec![], + }; + self.bank.register_tick(&tick.id); + self.sender.send(vec![tick])?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mint::Mint; + use solana_sdk::hash::hash; + use std::sync::mpsc::channel; + use std::sync::Arc; + use system_transaction::test_tx; + + #[test] + fn test_poh() { + let mint = Mint::new(1); + let bank = Arc::new(Bank::new(&mint)); + let prev_id = bank.last_id(); + let (entry_sender, entry_receiver) = channel(); + let mut poh_recorder = PohRecorder::new(bank, entry_sender, prev_id, None); + + //send some data + let h1 = hash(b"hello world!"); + let tx = test_tx(); + assert!(poh_recorder.record(h1, vec![tx]).is_ok()); + assert!(poh_recorder.tick().is_ok()); + + //get some events + let _ = entry_receiver.recv().unwrap(); + let _ = entry_receiver.recv().unwrap(); + + //make sure it handles channel close correctly + drop(entry_receiver); + assert!(poh_recorder.tick().is_err()); + } +} diff --git a/book/poh_service.rs b/book/poh_service.rs new file mode 100644 index 00000000000000..a9d53a464d54f3 --- /dev/null +++ b/book/poh_service.rs @@ -0,0 +1,94 @@ +//! The `poh_service` module implements a service that records the passing of +//! "ticks", a measure of time in the PoH stream + +use poh_recorder::PohRecorder; +use result::Result; +use service::Service; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread::sleep; +use std::thread::{self, Builder, JoinHandle}; +use std::time::Duration; +pub const NUM_TICKS_PER_SECOND: usize = 10; + +#[derive(Copy, Clone)] +pub enum Config { + /// * `Tick` - Run full PoH thread. Tick is a rough estimate of how many hashes to roll before transmitting a new entry. + Tick(usize), + /// * `Sleep`- Low power mode. Sleep is a rough estimate of how long to sleep before rolling 1 poh once and producing 1 + /// tick. + Sleep(Duration), +} + +impl Default for Config { + fn default() -> Config { + // TODO: Change this to Tick to enable PoH + Config::Sleep(Duration::from_millis(1000 / NUM_TICKS_PER_SECOND as u64)) + } +} + +pub struct PohService { + tick_producer: JoinHandle>, + pub poh_exit: Arc, +} + +impl PohService { + pub fn exit(&self) -> () { + self.poh_exit.store(true, Ordering::Relaxed); + } + + pub fn close(self) -> thread::Result> { + self.exit(); + self.join() + } + + pub fn new(poh_recorder: PohRecorder, config: Config) -> Self { + // PohService is a headless producer, so when it exits it should notify the banking stage. + // Since channel are not used to talk between these threads an AtomicBool is used as a + // signal. + let poh_exit = Arc::new(AtomicBool::new(false)); + let poh_exit_ = poh_exit.clone(); + // Single thread to generate ticks + let tick_producer = Builder::new() + .name("solana-poh-service-tick_producer".to_string()) + .spawn(move || { + let mut poh_recorder_ = poh_recorder; + let return_value = Self::tick_producer(&mut poh_recorder_, config, &poh_exit_); + poh_exit_.store(true, Ordering::Relaxed); + return_value + }).unwrap(); + + PohService { + tick_producer, + poh_exit, + } + } + + fn tick_producer(poh: &mut PohRecorder, config: Config, poh_exit: &AtomicBool) -> Result<()> { + loop { + match config { + Config::Tick(num) => { + for _ in 0..num { + poh.hash()?; + } + } + Config::Sleep(duration) => { + sleep(duration); + } + } + poh.tick()?; + if poh_exit.load(Ordering::Relaxed) { + debug!("tick service exited"); + return Ok(()); + } + } + } +} + +impl Service for PohService { + type JoinReturnType = Result<()>; + + fn join(self) -> thread::Result> { + self.tick_producer.join() + } +} diff --git a/book/print.html b/book/print.html new file mode 100644 index 00000000000000..d1e13035b1294a --- /dev/null +++ b/book/print.html @@ -0,0 +1,1282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

Disclaimer

+

All claims, content, designs, algorithms, estimates, roadmaps, specifications, +and performance measurements described in this project are done with the +author's best effort. It is up to the reader to check and validate their +accuracy and truthfulness. Furthermore nothing in this project constitutes a +solicitation for investment.

+

Introduction

+

This document defines the architecture of Solana, a blockchain built from the +ground up for scale. The goal of the architecture is to demonstrate there +exists a set of software algorithms that in combination, removes software as a +performance bottleneck, allowing transaction throughput to scale proportionally +with network bandwidth. The architecture goes on to satisfy all three desirable +properties of a proper blockchain, that it not only be scalable, but that it is +also secure and decentralized.

+

With this architecture, we calculate a theoretical upper bound of 710 thousand +transactions per second (tps) on a standard gigabit network and 28.4 million +tps on 40 gigabit. In practice, our focus has been on gigabit. We soak-tested +a 150 node permissioned testnet and it is able to maintain a mean transaction +throughput of approximately 200 thousand tps with peaks over 400 thousand.

+

Furthermore, we have found high throughput extends beyond simple payments, and +that this architecture is also able to perform safe, concurrent execution of +programs authored in a general purpose programming language, such as C. We feel +the extension warrants industry focus on an additional performance metric +already common in the CPU industry, millions of instructions per second or +mips. By measuring mips, we see that batching instructions within a transaction +amortizes the cost of signature verification, lifting the maximum theoretical +instruction throughput up to almost exactly that of centralized databases.

+

Lastly, we discuss the relationships between high throughput, security and +transaction fees. Solana's efficient use hardware drives transaction fees into +the ballpark of 1/1000th of a cent. The drop in fees in turn makes certain +denial of service attacks cheaper. We discuss what these attacks look like and +Solana's techniques to defend against them.

+

Terminology

+

Teminology Currently in Use

+

The following list contains words commonly used throughout the Solana architecture.

+
    +
  • account - a persistent file addressed by pubkey and with tokens tracking its lifetime
  • +
  • cluster - a set of fullnodes maintaining a single ledger
  • +
  • finality - the wallclock duration between a leader creating a tick entry and recoginizing +a supermajority of validator votes with a ledger interpretation that matches the leader's
  • +
  • fullnode - a full participant in the cluster - either a leader or validator node
  • +
  • entry - an entry on the ledger - either a tick or a transactions entry
  • +
  • instruction - the smallest unit of a program that a client can include in a transaction
  • +
  • keypair - a public and secret key
  • +
  • node count - the number of fullnodes participating in a cluster
  • +
  • program - the code that interprets instructions
  • +
  • pubkey - the public key of a keypair
  • +
  • tick - a ledger entry that estimates wallclock duration
  • +
  • tick height - the Nth tick in the ledger
  • +
  • tps - transactions per second
  • +
  • transaction - one or more instructions signed by the client and executed atomically
  • +
  • transactions entry - a set of transactions that may be executed in parallel
  • +
+

Terminology Reserved for Future Use

+

The following keywords do not have any functionality but are reserved by Solana +for potential future use.

+
    +
  • epoch - the time in which a leader schedule is valid
  • +
  • mips - millions of instructions per second
  • +
  • public key - We currently use pubkey
  • +
  • slot - the time in which a single leader may produce entries
  • +
  • secret key - Users currently only use keypair
  • +
+

Synchronization

+

It's possible for a centralized database to process 710,000 transactions per +second on a standard gigabit network if the transactions are, on average, no +more than 176 bytes. A centralized database can also replicate itself and +maintain high availability without significantly compromising that transaction +rate using the distributed system technique known as Optimistic Concurrency +Control [H.T.Kung, J.T.Robinson +(1981)]. At +Solana, we're demonstrating that these same theoretical limits apply just as +well to blockchain on an adversarial network. The key ingredient? Finding a way +to share time when nodes can't trust one-another. Once nodes can trust time, +suddenly ~40 years of distributed systems research becomes applicable to +blockchain!

+
+

Perhaps the most striking difference between algorithms obtained by our +method and ones based upon timeout is that using timeout produces a +traditional distributed algorithm in which the processes operate +asynchronously, while our method produces a globally synchronous one in which +every process does the same thing at (approximately) the same time. Our +method seems to contradict the whole purpose of distributed processing, which +is to permit different processes to operate independently and perform +different functions. However, if a distributed system is really a single +system, then the processes must be synchronized in some way. Conceptually, +the easiest way to synchronize processes is to get them all to do the same +thing at the same time. Therefore, our method is used to implement a kernel +that performs the necessary synchronization--for example, making sure that +two different processes do not try to modify a file at the same time. +Processes might spend only a small fraction of their time executing the +synchronizing kernel; the rest of the time, they can operate +independently--e.g., accessing different files. This is an approach we have +advocated even when fault-tolerance is not required. The method's basic +simplicity makes it easier to understand the precise properties of a system, +which is crucial if one is to know just how fault-tolerant the system is. +[L.Lamport +(1984)]

+
+

Introduction to VDFs

+

A Verifiable Delay Function is conceptually a water clock where its water marks +can be recorded and later verified that the water most certainly passed +through. Anatoly describes the water clock analogy in detail here:

+

water clock analogy

+

The same technique has been used in Bitcoin since day one. The Bitcoin feature +is called nLocktime and it can be used to postdate transactions using block +height instead of a timestamp. As a Bitcoin client, you'd use block height +instead of a timestamp if you don't trust the network. Block height turns out +to be an instance of what's being called a Verifiable Delay Function in +cryptography circles. It's a cryptographically secure way to say time has +passed. In Solana, we use a far more granular verifiable delay function, a SHA +256 hash chain, to checkpoint the ledger and coordinate consensus. With it, we +implement Optimistic Concurrency Control and are now well en route towards that +theoretical limit of 710,000 transactions per second.

+

Proof of History

+

Proof of History overview

+

Relationship to consensus mechanisms

+

Most confusingly, a Proof of History (PoH) is more similar to a Verifiable +Delay Function (VDF) than a Proof of Work or Proof of Stake consensus +mechanism. The name unfortunately requires some historical context to +understand. Proof of History was developed by Anatoly Yakovenko in November of +2017, roughly 6 months before we saw a paper using the term +VDF. At that time, it was commonplace to +publish new proofs of some desirable property used to build most any blockchain +component. Some time shortly after, the crypto community began charting out all +the different consensus mechanisms and because most of them started with "Proof +of", the prefix became synonymous with a "consensus" suffix. Proof of History +is not a consensus mechanism, but it is used to improve the performance of +Solana's Proof of Stake consensus. It is also used to improve the performance +of the replication and storage protocols. To minimize confusion, Solana may +rebrand PoH to some flavor of the term VDF.

+

Relationship to VDFs

+

A desirable property of a VDF is that verification time is very fast. Solana's +approach to verifying its delay function is proportional to the time it took to +create it. Split over a 4000 core GPU, it is sufficiently fast for Solana's +needs, but if you asked the authors the paper cited above, they might tell you +(and have) that Solana's approach is algorithmically slow it shouldn't be +called a VDF. We argue the term VDF should represent the category of verifiable +delay functions and not just the subset with certain performance +characteristics. Until that's resolved, Solana will likely continue using the +term PoH for its application-specific VDF.

+

Another difference between PoH and VDFs used only for tracking duration, is +that PoH's hash chain includes hashes of any data the application observed. +That data is a double-edged sword. On one side, the data "proves history" - +that the data most certainly existed before hashes after it. On the side, it +means the application can manipulate the hash chain by changing when the data +is hashed. The PoH chain therefore does not serve as a good source of +randomness whereas a VDF without that data could. Solana's leader selection +algorithm (TODO: add link), for example, is derived only from the VDF height +and not its hash at that height.

+

Leader Rotation

+

A property of any permissionless blockchain is that the entity choosing the next block is randomly selected. In proof of stake systems, +that entity is typically called the "leader" or "block producer." In Solana, we call it the leader. Under the hood, a leader is +simply a mode of the fullnode. A fullnode runs as either a leader or validator. In this chapter, we describe how a fullnode determines +what node is the leader, how that mechanism may choose different leaders at the same time, and if so, how the system converges in response.

+

Leader Seed Generation

+

Leader selection is decided via a random seed. The process is as follows:

+
    +
  1. Periodically, at a specific PoH tick count, select the signatures of the votes that made up the last supermajority
  2. +
  3. Concatenate the signatures
  4. +
  5. Hash the resulting string for N counts
  6. +
  7. The resulting hash is the random seed for M counts, M leader slots, where M > N
  8. +
+

Leader Rotation

+
    +
  1. The leader is chosen via a random seed generated from stake weights and votes (the leader schedule)
  2. +
  3. The leader is rotated every T PoH ticks (leader slot), according to the leader schedule
  4. +
  5. The schedule is applicable for M voting rounds
  6. +
+

Leader's transmit for a count of T PoH ticks. When T is reached all the validators should switch to the next scheduled leader. To schedule leaders, the supermajority + M nodes are shuffled using the above calculated random seed.

+

All T ticks must be observed from the current leader for that part of PoH to be accepted by the network. If T ticks (and any intervening transactions) are not observed, the network optimistically fills in the T ticks, and continues with PoH from the next leader.

+

Partitions, Forks

+

Forks can arise at PoH tick counts that correspond to leader rotations, because leader nodes may or may not have observed the previous leader's data. These empty ticks are generated by all nodes in the network at a network-specified rate for hashes-per-tick Z.

+

There are only two possible versions of the PoH during a voting round: PoH with T ticks and entries generated by the current leader, or PoH with just ticks. The "just ticks" version of the PoH can be thought of as a virtual ledger, one that all nodes in the network can derive from the last tick in the previous slot.

+

Validators can ignore forks at other points (e.g. from the wrong leader), or slash the leader responsible for the fork.

+

Validators vote on the longest chain that contains their previous vote, or a longer chain if the lockout on their previous vote has expired.

+

Validator's View

+
Time Progression
+

The diagram below represents a validator's view of the PoH stream with possible forks over time. L1, L2, etc. are leader slots, and Es represent entries from that leader during that leader's slot. The xs represent ticks only, and time flows downwards in the diagram.

+

Leader scheduler

+

Note that an E appearing on 2 branches at the same slot is a slashable condition, so a validator observing L3 and L3' can slash L3 and safely choose x for that slot. Once a validator observes a supermajority vote on any branch, other branches can be discarded below that tick count. For any slot, validators need only consider a single "has entries" chain or a "ticks only" chain.

+
Time Division
+

It's useful to consider leader rotation over PoH tick count as time division of the job of encoding state for the network. The following table presents the above tree of forks as a time-divided ledger.

+ + + +
leader slot L1 L2 L3 L4 L5
data E1 E2 E3 E4 E5
ticks since prev x xx
+

Note that only data from leader L3 will be accepted during leader slot +L3. Data from L3 may include "catchup" ticks back to a slot other than +L2 if L3 did not observe L2's data. L4 and L5's transmissions +include the "ticks since prev" PoH entries.

+

This arrangement of the network data streams permits nodes to save exactly this +to the ledger for replay, restart, and checkpoints.

+

Leader's View

+

When a new leader begins a slot, it must first transmit any PoH (ticks) +required to link the new slot with the most recently observed and voted +slot.

+

Examples

+

Small Partition

+
    +
  1. Network partition M occurs for 10% of the nodes
  2. +
  3. The larger partition K, with 90% of the stake weight continues to operate as +normal
  4. +
  5. M cycles through the ranks until one of them is leader, generating ticks for +slots where the leader is in K.
  6. +
  7. M validators observe 10% of the vote pool, finality is not reached.
  8. +
  9. M and K reconnect.
  10. +
  11. M validators cancel their votes on M, which has not reached finality, and +re-cast on K (after their vote lockout on M).
  12. +
+

Leader Timeout

+
    +
  1. Next rank leader node V observes a timeout from current leader A, fills in +A's slot with virtual ticks and starts sending out entries.
  2. +
  3. Nodes observing both streams keep track of the forks, waiting for: +
      +
    • their vote on leader A to expire in order to be able to vote on B
    • +
    • a supermajority on A's slot
    • +
    +
  4. +
  5. If the first case occurs, leader B's slot is filled with ticks. if the +second case occurs, A's slot is filled with ticks
  6. +
  7. Partition is resolved just like in the Small Partition +above
  8. +
+

Network Variables

+

A - name of a node

+

B - name of a node

+

K - number of nodes in the supermajority to whom leaders broadcast their +PoH hash for validation

+

M - number of nodes outside the supermajority to whom leaders broadcast their +PoH hash for validation

+

N - number of voting rounds for which a leader schedule is considered before +a new leader schedule is used

+

T - number of PoH ticks per leader slot (also voting round)

+

V - name of a node that will create virtual ticks

+

Z - number of hashes per PoH tick

+

Fullnode

+

Fullnode block diagrams

+

Pipelining

+

The fullnodes make extensive use of an optimization common in CPU design, +called pipelining. Pipelining is the right tool for the job when there's a +stream of input data that needs to be processed by a sequence of steps, and +there's different hardware responsible for each. The quintessential example is +using a washer and dryer to wash/dry/fold several loads of laundry. Washing +must occur before drying and drying before folding, but each of the three +operations is performed by a separate unit. To maximize efficiency, one creates +a pipeline of stages. We'll call the washer one stage, the dryer another, and +the folding process a third. To run the pipeline, one adds a second load of +laundry to the washer just after the first load is added to the dryer. +Likewise, the third load is added to the washer after the second is in the +dryer and the first is being folded. In this way, one can make progress on +three loads of laundry simultaneously. Given infinite loads, the pipeline will +consistently complete a load at the rate of the slowest stage in the pipeline.

+

Pipelining in the fullnode

+

The fullnode contains two pipelined processes, one used in leader mode called +the Tpu and one used in validator mode called the Tvu. In both cases, the +hardware being pipelined is the same, the network input, the GPU cards, the CPU +cores, writes to disk, and the network output. What it does with that hardware +is different. The Tpu exists to create ledger entries whereas the Tvu exists +to validate them.

+

The Transaction Processing Unit

+

Tpu block diagram

+

The Transaction Validation Unit

+

Tvu block diagram

+

Ncp

+

The Network Control Plane implements a gossip network between all nodes on in the cluster.

+

JsonRpcService

+

Avalanche replication

+

The Avalance explainer video is +a conceptual overview of how a Solana leader can continuously process a gigabit +of transaction data per second and then get that same data, after being +recorded on the ledger, out to multiple validators on a single gigabit +backplane.

+

In practice, we found that just one level of the Avalanche validator tree is +sufficient for at least 150 validators. We anticipate adding the second level +to solve one of two problems:

+
    +
  1. To transmit ledger segments to slower "replicator" nodes.
  2. +
  3. To scale up the number of validators nodes.
  4. +
+

Both problems justify the additional level, but you won't find it implemented +in the reference design just yet, because Solana's gossip implementation is +currently the bottleneck on the number of nodes per Solana cluster. That work +is being actively developed here:

+

Scalable Gossip

+

Storage

+

Background

+

At full capacity on a 1gbps network Solana would generate 4 petabytes of data +per year. If each fullnode was required to store the full ledger, the cost of +storage would discourage fullnode participation, thus centralizing the network +around those that could afford it. Solana aims to keep the cost of a fullnode +below $5,000 USD to maximize participation. To achieve that, the network needs +to minimize redundant storage while at the same time ensuring the validity and +availability of each copy.

+

To trust storage of ledger segments, Solana has replicators periodically +submit proofs to the network that the data was replicated. Each proof is called +a Proof of Replication. The basic idea of it is to encrypt a dataset with a +public symmetric key and then hash the encrypted dataset. Solana uses CBC +encryption. +To prevent a malicious replicator from deleting the data as soon as it's +hashed, a replicator is required hash random segments of the dataset. +Alternatively, Solana could require hashing the reverse of the encrypted data, +but random sampling is sufficient and much faster. Either solution ensures +that all the data is present during the generation of the proof and also +requires the validator to have the entirety of the encrypted data present for +verification of every proof of every identity. The space required to validate +is:

+

number_of_proofs * data_size

+

Optimization with PoH

+

Solana is not the only distribute systems project using Proof of Replication, +but it might be the most efficient implementation because of its ability to +synchronize nodes with its Proof of History. With PoH, Solana is able to record +a hash of the PoRep samples in the ledger. Thus the blocks stay in the exact +same order for every PoRep and verification can stream the data and verify all +the proofs in a single batch. This way Solana can verify multiple proofs +concurrently, each one on its own GPU core. With the current generation of +graphics cards our network can support up to 14,000 replication identities or +symmetric keys. The total space required for verification is:

+

2 CBC_blocks * number_of_identities

+

with core count of equal to (Number of Identities). A CBC block is expected to +be 1MB in size.

+

Network

+

Validators for PoRep are the same validators that are verifying transactions. +They have some stake that they have put up as collateral that ensures that +their work is honest. If you can prove that a validator verified a fake PoRep, +then the validator's stake is slashed.

+

Replicators are specialized light clients. They download a part of the ledger +and store it and provide proofs of storing the ledger. For each verified proof, +replicators are rewarded tokens from the mining pool.

+

Constraints

+

Solana's PoRep protocol instroduces the following constraints:

+
    +
  • At most 14,000 replication identities can be used, because that is how many GPU +cores are currently available to a computer costing under $5,000 USD.
  • +
  • Verification requires generating the CBC blocks. That requires space of 2 +blocks per identity, and 1 GPU core per identity for the same dataset. As +many identities at once are batched with as many proofs for those identities +verified concurrently for the same dataset.
  • +
+

Validation and Replication Protocol

+
    +
  1. The network sets a replication target number, let's say 1k. 1k PoRep +identities are created from signatures of a PoH hash. They are tied to a +specific PoH hash. It doesn't matter who creates them, or it could simply be +the last 1k validation signatures we saw for the ledger at that count. This is +maybe just the initial batch of identities, because we want to stagger identity +rotation.
  2. +
  3. Any client can use any of these identities to create PoRep proofs. +Replicator identities are the CBC encryption keys.
  4. +
  5. Periodically at a specific PoH count, a replicator that wants to create +PoRep proofs signs the PoH hash at that count. That signature is the seed +used to pick the block and identity to replicate. A block is 1TB of ledger.
  6. +
  7. Periodically at a specific PoH count, a replicator submits PoRep proofs for +their selected block. A signature of the PoH hash at that count is the seed +used to sample the 1TB encrypted block, and hash it. This is done faster than +it takes to encrypt the 1TB block with the original identity.
  8. +
  9. Replicators must submit some number of fake proofs, which they can prove to +be fake by providing the seed for the hash result.
  10. +
  11. Periodically at a specific PoH count, validators sign the hash and use the +signature to select the 1TB block that they need to validate. They batch all +the identities and proofs and submit approval for all the verified ones.
  12. +
  13. After #6, replicator client submit the proofs of fake proofs.
  14. +
+

For any random seed, Solana requires everyone to use a signature that is +derived from a PoH hash. Every node uses the same count so that the same PoH +hash is signed by every participant. The signatures are then each +cryptographically tied to the keypair, which prevents a leader from grinding on +the resulting value for more than 1 identity.

+

Key rotation is staggered. Once going, the next identity is generated by +hashing itself with a PoH hash.

+

Since there are many more client identities then encryption identities, the +reward is split amont multiple clients to prevent Sybil attacks from generating +many clients to acquire the same block of data. To remain BFT, the network +needs to avoid a single human entity from storing all the replications of a +single chunk of the ledger.

+

Solana's solution to this is to require clients to continue using the same +identity. If the first round is used to acquire the same block for many client +identities, the second round for the same client identities will require a +redistribution of the signatures, and therefore PoRep identities and blocks. +Thus to get a reward for storage, clients are not rewarded for storage of the +first block. The network rewards long-lived client identities more than new +ones.

+

The Solana SDK

+

Introduction

+

With the Solana runtime, we can execute on-chain programs concurrently, and +written in the client’s choice of programming language.

+

Client interactions with Solana

+

SDK tools

+

As shown in the diagram above an untrusted client, creates a program in the +language of their choice, (i.e. C/C++/Rust/Lua), and compiles it with LLVM to a +position independent shared object ELF, targeting BPF bytecode, and sends it to +the Solana cluster. Next, the client sends messages to the Solana cluster, +which target that program. The Solana runtime loads the previously submitted +ELF and passes it the client's message for interpretation.

+

Persistent Storage

+

Solana supports several kinds of persistent storage, called accounts:

+
    +
  1. Executable
  2. +
  3. Writable by a client
  4. +
  5. Writable by a program
  6. +
  7. Read-only
  8. +
+

All accounts are identified by public keys and may hold arbirary data. +When the client sends messages to programs, it requests access to storage +using those keys. The runtime loads the account data and passes it to the +program. The runtime also ensures accounts aren't written to if not owned +by the client or program. Any writes to read-only accounts are discarded +unless the write was to credit tokens. Any user may credit other accounts +tokens, regardless of account permission.

+

Runtime

+

The goal with the runtime is to have a general purpose execution environment +that is highly parallelizable. To achieve this goal the runtime forces each +Instruction to specify all of its memory dependencies up front, and therefore a +single Instruction cannot cause a dynamic memory allocation. An explicit +Instruction for memory allocation from the SystemProgram::CreateAccount is +the only way to allocate new memory in the engine. A Transaction may compose +multiple Instruction, including SystemProgram::CreateAccount, into a single +atomic sequence which allows for memory allocation to achieve a result that is +similar to dynamic allocation.

+

State

+

State is addressed by an Account which is at the moment simply the Pubkey. Our +goal is to eliminate memory allocation from within the program itself. Thus +the client of the program provides all the state that is necessary for the +program to execute in the transaction itself. The runtime interacts with the +program through an entry point with a well defined interface. The userdata +stored in an Account is an opaque type to the runtime, a Vec<u8>, the +contents of which the program code has full control over.

+

The Transaction structure specifies a list of Pubkey's and signatures for those +keys and a sequential list of instructions that will operate over the state's +associated with the account_keys. For the transaction to be committed all +the instructions must execute successfully, if any abort the whole transaction +fails to commit.

+

Account structure Accounts maintain token state as well as program specific

+

memory.

+

Transaction Engine

+

At its core, the engine looks up all the Pubkeys maps them to accounts and +routs them to the program_id entry point.

+

Execution

+

Transactions are batched and processed in a pipeline

+

Runtime pipeline

+

At the execute stage, the loaded pages have no data dependencies, so all the +programs can be executed in parallel.

+

The runtime enforces the following rules:

+
    +
  1. The program_id code is the only code that will modify the contents of +Account::userdata of Account's that have been assigned to it. This means +that upon assignment userdata vector is guaranteed to be 0.
  2. +
  3. Total balances on all the accounts is equal before and after execution of a +Transaction.
  4. +
  5. Balances of each of the accounts not assigned to program_id must be equal +to or greater after the Transaction than before the transaction.
  6. +
  7. All Instructions in the Transaction executed without a failure.
  8. +
+

Entry Point Execution of the program involves mapping the Program's public

+

key to an entry point which takes a pointer to the transaction, and an array of +loaded pages.

+

System Interface

+

The interface is best described by the Instruction::userdata that the +user encodes.

+
    +
  • CreateAccount - This allows the user to create and assign an Account to a +Program.
  • +
  • Assign - allows the user to assign an existing account to a Program.
  • +
  • Move - moves tokens between Accounts that are associated with +SystemProgram. This cannot be used to move tokens of other Accounts. +Programs need to implement their own version of Move.
  • +
+

Notes

+
    +
  1. There is no dynamic memory allocation. Client's need to call the +SystemProgram to create memory before passing it to another program. This +Instruction can be composed into a single Transaction with the call to the +program itself.
  2. +
  3. Runtime guarantees that when memory is assigned to the Program it is zero +initialized.
  4. +
  5. Runtime guarantees that Program's code is the only thing that can modify +memory that its assigned to
  6. +
  7. Runtime guarantees that the Program can only spend tokens that are in +Accounts that are assigned to it
  8. +
  9. Runtime guarantees the balances belonging to Accounts are balanced before +and after the transaction
  10. +
  11. Runtime guarantees that multiple instructions all executed successfully when +a transaction is committed.
  12. +
+

Future Work

+ +

Ledger format

+

Appendix

+

The following sections contain reference material you may find useful in your +Solana journey.

+

JSON RPC API

+

Solana nodes accept HTTP requests using the JSON-RPC 2.0 specification.

+

To interact with a Solana node inside a JavaScript application, use the solana-web3.js library, which gives a convenient interface for the RPC methods.

+

RPC HTTP Endpoint

+

Default port: 8899 +eg. http://localhost:8899, http://192.168.1.88:8899

+

RPC PubSub WebSocket Endpoint

+

Default port: 8900 +eg. ws://localhost:8900, http://192.168.1.88:8900

+

Methods

+ +

Request Formatting

+

To make a JSON-RPC request, send an HTTP POST request with a Content-Type: application/json header. The JSON request data should contain 4 fields:

+
    +
  • jsonrpc, set to "2.0"
  • +
  • id, a unique client-generated identifying integer
  • +
  • method, a string containing the method to be invoked
  • +
  • params, a JSON array of ordered parameter values
  • +
+

Example using curl:

+
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' 192.168.1.88:8899
+
+

The response output will be a JSON object with the following fields:

+
    +
  • jsonrpc, matching the request specification
  • +
  • id, matching the request identifier
  • +
  • result, requested data or success confirmation
  • +
+

Requests can be sent in batches by sending an array of JSON-RPC request objects as the data for a single POST.

+

Definitions

+
    +
  • Hash: A SHA-256 hash of a chunk of data.
  • +
  • Pubkey: The public key of a Ed25519 key-pair.
  • +
  • Signature: An Ed25519 signature of a chunk of data.
  • +
  • Transaction: A Solana instruction signed by a client key-pair.
  • +
+

JSON RPC API Reference

+

confirmTransaction

+

Returns a transaction receipt

+
Parameters:
+
    +
  • string - Signature of Transaction to confirm, as base-58 encoded string
  • +
+
Results:
+
    +
  • boolean - Transaction status, true if Transaction is confirmed
  • +
+
Example:
+
// Request
+curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"confirmTransaction", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
+
+// Result
+{"jsonrpc":"2.0","result":true,"id":1}
+
+
+

getBalance

+

Returns the balance of the account of provided Pubkey

+
Parameters:
+
    +
  • string - Pubkey of account to query, as base-58 encoded string
  • +
+
Results:
+
    +
  • integer - quantity, as a signed 64-bit integer
  • +
+
Example:
+
// Request
+curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' http://localhost:8899
+
+// Result
+{"jsonrpc":"2.0","result":0,"id":1}
+
+
+

getAccountInfo

+

Returns all information associated with the account of provided Pubkey

+
Parameters:
+
    +
  • string - Pubkey of account to query, as base-58 encoded string
  • +
+
Results:
+

The result field will be a JSON object with the following sub fields:

+
    +
  • tokens, number of tokens assigned to this account, as a signed 64-bit integer
  • +
  • owner, array of 32 bytes representing the program this account has been assigned to
  • +
  • userdata, array of bytes representing any userdata associated with the account
  • +
  • executable, boolean indicating if the account contains a program (and is strictly read-only)
  • +
  • loader, array of 32 bytes representing the loader for this program (if executable), otherwise all
  • +
+
Example:
+
// Request
+curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"]}' http://localhost:8899
+
+// Result
+{"jsonrpc":"2.0","result":{"executable":false,"loader":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"tokens":1,"userdata":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"id":1}
+
+
+

getLastId

+

Returns the last entry ID from the ledger

+
Parameters:
+

None

+
Results:
+
    +
  • string - the ID of last entry, a Hash as base-58 encoded string
  • +
+
Example:
+
// Request
+curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getLastId"}' http://localhost:8899
+
+// Result
+{"jsonrpc":"2.0","result":"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC","id":1}
+
+
+

getSignatureStatus

+

Returns the status of a given signature. This method is similar to +confirmTransaction but provides more resolution for error +events.

+
Parameters:
+
    +
  • string - Signature of Transaction to confirm, as base-58 encoded string
  • +
+
Results:
+
    +
  • string - Transaction status: +
      +
    • Confirmed - Transaction was successful
    • +
    • SignatureNotFound - Unknown transaction
    • +
    • ProgramRuntimeError - An error occurred in the program that processed this Transaction
    • +
    • AccountInUse - Another Transaction had a write lock one of the Accounts specified in this Transaction. The Transaction may succeed if retried
    • +
    • GenericFailure - Some other error occurred. Note: In the future new Transaction statuses may be added to this list. It's safe to assume that all new statuses will be more specific error conditions that previously presented as GenericFailure
    • +
    +
  • +
+
Example:
+
// Request
+curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatus", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
+
+// Result
+{"jsonrpc":"2.0","result":"SignatureNotFound","id":1}
+
+
+

getTransactionCount

+

Returns the current Transaction count from the ledger

+
Parameters:
+

None

+
Results:
+
    +
  • integer - count, as unsigned 64-bit integer
  • +
+
Example:
+
// Request
+curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://localhost:8899
+
+// Result
+{"jsonrpc":"2.0","result":268,"id":1}
+
+
+

requestAirdrop

+

Requests an airdrop of tokens to a Pubkey

+
Parameters:
+
    +
  • string - Pubkey of account to receive tokens, as base-58 encoded string
  • +
  • integer - token quantity, as a signed 64-bit integer
  • +
+
Results:
+
    +
  • string - Transaction Signature of airdrop, as base-58 encoded string
  • +
+
Example:
+
// Request
+curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"requestAirdrop", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri", 50]}' http://localhost:8899
+
+// Result
+{"jsonrpc":"2.0","result":"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW","id":1}
+
+
+

sendTransaction

+

Creates new transaction

+
Parameters:
+
    +
  • array - array of octets containing a fully-signed Transaction
  • +
+
Results:
+
    +
  • string - Transaction Signature, as base-58 encoded string
  • +
+
Example:
+
// Request
+curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"sendTransaction", "params":[[61, 98, 55, 49, 15, 187, 41, 215, 176, 49, 234, 229, 228, 77, 129, 221, 239, 88, 145, 227, 81, 158, 223, 123, 14, 229, 235, 247, 191, 115, 199, 71, 121, 17, 32, 67, 63, 209, 239, 160, 161, 2, 94, 105, 48, 159, 235, 235, 93, 98, 172, 97, 63, 197, 160, 164, 192, 20, 92, 111, 57, 145, 251, 6, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 13, 39, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 11, 12, 106, 49, 74, 226, 201, 16, 161, 192, 28, 84, 124, 97, 190, 201, 171, 186, 6, 18, 70, 142, 89, 185, 176, 154, 115, 61, 26, 163, 77, 1, 88, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]}' http://localhost:8899
+
+// Result
+{"jsonrpc":"2.0","result":"2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b","id":1}
+
+
+

Subscription Websocket

+

After connect to the RPC PubSub websocket at ws://<ADDRESS>/:

+
    +
  • Submit subscription requests to the websocket using the methods below
  • +
  • Multiple subscriptions may be active at once
  • +
+
+

accountSubscribe

+

Subscribe to an account to receive notifications when the userdata for a given account public key changes

+
Parameters:
+
    +
  • string - account Pubkey, as base-58 encoded string
  • +
+
Results:
+
    +
  • integer - Subscription id (needed to unsubscribe)
  • +
+
Example:
+
// Request
+{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"]}
+
+// Result
+{"jsonrpc": "2.0","result": 0,"id": 1}
+
+
Notification Format:
+
{"jsonrpc": "2.0","method": "accountNotification", "params": {"result": {"executable":false,"loader":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"tokens":1,"userdata":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"subscription":0}}
+
+
+

accountUnsubscribe

+

Unsubscribe from account userdata change notifications

+
Parameters:
+
    +
  • integer - id of account Subscription to cancel
  • +
+
Results:
+
    +
  • bool - unsubscribe success message
  • +
+
Example:
+
// Request
+{"jsonrpc":"2.0", "id":1, "method":"accountUnsubscribe", "params":[0]}
+
+// Result
+{"jsonrpc": "2.0","result": true,"id": 1}
+
+
+

signatureSubscribe

+

Subscribe to a transaction signature to receive notification when the transaction is confirmed +On signatureNotification, the subscription is automatically cancelled

+
Parameters:
+
    +
  • string - Transaction Signature, as base-58 encoded string
  • +
+
Results:
+
    +
  • integer - subscription id (needed to unsubscribe)
  • +
+
Example:
+
// Request
+{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b"]}
+
+// Result
+{"jsonrpc": "2.0","result": 0,"id": 1}
+
+
Notification Format:
+
{"jsonrpc": "2.0","method": "signatureNotification", "params": {"result": "Confirmed","subscription":0}}
+
+
+

signatureUnsubscribe

+

Unsubscribe from account userdata change notifications

+
Parameters:
+
    +
  • integer - id of account subscription to cancel
  • +
+
Results:
+
    +
  • bool - unsubscribe success message
  • +
+
Example:
+
// Request
+{"jsonrpc":"2.0", "id":1, "method":"signatureUnsubscribe", "params":[0]}
+
+// Result
+{"jsonrpc": "2.0","result": true,"id": 1}
+
+

solana-wallet CLI

+

The solana crate is distributed with a command-line interface tool

+

Examples

+

Get Pubkey

+
// Command
+$ solana-wallet address
+
+// Return
+<PUBKEY>
+
+

Airdrop Tokens

+
// Command
+$ solana-wallet airdrop 123
+
+// Return
+"Your balance is: 123"
+
+

Get Balance

+
// Command
+$ solana-wallet balance
+
+// Return
+"Your balance is: 123"
+
+

Confirm Transaction

+
// Command
+$ solana-wallet confirm <TX_SIGNATURE>
+
+// Return
+"Confirmed" / "Not found"
+
+

Deploy program

+
// Command
+$ solana-wallet deploy <PATH>
+
+// Return
+<PROGRAM_ID>
+
+

Unconditional Immediate Transfer

+
// Command
+$ solana-wallet pay <PUBKEY> 123
+
+// Return
+<TX_SIGNATURE>
+
+

Post-Dated Transfer

+
// Command
+$ solana-wallet pay <PUBKEY> 123 \
+    --after 2018-12-24T23:59:00 --require-timestamp-from <PUBKEY>
+
+// Return
+{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
+
+

require-timestamp-from is optional. If not provided, the transaction will expect a timestamp signed by this wallet's secret key

+

Authorized Transfer

+

A third party must send a signature to unlock the tokens.

+
// Command
+$ solana-wallet pay <PUBKEY> 123 \
+    --require-signature-from <PUBKEY>
+
+// Return
+{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
+
+

Post-Dated and Authorized Transfer

+
// Command
+$ solana-wallet pay <PUBKEY> 123 \
+    --after 2018-12-24T23:59 --require-timestamp-from <PUBKEY> \
+    --require-signature-from <PUBKEY>
+
+// Return
+{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
+
+

Multiple Witnesses

+
// Command
+$ solana-wallet pay <PUBKEY> 123 \
+    --require-signature-from <PUBKEY> \
+    --require-signature-from <PUBKEY>
+
+// Return
+{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
+
+

Cancelable Transfer

+
// Command
+$ solana-wallet pay <PUBKEY> 123 \
+    --require-signature-from <PUBKEY> \
+    --cancelable
+
+// Return
+{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
+
+

Cancel Transfer

+
// Command
+$ solana-wallet cancel <PROCESS_ID>
+
+// Return
+<TX_SIGNATURE>
+
+

Send Signature

+
// Command
+$ solana-wallet send-signature <PUBKEY> <PROCESS_ID>
+
+// Return
+<TX_SIGNATURE>
+
+

Indicate Elapsed Time

+

Use the current system time:

+
// Command
+$ solana-wallet send-timestamp <PUBKEY> <PROCESS_ID>
+
+// Return
+<TX_SIGNATURE>
+
+

Or specify some other arbitrary timestamp:

+
// Command
+$ solana-wallet send-timestamp <PUBKEY> <PROCESS_ID> --date 2018-12-24T23:59:00
+
+// Return
+<TX_SIGNATURE>
+
+

Usage

+
solana-wallet 0.11.0
+
+USAGE:
+    solana-wallet [OPTIONS] [SUBCOMMAND]
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+OPTIONS:
+    -k, --keypair <PATH>         /path/to/id.json
+    -n, --network <HOST:PORT>    Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001
+        --proxy <URL>            Address of TLS proxy
+        --port <NUM>             Optional rpc-port configuration to connect to non-default nodes
+        --timeout <SECS>         Max seconds to wait to get necessary gossip from the network
+
+SUBCOMMANDS:
+    address                  Get your public key
+    airdrop                  Request a batch of tokens
+    balance                  Get your balance
+    cancel                   Cancel a transfer
+    confirm                  Confirm transaction by signature
+    deploy                   Deploy a program
+    get-transaction-count    Get current transaction count
+    help                     Prints this message or the help of the given subcommand(s)
+    pay                      Send a payment
+    send-signature           Send a signature to authorize a transfer
+    send-timestamp           Send a timestamp to unlock a transfer
+
+
solana-wallet-address 
+Get your public key
+
+USAGE:
+    solana-wallet address
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+
solana-wallet-airdrop 
+Request a batch of tokens
+
+USAGE:
+    solana-wallet airdrop <NUM>
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+ARGS:
+    <NUM>    The number of tokens to request
+
+
solana-wallet-balance 
+Get your balance
+
+USAGE:
+    solana-wallet balance
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+
solana-wallet-cancel 
+Cancel a transfer
+
+USAGE:
+    solana-wallet cancel <PROCESS_ID>
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+ARGS:
+    <PROCESS_ID>    The process id of the transfer to cancel
+
+
solana-wallet-confirm 
+Confirm transaction by signature
+
+USAGE:
+    solana-wallet confirm <SIGNATURE>
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+ARGS:
+    <SIGNATURE>    The transaction signature to confirm
+
+
solana-wallet-deploy 
+Deploy a program
+
+USAGE:
+    solana-wallet deploy <PATH>
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+ARGS:
+    <PATH>    /path/to/program.o
+
+
solana-wallet-get-transaction-count 
+Get current transaction count
+
+USAGE:
+    solana-wallet get-transaction-count
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+
solana-wallet-pay 
+Send a payment
+
+USAGE:
+    solana-wallet pay [FLAGS] [OPTIONS] <PUBKEY> <NUM>
+
+FLAGS:
+        --cancelable    
+    -h, --help          Prints help information
+    -V, --version       Prints version information
+
+OPTIONS:
+        --after <DATETIME>                      A timestamp after which transaction will execute
+        --require-timestamp-from <PUBKEY>       Require timestamp from this third party
+        --require-signature-from <PUBKEY>...    Any third party signatures required to unlock the tokens
+
+ARGS:
+    <PUBKEY>    The pubkey of recipient
+    <NUM>       The number of tokens to send
+
+
solana-wallet-send-signature 
+Send a signature to authorize a transfer
+
+USAGE:
+    solana-wallet send-signature <PUBKEY> <PROCESS_ID>
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+ARGS:
+    <PUBKEY>        The pubkey of recipient
+    <PROCESS_ID>    The process id of the transfer to authorize
+
+
solana-wallet-send-timestamp 
+Send a timestamp to unlock a transfer
+
+USAGE:
+    solana-wallet send-timestamp [OPTIONS] <PUBKEY> <PROCESS_ID>
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+OPTIONS:
+        --date <DATETIME>    Optional arbitrary timestamp to apply
+
+ARGS:
+    <PUBKEY>        The pubkey of recipient
+    <PROCESS_ID>    The process id of the transfer to unlock
+
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/programs.html b/book/programs.html new file mode 100644 index 00000000000000..7beec11f5e749a --- /dev/null +++ b/book/programs.html @@ -0,0 +1,226 @@ + + + + + + On-chain programs - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

The Solana SDK

+

Introduction

+

With the Solana runtime, we can execute on-chain programs concurrently, and +written in the client’s choice of programming language.

+

Client interactions with Solana

+

SDK tools

+

As shown in the diagram above an untrusted client, creates a program in the +language of their choice, (i.e. C/C++/Rust/Lua), and compiles it with LLVM to a +position independent shared object ELF, targeting BPF bytecode, and sends it to +the Solana cluster. Next, the client sends messages to the Solana cluster, +which target that program. The Solana runtime loads the previously submitted +ELF and passes it the client's message for interpretation.

+

Persistent Storage

+

Solana supports several kinds of persistent storage, called accounts:

+
    +
  1. Executable
  2. +
  3. Writable by a client
  4. +
  5. Writable by a program
  6. +
  7. Read-only
  8. +
+

All accounts are identified by public keys and may hold arbirary data. +When the client sends messages to programs, it requests access to storage +using those keys. The runtime loads the account data and passes it to the +program. The runtime also ensures accounts aren't written to if not owned +by the client or program. Any writes to read-only accounts are discarded +unless the write was to credit tokens. Any user may credit other accounts +tokens, regardless of account permission.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/recvmmsg.rs b/book/recvmmsg.rs new file mode 100644 index 00000000000000..ce6f89a3790e5d --- /dev/null +++ b/book/recvmmsg.rs @@ -0,0 +1,184 @@ +//! The `recvmmsg` module provides recvmmsg() API implementation + +use packet::Packet; +use std::cmp; +use std::io; +use std::net::UdpSocket; + +pub const NUM_RCVMMSGS: usize = 16; + +#[cfg(not(target_os = "linux"))] +pub fn recv_mmsg(socket: &UdpSocket, packets: &mut [Packet]) -> io::Result { + let mut i = 0; + socket.set_nonblocking(false)?; + let count = cmp::min(NUM_RCVMMSGS, packets.len()); + for p in packets.iter_mut().take(count) { + p.meta.size = 0; + match socket.recv_from(&mut p.data) { + Err(_) if i > 0 => { + break; + } + Err(e) => { + return Err(e); + } + Ok((nrecv, from)) => { + p.meta.size = nrecv; + p.meta.set_addr(&from); + if i == 0 { + socket.set_nonblocking(true)?; + } + } + } + i += 1; + } + Ok(i) +} + +#[cfg(target_os = "linux")] +pub fn recv_mmsg(sock: &UdpSocket, packets: &mut [Packet]) -> io::Result { + use libc::{ + c_void, iovec, mmsghdr, recvmmsg, sockaddr_in, socklen_t, time_t, timespec, MSG_WAITFORONE, + }; + use nix::sys::socket::InetAddr; + use std::mem; + use std::os::unix::io::AsRawFd; + + let mut hdrs: [mmsghdr; NUM_RCVMMSGS] = unsafe { mem::zeroed() }; + let mut iovs: [iovec; NUM_RCVMMSGS] = unsafe { mem::zeroed() }; + let mut addr: [sockaddr_in; NUM_RCVMMSGS] = unsafe { mem::zeroed() }; + let addrlen = mem::size_of_val(&addr) as socklen_t; + + let sock_fd = sock.as_raw_fd(); + + let count = cmp::min(iovs.len(), packets.len()); + + for i in 0..count { + iovs[i].iov_base = packets[i].data.as_mut_ptr() as *mut c_void; + iovs[i].iov_len = packets[i].data.len(); + + hdrs[i].msg_hdr.msg_name = &mut addr[i] as *mut _ as *mut _; + hdrs[i].msg_hdr.msg_namelen = addrlen; + hdrs[i].msg_hdr.msg_iov = &mut iovs[i]; + hdrs[i].msg_hdr.msg_iovlen = 1; + } + let mut ts = timespec { + tv_sec: 1 as time_t, + tv_nsec: 0, + }; + + let npkts = + match unsafe { recvmmsg(sock_fd, &mut hdrs[0], count as u32, MSG_WAITFORONE, &mut ts) } { + -1 => return Err(io::Error::last_os_error()), + n => { + for i in 0..n as usize { + let mut p = &mut packets[i]; + p.meta.size = hdrs[i].msg_len as usize; + let inet_addr = InetAddr::V4(addr[i]); + p.meta.set_addr(&inet_addr.to_std()); + } + n as usize + } + }; + + Ok(npkts) +} + +#[cfg(test)] +mod tests { + use packet::PACKET_DATA_SIZE; + use recvmmsg::*; + + #[test] + pub fn test_recv_mmsg_one_iter() { + let reader = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let addr = reader.local_addr().unwrap(); + let sender = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let saddr = sender.local_addr().unwrap(); + let sent = NUM_RCVMMSGS - 1; + for _ in 0..sent { + let data = [0; PACKET_DATA_SIZE]; + sender.send_to(&data[..], &addr).unwrap(); + } + + let mut packets = vec![Packet::default(); NUM_RCVMMSGS]; + let recv = recv_mmsg(&reader, &mut packets[..]).unwrap(); + assert_eq!(sent, recv); + for i in 0..recv { + assert_eq!(packets[i].meta.size, PACKET_DATA_SIZE); + assert_eq!(packets[i].meta.addr(), saddr); + } + } + + #[test] + pub fn test_recv_mmsg_multi_iter() { + let reader = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let addr = reader.local_addr().unwrap(); + let sender = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let saddr = sender.local_addr().unwrap(); + let sent = NUM_RCVMMSGS + 10; + for _ in 0..sent { + let data = [0; PACKET_DATA_SIZE]; + sender.send_to(&data[..], &addr).unwrap(); + } + + let mut packets = vec![Packet::default(); NUM_RCVMMSGS * 2]; + let recv = recv_mmsg(&reader, &mut packets[..]).unwrap(); + assert_eq!(NUM_RCVMMSGS, recv); + for i in 0..recv { + assert_eq!(packets[i].meta.size, PACKET_DATA_SIZE); + assert_eq!(packets[i].meta.addr(), saddr); + } + + let recv = recv_mmsg(&reader, &mut packets[..]).unwrap(); + assert_eq!(sent - NUM_RCVMMSGS, recv); + for i in 0..recv { + assert_eq!(packets[i].meta.size, PACKET_DATA_SIZE); + assert_eq!(packets[i].meta.addr(), saddr); + } + } + + #[test] + pub fn test_recv_mmsg_multi_addrs() { + let reader = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let addr = reader.local_addr().unwrap(); + + let sender1 = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let saddr1 = sender1.local_addr().unwrap(); + let sent1 = NUM_RCVMMSGS - 1; + + let sender2 = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let saddr2 = sender2.local_addr().unwrap(); + let sent2 = NUM_RCVMMSGS + 1; + + for _ in 0..sent1 { + let data = [0; PACKET_DATA_SIZE]; + sender1.send_to(&data[..], &addr).unwrap(); + } + + for _ in 0..sent2 { + let data = [0; PACKET_DATA_SIZE]; + sender2.send_to(&data[..], &addr).unwrap(); + } + + let mut packets = vec![Packet::default(); NUM_RCVMMSGS * 2]; + + let recv = recv_mmsg(&reader, &mut packets[..]).unwrap(); + assert_eq!(NUM_RCVMMSGS, recv); + for i in 0..sent1 { + assert_eq!(packets[i].meta.size, PACKET_DATA_SIZE); + assert_eq!(packets[i].meta.addr(), saddr1); + } + + for i in sent1..recv { + assert_eq!(packets[i].meta.size, PACKET_DATA_SIZE); + assert_eq!(packets[i].meta.addr(), saddr2); + } + + let recv = recv_mmsg(&reader, &mut packets[..]).unwrap(); + assert_eq!(sent1 + sent2 - NUM_RCVMMSGS, recv); + for i in 0..recv { + assert_eq!(packets[i].meta.size, PACKET_DATA_SIZE); + assert_eq!(packets[i].meta.addr(), saddr2); + } + } +} diff --git a/book/replicate_stage.rs b/book/replicate_stage.rs new file mode 100644 index 00000000000000..bccf44eef46c82 --- /dev/null +++ b/book/replicate_stage.rs @@ -0,0 +1,683 @@ +//! The `replicate_stage` replicates transactions broadcast by the leader. + +use bank::Bank; +use cluster_info::ClusterInfo; +use counter::Counter; +use entry::{EntryReceiver, EntrySender}; +use solana_sdk::hash::Hash; + +use ledger::Block; +use log::Level; +use packet::BlobError; +use result::{Error, Result}; +use service::Service; +use signature::{Keypair, KeypairUtil}; +use solana_metrics::{influxdb, submit}; +use solana_sdk::timing::duration_as_ms; +use std::net::UdpSocket; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::mpsc::channel; +use std::sync::mpsc::RecvTimeoutError; +use std::sync::{Arc, RwLock}; +use std::thread::{self, Builder, JoinHandle}; +use std::time::Duration; +use std::time::Instant; +use streamer::{responder, BlobSender}; +use vote_stage::send_validator_vote; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum ReplicateStageReturnType { + LeaderRotation(u64, u64, Hash), +} + +// Implement a destructor for the ReplicateStage thread to signal it exited +// even on panics +struct Finalizer { + exit_sender: Arc, +} + +impl Finalizer { + fn new(exit_sender: Arc) -> Self { + Finalizer { exit_sender } + } +} +// Implement a destructor for Finalizer. +impl Drop for Finalizer { + fn drop(&mut self) { + self.exit_sender.clone().store(true, Ordering::Relaxed); + } +} + +pub struct ReplicateStage { + t_responder: JoinHandle<()>, + t_replicate: JoinHandle>, +} + +impl ReplicateStage { + /// Process entry blobs, already in order + fn replicate_requests( + bank: &Arc, + cluster_info: &Arc>, + window_receiver: &EntryReceiver, + keypair: &Arc, + vote_account_keypair: &Arc, + vote_blob_sender: Option<&BlobSender>, + ledger_entry_sender: &EntrySender, + entry_height: &mut u64, + last_entry_id: &mut Hash, + ) -> Result<()> { + let timer = Duration::new(1, 0); + //coalesce all the available entries into a single vote + let mut entries = window_receiver.recv_timeout(timer)?; + while let Ok(mut more) = window_receiver.try_recv() { + entries.append(&mut more); + } + + submit( + influxdb::Point::new("replicate-stage") + .add_field("count", influxdb::Value::Integer(entries.len() as i64)) + .to_owned(), + ); + + let mut res = Ok(()); + let mut num_entries_to_write = entries.len(); + let now = Instant::now(); + if !entries.as_slice().verify(last_entry_id) { + inc_new_counter_info!("replicate_stage-verify-fail", entries.len()); + return Err(Error::BlobError(BlobError::VerificationFailed)); + } + inc_new_counter_info!( + "replicate_stage-verify-duration", + duration_as_ms(&now.elapsed()) as usize + ); + let (current_leader, _) = bank + .get_current_leader() + .expect("Scheduled leader id should never be unknown while processing entries"); + for (i, entry) in entries.iter().enumerate() { + res = bank.process_entry(&entry); + let my_id = keypair.pubkey(); + let (scheduled_leader, _) = bank + .get_current_leader() + .expect("Scheduled leader id should never be unknown while processing entries"); + + // TODO: Remove this soon once we boot the leader from ClusterInfo + if scheduled_leader != current_leader { + cluster_info.write().unwrap().set_leader(scheduled_leader); + } + if my_id == scheduled_leader { + num_entries_to_write = i + 1; + break; + } + + if res.is_err() { + // TODO: This will return early from the first entry that has an erroneous + // transaction, instead of processing the rest of the entries in the vector + // of received entries. This is in line with previous behavior when + // bank.process_entries() was used to process the entries, but doesn't solve the + // issue that the bank state was still changed, leading to inconsistencies with the + // leader as the leader currently should not be publishing erroneous transactions + break; + } + } + + // If leader rotation happened, only write the entries up to leader rotation. + entries.truncate(num_entries_to_write); + *last_entry_id = entries + .last() + .expect("Entries cannot be empty at this point") + .id; + + inc_new_counter_info!( + "replicate-transactions", + entries.iter().map(|x| x.transactions.len()).sum() + ); + + let entries_len = entries.len() as u64; + // TODO: move this to another stage? + // TODO: In line with previous behavior, this will write all the entries even if + // an error occurred processing one of the entries (causing the rest of the entries to + // not be processed). + if entries_len != 0 { + ledger_entry_sender.send(entries)?; + } + + *entry_height += entries_len; + res?; + if let Some(sender) = vote_blob_sender { + send_validator_vote(bank, vote_account_keypair, &cluster_info, sender)?; + } + + Ok(()) + } + + pub fn new( + keypair: Arc, + vote_account_keypair: Arc, + bank: Arc, + cluster_info: Arc>, + window_receiver: EntryReceiver, + exit: Arc, + entry_height: u64, + last_entry_id: Hash, + ) -> (Self, EntryReceiver) { + let (vote_blob_sender, vote_blob_receiver) = channel(); + let (ledger_entry_sender, ledger_entry_receiver) = channel(); + let send = UdpSocket::bind("0.0.0.0:0").expect("bind"); + let t_responder = responder("replicate_stage", Arc::new(send), vote_blob_receiver); + + let keypair = Arc::new(keypair); + + let t_replicate = Builder::new() + .name("solana-replicate-stage".to_string()) + .spawn(move || { + let _exit = Finalizer::new(exit); + let now = Instant::now(); + let mut next_vote_secs = 1; + let mut entry_height_ = entry_height; + let mut last_entry_id = last_entry_id; + loop { + let (leader_id, _) = bank + .get_current_leader() + .expect("Scheduled leader id should never be unknown at this point"); + + if leader_id == keypair.pubkey() { + return Some(ReplicateStageReturnType::LeaderRotation( + bank.tick_height(), + entry_height_, + // We should never start the TPU / this stage on an exact entry that causes leader + // rotation (Fullnode should automatically transition on startup if it detects + // are no longer a validator. Hence we can assume that some entry must have + // triggered leader rotation + last_entry_id, + )); + } + + // Only vote once a second. + let vote_sender = if now.elapsed().as_secs() > next_vote_secs { + next_vote_secs += 1; + Some(&vote_blob_sender) + } else { + None + }; + + match Self::replicate_requests( + &bank, + &cluster_info, + &window_receiver, + &keypair, + &vote_account_keypair, + vote_sender, + &ledger_entry_sender, + &mut entry_height_, + &mut last_entry_id, + ) { + Err(Error::RecvTimeoutError(RecvTimeoutError::Disconnected)) => break, + Err(Error::RecvTimeoutError(RecvTimeoutError::Timeout)) => (), + Err(e) => error!("{:?}", e), + Ok(()) => (), + } + } + + None + }).unwrap(); + + ( + ReplicateStage { + t_responder, + t_replicate, + }, + ledger_entry_receiver, + ) + } +} + +impl Service for ReplicateStage { + type JoinReturnType = Option; + + fn join(self) -> thread::Result> { + self.t_responder.join()?; + self.t_replicate.join() + } +} + +#[cfg(test)] +mod test { + use bank::Bank; + use cluster_info::{ClusterInfo, Node}; + use entry::Entry; + use fullnode::Fullnode; + use leader_scheduler::{make_active_set_entries, LeaderScheduler, LeaderSchedulerConfig}; + use ledger::{create_ticks, create_tmp_sample_ledger, LedgerWriter}; + use logger; + use packet::BlobError; + use replicate_stage::{ReplicateStage, ReplicateStageReturnType}; + use result::Error; + use service::Service; + use signature::{Keypair, KeypairUtil}; + use solana_sdk::hash::Hash; + use std::fs::remove_dir_all; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::mpsc::channel; + use std::sync::{Arc, RwLock}; + use vote_stage::{send_validator_vote, VoteError}; + + #[test] + pub fn test_replicate_stage_leader_rotation_exit() { + logger::setup(); + + // Set up dummy node to host a ReplicateStage + let my_keypair = Keypair::new(); + let my_id = my_keypair.pubkey(); + let my_node = Node::new_localhost_with_pubkey(my_id); + let cluster_info_me = ClusterInfo::new(my_node.info.clone()); + + // Create keypair for the old leader + let old_leader_id = Keypair::new().pubkey(); + + // Create a ledger + let num_ending_ticks = 1; + let (mint, my_ledger_path, genesis_entries) = create_tmp_sample_ledger( + "test_replicate_stage_leader_rotation_exit", + 10_000, + num_ending_ticks, + old_leader_id, + 500, + ); + let mut last_id = genesis_entries + .last() + .expect("expected at least one genesis entry") + .id; + + // Write two entries to the ledger so that the validator is in the active set: + // 1) Give the validator a nonzero number of tokens 2) A vote from the validator . + // This will cause leader rotation after the bootstrap height + let mut ledger_writer = LedgerWriter::open(&my_ledger_path, false).unwrap(); + let (active_set_entries, vote_account_keypair) = + make_active_set_entries(&my_keypair, &mint.keypair(), &last_id, &last_id, 0); + last_id = active_set_entries.last().unwrap().id; + let initial_tick_height = genesis_entries + .iter() + .skip(2) + .fold(0, |tick_count, entry| tick_count + entry.is_tick() as u64); + let active_set_entries_len = active_set_entries.len() as u64; + let initial_non_tick_height = genesis_entries.len() as u64 - initial_tick_height; + let initial_entry_len = genesis_entries.len() as u64 + active_set_entries_len; + ledger_writer.write_entries(&active_set_entries).unwrap(); + + // Set up the LeaderScheduler so that this this node becomes the leader at + // bootstrap_height = num_bootstrap_slots * leader_rotation_interval + let leader_rotation_interval = 10; + let num_bootstrap_slots = 2; + let bootstrap_height = num_bootstrap_slots * leader_rotation_interval; + let leader_scheduler_config = LeaderSchedulerConfig::new( + Some(bootstrap_height), + Some(leader_rotation_interval), + Some(leader_rotation_interval * 2), + Some(bootstrap_height), + ); + + let leader_scheduler = + Arc::new(RwLock::new(LeaderScheduler::new(&leader_scheduler_config))); + + // Set up the bank + let (bank, _, last_entry_id) = + Fullnode::new_bank_from_ledger(&my_ledger_path, leader_scheduler); + + // Set up the replicate stage + let (entry_sender, entry_receiver) = channel(); + let exit = Arc::new(AtomicBool::new(false)); + let (replicate_stage, ledger_writer_recv) = ReplicateStage::new( + Arc::new(my_keypair), + Arc::new(vote_account_keypair), + Arc::new(bank), + Arc::new(RwLock::new(cluster_info_me)), + entry_receiver, + exit.clone(), + initial_entry_len, + last_entry_id, + ); + + // Send enough ticks to trigger leader rotation + let extra_entries = leader_rotation_interval; + let total_entries_to_send = (bootstrap_height + extra_entries) as usize; + let num_hashes = 1; + let mut entries_to_send = vec![]; + + while entries_to_send.len() < total_entries_to_send { + let entry = Entry::new(&mut last_id, num_hashes, vec![]); + last_id = entry.id; + entries_to_send.push(entry); + } + + assert!((num_ending_ticks as u64) < bootstrap_height); + + // Add on the only entries that weren't ticks to the bootstrap height to get the + // total expected entry length + let leader_rotation_index = (bootstrap_height - initial_tick_height - 1) as usize; + let expected_entry_height = + bootstrap_height + initial_non_tick_height + active_set_entries_len; + let expected_last_id = entries_to_send[leader_rotation_index].id; + entry_sender.send(entries_to_send.clone()).unwrap(); + + // Wait for replicate_stage to exit and check return value is correct + assert_eq!( + Some(ReplicateStageReturnType::LeaderRotation( + bootstrap_height, + expected_entry_height, + expected_last_id, + )), + replicate_stage.join().expect("replicate stage join") + ); + + // Check that the entries on the ledger writer channel are correct + let received_ticks = ledger_writer_recv + .recv() + .expect("Expected to recieve an entry on the ledger writer receiver"); + + assert_eq!( + &received_ticks[..], + &entries_to_send[..leader_rotation_index + 1] + ); + + assert_eq!(exit.load(Ordering::Relaxed), true); + + let _ignored = remove_dir_all(&my_ledger_path); + } + + #[test] + fn test_vote_error_replicate_stage_correctness() { + // Set up dummy node to host a ReplicateStage + let my_keypair = Keypair::new(); + let my_id = my_keypair.pubkey(); + let my_node = Node::new_localhost_with_pubkey(my_id); + + // Create keypair for the leader + let leader_id = Keypair::new().pubkey(); + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::default())); + + let num_ending_ticks = 0; + let (_, my_ledger_path, genesis_entries) = create_tmp_sample_ledger( + "test_vote_error_replicate_stage_correctness", + 10_000, + num_ending_ticks, + leader_id, + 500, + ); + + let initial_entry_len = genesis_entries.len(); + + // Set up the bank + let (bank, _, last_entry_id) = + Fullnode::new_bank_from_ledger(&my_ledger_path, leader_scheduler); + + // Set up the cluster info + let cluster_info_me = Arc::new(RwLock::new(ClusterInfo::new(my_node.info.clone()))); + + // Set up the replicate stage + let vote_account_keypair = Arc::new(Keypair::new()); + let bank = Arc::new(bank); + let (entry_sender, entry_receiver) = channel(); + let exit = Arc::new(AtomicBool::new(false)); + let (replicate_stage, ledger_writer_recv) = ReplicateStage::new( + Arc::new(my_keypair), + vote_account_keypair.clone(), + bank.clone(), + cluster_info_me.clone(), + entry_receiver, + exit.clone(), + initial_entry_len as u64, + last_entry_id, + ); + + // Vote sender should error because no leader contact info is found in the + // ClusterInfo + let (mock_sender, _mock_receiver) = channel(); + let vote_err = + send_validator_vote(&bank, &vote_account_keypair, &cluster_info_me, &mock_sender); + if let Err(Error::VoteError(vote_error)) = vote_err { + assert_eq!(vote_error, VoteError::LeaderInfoNotFound); + } else { + panic!("Expected validator vote to fail with LeaderInfoNotFound"); + } + + // Send ReplicateStage an entry, should see it on the ledger writer receiver + let next_tick = create_ticks( + 1, + genesis_entries + .last() + .expect("Expected nonzero number of entries in genesis") + .id, + ); + entry_sender + .send(next_tick.clone()) + .expect("Error sending entry to ReplicateStage"); + let received_tick = ledger_writer_recv + .recv() + .expect("Expected to recieve an entry on the ledger writer receiver"); + + assert_eq!(next_tick, received_tick); + drop(entry_sender); + replicate_stage + .join() + .expect("Expect successful ReplicateStage exit"); + let _ignored = remove_dir_all(&my_ledger_path); + } + + #[test] + fn test_vote_error_replicate_stage_leader_rotation() { + // Set up dummy node to host a ReplicateStage + let my_keypair = Keypair::new(); + let my_id = my_keypair.pubkey(); + let my_node = Node::new_localhost_with_pubkey(my_id); + + // Create keypair for the leader + let leader_id = Keypair::new().pubkey(); + + // Create the ledger + let (mint, my_ledger_path, genesis_entries) = create_tmp_sample_ledger( + "test_vote_error_replicate_stage_leader_rotation", + 10_000, + 0, + leader_id, + 500, + ); + + let mut last_id = genesis_entries + .last() + .expect("expected at least one genesis entry") + .id; + + // Write two entries to the ledger so that the validator is in the active set: + // 1) Give the validator a nonzero number of tokens 2) A vote from the validator. + // This will cause leader rotation after the bootstrap height + let mut ledger_writer = LedgerWriter::open(&my_ledger_path, false).unwrap(); + let (active_set_entries, vote_account_keypair) = + make_active_set_entries(&my_keypair, &mint.keypair(), &last_id, &last_id, 0); + last_id = active_set_entries.last().unwrap().id; + let initial_tick_height = genesis_entries + .iter() + .skip(2) + .fold(0, |tick_count, entry| tick_count + entry.is_tick() as u64); + let active_set_entries_len = active_set_entries.len() as u64; + let initial_non_tick_height = genesis_entries.len() as u64 - initial_tick_height; + let initial_entry_len = genesis_entries.len() as u64 + active_set_entries_len; + ledger_writer.write_entries(&active_set_entries).unwrap(); + + // Set up the LeaderScheduler so that this this node becomes the leader at + // bootstrap_height = num_bootstrap_slots * leader_rotation_interval + let leader_rotation_interval = 10; + let num_bootstrap_slots = 2; + let bootstrap_height = num_bootstrap_slots * leader_rotation_interval; + let leader_scheduler_config = LeaderSchedulerConfig::new( + Some(bootstrap_height), + Some(leader_rotation_interval), + Some(leader_rotation_interval * 2), + Some(bootstrap_height), + ); + + let leader_scheduler = + Arc::new(RwLock::new(LeaderScheduler::new(&leader_scheduler_config))); + + // Set up the bank + let (bank, _, last_entry_id) = + Fullnode::new_bank_from_ledger(&my_ledger_path, leader_scheduler); + + // Set up the cluster info + let cluster_info_me = Arc::new(RwLock::new(ClusterInfo::new(my_node.info.clone()))); + + // Set up the replicate stage + let vote_account_keypair = Arc::new(vote_account_keypair); + let bank = Arc::new(bank); + let (entry_sender, entry_receiver) = channel(); + let exit = Arc::new(AtomicBool::new(false)); + let (replicate_stage, ledger_writer_recv) = ReplicateStage::new( + Arc::new(my_keypair), + vote_account_keypair.clone(), + bank.clone(), + cluster_info_me.clone(), + entry_receiver, + exit.clone(), + initial_entry_len as u64, + last_entry_id, + ); + + // Vote sender should error because no leader contact info is found in the + // ClusterInfo + let (mock_sender, _mock_receiver) = channel(); + let vote_err = + send_validator_vote(&bank, &vote_account_keypair, &cluster_info_me, &mock_sender); + if let Err(Error::VoteError(vote_error)) = vote_err { + assert_eq!(vote_error, VoteError::LeaderInfoNotFound); + } else { + panic!("Expected validator vote to fail with LeaderInfoNotFound"); + } + + // Send enough ticks to trigger leader rotation + let total_entries_to_send = (bootstrap_height - initial_tick_height) as usize; + let num_hashes = 1; + + // Add on the only entries that weren't ticks to the bootstrap height to get the + // total expected entry length + let expected_entry_height = + bootstrap_height + initial_non_tick_height + active_set_entries_len; + let leader_rotation_index = (bootstrap_height - initial_tick_height - 1) as usize; + let mut expected_last_id = Hash::default(); + for i in 0..total_entries_to_send { + let entry = Entry::new(&mut last_id, num_hashes, vec![]); + last_id = entry.id; + entry_sender + .send(vec![entry.clone()]) + .expect("Expected to be able to send entry to ReplicateStage"); + // Check that the entries on the ledger writer channel are correct + let received_entry = ledger_writer_recv + .recv() + .expect("Expected to recieve an entry on the ledger writer receiver"); + assert_eq!(received_entry[0], entry); + + if i == leader_rotation_index { + expected_last_id = entry.id; + } + } + + assert_ne!(expected_last_id, Hash::default()); + + // Wait for replicate_stage to exit and check return value is correct + assert_eq!( + Some(ReplicateStageReturnType::LeaderRotation( + bootstrap_height, + expected_entry_height, + expected_last_id, + )), + replicate_stage.join().expect("replicate stage join") + ); + + assert_eq!(exit.load(Ordering::Relaxed), true); + let _ignored = remove_dir_all(&my_ledger_path); + } + + #[test] + fn test_replicate_stage_poh_error_entry_receiver() { + // Set up dummy node to host a ReplicateStage + let my_keypair = Keypair::new(); + let my_id = my_keypair.pubkey(); + let vote_keypair = Keypair::new(); + let my_node = Node::new_localhost_with_pubkey(my_id); + // Set up the cluster info + let cluster_info_me = Arc::new(RwLock::new(ClusterInfo::new(my_node.info.clone()))); + let (entry_sender, entry_receiver) = channel(); + let (ledger_entry_sender, _ledger_entry_receiver) = channel(); + let mut last_entry_id = Hash::default(); + // Create keypair for the old leader + let old_leader_id = Keypair::new().pubkey(); + + let (_, my_ledger_path, _) = create_tmp_sample_ledger( + "test_replicate_stage_leader_rotation_exit", + 10_000, + 0, + old_leader_id, + 500, + ); + + let mut entry_height = 0; + let mut last_id = Hash::default(); + let mut entries = Vec::new(); + for _ in 0..5 { + let entry = Entry::new(&mut last_id, 1, vec![]); //just ticks + last_id = entry.id; + entries.push(entry); + } + entry_sender + .send(entries.clone()) + .expect("Expected to err out"); + + let res = ReplicateStage::replicate_requests( + &Arc::new(Bank::default()), + &cluster_info_me, + &entry_receiver, + &Arc::new(my_keypair), + &Arc::new(vote_keypair), + None, + &ledger_entry_sender, + &mut entry_height, + &mut last_entry_id, + ); + + match res { + Ok(_) => (), + Err(e) => assert!(false, "Entries were not sent correctly {:?}", e), + } + + entries.clear(); + for _ in 0..5 { + let entry = Entry::new(&mut Hash::default(), 0, vec![]); //just broken entries + entries.push(entry); + } + entry_sender + .send(entries.clone()) + .expect("Expected to err out"); + + let res = ReplicateStage::replicate_requests( + &Arc::new(Bank::default()), + &cluster_info_me, + &entry_receiver, + &Arc::new(Keypair::new()), + &Arc::new(Keypair::new()), + None, + &ledger_entry_sender, + &mut entry_height, + &mut last_entry_id, + ); + + match res { + Ok(_) => assert!(false, "Should have failed because entries are broken"), + Err(Error::BlobError(BlobError::VerificationFailed)) => (), + Err(e) => assert!( + false, + "Should have failed because with blob error, instead, got {:?}", + e + ), + } + + let _ignored = remove_dir_all(&my_ledger_path); + } +} diff --git a/book/replicator.rs b/book/replicator.rs new file mode 100644 index 00000000000000..c6a8563fb5ea49 --- /dev/null +++ b/book/replicator.rs @@ -0,0 +1,337 @@ +use blob_fetch_stage::BlobFetchStage; +use cluster_info::{ClusterInfo, Node, NodeInfo}; +use leader_scheduler::LeaderScheduler; +use ncp::Ncp; +use service::Service; +use solana_sdk::hash::{Hash, Hasher}; +use std::fs::File; +use std::io; +use std::io::BufReader; +use std::io::Read; +use std::io::Seek; +use std::io::SeekFrom; +use std::io::{Error, ErrorKind}; +use std::mem::size_of; +use std::net::SocketAddr; +use std::net::UdpSocket; +use std::path::Path; +use std::sync::atomic::AtomicBool; +use std::sync::mpsc::channel; +use std::sync::{Arc, RwLock}; +use std::thread::JoinHandle; +use std::time::Duration; +use store_ledger_stage::StoreLedgerStage; +use streamer::BlobReceiver; +use thin_client::poll_gossip_for_leader; +use window; +use window_service::window_service; + +pub struct Replicator { + ncp: Ncp, + fetch_stage: BlobFetchStage, + store_ledger_stage: StoreLedgerStage, + t_window: JoinHandle<()>, + pub retransmit_receiver: BlobReceiver, +} + +pub fn sample_file(in_path: &Path, sample_offsets: &[u64]) -> io::Result { + let in_file = File::open(in_path)?; + let metadata = in_file.metadata()?; + let mut buffer_file = BufReader::new(in_file); + + let mut hasher = Hasher::default(); + let sample_size = size_of::(); + let sample_size64 = sample_size as u64; + let mut buf = vec![0; sample_size]; + + let file_len = metadata.len(); + if file_len < sample_size64 { + return Err(Error::new(ErrorKind::Other, "file too short!")); + } + for offset in sample_offsets { + if *offset > (file_len - sample_size64) / sample_size64 { + return Err(Error::new(ErrorKind::Other, "offset too large")); + } + buffer_file.seek(SeekFrom::Start(*offset * sample_size64))?; + trace!("sampling @ {} ", *offset); + match buffer_file.read(&mut buf) { + Ok(size) => { + assert_eq!(size, buf.len()); + hasher.hash(&buf); + } + Err(e) => { + warn!("Error sampling file"); + return Err(e); + } + } + } + + Ok(hasher.result()) +} + +impl Replicator { + pub fn new( + entry_height: u64, + max_entry_height: u64, + exit: &Arc, + ledger_path: Option<&str>, + node: Node, + network_addr: Option, + done: Arc, + ) -> (Replicator, NodeInfo) { + const REPLICATOR_WINDOW_SIZE: usize = 32 * 1024; + let window = window::new_window(REPLICATOR_WINDOW_SIZE); + let shared_window = Arc::new(RwLock::new(window)); + + let cluster_info = Arc::new(RwLock::new(ClusterInfo::new(node.info))); + + let leader_info = network_addr.map(|i| NodeInfo::new_entry_point(&i)); + let leader_pubkey; + if let Some(leader_info) = leader_info { + leader_pubkey = leader_info.id; + cluster_info.write().unwrap().insert_info(leader_info); + } else { + panic!("No leader info!"); + } + + let repair_socket = Arc::new(node.sockets.repair); + let mut blob_sockets: Vec> = + node.sockets.replicate.into_iter().map(Arc::new).collect(); + blob_sockets.push(repair_socket.clone()); + let (fetch_stage, blob_fetch_receiver) = + BlobFetchStage::new_multi_socket(blob_sockets, exit.clone()); + + let (entry_window_sender, entry_window_receiver) = channel(); + // todo: pull blobs off the retransmit_receiver and recycle them? + let (retransmit_sender, retransmit_receiver) = channel(); + let t_window = window_service( + cluster_info.clone(), + shared_window.clone(), + 0, + entry_height, + max_entry_height, + blob_fetch_receiver, + entry_window_sender, + retransmit_sender, + repair_socket, + Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + leader_pubkey, + ))), + done, + ); + + let store_ledger_stage = StoreLedgerStage::new(entry_window_receiver, ledger_path); + + let ncp = Ncp::new( + &cluster_info, + shared_window.clone(), + ledger_path, + node.sockets.gossip, + exit.clone(), + ); + + let leader = + poll_gossip_for_leader(network_addr.unwrap(), Some(10)).expect("couldn't reach leader"); + + ( + Replicator { + ncp, + fetch_stage, + store_ledger_stage, + t_window, + retransmit_receiver, + }, + leader, + ) + } + + pub fn join(self) { + self.ncp.join().unwrap(); + self.fetch_stage.join().unwrap(); + self.t_window.join().unwrap(); + self.store_ledger_stage.join().unwrap(); + + // Drain the queue here to prevent self.retransmit_receiver from being dropped + // before the window_service thread is joined + let mut retransmit_queue_count = 0; + while let Ok(_blob) = self.retransmit_receiver.recv_timeout(Duration::new(1, 0)) { + retransmit_queue_count += 1; + } + debug!("retransmit channel count: {}", retransmit_queue_count); + } +} + +#[cfg(test)] +mod tests { + use client::mk_client; + use cluster_info::Node; + use fullnode::Fullnode; + use leader_scheduler::LeaderScheduler; + use ledger::{create_tmp_genesis, get_tmp_ledger_path, read_ledger}; + use logger; + use replicator::sample_file; + use replicator::Replicator; + use signature::{Keypair, KeypairUtil}; + use solana_sdk::hash::Hash; + use std::fs::File; + use std::fs::{create_dir_all, remove_dir_all, remove_file}; + use std::io::Write; + use std::mem::size_of; + use std::path::PathBuf; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::Arc; + use std::thread::sleep; + use std::time::Duration; + + #[test] + fn test_replicator_startup() { + logger::setup(); + info!("starting replicator test"); + let entry_height = 0; + let replicator_ledger_path = &get_tmp_ledger_path("replicator_test_replicator_ledger"); + + let exit = Arc::new(AtomicBool::new(false)); + let done = Arc::new(AtomicBool::new(false)); + + info!("starting leader node"); + let leader_keypair = Arc::new(Keypair::new()); + let leader_node = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); + let network_addr = leader_node.sockets.gossip.local_addr().unwrap(); + let leader_info = leader_node.info.clone(); + let vote_account_keypair = Arc::new(Keypair::new()); + + let leader_ledger_path = "replicator_test_leader_ledger"; + let (mint, leader_ledger_path) = + create_tmp_genesis(leader_ledger_path, 100, leader_info.id, 1); + + let leader = Fullnode::new( + leader_node, + &leader_ledger_path, + leader_keypair, + vote_account_keypair, + None, + false, + LeaderScheduler::from_bootstrap_leader(leader_info.id), + None, + ); + + let mut leader_client = mk_client(&leader_info); + + let bob = Keypair::new(); + + let last_id = leader_client.get_last_id(); + leader_client + .transfer(1, &mint.keypair(), bob.pubkey(), &last_id) + .unwrap(); + + let replicator_keypair = Keypair::new(); + + info!("starting replicator node"); + let replicator_node = Node::new_localhost_with_pubkey(replicator_keypair.pubkey()); + let (replicator, _leader_info) = Replicator::new( + entry_height, + 1, + &exit, + Some(replicator_ledger_path), + replicator_node, + Some(network_addr), + done.clone(), + ); + + let mut num_entries = 0; + for _ in 0..60 { + match read_ledger(replicator_ledger_path, true) { + Ok(entries) => { + for _ in entries { + num_entries += 1; + } + info!("{} entries", num_entries); + if num_entries > 0 { + break; + } + } + Err(e) => { + info!("error reading ledger: {:?}", e); + } + } + sleep(Duration::from_millis(300)); + let last_id = leader_client.get_last_id(); + leader_client + .transfer(1, &mint.keypair(), bob.pubkey(), &last_id) + .unwrap(); + } + assert_eq!(done.load(Ordering::Relaxed), true); + assert!(num_entries > 0); + exit.store(true, Ordering::Relaxed); + replicator.join(); + leader.exit(); + let _ignored = remove_dir_all(&leader_ledger_path); + let _ignored = remove_dir_all(&replicator_ledger_path); + } + + fn tmp_file_path(name: &str) -> PathBuf { + use std::env; + let out_dir = env::var("OUT_DIR").unwrap_or_else(|_| "target".to_string()); + let keypair = Keypair::new(); + + let mut path = PathBuf::new(); + path.push(out_dir); + path.push("tmp"); + create_dir_all(&path).unwrap(); + + path.push(format!("{}-{}", name, keypair.pubkey())); + path + } + + #[test] + fn test_sample_file() { + logger::setup(); + let in_path = tmp_file_path("test_sample_file_input.txt"); + let num_strings = 4096; + let string = "12foobar"; + { + let mut in_file = File::create(&in_path).unwrap(); + for _ in 0..num_strings { + in_file.write(string.as_bytes()).unwrap(); + } + } + let num_samples = (string.len() * num_strings / size_of::()) as u64; + let samples: Vec<_> = (0..num_samples).collect(); + let res = sample_file(&in_path, samples.as_slice()); + assert!(res.is_ok()); + let ref_hash: Hash = Hash::new(&[ + 173, 251, 182, 165, 10, 54, 33, 150, 133, 226, 106, 150, 99, 192, 179, 1, 230, 144, + 151, 126, 18, 191, 54, 67, 249, 140, 230, 160, 56, 30, 170, 52, + ]); + let res = res.unwrap(); + assert_eq!(res, ref_hash); + + // Sample just past the end + assert!(sample_file(&in_path, &[num_samples]).is_err()); + remove_file(&in_path).unwrap(); + } + + #[test] + fn test_sample_file_invalid_offset() { + let in_path = tmp_file_path("test_sample_file_invalid_offset_input.txt"); + { + let mut in_file = File::create(&in_path).unwrap(); + for _ in 0..4096 { + in_file.write("123456foobar".as_bytes()).unwrap(); + } + } + let samples = [0, 200000]; + let res = sample_file(&in_path, &samples); + assert!(res.is_err()); + remove_file(in_path).unwrap(); + } + + #[test] + fn test_sample_file_missing_file() { + let in_path = tmp_file_path("test_sample_file_that_doesnt_exist.txt"); + let samples = [0, 5]; + let res = sample_file(&in_path, &samples); + assert!(res.is_err()); + } + +} diff --git a/book/result.rs b/book/result.rs new file mode 100644 index 00000000000000..7d17f114e519af --- /dev/null +++ b/book/result.rs @@ -0,0 +1,187 @@ +//! The `result` module exposes a Result type that propagates one of many different Error types. + +use bank; +use bincode; +use cluster_info; +use db_ledger; +#[cfg(feature = "erasure")] +use erasure; +use packet; +use poh_recorder; +use rocksdb; +use serde_json; +use std; +use std::any::Any; +use vote_stage; + +#[derive(Debug)] +pub enum Error { + IO(std::io::Error), + JSON(serde_json::Error), + AddrParse(std::net::AddrParseError), + JoinError(Box), + RecvError(std::sync::mpsc::RecvError), + RecvTimeoutError(std::sync::mpsc::RecvTimeoutError), + Serialize(std::boxed::Box), + BankError(bank::BankError), + ClusterInfoError(cluster_info::ClusterInfoError), + BlobError(packet::BlobError), + #[cfg(feature = "erasure")] + ErasureError(erasure::ErasureError), + SendError, + PohRecorderError(poh_recorder::PohRecorderError), + VoteError(vote_stage::VoteError), + RocksDb(rocksdb::Error), + DbLedgerError(db_ledger::DbLedgerError), +} + +pub type Result = std::result::Result; + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "solana error") + } +} + +impl std::error::Error for Error {} + +impl std::convert::From for Error { + fn from(e: std::sync::mpsc::RecvError) -> Error { + Error::RecvError(e) + } +} +impl std::convert::From for Error { + fn from(e: std::sync::mpsc::RecvTimeoutError) -> Error { + Error::RecvTimeoutError(e) + } +} +impl std::convert::From for Error { + fn from(e: bank::BankError) -> Error { + Error::BankError(e) + } +} +impl std::convert::From for Error { + fn from(e: cluster_info::ClusterInfoError) -> Error { + Error::ClusterInfoError(e) + } +} +#[cfg(feature = "erasure")] +impl std::convert::From for Error { + fn from(e: erasure::ErasureError) -> Error { + Error::ErasureError(e) + } +} +impl std::convert::From> for Error { + fn from(_e: std::sync::mpsc::SendError) -> Error { + Error::SendError + } +} +impl std::convert::From> for Error { + fn from(e: Box) -> Error { + Error::JoinError(e) + } +} +impl std::convert::From for Error { + fn from(e: std::io::Error) -> Error { + Error::IO(e) + } +} +impl std::convert::From for Error { + fn from(e: serde_json::Error) -> Error { + Error::JSON(e) + } +} +impl std::convert::From for Error { + fn from(e: std::net::AddrParseError) -> Error { + Error::AddrParse(e) + } +} +impl std::convert::From> for Error { + fn from(e: std::boxed::Box) -> Error { + Error::Serialize(e) + } +} +impl std::convert::From for Error { + fn from(e: poh_recorder::PohRecorderError) -> Error { + Error::PohRecorderError(e) + } +} +impl std::convert::From for Error { + fn from(e: vote_stage::VoteError) -> Error { + Error::VoteError(e) + } +} +impl std::convert::From for Error { + fn from(e: rocksdb::Error) -> Error { + Error::RocksDb(e) + } +} +impl std::convert::From for Error { + fn from(e: db_ledger::DbLedgerError) -> Error { + Error::DbLedgerError(e) + } +} + +#[cfg(test)] +mod tests { + use result::Error; + use result::Result; + use serde_json; + use std::io; + use std::io::Write; + use std::net::SocketAddr; + use std::panic; + use std::sync::mpsc::channel; + use std::sync::mpsc::RecvError; + use std::sync::mpsc::RecvTimeoutError; + use std::thread; + + fn addr_parse_error() -> Result { + let r = "12fdfasfsafsadfs".parse()?; + Ok(r) + } + + fn join_error() -> Result<()> { + panic::set_hook(Box::new(|_info| {})); + let r = thread::spawn(|| panic!("hi")).join()?; + Ok(r) + } + fn json_error() -> Result<()> { + let r = serde_json::from_slice("=342{;;;;:}".as_bytes())?; + Ok(r) + } + fn send_error() -> Result<()> { + let (s, r) = channel(); + drop(r); + s.send(())?; + Ok(()) + } + + #[test] + fn from_test() { + assert_matches!(addr_parse_error(), Err(Error::AddrParse(_))); + assert_matches!(Error::from(RecvError {}), Error::RecvError(_)); + assert_matches!( + Error::from(RecvTimeoutError::Timeout), + Error::RecvTimeoutError(_) + ); + assert_matches!(send_error(), Err(Error::SendError)); + assert_matches!(join_error(), Err(Error::JoinError(_))); + let ioe = io::Error::new(io::ErrorKind::NotFound, "hi"); + assert_matches!(Error::from(ioe), Error::IO(_)); + } + #[test] + fn fmt_test() { + write!(io::sink(), "{:?}", addr_parse_error()).unwrap(); + write!(io::sink(), "{:?}", Error::from(RecvError {})).unwrap(); + write!(io::sink(), "{:?}", Error::from(RecvTimeoutError::Timeout)).unwrap(); + write!(io::sink(), "{:?}", send_error()).unwrap(); + write!(io::sink(), "{:?}", join_error()).unwrap(); + write!(io::sink(), "{:?}", json_error()).unwrap(); + write!( + io::sink(), + "{:?}", + Error::from(io::Error::new(io::ErrorKind::NotFound, "hi")) + ).unwrap(); + } +} diff --git a/book/retransmit_stage.rs b/book/retransmit_stage.rs new file mode 100644 index 00000000000000..9f462336e303fd --- /dev/null +++ b/book/retransmit_stage.rs @@ -0,0 +1,127 @@ +//! The `retransmit_stage` retransmits blobs between validators + +use cluster_info::ClusterInfo; +use counter::Counter; +use entry::Entry; + +use leader_scheduler::LeaderScheduler; +use log::Level; +use result::{Error, Result}; +use service::Service; +use solana_metrics::{influxdb, submit}; +use std::net::UdpSocket; +use std::sync::atomic::{AtomicBool, AtomicUsize}; +use std::sync::mpsc::RecvTimeoutError; +use std::sync::mpsc::{channel, Receiver}; +use std::sync::{Arc, RwLock}; +use std::thread::{self, Builder, JoinHandle}; +use std::time::Duration; +use streamer::BlobReceiver; +use window::SharedWindow; +use window_service::window_service; + +fn retransmit( + cluster_info: &Arc>, + r: &BlobReceiver, + sock: &UdpSocket, +) -> Result<()> { + let timer = Duration::new(1, 0); + let mut dq = r.recv_timeout(timer)?; + while let Ok(mut nq) = r.try_recv() { + dq.append(&mut nq); + } + + submit( + influxdb::Point::new("retransmit-stage") + .add_field("count", influxdb::Value::Integer(dq.len() as i64)) + .to_owned(), + ); + + for b in &mut dq { + ClusterInfo::retransmit(&cluster_info, b, sock)?; + } + Ok(()) +} + +/// Service to retransmit messages from the leader to layer 1 nodes. +/// See `cluster_info` for network layer definitions. +/// # Arguments +/// * `sock` - Socket to read from. Read timeout is set to 1. +/// * `exit` - Boolean to signal system exit. +/// * `cluster_info` - This structure needs to be updated and populated by the bank and via gossip. +/// * `recycler` - Blob recycler. +/// * `r` - Receive channel for blobs to be retransmitted to all the layer 1 nodes. +fn retransmitter( + sock: Arc, + cluster_info: Arc>, + r: BlobReceiver, +) -> JoinHandle<()> { + Builder::new() + .name("solana-retransmitter".to_string()) + .spawn(move || { + trace!("retransmitter started"); + loop { + if let Err(e) = retransmit(&cluster_info, &r, &sock) { + match e { + Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => break, + Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), + _ => { + inc_new_counter_info!("streamer-retransmit-error", 1, 1); + } + } + } + } + trace!("exiting retransmitter"); + }).unwrap() +} + +pub struct RetransmitStage { + thread_hdls: Vec>, +} + +impl RetransmitStage { + pub fn new( + cluster_info: &Arc>, + window: SharedWindow, + tick_height: u64, + entry_height: u64, + retransmit_socket: Arc, + repair_socket: Arc, + fetch_stage_receiver: BlobReceiver, + leader_scheduler: Arc>, + ) -> (Self, Receiver>) { + let (retransmit_sender, retransmit_receiver) = channel(); + + let t_retransmit = + retransmitter(retransmit_socket, cluster_info.clone(), retransmit_receiver); + let (entry_sender, entry_receiver) = channel(); + let done = Arc::new(AtomicBool::new(false)); + let t_window = window_service( + cluster_info.clone(), + window, + tick_height, + entry_height, + 0, + fetch_stage_receiver, + entry_sender, + retransmit_sender, + repair_socket, + leader_scheduler, + done, + ); + + let thread_hdls = vec![t_retransmit, t_window]; + (RetransmitStage { thread_hdls }, entry_receiver) + } +} + +impl Service for RetransmitStage { + type JoinReturnType = (); + + fn join(self) -> thread::Result<()> { + for thread_hdl in self.thread_hdls { + thread_hdl.join()?; + } + Ok(()) + } +} diff --git a/book/rpc.rs b/book/rpc.rs new file mode 100644 index 00000000000000..b861a3d492f409 --- /dev/null +++ b/book/rpc.rs @@ -0,0 +1,771 @@ +//! The `rpc` module implements the Solana RPC interface. + +use bank::{Bank, BankError}; +use bincode::{deserialize, serialize}; +use bs58; +use cluster_info::ClusterInfo; +use jsonrpc_core::*; +use jsonrpc_http_server::*; +use packet::PACKET_DATA_SIZE; +use service::Service; +use signature::Signature; +use solana_drone::drone::{request_airdrop_transaction, DRONE_PORT}; +use solana_sdk::account::Account; +use solana_sdk::pubkey::Pubkey; +use std::mem; +use std::net::{SocketAddr, UdpSocket}; +use std::result; +use std::str::FromStr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, RwLock}; +use std::thread::{self, sleep, Builder, JoinHandle}; +use std::time::Duration; +use std::time::Instant; +use transaction::Transaction; + +pub const RPC_PORT: u16 = 8899; + +pub struct JsonRpcService { + thread_hdl: JoinHandle<()>, + exit: Arc, +} + +impl JsonRpcService { + pub fn new( + bank: &Arc, + cluster_info: &Arc>, + rpc_addr: SocketAddr, + ) -> Self { + let exit = Arc::new(AtomicBool::new(false)); + let request_processor = JsonRpcRequestProcessor::new(bank.clone()); + let info = cluster_info.clone(); + let exit_pubsub = exit.clone(); + let exit_ = exit.clone(); + let thread_hdl = Builder::new() + .name("solana-jsonrpc".to_string()) + .spawn(move || { + let mut io = MetaIoHandler::default(); + let rpc = RpcSolImpl; + io.extend_with(rpc.to_delegate()); + + let server = + ServerBuilder::with_meta_extractor(io, move |_req: &hyper::Request| Meta { + request_processor: request_processor.clone(), + cluster_info: info.clone(), + rpc_addr, + exit: exit_pubsub.clone(), + }).threads(4) + .cors(DomainsValidation::AllowOnly(vec![ + AccessControlAllowOrigin::Any, + ])) + .start_http(&rpc_addr); + if server.is_err() { + warn!("JSON RPC service unavailable: unable to bind to RPC port {}. \nMake sure this port is not already in use by another application", rpc_addr.port()); + return; + } + while !exit_.load(Ordering::Relaxed) { + sleep(Duration::from_millis(100)); + } + server.unwrap().close(); + () + }) + .unwrap(); + JsonRpcService { thread_hdl, exit } + } + + pub fn exit(&self) { + self.exit.store(true, Ordering::Relaxed); + } + + pub fn close(self) -> thread::Result<()> { + self.exit(); + self.join() + } +} + +impl Service for JsonRpcService { + type JoinReturnType = (); + + fn join(self) -> thread::Result<()> { + self.thread_hdl.join() + } +} + +#[derive(Clone)] +pub struct Meta { + pub request_processor: JsonRpcRequestProcessor, + pub cluster_info: Arc>, + pub rpc_addr: SocketAddr, + pub exit: Arc, +} +impl Metadata for Meta {} + +#[derive(Copy, Clone, PartialEq, Serialize, Debug)] +pub enum RpcSignatureStatus { + AccountInUse, + Confirmed, + GenericFailure, + ProgramRuntimeError, + SignatureNotFound, +} +impl FromStr for RpcSignatureStatus { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "AccountInUse" => Ok(RpcSignatureStatus::AccountInUse), + "Confirmed" => Ok(RpcSignatureStatus::Confirmed), + "GenericFailure" => Ok(RpcSignatureStatus::GenericFailure), + "ProgramRuntimeError" => Ok(RpcSignatureStatus::ProgramRuntimeError), + "SignatureNotFound" => Ok(RpcSignatureStatus::SignatureNotFound), + _ => Err(Error::parse_error()), + } + } +} + +build_rpc_trait! { + pub trait RpcSol { + type Metadata; + + #[rpc(meta, name = "confirmTransaction")] + fn confirm_transaction(&self, Self::Metadata, String) -> Result; + + #[rpc(meta, name = "getAccountInfo")] + fn get_account_info(&self, Self::Metadata, String) -> Result; + + #[rpc(meta, name = "getBalance")] + fn get_balance(&self, Self::Metadata, String) -> Result; + + #[rpc(meta, name = "getFinality")] + fn get_finality(&self, Self::Metadata) -> Result; + + #[rpc(meta, name = "getLastId")] + fn get_last_id(&self, Self::Metadata) -> Result; + + #[rpc(meta, name = "getSignatureStatus")] + fn get_signature_status(&self, Self::Metadata, String) -> Result; + + #[rpc(meta, name = "getTransactionCount")] + fn get_transaction_count(&self, Self::Metadata) -> Result; + + #[rpc(meta, name= "requestAirdrop")] + fn request_airdrop(&self, Self::Metadata, String, u64) -> Result; + + #[rpc(meta, name = "sendTransaction")] + fn send_transaction(&self, Self::Metadata, Vec) -> Result; + } +} + +pub struct RpcSolImpl; +impl RpcSol for RpcSolImpl { + type Metadata = Meta; + + fn confirm_transaction(&self, meta: Self::Metadata, id: String) -> Result { + info!("confirm_transaction rpc request received: {:?}", id); + self.get_signature_status(meta, id) + .map(|status| status == RpcSignatureStatus::Confirmed) + } + + fn get_account_info(&self, meta: Self::Metadata, id: String) -> Result { + info!("get_account_info rpc request received: {:?}", id); + let pubkey = verify_pubkey(id)?; + meta.request_processor.get_account_info(pubkey) + } + fn get_balance(&self, meta: Self::Metadata, id: String) -> Result { + info!("get_balance rpc request received: {:?}", id); + let pubkey = verify_pubkey(id)?; + meta.request_processor.get_balance(pubkey) + } + fn get_finality(&self, meta: Self::Metadata) -> Result { + info!("get_finality rpc request received"); + meta.request_processor.get_finality() + } + fn get_last_id(&self, meta: Self::Metadata) -> Result { + info!("get_last_id rpc request received"); + meta.request_processor.get_last_id() + } + fn get_signature_status(&self, meta: Self::Metadata, id: String) -> Result { + info!("get_signature_status rpc request received: {:?}", id); + let signature = verify_signature(&id)?; + Ok( + match meta.request_processor.get_signature_status(signature) { + Ok(_) => RpcSignatureStatus::Confirmed, + Err(BankError::AccountInUse) => RpcSignatureStatus::AccountInUse, + Err(BankError::ProgramRuntimeError(_)) => RpcSignatureStatus::ProgramRuntimeError, + // Report SignatureReserved as SignatureNotFound as SignatureReserved is + // transitory while the bank processes the associated transaction. + Err(BankError::SignatureReserved) => RpcSignatureStatus::SignatureNotFound, + Err(BankError::SignatureNotFound) => RpcSignatureStatus::SignatureNotFound, + Err(err) => { + trace!("mapping {:?} to GenericFailure", err); + RpcSignatureStatus::GenericFailure + } + }, + ) + } + fn get_transaction_count(&self, meta: Self::Metadata) -> Result { + info!("get_transaction_count rpc request received"); + meta.request_processor.get_transaction_count() + } + fn request_airdrop(&self, meta: Self::Metadata, id: String, tokens: u64) -> Result { + trace!("request_airdrop id={} tokens={}", id, tokens); + let pubkey = verify_pubkey(id)?; + + let mut drone_addr = get_leader_addr(&meta.cluster_info)?; + drone_addr.set_port(DRONE_PORT); + let last_id = meta.request_processor.bank.last_id(); + let transaction = request_airdrop_transaction(&drone_addr, &pubkey, tokens, last_id) + .map_err(|err| { + info!("request_airdrop_transaction failed: {:?}", err); + Error::internal_error() + })?;; + + let data = serialize(&transaction).map_err(|err| { + info!("request_airdrop: serialize error: {:?}", err); + Error::internal_error() + })?; + + let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + let transactions_addr = get_leader_addr(&meta.cluster_info)?; + transactions_socket + .send_to(&data, transactions_addr) + .map_err(|err| { + info!("request_airdrop: send_to error: {:?}", err); + Error::internal_error() + })?; + + let signature = transaction.signatures[0]; + let now = Instant::now(); + let mut signature_status; + loop { + signature_status = meta.request_processor.get_signature_status(signature); + + if signature_status.is_ok() { + info!("airdrop signature ok"); + return Ok(bs58::encode(signature).into_string()); + } else if now.elapsed().as_secs() > 5 { + info!("airdrop signature timeout"); + return Err(Error::internal_error()); + } + sleep(Duration::from_millis(100)); + } + } + fn send_transaction(&self, meta: Self::Metadata, data: Vec) -> Result { + let tx: Transaction = deserialize(&data).map_err(|err| { + info!("send_transaction: deserialize error: {:?}", err); + Error::invalid_request() + })?; + if data.len() >= PACKET_DATA_SIZE { + info!( + "send_transaction: transaction too large: {} bytes (max: {} bytes)", + data.len(), + PACKET_DATA_SIZE + ); + return Err(Error::invalid_request()); + } + let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + let transactions_addr = get_leader_addr(&meta.cluster_info)?; + transactions_socket + .send_to(&data, transactions_addr) + .map_err(|err| { + info!("send_transaction: send_to error: {:?}", err); + Error::internal_error() + })?; + let signature = bs58::encode(tx.signatures[0]).into_string(); + trace!( + "send_transaction: sent {} bytes, signature={}", + data.len(), + signature + ); + Ok(signature) + } +} +#[derive(Clone)] +pub struct JsonRpcRequestProcessor { + bank: Arc, +} +impl JsonRpcRequestProcessor { + /// Create a new request processor that wraps the given Bank. + pub fn new(bank: Arc) -> Self { + JsonRpcRequestProcessor { bank } + } + + /// Process JSON-RPC request items sent via JSON-RPC. + pub fn get_account_info(&self, pubkey: Pubkey) -> Result { + self.bank + .get_account(&pubkey) + .ok_or_else(Error::invalid_request) + } + fn get_balance(&self, pubkey: Pubkey) -> Result { + let val = self.bank.get_balance(&pubkey); + Ok(val) + } + fn get_finality(&self) -> Result { + Ok(self.bank.finality()) + } + fn get_last_id(&self) -> Result { + let id = self.bank.last_id(); + Ok(bs58::encode(id).into_string()) + } + pub fn get_signature_status(&self, signature: Signature) -> result::Result<(), BankError> { + self.bank.get_signature_status(&signature) + } + fn get_transaction_count(&self) -> Result { + Ok(self.bank.transaction_count() as u64) + } +} + +fn get_leader_addr(cluster_info: &Arc>) -> Result { + if let Some(leader_data) = cluster_info.read().unwrap().leader_data() { + Ok(leader_data.tpu) + } else { + Err(Error { + code: ErrorCode::InternalError, + message: "No leader detected".into(), + data: None, + }) + } +} + +fn verify_pubkey(input: String) -> Result { + let pubkey_vec = bs58::decode(input).into_vec().map_err(|err| { + info!("verify_pubkey: invalid input: {:?}", err); + Error::invalid_request() + })?; + if pubkey_vec.len() != mem::size_of::() { + info!( + "verify_pubkey: invalid pubkey_vec length: {}", + pubkey_vec.len() + ); + Err(Error::invalid_request()) + } else { + Ok(Pubkey::new(&pubkey_vec)) + } +} + +fn verify_signature(input: &str) -> Result { + let signature_vec = bs58::decode(input).into_vec().map_err(|err| { + info!("verify_signature: invalid input: {}: {:?}", input, err); + Error::invalid_request() + })?; + if signature_vec.len() != mem::size_of::() { + info!( + "verify_signature: invalid signature_vec length: {}", + signature_vec.len() + ); + Err(Error::invalid_request()) + } else { + Ok(Signature::new(&signature_vec)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bank::Bank; + use bincode::serialize; + use cluster_info::{Node, NodeInfo}; + use fullnode::Fullnode; + use jsonrpc_core::Response; + use leader_scheduler::LeaderScheduler; + use ledger::create_tmp_ledger_with_mint; + use mint::Mint; + use reqwest; + use reqwest::header::CONTENT_TYPE; + use signature::{Keypair, KeypairUtil}; + use solana_sdk::hash::{hash, Hash}; + use std::fs::remove_dir_all; + use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use system_transaction::SystemTransaction; + use transaction::Transaction; + + fn start_rpc_handler_with_tx(pubkey: Pubkey) -> (MetaIoHandler, Meta, Hash, Keypair) { + let alice = Mint::new(10_000); + let bank = Bank::new(&alice); + + let last_id = bank.last_id(); + let tx = Transaction::system_move(&alice.keypair(), pubkey, 20, last_id, 0); + bank.process_transaction(&tx).expect("process transaction"); + + let request_processor = JsonRpcRequestProcessor::new(Arc::new(bank)); + let cluster_info = Arc::new(RwLock::new(ClusterInfo::new(NodeInfo::default()))); + let leader = NodeInfo::new_with_socketaddr(&socketaddr!("127.0.0.1:1234")); + cluster_info.write().unwrap().insert_info(leader.clone()); + cluster_info.write().unwrap().set_leader(leader.id); + let rpc_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0); + let exit = Arc::new(AtomicBool::new(false)); + + let mut io = MetaIoHandler::default(); + let rpc = RpcSolImpl; + io.extend_with(rpc.to_delegate()); + let meta = Meta { + request_processor, + cluster_info, + rpc_addr, + exit, + }; + (io, meta, last_id, alice.keypair()) + } + + #[test] + #[ignore] + fn test_rpc_new() { + let alice = Mint::new(10_000); + let bank = Bank::new(&alice); + let cluster_info = Arc::new(RwLock::new(ClusterInfo::new(NodeInfo::default()))); + let rpc_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 24680); + let rpc_service = JsonRpcService::new(&Arc::new(bank), &cluster_info, rpc_addr); + let thread = rpc_service.thread_hdl.thread(); + assert_eq!(thread.name().unwrap(), "solana-jsonrpc"); + + let rpc_string = format!("http://{}", rpc_addr.to_string()); + let client = reqwest::Client::new(); + let request = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "getBalance", + "params": [alice.pubkey().to_string()], + }); + let mut response = client + .post(&rpc_string) + .header(CONTENT_TYPE, "application/json") + .body(request.to_string()) + .send() + .unwrap(); + let json: Value = serde_json::from_str(&response.text().unwrap()).unwrap(); + + assert_eq!(10_000, json["result"].as_u64().unwrap()); + } + + #[test] + fn test_rpc_request_processor_new() { + let alice = Mint::new(10_000); + let bob_pubkey = Keypair::new().pubkey(); + let bank = Bank::new(&alice); + let arc_bank = Arc::new(bank); + let request_processor = JsonRpcRequestProcessor::new(arc_bank.clone()); + thread::spawn(move || { + let last_id = arc_bank.last_id(); + let tx = Transaction::system_move(&alice.keypair(), bob_pubkey, 20, last_id, 0); + arc_bank + .process_transaction(&tx) + .expect("process transaction"); + }).join() + .unwrap(); + assert_eq!(request_processor.get_transaction_count().unwrap(), 1); + } + + #[test] + fn test_rpc_get_balance() { + let bob_pubkey = Keypair::new().pubkey(); + let (io, meta, _last_id, _alice_keypair) = start_rpc_handler_with_tx(bob_pubkey); + + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"getBalance","params":["{}"]}}"#, + bob_pubkey + ); + let res = io.handle_request_sync(&req, meta); + let expected = format!(r#"{{"jsonrpc":"2.0","result":20,"id":1}}"#); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + } + + #[test] + fn test_rpc_get_tx_count() { + let bob_pubkey = Keypair::new().pubkey(); + let (io, meta, _last_id, _alice_keypair) = start_rpc_handler_with_tx(bob_pubkey); + + let req = format!(r#"{{"jsonrpc":"2.0","id":1,"method":"getTransactionCount"}}"#); + let res = io.handle_request_sync(&req, meta); + let expected = format!(r#"{{"jsonrpc":"2.0","result":1,"id":1}}"#); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + } + + #[test] + fn test_rpc_get_account_info() { + let bob_pubkey = Keypair::new().pubkey(); + let (io, meta, _last_id, _alice_keypair) = start_rpc_handler_with_tx(bob_pubkey); + + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"getAccountInfo","params":["{}"]}}"#, + bob_pubkey + ); + let res = io.handle_request_sync(&req, meta); + let expected = r#"{ + "jsonrpc":"2.0", + "result":{ + "owner": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + "tokens": 20, + "userdata": [], + "executable": false, + "loader": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] + }, + "id":1} + "#; + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + } + + #[test] + fn test_rpc_confirm_tx() { + let bob_pubkey = Keypair::new().pubkey(); + let (io, meta, last_id, alice_keypair) = start_rpc_handler_with_tx(bob_pubkey); + let tx = Transaction::system_move(&alice_keypair, bob_pubkey, 20, last_id, 0); + + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"confirmTransaction","params":["{}"]}}"#, + tx.signatures[0] + ); + let res = io.handle_request_sync(&req, meta); + let expected = format!(r#"{{"jsonrpc":"2.0","result":true,"id":1}}"#); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + } + + #[test] + fn test_rpc_get_signature_status() { + let bob_pubkey = Keypair::new().pubkey(); + let (io, meta, last_id, alice_keypair) = start_rpc_handler_with_tx(bob_pubkey); + let tx = Transaction::system_move(&alice_keypair, bob_pubkey, 20, last_id, 0); + + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":["{}"]}}"#, + tx.signatures[0] + ); + let res = io.handle_request_sync(&req, meta.clone()); + let expected = format!(r#"{{"jsonrpc":"2.0","result":"Confirmed","id":1}}"#); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + + // Test getSignatureStatus request on unprocessed tx + let tx = Transaction::system_move(&alice_keypair, bob_pubkey, 10, last_id, 0); + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":["{}"]}}"#, + tx.signatures[0] + ); + let res = io.handle_request_sync(&req, meta); + let expected = format!(r#"{{"jsonrpc":"2.0","result":"SignatureNotFound","id":1}}"#); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + } + + #[test] + fn test_rpc_get_finality() { + let bob_pubkey = Keypair::new().pubkey(); + let (io, meta, _last_id, _alice_keypair) = start_rpc_handler_with_tx(bob_pubkey); + + let req = format!(r#"{{"jsonrpc":"2.0","id":1,"method":"getFinality"}}"#); + let res = io.handle_request_sync(&req, meta); + let expected = format!(r#"{{"jsonrpc":"2.0","result":18446744073709551615,"id":1}}"#); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + } + + #[test] + fn test_rpc_get_last_id() { + let bob_pubkey = Keypair::new().pubkey(); + let (io, meta, last_id, _alice_keypair) = start_rpc_handler_with_tx(bob_pubkey); + + let req = format!(r#"{{"jsonrpc":"2.0","id":1,"method":"getLastId"}}"#); + let res = io.handle_request_sync(&req, meta); + let expected = format!(r#"{{"jsonrpc":"2.0","result":"{}","id":1}}"#, last_id); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + } + + #[test] + fn test_rpc_fail_request_airdrop() { + let bob_pubkey = Keypair::new().pubkey(); + let (io, meta, _last_id, _alice_keypair) = start_rpc_handler_with_tx(bob_pubkey); + + // Expect internal error because no leader is running + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"requestAirdrop","params":["{}", 50]}}"#, + bob_pubkey + ); + let res = io.handle_request_sync(&req, meta); + let expected = + r#"{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error"},"id":1}"#; + let expected: Response = + serde_json::from_str(expected).expect("expected response deserialization"); + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + } + + #[test] + fn test_rpc_send_tx() { + let leader_keypair = Arc::new(Keypair::new()); + let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); + + let alice = Mint::new(10_000_000); + let mut bank = Bank::new(&alice); + let bob_pubkey = Keypair::new().pubkey(); + let leader_data = leader.info.clone(); + let ledger_path = create_tmp_ledger_with_mint("rpc_send_tx", &alice); + + let last_id = bank.last_id(); + let tx = Transaction::system_move(&alice.keypair(), bob_pubkey, 20, last_id, 0); + let serial_tx = serialize(&tx).unwrap(); + + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + leader_data.id, + ))); + bank.leader_scheduler = leader_scheduler; + + let vote_account_keypair = Arc::new(Keypair::new()); + let entry_height = alice.create_entries().len() as u64; + let server = Fullnode::new_with_bank( + leader_keypair, + vote_account_keypair, + bank, + entry_height, + &last_id, + leader, + None, + &ledger_path, + false, + None, + ); + sleep(Duration::from_millis(900)); + + let client = reqwest::Client::new(); + let request = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "sendTransaction", + "params": json!([serial_tx]) + }); + let rpc_addr = leader_data.rpc; + let rpc_string = format!("http://{}", rpc_addr.to_string()); + let mut response = client + .post(&rpc_string) + .header(CONTENT_TYPE, "application/json") + .body(request.to_string()) + .send() + .unwrap(); + let json: Value = serde_json::from_str(&response.text().unwrap()).unwrap(); + let signature = &json["result"]; + + sleep(Duration::from_millis(500)); + + let client = reqwest::Client::new(); + let request = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "confirmTransaction", + "params": [signature], + }); + let mut response = client + .post(&rpc_string) + .header(CONTENT_TYPE, "application/json") + .body(request.to_string()) + .send() + .unwrap(); + let response_json_text = response.text().unwrap(); + let json: Value = serde_json::from_str(&response_json_text).unwrap(); + + assert_eq!(true, json["result"]); + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); + } + + #[test] + fn test_rpc_send_bad_tx() { + let alice = Mint::new(10_000); + let bank = Bank::new(&alice); + + let mut io = MetaIoHandler::default(); + let rpc = RpcSolImpl; + io.extend_with(rpc.to_delegate()); + let meta = Meta { + request_processor: JsonRpcRequestProcessor::new(Arc::new(bank)), + cluster_info: Arc::new(RwLock::new(ClusterInfo::new(NodeInfo::default()))), + rpc_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0), + exit: Arc::new(AtomicBool::new(false)), + }; + + let req = + r#"{"jsonrpc":"2.0","id":1,"method":"sendTransaction","params":[[0,0,0,0,0,0,0,0]]}"#; + let res = io.handle_request_sync(req, meta.clone()); + let expected = + r#"{"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid request"},"id":1}"#; + let expected: Response = + serde_json::from_str(expected).expect("expected response deserialization"); + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + } + + #[test] + fn test_rpc_get_leader_addr() { + let cluster_info = Arc::new(RwLock::new(ClusterInfo::new(NodeInfo::default()))); + assert_eq!( + get_leader_addr(&cluster_info), + Err(Error { + code: ErrorCode::InternalError, + message: "No leader detected".into(), + data: None, + }) + ); + let leader = NodeInfo::new_with_socketaddr(&socketaddr!("127.0.0.1:1234")); + cluster_info.write().unwrap().insert_info(leader.clone()); + cluster_info.write().unwrap().set_leader(leader.id); + assert_eq!( + get_leader_addr(&cluster_info), + Ok(socketaddr!("127.0.0.1:1234")) + ); + } + + #[test] + fn test_rpc_verify_pubkey() { + let pubkey = Keypair::new().pubkey(); + assert_eq!(verify_pubkey(pubkey.to_string()).unwrap(), pubkey); + let bad_pubkey = "a1b2c3d4"; + assert_eq!( + verify_pubkey(bad_pubkey.to_string()), + Err(Error::invalid_request()) + ); + } + + #[test] + fn test_rpc_verify_signature() { + let tx = + Transaction::system_move(&Keypair::new(), Keypair::new().pubkey(), 20, hash(&[0]), 0); + assert_eq!( + verify_signature(&tx.signatures[0].to_string()).unwrap(), + tx.signatures[0] + ); + let bad_signature = "a1b2c3d4"; + assert_eq!( + verify_signature(&bad_signature.to_string()), + Err(Error::invalid_request()) + ); + } +} diff --git a/book/rpc_pubsub.rs b/book/rpc_pubsub.rs new file mode 100644 index 00000000000000..1bf4bfc86624ef --- /dev/null +++ b/book/rpc_pubsub.rs @@ -0,0 +1,632 @@ +//! The `pubsub` module implements a threaded subscription service on client RPC request + +use bank::Bank; +use bs58; +use jsonrpc_core::futures::Future; +use jsonrpc_core::*; +use jsonrpc_macros::pubsub; +use jsonrpc_pubsub::{PubSubHandler, Session, SubscriptionId}; +use jsonrpc_ws_server::{RequestContext, Sender, ServerBuilder}; +use rpc::{JsonRpcRequestProcessor, RpcSignatureStatus}; +use service::Service; +use signature::{Keypair, KeypairUtil, Signature}; +use solana_sdk::account::Account; +use solana_sdk::pubkey::Pubkey; +use std::collections::HashMap; +use std::mem; +use std::net::SocketAddr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{atomic, Arc, RwLock}; +use std::thread::{self, sleep, Builder, JoinHandle}; +use std::time::Duration; + +pub enum ClientState { + Uninitialized, + Init(Sender), +} + +pub struct PubSubService { + thread_hdl: JoinHandle<()>, + exit: Arc, +} + +impl Service for PubSubService { + type JoinReturnType = (); + + fn join(self) -> thread::Result<()> { + self.thread_hdl.join() + } +} + +impl PubSubService { + pub fn new(bank: &Arc, pubsub_addr: SocketAddr) -> Self { + let rpc = RpcSolPubSubImpl::new(JsonRpcRequestProcessor::new(bank.clone()), bank.clone()); + let exit = Arc::new(AtomicBool::new(false)); + let exit_ = exit.clone(); + let thread_hdl = Builder::new() + .name("solana-pubsub".to_string()) + .spawn(move || { + let mut io = PubSubHandler::default(); + io.extend_with(rpc.to_delegate()); + + let server = ServerBuilder::with_meta_extractor(io, |context: &RequestContext| { + info!("New pubsub connection"); + let session = Arc::new(Session::new(context.sender().clone())); + session.on_drop(Box::new(|| { + info!("Pubsub connection dropped"); + })); + session + }) + .start(&pubsub_addr); + + if server.is_err() { + warn!("Pubsub service unavailable: unable to bind to port {}. \nMake sure this port is not already in use by another application", pubsub_addr.port()); + return; + } + while !exit_.load(Ordering::Relaxed) { + sleep(Duration::from_millis(100)); + } + server.unwrap().close(); + () + }) + .unwrap(); + PubSubService { thread_hdl, exit } + } + + pub fn exit(&self) { + self.exit.store(true, Ordering::Relaxed); + } + + pub fn close(self) -> thread::Result<()> { + self.exit(); + self.join() + } +} + +build_rpc_trait! { + pub trait RpcSolPubSub { + type Metadata; + + #[pubsub(name = "accountNotification")] { + // Get notification every time account userdata is changed + // Accepts pubkey parameter as base-58 encoded string + #[rpc(name = "accountSubscribe")] + fn account_subscribe(&self, Self::Metadata, pubsub::Subscriber, String); + + // Unsubscribe from account notification subscription. + #[rpc(name = "accountUnsubscribe")] + fn account_unsubscribe(&self, SubscriptionId) -> Result; + } + #[pubsub(name = "signatureNotification")] { + // Get notification when signature is verified + // Accepts signature parameter as base-58 encoded string + #[rpc(name = "signatureSubscribe")] + fn signature_subscribe(&self, Self::Metadata, pubsub::Subscriber, String); + + // Unsubscribe from signature notification subscription. + #[rpc(name = "signatureUnsubscribe")] + fn signature_unsubscribe(&self, SubscriptionId) -> Result; + } + } +} + +struct RpcSolPubSubImpl { + uid: Arc, + request_processor: JsonRpcRequestProcessor, + bank: Arc, + account_subscriptions: Arc>>, + signature_subscriptions: Arc>>, +} + +impl RpcSolPubSubImpl { + fn new(request_processor: JsonRpcRequestProcessor, bank: Arc) -> Self { + RpcSolPubSubImpl { + uid: Default::default(), + request_processor, + bank, + account_subscriptions: Default::default(), + signature_subscriptions: Default::default(), + } + } +} + +impl RpcSolPubSub for RpcSolPubSubImpl { + type Metadata = Arc; + + fn account_subscribe( + &self, + _meta: Self::Metadata, + subscriber: pubsub::Subscriber, + pubkey_str: String, + ) { + let pubkey_vec = bs58::decode(pubkey_str).into_vec().unwrap(); + if pubkey_vec.len() != mem::size_of::() { + subscriber + .reject(Error { + code: ErrorCode::InvalidParams, + message: "Invalid Request: Invalid pubkey provided".into(), + data: None, + }).unwrap(); + return; + } + let pubkey = Pubkey::new(&pubkey_vec); + + let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst); + let sub_id = SubscriptionId::Number(id as u64); + info!("account_subscribe: account={:?} id={:?}", pubkey, sub_id); + let sink = subscriber.assign_id(sub_id.clone()).unwrap(); + let bank_sub_id = Keypair::new().pubkey(); + self.account_subscriptions + .write() + .unwrap() + .insert(sub_id.clone(), (bank_sub_id, pubkey)); + + self.bank + .add_account_subscription(bank_sub_id, pubkey, sink); + } + + fn account_unsubscribe(&self, id: SubscriptionId) -> Result { + info!("account_unsubscribe: id={:?}", id); + if let Some((bank_sub_id, pubkey)) = self.account_subscriptions.write().unwrap().remove(&id) + { + self.bank.remove_account_subscription(&bank_sub_id, &pubkey); + Ok(true) + } else { + Err(Error { + code: ErrorCode::InvalidParams, + message: "Invalid Request: Subscription id does not exist".into(), + data: None, + }) + } + } + + fn signature_subscribe( + &self, + _meta: Self::Metadata, + subscriber: pubsub::Subscriber, + signature_str: String, + ) { + info!("signature_subscribe"); + let signature_vec = bs58::decode(signature_str).into_vec().unwrap(); + if signature_vec.len() != mem::size_of::() { + subscriber + .reject(Error { + code: ErrorCode::InvalidParams, + message: "Invalid Request: Invalid signature provided".into(), + data: None, + }).unwrap(); + return; + } + let signature = Signature::new(&signature_vec); + + let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst); + let sub_id = SubscriptionId::Number(id as u64); + let sink = subscriber.assign_id(sub_id.clone()).unwrap(); + let bank_sub_id = Keypair::new().pubkey(); + self.signature_subscriptions + .write() + .unwrap() + .insert(sub_id.clone(), (bank_sub_id, signature)); + + match self.request_processor.get_signature_status(signature) { + Ok(_) => { + sink.notify(Ok(RpcSignatureStatus::Confirmed)) + .wait() + .unwrap(); + self.signature_subscriptions + .write() + .unwrap() + .remove(&sub_id); + } + Err(_) => { + self.bank + .add_signature_subscription(bank_sub_id, signature, sink); + } + } + } + + fn signature_unsubscribe(&self, id: SubscriptionId) -> Result { + info!("signature_unsubscribe"); + if let Some((bank_sub_id, signature)) = + self.signature_subscriptions.write().unwrap().remove(&id) + { + self.bank + .remove_signature_subscription(&bank_sub_id, &signature); + Ok(true) + } else { + Err(Error { + code: ErrorCode::InvalidParams, + message: "Invalid Request: Subscription id does not exist".into(), + data: None, + }) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use budget_program::BudgetState; + use budget_transaction::BudgetTransaction; + use jsonrpc_core::futures::sync::mpsc; + use mint::Mint; + use signature::{Keypair, KeypairUtil}; + use std::net::{IpAddr, Ipv4Addr}; + use system_transaction::SystemTransaction; + use tokio::prelude::{Async, Stream}; + use transaction::Transaction; + + #[test] + fn test_pubsub_new() { + let alice = Mint::new(10_000); + let bank = Bank::new(&alice); + let pubsub_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0); + let pubsub_service = PubSubService::new(&Arc::new(bank), pubsub_addr); + let thread = pubsub_service.thread_hdl.thread(); + assert_eq!(thread.name().unwrap(), "solana-pubsub"); + } + + #[test] + fn test_signature_subscribe() { + let alice = Mint::new(10_000); + let bob = Keypair::new(); + let bob_pubkey = bob.pubkey(); + let bank = Bank::new(&alice); + let arc_bank = Arc::new(bank); + let last_id = arc_bank.last_id(); + + let (sender, mut receiver) = mpsc::channel(1); + let session = Arc::new(Session::new(sender)); + + let mut io = PubSubHandler::default(); + let rpc = RpcSolPubSubImpl::new( + JsonRpcRequestProcessor::new(arc_bank.clone()), + arc_bank.clone(), + ); + io.extend_with(rpc.to_delegate()); + + // Test signature subscription + let tx = Transaction::system_move(&alice.keypair(), bob_pubkey, 20, last_id, 0); + + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"signatureSubscribe","params":["{}"]}}"#, + tx.signatures[0].to_string() + ); + let res = io.handle_request_sync(&req, session.clone()); + let expected = format!(r#"{{"jsonrpc":"2.0","result":0,"id":1}}"#); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + + // Test bad parameter + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"signatureSubscribe","params":["a1b2c3"]}}"# + ); + let res = io.handle_request_sync(&req, session.clone()); + let expected = format!(r#"{{"jsonrpc":"2.0","error":{{"code":-32602,"message":"Invalid Request: Invalid signature provided"}},"id":1}}"#); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + + arc_bank + .process_transaction(&tx) + .expect("process transaction"); + sleep(Duration::from_millis(200)); + + // Test signature confirmation notification + let string = receiver.poll(); + assert!(string.is_ok()); + if let Async::Ready(Some(response)) = string.unwrap() { + let expected = format!(r#"{{"jsonrpc":"2.0","method":"signatureNotification","params":{{"result":"Confirmed","subscription":0}}}}"#); + assert_eq!(expected, response); + } + + // Test subscription id increment + let tx = Transaction::system_move(&alice.keypair(), bob_pubkey, 10, last_id, 0); + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"signatureSubscribe","params":["{}"]}}"#, + tx.signatures[0].to_string() + ); + let res = io.handle_request_sync(&req, session.clone()); + let expected = format!(r#"{{"jsonrpc":"2.0","result":1,"id":1}}"#); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + } + + #[test] + fn test_signature_unsubscribe() { + let alice = Mint::new(10_000); + let bob_pubkey = Keypair::new().pubkey(); + let bank = Bank::new(&alice); + let arc_bank = Arc::new(bank); + let last_id = arc_bank.last_id(); + + let (sender, _receiver) = mpsc::channel(1); + let session = Arc::new(Session::new(sender)); + + let mut io = PubSubHandler::default(); + let rpc = RpcSolPubSubImpl::new( + JsonRpcRequestProcessor::new(arc_bank.clone()), + arc_bank.clone(), + ); + io.extend_with(rpc.to_delegate()); + + let tx = Transaction::system_move(&alice.keypair(), bob_pubkey, 20, last_id, 0); + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"signatureSubscribe","params":["{}"]}}"#, + tx.signatures[0].to_string() + ); + let _res = io.handle_request_sync(&req, session.clone()); + + let req = + format!(r#"{{"jsonrpc":"2.0","id":1,"method":"signatureUnsubscribe","params":[0]}}"#); + let res = io.handle_request_sync(&req, session.clone()); + + let expected = format!(r#"{{"jsonrpc":"2.0","result":true,"id":1}}"#); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + + // Test bad parameter + let req = + format!(r#"{{"jsonrpc":"2.0","id":1,"method":"signatureUnsubscribe","params":[1]}}"#); + let res = io.handle_request_sync(&req, session.clone()); + let expected = format!(r#"{{"jsonrpc":"2.0","error":{{"code":-32602,"message":"Invalid Request: Subscription id does not exist"}},"id":1}}"#); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + } + + #[test] + fn test_account_subscribe() { + let alice = Mint::new(10_000); + let bob_pubkey = Keypair::new().pubkey(); + let witness = Keypair::new(); + let contract_funds = Keypair::new(); + let contract_state = Keypair::new(); + let budget_program_id = BudgetState::id(); + let loader = Pubkey::default(); // TODO + let executable = false; // TODO + let bank = Bank::new(&alice); + let arc_bank = Arc::new(bank); + let last_id = arc_bank.last_id(); + + let (sender, mut receiver) = mpsc::channel(1); + let session = Arc::new(Session::new(sender)); + + let mut io = PubSubHandler::default(); + let rpc = RpcSolPubSubImpl::new( + JsonRpcRequestProcessor::new(arc_bank.clone()), + arc_bank.clone(), + ); + io.extend_with(rpc.to_delegate()); + + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"accountSubscribe","params":["{}"]}}"#, + contract_state.pubkey().to_string() + ); + + let res = io.handle_request_sync(&req, session.clone()); + let expected = format!(r#"{{"jsonrpc":"2.0","result":0,"id":1}}"#); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + + // Test bad parameter + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"accountSubscribe","params":["a1b2c3"]}}"# + ); + let res = io.handle_request_sync(&req, session.clone()); + let expected = format!(r#"{{"jsonrpc":"2.0","error":{{"code":-32602,"message":"Invalid Request: Invalid pubkey provided"}},"id":1}}"#); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + + let tx = Transaction::system_create( + &alice.keypair(), + contract_funds.pubkey(), + last_id, + 50, + 0, + budget_program_id, + 0, + ); + arc_bank + .process_transaction(&tx) + .expect("process transaction"); + + let tx = Transaction::system_create( + &alice.keypair(), + contract_state.pubkey(), + last_id, + 1, + 196, + budget_program_id, + 0, + ); + + arc_bank + .process_transaction(&tx) + .expect("process transaction"); + + // Test signature confirmation notification #1 + let string = receiver.poll(); + assert!(string.is_ok()); + + let expected_userdata = arc_bank + .get_account(&contract_state.pubkey()) + .unwrap() + .userdata; + + let expected = json!({ + "jsonrpc": "2.0", + "method": "accountNotification", + "params": { + "result": { + "owner": budget_program_id, + "tokens": 1, + "userdata": expected_userdata, + "executable": executable, + "loader": loader, + + }, + "subscription": 0, + } + }); + + if let Async::Ready(Some(response)) = string.unwrap() { + assert_eq!(serde_json::to_string(&expected).unwrap(), response); + } + + let tx = Transaction::budget_new_when_signed( + &contract_funds, + bob_pubkey, + contract_state.pubkey(), + witness.pubkey(), + None, + 50, + last_id, + ); + arc_bank + .process_transaction(&tx) + .expect("process transaction"); + sleep(Duration::from_millis(200)); + + // Test signature confirmation notification #2 + let string = receiver.poll(); + assert!(string.is_ok()); + let expected_userdata = arc_bank + .get_account(&contract_state.pubkey()) + .unwrap() + .userdata; + let expected = json!({ + "jsonrpc": "2.0", + "method": "accountNotification", + "params": { + "result": { + "owner": budget_program_id, + "tokens": 51, + "userdata": expected_userdata, + "executable": executable, + "loader": loader, + }, + "subscription": 0, + } + }); + + if let Async::Ready(Some(response)) = string.unwrap() { + assert_eq!(serde_json::to_string(&expected).unwrap(), response); + } + + let tx = Transaction::system_new(&alice.keypair(), witness.pubkey(), 1, last_id); + arc_bank + .process_transaction(&tx) + .expect("process transaction"); + sleep(Duration::from_millis(200)); + let tx = Transaction::budget_new_signature( + &witness, + contract_state.pubkey(), + bob_pubkey, + last_id, + ); + arc_bank + .process_transaction(&tx) + .expect("process transaction"); + sleep(Duration::from_millis(200)); + + let expected_userdata = arc_bank + .get_account(&contract_state.pubkey()) + .unwrap() + .userdata; + let expected = json!({ + "jsonrpc": "2.0", + "method": "accountNotification", + "params": { + "result": { + "owner": budget_program_id, + "tokens": 1, + "userdata": expected_userdata, + "executable": executable, + "loader": loader, + }, + "subscription": 0, + } + }); + let string = receiver.poll(); + assert!(string.is_ok()); + if let Async::Ready(Some(response)) = string.unwrap() { + assert_eq!(serde_json::to_string(&expected).unwrap(), response); + } + } + + #[test] + fn test_account_unsubscribe() { + let alice = Mint::new(10_000); + let bob_pubkey = Keypair::new().pubkey(); + let bank = Bank::new(&alice); + let arc_bank = Arc::new(bank); + + let (sender, _receiver) = mpsc::channel(1); + let session = Arc::new(Session::new(sender)); + + let mut io = PubSubHandler::default(); + let rpc = RpcSolPubSubImpl::new( + JsonRpcRequestProcessor::new(arc_bank.clone()), + arc_bank.clone(), + ); + + io.extend_with(rpc.to_delegate()); + + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"accountSubscribe","params":["{}"]}}"#, + bob_pubkey.to_string() + ); + let _res = io.handle_request_sync(&req, session.clone()); + + let req = + format!(r#"{{"jsonrpc":"2.0","id":1,"method":"accountUnsubscribe","params":[0]}}"#); + let res = io.handle_request_sync(&req, session.clone()); + + let expected = format!(r#"{{"jsonrpc":"2.0","result":true,"id":1}}"#); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + + // Test bad parameter + let req = + format!(r#"{{"jsonrpc":"2.0","id":1,"method":"accountUnsubscribe","params":[1]}}"#); + let res = io.handle_request_sync(&req, session.clone()); + let expected = format!(r#"{{"jsonrpc":"2.0","error":{{"code":-32602,"message":"Invalid Request: Subscription id does not exist"}},"id":1}}"#); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + } +} diff --git a/book/rpc_request.rs b/book/rpc_request.rs new file mode 100644 index 00000000000000..d1b58f9659b2bd --- /dev/null +++ b/book/rpc_request.rs @@ -0,0 +1,191 @@ +use reqwest; +use reqwest::header::CONTENT_TYPE; +use serde_json::{self, Value}; +use std::{error, fmt}; + +pub enum RpcRequest { + ConfirmTransaction, + GetAccountInfo, + GetBalance, + GetFinality, + GetLastId, + GetSignatureStatus, + GetTransactionCount, + RequestAirdrop, + SendTransaction, +} +impl RpcRequest { + pub fn make_rpc_request( + &self, + rpc_addr: &str, + id: u64, + params: Option, + ) -> Result> { + let request = self.build_request_json(id, params); + let client = reqwest::Client::new(); + let mut response = client + .post(rpc_addr) + .header(CONTENT_TYPE, "application/json") + .body(request.to_string()) + .send()?; + let json: Value = serde_json::from_str(&response.text()?)?; + if json["error"].is_object() { + Err(RpcError::RpcRequestError(format!( + "RPC Error response: {}", + serde_json::to_string(&json["error"]).unwrap() + )))? + } + Ok(json["result"].clone()) + } + fn build_request_json(&self, id: u64, params: Option) -> Value { + let jsonrpc = "2.0"; + let method = match self { + RpcRequest::ConfirmTransaction => "confirmTransaction", + RpcRequest::GetAccountInfo => "getAccountInfo", + RpcRequest::GetBalance => "getBalance", + RpcRequest::GetFinality => "getFinality", + RpcRequest::GetLastId => "getLastId", + RpcRequest::GetSignatureStatus => "getSignatureStatus", + RpcRequest::GetTransactionCount => "getTransactionCount", + RpcRequest::RequestAirdrop => "requestAirdrop", + RpcRequest::SendTransaction => "sendTransaction", + }; + let mut request = json!({ + "jsonrpc": jsonrpc, + "id": id, + "method": method, + }); + if let Some(param_string) = params { + request["params"] = param_string; + } + request + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum RpcError { + RpcRequestError(String), +} + +impl fmt::Display for RpcError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "invalid") + } +} + +impl error::Error for RpcError { + fn description(&self) -> &str { + "invalid" + } + + fn cause(&self) -> Option<&error::Error> { + // Generic error, underlying cause isn't tracked. + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + use jsonrpc_core::*; + use jsonrpc_http_server::*; + use serde_json::Number; + use std::net::{Ipv4Addr, SocketAddr}; + use std::sync::mpsc::channel; + use std::thread; + + #[test] + fn test_build_request_json() { + let test_request = RpcRequest::GetAccountInfo; + let request = test_request.build_request_json( + 1, + Some(json!(["deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx"])), + ); + assert_eq!(request["method"], "getAccountInfo"); + assert_eq!( + request["params"], + json!(["deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx"]) + ); + + let test_request = RpcRequest::GetBalance; + let request = test_request.build_request_json( + 1, + Some(json!(["deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx"])), + ); + assert_eq!(request["method"], "getBalance"); + + let test_request = RpcRequest::GetFinality; + let request = test_request.build_request_json(1, None); + assert_eq!(request["method"], "getFinality"); + assert_eq!(request["params"], json!(null)); + + let test_request = RpcRequest::GetLastId; + let request = test_request.build_request_json(1, None); + assert_eq!(request["method"], "getLastId"); + + let test_request = RpcRequest::GetTransactionCount; + let request = test_request.build_request_json(1, None); + assert_eq!(request["method"], "getTransactionCount"); + + let test_request = RpcRequest::RequestAirdrop; + let request = test_request.build_request_json(1, None); + assert_eq!(request["method"], "requestAirdrop"); + + let test_request = RpcRequest::SendTransaction; + let request = test_request.build_request_json(1, None); + assert_eq!(request["method"], "sendTransaction"); + } + #[test] + fn test_make_rpc_request() { + let (sender, receiver) = channel(); + thread::spawn(move || { + let rpc_addr = socketaddr!(0, 0); + let mut io = IoHandler::default(); + // Successful request + io.add_method("getBalance", |_params: Params| { + Ok(Value::Number(Number::from(50))) + }); + // Failed request + io.add_method("getLastId", |params: Params| { + if params != Params::None { + Err(Error::invalid_request()) + } else { + Ok(Value::String( + "deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx".to_string(), + )) + } + }); + + let server = ServerBuilder::new(io) + .threads(1) + .cors(DomainsValidation::AllowOnly(vec![ + AccessControlAllowOrigin::Any, + ])).start_http(&rpc_addr) + .expect("Unable to start RPC server"); + sender.send(*server.address()).unwrap(); + server.wait(); + }); + + let rpc_addr = receiver.recv().unwrap(); + let rpc_addr = format!("http://{}", rpc_addr.to_string()); + + let balance = RpcRequest::GetBalance.make_rpc_request( + &rpc_addr, + 1, + Some(json!(["deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx"])), + ); + assert!(balance.is_ok()); + assert_eq!(balance.unwrap().as_u64().unwrap(), 50); + + let last_id = RpcRequest::GetLastId.make_rpc_request(&rpc_addr, 2, None); + assert!(last_id.is_ok()); + assert_eq!( + last_id.unwrap().as_str().unwrap(), + "deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx" + ); + + // Send erroneous parameter + let last_id = RpcRequest::GetLastId.make_rpc_request(&rpc_addr, 3, Some(json!("paramter"))); + assert_eq!(last_id.is_err(), true); + } +} diff --git a/book/runtime.html b/book/runtime.html new file mode 100644 index 00000000000000..243290510c054e --- /dev/null +++ b/book/runtime.html @@ -0,0 +1,279 @@ + + + + + + The Runtime - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

Runtime

+

The goal with the runtime is to have a general purpose execution environment +that is highly parallelizable. To achieve this goal the runtime forces each +Instruction to specify all of its memory dependencies up front, and therefore a +single Instruction cannot cause a dynamic memory allocation. An explicit +Instruction for memory allocation from the SystemProgram::CreateAccount is +the only way to allocate new memory in the engine. A Transaction may compose +multiple Instruction, including SystemProgram::CreateAccount, into a single +atomic sequence which allows for memory allocation to achieve a result that is +similar to dynamic allocation.

+

State

+

State is addressed by an Account which is at the moment simply the Pubkey. Our +goal is to eliminate memory allocation from within the program itself. Thus +the client of the program provides all the state that is necessary for the +program to execute in the transaction itself. The runtime interacts with the +program through an entry point with a well defined interface. The userdata +stored in an Account is an opaque type to the runtime, a Vec<u8>, the +contents of which the program code has full control over.

+

The Transaction structure specifies a list of Pubkey's and signatures for those +keys and a sequential list of instructions that will operate over the state's +associated with the account_keys. For the transaction to be committed all +the instructions must execute successfully, if any abort the whole transaction +fails to commit.

+

Account structure Accounts maintain token state as well as program specific

+

memory.

+

Transaction Engine

+

At its core, the engine looks up all the Pubkeys maps them to accounts and +routs them to the program_id entry point.

+

Execution

+

Transactions are batched and processed in a pipeline

+

Runtime pipeline

+

At the execute stage, the loaded pages have no data dependencies, so all the +programs can be executed in parallel.

+

The runtime enforces the following rules:

+
    +
  1. The program_id code is the only code that will modify the contents of +Account::userdata of Account's that have been assigned to it. This means +that upon assignment userdata vector is guaranteed to be 0.
  2. +
  3. Total balances on all the accounts is equal before and after execution of a +Transaction.
  4. +
  5. Balances of each of the accounts not assigned to program_id must be equal +to or greater after the Transaction than before the transaction.
  6. +
  7. All Instructions in the Transaction executed without a failure.
  8. +
+

Entry Point Execution of the program involves mapping the Program's public

+

key to an entry point which takes a pointer to the transaction, and an array of +loaded pages.

+

System Interface

+

The interface is best described by the Instruction::userdata that the +user encodes.

+
    +
  • CreateAccount - This allows the user to create and assign an Account to a +Program.
  • +
  • Assign - allows the user to assign an existing account to a Program.
  • +
  • Move - moves tokens between Accounts that are associated with +SystemProgram. This cannot be used to move tokens of other Accounts. +Programs need to implement their own version of Move.
  • +
+

Notes

+
    +
  1. There is no dynamic memory allocation. Client's need to call the +SystemProgram to create memory before passing it to another program. This +Instruction can be composed into a single Transaction with the call to the +program itself.
  2. +
  3. Runtime guarantees that when memory is assigned to the Program it is zero +initialized.
  4. +
  5. Runtime guarantees that Program's code is the only thing that can modify +memory that its assigned to
  6. +
  7. Runtime guarantees that the Program can only spend tokens that are in +Accounts that are assigned to it
  8. +
  9. Runtime guarantees the balances belonging to Accounts are balanced before +and after the transaction
  10. +
  11. Runtime guarantees that multiple instructions all executed successfully when +a transaction is committed.
  12. +
+

Future Work

+ + +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/searcher.js b/book/searcher.js new file mode 100644 index 00000000000000..7fd97d4879269f --- /dev/null +++ b/book/searcher.js @@ -0,0 +1,477 @@ +"use strict"; +window.search = window.search || {}; +(function search(search) { + // Search functionality + // + // You can use !hasFocus() to prevent keyhandling in your key + // event handlers while the user is typing their search. + + if (!Mark || !elasticlunr) { + return; + } + + //IE 11 Compatibility from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith + if (!String.prototype.startsWith) { + String.prototype.startsWith = function(search, pos) { + return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; + }; + } + + var search_wrap = document.getElementById('search-wrapper'), + searchbar = document.getElementById('searchbar'), + searchbar_outer = document.getElementById('searchbar-outer'), + searchresults = document.getElementById('searchresults'), + searchresults_outer = document.getElementById('searchresults-outer'), + searchresults_header = document.getElementById('searchresults-header'), + searchicon = document.getElementById('search-toggle'), + content = document.getElementById('content'), + + searchindex = null, + doc_urls = [], + results_options = { + teaser_word_count: 30, + limit_results: 30, + }, + search_options = { + bool: "AND", + expand: true, + fields: { + title: {boost: 1}, + body: {boost: 1}, + breadcrumbs: {boost: 0} + } + }, + mark_exclude = [], + marker = new Mark(content), + current_searchterm = "", + URL_SEARCH_PARAM = 'search', + URL_MARK_PARAM = 'highlight', + teaser_count = 0, + + SEARCH_HOTKEY_KEYCODE = 83, + ESCAPE_KEYCODE = 27, + DOWN_KEYCODE = 40, + UP_KEYCODE = 38, + SELECT_KEYCODE = 13; + + function hasFocus() { + return searchbar === document.activeElement; + } + + function removeChildren(elem) { + while (elem.firstChild) { + elem.removeChild(elem.firstChild); + } + } + + // Helper to parse a url into its building blocks. + function parseURL(url) { + var a = document.createElement('a'); + a.href = url; + return { + source: url, + protocol: a.protocol.replace(':',''), + host: a.hostname, + port: a.port, + params: (function(){ + var ret = {}; + var seg = a.search.replace(/^\?/,'').split('&'); + var len = seg.length, i = 0, s; + for (;i': '>', + '"': '"', + "'": ''' + }; + var repl = function(c) { return MAP[c]; }; + return function(s) { + return s.replace(/[&<>'"]/g, repl); + }; + })(); + + function formatSearchMetric(count, searchterm) { + if (count == 1) { + return count + " search result for '" + searchterm + "':"; + } else if (count == 0) { + return "No search results for '" + searchterm + "'."; + } else { + return count + " search results for '" + searchterm + "':"; + } + } + + function formatSearchResult(result, searchterms) { + var teaser = makeTeaser(escapeHTML(result.doc.body), searchterms); + teaser_count++; + + // The ?URL_MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor + var url = doc_urls[result.ref].split("#"); + if (url.length == 1) { // no anchor found + url.push(""); + } + + return '' + result.doc.breadcrumbs + '' + + '' + + teaser + ''; + } + + function makeTeaser(body, searchterms) { + // The strategy is as follows: + // First, assign a value to each word in the document: + // Words that correspond to search terms (stemmer aware): 40 + // Normal words: 2 + // First word in a sentence: 8 + // Then use a sliding window with a constant number of words and count the + // sum of the values of the words within the window. Then use the window that got the + // maximum sum. If there are multiple maximas, then get the last one. + // Enclose the terms in . + var stemmed_searchterms = searchterms.map(function(w) { + return elasticlunr.stemmer(w.toLowerCase()); + }); + var searchterm_weight = 40; + var weighted = []; // contains elements of ["word", weight, index_in_document] + // split in sentences, then words + var sentences = body.toLowerCase().split('. '); + var index = 0; + var value = 0; + var searchterm_found = false; + for (var sentenceindex in sentences) { + var words = sentences[sentenceindex].split(' '); + value = 8; + for (var wordindex in words) { + var word = words[wordindex]; + if (word.length > 0) { + for (var searchtermindex in stemmed_searchterms) { + if (elasticlunr.stemmer(word).startsWith(stemmed_searchterms[searchtermindex])) { + value = searchterm_weight; + searchterm_found = true; + } + }; + weighted.push([word, value, index]); + value = 2; + } + index += word.length; + index += 1; // ' ' or '.' if last word in sentence + }; + index += 1; // because we split at a two-char boundary '. ' + }; + + if (weighted.length == 0) { + return body; + } + + var window_weight = []; + var window_size = Math.min(weighted.length, results_options.teaser_word_count); + + var cur_sum = 0; + for (var wordindex = 0; wordindex < window_size; wordindex++) { + cur_sum += weighted[wordindex][1]; + }; + window_weight.push(cur_sum); + for (var wordindex = 0; wordindex < weighted.length - window_size; wordindex++) { + cur_sum -= weighted[wordindex][1]; + cur_sum += weighted[wordindex + window_size][1]; + window_weight.push(cur_sum); + }; + + if (searchterm_found) { + var max_sum = 0; + var max_sum_window_index = 0; + // backwards + for (var i = window_weight.length - 1; i >= 0; i--) { + if (window_weight[i] > max_sum) { + max_sum = window_weight[i]; + max_sum_window_index = i; + } + }; + } else { + max_sum_window_index = 0; + } + + // add around searchterms + var teaser_split = []; + var index = weighted[max_sum_window_index][2]; + for (var i = max_sum_window_index; i < max_sum_window_index+window_size; i++) { + var word = weighted[i]; + if (index < word[2]) { + // missing text from index to start of `word` + teaser_split.push(body.substring(index, word[2])); + index = word[2]; + } + if (word[1] == searchterm_weight) { + teaser_split.push("") + } + index = word[2] + word[0].length; + teaser_split.push(body.substring(word[2], index)); + if (word[1] == searchterm_weight) { + teaser_split.push("") + } + }; + + return teaser_split.join(''); + } + + function init(config) { + results_options = config.results_options; + search_options = config.search_options; + searchbar_outer = config.searchbar_outer; + doc_urls = config.doc_urls; + searchindex = elasticlunr.Index.load(config.index); + + // Set up events + searchicon.addEventListener('click', function(e) { searchIconClickHandler(); }, false); + searchbar.addEventListener('keyup', function(e) { searchbarKeyUpHandler(); }, false); + document.addEventListener('keydown', function(e) { globalKeyHandler(e); }, false); + // If the user uses the browser buttons, do the same as if a reload happened + window.onpopstate = function(e) { doSearchOrMarkFromUrl(); }; + // Suppress "submit" events so the page doesn't reload when the user presses Enter + document.addEventListener('submit', function(e) { e.preventDefault(); }, false); + + // If reloaded, do the search or mark again, depending on the current url parameters + doSearchOrMarkFromUrl(); + } + + function unfocusSearchbar() { + // hacky, but just focusing a div only works once + var tmp = document.createElement('input'); + tmp.setAttribute('style', 'position: absolute; opacity: 0;'); + searchicon.appendChild(tmp); + tmp.focus(); + tmp.remove(); + } + + // On reload or browser history backwards/forwards events, parse the url and do search or mark + function doSearchOrMarkFromUrl() { + // Check current URL for search request + var url = parseURL(window.location.href); + if (url.params.hasOwnProperty(URL_SEARCH_PARAM) + && url.params[URL_SEARCH_PARAM] != "") { + showSearch(true); + searchbar.value = decodeURIComponent( + (url.params[URL_SEARCH_PARAM]+'').replace(/\+/g, '%20')); + searchbarKeyUpHandler(); // -> doSearch() + } else { + showSearch(false); + } + + if (url.params.hasOwnProperty(URL_MARK_PARAM)) { + var words = url.params[URL_MARK_PARAM].split(' '); + marker.mark(words, { + exclude: mark_exclude + }); + + var markers = document.querySelectorAll("mark"); + function hide() { + for (var i = 0; i < markers.length; i++) { + markers[i].classList.add("fade-out"); + window.setTimeout(function(e) { marker.unmark(); }, 300); + } + } + for (var i = 0; i < markers.length; i++) { + markers[i].addEventListener('click', hide); + } + } + } + + // Eventhandler for keyevents on `document` + function globalKeyHandler(e) { + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea') { return; } + + if (e.keyCode === ESCAPE_KEYCODE) { + e.preventDefault(); + searchbar.classList.remove("active"); + setSearchUrlParameters("", + (searchbar.value.trim() !== "") ? "push" : "replace"); + if (hasFocus()) { + unfocusSearchbar(); + } + showSearch(false); + marker.unmark(); + } else if (!hasFocus() && e.keyCode === SEARCH_HOTKEY_KEYCODE) { + e.preventDefault(); + showSearch(true); + window.scrollTo(0, 0); + searchbar.select(); + } else if (hasFocus() && e.keyCode === DOWN_KEYCODE) { + e.preventDefault(); + unfocusSearchbar(); + searchresults.firstElementChild.classList.add("focus"); + } else if (!hasFocus() && (e.keyCode === DOWN_KEYCODE + || e.keyCode === UP_KEYCODE + || e.keyCode === SELECT_KEYCODE)) { + // not `:focus` because browser does annoying scrolling + var focused = searchresults.querySelector("li.focus"); + if (!focused) return; + e.preventDefault(); + if (e.keyCode === DOWN_KEYCODE) { + var next = focused.nextElementSibling; + if (next) { + focused.classList.remove("focus"); + next.classList.add("focus"); + } + } else if (e.keyCode === UP_KEYCODE) { + focused.classList.remove("focus"); + var prev = focused.previousElementSibling; + if (prev) { + prev.classList.add("focus"); + } else { + searchbar.select(); + } + } else { // SELECT_KEYCODE + window.location.assign(focused.querySelector('a')); + } + } + } + + function showSearch(yes) { + if (yes) { + search_wrap.classList.remove('hidden'); + searchicon.setAttribute('aria-expanded', 'true'); + } else { + search_wrap.classList.add('hidden'); + searchicon.setAttribute('aria-expanded', 'false'); + var results = searchresults.children; + for (var i = 0; i < results.length; i++) { + results[i].classList.remove("focus"); + } + } + } + + function showResults(yes) { + if (yes) { + searchresults_outer.classList.remove('hidden'); + } else { + searchresults_outer.classList.add('hidden'); + } + } + + // Eventhandler for search icon + function searchIconClickHandler() { + if (search_wrap.classList.contains('hidden')) { + showSearch(true); + window.scrollTo(0, 0); + searchbar.select(); + } else { + showSearch(false); + } + } + + // Eventhandler for keyevents while the searchbar is focused + function searchbarKeyUpHandler() { + var searchterm = searchbar.value.trim(); + if (searchterm != "") { + searchbar.classList.add("active"); + doSearch(searchterm); + } else { + searchbar.classList.remove("active"); + showResults(false); + removeChildren(searchresults); + } + + setSearchUrlParameters(searchterm, "push_if_new_search_else_replace"); + + // Remove marks + marker.unmark(); + } + + // Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and #heading-anchor . + // `action` can be one of "push", "replace", "push_if_new_search_else_replace" + // and replaces or pushes a new browser history item. + // "push_if_new_search_else_replace" pushes if there is no `?URL_SEARCH_PARAM=abc` yet. + function setSearchUrlParameters(searchterm, action) { + var url = parseURL(window.location.href); + var first_search = ! url.params.hasOwnProperty(URL_SEARCH_PARAM); + if (searchterm != "" || action == "push_if_new_search_else_replace") { + url.params[URL_SEARCH_PARAM] = searchterm; + delete url.params[URL_MARK_PARAM]; + url.hash = ""; + } else { + delete url.params[URL_SEARCH_PARAM]; + } + // A new search will also add a new history item, so the user can go back + // to the page prior to searching. A updated search term will only replace + // the url. + if (action == "push" || (action == "push_if_new_search_else_replace" && first_search) ) { + history.pushState({}, document.title, renderURL(url)); + } else if (action == "replace" || (action == "push_if_new_search_else_replace" && !first_search) ) { + history.replaceState({}, document.title, renderURL(url)); + } + } + + function doSearch(searchterm) { + + // Don't search the same twice + if (current_searchterm == searchterm) { return; } + else { current_searchterm = searchterm; } + + if (searchindex == null) { return; } + + // Do the actual search + var results = searchindex.search(searchterm, search_options); + var resultcount = Math.min(results.length, results_options.limit_results); + + // Display search metrics + searchresults_header.innerText = formatSearchMetric(resultcount, searchterm); + + // Clear and insert results + var searchterms = searchterm.split(' '); + removeChildren(searchresults); + for(var i = 0; i < resultcount ; i++){ + var resultElem = document.createElement('li'); + resultElem.innerHTML = formatSearchResult(results[i], searchterms); + searchresults.appendChild(resultElem); + } + + // Display results + showResults(true); + } + + fetch(path_to_root + 'searchindex.json') + .then(response => response.json()) + .then(json => init(json)) + .catch(error => { // Try to load searchindex.js if fetch failed + var script = document.createElement('script'); + script.src = path_to_root + 'searchindex.js'; + script.onload = () => init(window.search); + document.head.appendChild(script); + }); + + // Exported functions + search.hasFocus = hasFocus; +})(window.search); diff --git a/book/searchindex.js b/book/searchindex.js new file mode 100644 index 00000000000000..389fddd04d93b1 --- /dev/null +++ b/book/searchindex.js @@ -0,0 +1 @@ +window.search = {"doc_urls":["introduction.html#disclaimer","introduction.html#introduction","terminology.html#terminology","terminology.html#teminology-currently-in-use","terminology.html#terminology-reserved-for-future-use","synchronization.html#synchronization","vdf.html#introduction-to-vdfs","poh.html#proof-of-history","poh.html#relationship-to-consensus-mechanisms","poh.html#relationship-to-vdfs","leader-scheduler.html#leader-rotation","leader-scheduler.html#leader-seed-generation","leader-scheduler.html#leader-rotation","leader-scheduler.html#partitions-forks","leader-scheduler.html#examples","leader-scheduler.html#small-partition","leader-scheduler.html#leader-timeout","leader-scheduler.html#network-variables","fullnode.html#fullnode","fullnode.html#pipelining","fullnode.html#pipelining-in-the-fullnode","tpu.html#the-transaction-processing-unit","tvu.html#the-transaction-validation-unit","ncp.html#ncp","jsonrpc-service.html#jsonrpcservice","avalanche.html#avalanche-replication","storage.html#storage","storage.html#background","storage.html#optimization-with-poh","storage.html#network","storage.html#constraints","storage.html#validation-and-replication-protocol","programs.html#the-solana-sdk","programs.html#introduction","programs.html#client-interactions-with-solana","programs.html#persistent-storage","runtime.html#runtime","runtime.html#state","runtime.html#account-structure-accounts-maintain-token-state-as-well-as-program-specific","runtime.html#transaction-engine","runtime.html#execution","runtime.html#entry-point-execution-of-the-program-involves-mapping-the-programs-public","runtime.html#system-interface","runtime.html#notes","runtime.html#future-work","ledger.html#ledger-format","appendix.html#appendix","jsonrpc-api.html#json-rpc-api","jsonrpc-api.html#rpc-http-endpoint","jsonrpc-api.html#rpc-pubsub-websocket-endpoint","jsonrpc-api.html#methods","jsonrpc-api.html#request-formatting","jsonrpc-api.html#definitions","jsonrpc-api.html#json-rpc-api-reference","jsonrpc-api.html#confirmtransaction","jsonrpc-api.html#getbalance","jsonrpc-api.html#getaccountinfo","jsonrpc-api.html#getlastid","jsonrpc-api.html#getsignaturestatus","jsonrpc-api.html#gettransactioncount","jsonrpc-api.html#requestairdrop","jsonrpc-api.html#sendtransaction","jsonrpc-api.html#subscription-websocket","jsonrpc-api.html#accountsubscribe","jsonrpc-api.html#accountunsubscribe","jsonrpc-api.html#signaturesubscribe","jsonrpc-api.html#signatureunsubscribe","wallet.html#solana-wallet-cli","wallet.html#examples","wallet.html#usage"],"index":{"documentStore":{"docInfo":{"0":{"body":27,"breadcrumbs":1,"title":1},"1":{"body":172,"breadcrumbs":1,"title":1},"10":{"body":46,"breadcrumbs":3,"title":2},"11":{"body":38,"breadcrumbs":4,"title":3},"12":{"body":73,"breadcrumbs":3,"title":2},"13":{"body":259,"breadcrumbs":3,"title":2},"14":{"body":0,"breadcrumbs":2,"title":1},"15":{"body":51,"breadcrumbs":3,"title":2},"16":{"body":56,"breadcrumbs":3,"title":2},"17":{"body":57,"breadcrumbs":3,"title":2},"18":{"body":0,"breadcrumbs":1,"title":1},"19":{"body":106,"breadcrumbs":1,"title":1},"2":{"body":0,"breadcrumbs":1,"title":1},"20":{"body":44,"breadcrumbs":2,"title":2},"21":{"body":0,"breadcrumbs":4,"title":3},"22":{"body":0,"breadcrumbs":4,"title":3},"23":{"body":9,"breadcrumbs":2,"title":1},"24":{"body":0,"breadcrumbs":2,"title":1},"25":{"body":81,"breadcrumbs":2,"title":2},"26":{"body":0,"breadcrumbs":3,"title":1},"27":{"body":129,"breadcrumbs":3,"title":1},"28":{"body":74,"breadcrumbs":4,"title":2},"29":{"body":41,"breadcrumbs":3,"title":1},"3":{"body":106,"breadcrumbs":3,"title":3},"30":{"body":50,"breadcrumbs":3,"title":1},"31":{"body":262,"breadcrumbs":5,"title":3},"32":{"body":0,"breadcrumbs":2,"title":2},"33":{"body":11,"breadcrumbs":1,"title":1},"34":{"body":42,"breadcrumbs":3,"title":3},"35":{"body":60,"breadcrumbs":2,"title":2},"36":{"body":53,"breadcrumbs":3,"title":1},"37":{"body":71,"breadcrumbs":3,"title":1},"38":{"body":1,"breadcrumbs":11,"title":9},"39":{"body":11,"breadcrumbs":4,"title":2},"4":{"body":35,"breadcrumbs":4,"title":4},"40":{"body":54,"breadcrumbs":3,"title":1},"41":{"body":9,"breadcrumbs":10,"title":8},"42":{"body":38,"breadcrumbs":4,"title":2},"43":{"body":61,"breadcrumbs":3,"title":1},"44":{"body":5,"breadcrumbs":4,"title":2},"45":{"body":0,"breadcrumbs":4,"title":2},"46":{"body":9,"breadcrumbs":1,"title":1},"47":{"body":25,"breadcrumbs":4,"title":3},"48":{"body":6,"breadcrumbs":4,"title":3},"49":{"body":6,"breadcrumbs":5,"title":4},"5":{"body":196,"breadcrumbs":1,"title":1},"50":{"body":15,"breadcrumbs":2,"title":1},"51":{"body":85,"breadcrumbs":3,"title":2},"52":{"body":24,"breadcrumbs":2,"title":1},"53":{"body":0,"breadcrumbs":5,"title":4},"54":{"body":36,"breadcrumbs":2,"title":1},"55":{"body":38,"breadcrumbs":2,"title":1},"56":{"body":81,"breadcrumbs":2,"title":1},"57":{"body":32,"breadcrumbs":2,"title":1},"58":{"body":88,"breadcrumbs":2,"title":1},"59":{"body":29,"breadcrumbs":2,"title":1},"6":{"body":104,"breadcrumbs":3,"title":2},"60":{"body":47,"breadcrumbs":2,"title":1},"61":{"body":238,"breadcrumbs":2,"title":1},"62":{"body":16,"breadcrumbs":3,"title":2},"63":{"body":43,"breadcrumbs":2,"title":1},"64":{"body":27,"breadcrumbs":2,"title":1},"65":{"body":44,"breadcrumbs":2,"title":1},"66":{"body":27,"breadcrumbs":2,"title":1},"67":{"body":7,"breadcrumbs":4,"title":3},"68":{"body":220,"breadcrumbs":2,"title":1},"69":{"body":402,"breadcrumbs":2,"title":1},"7":{"body":3,"breadcrumbs":3,"title":2},"8":{"body":91,"breadcrumbs":4,"title":3},"9":{"body":124,"breadcrumbs":3,"title":2}},"docs":{"0":{"body":"All claims, content, designs, algorithms, estimates, roadmaps, specifications, and performance measurements described in this project are done with the author's best effort. It is up to the reader to check and validate their accuracy and truthfulness. Furthermore nothing in this project constitutes a solicitation for investment.","breadcrumbs":"Disclaimer","id":"0","title":"Disclaimer"},"1":{"body":"This document defines the architecture of Solana, a blockchain built from the ground up for scale. The goal of the architecture is to demonstrate there exists a set of software algorithms that in combination, removes software as a performance bottleneck, allowing transaction throughput to scale proportionally with network bandwidth. The architecture goes on to satisfy all three desirable properties of a proper blockchain, that it not only be scalable, but that it is also secure and decentralized. With this architecture, we calculate a theoretical upper bound of 710 thousand transactions per second (tps) on a standard gigabit network and 28.4 million tps on 40 gigabit. In practice, our focus has been on gigabit. We soak-tested a 150 node permissioned testnet and it is able to maintain a mean transaction throughput of approximately 200 thousand tps with peaks over 400 thousand. Furthermore, we have found high throughput extends beyond simple payments, and that this architecture is also able to perform safe, concurrent execution of programs authored in a general purpose programming language, such as C. We feel the extension warrants industry focus on an additional performance metric already common in the CPU industry, millions of instructions per second or mips. By measuring mips, we see that batching instructions within a transaction amortizes the cost of signature verification, lifting the maximum theoretical instruction throughput up to almost exactly that of centralized databases. Lastly, we discuss the relationships between high throughput, security and transaction fees. Solana's efficient use hardware drives transaction fees into the ballpark of 1/1000th of a cent. The drop in fees in turn makes certain denial of service attacks cheaper. We discuss what these attacks look like and Solana's techniques to defend against them.","breadcrumbs":"Introduction","id":"1","title":"Introduction"},"10":{"body":"A property of any permissionless blockchain is that the entity choosing the next block is randomly selected. In proof of stake systems, that entity is typically called the \"leader\" or \"block producer.\" In Solana, we call it the leader. Under the hood, a leader is simply a mode of the fullnode. A fullnode runs as either a leader or validator. In this chapter, we describe how a fullnode determines what node is the leader, how that mechanism may choose different leaders at the same time, and if so, how the system converges in response.","breadcrumbs":"Synchronization » Leader Rotation","id":"10","title":"Leader Rotation"},"11":{"body":"Leader selection is decided via a random seed. The process is as follows: Periodically, at a specific PoH tick count, select the signatures of the votes that made up the last supermajority Concatenate the signatures Hash the resulting string for N counts The resulting hash is the random seed for M counts, M leader slots, where M > N","breadcrumbs":"Synchronization » Leader Seed Generation","id":"11","title":"Leader Seed Generation"},"12":{"body":"The leader is chosen via a random seed generated from stake weights and votes (the leader schedule) The leader is rotated every T PoH ticks (leader slot), according to the leader schedule The schedule is applicable for M voting rounds Leader's transmit for a count of T PoH ticks. When T is reached all the validators should switch to the next scheduled leader. To schedule leaders, the supermajority + M nodes are shuffled using the above calculated random seed. All T ticks must be observed from the current leader for that part of PoH to be accepted by the network. If T ticks (and any intervening transactions) are not observed, the network optimistically fills in the T ticks, and continues with PoH from the next leader.","breadcrumbs":"Synchronization » Leader Rotation","id":"12","title":"Leader Rotation"},"13":{"body":"Forks can arise at PoH tick counts that correspond to leader rotations, because leader nodes may or may not have observed the previous leader's data. These empty ticks are generated by all nodes in the network at a network-specified rate for hashes-per-tick Z . There are only two possible versions of the PoH during a voting round: PoH with T ticks and entries generated by the current leader, or PoH with just ticks. The \"just ticks\" version of the PoH can be thought of as a virtual ledger, one that all nodes in the network can derive from the last tick in the previous slot. Validators can ignore forks at other points (e.g. from the wrong leader), or slash the leader responsible for the fork. Validators vote on the longest chain that contains their previous vote, or a longer chain if the lockout on their previous vote has expired. Validator's View Time Progression The diagram below represents a validator's view of the PoH stream with possible forks over time. L1, L2, etc. are leader slots, and E s represent entries from that leader during that leader's slot. The x s represent ticks only, and time flows downwards in the diagram. Note that an E appearing on 2 branches at the same slot is a slashable condition, so a validator observing L3 and L3' can slash L3 and safely choose x for that slot. Once a validator observes a supermajority vote on any branch, other branches can be discarded below that tick count. For any slot, validators need only consider a single \"has entries\" chain or a \"ticks only\" chain. Time Division It's useful to consider leader rotation over PoH tick count as time division of the job of encoding state for the network. The following table presents the above tree of forks as a time-divided ledger. leader slot L1 L2 L3 L4 L5 data E1 E2 E3 E4 E5 ticks since prev x xx Note that only data from leader L3 will be accepted during leader slot L3 . Data from L3 may include \"catchup\" ticks back to a slot other than L2 if L3 did not observe L2 's data. L4 and L5 's transmissions include the \"ticks since prev\" PoH entries. This arrangement of the network data streams permits nodes to save exactly this to the ledger for replay, restart, and checkpoints. Leader's View When a new leader begins a slot, it must first transmit any PoH (ticks) required to link the new slot with the most recently observed and voted slot.","breadcrumbs":"Synchronization » Partitions, Forks","id":"13","title":"Partitions, Forks"},"14":{"body":"","breadcrumbs":"Synchronization » Examples","id":"14","title":"Examples"},"15":{"body":"Network partition M occurs for 10% of the nodes The larger partition K , with 90% of the stake weight continues to operate as normal M cycles through the ranks until one of them is leader, generating ticks for slots where the leader is in K . M validators observe 10% of the vote pool, finality is not reached. M and K reconnect. M validators cancel their votes on M , which has not reached finality, and re-cast on K (after their vote lockout on M ).","breadcrumbs":"Synchronization » Small Partition","id":"15","title":"Small Partition"},"16":{"body":"Next rank leader node V observes a timeout from current leader A , fills in A 's slot with virtual ticks and starts sending out entries. Nodes observing both streams keep track of the forks, waiting for: their vote on leader A to expire in order to be able to vote on B a supermajority on A 's slot If the first case occurs, leader B 's slot is filled with ticks. if the second case occurs, A's slot is filled with ticks Partition is resolved just like in the Small Partition above","breadcrumbs":"Synchronization » Leader Timeout","id":"16","title":"Leader Timeout"},"17":{"body":"A - name of a node B - name of a node K - number of nodes in the supermajority to whom leaders broadcast their PoH hash for validation M - number of nodes outside the supermajority to whom leaders broadcast their PoH hash for validation N - number of voting rounds for which a leader schedule is considered before a new leader schedule is used T - number of PoH ticks per leader slot (also voting round) V - name of a node that will create virtual ticks Z - number of hashes per PoH tick","breadcrumbs":"Synchronization » Network Variables","id":"17","title":"Network Variables"},"18":{"body":"","breadcrumbs":"Fullnode","id":"18","title":"Fullnode"},"19":{"body":"The fullnodes make extensive use of an optimization common in CPU design, called pipelining . Pipelining is the right tool for the job when there's a stream of input data that needs to be processed by a sequence of steps, and there's different hardware responsible for each. The quintessential example is using a washer and dryer to wash/dry/fold several loads of laundry. Washing must occur before drying and drying before folding, but each of the three operations is performed by a separate unit. To maximize efficiency, one creates a pipeline of stages . We'll call the washer one stage, the dryer another, and the folding process a third. To run the pipeline, one adds a second load of laundry to the washer just after the first load is added to the dryer. Likewise, the third load is added to the washer after the second is in the dryer and the first is being folded. In this way, one can make progress on three loads of laundry simultaneously. Given infinite loads, the pipeline will consistently complete a load at the rate of the slowest stage in the pipeline.","breadcrumbs":"Pipelining","id":"19","title":"Pipelining"},"2":{"body":"","breadcrumbs":"Terminology","id":"2","title":"Terminology"},"20":{"body":"The fullnode contains two pipelined processes, one used in leader mode called the Tpu and one used in validator mode called the Tvu. In both cases, the hardware being pipelined is the same, the network input, the GPU cards, the CPU cores, writes to disk, and the network output. What it does with that hardware is different. The Tpu exists to create ledger entries whereas the Tvu exists to validate them.","breadcrumbs":"Pipelining in the fullnode","id":"20","title":"Pipelining in the fullnode"},"21":{"body":"","breadcrumbs":"Fullnode » The Transaction Processing Unit","id":"21","title":"The Transaction Processing Unit"},"22":{"body":"","breadcrumbs":"Fullnode » The Transaction Validation Unit","id":"22","title":"The Transaction Validation Unit"},"23":{"body":"The Network Control Plane implements a gossip network between all nodes on in the cluster.","breadcrumbs":"Fullnode » Ncp","id":"23","title":"Ncp"},"24":{"body":"","breadcrumbs":"Fullnode » JsonRpcService","id":"24","title":"JsonRpcService"},"25":{"body":"The Avalance explainer video is a conceptual overview of how a Solana leader can continuously process a gigabit of transaction data per second and then get that same data, after being recorded on the ledger, out to multiple validators on a single gigabit backplane. In practice, we found that just one level of the Avalanche validator tree is sufficient for at least 150 validators. We anticipate adding the second level to solve one of two problems: To transmit ledger segments to slower \"replicator\" nodes. To scale up the number of validators nodes. Both problems justify the additional level, but you won't find it implemented in the reference design just yet, because Solana's gossip implementation is currently the bottleneck on the number of nodes per Solana cluster. That work is being actively developed here: Scalable Gossip","breadcrumbs":"Avalanche replication","id":"25","title":"Avalanche replication"},"26":{"body":"","breadcrumbs":"Avalanche replication » Storage","id":"26","title":"Storage"},"27":{"body":"At full capacity on a 1gbps network Solana would generate 4 petabytes of data per year. If each fullnode was required to store the full ledger, the cost of storage would discourage fullnode participation, thus centralizing the network around those that could afford it. Solana aims to keep the cost of a fullnode below $5,000 USD to maximize participation. To achieve that, the network needs to minimize redundant storage while at the same time ensuring the validity and availability of each copy. To trust storage of ledger segments, Solana has replicators periodically submit proofs to the network that the data was replicated. Each proof is called a Proof of Replication. The basic idea of it is to encrypt a dataset with a public symmetric key and then hash the encrypted dataset. Solana uses CBC encryption . To prevent a malicious replicator from deleting the data as soon as it's hashed, a replicator is required hash random segments of the dataset. Alternatively, Solana could require hashing the reverse of the encrypted data, but random sampling is sufficient and much faster. Either solution ensures that all the data is present during the generation of the proof and also requires the validator to have the entirety of the encrypted data present for verification of every proof of every identity. The space required to validate is: number_of_proofs * data_size","breadcrumbs":"Avalanche replication » Background","id":"27","title":"Background"},"28":{"body":"Solana is not the only distribute systems project using Proof of Replication, but it might be the most efficient implementation because of its ability to synchronize nodes with its Proof of History. With PoH, Solana is able to record a hash of the PoRep samples in the ledger. Thus the blocks stay in the exact same order for every PoRep and verification can stream the data and verify all the proofs in a single batch. This way Solana can verify multiple proofs concurrently, each one on its own GPU core. With the current generation of graphics cards our network can support up to 14,000 replication identities or symmetric keys. The total space required for verification is: 2 CBC_blocks * number_of_identities with core count of equal to (Number of Identities). A CBC block is expected to be 1MB in size.","breadcrumbs":"Avalanche replication » Optimization with PoH","id":"28","title":"Optimization with PoH"},"29":{"body":"Validators for PoRep are the same validators that are verifying transactions. They have some stake that they have put up as collateral that ensures that their work is honest. If you can prove that a validator verified a fake PoRep, then the validator's stake is slashed. Replicators are specialized light clients. They download a part of the ledger and store it and provide proofs of storing the ledger. For each verified proof, replicators are rewarded tokens from the mining pool.","breadcrumbs":"Avalanche replication » Network","id":"29","title":"Network"},"3":{"body":"The following list contains words commonly used throughout the Solana architecture. account - a persistent file addressed by pubkey and with tokens tracking its lifetime cluster - a set of fullnodes maintaining a single ledger finality - the wallclock duration between a leader creating a tick entry and recoginizing a supermajority of validator votes with a ledger interpretation that matches the leader's fullnode - a full participant in the cluster - either a leader or validator node entry - an entry on the ledger - either a tick or a transactions entry instruction - the smallest unit of a program that a client can include in a transaction keypair - a public and secret key node count - the number of fullnodes participating in a cluster program - the code that interprets instructions pubkey - the public key of a keypair tick - a ledger entry that estimates wallclock duration tick height - the Nth tick in the ledger tps - transactions per second transaction - one or more instructions signed by the client and executed atomically transactions entry - a set of transactions that may be executed in parallel","breadcrumbs":"Teminology Currently in Use","id":"3","title":"Teminology Currently in Use"},"30":{"body":"Solana's PoRep protocol instroduces the following constraints: At most 14,000 replication identities can be used, because that is how many GPU cores are currently available to a computer costing under $5,000 USD. Verification requires generating the CBC blocks. That requires space of 2 blocks per identity, and 1 GPU core per identity for the same dataset. As many identities at once are batched with as many proofs for those identities verified concurrently for the same dataset.","breadcrumbs":"Avalanche replication » Constraints","id":"30","title":"Constraints"},"31":{"body":"The network sets a replication target number, let's say 1k. 1k PoRep identities are created from signatures of a PoH hash. They are tied to a specific PoH hash. It doesn't matter who creates them, or it could simply be the last 1k validation signatures we saw for the ledger at that count. This is maybe just the initial batch of identities, because we want to stagger identity rotation. Any client can use any of these identities to create PoRep proofs. Replicator identities are the CBC encryption keys. Periodically at a specific PoH count, a replicator that wants to create PoRep proofs signs the PoH hash at that count. That signature is the seed used to pick the block and identity to replicate. A block is 1TB of ledger. Periodically at a specific PoH count, a replicator submits PoRep proofs for their selected block. A signature of the PoH hash at that count is the seed used to sample the 1TB encrypted block, and hash it. This is done faster than it takes to encrypt the 1TB block with the original identity. Replicators must submit some number of fake proofs, which they can prove to be fake by providing the seed for the hash result. Periodically at a specific PoH count, validators sign the hash and use the signature to select the 1TB block that they need to validate. They batch all the identities and proofs and submit approval for all the verified ones. After #6, replicator client submit the proofs of fake proofs. For any random seed, Solana requires everyone to use a signature that is derived from a PoH hash. Every node uses the same count so that the same PoH hash is signed by every participant. The signatures are then each cryptographically tied to the keypair, which prevents a leader from grinding on the resulting value for more than 1 identity. Key rotation is staggered . Once going, the next identity is generated by hashing itself with a PoH hash. Since there are many more client identities then encryption identities, the reward is split amont multiple clients to prevent Sybil attacks from generating many clients to acquire the same block of data. To remain BFT, the network needs to avoid a single human entity from storing all the replications of a single chunk of the ledger. Solana's solution to this is to require clients to continue using the same identity. If the first round is used to acquire the same block for many client identities, the second round for the same client identities will require a redistribution of the signatures, and therefore PoRep identities and blocks. Thus to get a reward for storage, clients are not rewarded for storage of the first block. The network rewards long-lived client identities more than new ones.","breadcrumbs":"Avalanche replication » Validation and Replication Protocol","id":"31","title":"Validation and Replication Protocol"},"32":{"body":"","breadcrumbs":"The Solana SDK","id":"32","title":"The Solana SDK"},"33":{"body":"With the Solana runtime, we can execute on-chain programs concurrently, and written in the client’s choice of programming language.","breadcrumbs":"Introduction","id":"33","title":"Introduction"},"34":{"body":"As shown in the diagram above an untrusted client, creates a program in the language of their choice, (i.e. C/C++/Rust/Lua), and compiles it with LLVM to a position independent shared object ELF, targeting BPF bytecode, and sends it to the Solana cluster. Next, the client sends messages to the Solana cluster, which target that program. The Solana runtime loads the previously submitted ELF and passes it the client's message for interpretation.","breadcrumbs":"Client interactions with Solana","id":"34","title":"Client interactions with Solana"},"35":{"body":"Solana supports several kinds of persistent storage, called accounts : Executable Writable by a client Writable by a program Read-only All accounts are identified by public keys and may hold arbirary data. When the client sends messages to programs, it requests access to storage using those keys. The runtime loads the account data and passes it to the program. The runtime also ensures accounts aren't written to if not owned by the client or program. Any writes to read-only accounts are discarded unless the write was to credit tokens. Any user may credit other accounts tokens, regardless of account permission.","breadcrumbs":"Persistent Storage","id":"35","title":"Persistent Storage"},"36":{"body":"The goal with the runtime is to have a general purpose execution environment that is highly parallelizable. To achieve this goal the runtime forces each Instruction to specify all of its memory dependencies up front, and therefore a single Instruction cannot cause a dynamic memory allocation. An explicit Instruction for memory allocation from the SystemProgram::CreateAccount is the only way to allocate new memory in the engine. A Transaction may compose multiple Instruction, including SystemProgram::CreateAccount , into a single atomic sequence which allows for memory allocation to achieve a result that is similar to dynamic allocation.","breadcrumbs":"On-chain programs » Runtime","id":"36","title":"Runtime"},"37":{"body":"State is addressed by an Account which is at the moment simply the Pubkey. Our goal is to eliminate memory allocation from within the program itself. Thus the client of the program provides all the state that is necessary for the program to execute in the transaction itself. The runtime interacts with the program through an entry point with a well defined interface. The userdata stored in an Account is an opaque type to the runtime, a Vec , the contents of which the program code has full control over. The Transaction structure specifies a list of Pubkey's and signatures for those keys and a sequential list of instructions that will operate over the state's associated with the account_keys . For the transaction to be committed all the instructions must execute successfully, if any abort the whole transaction fails to commit.","breadcrumbs":"On-chain programs » State","id":"37","title":"State"},"38":{"body":"memory.","breadcrumbs":"On-chain programs » Account structure Accounts maintain token state as well as program specific","id":"38","title":"Account structure Accounts maintain token state as well as program specific"},"39":{"body":"At its core, the engine looks up all the Pubkeys maps them to accounts and routs them to the program_id entry point.","breadcrumbs":"On-chain programs » Transaction Engine","id":"39","title":"Transaction Engine"},"4":{"body":"The following keywords do not have any functionality but are reserved by Solana for potential future use. epoch - the time in which a leader schedule is valid mips - millions of instructions per second public key - We currently use pubkey slot - the time in which a single leader may produce entries secret key - Users currently only use keypair","breadcrumbs":"Terminology Reserved for Future Use","id":"4","title":"Terminology Reserved for Future Use"},"40":{"body":"Transactions are batched and processed in a pipeline At the execute stage, the loaded pages have no data dependencies, so all the programs can be executed in parallel. The runtime enforces the following rules: The program_id code is the only code that will modify the contents of Account::userdata of Account's that have been assigned to it. This means that upon assignment userdata vector is guaranteed to be 0 . Total balances on all the accounts is equal before and after execution of a Transaction. Balances of each of the accounts not assigned to program_id must be equal to or greater after the Transaction than before the transaction. All Instructions in the Transaction executed without a failure.","breadcrumbs":"On-chain programs » Execution","id":"40","title":"Execution"},"41":{"body":"key to an entry point which takes a pointer to the transaction, and an array of loaded pages.","breadcrumbs":"On-chain programs » Entry Point Execution of the program involves mapping the Program's public","id":"41","title":"Entry Point Execution of the program involves mapping the Program's public"},"42":{"body":"The interface is best described by the Instruction::userdata that the user encodes. CreateAccount - This allows the user to create and assign an Account to a Program. Assign - allows the user to assign an existing account to a Program . Move - moves tokens between Account s that are associated with SystemProgram . This cannot be used to move tokens of other Account s. Programs need to implement their own version of Move.","breadcrumbs":"On-chain programs » System Interface","id":"42","title":"System Interface"},"43":{"body":"There is no dynamic memory allocation. Client's need to call the SystemProgram to create memory before passing it to another program. This Instruction can be composed into a single Transaction with the call to the program itself. Runtime guarantees that when memory is assigned to the Program it is zero initialized. Runtime guarantees that Program 's code is the only thing that can modify memory that its assigned to Runtime guarantees that the Program can only spend tokens that are in Account s that are assigned to it Runtime guarantees the balances belonging to Account s are balanced before and after the transaction Runtime guarantees that multiple instructions all executed successfully when a transaction is committed.","breadcrumbs":"On-chain programs » Notes","id":"43","title":"Notes"},"44":{"body":"Continuations and Signals for long running Transactions","breadcrumbs":"On-chain programs » Future Work","id":"44","title":"Future Work"},"45":{"body":"","breadcrumbs":"On-chain programs » Ledger format","id":"45","title":"Ledger format"},"46":{"body":"The following sections contain reference material you may find useful in your Solana journey.","breadcrumbs":"Appendix","id":"46","title":"Appendix"},"47":{"body":"Solana nodes accept HTTP requests using the JSON-RPC 2.0 specification. To interact with a Solana node inside a JavaScript application, use the solana-web3.js library, which gives a convenient interface for the RPC methods.","breadcrumbs":"Appendix » JSON RPC API","id":"47","title":"JSON RPC API"},"48":{"body":"Default port: 8899 eg. http://localhost:8899, http://192.168.1.88:8899","breadcrumbs":"Appendix » RPC HTTP Endpoint","id":"48","title":"RPC HTTP Endpoint"},"49":{"body":"Default port: 8900 eg. ws://localhost:8900, http://192.168.1.88:8900","breadcrumbs":"Appendix » RPC PubSub WebSocket Endpoint","id":"49","title":"RPC PubSub WebSocket Endpoint"},"5":{"body":"It's possible for a centralized database to process 710,000 transactions per second on a standard gigabit network if the transactions are, on average, no more than 176 bytes. A centralized database can also replicate itself and maintain high availability without significantly compromising that transaction rate using the distributed system technique known as Optimistic Concurrency Control [H.T.Kung, J.T.Robinson (1981)] . At Solana, we're demonstrating that these same theoretical limits apply just as well to blockchain on an adversarial network. The key ingredient? Finding a way to share time when nodes can't trust one-another. Once nodes can trust time, suddenly ~40 years of distributed systems research becomes applicable to blockchain! Perhaps the most striking difference between algorithms obtained by our method and ones based upon timeout is that using timeout produces a traditional distributed algorithm in which the processes operate asynchronously, while our method produces a globally synchronous one in which every process does the same thing at (approximately) the same time. Our method seems to contradict the whole purpose of distributed processing, which is to permit different processes to operate independently and perform different functions. However, if a distributed system is really a single system, then the processes must be synchronized in some way. Conceptually, the easiest way to synchronize processes is to get them all to do the same thing at the same time. Therefore, our method is used to implement a kernel that performs the necessary synchronization--for example, making sure that two different processes do not try to modify a file at the same time. Processes might spend only a small fraction of their time executing the synchronizing kernel; the rest of the time, they can operate independently--e.g., accessing different files. This is an approach we have advocated even when fault-tolerance is not required. The method's basic simplicity makes it easier to understand the precise properties of a system, which is crucial if one is to know just how fault-tolerant the system is. [L.Lamport (1984)]","breadcrumbs":"Synchronization","id":"5","title":"Synchronization"},"50":{"body":"confirmTransaction getBalance getAccountInfo getLastId getSignatureStatus getTransactionCount requestAirdrop sendTransaction startSubscriptionChannel Subscription Websocket accountSubscribe accountUnsubscribe signatureSubscribe signatureUnsubscribe","breadcrumbs":"Appendix » Methods","id":"50","title":"Methods"},"51":{"body":"To make a JSON-RPC request, send an HTTP POST request with a Content-Type: application/json header. The JSON request data should contain 4 fields: jsonrpc , set to \"2.0\" id , a unique client-generated identifying integer method , a string containing the method to be invoked params , a JSON array of ordered parameter values Example using curl: curl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"getBalance\", \"params\":[\"83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri\"]}' 192.168.1.88:8899 The response output will be a JSON object with the following fields: jsonrpc , matching the request specification id , matching the request identifier result , requested data or success confirmation Requests can be sent in batches by sending an array of JSON-RPC request objects as the data for a single POST.","breadcrumbs":"Appendix » Request Formatting","id":"51","title":"Request Formatting"},"52":{"body":"Hash: A SHA-256 hash of a chunk of data. Pubkey: The public key of a Ed25519 key-pair. Signature: An Ed25519 signature of a chunk of data. Transaction: A Solana instruction signed by a client key-pair.","breadcrumbs":"Appendix » Definitions","id":"52","title":"Definitions"},"53":{"body":"","breadcrumbs":"Appendix » JSON RPC API Reference","id":"53","title":"JSON RPC API Reference"},"54":{"body":"Returns a transaction receipt Parameters: string - Signature of Transaction to confirm, as base-58 encoded string Results: boolean - Transaction status, true if Transaction is confirmed Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"confirmTransaction\", \"params\":[\"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW\"]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":true,\"id\":1}","breadcrumbs":"Appendix » confirmTransaction","id":"54","title":"confirmTransaction"},"55":{"body":"Returns the balance of the account of provided Pubkey Parameters: string - Pubkey of account to query, as base-58 encoded string Results: integer - quantity, as a signed 64-bit integer Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"getBalance\", \"params\":[\"83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri\"]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":0,\"id\":1}","breadcrumbs":"Appendix » getBalance","id":"55","title":"getBalance"},"56":{"body":"Returns all information associated with the account of provided Pubkey Parameters: string - Pubkey of account to query, as base-58 encoded string Results: The result field will be a JSON object with the following sub fields: tokens , number of tokens assigned to this account, as a signed 64-bit integer owner , array of 32 bytes representing the program this account has been assigned to userdata , array of bytes representing any userdata associated with the account executable , boolean indicating if the account contains a program (and is strictly read-only) loader , array of 32 bytes representing the loader for this program (if executable ), otherwise all Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"getAccountInfo\", \"params\":[\"2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST\"]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":{\"executable\":false,\"loader\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"owner\":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tokens\":1,\"userdata\":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},\"id\":1}","breadcrumbs":"Appendix » getAccountInfo","id":"56","title":"getAccountInfo"},"57":{"body":"Returns the last entry ID from the ledger Parameters: None Results: string - the ID of last entry, a Hash as base-58 encoded string Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getLastId\"}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":\"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC\",\"id\":1}","breadcrumbs":"Appendix » getLastId","id":"57","title":"getLastId"},"58":{"body":"Returns the status of a given signature. This method is similar to confirmTransaction but provides more resolution for error events. Parameters: string - Signature of Transaction to confirm, as base-58 encoded string Results: string - Transaction status: Confirmed - Transaction was successful SignatureNotFound - Unknown transaction ProgramRuntimeError - An error occurred in the program that processed this Transaction AccountInUse - Another Transaction had a write lock one of the Accounts specified in this Transaction. The Transaction may succeed if retried GenericFailure - Some other error occurred. Note : In the future new Transaction statuses may be added to this list. It's safe to assume that all new statuses will be more specific error conditions that previously presented as GenericFailure Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"getSignatureStatus\", \"params\":[\"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW\"]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":\"SignatureNotFound\",\"id\":1}","breadcrumbs":"Appendix » getSignatureStatus","id":"58","title":"getSignatureStatus"},"59":{"body":"Returns the current Transaction count from the ledger Parameters: None Results: integer - count, as unsigned 64-bit integer Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getTransactionCount\"}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":268,\"id\":1}","breadcrumbs":"Appendix » getTransactionCount","id":"59","title":"getTransactionCount"},"6":{"body":"A Verifiable Delay Function is conceptually a water clock where its water marks can be recorded and later verified that the water most certainly passed through. Anatoly describes the water clock analogy in detail here: water clock analogy The same technique has been used in Bitcoin since day one. The Bitcoin feature is called nLocktime and it can be used to postdate transactions using block height instead of a timestamp. As a Bitcoin client, you'd use block height instead of a timestamp if you don't trust the network. Block height turns out to be an instance of what's being called a Verifiable Delay Function in cryptography circles. It's a cryptographically secure way to say time has passed. In Solana, we use a far more granular verifiable delay function, a SHA 256 hash chain, to checkpoint the ledger and coordinate consensus. With it, we implement Optimistic Concurrency Control and are now well en route towards that theoretical limit of 710,000 transactions per second.","breadcrumbs":"Synchronization » Introduction to VDFs","id":"6","title":"Introduction to VDFs"},"60":{"body":"Requests an airdrop of tokens to a Pubkey Parameters: string - Pubkey of account to receive tokens, as base-58 encoded string integer - token quantity, as a signed 64-bit integer Results: string - Transaction Signature of airdrop, as base-58 encoded string Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"requestAirdrop\", \"params\":[\"83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri\", 50]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":\"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW\",\"id\":1}","breadcrumbs":"Appendix » requestAirdrop","id":"60","title":"requestAirdrop"},"61":{"body":"Creates new transaction Parameters: array - array of octets containing a fully-signed Transaction Results: string - Transaction Signature, as base-58 encoded string Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"sendTransaction\", \"params\":[[61, 98, 55, 49, 15, 187, 41, 215, 176, 49, 234, 229, 228, 77, 129, 221, 239, 88, 145, 227, 81, 158, 223, 123, 14, 229, 235, 247, 191, 115, 199, 71, 121, 17, 32, 67, 63, 209, 239, 160, 161, 2, 94, 105, 48, 159, 235, 235, 93, 98, 172, 97, 63, 197, 160, 164, 192, 20, 92, 111, 57, 145, 251, 6, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 13, 39, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 11, 12, 106, 49, 74, 226, 201, 16, 161, 192, 28, 84, 124, 97, 190, 201, 171, 186, 6, 18, 70, 142, 89, 185, 176, 154, 115, 61, 26, 163, 77, 1, 88, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":\"2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b\",\"id\":1}","breadcrumbs":"Appendix » sendTransaction","id":"61","title":"sendTransaction"},"62":{"body":"After connect to the RPC PubSub websocket at ws://
/ : Submit subscription requests to the websocket using the methods below Multiple subscriptions may be active at once","breadcrumbs":"Appendix » Subscription Websocket","id":"62","title":"Subscription Websocket"},"63":{"body":"Subscribe to an account to receive notifications when the userdata for a given account public key changes Parameters: string - account Pubkey, as base-58 encoded string Results: integer - Subscription id (needed to unsubscribe) Example: // Request\n{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"accountSubscribe\", \"params\":[\"CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12\"]} // Result\n{\"jsonrpc\": \"2.0\",\"result\": 0,\"id\": 1} Notification Format: {\"jsonrpc\": \"2.0\",\"method\": \"accountNotification\", \"params\": {\"result\": {\"executable\":false,\"loader\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"owner\":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tokens\":1,\"userdata\":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},\"subscription\":0}}","breadcrumbs":"Appendix » accountSubscribe","id":"63","title":"accountSubscribe"},"64":{"body":"Unsubscribe from account userdata change notifications Parameters: integer - id of account Subscription to cancel Results: bool - unsubscribe success message Example: // Request\n{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"accountUnsubscribe\", \"params\":[0]} // Result\n{\"jsonrpc\": \"2.0\",\"result\": true,\"id\": 1}","breadcrumbs":"Appendix » accountUnsubscribe","id":"64","title":"accountUnsubscribe"},"65":{"body":"Subscribe to a transaction signature to receive notification when the transaction is confirmed On signatureNotification , the subscription is automatically cancelled Parameters: string - Transaction Signature, as base-58 encoded string Results: integer - subscription id (needed to unsubscribe) Example: // Request\n{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"signatureSubscribe\", \"params\":[\"2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b\"]} // Result\n{\"jsonrpc\": \"2.0\",\"result\": 0,\"id\": 1} Notification Format: {\"jsonrpc\": \"2.0\",\"method\": \"signatureNotification\", \"params\": {\"result\": \"Confirmed\",\"subscription\":0}}","breadcrumbs":"Appendix » signatureSubscribe","id":"65","title":"signatureSubscribe"},"66":{"body":"Unsubscribe from account userdata change notifications Parameters: integer - id of account subscription to cancel Results: bool - unsubscribe success message Example: // Request\n{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"signatureUnsubscribe\", \"params\":[0]} // Result\n{\"jsonrpc\": \"2.0\",\"result\": true,\"id\": 1}","breadcrumbs":"Appendix » signatureUnsubscribe","id":"66","title":"signatureUnsubscribe"},"67":{"body":"The solana crate is distributed with a command-line interface tool","breadcrumbs":"Appendix » solana-wallet CLI","id":"67","title":"solana-wallet CLI"},"68":{"body":"Get Pubkey // Command\n$ solana-wallet address // Return\n Airdrop Tokens // Command\n$ solana-wallet airdrop 123 // Return\n\"Your balance is: 123\" Get Balance // Command\n$ solana-wallet balance // Return\n\"Your balance is: 123\" Confirm Transaction // Command\n$ solana-wallet confirm // Return\n\"Confirmed\" / \"Not found\" Deploy program // Command\n$ solana-wallet deploy // Return\n Unconditional Immediate Transfer // Command\n$ solana-wallet pay 123 // Return\n Post-Dated Transfer // Command\n$ solana-wallet pay 123 \\ --after 2018-12-24T23:59:00 --require-timestamp-from // Return\n{signature: , processId: } require-timestamp-from is optional. If not provided, the transaction will expect a timestamp signed by this wallet's secret key Authorized Transfer A third party must send a signature to unlock the tokens. // Command\n$ solana-wallet pay 123 \\ --require-signature-from // Return\n{signature: , processId: } Post-Dated and Authorized Transfer // Command\n$ solana-wallet pay 123 \\ --after 2018-12-24T23:59 --require-timestamp-from \\ --require-signature-from // Return\n{signature: , processId: } Multiple Witnesses // Command\n$ solana-wallet pay 123 \\ --require-signature-from \\ --require-signature-from // Return\n{signature: , processId: } Cancelable Transfer // Command\n$ solana-wallet pay 123 \\ --require-signature-from \\ --cancelable // Return\n{signature: , processId: } Cancel Transfer // Command\n$ solana-wallet cancel // Return\n Send Signature // Command\n$ solana-wallet send-signature // Return\n Indicate Elapsed Time Use the current system time: // Command\n$ solana-wallet send-timestamp // Return\n Or specify some other arbitrary timestamp: // Command\n$ solana-wallet send-timestamp --date 2018-12-24T23:59:00 // Return\n","breadcrumbs":"Appendix » Examples","id":"68","title":"Examples"},"69":{"body":"solana-wallet 0.11.0 USAGE: solana-wallet [OPTIONS] [SUBCOMMAND] FLAGS: -h, --help Prints help information -V, --version Prints version information OPTIONS: -k, --keypair /path/to/id.json -n, --network Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001 --proxy Address of TLS proxy --port Optional rpc-port configuration to connect to non-default nodes --timeout Max seconds to wait to get necessary gossip from the network SUBCOMMANDS: address Get your public key airdrop Request a batch of tokens balance Get your balance cancel Cancel a transfer confirm Confirm transaction by signature deploy Deploy a program get-transaction-count Get current transaction count help Prints this message or the help of the given subcommand(s) pay Send a payment send-signature Send a signature to authorize a transfer send-timestamp Send a timestamp to unlock a transfer solana-wallet-address Get your public key USAGE: solana-wallet address FLAGS: -h, --help Prints help information -V, --version Prints version information solana-wallet-airdrop Request a batch of tokens USAGE: solana-wallet airdrop FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: The number of tokens to request solana-wallet-balance Get your balance USAGE: solana-wallet balance FLAGS: -h, --help Prints help information -V, --version Prints version information solana-wallet-cancel Cancel a transfer USAGE: solana-wallet cancel FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: The process id of the transfer to cancel solana-wallet-confirm Confirm transaction by signature USAGE: solana-wallet confirm FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: The transaction signature to confirm solana-wallet-deploy Deploy a program USAGE: solana-wallet deploy FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: /path/to/program.o solana-wallet-get-transaction-count Get current transaction count USAGE: solana-wallet get-transaction-count FLAGS: -h, --help Prints help information -V, --version Prints version information solana-wallet-pay Send a payment USAGE: solana-wallet pay [FLAGS] [OPTIONS] FLAGS: --cancelable -h, --help Prints help information -V, --version Prints version information OPTIONS: --after A timestamp after which transaction will execute --require-timestamp-from Require timestamp from this third party --require-signature-from ... Any third party signatures required to unlock the tokens ARGS: The pubkey of recipient The number of tokens to send solana-wallet-send-signature Send a signature to authorize a transfer USAGE: solana-wallet send-signature FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: The pubkey of recipient The process id of the transfer to authorize solana-wallet-send-timestamp Send a timestamp to unlock a transfer USAGE: solana-wallet send-timestamp [OPTIONS] FLAGS: -h, --help Prints help information -V, --version Prints version information OPTIONS: --date Optional arbitrary timestamp to apply ARGS: The pubkey of recipient The process id of the transfer to unlock","breadcrumbs":"Appendix » Usage","id":"69","title":"Usage"},"7":{"body":"Proof of History overview","breadcrumbs":"Synchronization » Proof of History","id":"7","title":"Proof of History"},"8":{"body":"Most confusingly, a Proof of History (PoH) is more similar to a Verifiable Delay Function (VDF) than a Proof of Work or Proof of Stake consensus mechanism. The name unfortunately requires some historical context to understand. Proof of History was developed by Anatoly Yakovenko in November of 2017, roughly 6 months before we saw a paper using the term VDF . At that time, it was commonplace to publish new proofs of some desirable property used to build most any blockchain component. Some time shortly after, the crypto community began charting out all the different consensus mechanisms and because most of them started with \"Proof of\", the prefix became synonymous with a \"consensus\" suffix. Proof of History is not a consensus mechanism, but it is used to improve the performance of Solana's Proof of Stake consensus. It is also used to improve the performance of the replication and storage protocols. To minimize confusion, Solana may rebrand PoH to some flavor of the term VDF.","breadcrumbs":"Synchronization » Relationship to consensus mechanisms","id":"8","title":"Relationship to consensus mechanisms"},"9":{"body":"A desirable property of a VDF is that verification time is very fast. Solana's approach to verifying its delay function is proportional to the time it took to create it. Split over a 4000 core GPU, it is sufficiently fast for Solana's needs, but if you asked the authors the paper cited above, they might tell you (and have) that Solana's approach is algorithmically slow it shouldn't be called a VDF. We argue the term VDF should represent the category of verifiable delay functions and not just the subset with certain performance characteristics. Until that's resolved, Solana will likely continue using the term PoH for its application-specific VDF. Another difference between PoH and VDFs used only for tracking duration, is that PoH's hash chain includes hashes of any data the application observed. That data is a double-edged sword. On one side, the data \"proves history\" - that the data most certainly existed before hashes after it. On the side, it means the application can manipulate the hash chain by changing when the data is hashed. The PoH chain therefore does not serve as a good source of randomness whereas a VDF without that data could. Solana's leader selection algorithm (TODO: add link), for example, is derived only from the VDF height and not its hash at that height.","breadcrumbs":"Synchronization » Relationship to VDFs","id":"9","title":"Relationship to VDFs"}},"length":70,"save":true},"fields":["title","body","breadcrumbs"],"index":{"body":{"root":{"0":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},".":{"1":{"1":{".":{"0":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"40":{"tf":1.0},"61":{"tf":6.48074069840786}}},"1":{"/":{"1":{"0":{"0":{"0":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"15":{"tf":1.4142135623730951}}},"1":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"2":{"1":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":2,"docs":{"61":{"tf":1.0},"68":{"tf":3.0}}},"4":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"7":{".":{"0":{".":{"0":{".":{"1":{":":{"8":{"0":{"0":{"1":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":2,"docs":{"61":{"tf":1.0},"68":{"tf":1.7320508075688772}}},"3":{"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"4":{",":{"0":{"0":{"0":{"df":2,"docs":{"28":{"tf":1.0},"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"5":{"0":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"6":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"7":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":2,"docs":{"5":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"8":{"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"9":{"0":{"df":1,"docs":{"61":{"tf":1.0}}},"1":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"2":{".":{"1":{"6":{"8":{".":{"1":{".":{"8":{"8":{":":{"8":{"8":{"9":{"9":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"4":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"1":{"df":1,"docs":{"5":{"tf":1.0}}},"4":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":7,"docs":{"30":{"tf":1.0},"31":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}},"g":{"b":{"df":0,"docs":{},"p":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}},"k":{"df":1,"docs":{"31":{"tf":1.7320508075688772}}},"m":{"b":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}},"t":{"b":{"df":1,"docs":{"31":{"tf":2.0}}},"df":0,"docs":{}}},"2":{".":{"0":{"\"":{",":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"47":{"tf":1.0},"51":{"tf":1.0}}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"1":{"tf":1.0}}},"1":{"7":{"df":1,"docs":{"8":{"tf":1.0}}},"8":{"df":1,"docs":{"68":{"tf":1.7320508075688772}}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"1":{"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"2":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"3":{"4":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"5":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"4":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{},"t":{"2":{"3":{":":{"5":{"9":{":":{"0":{"0":{"df":1,"docs":{"68":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"68":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"5":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":2,"docs":{"52":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{".":{"4":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.0}}},"df":4,"docs":{"13":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"61":{"tf":1.0}}},"3":{"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"2":{"df":2,"docs":{"56":{"tf":1.4142135623730951},"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"4":{"0":{"0":{"0":{"df":1,"docs":{"9":{"tf":1.0}}},"df":1,"docs":{"1":{"tf":1.0}}},"df":3,"docs":{"1":{"tf":1.0},"5":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"df":2,"docs":{"27":{"tf":1.0},"51":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":2,"docs":{"27":{"tf":1.0},"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"df":2,"docs":{"60":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"8":{"df":9,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}},"6":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"4":{"df":4,"docs":{"55":{"tf":1.0},"56":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":3,"docs":{"31":{"tf":1.0},"61":{"tf":1.4142135623730951},"8":{"tf":1.0}}},"7":{"0":{"df":1,"docs":{"61":{"tf":1.0}}},"1":{"0":{",":{"0":{"0":{"0":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"8":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"9":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"9":{"0":{"df":1,"docs":{"15":{"tf":1.0}}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"a":{"'":{"df":1,"docs":{"16":{"tf":1.0}}},"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"28":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"v":{"df":5,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"34":{"tf":1.0},"9":{"tf":1.0}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"47":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"35":{"tf":1.0},"5":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"40":{"tf":1.0}}},":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":15,"docs":{"3":{"tf":1.0},"35":{"tf":2.6457513110645907},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"42":{"tf":2.0},"43":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":2.449489742783178},"58":{"tf":1.0},"60":{"tf":1.0},"63":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"66":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"63":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"63":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"64":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"u":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":2,"docs":{"27":{"tf":1.0},"36":{"tf":1.4142135623730951}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":2,"docs":{"25":{"tf":1.0},"62":{"tf":1.0}}}}}},"d":{"d":{"df":2,"docs":{"19":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"3":{"tf":1.0},"37":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":2.0}}}}}}},"df":3,"docs":{"19":{"tf":1.4142135623730951},"25":{"tf":1.0},"58":{"tf":1.0}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}}},"o":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"27":{"tf":1.0}}},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":3,"docs":{"60":{"tf":1.4142135623730951},"68":{"tf":1.4142135623730951},"69":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"df":4,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"5":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}}}}}}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":3,"docs":{"36":{"tf":2.23606797749979},"37":{"tf":1.0},"43":{"tf":1.0}}},"df":0,"docs":{},"w":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.0},"42":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"27":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"6":{"tf":1.0},"8":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":5,"docs":{"19":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"9":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"i":{"df":2,"docs":{"47":{"tf":1.0},"53":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"46":{"tf":1.0}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":9,"docs":{"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":4,"docs":{"12":{"tf":1.0},"47":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.7320508075688772}}},"df":2,"docs":{"5":{"tf":1.0},"69":{"tf":1.0}}}},"r":{"df":0,"docs":{},"o":{"a":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"5":{"tf":1.0},"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"v":{"df":1,"docs":{"31":{"tf":1.0}}},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}}}}}}}},"r":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":2.23606797749979},"3":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}}},"g":{"df":1,"docs":{"69":{"tf":2.6457513110645907}},"u":{"df":1,"docs":{"9":{"tf":1.0}}}},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}},"y":{"df":4,"docs":{"41":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.7320508075688772},"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"k":{"df":1,"docs":{"9":{"tf":1.0}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":4,"docs":{"40":{"tf":1.7320508075688772},"42":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772},"56":{"tf":1.4142135623730951}}}}},"o":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"37":{"tf":1.0},"42":{"tf":1.0},"56":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"58":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":2,"docs":{"3":{"tf":1.0},"36":{"tf":1.0}}}},"t":{"a":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"31":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"0":{"tf":1.0}}},"df":4,"docs":{"1":{"tf":1.0},"68":{"tf":1.4142135623730951},"69":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"65":{"tf":1.0}}}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"30":{"tf":1.0},"5":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"25":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}}}},"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"13":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":5,"docs":{"40":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"55":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":2.23606797749979}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"n":{"d":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"e":{"df":10,"docs":{"5":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"i":{"c":{"df":2,"docs":{"27":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"1":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"40":{"tf":1.0},"51":{"tf":1.0},"69":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":2,"docs":{"16":{"tf":1.4142135623730951},"17":{"tf":1.0}},"e":{"c":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":4,"docs":{"19":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.4142135623730951},"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":6,"docs":{"17":{"tf":1.0},"19":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"g":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"43":{"tf":1.0}}}},"w":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"27":{"tf":1.0},"62":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.0},"42":{"tf":1.0}}}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":6,"docs":{"1":{"tf":1.0},"23":{"tf":1.0},"3":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}}}},"df":4,"docs":{"55":{"tf":1.0},"56":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"1":{"tf":1.4142135623730951},"10":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":5,"docs":{"10":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"6":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"54":{"tf":1.0},"56":{"tf":1.0}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"16":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0}}},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"f":{"df":1,"docs":{"34":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"a":{"d":{"c":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"34":{"tf":1.0}}},"df":0,"docs":{}}},"df":2,"docs":{"5":{"tf":1.0},"56":{"tf":1.7320508075688772}}}}}},"c":{"/":{"c":{"+":{"+":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"a":{"df":1,"docs":{"34":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"12":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":8,"docs":{"10":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"27":{"tf":1.0},"35":{"tf":1.0},"43":{"tf":1.4142135623730951},"6":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":6,"docs":{"15":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":2.6457513110645907}}}}},"df":0,"docs":{}},"p":{"a":{"c":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"d":{"df":2,"docs":{"20":{"tf":1.0},"28":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"e":{"df":2,"docs":{"16":{"tf":1.4142135623730951},"20":{"tf":1.0}}},"t":{"df":1,"docs":{"15":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"36":{"tf":1.0}}}}},"b":{"c":{"_":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"28":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}},"r":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"1":{"tf":1.0},"27":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"6":{"tf":1.0},"9":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"13":{"tf":2.0},"33":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":1.7320508075688772}}}},"n":{"df":0,"docs":{},"g":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0},"9":{"tf":1.0}}}},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"10":{"tf":1.0}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"0":{"tf":1.0}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"6":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"33":{"tf":1.0},"34":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"s":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"13":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":2,"docs":{"31":{"tf":1.0},"52":{"tf":1.4142135623730951}}}}}},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"9":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":1,"docs":{"67":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":2,"docs":{"34":{"tf":1.0},"43":{"tf":1.0}}},"df":9,"docs":{"29":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"34":{"tf":1.7320508075688772},"35":{"tf":1.7320508075688772},"37":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.0}},"’":{"df":1,"docs":{"33":{"tf":1.0}}}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"23":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.7320508075688772},"34":{"tf":1.4142135623730951}}}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":4,"docs":{"3":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}},"m":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"67":{"tf":1.0},"68":{"tf":3.872983346207417}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"3":{"tf":1.0}}}},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"34":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}},"s":{"df":2,"docs":{"36":{"tf":1.0},"43":{"tf":1.0}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":3,"docs":{"25":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":6,"docs":{"1":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"33":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"58":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"69":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"65":{"tf":1.0},"68":{"tf":1.7320508075688772},"69":{"tf":2.449489742783178}},"e":{"d":{"\"":{",":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\"":{":":{"0":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"50":{"tf":1.0},"54":{"tf":1.0},"58":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"8":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"62":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":2,"docs":{"6":{"tf":1.0},"8":{"tf":2.449489742783178}}}}}},"i":{"d":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"17":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":7,"docs":{"13":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":1.0},"46":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.0},"61":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":12,"docs":{"0":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":6,"docs":{"12":{"tf":1.0},"15":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.0},"44":{"tf":1.0},"9":{"tf":1.0}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":4,"docs":{"23":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"47":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":5,"docs":{"20":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"39":{"tf":1.0},"9":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"27":{"tf":1.4142135623730951},"30":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":8,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.0},"13":{"tf":1.7320508075688772},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":2.6457513110645907},"59":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}}},"p":{"df":0,"docs":{},"u":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"67":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":10,"docs":{"17":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":2.0},"34":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"61":{"tf":1.0},"9":{"tf":1.0}},"e":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"42":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"8":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":2,"docs":{"31":{"tf":1.0},"6":{"tf":1.0}},"i":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":9,"docs":{"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":11,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.0},"4":{"tf":1.4142135623730951},"59":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}}}},"y":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"_":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.0}}}},"b":{"a":{"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":11,"docs":{"13":{"tf":2.449489742783178},"19":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":2.449489742783178},"28":{"tf":1.0},"31":{"tf":1.0},"35":{"tf":1.4142135623730951},"40":{"tf":1.0},"51":{"tf":1.7320508075688772},"52":{"tf":1.4142135623730951},"9":{"tf":2.449489742783178}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.7320508075688772},"30":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"68":{"tf":1.7320508075688772},"69":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}}}},"y":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"i":{"d":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"48":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"37":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"52":{"tf":1.0}}}}}}},"l":{"a":{"df":0,"docs":{},"y":{"df":3,"docs":{"6":{"tf":1.7320508075688772},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"27":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}}}}}}},"n":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"36":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":2,"docs":{"68":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":3,"docs":{"13":{"tf":1.0},"31":{"tf":1.0},"9":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":4,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"42":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":3,"docs":{"0":{"tf":1.0},"19":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":3,"docs":{"1":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"25":{"tf":1.0},"8":{"tf":1.0}}}}}}}},"i":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"34":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"10":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"5":{"tf":2.23606797749979},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"s":{"c":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"13":{"tf":1.0},"35":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"k":{"df":1,"docs":{"20":{"tf":1.0}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"28":{"tf":1.0},"5":{"tf":2.23606797749979},"67":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"0":{"tf":1.0},"31":{"tf":1.0}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"29":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.4142135623730951}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"1":{"tf":1.0}}}},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":2.0}}}}}},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"13":{"tf":1.7320508075688772},"27":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"36":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"g":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}},"1":{"df":1,"docs":{"13":{"tf":1.0}}},"2":{"df":1,"docs":{"13":{"tf":1.0}}},"3":{"df":1,"docs":{"13":{"tf":1.0}}},"4":{"df":1,"docs":{"13":{"tf":1.0}}},"5":{"df":1,"docs":{"13":{"tf":1.0}}},"a":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"19":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"28":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"d":{"2":{"5":{"5":{"1":{"9":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":1,"docs":{"13":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"28":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}}},"g":{"df":2,"docs":{"48":{"tf":1.0},"49":{"tf":1.0}}},"l":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":1,"docs":{"68":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":1,"docs":{"34":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"n":{"c":{"df":0,"docs":{},"o":{"d":{"df":11,"docs":{"13":{"tf":1.0},"42":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":2.23606797749979},"31":{"tf":2.0}}}}}}},"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"48":{"tf":1.0},"49":{"tf":1.0}}}}}}}},"df":1,"docs":{"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"36":{"tf":1.0},"39":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"29":{"tf":1.0},"35":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":10,"docs":{"13":{"tf":2.0},"16":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":2.449489742783178},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"p":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"4":{"tf":1.0}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"28":{"tf":1.0},"40":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"0":{"tf":1.0},"3":{"tf":1.0}}}}}},"t":{"c":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}},"x":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"1":{"tf":1.0},"13":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":18,"docs":{"14":{"tf":1.0},"19":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"68":{"tf":1.0},"9":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"\"":{":":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"\"":{":":{"1":{",":{"\"":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"\"":{":":{"[":{"3":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"5":{"0":{",":{"4":{"8":{",":{"5":{"3":{",":{"4":{"8":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"8":{"4":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"9":{"0":{",":{"2":{"5":{"2":{",":{"1":{"0":{",":{"7":{",":{"2":{"8":{",":{"2":{"4":{"6":{",":{"1":{"4":{"0":{",":{"8":{"8":{",":{"1":{"7":{"7":{",":{"9":{"8":{",":{"8":{"2":{",":{"1":{"0":{",":{"2":{"2":{"7":{",":{"8":{"9":{",":{"8":{"1":{",":{"1":{"8":{",":{"3":{"0":{",":{"1":{"9":{"4":{",":{"1":{"0":{"1":{",":{"1":{"9":{"9":{",":{"1":{"6":{",":{"1":{"1":{",":{"7":{"3":{",":{"1":{"3":{"3":{",":{"2":{"0":{",":{"2":{"4":{"6":{",":{"6":{"2":{",":{"1":{"1":{"4":{",":{"3":{"9":{",":{"2":{"0":{",":{"1":{"1":{"3":{",":{"1":{"8":{"9":{",":{"3":{"2":{",":{"5":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"4":{"7":{",":{"1":{"5":{",":{"3":{"6":{",":{"1":{"0":{"2":{",":{"1":{"6":{"7":{",":{"8":{"3":{",":{"2":{"2":{"5":{",":{"4":{"2":{",":{"1":{"3":{"3":{",":{"1":{"2":{"7":{",":{"8":{"2":{",":{"3":{"4":{",":{"3":{"6":{",":{"2":{"2":{"4":{",":{"2":{"0":{"7":{",":{"1":{"3":{"0":{",":{"1":{"0":{"9":{",":{"2":{"3":{"0":{",":{"2":{"2":{"4":{",":{"1":{"8":{"8":{",":{"1":{"6":{"3":{",":{"3":{"3":{",":{"2":{"1":{"3":{",":{"1":{"3":{",":{"5":{",":{"1":{"1":{"7":{",":{"2":{"1":{"1":{",":{"2":{"5":{"1":{",":{"6":{"5":{",":{"1":{"5":{"9":{",":{"1":{"9":{"7":{",":{"5":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{"df":0,"docs":{},"}":{",":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\"":{":":{"0":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":12,"docs":{"1":{"tf":1.0},"3":{"tf":1.4142135623730951},"33":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"40":{"tf":2.23606797749979},"41":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"56":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"1":{"tf":1.0},"20":{"tf":1.4142135623730951},"42":{"tf":1.0},"9":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"28":{"tf":1.0},"68":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"r":{"df":2,"docs":{"13":{"tf":1.0},"16":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.0}}}}}}}},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.7320508075688772}}}},"r":{"df":1,"docs":{"6":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.7320508075688772}},"l":{"df":1,"docs":{"1":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"51":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"3":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"l":{"df":2,"docs":{"12":{"tf":1.0},"16":{"tf":1.7320508075688772}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.4142135623730951},"3":{"tf":1.0}}}},"d":{"df":3,"docs":{"25":{"tf":1.0},"46":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"13":{"tf":1.0},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}}},"l":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":3.4641016151377544}}},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"13":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"19":{"tf":1.7320508075688772}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":9,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.0},"4":{"tf":1.0},"40":{"tf":1.0},"46":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0}}}}}},"r":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":2,"docs":{"13":{"tf":2.449489742783178},"16":{"tf":1.0}}},"m":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"45":{"tf":1.0},"51":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"1":{"tf":1.0},"25":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"3":{"tf":1.0},"37":{"tf":1.0}},"i":{"df":1,"docs":{"61":{"tf":1.0}}},"n":{"df":0,"docs":{},"o":{"d":{"df":6,"docs":{"10":{"tf":1.7320508075688772},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"3":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":5,"docs":{"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.7320508075688772},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"0":{"tf":1.0},"1":{"tf":1.0}}}}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"4":{"tf":1.4142135623730951},"44":{"tf":1.0},"58":{"tf":1.0}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"1":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.4142135623730951},"15":{"tf":1.0},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.0},"51":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":2,"docs":{"50":{"tf":1.0},"56":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"50":{"tf":1.0},"55":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"50":{"tf":1.0},"57":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":2,"docs":{"50":{"tf":1.0},"58":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"50":{"tf":1.0},"59":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"a":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"5":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"47":{"tf":1.0}},"n":{"df":4,"docs":{"19":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0}}}},"df":1,"docs":{"31":{"tf":1.0}},"e":{"df":1,"docs":{"1":{"tf":1.0}}},"o":{"d":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951}}}}}}},"p":{"df":0,"docs":{},"u":{"df":4,"docs":{"20":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"40":{"tf":1.0},"43":{"tf":2.23606797749979}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"h":{".":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"h":{"df":10,"docs":{"11":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.7320508075688772},"27":{"tf":2.0},"28":{"tf":1.0},"31":{"tf":3.3166247903554},"52":{"tf":1.4142135623730951},"57":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":2.449489742783178}}}}},"df":10,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"69":{"tf":3.3166247903554}},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"6":{"tf":1.7320508075688772},"9":{"tf":1.4142135623730951}}}}}},"l":{"df":0,"docs":{},"p":{"df":1,"docs":{"69":{"tf":4.898979485566356}}}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"25":{"tf":1.0},"6":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"5":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}},"i":{"df":4,"docs":{"28":{"tf":1.0},"7":{"tf":1.4142135623730951},"8":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"35":{"tf":1.0}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"o":{"d":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{":":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{":":{"/":{"/":{"1":{"9":{"2":{".":{"1":{"6":{"8":{".":{"1":{".":{"8":{"8":{":":{"8":{"8":{"9":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"8":{"8":{"9":{"9":{"df":9,"docs":{"48":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"47":{"tf":1.0},"48":{"tf":1.0},"51":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{".":{"df":1,"docs":{"34":{"tf":1.0}}},"d":{"\"":{":":{"1":{"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":7,"docs":{"51":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.7320508075688772}},"e":{"a":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":2.23606797749979},"31":{"tf":4.123105625617661}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":2,"docs":{"35":{"tf":1.0},"51":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"68":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":6,"docs":{"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"8":{"tf":1.4142135623730951}}}}}}},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"3":{"tf":1.0},"36":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"34":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"i":{"c":{"df":2,"docs":{"56":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"56":{"tf":1.0},"69":{"tf":4.69041575982343}}}}}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"31":{"tf":1.0},"43":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"19":{"tf":1.0},"20":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"47":{"tf":1.0}}},"df":0,"docs":{}},"t":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":1,"docs":{"30":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"1":{"tf":1.7320508075688772},"3":{"tf":1.7320508075688772},"36":{"tf":2.0},"37":{"tf":1.4142135623730951},"4":{"tf":1.0},"40":{"tf":1.0},"43":{"tf":1.4142135623730951},"52":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"42":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":9,"docs":{"51":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.0},"59":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.0},"37":{"tf":1.0},"47":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"a":{"c":{"df":4,"docs":{"37":{"tf":1.0},"42":{"tf":1.4142135623730951},"47":{"tf":1.0},"67":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.4142135623730951},"34":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"33":{"tf":1.0},"6":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"51":{"tf":1.0}}},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"41":{"tf":1.0}}}}}}},"t":{"'":{"df":5,"docs":{"13":{"tf":1.0},"27":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":4,"docs":{"31":{"tf":1.0},"37":{"tf":1.4142135623730951},"43":{"tf":1.0},"5":{"tf":1.0}}}}}}}},"j":{".":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"47":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"b":{"df":2,"docs":{"13":{"tf":1.0},"19":{"tf":1.0}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"46":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":4,"docs":{"47":{"tf":1.4142135623730951},"51":{"tf":2.23606797749979},"53":{"tf":1.0},"56":{"tf":1.0}},"r":{"df":0,"docs":{},"p":{"c":{"\"":{":":{"\"":{"2":{".":{"0":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":4,"docs":{"57":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"\"":{":":{"\"":{"2":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"6":{"c":{"b":{"8":{"df":0,"docs":{},"v":{"a":{"a":{"d":{"9":{"3":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"6":{"df":0,"docs":{},"v":{"d":{"8":{"df":0,"docs":{},"p":{"6":{"7":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"q":{"df":0,"docs":{},"z":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"x":{"4":{"7":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"g":{"9":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"1":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"3":{"a":{"2":{"df":0,"docs":{},"g":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"7":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"j":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"y":{"9":{"b":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"5":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"v":{"df":0,"docs":{},"z":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"x":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"9":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"j":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"j":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"8":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"q":{"df":0,"docs":{},"p":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"4":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"3":{"df":0,"docs":{},"z":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"z":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"v":{"6":{"df":0,"docs":{},"u":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"k":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"w":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"7":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"3":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"7":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"9":{"df":0,"docs":{},"j":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"2":{"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"w":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"3":{"df":0,"docs":{},"z":{"6":{"9":{"df":0,"docs":{},"x":{"df":0,"docs":{},"m":{"1":{"df":0,"docs":{},"z":{"df":0,"docs":{},"e":{"3":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"6":{"df":0,"docs":{},"j":{"c":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"0":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"55":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"6":{"8":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"54":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"{":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"\"":{":":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"\"":{":":{"1":{",":{"\"":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"\"":{":":{"[":{"3":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"5":{"0":{",":{"4":{"8":{",":{"5":{"3":{",":{"4":{"8":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"8":{"4":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"9":{"0":{",":{"2":{"5":{"2":{",":{"1":{"0":{",":{"7":{",":{"2":{"8":{",":{"2":{"4":{"6":{",":{"1":{"4":{"0":{",":{"8":{"8":{",":{"1":{"7":{"7":{",":{"9":{"8":{",":{"8":{"2":{",":{"1":{"0":{",":{"2":{"2":{"7":{",":{"8":{"9":{",":{"8":{"1":{",":{"1":{"8":{",":{"3":{"0":{",":{"1":{"9":{"4":{",":{"1":{"0":{"1":{",":{"1":{"9":{"9":{",":{"1":{"6":{",":{"1":{"1":{",":{"7":{"3":{",":{"1":{"3":{"3":{",":{"2":{"0":{",":{"2":{"4":{"6":{",":{"6":{"2":{",":{"1":{"1":{"4":{",":{"3":{"9":{",":{"2":{"0":{",":{"1":{"1":{"3":{",":{"1":{"8":{"9":{",":{"3":{"2":{",":{"5":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"4":{"7":{",":{"1":{"5":{",":{"3":{"6":{",":{"1":{"0":{"2":{",":{"1":{"6":{"7":{",":{"8":{"3":{",":{"2":{"2":{"5":{",":{"4":{"2":{",":{"1":{"3":{"3":{",":{"1":{"2":{"7":{",":{"8":{"2":{",":{"3":{"4":{",":{"3":{"6":{",":{"2":{"2":{"4":{",":{"2":{"0":{"7":{",":{"1":{"3":{"0":{",":{"1":{"0":{"9":{",":{"2":{"3":{"0":{",":{"2":{"2":{"4":{",":{"1":{"8":{"8":{",":{"1":{"6":{"3":{",":{"3":{"3":{",":{"2":{"1":{"3":{",":{"1":{"3":{",":{"5":{",":{"1":{"1":{"7":{",":{"2":{"1":{"1":{",":{"2":{"5":{"1":{",":{"6":{"5":{",":{"1":{"5":{"9":{",":{"1":{"9":{"7":{",":{"5":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{"df":0,"docs":{},"}":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"56":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":5,"docs":{"51":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"25":{"tf":1.0}}}}}}}}},"k":{"df":3,"docs":{"15":{"tf":2.0},"17":{"tf":1.0},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":2,"docs":{"16":{"tf":1.0},"27":{"tf":1.0}}}},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}}},"y":{"df":13,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"35":{"tf":1.4142135623730951},"37":{"tf":1.0},"4":{"tf":1.4142135623730951},"41":{"tf":1.0},"5":{"tf":1.0},"52":{"tf":1.7320508075688772},"63":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"p":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":4,"docs":{"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"4":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"4":{"tf":1.0}}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"35":{"tf":1.0}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"5":{"tf":1.0}},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"l":{".":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"1":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"2":{"df":1,"docs":{"13":{"tf":2.0}}},"3":{"df":1,"docs":{"13":{"tf":2.8284271247461903}}},"4":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"g":{"df":3,"docs":{"1":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"15":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"31":{"tf":1.0},"57":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.7320508075688772},"3":{"tf":1.0}}},"df":13,"docs":{"10":{"tf":2.6457513110645907},"11":{"tf":1.7320508075688772},"12":{"tf":3.1622776601683795},"13":{"tf":3.4641016151377544},"15":{"tf":1.4142135623730951},"16":{"tf":2.23606797749979},"17":{"tf":2.23606797749979},"20":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"4":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":12,"docs":{"13":{"tf":1.7320508075688772},"20":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"3":{"tf":2.23606797749979},"31":{"tf":1.7320508075688772},"45":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0}}}}}},"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"25":{"tf":1.7320508075688772}}}}}},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"47":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"3":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"1":{"tf":1.0}}}},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"19":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"67":{"tf":1.0}}},"k":{"df":2,"docs":{"13":{"tf":1.0},"9":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"37":{"tf":1.4142135623730951},"58":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"31":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"df":1,"docs":{"34":{"tf":1.0}}}}},"o":{"a":{"d":{"df":5,"docs":{"19":{"tf":2.6457513110645907},"34":{"tf":1.0},"35":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"56":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"58":{"tf":1.0}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"15":{"tf":1.0}}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"31":{"tf":1.0},"44":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.0},"39":{"tf":1.0}}}}}},"m":{"a":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"1":{"tf":1.0},"3":{"tf":1.0},"38":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{}}}},"k":{"df":0,"docs":{},"e":{"df":4,"docs":{"1":{"tf":1.0},"19":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"51":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"i":{"df":2,"docs":{"30":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}}}}},"p":{"df":2,"docs":{"39":{"tf":1.0},"41":{"tf":1.0}}},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"3":{"tf":1.0},"51":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"46":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.0}}}}}},"x":{"df":1,"docs":{"69":{"tf":1.0}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"19":{"tf":1.0},"27":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"y":{"b":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}},"df":4,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"15":{"tf":2.6457513110645907},"17":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"n":{"df":3,"docs":{"1":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"0":{"tf":1.0},"1":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"10":{"tf":1.0},"8":{"tf":2.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"36":{"tf":2.23606797749979},"37":{"tf":1.0},"38":{"tf":1.0},"43":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"\"":{":":{"\"":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"51":{"tf":1.0},"55":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"66":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"'":{"df":1,"docs":{"5":{"tf":1.0}}},"df":6,"docs":{"47":{"tf":1.0},"5":{"tf":2.0},"50":{"tf":1.0},"51":{"tf":1.4142135623730951},"58":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"4":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"29":{"tf":1.0}}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"27":{"tf":1.0},"8":{"tf":1.0}}}}},"p":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"4":{"tf":1.0}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":2,"docs":{"10":{"tf":1.0},"20":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":3,"docs":{"40":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"8":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":6,"docs":{"3":{"tf":1.0},"31":{"tf":1.7320508075688772},"5":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0},"8":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"42":{"tf":2.0}}}}},"u":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":7,"docs":{"25":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"43":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.0}}}}}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":2,"docs":{"17":{"tf":1.7320508075688772},"8":{"tf":1.0}}}}},"c":{"df":0,"docs":{},"p":{"df":1,"docs":{"23":{"tf":1.0}}}},"df":3,"docs":{"11":{"tf":1.4142135623730951},"17":{"tf":1.0},"69":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"37":{"tf":1.0},"5":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"d":{"df":9,"docs":{"13":{"tf":1.0},"19":{"tf":1.0},"27":{"tf":1.0},"31":{"tf":1.4142135623730951},"42":{"tf":1.0},"43":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":14,"docs":{"1":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"13":{"tf":2.23606797749979},"15":{"tf":1.0},"17":{"tf":1.0},"20":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"27":{"tf":2.0},"28":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951},"6":{"tf":1.0},"69":{"tf":1.7320508075688772}}}}}}},"w":{"df":7,"docs":{"13":{"tf":1.4142135623730951},"17":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"58":{"tf":1.4142135623730951},"61":{"tf":1.0},"8":{"tf":1.0}}},"x":{"df":0,"docs":{},"t":{"df":5,"docs":{"10":{"tf":1.0},"12":{"tf":1.4142135623730951},"16":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"o":{"d":{"df":0,"docs":{},"e":{"df":15,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":2.0},"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"17":{"tf":2.23606797749979},"23":{"tf":1.0},"25":{"tf":1.7320508075688772},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"47":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"69":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}},"e":{"df":2,"docs":{"57":{"tf":1.0},"59":{"tf":1.0}}}},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"43":{"tf":1.0},"58":{"tf":1.0}}},"h":{"df":1,"docs":{"0":{"tf":1.0}}},"i":{"df":0,"docs":{},"f":{"df":4,"docs":{"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}}},"w":{"df":1,"docs":{"6":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"3":{"tf":1.0}}}},"u":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":1,"docs":{"27":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"df":7,"docs":{"17":{"tf":2.23606797749979},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"56":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}},"df":1,"docs":{"69":{"tf":2.23606797749979}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":5,"docs":{"12":{"tf":1.4142135623730951},"13":{"tf":2.23606797749979},"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}},"c":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":4,"docs":{"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"19":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"c":{"df":5,"docs":{"13":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"5":{"tf":1.0},"62":{"tf":1.0}}},"df":12,"docs":{"13":{"tf":1.0},"15":{"tf":1.0},"19":{"tf":2.0},"20":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"5":{"tf":2.0},"58":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":1.0}}},"p":{"a":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"15":{"tf":1.0},"19":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.7320508075688772}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"19":{"tf":1.0},"28":{"tf":1.0}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"12":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":2.8284271247461903}}}}}}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"16":{"tf":1.0},"28":{"tf":1.0},"51":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"16":{"tf":1.0},"25":{"tf":1.0},"6":{"tf":1.0},"8":{"tf":1.0}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"20":{"tf":1.0},"51":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"9":{"tf":1.0}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":2,"docs":{"25":{"tf":1.0},"7":{"tf":1.0}}}}}}}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"35":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"56":{"tf":1.0}}}}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":2,"docs":{"40":{"tf":1.0},"41":{"tf":1.0}}}},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"8":{"tf":1.0},"9":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":2,"docs":{"3":{"tf":1.0},"40":{"tf":1.0}},"i":{"df":0,"docs":{},"z":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"m":{"df":3,"docs":{"51":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":13,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}}},"s":{"\"":{":":{"[":{"\"":{"2":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"6":{"c":{"b":{"8":{"df":0,"docs":{},"v":{"a":{"a":{"d":{"9":{"3":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"6":{"df":0,"docs":{},"v":{"d":{"8":{"df":0,"docs":{},"p":{"6":{"7":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"q":{"df":0,"docs":{},"z":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"x":{"4":{"7":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"g":{"9":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"1":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"3":{"a":{"2":{"df":0,"docs":{},"g":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"7":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"j":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"y":{"9":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"g":{"df":0,"docs":{},"v":{"df":0,"docs":{},"k":{"df":0,"docs":{},"y":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"h":{"b":{"2":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"n":{"3":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"f":{"df":0,"docs":{},"k":{"df":0,"docs":{},"x":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"h":{"df":0,"docs":{},"p":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"56":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}}},"5":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"v":{"df":0,"docs":{},"z":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"x":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"9":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"j":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"j":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"8":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"q":{"df":0,"docs":{},"p":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"4":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"3":{"df":0,"docs":{},"z":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"z":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"v":{"6":{"df":0,"docs":{},"u":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"k":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"w":{"df":2,"docs":{"54":{"tf":1.0},"58":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"8":{"3":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"t":{"2":{"df":0,"docs":{},"h":{"5":{"df":0,"docs":{},"u":{"1":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"d":{"df":0,"docs":{},"q":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"6":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"y":{"3":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"51":{"tf":1.0},"55":{"tf":1.0},"60":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"m":{"7":{"8":{"c":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"8":{"df":0,"docs":{},"o":{"3":{"df":0,"docs":{},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"h":{"df":0,"docs":{},"x":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"z":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"4":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"df":0,"docs":{},"k":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"x":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"k":{"df":0,"docs":{},"n":{"df":0,"docs":{},"h":{"1":{"2":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"0":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}}},"[":{"6":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"29":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}},"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"t":{"df":3,"docs":{"13":{"tf":1.0},"15":{"tf":1.7320508075688772},"16":{"tf":1.4142135623730951}}}}}},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"34":{"tf":1.0},"35":{"tf":1.0},"43":{"tf":1.0},"6":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"i":{"d":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{".":{"df":0,"docs":{},"o":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.7320508075688772}}}},"y":{"df":2,"docs":{"68":{"tf":2.449489742783178},"69":{"tf":1.7320508075688772}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":10,"docs":{"1":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"27":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.4142135623730951},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"0":{"tf":1.0},"1":{"tf":1.7320508075688772},"19":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"11":{"tf":1.0},"27":{"tf":1.0},"31":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"35":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}}}}},"t":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.0},"35":{"tf":1.4142135623730951}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":1,"docs":{"27":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"19":{"tf":2.6457513110645907},"20":{"tf":1.7320508075688772},"40":{"tf":1.0}}}}}}}},"l":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"23":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"h":{"'":{"df":1,"docs":{"9":{"tf":1.0}}},"df":8,"docs":{"11":{"tf":1.0},"12":{"tf":2.0},"13":{"tf":3.0},"17":{"tf":2.0},"28":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"8":{"tf":1.4142135623730951},"9":{"tf":1.7320508075688772}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"13":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"41":{"tf":1.4142135623730951},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"41":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.0},"29":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":4,"docs":{"28":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.23606797749979}}}},"t":{"df":3,"docs":{"48":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"5":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"d":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"df":10,"docs":{"51":{"tf":1.7320508075688772},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"4":{"tf":1.0}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"8":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":3,"docs":{"13":{"tf":1.0},"27":{"tf":1.4142135623730951},"58":{"tf":1.0}}}}}},"v":{"df":1,"docs":{"13":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":1,"docs":{"13":{"tf":2.0}},"s":{"df":2,"docs":{"34":{"tf":1.0},"58":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":4.795831523312719}}}}},"o":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"25":{"tf":1.4142135623730951}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"68":{"tf":3.0},"69":{"tf":2.449489742783178}}},"df":0,"docs":{}}},"df":9,"docs":{"11":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.0},"21":{"tf":1.0},"25":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":3.0},"58":{"tf":1.0},"69":{"tf":1.7320508075688772}},"i":{"d":{"df":1,"docs":{"68":{"tf":2.23606797749979}}},"df":0,"docs":{}}}}}},"d":{"df":0,"docs":{},"u":{"c":{"df":3,"docs":{"10":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"'":{"df":1,"docs":{"41":{"tf":1.0}}},"_":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"68":{"tf":1.0}}},"df":0,"docs":{}}},"df":15,"docs":{"1":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951},"35":{"tf":2.0},"37":{"tf":2.23606797749979},"38":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.7320508075688772},"43":{"tf":2.23606797749979},"56":{"tf":1.7320508075688772},"58":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.0}}}}}}}}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"13":{"tf":1.0},"19":{"tf":1.0}}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.4142135623730951},"28":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"f":{"df":8,"docs":{"10":{"tf":1.0},"27":{"tf":2.23606797749979},"28":{"tf":2.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.6457513110645907},"7":{"tf":1.4142135623730951},"8":{"tf":2.8284271247461903}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":5,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"5":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}}},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"30":{"tf":1.0},"31":{"tf":1.0},"8":{"tf":1.0}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"e":{"df":3,"docs":{"29":{"tf":1.0},"31":{"tf":1.0},"9":{"tf":1.0}}},"i":{"d":{"df":7,"docs":{"29":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}},"x":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}}},"u":{"b":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":11,"docs":{"3":{"tf":1.4142135623730951},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"52":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"63":{"tf":1.0},"68":{"tf":4.242640687119285},"69":{"tf":3.3166247903554}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":8,"docs":{"27":{"tf":1.0},"3":{"tf":1.4142135623730951},"35":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.0},"52":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.4142135623730951}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":2,"docs":{"49":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"55":{"tf":1.0},"60":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"55":{"tf":1.0},"56":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}}}}}}}},"r":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":5,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"31":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"df":0,"docs":{},"k":{"df":2,"docs":{"15":{"tf":1.0},"16":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"13":{"tf":1.0},"19":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"12":{"tf":1.0},"15":{"tf":1.4142135623730951}}}},"d":{"df":2,"docs":{"35":{"tf":1.4142135623730951},"56":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}},"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"v":{"df":3,"docs":{"60":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.7320508075688772}}}}},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"d":{"df":3,"docs":{"25":{"tf":1.0},"28":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}},"df":1,"docs":{"15":{"tf":1.0}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"25":{"tf":1.0},"46":{"tf":1.0},"53":{"tf":1.0}}}}},"g":{"a":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"1":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"1":{"tf":1.0}}}}},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"z":{"df":0,"docs":{},"v":{"df":1,"docs":{"69":{"tf":1.0}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"i":{"c":{"df":8,"docs":{"25":{"tf":1.4142135623730951},"27":{"tf":2.23606797749979},"28":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":3.0},"5":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":3,"docs":{"13":{"tf":1.7320508075688772},"56":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"50":{"tf":1.0},"60":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":17,"docs":{"35":{"tf":1.0},"47":{"tf":1.0},"51":{"tf":3.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"r":{"df":9,"docs":{"13":{"tf":1.0},"27":{"tf":2.23606797749979},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.7320508075688772},"5":{"tf":1.0},"68":{"tf":2.8284271247461903},"69":{"tf":2.0},"8":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"4":{"tf":1.4142135623730951}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"v":{"df":2,"docs":{"16":{"tf":1.0},"9":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":4,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0},"51":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":1,"docs":{"5":{"tf":1.0}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":16,"docs":{"11":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"36":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.7320508075688772},"57":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"59":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951},"63":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"65":{"tf":1.7320508075688772},"66":{"tf":1.4142135623730951}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"58":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":7,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"68":{"tf":3.872983346207417}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":2.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"o":{"a":{"d":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.0},"12":{"tf":1.4142135623730951},"13":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"n":{"d":{"df":4,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"t":{"df":2,"docs":{"39":{"tf":1.0},"6":{"tf":1.0}}}}},"p":{"c":{"df":7,"docs":{"47":{"tf":1.7320508075688772},"48":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.4142135623730951},"53":{"tf":1.0},"62":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"40":{"tf":1.0}}}},"n":{"df":3,"docs":{"10":{"tf":1.0},"19":{"tf":1.0},"44":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":7,"docs":{"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"43":{"tf":2.23606797749979}}}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":3,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"58":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":11,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":2.449489742783178},"5":{"tf":2.449489742783178},"6":{"tf":1.0}}},"p":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"13":{"tf":1.0}}}},"w":{"df":2,"docs":{"31":{"tf":1.0},"8":{"tf":1.0}}}},"c":{"a":{"df":0,"docs":{},"l":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"25":{"tf":1.0}}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":3,"docs":{"12":{"tf":2.23606797749979},"17":{"tf":1.4142135623730951},"4":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":4,"docs":{"13":{"tf":2.0},"16":{"tf":1.7320508075688772},"42":{"tf":1.4142135623730951},"43":{"tf":1.7320508075688772}},"e":{"c":{"df":1,"docs":{"69":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"d":{"df":10,"docs":{"1":{"tf":1.4142135623730951},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"3":{"tf":1.0},"31":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"4":{"tf":1.0},"68":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"46":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"6":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"d":{"df":3,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"31":{"tf":2.0}}},"df":1,"docs":{"1":{"tf":1.0}},"m":{"df":1,"docs":{"5":{"tf":1.0}}}},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"25":{"tf":1.0},"27":{"tf":1.4142135623730951}}}}}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.0},"11":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"d":{"df":6,"docs":{"16":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"51":{"tf":1.4142135623730951},"68":{"tf":2.23606797749979},"69":{"tf":3.605551275463989}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"50":{"tf":1.0},"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":2,"docs":{"19":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"9":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":4,"docs":{"1":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"51":{"tf":1.0}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"19":{"tf":1.0},"35":{"tf":1.0}}}}}},"h":{"a":{"df":2,"docs":{"52":{"tf":1.0},"6":{"tf":1.0}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"34":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"34":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"44":{"tf":1.0}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":12,"docs":{"1":{"tf":1.0},"11":{"tf":1.4142135623730951},"31":{"tf":2.8284271247461903},"37":{"tf":1.0},"52":{"tf":1.4142135623730951},"54":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"65":{"tf":1.4142135623730951},"68":{"tf":3.605551275463989},"69":{"tf":3.4641016151377544}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"65":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":8,"docs":{"3":{"tf":1.0},"31":{"tf":1.7320508075688772},"52":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.0}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"36":{"tf":1.0},"58":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":3,"docs":{"10":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":10,"docs":{"13":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"4":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0}}}}},"z":{"df":0,"docs":{},"e":{"df":1,"docs":{"28":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}},"df":2,"docs":{"13":{"tf":1.4142135623730951},"29":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":3.4641016151377544},"15":{"tf":1.0},"16":{"tf":2.0},"17":{"tf":1.0},"4":{"tf":1.0}}},"w":{"df":1,"docs":{"9":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"25":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"15":{"tf":1.0},"16":{"tf":1.0},"5":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"o":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"l":{"a":{"df":0,"docs":{},"n":{"a":{"'":{"df":6,"docs":{"1":{"tf":1.4142135623730951},"25":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":2.0}}},"df":22,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":2.23606797749979},"28":{"tf":1.7320508075688772},"3":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":2.0},"35":{"tf":1.0},"4":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.7320508075688772},"5":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.0},"67":{"tf":1.4142135623730951},"68":{"tf":3.872983346207417},"69":{"tf":4.69041575982343},"8":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.0}}}},"v":{"df":1,"docs":{"25":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"27":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"a":{"c":{"df":0,"docs":{},"e":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":8,"docs":{"0":{"tf":1.0},"11":{"tf":1.0},"31":{"tf":2.0},"38":{"tf":1.0},"47":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":5,"docs":{"13":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.0},"68":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"43":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"9":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":2,"docs":{"19":{"tf":1.7320508075688772},"40":{"tf":1.0}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}},"k":{"df":0,"docs":{},"e":{"df":5,"docs":{"10":{"tf":1.0},"12":{"tf":1.0},"15":{"tf":1.0},"29":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951}}}},"n":{"d":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.0},"8":{"tf":1.0}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"50":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":3,"docs":{"13":{"tf":1.0},"37":{"tf":1.7320508075688772},"38":{"tf":1.0}}},"u":{"df":2,"docs":{"54":{"tf":1.0},"58":{"tf":1.4142135623730951}},"s":{"df":1,"docs":{"58":{"tf":1.4142135623730951}}}}},"y":{"df":1,"docs":{"28":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"19":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"26":{"tf":1.0},"27":{"tf":1.7320508075688772},"31":{"tf":1.4142135623730951},"35":{"tf":1.7320508075688772},"8":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":4,"docs":{"27":{"tf":1.0},"29":{"tf":1.4142135623730951},"31":{"tf":1.0},"37":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"16":{"tf":1.0},"19":{"tf":1.0},"28":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"56":{"tf":1.0}}}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"5":{"tf":1.0}}}},"n":{"df":0,"docs":{},"g":{"df":11,"docs":{"11":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"58":{"tf":1.7320508075688772},"60":{"tf":2.0},"61":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"65":{"tf":1.4142135623730951}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"u":{"b":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"(":{"df":1,"docs":{"69":{"tf":1.0}}},"df":1,"docs":{"69":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":1,"docs":{"56":{"tf":1.0}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"27":{"tf":1.0},"31":{"tf":2.0},"34":{"tf":1.0},"62":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":6,"docs":{"50":{"tf":1.0},"62":{"tf":1.7320508075688772},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":1,"docs":{"58":{"tf":1.0}},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"51":{"tf":1.0},"58":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"37":{"tf":1.0},"43":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}},"d":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"25":{"tf":1.0},"27":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{},"x":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":6,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"28":{"tf":1.0},"35":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"5":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"12":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}}},"y":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"28":{"tf":1.0}}}}}}},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"28":{"tf":1.0},"5":{"tf":2.449489742783178}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":1,"docs":{"8":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":5,"docs":{"10":{"tf":1.4142135623730951},"28":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":2.449489742783178},"68":{"tf":1.0}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{":":{":":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"42":{"tf":1.0},"43":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"31":{"tf":1.0},"41":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"34":{"tf":1.4142135623730951}}}}}}},"df":3,"docs":{"12":{"tf":2.449489742783178},"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":3,"docs":{"1":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}}},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":2,"docs":{"2":{"tf":1.0},"4":{"tf":1.0}}}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.4142135623730951},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"19":{"tf":1.4142135623730951}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":4,"docs":{"31":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"43":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"r":{"d":{"df":3,"docs":{"19":{"tf":1.4142135623730951},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":4,"docs":{"27":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"37":{"tf":1.0}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"s":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":3,"docs":{"15":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":1.0}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":2.23606797749979}}}}}}}}}},"u":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":2.23606797749979},"13":{"tf":3.872983346207417},"15":{"tf":1.0},"16":{"tf":1.7320508075688772},"17":{"tf":1.7320508075688772},"3":{"tf":2.23606797749979}}}},"df":1,"docs":{"31":{"tf":1.4142135623730951}},"m":{"df":0,"docs":{},"e":{"df":9,"docs":{"10":{"tf":1.0},"13":{"tf":2.449489742783178},"27":{"tf":1.0},"4":{"tf":1.4142135623730951},"5":{"tf":2.6457513110645907},"6":{"tf":1.0},"68":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"16":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":3,"docs":{"6":{"tf":1.4142135623730951},"68":{"tf":2.6457513110645907},"69":{"tf":3.0}}}}},"df":0,"docs":{}}}}}},"l":{"df":1,"docs":{"69":{"tf":1.0}}},"o":{"d":{"df":0,"docs":{},"o":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":10,"docs":{"29":{"tf":1.0},"3":{"tf":1.0},"35":{"tf":1.4142135623730951},"38":{"tf":1.0},"42":{"tf":1.4142135623730951},"43":{"tf":1.0},"56":{"tf":1.4142135623730951},"60":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"9":{"tf":1.0}}},"l":{"df":2,"docs":{"19":{"tf":1.0},"67":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"28":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"p":{"df":2,"docs":{"1":{"tf":1.7320508075688772},"3":{"tf":1.0}},"u":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}}},"r":{"a":{"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"16":{"tf":1.0},"3":{"tf":1.0},"9":{"tf":1.0}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":25,"docs":{"1":{"tf":2.449489742783178},"12":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.0},"3":{"tf":2.449489742783178},"36":{"tf":1.0},"37":{"tf":2.0},"39":{"tf":1.0},"40":{"tf":2.23606797749979},"41":{"tf":1.0},"43":{"tf":1.7320508075688772},"44":{"tf":1.0},"5":{"tf":1.7320508075688772},"52":{"tf":1.0},"54":{"tf":2.0},"58":{"tf":3.0},"59":{"tf":1.0},"6":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.7320508075688772},"65":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"69":{"tf":3.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"68":{"tf":2.449489742783178},"69":{"tf":3.0}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"t":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"25":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":2,"docs":{"13":{"tf":1.0},"25":{"tf":1.0}}}},"i":{"df":1,"docs":{"5":{"tf":1.0}}},"u":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":1,"docs":{"54":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"27":{"tf":1.0},"5":{"tf":1.4142135623730951},"6":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"6":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"u":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}}},"w":{"df":0,"docs":{},"o":{"df":4,"docs":{"13":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0},"5":{"tf":1.0}}}},"x":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"68":{"tf":3.3166247903554}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":10,"docs":{"37":{"tf":1.0},"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"i":{"c":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"10":{"tf":1.0},"30":{"tf":1.0}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"5":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"51":{"tf":1.0}}}},"t":{"df":4,"docs":{"19":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"3":{"tf":1.0}}}},"k":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"58":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":2.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":1,"docs":{"59":{"tf":1.0}}}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"65":{"tf":1.0},"66":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.0},"9":{"tf":1.0}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}},"p":{"df":8,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"11":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"40":{"tf":1.0},"5":{"tf":1.0}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}},"s":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":3.4641016151377544}}}},"d":{"df":2,"docs":{"27":{"tf":1.0},"30":{"tf":1.0}}},"df":23,"docs":{"1":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.8284271247461903},"35":{"tf":1.0},"4":{"tf":2.0},"42":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.4142135623730951},"5":{"tf":1.7320508075688772},"51":{"tf":1.0},"6":{"tf":2.23606797749979},"62":{"tf":1.0},"68":{"tf":1.0},"8":{"tf":2.0},"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":6,"docs":{"37":{"tf":1.0},"40":{"tf":1.0},"56":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":3,"docs":{"35":{"tf":1.0},"4":{"tf":1.0},"42":{"tf":1.7320508075688772}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"'":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"29":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":14,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":2.23606797749979},"15":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"22":{"tf":1.0},"25":{"tf":2.0},"27":{"tf":1.7320508075688772},"29":{"tf":1.7320508075688772},"3":{"tf":1.4142135623730951},"31":{"tf":2.0},"4":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":2,"docs":{"31":{"tf":1.0},"51":{"tf":1.0}}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"f":{"df":3,"docs":{"6":{"tf":1.0},"8":{"tf":1.7320508075688772},"9":{"tf":2.8284271247461903}}}},"df":3,"docs":{"16":{"tf":1.0},"17":{"tf":1.0},"69":{"tf":3.3166247903554}},"e":{"c":{"<":{"df":0,"docs":{},"u":{"8":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}},"f":{"df":5,"docs":{"1":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":7,"docs":{"28":{"tf":1.4142135623730951},"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":1.0},"6":{"tf":2.0},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"42":{"tf":1.0},"69":{"tf":4.69041575982343}}}}}}}},"i":{"a":{"df":2,"docs":{"11":{"tf":1.0},"12":{"tf":1.0}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"13":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0}}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":1.4142135623730951},"13":{"tf":2.449489742783178},"15":{"tf":1.7320508075688772},"16":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.0},"69":{"tf":1.0}}}},"l":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"68":{"tf":1.0}}},"df":3,"docs":{"67":{"tf":1.0},"68":{"tf":3.872983346207417},"69":{"tf":4.69041575982343}}}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"r":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"h":{"/":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"19":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":2.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":2.23606797749979}}}}},"y":{"df":5,"docs":{"19":{"tf":1.0},"28":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.7320508075688772},"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"'":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.0}}}},"r":{"df":1,"docs":{"5":{"tf":1.0}}}},"b":{"3":{".":{"df":0,"docs":{},"j":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"49":{"tf":1.0},"50":{"tf":1.0},"62":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"15":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"l":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":2,"docs":{"20":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"37":{"tf":1.0},"5":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"37":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"40":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"d":{"df":1,"docs":{"3":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":4,"docs":{"25":{"tf":1.0},"29":{"tf":1.0},"44":{"tf":1.0},"8":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":3,"docs":{"20":{"tf":1.0},"35":{"tf":1.4142135623730951},"58":{"tf":1.0}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"33":{"tf":1.0},"35":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"s":{":":{"/":{"/":{"<":{"a":{"d":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"8":{"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"df":10,"docs":{"13":{"tf":1.7320508075688772},"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}},"x":{"df":1,"docs":{"13":{"tf":1.0}}}},"y":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"'":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"z":{"df":2,"docs":{"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":1,"docs":{"43":{"tf":1.0}}}}}}}},"breadcrumbs":{"root":{"0":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},".":{"1":{"1":{".":{"0":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"40":{"tf":1.0},"61":{"tf":6.48074069840786}}},"1":{"/":{"1":{"0":{"0":{"0":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"15":{"tf":1.4142135623730951}}},"1":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"2":{"1":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":2,"docs":{"61":{"tf":1.0},"68":{"tf":3.0}}},"4":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"7":{".":{"0":{".":{"0":{".":{"1":{":":{"8":{"0":{"0":{"1":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":2,"docs":{"61":{"tf":1.0},"68":{"tf":1.7320508075688772}}},"3":{"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"4":{",":{"0":{"0":{"0":{"df":2,"docs":{"28":{"tf":1.0},"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"5":{"0":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"6":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"7":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":2,"docs":{"5":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"8":{"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"9":{"0":{"df":1,"docs":{"61":{"tf":1.0}}},"1":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"2":{".":{"1":{"6":{"8":{".":{"1":{".":{"8":{"8":{":":{"8":{"8":{"9":{"9":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"4":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"1":{"df":1,"docs":{"5":{"tf":1.0}}},"4":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":7,"docs":{"30":{"tf":1.0},"31":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}},"g":{"b":{"df":0,"docs":{},"p":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}},"k":{"df":1,"docs":{"31":{"tf":1.7320508075688772}}},"m":{"b":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}},"t":{"b":{"df":1,"docs":{"31":{"tf":2.0}}},"df":0,"docs":{}}},"2":{".":{"0":{"\"":{",":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"47":{"tf":1.0},"51":{"tf":1.0}}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"1":{"tf":1.0}}},"1":{"7":{"df":1,"docs":{"8":{"tf":1.0}}},"8":{"df":1,"docs":{"68":{"tf":1.7320508075688772}}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"1":{"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"2":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"3":{"4":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"5":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"4":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{},"t":{"2":{"3":{":":{"5":{"9":{":":{"0":{"0":{"df":1,"docs":{"68":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"68":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"5":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":2,"docs":{"52":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{".":{"4":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.0}}},"df":4,"docs":{"13":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"61":{"tf":1.0}}},"3":{"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"2":{"df":2,"docs":{"56":{"tf":1.4142135623730951},"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"4":{"0":{"0":{"0":{"df":1,"docs":{"9":{"tf":1.0}}},"df":1,"docs":{"1":{"tf":1.0}}},"df":3,"docs":{"1":{"tf":1.0},"5":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"df":2,"docs":{"27":{"tf":1.0},"51":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":2,"docs":{"27":{"tf":1.0},"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"df":2,"docs":{"60":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"8":{"df":9,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}},"6":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"4":{"df":4,"docs":{"55":{"tf":1.0},"56":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":3,"docs":{"31":{"tf":1.0},"61":{"tf":1.4142135623730951},"8":{"tf":1.0}}},"7":{"0":{"df":1,"docs":{"61":{"tf":1.0}}},"1":{"0":{",":{"0":{"0":{"0":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"8":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"9":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"9":{"0":{"df":1,"docs":{"15":{"tf":1.0}}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"a":{"'":{"df":1,"docs":{"16":{"tf":1.0}}},"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"28":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"v":{"df":5,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"34":{"tf":1.0},"9":{"tf":1.0}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"47":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"35":{"tf":1.0},"5":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"40":{"tf":1.0}}},":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":15,"docs":{"3":{"tf":1.0},"35":{"tf":2.6457513110645907},"37":{"tf":1.4142135623730951},"38":{"tf":2.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"42":{"tf":2.0},"43":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":2.449489742783178},"58":{"tf":1.0},"60":{"tf":1.0},"63":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"66":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"63":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"63":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"64":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"u":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":2,"docs":{"27":{"tf":1.0},"36":{"tf":1.4142135623730951}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":2,"docs":{"25":{"tf":1.0},"62":{"tf":1.0}}}}}},"d":{"d":{"df":2,"docs":{"19":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"3":{"tf":1.0},"37":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":2.0}}}}}}},"df":3,"docs":{"19":{"tf":1.4142135623730951},"25":{"tf":1.0},"58":{"tf":1.0}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}}},"o":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"27":{"tf":1.0}}},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":3,"docs":{"60":{"tf":1.4142135623730951},"68":{"tf":1.4142135623730951},"69":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"df":4,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"5":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}}}}}}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":3,"docs":{"36":{"tf":2.23606797749979},"37":{"tf":1.0},"43":{"tf":1.0}}},"df":0,"docs":{},"w":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.0},"42":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"27":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"6":{"tf":1.0},"8":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":5,"docs":{"19":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"9":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"i":{"df":2,"docs":{"47":{"tf":1.4142135623730951},"53":{"tf":1.4142135623730951}}},"p":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":24,"docs":{"46":{"tf":1.4142135623730951},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":9,"docs":{"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":4,"docs":{"12":{"tf":1.0},"47":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.7320508075688772}}},"df":2,"docs":{"5":{"tf":1.0},"69":{"tf":1.0}}}},"r":{"df":0,"docs":{},"o":{"a":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"5":{"tf":1.0},"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"v":{"df":1,"docs":{"31":{"tf":1.0}}},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}}}}}}}},"r":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":2.23606797749979},"3":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}}},"g":{"df":1,"docs":{"69":{"tf":2.6457513110645907}},"u":{"df":1,"docs":{"9":{"tf":1.0}}}},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}},"y":{"df":4,"docs":{"41":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.7320508075688772},"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"k":{"df":1,"docs":{"9":{"tf":1.0}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":4,"docs":{"40":{"tf":1.7320508075688772},"42":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772},"56":{"tf":1.4142135623730951}}}}},"o":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"37":{"tf":1.0},"42":{"tf":1.0},"56":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"58":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":2,"docs":{"3":{"tf":1.0},"36":{"tf":1.0}}}},"t":{"a":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"31":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"0":{"tf":1.0}}},"df":4,"docs":{"1":{"tf":1.0},"68":{"tf":1.4142135623730951},"69":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"65":{"tf":1.0}}}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"30":{"tf":1.0},"5":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"25":{"tf":1.7320508075688772},"26":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0}}}},"df":0,"docs":{}}},"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}}}},"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"13":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":5,"docs":{"40":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"55":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":2.23606797749979}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"n":{"d":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"e":{"df":10,"docs":{"5":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"i":{"c":{"df":2,"docs":{"27":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"1":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"40":{"tf":1.0},"51":{"tf":1.0},"69":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":2,"docs":{"16":{"tf":1.4142135623730951},"17":{"tf":1.0}},"e":{"c":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":4,"docs":{"19":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.4142135623730951},"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":6,"docs":{"17":{"tf":1.0},"19":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"g":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"43":{"tf":1.0}}}},"w":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"27":{"tf":1.0},"62":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.0},"42":{"tf":1.0}}}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":6,"docs":{"1":{"tf":1.0},"23":{"tf":1.0},"3":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}}}},"df":4,"docs":{"55":{"tf":1.0},"56":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"1":{"tf":1.4142135623730951},"10":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":5,"docs":{"10":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"6":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"54":{"tf":1.0},"56":{"tf":1.0}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"16":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0}}},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"f":{"df":1,"docs":{"34":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"a":{"d":{"c":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"34":{"tf":1.0}}},"df":0,"docs":{}}},"df":2,"docs":{"5":{"tf":1.0},"56":{"tf":1.7320508075688772}}}}}},"c":{"/":{"c":{"+":{"+":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"a":{"df":1,"docs":{"34":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"12":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":8,"docs":{"10":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"27":{"tf":1.0},"35":{"tf":1.0},"43":{"tf":1.4142135623730951},"6":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":6,"docs":{"15":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":2.6457513110645907}}}}},"df":0,"docs":{}},"p":{"a":{"c":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"d":{"df":2,"docs":{"20":{"tf":1.0},"28":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"e":{"df":2,"docs":{"16":{"tf":1.4142135623730951},"20":{"tf":1.0}}},"t":{"df":1,"docs":{"15":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"36":{"tf":1.0}}}}},"b":{"c":{"_":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"28":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}},"r":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"1":{"tf":1.0},"27":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"6":{"tf":1.0},"9":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":14,"docs":{"13":{"tf":2.0},"33":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":1.7320508075688772}}}},"n":{"df":0,"docs":{},"g":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0},"9":{"tf":1.0}}}},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"10":{"tf":1.0}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"0":{"tf":1.0}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"6":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"33":{"tf":1.0},"34":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"s":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"13":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":2,"docs":{"31":{"tf":1.0},"52":{"tf":1.4142135623730951}}}}}},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"9":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":1,"docs":{"67":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":2,"docs":{"34":{"tf":1.0},"43":{"tf":1.0}}},"df":9,"docs":{"29":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"34":{"tf":2.0},"35":{"tf":1.7320508075688772},"37":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.0}},"’":{"df":1,"docs":{"33":{"tf":1.0}}}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"23":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.7320508075688772},"34":{"tf":1.4142135623730951}}}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":4,"docs":{"3":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}},"m":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"67":{"tf":1.0},"68":{"tf":3.872983346207417}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"3":{"tf":1.0}}}},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"34":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}},"s":{"df":2,"docs":{"36":{"tf":1.0},"43":{"tf":1.0}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":3,"docs":{"25":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":6,"docs":{"1":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"33":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"58":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"69":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"65":{"tf":1.0},"68":{"tf":1.7320508075688772},"69":{"tf":2.449489742783178}},"e":{"d":{"\"":{",":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\"":{":":{"0":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"50":{"tf":1.0},"54":{"tf":1.4142135623730951},"58":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"8":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"62":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":2,"docs":{"6":{"tf":1.0},"8":{"tf":2.6457513110645907}}}}}},"i":{"d":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"17":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":7,"docs":{"13":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":1.0},"46":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.0},"61":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":12,"docs":{"0":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":6,"docs":{"12":{"tf":1.0},"15":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.0},"44":{"tf":1.0},"9":{"tf":1.0}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":4,"docs":{"23":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"47":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":5,"docs":{"20":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"39":{"tf":1.0},"9":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"27":{"tf":1.4142135623730951},"30":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":8,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.0},"13":{"tf":1.7320508075688772},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":2.6457513110645907},"59":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}}},"p":{"df":0,"docs":{},"u":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"67":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":10,"docs":{"17":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":2.0},"34":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"61":{"tf":1.0},"9":{"tf":1.0}},"e":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"42":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"8":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":2,"docs":{"31":{"tf":1.0},"6":{"tf":1.0}},"i":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":9,"docs":{"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":11,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"30":{"tf":1.0},"4":{"tf":1.4142135623730951},"59":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}}}},"y":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"_":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.0}}}},"b":{"a":{"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":11,"docs":{"13":{"tf":2.449489742783178},"19":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":2.449489742783178},"28":{"tf":1.0},"31":{"tf":1.0},"35":{"tf":1.4142135623730951},"40":{"tf":1.0},"51":{"tf":1.7320508075688772},"52":{"tf":1.4142135623730951},"9":{"tf":2.449489742783178}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.7320508075688772},"30":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"68":{"tf":1.7320508075688772},"69":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}}}},"y":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"i":{"d":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"48":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"37":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}}}}}},"l":{"a":{"df":0,"docs":{},"y":{"df":3,"docs":{"6":{"tf":1.7320508075688772},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"27":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}}}}}}},"n":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"36":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":2,"docs":{"68":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":3,"docs":{"13":{"tf":1.0},"31":{"tf":1.0},"9":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":4,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"42":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":3,"docs":{"0":{"tf":1.0},"19":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":3,"docs":{"1":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"25":{"tf":1.0},"8":{"tf":1.0}}}}}}}},"i":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"34":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"10":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"5":{"tf":2.23606797749979},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"s":{"c":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"13":{"tf":1.0},"35":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"k":{"df":1,"docs":{"20":{"tf":1.0}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"28":{"tf":1.0},"5":{"tf":2.23606797749979},"67":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"0":{"tf":1.0},"31":{"tf":1.0}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"29":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.4142135623730951}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"1":{"tf":1.0}}}},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":2.0}}}}}},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"13":{"tf":1.7320508075688772},"27":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"36":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"g":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}},"1":{"df":1,"docs":{"13":{"tf":1.0}}},"2":{"df":1,"docs":{"13":{"tf":1.0}}},"3":{"df":1,"docs":{"13":{"tf":1.0}}},"4":{"df":1,"docs":{"13":{"tf":1.0}}},"5":{"df":1,"docs":{"13":{"tf":1.0}}},"a":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"19":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"28":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"d":{"2":{"5":{"5":{"1":{"9":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":1,"docs":{"13":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"28":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}}},"g":{"df":2,"docs":{"48":{"tf":1.0},"49":{"tf":1.0}}},"l":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":1,"docs":{"68":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":1,"docs":{"34":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"n":{"c":{"df":0,"docs":{},"o":{"d":{"df":11,"docs":{"13":{"tf":1.0},"42":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":2.23606797749979},"31":{"tf":2.0}}}}}}},"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"48":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951}}}}}}}},"df":1,"docs":{"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"36":{"tf":1.0},"39":{"tf":1.7320508075688772}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"29":{"tf":1.0},"35":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":10,"docs":{"13":{"tf":2.0},"16":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":2.449489742783178},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.7320508075688772},"57":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"p":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"4":{"tf":1.0}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"28":{"tf":1.0},"40":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"0":{"tf":1.0},"3":{"tf":1.0}}}}}},"t":{"c":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}},"x":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"1":{"tf":1.0},"13":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":18,"docs":{"14":{"tf":1.4142135623730951},"19":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"68":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"\"":{":":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"\"":{":":{"1":{",":{"\"":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"\"":{":":{"[":{"3":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"5":{"0":{",":{"4":{"8":{",":{"5":{"3":{",":{"4":{"8":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"8":{"4":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"9":{"0":{",":{"2":{"5":{"2":{",":{"1":{"0":{",":{"7":{",":{"2":{"8":{",":{"2":{"4":{"6":{",":{"1":{"4":{"0":{",":{"8":{"8":{",":{"1":{"7":{"7":{",":{"9":{"8":{",":{"8":{"2":{",":{"1":{"0":{",":{"2":{"2":{"7":{",":{"8":{"9":{",":{"8":{"1":{",":{"1":{"8":{",":{"3":{"0":{",":{"1":{"9":{"4":{",":{"1":{"0":{"1":{",":{"1":{"9":{"9":{",":{"1":{"6":{",":{"1":{"1":{",":{"7":{"3":{",":{"1":{"3":{"3":{",":{"2":{"0":{",":{"2":{"4":{"6":{",":{"6":{"2":{",":{"1":{"1":{"4":{",":{"3":{"9":{",":{"2":{"0":{",":{"1":{"1":{"3":{",":{"1":{"8":{"9":{",":{"3":{"2":{",":{"5":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"4":{"7":{",":{"1":{"5":{",":{"3":{"6":{",":{"1":{"0":{"2":{",":{"1":{"6":{"7":{",":{"8":{"3":{",":{"2":{"2":{"5":{",":{"4":{"2":{",":{"1":{"3":{"3":{",":{"1":{"2":{"7":{",":{"8":{"2":{",":{"3":{"4":{",":{"3":{"6":{",":{"2":{"2":{"4":{",":{"2":{"0":{"7":{",":{"1":{"3":{"0":{",":{"1":{"0":{"9":{",":{"2":{"3":{"0":{",":{"2":{"2":{"4":{",":{"1":{"8":{"8":{",":{"1":{"6":{"3":{",":{"3":{"3":{",":{"2":{"1":{"3":{",":{"1":{"3":{",":{"5":{",":{"1":{"1":{"7":{",":{"2":{"1":{"1":{",":{"2":{"5":{"1":{",":{"6":{"5":{",":{"1":{"5":{"9":{",":{"1":{"9":{"7":{",":{"5":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{"df":0,"docs":{},"}":{",":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\"":{":":{"0":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":12,"docs":{"1":{"tf":1.0},"3":{"tf":1.4142135623730951},"33":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"40":{"tf":2.449489742783178},"41":{"tf":1.4142135623730951},"43":{"tf":1.0},"5":{"tf":1.0},"56":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"1":{"tf":1.0},"20":{"tf":1.4142135623730951},"42":{"tf":1.0},"9":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"28":{"tf":1.0},"68":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"r":{"df":2,"docs":{"13":{"tf":1.0},"16":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.0}}}}}}}},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.7320508075688772}}}},"r":{"df":1,"docs":{"6":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.7320508075688772}},"l":{"df":1,"docs":{"1":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"51":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"3":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"l":{"df":2,"docs":{"12":{"tf":1.0},"16":{"tf":1.7320508075688772}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.4142135623730951},"3":{"tf":1.0}}}},"d":{"df":3,"docs":{"25":{"tf":1.0},"46":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"13":{"tf":1.0},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}}},"l":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":3.4641016151377544}}},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"13":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"19":{"tf":1.7320508075688772}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":9,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.0},"4":{"tf":1.0},"40":{"tf":1.0},"46":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0}}}}}},"r":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":2,"docs":{"13":{"tf":2.6457513110645907},"16":{"tf":1.0}}},"m":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"45":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"63":{"tf":1.0},"65":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"1":{"tf":1.0},"25":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"3":{"tf":1.0},"37":{"tf":1.0}},"i":{"df":1,"docs":{"61":{"tf":1.0}}},"n":{"df":0,"docs":{},"o":{"d":{"df":10,"docs":{"10":{"tf":1.7320508075688772},"18":{"tf":1.4142135623730951},"19":{"tf":1.0},"20":{"tf":1.7320508075688772},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"27":{"tf":1.7320508075688772},"3":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":5,"docs":{"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.7320508075688772},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"0":{"tf":1.0},"1":{"tf":1.0}}}}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"4":{"tf":1.7320508075688772},"44":{"tf":1.4142135623730951},"58":{"tf":1.0}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"1":{"tf":1.0},"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"13":{"tf":1.4142135623730951},"15":{"tf":1.0},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.0},"51":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":2,"docs":{"50":{"tf":1.0},"56":{"tf":1.4142135623730951}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"50":{"tf":1.0},"55":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"50":{"tf":1.0},"57":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":2,"docs":{"50":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"50":{"tf":1.0},"59":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"a":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"5":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"47":{"tf":1.0}},"n":{"df":4,"docs":{"19":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0}}}},"df":1,"docs":{"31":{"tf":1.0}},"e":{"df":1,"docs":{"1":{"tf":1.0}}},"o":{"d":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951}}}}}}},"p":{"df":0,"docs":{},"u":{"df":4,"docs":{"20":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"40":{"tf":1.0},"43":{"tf":2.23606797749979}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"h":{".":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"h":{"df":10,"docs":{"11":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.7320508075688772},"27":{"tf":2.0},"28":{"tf":1.0},"31":{"tf":3.3166247903554},"52":{"tf":1.4142135623730951},"57":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":2.449489742783178}}}}},"df":10,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"69":{"tf":3.3166247903554}},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"6":{"tf":1.7320508075688772},"9":{"tf":1.4142135623730951}}}}}},"l":{"df":0,"docs":{},"p":{"df":1,"docs":{"69":{"tf":4.898979485566356}}}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"25":{"tf":1.0},"6":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"5":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}},"i":{"df":4,"docs":{"28":{"tf":1.0},"7":{"tf":1.7320508075688772},"8":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"35":{"tf":1.0}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"o":{"d":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{":":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{":":{"/":{"/":{"1":{"9":{"2":{".":{"1":{"6":{"8":{".":{"1":{".":{"8":{"8":{":":{"8":{"8":{"9":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"8":{"8":{"9":{"9":{"df":9,"docs":{"48":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"47":{"tf":1.0},"48":{"tf":1.4142135623730951},"51":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{".":{"df":1,"docs":{"34":{"tf":1.0}}},"d":{"\"":{":":{"1":{"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":7,"docs":{"51":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.7320508075688772}},"e":{"a":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":2.23606797749979},"31":{"tf":4.123105625617661}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":2,"docs":{"35":{"tf":1.0},"51":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"68":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":6,"docs":{"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"8":{"tf":1.4142135623730951}}}}}}},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"3":{"tf":1.0},"36":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"34":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"i":{"c":{"df":2,"docs":{"56":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"56":{"tf":1.0},"69":{"tf":4.69041575982343}}}}}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"31":{"tf":1.0},"43":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"19":{"tf":1.0},"20":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"47":{"tf":1.0}}},"df":0,"docs":{}},"t":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":1,"docs":{"30":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"1":{"tf":1.7320508075688772},"3":{"tf":1.7320508075688772},"36":{"tf":2.0},"37":{"tf":1.4142135623730951},"4":{"tf":1.0},"40":{"tf":1.0},"43":{"tf":1.4142135623730951},"52":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"42":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":9,"docs":{"51":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.0},"59":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.4142135623730951},"37":{"tf":1.0},"47":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"a":{"c":{"df":4,"docs":{"37":{"tf":1.0},"42":{"tf":1.7320508075688772},"47":{"tf":1.0},"67":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.4142135623730951},"34":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"6":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"51":{"tf":1.0}}},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"41":{"tf":1.4142135623730951}}}}}}},"t":{"'":{"df":5,"docs":{"13":{"tf":1.0},"27":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":4,"docs":{"31":{"tf":1.0},"37":{"tf":1.4142135623730951},"43":{"tf":1.0},"5":{"tf":1.0}}}}}}}},"j":{".":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"47":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"b":{"df":2,"docs":{"13":{"tf":1.0},"19":{"tf":1.0}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"46":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":4,"docs":{"47":{"tf":1.7320508075688772},"51":{"tf":2.23606797749979},"53":{"tf":1.4142135623730951},"56":{"tf":1.0}},"r":{"df":0,"docs":{},"p":{"c":{"\"":{":":{"\"":{"2":{".":{"0":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":4,"docs":{"57":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"\"":{":":{"\"":{"2":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"6":{"c":{"b":{"8":{"df":0,"docs":{},"v":{"a":{"a":{"d":{"9":{"3":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"6":{"df":0,"docs":{},"v":{"d":{"8":{"df":0,"docs":{},"p":{"6":{"7":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"q":{"df":0,"docs":{},"z":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"x":{"4":{"7":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"g":{"9":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"1":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"3":{"a":{"2":{"df":0,"docs":{},"g":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"7":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"j":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"y":{"9":{"b":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"5":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"v":{"df":0,"docs":{},"z":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"x":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"9":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"j":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"j":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"8":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"q":{"df":0,"docs":{},"p":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"4":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"3":{"df":0,"docs":{},"z":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"z":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"v":{"6":{"df":0,"docs":{},"u":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"k":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"w":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"7":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"3":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"7":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"9":{"df":0,"docs":{},"j":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"2":{"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"w":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"3":{"df":0,"docs":{},"z":{"6":{"9":{"df":0,"docs":{},"x":{"df":0,"docs":{},"m":{"1":{"df":0,"docs":{},"z":{"df":0,"docs":{},"e":{"3":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"6":{"df":0,"docs":{},"j":{"c":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"0":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"55":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"6":{"8":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"54":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"{":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"\"":{":":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"\"":{":":{"1":{",":{"\"":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"\"":{":":{"[":{"3":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"5":{"0":{",":{"4":{"8":{",":{"5":{"3":{",":{"4":{"8":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"8":{"4":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"9":{"0":{",":{"2":{"5":{"2":{",":{"1":{"0":{",":{"7":{",":{"2":{"8":{",":{"2":{"4":{"6":{",":{"1":{"4":{"0":{",":{"8":{"8":{",":{"1":{"7":{"7":{",":{"9":{"8":{",":{"8":{"2":{",":{"1":{"0":{",":{"2":{"2":{"7":{",":{"8":{"9":{",":{"8":{"1":{",":{"1":{"8":{",":{"3":{"0":{",":{"1":{"9":{"4":{",":{"1":{"0":{"1":{",":{"1":{"9":{"9":{",":{"1":{"6":{",":{"1":{"1":{",":{"7":{"3":{",":{"1":{"3":{"3":{",":{"2":{"0":{",":{"2":{"4":{"6":{",":{"6":{"2":{",":{"1":{"1":{"4":{",":{"3":{"9":{",":{"2":{"0":{",":{"1":{"1":{"3":{",":{"1":{"8":{"9":{",":{"3":{"2":{",":{"5":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"4":{"7":{",":{"1":{"5":{",":{"3":{"6":{",":{"1":{"0":{"2":{",":{"1":{"6":{"7":{",":{"8":{"3":{",":{"2":{"2":{"5":{",":{"4":{"2":{",":{"1":{"3":{"3":{",":{"1":{"2":{"7":{",":{"8":{"2":{",":{"3":{"4":{",":{"3":{"6":{",":{"2":{"2":{"4":{",":{"2":{"0":{"7":{",":{"1":{"3":{"0":{",":{"1":{"0":{"9":{",":{"2":{"3":{"0":{",":{"2":{"2":{"4":{",":{"1":{"8":{"8":{",":{"1":{"6":{"3":{",":{"3":{"3":{",":{"2":{"1":{"3":{",":{"1":{"3":{",":{"5":{",":{"1":{"1":{"7":{",":{"2":{"1":{"1":{",":{"2":{"5":{"1":{",":{"6":{"5":{",":{"1":{"5":{"9":{",":{"1":{"9":{"7":{",":{"5":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{"df":0,"docs":{},"}":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"56":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":5,"docs":{"51":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"24":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"25":{"tf":1.0}}}}}}}}},"k":{"df":3,"docs":{"15":{"tf":2.0},"17":{"tf":1.0},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":2,"docs":{"16":{"tf":1.0},"27":{"tf":1.0}}}},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}}},"y":{"df":13,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"35":{"tf":1.4142135623730951},"37":{"tf":1.0},"4":{"tf":1.4142135623730951},"41":{"tf":1.0},"5":{"tf":1.0},"52":{"tf":1.7320508075688772},"63":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"p":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":4,"docs":{"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"4":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"4":{"tf":1.0}}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"35":{"tf":1.0}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"5":{"tf":1.0}},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"l":{".":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"1":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"2":{"df":1,"docs":{"13":{"tf":2.0}}},"3":{"df":1,"docs":{"13":{"tf":2.8284271247461903}}},"4":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"g":{"df":3,"docs":{"1":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"15":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"31":{"tf":1.0},"57":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.7320508075688772},"3":{"tf":1.0}}},"df":13,"docs":{"10":{"tf":2.8284271247461903},"11":{"tf":2.0},"12":{"tf":3.3166247903554},"13":{"tf":3.4641016151377544},"15":{"tf":1.4142135623730951},"16":{"tf":2.449489742783178},"17":{"tf":2.23606797749979},"20":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"4":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":12,"docs":{"13":{"tf":1.7320508075688772},"20":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"3":{"tf":2.23606797749979},"31":{"tf":1.7320508075688772},"45":{"tf":1.4142135623730951},"57":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0}}}}}},"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"25":{"tf":1.7320508075688772}}}}}},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"47":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"3":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"1":{"tf":1.0}}}},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"19":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"67":{"tf":1.0}}},"k":{"df":2,"docs":{"13":{"tf":1.0},"9":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"37":{"tf":1.4142135623730951},"58":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"31":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"df":1,"docs":{"34":{"tf":1.0}}}}},"o":{"a":{"d":{"df":5,"docs":{"19":{"tf":2.6457513110645907},"34":{"tf":1.0},"35":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"56":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"58":{"tf":1.0}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"15":{"tf":1.0}}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"31":{"tf":1.0},"44":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.0},"39":{"tf":1.0}}}}}},"m":{"a":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"1":{"tf":1.0},"3":{"tf":1.0},"38":{"tf":1.4142135623730951},"5":{"tf":1.0}}}}},"df":0,"docs":{}}}},"k":{"df":0,"docs":{},"e":{"df":4,"docs":{"1":{"tf":1.0},"19":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"51":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"i":{"df":2,"docs":{"30":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}}}}},"p":{"df":2,"docs":{"39":{"tf":1.0},"41":{"tf":1.4142135623730951}}},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"3":{"tf":1.0},"51":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"46":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.0}}}}}},"x":{"df":1,"docs":{"69":{"tf":1.0}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"19":{"tf":1.0},"27":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"y":{"b":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}},"df":4,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"15":{"tf":2.6457513110645907},"17":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"n":{"df":3,"docs":{"1":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"0":{"tf":1.0},"1":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"10":{"tf":1.0},"8":{"tf":2.23606797749979}}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"36":{"tf":2.23606797749979},"37":{"tf":1.0},"38":{"tf":1.0},"43":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"\"":{":":{"\"":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"51":{"tf":1.0},"55":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"66":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"'":{"df":1,"docs":{"5":{"tf":1.0}}},"df":6,"docs":{"47":{"tf":1.0},"5":{"tf":2.0},"50":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"58":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"4":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"29":{"tf":1.0}}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"27":{"tf":1.0},"8":{"tf":1.0}}}}},"p":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"4":{"tf":1.0}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":2,"docs":{"10":{"tf":1.0},"20":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":3,"docs":{"40":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"8":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":6,"docs":{"3":{"tf":1.0},"31":{"tf":1.7320508075688772},"5":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0},"8":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"42":{"tf":2.0}}}}},"u":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":7,"docs":{"25":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"43":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.0}}}}}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":2,"docs":{"17":{"tf":1.7320508075688772},"8":{"tf":1.0}}}}},"c":{"df":0,"docs":{},"p":{"df":1,"docs":{"23":{"tf":1.4142135623730951}}}},"df":3,"docs":{"11":{"tf":1.4142135623730951},"17":{"tf":1.0},"69":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"37":{"tf":1.0},"5":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"d":{"df":9,"docs":{"13":{"tf":1.0},"19":{"tf":1.0},"27":{"tf":1.0},"31":{"tf":1.4142135623730951},"42":{"tf":1.0},"43":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":14,"docs":{"1":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"13":{"tf":2.23606797749979},"15":{"tf":1.0},"17":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"27":{"tf":2.0},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"31":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951},"6":{"tf":1.0},"69":{"tf":1.7320508075688772}}}}}}},"w":{"df":7,"docs":{"13":{"tf":1.4142135623730951},"17":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"58":{"tf":1.4142135623730951},"61":{"tf":1.0},"8":{"tf":1.0}}},"x":{"df":0,"docs":{},"t":{"df":5,"docs":{"10":{"tf":1.0},"12":{"tf":1.4142135623730951},"16":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"o":{"d":{"df":0,"docs":{},"e":{"df":15,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":2.0},"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"17":{"tf":2.23606797749979},"23":{"tf":1.0},"25":{"tf":1.7320508075688772},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"47":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"69":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}},"e":{"df":2,"docs":{"57":{"tf":1.0},"59":{"tf":1.0}}}},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"58":{"tf":1.0}}},"h":{"df":1,"docs":{"0":{"tf":1.0}}},"i":{"df":0,"docs":{},"f":{"df":4,"docs":{"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}}},"w":{"df":1,"docs":{"6":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"3":{"tf":1.0}}}},"u":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":1,"docs":{"27":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"df":7,"docs":{"17":{"tf":2.23606797749979},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"56":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}},"df":1,"docs":{"69":{"tf":2.23606797749979}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":5,"docs":{"12":{"tf":1.4142135623730951},"13":{"tf":2.23606797749979},"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}},"c":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":4,"docs":{"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"19":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"c":{"df":5,"docs":{"13":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"5":{"tf":1.0},"62":{"tf":1.0}}},"df":12,"docs":{"13":{"tf":1.0},"15":{"tf":1.0},"19":{"tf":2.0},"20":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"5":{"tf":2.0},"58":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":1.0}}},"p":{"a":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"15":{"tf":1.0},"19":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.7320508075688772}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"19":{"tf":1.0},"28":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"12":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":2.8284271247461903}}}}}}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"16":{"tf":1.0},"28":{"tf":1.0},"51":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"16":{"tf":1.0},"25":{"tf":1.0},"6":{"tf":1.0},"8":{"tf":1.0}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"20":{"tf":1.0},"51":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"9":{"tf":1.0}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":2,"docs":{"25":{"tf":1.0},"7":{"tf":1.0}}}}}}}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"35":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"56":{"tf":1.0}}}}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":2,"docs":{"40":{"tf":1.0},"41":{"tf":1.0}}}},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"8":{"tf":1.0},"9":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":2,"docs":{"3":{"tf":1.0},"40":{"tf":1.0}},"i":{"df":0,"docs":{},"z":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"m":{"df":3,"docs":{"51":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":13,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}}},"s":{"\"":{":":{"[":{"\"":{"2":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"6":{"c":{"b":{"8":{"df":0,"docs":{},"v":{"a":{"a":{"d":{"9":{"3":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"6":{"df":0,"docs":{},"v":{"d":{"8":{"df":0,"docs":{},"p":{"6":{"7":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"q":{"df":0,"docs":{},"z":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"x":{"4":{"7":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"g":{"9":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"1":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"3":{"a":{"2":{"df":0,"docs":{},"g":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"7":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"j":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"y":{"9":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"g":{"df":0,"docs":{},"v":{"df":0,"docs":{},"k":{"df":0,"docs":{},"y":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"h":{"b":{"2":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"n":{"3":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"f":{"df":0,"docs":{},"k":{"df":0,"docs":{},"x":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"h":{"df":0,"docs":{},"p":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"56":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}}},"5":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"v":{"df":0,"docs":{},"z":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"x":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"9":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"j":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"j":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"8":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"q":{"df":0,"docs":{},"p":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"4":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"3":{"df":0,"docs":{},"z":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"z":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"v":{"6":{"df":0,"docs":{},"u":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"k":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"w":{"df":2,"docs":{"54":{"tf":1.0},"58":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"8":{"3":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"t":{"2":{"df":0,"docs":{},"h":{"5":{"df":0,"docs":{},"u":{"1":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"d":{"df":0,"docs":{},"q":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"6":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"y":{"3":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"51":{"tf":1.0},"55":{"tf":1.0},"60":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"m":{"7":{"8":{"c":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"8":{"df":0,"docs":{},"o":{"3":{"df":0,"docs":{},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"h":{"df":0,"docs":{},"x":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"z":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"4":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"df":0,"docs":{},"k":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"x":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"k":{"df":0,"docs":{},"n":{"df":0,"docs":{},"h":{"1":{"2":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"0":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}}},"[":{"6":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"29":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}},"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"t":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"15":{"tf":2.0},"16":{"tf":1.4142135623730951}}}}}},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"34":{"tf":1.0},"35":{"tf":1.0},"43":{"tf":1.0},"6":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"i":{"d":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{".":{"df":0,"docs":{},"o":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.7320508075688772}}}},"y":{"df":2,"docs":{"68":{"tf":2.449489742783178},"69":{"tf":1.7320508075688772}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":10,"docs":{"1":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"27":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.4142135623730951},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"0":{"tf":1.0},"1":{"tf":1.7320508075688772},"19":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"11":{"tf":1.0},"27":{"tf":1.0},"31":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"35":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}}}}},"t":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.0},"35":{"tf":1.7320508075688772}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":1,"docs":{"27":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"19":{"tf":2.8284271247461903},"20":{"tf":2.0},"40":{"tf":1.0}}}}}}}},"l":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"23":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"h":{"'":{"df":1,"docs":{"9":{"tf":1.0}}},"df":8,"docs":{"11":{"tf":1.0},"12":{"tf":2.0},"13":{"tf":3.0},"17":{"tf":2.0},"28":{"tf":1.7320508075688772},"31":{"tf":3.1622776601683795},"8":{"tf":1.4142135623730951},"9":{"tf":1.7320508075688772}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"13":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"41":{"tf":1.7320508075688772},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"41":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.0},"29":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":4,"docs":{"28":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.23606797749979}}}},"t":{"df":3,"docs":{"48":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"5":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"d":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"df":10,"docs":{"51":{"tf":1.7320508075688772},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"4":{"tf":1.0}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"8":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":3,"docs":{"13":{"tf":1.0},"27":{"tf":1.4142135623730951},"58":{"tf":1.0}}}}}},"v":{"df":1,"docs":{"13":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":1,"docs":{"13":{"tf":2.0}},"s":{"df":2,"docs":{"34":{"tf":1.0},"58":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":4.795831523312719}}}}},"o":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"25":{"tf":1.4142135623730951}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"68":{"tf":3.0},"69":{"tf":2.449489742783178}}},"df":0,"docs":{}}},"df":9,"docs":{"11":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.0},"21":{"tf":1.4142135623730951},"25":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":3.0},"58":{"tf":1.0},"69":{"tf":1.7320508075688772}},"i":{"d":{"df":1,"docs":{"68":{"tf":2.23606797749979}}},"df":0,"docs":{}}}}}},"d":{"df":0,"docs":{},"u":{"c":{"df":3,"docs":{"10":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"'":{"df":1,"docs":{"41":{"tf":1.4142135623730951}}},"_":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"68":{"tf":1.0}}},"df":0,"docs":{}}},"df":19,"docs":{"1":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951},"35":{"tf":2.0},"36":{"tf":1.0},"37":{"tf":2.449489742783178},"38":{"tf":1.7320508075688772},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"41":{"tf":1.7320508075688772},"42":{"tf":2.0},"43":{"tf":2.449489742783178},"44":{"tf":1.0},"45":{"tf":1.0},"56":{"tf":1.7320508075688772},"58":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.0}}}}}}}}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"13":{"tf":1.0},"19":{"tf":1.0}}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.4142135623730951},"28":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"f":{"df":8,"docs":{"10":{"tf":1.0},"27":{"tf":2.23606797749979},"28":{"tf":2.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.6457513110645907},"7":{"tf":1.7320508075688772},"8":{"tf":2.8284271247461903}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":5,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"5":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}}},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"e":{"df":3,"docs":{"29":{"tf":1.0},"31":{"tf":1.0},"9":{"tf":1.0}}},"i":{"d":{"df":7,"docs":{"29":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}},"x":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}}},"u":{"b":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":11,"docs":{"3":{"tf":1.4142135623730951},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"52":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"63":{"tf":1.0},"68":{"tf":4.242640687119285},"69":{"tf":3.3166247903554}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":8,"docs":{"27":{"tf":1.0},"3":{"tf":1.4142135623730951},"35":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.4142135623730951},"52":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.4142135623730951}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":2,"docs":{"49":{"tf":1.4142135623730951},"62":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"55":{"tf":1.0},"60":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"55":{"tf":1.0},"56":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}}}}}}}},"r":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":5,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"31":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"df":0,"docs":{},"k":{"df":2,"docs":{"15":{"tf":1.0},"16":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"13":{"tf":1.0},"19":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"12":{"tf":1.0},"15":{"tf":1.4142135623730951}}}},"d":{"df":2,"docs":{"35":{"tf":1.4142135623730951},"56":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}},"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"v":{"df":3,"docs":{"60":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.7320508075688772}}}}},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"d":{"df":3,"docs":{"25":{"tf":1.0},"28":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}},"df":1,"docs":{"15":{"tf":1.0}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"25":{"tf":1.0},"46":{"tf":1.0},"53":{"tf":1.4142135623730951}}}}},"g":{"a":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"1":{"tf":1.0},"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}}}}}}}}}}},"df":0,"docs":{}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"1":{"tf":1.0}}}}},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"z":{"df":0,"docs":{},"v":{"df":1,"docs":{"69":{"tf":1.0}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"i":{"c":{"df":9,"docs":{"25":{"tf":1.7320508075688772},"26":{"tf":1.0},"27":{"tf":2.449489742783178},"28":{"tf":1.7320508075688772},"29":{"tf":1.7320508075688772},"30":{"tf":1.4142135623730951},"31":{"tf":3.3166247903554},"5":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":3,"docs":{"13":{"tf":1.7320508075688772},"56":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"50":{"tf":1.0},"60":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"df":17,"docs":{"35":{"tf":1.0},"47":{"tf":1.0},"51":{"tf":3.1622776601683795},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"r":{"df":9,"docs":{"13":{"tf":1.0},"27":{"tf":2.23606797749979},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.7320508075688772},"5":{"tf":1.0},"68":{"tf":2.8284271247461903},"69":{"tf":2.0},"8":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"4":{"tf":1.7320508075688772}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"v":{"df":2,"docs":{"16":{"tf":1.0},"9":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":4,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0},"51":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":1,"docs":{"5":{"tf":1.0}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":16,"docs":{"11":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"36":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.7320508075688772},"57":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"59":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951},"63":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"65":{"tf":1.7320508075688772},"66":{"tf":1.4142135623730951}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"58":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":7,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"68":{"tf":3.872983346207417}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":2.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"o":{"a":{"d":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.4142135623730951},"12":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"n":{"d":{"df":4,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"t":{"df":2,"docs":{"39":{"tf":1.0},"6":{"tf":1.0}}}}},"p":{"c":{"df":7,"docs":{"47":{"tf":2.0},"48":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"53":{"tf":1.4142135623730951},"62":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"40":{"tf":1.0}}}},"n":{"df":3,"docs":{"10":{"tf":1.0},"19":{"tf":1.0},"44":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":7,"docs":{"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":2.0},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"43":{"tf":2.23606797749979}}}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":3,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"58":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":11,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":2.449489742783178},"5":{"tf":2.449489742783178},"6":{"tf":1.0}}},"p":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"13":{"tf":1.0}}}},"w":{"df":2,"docs":{"31":{"tf":1.0},"8":{"tf":1.0}}}},"c":{"a":{"df":0,"docs":{},"l":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"25":{"tf":1.0}}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":3,"docs":{"12":{"tf":2.23606797749979},"17":{"tf":1.4142135623730951},"4":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}}},"df":4,"docs":{"13":{"tf":2.0},"16":{"tf":1.7320508075688772},"42":{"tf":1.4142135623730951},"43":{"tf":1.7320508075688772}},"e":{"c":{"df":1,"docs":{"69":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"d":{"df":10,"docs":{"1":{"tf":1.4142135623730951},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"3":{"tf":1.0},"31":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"4":{"tf":1.0},"68":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"46":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"6":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"d":{"df":3,"docs":{"11":{"tf":2.0},"12":{"tf":1.4142135623730951},"31":{"tf":2.0}}},"df":1,"docs":{"1":{"tf":1.0}},"m":{"df":1,"docs":{"5":{"tf":1.0}}}},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"25":{"tf":1.0},"27":{"tf":1.4142135623730951}}}}}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.0},"11":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"d":{"df":6,"docs":{"16":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"51":{"tf":1.4142135623730951},"68":{"tf":2.23606797749979},"69":{"tf":3.605551275463989}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"50":{"tf":1.0},"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":2,"docs":{"19":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"9":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":4,"docs":{"1":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"51":{"tf":1.0}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"19":{"tf":1.0},"35":{"tf":1.0}}}}}},"h":{"a":{"df":2,"docs":{"52":{"tf":1.0},"6":{"tf":1.0}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"34":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"34":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"44":{"tf":1.0}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":12,"docs":{"1":{"tf":1.0},"11":{"tf":1.4142135623730951},"31":{"tf":2.8284271247461903},"37":{"tf":1.0},"52":{"tf":1.4142135623730951},"54":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"65":{"tf":1.4142135623730951},"68":{"tf":3.605551275463989},"69":{"tf":3.4641016151377544}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"65":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"65":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"66":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":8,"docs":{"3":{"tf":1.0},"31":{"tf":1.7320508075688772},"52":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.0}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"36":{"tf":1.0},"58":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":3,"docs":{"10":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":10,"docs":{"13":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"4":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0}}}}},"z":{"df":0,"docs":{},"e":{"df":1,"docs":{"28":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}},"df":2,"docs":{"13":{"tf":1.4142135623730951},"29":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":3.4641016151377544},"15":{"tf":1.0},"16":{"tf":2.0},"17":{"tf":1.0},"4":{"tf":1.0}}},"w":{"df":1,"docs":{"9":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"25":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"15":{"tf":1.4142135623730951},"16":{"tf":1.0},"5":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"o":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"l":{"a":{"df":0,"docs":{},"n":{"a":{"'":{"df":6,"docs":{"1":{"tf":1.4142135623730951},"25":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":2.0}}},"df":22,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":2.23606797749979},"28":{"tf":1.7320508075688772},"3":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":2.23606797749979},"35":{"tf":1.0},"4":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.7320508075688772},"5":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.0},"67":{"tf":1.7320508075688772},"68":{"tf":3.872983346207417},"69":{"tf":4.69041575982343},"8":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.0}}}},"v":{"df":1,"docs":{"25":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"27":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"a":{"c":{"df":0,"docs":{},"e":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":8,"docs":{"0":{"tf":1.0},"11":{"tf":1.0},"31":{"tf":2.0},"38":{"tf":1.4142135623730951},"47":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":5,"docs":{"13":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.0},"68":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"43":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"9":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":2,"docs":{"19":{"tf":1.7320508075688772},"40":{"tf":1.0}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}},"k":{"df":0,"docs":{},"e":{"df":5,"docs":{"10":{"tf":1.0},"12":{"tf":1.0},"15":{"tf":1.0},"29":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951}}}},"n":{"d":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.0},"8":{"tf":1.0}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"50":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":3,"docs":{"13":{"tf":1.0},"37":{"tf":2.0},"38":{"tf":1.4142135623730951}}},"u":{"df":2,"docs":{"54":{"tf":1.0},"58":{"tf":1.4142135623730951}},"s":{"df":1,"docs":{"58":{"tf":1.4142135623730951}}}}},"y":{"df":1,"docs":{"28":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"19":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"26":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"31":{"tf":1.4142135623730951},"35":{"tf":2.0},"8":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":4,"docs":{"27":{"tf":1.0},"29":{"tf":1.4142135623730951},"31":{"tf":1.0},"37":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"16":{"tf":1.0},"19":{"tf":1.0},"28":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"56":{"tf":1.0}}}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"5":{"tf":1.0}}}},"n":{"df":0,"docs":{},"g":{"df":11,"docs":{"11":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"58":{"tf":1.7320508075688772},"60":{"tf":2.0},"61":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"65":{"tf":1.4142135623730951}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"u":{"b":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"(":{"df":1,"docs":{"69":{"tf":1.0}}},"df":1,"docs":{"69":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":1,"docs":{"56":{"tf":1.0}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"27":{"tf":1.0},"31":{"tf":2.0},"34":{"tf":1.0},"62":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":6,"docs":{"50":{"tf":1.0},"62":{"tf":2.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":1,"docs":{"58":{"tf":1.0}},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"51":{"tf":1.0},"58":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"37":{"tf":1.0},"43":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}},"d":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"25":{"tf":1.0},"27":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{},"x":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":6,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"28":{"tf":1.0},"35":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"5":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"12":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}}},"y":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"28":{"tf":1.0}}}}}}},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":14,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"15":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"28":{"tf":1.0},"5":{"tf":2.6457513110645907},"6":{"tf":1.0},"7":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":1,"docs":{"8":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":5,"docs":{"10":{"tf":1.4142135623730951},"28":{"tf":1.0},"42":{"tf":1.4142135623730951},"5":{"tf":2.449489742783178},"68":{"tf":1.0}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{":":{":":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"42":{"tf":1.0},"43":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"31":{"tf":1.0},"41":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"34":{"tf":1.4142135623730951}}}}}}},"df":3,"docs":{"12":{"tf":2.449489742783178},"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":3,"docs":{"1":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}}}}}}},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":2,"docs":{"2":{"tf":1.4142135623730951},"4":{"tf":1.4142135623730951}}}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.4142135623730951},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"19":{"tf":1.4142135623730951}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":4,"docs":{"31":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"43":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"r":{"d":{"df":3,"docs":{"19":{"tf":1.4142135623730951},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":4,"docs":{"27":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"37":{"tf":1.0}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"s":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":3,"docs":{"15":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":1.0}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":2.23606797749979}}}}}}}}}},"u":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":2.23606797749979},"13":{"tf":3.872983346207417},"15":{"tf":1.0},"16":{"tf":1.7320508075688772},"17":{"tf":1.7320508075688772},"3":{"tf":2.23606797749979}}}},"df":1,"docs":{"31":{"tf":1.4142135623730951}},"m":{"df":0,"docs":{},"e":{"df":9,"docs":{"10":{"tf":1.0},"13":{"tf":2.449489742783178},"27":{"tf":1.0},"4":{"tf":1.4142135623730951},"5":{"tf":2.6457513110645907},"6":{"tf":1.0},"68":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"16":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":3,"docs":{"6":{"tf":1.4142135623730951},"68":{"tf":2.6457513110645907},"69":{"tf":3.0}}}}},"df":0,"docs":{}}}}}},"l":{"df":1,"docs":{"69":{"tf":1.0}}},"o":{"d":{"df":0,"docs":{},"o":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":10,"docs":{"29":{"tf":1.0},"3":{"tf":1.0},"35":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"42":{"tf":1.4142135623730951},"43":{"tf":1.0},"56":{"tf":1.4142135623730951},"60":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"9":{"tf":1.0}}},"l":{"df":2,"docs":{"19":{"tf":1.0},"67":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"28":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"p":{"df":2,"docs":{"1":{"tf":1.7320508075688772},"3":{"tf":1.0}},"u":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}}},"r":{"a":{"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"16":{"tf":1.0},"3":{"tf":1.0},"9":{"tf":1.0}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":25,"docs":{"1":{"tf":2.449489742783178},"12":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"25":{"tf":1.0},"29":{"tf":1.0},"3":{"tf":2.449489742783178},"36":{"tf":1.0},"37":{"tf":2.0},"39":{"tf":1.4142135623730951},"40":{"tf":2.23606797749979},"41":{"tf":1.0},"43":{"tf":1.7320508075688772},"44":{"tf":1.0},"5":{"tf":1.7320508075688772},"52":{"tf":1.0},"54":{"tf":2.0},"58":{"tf":3.0},"59":{"tf":1.0},"6":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.7320508075688772},"65":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"69":{"tf":3.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"68":{"tf":2.449489742783178},"69":{"tf":3.0}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"t":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"25":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":2,"docs":{"13":{"tf":1.0},"25":{"tf":1.0}}}},"i":{"df":1,"docs":{"5":{"tf":1.0}}},"u":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":1,"docs":{"54":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"27":{"tf":1.0},"5":{"tf":1.4142135623730951},"6":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"6":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"u":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}}},"w":{"df":0,"docs":{},"o":{"df":4,"docs":{"13":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0},"5":{"tf":1.0}}}},"x":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"68":{"tf":3.3166247903554}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":10,"docs":{"37":{"tf":1.0},"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"i":{"c":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"10":{"tf":1.0},"30":{"tf":1.0}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"5":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"51":{"tf":1.0}}}},"t":{"df":4,"docs":{"19":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"3":{"tf":1.0}}}},"k":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"58":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":2.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":1,"docs":{"59":{"tf":1.0}}}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"65":{"tf":1.0},"66":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.0},"9":{"tf":1.0}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}},"p":{"df":8,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"11":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"40":{"tf":1.0},"5":{"tf":1.0}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}},"s":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":3.605551275463989}}}},"d":{"df":2,"docs":{"27":{"tf":1.0},"30":{"tf":1.0}}},"df":23,"docs":{"1":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":2.8284271247461903},"35":{"tf":1.0},"4":{"tf":2.23606797749979},"42":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.4142135623730951},"5":{"tf":1.7320508075688772},"51":{"tf":1.0},"6":{"tf":2.23606797749979},"62":{"tf":1.0},"68":{"tf":1.0},"8":{"tf":2.0},"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":6,"docs":{"37":{"tf":1.0},"40":{"tf":1.0},"56":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":3,"docs":{"35":{"tf":1.0},"4":{"tf":1.0},"42":{"tf":1.7320508075688772}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"'":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"29":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":14,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":2.23606797749979},"15":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"25":{"tf":2.0},"27":{"tf":1.7320508075688772},"29":{"tf":1.7320508075688772},"3":{"tf":1.4142135623730951},"31":{"tf":2.23606797749979},"4":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":2,"docs":{"31":{"tf":1.0},"51":{"tf":1.0}}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"f":{"df":3,"docs":{"6":{"tf":1.4142135623730951},"8":{"tf":1.7320508075688772},"9":{"tf":3.0}}}},"df":3,"docs":{"16":{"tf":1.0},"17":{"tf":1.0},"69":{"tf":3.3166247903554}},"e":{"c":{"<":{"df":0,"docs":{},"u":{"8":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}},"f":{"df":5,"docs":{"1":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":7,"docs":{"28":{"tf":1.4142135623730951},"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":1.0},"6":{"tf":2.0},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"42":{"tf":1.0},"69":{"tf":4.69041575982343}}}}}}}},"i":{"a":{"df":2,"docs":{"11":{"tf":1.0},"12":{"tf":1.0}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"13":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0}}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":1.4142135623730951},"13":{"tf":2.449489742783178},"15":{"tf":1.7320508075688772},"16":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.0},"69":{"tf":1.0}}}},"l":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"68":{"tf":1.0}}},"df":3,"docs":{"67":{"tf":1.4142135623730951},"68":{"tf":3.872983346207417},"69":{"tf":4.69041575982343}}}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"r":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"h":{"/":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"19":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":2.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":2.23606797749979}}}}},"y":{"df":5,"docs":{"19":{"tf":1.0},"28":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.7320508075688772},"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"'":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.0}}}},"r":{"df":1,"docs":{"5":{"tf":1.0}}}},"b":{"3":{".":{"df":0,"docs":{},"j":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"49":{"tf":1.4142135623730951},"50":{"tf":1.0},"62":{"tf":2.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"15":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"l":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"5":{"tf":1.0},"6":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":2,"docs":{"20":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"37":{"tf":1.0},"5":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"37":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"40":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"d":{"df":1,"docs":{"3":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":4,"docs":{"25":{"tf":1.0},"29":{"tf":1.0},"44":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":3,"docs":{"20":{"tf":1.0},"35":{"tf":1.4142135623730951},"58":{"tf":1.0}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"33":{"tf":1.0},"35":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"s":{":":{"/":{"/":{"<":{"a":{"d":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"8":{"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"df":10,"docs":{"13":{"tf":1.7320508075688772},"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}},"x":{"df":1,"docs":{"13":{"tf":1.0}}}},"y":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"'":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"z":{"df":2,"docs":{"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":1,"docs":{"43":{"tf":1.0}}}}}}}},"title":{"root":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"38":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":2,"docs":{"47":{"tf":1.0},"53":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"46":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"67":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"52":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"48":{"tf":1.0},"49":{"tf":1.0}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"39":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"41":{"tf":1.0}}}}}},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"14":{"tf":1.0},"68":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"40":{"tf":1.0},"41":{"tf":1.0}}}}},"df":0,"docs":{}}}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"13":{"tf":1.0}}},"m":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"45":{"tf":1.0},"51":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"d":{"df":2,"docs":{"18":{"tf":1.0},"20":{"tf":1.0}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"4":{"tf":1.0},"44":{"tf":1.0}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"11":{"tf":1.0}}}}},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"55":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"7":{"tf":1.0}}}}}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":1,"docs":{"48":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"a":{"c":{"df":1,"docs":{"42":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"33":{"tf":1.0},"6":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"41":{"tf":1.0}}}}}}}},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"47":{"tf":1.0},"53":{"tf":1.0}},"r":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"l":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"16":{"tf":1.0}}}}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"45":{"tf":1.0}}}}}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{}}}},"p":{"df":1,"docs":{"41":{"tf":1.0}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"50":{"tf":1.0}}},"df":0,"docs":{}}}}}},"n":{"c":{"df":0,"docs":{},"p":{"df":1,"docs":{"23":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":2,"docs":{"17":{"tf":1.0},"29":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"43":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":1.0}}}}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"15":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"19":{"tf":1.0},"20":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"h":{"df":1,"docs":{"28":{"tf":1.0}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"41":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"21":{"tf":1.0}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"'":{"df":1,"docs":{"41":{"tf":1.0}}},"df":2,"docs":{"38":{"tf":1.0},"41":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"f":{"df":1,"docs":{"7":{"tf":1.0}}}},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"41":{"tf":1.0}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"u":{"b":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"53":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":2,"docs":{"8":{"tf":1.0},"9":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"25":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":1,"docs":{"51":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"4":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.0},"12":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"c":{"df":4,"docs":{"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"53":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"s":{"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"d":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{}},"n":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"66":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"a":{"df":3,"docs":{"32":{"tf":1.0},"34":{"tf":1.0},"67":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{}}},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"26":{"tf":1.0},"35":{"tf":1.0}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"42":{"tf":1.0}}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}}},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":2,"docs":{"2":{"tf":1.0},"4":{"tf":1.0}}}}}}}}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"21":{"tf":1.0},"22":{"tf":1.0},"39":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"21":{"tf":1.0},"22":{"tf":1.0}}}}},"s":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":2,"docs":{"3":{"tf":1.0},"4":{"tf":1.0}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"22":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"f":{"df":2,"docs":{"6":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"67":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"49":{"tf":1.0},"62":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"38":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"44":{"tf":1.0}}}}}}}}},"pipeline":["trimmer","stopWordFilter","stemmer"],"ref":"id","version":"0.9.5"},"results_options":{"limit_results":30,"teaser_word_count":30},"search_options":{"bool":"OR","expand":true,"fields":{"body":{"boost":1},"breadcrumbs":{"boost":1},"title":{"boost":2}}}}; \ No newline at end of file diff --git a/book/searchindex.json b/book/searchindex.json new file mode 100644 index 00000000000000..af0c3757557e1a --- /dev/null +++ b/book/searchindex.json @@ -0,0 +1 @@ +{"doc_urls":["introduction.html#disclaimer","introduction.html#introduction","terminology.html#terminology","terminology.html#teminology-currently-in-use","terminology.html#terminology-reserved-for-future-use","synchronization.html#synchronization","vdf.html#introduction-to-vdfs","poh.html#proof-of-history","poh.html#relationship-to-consensus-mechanisms","poh.html#relationship-to-vdfs","leader-scheduler.html#leader-rotation","leader-scheduler.html#leader-seed-generation","leader-scheduler.html#leader-rotation","leader-scheduler.html#partitions-forks","leader-scheduler.html#examples","leader-scheduler.html#small-partition","leader-scheduler.html#leader-timeout","leader-scheduler.html#network-variables","fullnode.html#fullnode","fullnode.html#pipelining","fullnode.html#pipelining-in-the-fullnode","tpu.html#the-transaction-processing-unit","tvu.html#the-transaction-validation-unit","ncp.html#ncp","jsonrpc-service.html#jsonrpcservice","avalanche.html#avalanche-replication","storage.html#storage","storage.html#background","storage.html#optimization-with-poh","storage.html#network","storage.html#constraints","storage.html#validation-and-replication-protocol","programs.html#the-solana-sdk","programs.html#introduction","programs.html#client-interactions-with-solana","programs.html#persistent-storage","runtime.html#runtime","runtime.html#state","runtime.html#account-structure-accounts-maintain-token-state-as-well-as-program-specific","runtime.html#transaction-engine","runtime.html#execution","runtime.html#entry-point-execution-of-the-program-involves-mapping-the-programs-public","runtime.html#system-interface","runtime.html#notes","runtime.html#future-work","ledger.html#ledger-format","appendix.html#appendix","jsonrpc-api.html#json-rpc-api","jsonrpc-api.html#rpc-http-endpoint","jsonrpc-api.html#rpc-pubsub-websocket-endpoint","jsonrpc-api.html#methods","jsonrpc-api.html#request-formatting","jsonrpc-api.html#definitions","jsonrpc-api.html#json-rpc-api-reference","jsonrpc-api.html#confirmtransaction","jsonrpc-api.html#getbalance","jsonrpc-api.html#getaccountinfo","jsonrpc-api.html#getlastid","jsonrpc-api.html#getsignaturestatus","jsonrpc-api.html#gettransactioncount","jsonrpc-api.html#requestairdrop","jsonrpc-api.html#sendtransaction","jsonrpc-api.html#subscription-websocket","jsonrpc-api.html#accountsubscribe","jsonrpc-api.html#accountunsubscribe","jsonrpc-api.html#signaturesubscribe","jsonrpc-api.html#signatureunsubscribe","wallet.html#solana-wallet-cli","wallet.html#examples","wallet.html#usage"],"index":{"documentStore":{"docInfo":{"0":{"body":27,"breadcrumbs":1,"title":1},"1":{"body":172,"breadcrumbs":1,"title":1},"10":{"body":46,"breadcrumbs":3,"title":2},"11":{"body":38,"breadcrumbs":4,"title":3},"12":{"body":73,"breadcrumbs":3,"title":2},"13":{"body":259,"breadcrumbs":3,"title":2},"14":{"body":0,"breadcrumbs":2,"title":1},"15":{"body":51,"breadcrumbs":3,"title":2},"16":{"body":56,"breadcrumbs":3,"title":2},"17":{"body":57,"breadcrumbs":3,"title":2},"18":{"body":0,"breadcrumbs":1,"title":1},"19":{"body":106,"breadcrumbs":1,"title":1},"2":{"body":0,"breadcrumbs":1,"title":1},"20":{"body":44,"breadcrumbs":2,"title":2},"21":{"body":0,"breadcrumbs":4,"title":3},"22":{"body":0,"breadcrumbs":4,"title":3},"23":{"body":9,"breadcrumbs":2,"title":1},"24":{"body":0,"breadcrumbs":2,"title":1},"25":{"body":81,"breadcrumbs":2,"title":2},"26":{"body":0,"breadcrumbs":3,"title":1},"27":{"body":129,"breadcrumbs":3,"title":1},"28":{"body":74,"breadcrumbs":4,"title":2},"29":{"body":41,"breadcrumbs":3,"title":1},"3":{"body":106,"breadcrumbs":3,"title":3},"30":{"body":50,"breadcrumbs":3,"title":1},"31":{"body":262,"breadcrumbs":5,"title":3},"32":{"body":0,"breadcrumbs":2,"title":2},"33":{"body":11,"breadcrumbs":1,"title":1},"34":{"body":42,"breadcrumbs":3,"title":3},"35":{"body":60,"breadcrumbs":2,"title":2},"36":{"body":53,"breadcrumbs":3,"title":1},"37":{"body":71,"breadcrumbs":3,"title":1},"38":{"body":1,"breadcrumbs":11,"title":9},"39":{"body":11,"breadcrumbs":4,"title":2},"4":{"body":35,"breadcrumbs":4,"title":4},"40":{"body":54,"breadcrumbs":3,"title":1},"41":{"body":9,"breadcrumbs":10,"title":8},"42":{"body":38,"breadcrumbs":4,"title":2},"43":{"body":61,"breadcrumbs":3,"title":1},"44":{"body":5,"breadcrumbs":4,"title":2},"45":{"body":0,"breadcrumbs":4,"title":2},"46":{"body":9,"breadcrumbs":1,"title":1},"47":{"body":25,"breadcrumbs":4,"title":3},"48":{"body":6,"breadcrumbs":4,"title":3},"49":{"body":6,"breadcrumbs":5,"title":4},"5":{"body":196,"breadcrumbs":1,"title":1},"50":{"body":15,"breadcrumbs":2,"title":1},"51":{"body":85,"breadcrumbs":3,"title":2},"52":{"body":24,"breadcrumbs":2,"title":1},"53":{"body":0,"breadcrumbs":5,"title":4},"54":{"body":36,"breadcrumbs":2,"title":1},"55":{"body":38,"breadcrumbs":2,"title":1},"56":{"body":81,"breadcrumbs":2,"title":1},"57":{"body":32,"breadcrumbs":2,"title":1},"58":{"body":88,"breadcrumbs":2,"title":1},"59":{"body":29,"breadcrumbs":2,"title":1},"6":{"body":104,"breadcrumbs":3,"title":2},"60":{"body":47,"breadcrumbs":2,"title":1},"61":{"body":238,"breadcrumbs":2,"title":1},"62":{"body":16,"breadcrumbs":3,"title":2},"63":{"body":43,"breadcrumbs":2,"title":1},"64":{"body":27,"breadcrumbs":2,"title":1},"65":{"body":44,"breadcrumbs":2,"title":1},"66":{"body":27,"breadcrumbs":2,"title":1},"67":{"body":7,"breadcrumbs":4,"title":3},"68":{"body":220,"breadcrumbs":2,"title":1},"69":{"body":402,"breadcrumbs":2,"title":1},"7":{"body":3,"breadcrumbs":3,"title":2},"8":{"body":91,"breadcrumbs":4,"title":3},"9":{"body":124,"breadcrumbs":3,"title":2}},"docs":{"0":{"body":"All claims, content, designs, algorithms, estimates, roadmaps, specifications, and performance measurements described in this project are done with the author's best effort. It is up to the reader to check and validate their accuracy and truthfulness. Furthermore nothing in this project constitutes a solicitation for investment.","breadcrumbs":"Disclaimer","id":"0","title":"Disclaimer"},"1":{"body":"This document defines the architecture of Solana, a blockchain built from the ground up for scale. The goal of the architecture is to demonstrate there exists a set of software algorithms that in combination, removes software as a performance bottleneck, allowing transaction throughput to scale proportionally with network bandwidth. The architecture goes on to satisfy all three desirable properties of a proper blockchain, that it not only be scalable, but that it is also secure and decentralized. With this architecture, we calculate a theoretical upper bound of 710 thousand transactions per second (tps) on a standard gigabit network and 28.4 million tps on 40 gigabit. In practice, our focus has been on gigabit. We soak-tested a 150 node permissioned testnet and it is able to maintain a mean transaction throughput of approximately 200 thousand tps with peaks over 400 thousand. Furthermore, we have found high throughput extends beyond simple payments, and that this architecture is also able to perform safe, concurrent execution of programs authored in a general purpose programming language, such as C. We feel the extension warrants industry focus on an additional performance metric already common in the CPU industry, millions of instructions per second or mips. By measuring mips, we see that batching instructions within a transaction amortizes the cost of signature verification, lifting the maximum theoretical instruction throughput up to almost exactly that of centralized databases. Lastly, we discuss the relationships between high throughput, security and transaction fees. Solana's efficient use hardware drives transaction fees into the ballpark of 1/1000th of a cent. The drop in fees in turn makes certain denial of service attacks cheaper. We discuss what these attacks look like and Solana's techniques to defend against them.","breadcrumbs":"Introduction","id":"1","title":"Introduction"},"10":{"body":"A property of any permissionless blockchain is that the entity choosing the next block is randomly selected. In proof of stake systems, that entity is typically called the \"leader\" or \"block producer.\" In Solana, we call it the leader. Under the hood, a leader is simply a mode of the fullnode. A fullnode runs as either a leader or validator. In this chapter, we describe how a fullnode determines what node is the leader, how that mechanism may choose different leaders at the same time, and if so, how the system converges in response.","breadcrumbs":"Synchronization » Leader Rotation","id":"10","title":"Leader Rotation"},"11":{"body":"Leader selection is decided via a random seed. The process is as follows: Periodically, at a specific PoH tick count, select the signatures of the votes that made up the last supermajority Concatenate the signatures Hash the resulting string for N counts The resulting hash is the random seed for M counts, M leader slots, where M > N","breadcrumbs":"Synchronization » Leader Seed Generation","id":"11","title":"Leader Seed Generation"},"12":{"body":"The leader is chosen via a random seed generated from stake weights and votes (the leader schedule) The leader is rotated every T PoH ticks (leader slot), according to the leader schedule The schedule is applicable for M voting rounds Leader's transmit for a count of T PoH ticks. When T is reached all the validators should switch to the next scheduled leader. To schedule leaders, the supermajority + M nodes are shuffled using the above calculated random seed. All T ticks must be observed from the current leader for that part of PoH to be accepted by the network. If T ticks (and any intervening transactions) are not observed, the network optimistically fills in the T ticks, and continues with PoH from the next leader.","breadcrumbs":"Synchronization » Leader Rotation","id":"12","title":"Leader Rotation"},"13":{"body":"Forks can arise at PoH tick counts that correspond to leader rotations, because leader nodes may or may not have observed the previous leader's data. These empty ticks are generated by all nodes in the network at a network-specified rate for hashes-per-tick Z . There are only two possible versions of the PoH during a voting round: PoH with T ticks and entries generated by the current leader, or PoH with just ticks. The \"just ticks\" version of the PoH can be thought of as a virtual ledger, one that all nodes in the network can derive from the last tick in the previous slot. Validators can ignore forks at other points (e.g. from the wrong leader), or slash the leader responsible for the fork. Validators vote on the longest chain that contains their previous vote, or a longer chain if the lockout on their previous vote has expired. Validator's View Time Progression The diagram below represents a validator's view of the PoH stream with possible forks over time. L1, L2, etc. are leader slots, and E s represent entries from that leader during that leader's slot. The x s represent ticks only, and time flows downwards in the diagram. Note that an E appearing on 2 branches at the same slot is a slashable condition, so a validator observing L3 and L3' can slash L3 and safely choose x for that slot. Once a validator observes a supermajority vote on any branch, other branches can be discarded below that tick count. For any slot, validators need only consider a single \"has entries\" chain or a \"ticks only\" chain. Time Division It's useful to consider leader rotation over PoH tick count as time division of the job of encoding state for the network. The following table presents the above tree of forks as a time-divided ledger. leader slot L1 L2 L3 L4 L5 data E1 E2 E3 E4 E5 ticks since prev x xx Note that only data from leader L3 will be accepted during leader slot L3 . Data from L3 may include \"catchup\" ticks back to a slot other than L2 if L3 did not observe L2 's data. L4 and L5 's transmissions include the \"ticks since prev\" PoH entries. This arrangement of the network data streams permits nodes to save exactly this to the ledger for replay, restart, and checkpoints. Leader's View When a new leader begins a slot, it must first transmit any PoH (ticks) required to link the new slot with the most recently observed and voted slot.","breadcrumbs":"Synchronization » Partitions, Forks","id":"13","title":"Partitions, Forks"},"14":{"body":"","breadcrumbs":"Synchronization » Examples","id":"14","title":"Examples"},"15":{"body":"Network partition M occurs for 10% of the nodes The larger partition K , with 90% of the stake weight continues to operate as normal M cycles through the ranks until one of them is leader, generating ticks for slots where the leader is in K . M validators observe 10% of the vote pool, finality is not reached. M and K reconnect. M validators cancel their votes on M , which has not reached finality, and re-cast on K (after their vote lockout on M ).","breadcrumbs":"Synchronization » Small Partition","id":"15","title":"Small Partition"},"16":{"body":"Next rank leader node V observes a timeout from current leader A , fills in A 's slot with virtual ticks and starts sending out entries. Nodes observing both streams keep track of the forks, waiting for: their vote on leader A to expire in order to be able to vote on B a supermajority on A 's slot If the first case occurs, leader B 's slot is filled with ticks. if the second case occurs, A's slot is filled with ticks Partition is resolved just like in the Small Partition above","breadcrumbs":"Synchronization » Leader Timeout","id":"16","title":"Leader Timeout"},"17":{"body":"A - name of a node B - name of a node K - number of nodes in the supermajority to whom leaders broadcast their PoH hash for validation M - number of nodes outside the supermajority to whom leaders broadcast their PoH hash for validation N - number of voting rounds for which a leader schedule is considered before a new leader schedule is used T - number of PoH ticks per leader slot (also voting round) V - name of a node that will create virtual ticks Z - number of hashes per PoH tick","breadcrumbs":"Synchronization » Network Variables","id":"17","title":"Network Variables"},"18":{"body":"","breadcrumbs":"Fullnode","id":"18","title":"Fullnode"},"19":{"body":"The fullnodes make extensive use of an optimization common in CPU design, called pipelining . Pipelining is the right tool for the job when there's a stream of input data that needs to be processed by a sequence of steps, and there's different hardware responsible for each. The quintessential example is using a washer and dryer to wash/dry/fold several loads of laundry. Washing must occur before drying and drying before folding, but each of the three operations is performed by a separate unit. To maximize efficiency, one creates a pipeline of stages . We'll call the washer one stage, the dryer another, and the folding process a third. To run the pipeline, one adds a second load of laundry to the washer just after the first load is added to the dryer. Likewise, the third load is added to the washer after the second is in the dryer and the first is being folded. In this way, one can make progress on three loads of laundry simultaneously. Given infinite loads, the pipeline will consistently complete a load at the rate of the slowest stage in the pipeline.","breadcrumbs":"Pipelining","id":"19","title":"Pipelining"},"2":{"body":"","breadcrumbs":"Terminology","id":"2","title":"Terminology"},"20":{"body":"The fullnode contains two pipelined processes, one used in leader mode called the Tpu and one used in validator mode called the Tvu. In both cases, the hardware being pipelined is the same, the network input, the GPU cards, the CPU cores, writes to disk, and the network output. What it does with that hardware is different. The Tpu exists to create ledger entries whereas the Tvu exists to validate them.","breadcrumbs":"Pipelining in the fullnode","id":"20","title":"Pipelining in the fullnode"},"21":{"body":"","breadcrumbs":"Fullnode » The Transaction Processing Unit","id":"21","title":"The Transaction Processing Unit"},"22":{"body":"","breadcrumbs":"Fullnode » The Transaction Validation Unit","id":"22","title":"The Transaction Validation Unit"},"23":{"body":"The Network Control Plane implements a gossip network between all nodes on in the cluster.","breadcrumbs":"Fullnode » Ncp","id":"23","title":"Ncp"},"24":{"body":"","breadcrumbs":"Fullnode » JsonRpcService","id":"24","title":"JsonRpcService"},"25":{"body":"The Avalance explainer video is a conceptual overview of how a Solana leader can continuously process a gigabit of transaction data per second and then get that same data, after being recorded on the ledger, out to multiple validators on a single gigabit backplane. In practice, we found that just one level of the Avalanche validator tree is sufficient for at least 150 validators. We anticipate adding the second level to solve one of two problems: To transmit ledger segments to slower \"replicator\" nodes. To scale up the number of validators nodes. Both problems justify the additional level, but you won't find it implemented in the reference design just yet, because Solana's gossip implementation is currently the bottleneck on the number of nodes per Solana cluster. That work is being actively developed here: Scalable Gossip","breadcrumbs":"Avalanche replication","id":"25","title":"Avalanche replication"},"26":{"body":"","breadcrumbs":"Avalanche replication » Storage","id":"26","title":"Storage"},"27":{"body":"At full capacity on a 1gbps network Solana would generate 4 petabytes of data per year. If each fullnode was required to store the full ledger, the cost of storage would discourage fullnode participation, thus centralizing the network around those that could afford it. Solana aims to keep the cost of a fullnode below $5,000 USD to maximize participation. To achieve that, the network needs to minimize redundant storage while at the same time ensuring the validity and availability of each copy. To trust storage of ledger segments, Solana has replicators periodically submit proofs to the network that the data was replicated. Each proof is called a Proof of Replication. The basic idea of it is to encrypt a dataset with a public symmetric key and then hash the encrypted dataset. Solana uses CBC encryption . To prevent a malicious replicator from deleting the data as soon as it's hashed, a replicator is required hash random segments of the dataset. Alternatively, Solana could require hashing the reverse of the encrypted data, but random sampling is sufficient and much faster. Either solution ensures that all the data is present during the generation of the proof and also requires the validator to have the entirety of the encrypted data present for verification of every proof of every identity. The space required to validate is: number_of_proofs * data_size","breadcrumbs":"Avalanche replication » Background","id":"27","title":"Background"},"28":{"body":"Solana is not the only distribute systems project using Proof of Replication, but it might be the most efficient implementation because of its ability to synchronize nodes with its Proof of History. With PoH, Solana is able to record a hash of the PoRep samples in the ledger. Thus the blocks stay in the exact same order for every PoRep and verification can stream the data and verify all the proofs in a single batch. This way Solana can verify multiple proofs concurrently, each one on its own GPU core. With the current generation of graphics cards our network can support up to 14,000 replication identities or symmetric keys. The total space required for verification is: 2 CBC_blocks * number_of_identities with core count of equal to (Number of Identities). A CBC block is expected to be 1MB in size.","breadcrumbs":"Avalanche replication » Optimization with PoH","id":"28","title":"Optimization with PoH"},"29":{"body":"Validators for PoRep are the same validators that are verifying transactions. They have some stake that they have put up as collateral that ensures that their work is honest. If you can prove that a validator verified a fake PoRep, then the validator's stake is slashed. Replicators are specialized light clients. They download a part of the ledger and store it and provide proofs of storing the ledger. For each verified proof, replicators are rewarded tokens from the mining pool.","breadcrumbs":"Avalanche replication » Network","id":"29","title":"Network"},"3":{"body":"The following list contains words commonly used throughout the Solana architecture. account - a persistent file addressed by pubkey and with tokens tracking its lifetime cluster - a set of fullnodes maintaining a single ledger finality - the wallclock duration between a leader creating a tick entry and recoginizing a supermajority of validator votes with a ledger interpretation that matches the leader's fullnode - a full participant in the cluster - either a leader or validator node entry - an entry on the ledger - either a tick or a transactions entry instruction - the smallest unit of a program that a client can include in a transaction keypair - a public and secret key node count - the number of fullnodes participating in a cluster program - the code that interprets instructions pubkey - the public key of a keypair tick - a ledger entry that estimates wallclock duration tick height - the Nth tick in the ledger tps - transactions per second transaction - one or more instructions signed by the client and executed atomically transactions entry - a set of transactions that may be executed in parallel","breadcrumbs":"Teminology Currently in Use","id":"3","title":"Teminology Currently in Use"},"30":{"body":"Solana's PoRep protocol instroduces the following constraints: At most 14,000 replication identities can be used, because that is how many GPU cores are currently available to a computer costing under $5,000 USD. Verification requires generating the CBC blocks. That requires space of 2 blocks per identity, and 1 GPU core per identity for the same dataset. As many identities at once are batched with as many proofs for those identities verified concurrently for the same dataset.","breadcrumbs":"Avalanche replication » Constraints","id":"30","title":"Constraints"},"31":{"body":"The network sets a replication target number, let's say 1k. 1k PoRep identities are created from signatures of a PoH hash. They are tied to a specific PoH hash. It doesn't matter who creates them, or it could simply be the last 1k validation signatures we saw for the ledger at that count. This is maybe just the initial batch of identities, because we want to stagger identity rotation. Any client can use any of these identities to create PoRep proofs. Replicator identities are the CBC encryption keys. Periodically at a specific PoH count, a replicator that wants to create PoRep proofs signs the PoH hash at that count. That signature is the seed used to pick the block and identity to replicate. A block is 1TB of ledger. Periodically at a specific PoH count, a replicator submits PoRep proofs for their selected block. A signature of the PoH hash at that count is the seed used to sample the 1TB encrypted block, and hash it. This is done faster than it takes to encrypt the 1TB block with the original identity. Replicators must submit some number of fake proofs, which they can prove to be fake by providing the seed for the hash result. Periodically at a specific PoH count, validators sign the hash and use the signature to select the 1TB block that they need to validate. They batch all the identities and proofs and submit approval for all the verified ones. After #6, replicator client submit the proofs of fake proofs. For any random seed, Solana requires everyone to use a signature that is derived from a PoH hash. Every node uses the same count so that the same PoH hash is signed by every participant. The signatures are then each cryptographically tied to the keypair, which prevents a leader from grinding on the resulting value for more than 1 identity. Key rotation is staggered . Once going, the next identity is generated by hashing itself with a PoH hash. Since there are many more client identities then encryption identities, the reward is split amont multiple clients to prevent Sybil attacks from generating many clients to acquire the same block of data. To remain BFT, the network needs to avoid a single human entity from storing all the replications of a single chunk of the ledger. Solana's solution to this is to require clients to continue using the same identity. If the first round is used to acquire the same block for many client identities, the second round for the same client identities will require a redistribution of the signatures, and therefore PoRep identities and blocks. Thus to get a reward for storage, clients are not rewarded for storage of the first block. The network rewards long-lived client identities more than new ones.","breadcrumbs":"Avalanche replication » Validation and Replication Protocol","id":"31","title":"Validation and Replication Protocol"},"32":{"body":"","breadcrumbs":"The Solana SDK","id":"32","title":"The Solana SDK"},"33":{"body":"With the Solana runtime, we can execute on-chain programs concurrently, and written in the client’s choice of programming language.","breadcrumbs":"Introduction","id":"33","title":"Introduction"},"34":{"body":"As shown in the diagram above an untrusted client, creates a program in the language of their choice, (i.e. C/C++/Rust/Lua), and compiles it with LLVM to a position independent shared object ELF, targeting BPF bytecode, and sends it to the Solana cluster. Next, the client sends messages to the Solana cluster, which target that program. The Solana runtime loads the previously submitted ELF and passes it the client's message for interpretation.","breadcrumbs":"Client interactions with Solana","id":"34","title":"Client interactions with Solana"},"35":{"body":"Solana supports several kinds of persistent storage, called accounts : Executable Writable by a client Writable by a program Read-only All accounts are identified by public keys and may hold arbirary data. When the client sends messages to programs, it requests access to storage using those keys. The runtime loads the account data and passes it to the program. The runtime also ensures accounts aren't written to if not owned by the client or program. Any writes to read-only accounts are discarded unless the write was to credit tokens. Any user may credit other accounts tokens, regardless of account permission.","breadcrumbs":"Persistent Storage","id":"35","title":"Persistent Storage"},"36":{"body":"The goal with the runtime is to have a general purpose execution environment that is highly parallelizable. To achieve this goal the runtime forces each Instruction to specify all of its memory dependencies up front, and therefore a single Instruction cannot cause a dynamic memory allocation. An explicit Instruction for memory allocation from the SystemProgram::CreateAccount is the only way to allocate new memory in the engine. A Transaction may compose multiple Instruction, including SystemProgram::CreateAccount , into a single atomic sequence which allows for memory allocation to achieve a result that is similar to dynamic allocation.","breadcrumbs":"On-chain programs » Runtime","id":"36","title":"Runtime"},"37":{"body":"State is addressed by an Account which is at the moment simply the Pubkey. Our goal is to eliminate memory allocation from within the program itself. Thus the client of the program provides all the state that is necessary for the program to execute in the transaction itself. The runtime interacts with the program through an entry point with a well defined interface. The userdata stored in an Account is an opaque type to the runtime, a Vec , the contents of which the program code has full control over. The Transaction structure specifies a list of Pubkey's and signatures for those keys and a sequential list of instructions that will operate over the state's associated with the account_keys . For the transaction to be committed all the instructions must execute successfully, if any abort the whole transaction fails to commit.","breadcrumbs":"On-chain programs » State","id":"37","title":"State"},"38":{"body":"memory.","breadcrumbs":"On-chain programs » Account structure Accounts maintain token state as well as program specific","id":"38","title":"Account structure Accounts maintain token state as well as program specific"},"39":{"body":"At its core, the engine looks up all the Pubkeys maps them to accounts and routs them to the program_id entry point.","breadcrumbs":"On-chain programs » Transaction Engine","id":"39","title":"Transaction Engine"},"4":{"body":"The following keywords do not have any functionality but are reserved by Solana for potential future use. epoch - the time in which a leader schedule is valid mips - millions of instructions per second public key - We currently use pubkey slot - the time in which a single leader may produce entries secret key - Users currently only use keypair","breadcrumbs":"Terminology Reserved for Future Use","id":"4","title":"Terminology Reserved for Future Use"},"40":{"body":"Transactions are batched and processed in a pipeline At the execute stage, the loaded pages have no data dependencies, so all the programs can be executed in parallel. The runtime enforces the following rules: The program_id code is the only code that will modify the contents of Account::userdata of Account's that have been assigned to it. This means that upon assignment userdata vector is guaranteed to be 0 . Total balances on all the accounts is equal before and after execution of a Transaction. Balances of each of the accounts not assigned to program_id must be equal to or greater after the Transaction than before the transaction. All Instructions in the Transaction executed without a failure.","breadcrumbs":"On-chain programs » Execution","id":"40","title":"Execution"},"41":{"body":"key to an entry point which takes a pointer to the transaction, and an array of loaded pages.","breadcrumbs":"On-chain programs » Entry Point Execution of the program involves mapping the Program's public","id":"41","title":"Entry Point Execution of the program involves mapping the Program's public"},"42":{"body":"The interface is best described by the Instruction::userdata that the user encodes. CreateAccount - This allows the user to create and assign an Account to a Program. Assign - allows the user to assign an existing account to a Program . Move - moves tokens between Account s that are associated with SystemProgram . This cannot be used to move tokens of other Account s. Programs need to implement their own version of Move.","breadcrumbs":"On-chain programs » System Interface","id":"42","title":"System Interface"},"43":{"body":"There is no dynamic memory allocation. Client's need to call the SystemProgram to create memory before passing it to another program. This Instruction can be composed into a single Transaction with the call to the program itself. Runtime guarantees that when memory is assigned to the Program it is zero initialized. Runtime guarantees that Program 's code is the only thing that can modify memory that its assigned to Runtime guarantees that the Program can only spend tokens that are in Account s that are assigned to it Runtime guarantees the balances belonging to Account s are balanced before and after the transaction Runtime guarantees that multiple instructions all executed successfully when a transaction is committed.","breadcrumbs":"On-chain programs » Notes","id":"43","title":"Notes"},"44":{"body":"Continuations and Signals for long running Transactions","breadcrumbs":"On-chain programs » Future Work","id":"44","title":"Future Work"},"45":{"body":"","breadcrumbs":"On-chain programs » Ledger format","id":"45","title":"Ledger format"},"46":{"body":"The following sections contain reference material you may find useful in your Solana journey.","breadcrumbs":"Appendix","id":"46","title":"Appendix"},"47":{"body":"Solana nodes accept HTTP requests using the JSON-RPC 2.0 specification. To interact with a Solana node inside a JavaScript application, use the solana-web3.js library, which gives a convenient interface for the RPC methods.","breadcrumbs":"Appendix » JSON RPC API","id":"47","title":"JSON RPC API"},"48":{"body":"Default port: 8899 eg. http://localhost:8899, http://192.168.1.88:8899","breadcrumbs":"Appendix » RPC HTTP Endpoint","id":"48","title":"RPC HTTP Endpoint"},"49":{"body":"Default port: 8900 eg. ws://localhost:8900, http://192.168.1.88:8900","breadcrumbs":"Appendix » RPC PubSub WebSocket Endpoint","id":"49","title":"RPC PubSub WebSocket Endpoint"},"5":{"body":"It's possible for a centralized database to process 710,000 transactions per second on a standard gigabit network if the transactions are, on average, no more than 176 bytes. A centralized database can also replicate itself and maintain high availability without significantly compromising that transaction rate using the distributed system technique known as Optimistic Concurrency Control [H.T.Kung, J.T.Robinson (1981)] . At Solana, we're demonstrating that these same theoretical limits apply just as well to blockchain on an adversarial network. The key ingredient? Finding a way to share time when nodes can't trust one-another. Once nodes can trust time, suddenly ~40 years of distributed systems research becomes applicable to blockchain! Perhaps the most striking difference between algorithms obtained by our method and ones based upon timeout is that using timeout produces a traditional distributed algorithm in which the processes operate asynchronously, while our method produces a globally synchronous one in which every process does the same thing at (approximately) the same time. Our method seems to contradict the whole purpose of distributed processing, which is to permit different processes to operate independently and perform different functions. However, if a distributed system is really a single system, then the processes must be synchronized in some way. Conceptually, the easiest way to synchronize processes is to get them all to do the same thing at the same time. Therefore, our method is used to implement a kernel that performs the necessary synchronization--for example, making sure that two different processes do not try to modify a file at the same time. Processes might spend only a small fraction of their time executing the synchronizing kernel; the rest of the time, they can operate independently--e.g., accessing different files. This is an approach we have advocated even when fault-tolerance is not required. The method's basic simplicity makes it easier to understand the precise properties of a system, which is crucial if one is to know just how fault-tolerant the system is. [L.Lamport (1984)]","breadcrumbs":"Synchronization","id":"5","title":"Synchronization"},"50":{"body":"confirmTransaction getBalance getAccountInfo getLastId getSignatureStatus getTransactionCount requestAirdrop sendTransaction startSubscriptionChannel Subscription Websocket accountSubscribe accountUnsubscribe signatureSubscribe signatureUnsubscribe","breadcrumbs":"Appendix » Methods","id":"50","title":"Methods"},"51":{"body":"To make a JSON-RPC request, send an HTTP POST request with a Content-Type: application/json header. The JSON request data should contain 4 fields: jsonrpc , set to \"2.0\" id , a unique client-generated identifying integer method , a string containing the method to be invoked params , a JSON array of ordered parameter values Example using curl: curl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"getBalance\", \"params\":[\"83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri\"]}' 192.168.1.88:8899 The response output will be a JSON object with the following fields: jsonrpc , matching the request specification id , matching the request identifier result , requested data or success confirmation Requests can be sent in batches by sending an array of JSON-RPC request objects as the data for a single POST.","breadcrumbs":"Appendix » Request Formatting","id":"51","title":"Request Formatting"},"52":{"body":"Hash: A SHA-256 hash of a chunk of data. Pubkey: The public key of a Ed25519 key-pair. Signature: An Ed25519 signature of a chunk of data. Transaction: A Solana instruction signed by a client key-pair.","breadcrumbs":"Appendix » Definitions","id":"52","title":"Definitions"},"53":{"body":"","breadcrumbs":"Appendix » JSON RPC API Reference","id":"53","title":"JSON RPC API Reference"},"54":{"body":"Returns a transaction receipt Parameters: string - Signature of Transaction to confirm, as base-58 encoded string Results: boolean - Transaction status, true if Transaction is confirmed Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"confirmTransaction\", \"params\":[\"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW\"]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":true,\"id\":1}","breadcrumbs":"Appendix » confirmTransaction","id":"54","title":"confirmTransaction"},"55":{"body":"Returns the balance of the account of provided Pubkey Parameters: string - Pubkey of account to query, as base-58 encoded string Results: integer - quantity, as a signed 64-bit integer Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"getBalance\", \"params\":[\"83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri\"]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":0,\"id\":1}","breadcrumbs":"Appendix » getBalance","id":"55","title":"getBalance"},"56":{"body":"Returns all information associated with the account of provided Pubkey Parameters: string - Pubkey of account to query, as base-58 encoded string Results: The result field will be a JSON object with the following sub fields: tokens , number of tokens assigned to this account, as a signed 64-bit integer owner , array of 32 bytes representing the program this account has been assigned to userdata , array of bytes representing any userdata associated with the account executable , boolean indicating if the account contains a program (and is strictly read-only) loader , array of 32 bytes representing the loader for this program (if executable ), otherwise all Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"getAccountInfo\", \"params\":[\"2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST\"]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":{\"executable\":false,\"loader\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"owner\":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tokens\":1,\"userdata\":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},\"id\":1}","breadcrumbs":"Appendix » getAccountInfo","id":"56","title":"getAccountInfo"},"57":{"body":"Returns the last entry ID from the ledger Parameters: None Results: string - the ID of last entry, a Hash as base-58 encoded string Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getLastId\"}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":\"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC\",\"id\":1}","breadcrumbs":"Appendix » getLastId","id":"57","title":"getLastId"},"58":{"body":"Returns the status of a given signature. This method is similar to confirmTransaction but provides more resolution for error events. Parameters: string - Signature of Transaction to confirm, as base-58 encoded string Results: string - Transaction status: Confirmed - Transaction was successful SignatureNotFound - Unknown transaction ProgramRuntimeError - An error occurred in the program that processed this Transaction AccountInUse - Another Transaction had a write lock one of the Accounts specified in this Transaction. The Transaction may succeed if retried GenericFailure - Some other error occurred. Note : In the future new Transaction statuses may be added to this list. It's safe to assume that all new statuses will be more specific error conditions that previously presented as GenericFailure Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"getSignatureStatus\", \"params\":[\"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW\"]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":\"SignatureNotFound\",\"id\":1}","breadcrumbs":"Appendix » getSignatureStatus","id":"58","title":"getSignatureStatus"},"59":{"body":"Returns the current Transaction count from the ledger Parameters: None Results: integer - count, as unsigned 64-bit integer Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getTransactionCount\"}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":268,\"id\":1}","breadcrumbs":"Appendix » getTransactionCount","id":"59","title":"getTransactionCount"},"6":{"body":"A Verifiable Delay Function is conceptually a water clock where its water marks can be recorded and later verified that the water most certainly passed through. Anatoly describes the water clock analogy in detail here: water clock analogy The same technique has been used in Bitcoin since day one. The Bitcoin feature is called nLocktime and it can be used to postdate transactions using block height instead of a timestamp. As a Bitcoin client, you'd use block height instead of a timestamp if you don't trust the network. Block height turns out to be an instance of what's being called a Verifiable Delay Function in cryptography circles. It's a cryptographically secure way to say time has passed. In Solana, we use a far more granular verifiable delay function, a SHA 256 hash chain, to checkpoint the ledger and coordinate consensus. With it, we implement Optimistic Concurrency Control and are now well en route towards that theoretical limit of 710,000 transactions per second.","breadcrumbs":"Synchronization » Introduction to VDFs","id":"6","title":"Introduction to VDFs"},"60":{"body":"Requests an airdrop of tokens to a Pubkey Parameters: string - Pubkey of account to receive tokens, as base-58 encoded string integer - token quantity, as a signed 64-bit integer Results: string - Transaction Signature of airdrop, as base-58 encoded string Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"requestAirdrop\", \"params\":[\"83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri\", 50]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":\"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW\",\"id\":1}","breadcrumbs":"Appendix » requestAirdrop","id":"60","title":"requestAirdrop"},"61":{"body":"Creates new transaction Parameters: array - array of octets containing a fully-signed Transaction Results: string - Transaction Signature, as base-58 encoded string Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"sendTransaction\", \"params\":[[61, 98, 55, 49, 15, 187, 41, 215, 176, 49, 234, 229, 228, 77, 129, 221, 239, 88, 145, 227, 81, 158, 223, 123, 14, 229, 235, 247, 191, 115, 199, 71, 121, 17, 32, 67, 63, 209, 239, 160, 161, 2, 94, 105, 48, 159, 235, 235, 93, 98, 172, 97, 63, 197, 160, 164, 192, 20, 92, 111, 57, 145, 251, 6, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 13, 39, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 11, 12, 106, 49, 74, 226, 201, 16, 161, 192, 28, 84, 124, 97, 190, 201, 171, 186, 6, 18, 70, 142, 89, 185, 176, 154, 115, 61, 26, 163, 77, 1, 88, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":\"2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b\",\"id\":1}","breadcrumbs":"Appendix » sendTransaction","id":"61","title":"sendTransaction"},"62":{"body":"After connect to the RPC PubSub websocket at ws://
/ : Submit subscription requests to the websocket using the methods below Multiple subscriptions may be active at once","breadcrumbs":"Appendix » Subscription Websocket","id":"62","title":"Subscription Websocket"},"63":{"body":"Subscribe to an account to receive notifications when the userdata for a given account public key changes Parameters: string - account Pubkey, as base-58 encoded string Results: integer - Subscription id (needed to unsubscribe) Example: // Request\n{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"accountSubscribe\", \"params\":[\"CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12\"]} // Result\n{\"jsonrpc\": \"2.0\",\"result\": 0,\"id\": 1} Notification Format: {\"jsonrpc\": \"2.0\",\"method\": \"accountNotification\", \"params\": {\"result\": {\"executable\":false,\"loader\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"owner\":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tokens\":1,\"userdata\":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},\"subscription\":0}}","breadcrumbs":"Appendix » accountSubscribe","id":"63","title":"accountSubscribe"},"64":{"body":"Unsubscribe from account userdata change notifications Parameters: integer - id of account Subscription to cancel Results: bool - unsubscribe success message Example: // Request\n{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"accountUnsubscribe\", \"params\":[0]} // Result\n{\"jsonrpc\": \"2.0\",\"result\": true,\"id\": 1}","breadcrumbs":"Appendix » accountUnsubscribe","id":"64","title":"accountUnsubscribe"},"65":{"body":"Subscribe to a transaction signature to receive notification when the transaction is confirmed On signatureNotification , the subscription is automatically cancelled Parameters: string - Transaction Signature, as base-58 encoded string Results: integer - subscription id (needed to unsubscribe) Example: // Request\n{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"signatureSubscribe\", \"params\":[\"2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b\"]} // Result\n{\"jsonrpc\": \"2.0\",\"result\": 0,\"id\": 1} Notification Format: {\"jsonrpc\": \"2.0\",\"method\": \"signatureNotification\", \"params\": {\"result\": \"Confirmed\",\"subscription\":0}}","breadcrumbs":"Appendix » signatureSubscribe","id":"65","title":"signatureSubscribe"},"66":{"body":"Unsubscribe from account userdata change notifications Parameters: integer - id of account subscription to cancel Results: bool - unsubscribe success message Example: // Request\n{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"signatureUnsubscribe\", \"params\":[0]} // Result\n{\"jsonrpc\": \"2.0\",\"result\": true,\"id\": 1}","breadcrumbs":"Appendix » signatureUnsubscribe","id":"66","title":"signatureUnsubscribe"},"67":{"body":"The solana crate is distributed with a command-line interface tool","breadcrumbs":"Appendix » solana-wallet CLI","id":"67","title":"solana-wallet CLI"},"68":{"body":"Get Pubkey // Command\n$ solana-wallet address // Return\n Airdrop Tokens // Command\n$ solana-wallet airdrop 123 // Return\n\"Your balance is: 123\" Get Balance // Command\n$ solana-wallet balance // Return\n\"Your balance is: 123\" Confirm Transaction // Command\n$ solana-wallet confirm // Return\n\"Confirmed\" / \"Not found\" Deploy program // Command\n$ solana-wallet deploy // Return\n Unconditional Immediate Transfer // Command\n$ solana-wallet pay 123 // Return\n Post-Dated Transfer // Command\n$ solana-wallet pay 123 \\ --after 2018-12-24T23:59:00 --require-timestamp-from // Return\n{signature: , processId: } require-timestamp-from is optional. If not provided, the transaction will expect a timestamp signed by this wallet's secret key Authorized Transfer A third party must send a signature to unlock the tokens. // Command\n$ solana-wallet pay 123 \\ --require-signature-from // Return\n{signature: , processId: } Post-Dated and Authorized Transfer // Command\n$ solana-wallet pay 123 \\ --after 2018-12-24T23:59 --require-timestamp-from \\ --require-signature-from // Return\n{signature: , processId: } Multiple Witnesses // Command\n$ solana-wallet pay 123 \\ --require-signature-from \\ --require-signature-from // Return\n{signature: , processId: } Cancelable Transfer // Command\n$ solana-wallet pay 123 \\ --require-signature-from \\ --cancelable // Return\n{signature: , processId: } Cancel Transfer // Command\n$ solana-wallet cancel // Return\n Send Signature // Command\n$ solana-wallet send-signature // Return\n Indicate Elapsed Time Use the current system time: // Command\n$ solana-wallet send-timestamp // Return\n Or specify some other arbitrary timestamp: // Command\n$ solana-wallet send-timestamp --date 2018-12-24T23:59:00 // Return\n","breadcrumbs":"Appendix » Examples","id":"68","title":"Examples"},"69":{"body":"solana-wallet 0.11.0 USAGE: solana-wallet [OPTIONS] [SUBCOMMAND] FLAGS: -h, --help Prints help information -V, --version Prints version information OPTIONS: -k, --keypair /path/to/id.json -n, --network Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001 --proxy Address of TLS proxy --port Optional rpc-port configuration to connect to non-default nodes --timeout Max seconds to wait to get necessary gossip from the network SUBCOMMANDS: address Get your public key airdrop Request a batch of tokens balance Get your balance cancel Cancel a transfer confirm Confirm transaction by signature deploy Deploy a program get-transaction-count Get current transaction count help Prints this message or the help of the given subcommand(s) pay Send a payment send-signature Send a signature to authorize a transfer send-timestamp Send a timestamp to unlock a transfer solana-wallet-address Get your public key USAGE: solana-wallet address FLAGS: -h, --help Prints help information -V, --version Prints version information solana-wallet-airdrop Request a batch of tokens USAGE: solana-wallet airdrop FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: The number of tokens to request solana-wallet-balance Get your balance USAGE: solana-wallet balance FLAGS: -h, --help Prints help information -V, --version Prints version information solana-wallet-cancel Cancel a transfer USAGE: solana-wallet cancel FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: The process id of the transfer to cancel solana-wallet-confirm Confirm transaction by signature USAGE: solana-wallet confirm FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: The transaction signature to confirm solana-wallet-deploy Deploy a program USAGE: solana-wallet deploy FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: /path/to/program.o solana-wallet-get-transaction-count Get current transaction count USAGE: solana-wallet get-transaction-count FLAGS: -h, --help Prints help information -V, --version Prints version information solana-wallet-pay Send a payment USAGE: solana-wallet pay [FLAGS] [OPTIONS] FLAGS: --cancelable -h, --help Prints help information -V, --version Prints version information OPTIONS: --after A timestamp after which transaction will execute --require-timestamp-from Require timestamp from this third party --require-signature-from ... Any third party signatures required to unlock the tokens ARGS: The pubkey of recipient The number of tokens to send solana-wallet-send-signature Send a signature to authorize a transfer USAGE: solana-wallet send-signature FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: The pubkey of recipient The process id of the transfer to authorize solana-wallet-send-timestamp Send a timestamp to unlock a transfer USAGE: solana-wallet send-timestamp [OPTIONS] FLAGS: -h, --help Prints help information -V, --version Prints version information OPTIONS: --date Optional arbitrary timestamp to apply ARGS: The pubkey of recipient The process id of the transfer to unlock","breadcrumbs":"Appendix » Usage","id":"69","title":"Usage"},"7":{"body":"Proof of History overview","breadcrumbs":"Synchronization » Proof of History","id":"7","title":"Proof of History"},"8":{"body":"Most confusingly, a Proof of History (PoH) is more similar to a Verifiable Delay Function (VDF) than a Proof of Work or Proof of Stake consensus mechanism. The name unfortunately requires some historical context to understand. Proof of History was developed by Anatoly Yakovenko in November of 2017, roughly 6 months before we saw a paper using the term VDF . At that time, it was commonplace to publish new proofs of some desirable property used to build most any blockchain component. Some time shortly after, the crypto community began charting out all the different consensus mechanisms and because most of them started with \"Proof of\", the prefix became synonymous with a \"consensus\" suffix. Proof of History is not a consensus mechanism, but it is used to improve the performance of Solana's Proof of Stake consensus. It is also used to improve the performance of the replication and storage protocols. To minimize confusion, Solana may rebrand PoH to some flavor of the term VDF.","breadcrumbs":"Synchronization » Relationship to consensus mechanisms","id":"8","title":"Relationship to consensus mechanisms"},"9":{"body":"A desirable property of a VDF is that verification time is very fast. Solana's approach to verifying its delay function is proportional to the time it took to create it. Split over a 4000 core GPU, it is sufficiently fast for Solana's needs, but if you asked the authors the paper cited above, they might tell you (and have) that Solana's approach is algorithmically slow it shouldn't be called a VDF. We argue the term VDF should represent the category of verifiable delay functions and not just the subset with certain performance characteristics. Until that's resolved, Solana will likely continue using the term PoH for its application-specific VDF. Another difference between PoH and VDFs used only for tracking duration, is that PoH's hash chain includes hashes of any data the application observed. That data is a double-edged sword. On one side, the data \"proves history\" - that the data most certainly existed before hashes after it. On the side, it means the application can manipulate the hash chain by changing when the data is hashed. The PoH chain therefore does not serve as a good source of randomness whereas a VDF without that data could. Solana's leader selection algorithm (TODO: add link), for example, is derived only from the VDF height and not its hash at that height.","breadcrumbs":"Synchronization » Relationship to VDFs","id":"9","title":"Relationship to VDFs"}},"length":70,"save":true},"fields":["title","body","breadcrumbs"],"index":{"body":{"root":{"0":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},".":{"1":{"1":{".":{"0":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"40":{"tf":1.0},"61":{"tf":6.48074069840786}}},"1":{"/":{"1":{"0":{"0":{"0":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"15":{"tf":1.4142135623730951}}},"1":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"2":{"1":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":2,"docs":{"61":{"tf":1.0},"68":{"tf":3.0}}},"4":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"7":{".":{"0":{".":{"0":{".":{"1":{":":{"8":{"0":{"0":{"1":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":2,"docs":{"61":{"tf":1.0},"68":{"tf":1.7320508075688772}}},"3":{"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"4":{",":{"0":{"0":{"0":{"df":2,"docs":{"28":{"tf":1.0},"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"5":{"0":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"6":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"7":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":2,"docs":{"5":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"8":{"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"9":{"0":{"df":1,"docs":{"61":{"tf":1.0}}},"1":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"2":{".":{"1":{"6":{"8":{".":{"1":{".":{"8":{"8":{":":{"8":{"8":{"9":{"9":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"4":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"1":{"df":1,"docs":{"5":{"tf":1.0}}},"4":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":7,"docs":{"30":{"tf":1.0},"31":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}},"g":{"b":{"df":0,"docs":{},"p":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}},"k":{"df":1,"docs":{"31":{"tf":1.7320508075688772}}},"m":{"b":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}},"t":{"b":{"df":1,"docs":{"31":{"tf":2.0}}},"df":0,"docs":{}}},"2":{".":{"0":{"\"":{",":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"47":{"tf":1.0},"51":{"tf":1.0}}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"1":{"tf":1.0}}},"1":{"7":{"df":1,"docs":{"8":{"tf":1.0}}},"8":{"df":1,"docs":{"68":{"tf":1.7320508075688772}}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"1":{"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"2":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"3":{"4":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"5":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"4":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{},"t":{"2":{"3":{":":{"5":{"9":{":":{"0":{"0":{"df":1,"docs":{"68":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"68":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"5":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":2,"docs":{"52":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{".":{"4":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.0}}},"df":4,"docs":{"13":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"61":{"tf":1.0}}},"3":{"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"2":{"df":2,"docs":{"56":{"tf":1.4142135623730951},"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"4":{"0":{"0":{"0":{"df":1,"docs":{"9":{"tf":1.0}}},"df":1,"docs":{"1":{"tf":1.0}}},"df":3,"docs":{"1":{"tf":1.0},"5":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"df":2,"docs":{"27":{"tf":1.0},"51":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":2,"docs":{"27":{"tf":1.0},"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"df":2,"docs":{"60":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"8":{"df":9,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}},"6":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"4":{"df":4,"docs":{"55":{"tf":1.0},"56":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":3,"docs":{"31":{"tf":1.0},"61":{"tf":1.4142135623730951},"8":{"tf":1.0}}},"7":{"0":{"df":1,"docs":{"61":{"tf":1.0}}},"1":{"0":{",":{"0":{"0":{"0":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"8":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"9":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"9":{"0":{"df":1,"docs":{"15":{"tf":1.0}}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"a":{"'":{"df":1,"docs":{"16":{"tf":1.0}}},"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"28":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"v":{"df":5,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"34":{"tf":1.0},"9":{"tf":1.0}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"47":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"35":{"tf":1.0},"5":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"40":{"tf":1.0}}},":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":15,"docs":{"3":{"tf":1.0},"35":{"tf":2.6457513110645907},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"42":{"tf":2.0},"43":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":2.449489742783178},"58":{"tf":1.0},"60":{"tf":1.0},"63":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"66":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"63":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"63":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"64":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"u":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":2,"docs":{"27":{"tf":1.0},"36":{"tf":1.4142135623730951}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":2,"docs":{"25":{"tf":1.0},"62":{"tf":1.0}}}}}},"d":{"d":{"df":2,"docs":{"19":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"3":{"tf":1.0},"37":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":2.0}}}}}}},"df":3,"docs":{"19":{"tf":1.4142135623730951},"25":{"tf":1.0},"58":{"tf":1.0}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}}},"o":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"27":{"tf":1.0}}},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":3,"docs":{"60":{"tf":1.4142135623730951},"68":{"tf":1.4142135623730951},"69":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"df":4,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"5":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}}}}}}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":3,"docs":{"36":{"tf":2.23606797749979},"37":{"tf":1.0},"43":{"tf":1.0}}},"df":0,"docs":{},"w":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.0},"42":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"27":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"6":{"tf":1.0},"8":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":5,"docs":{"19":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"9":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"i":{"df":2,"docs":{"47":{"tf":1.0},"53":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"46":{"tf":1.0}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":9,"docs":{"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":4,"docs":{"12":{"tf":1.0},"47":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.7320508075688772}}},"df":2,"docs":{"5":{"tf":1.0},"69":{"tf":1.0}}}},"r":{"df":0,"docs":{},"o":{"a":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"5":{"tf":1.0},"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"v":{"df":1,"docs":{"31":{"tf":1.0}}},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}}}}}}}},"r":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":2.23606797749979},"3":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}}},"g":{"df":1,"docs":{"69":{"tf":2.6457513110645907}},"u":{"df":1,"docs":{"9":{"tf":1.0}}}},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}},"y":{"df":4,"docs":{"41":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.7320508075688772},"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"k":{"df":1,"docs":{"9":{"tf":1.0}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":4,"docs":{"40":{"tf":1.7320508075688772},"42":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772},"56":{"tf":1.4142135623730951}}}}},"o":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"37":{"tf":1.0},"42":{"tf":1.0},"56":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"58":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":2,"docs":{"3":{"tf":1.0},"36":{"tf":1.0}}}},"t":{"a":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"31":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"0":{"tf":1.0}}},"df":4,"docs":{"1":{"tf":1.0},"68":{"tf":1.4142135623730951},"69":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"65":{"tf":1.0}}}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"30":{"tf":1.0},"5":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"25":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}}}},"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"13":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":5,"docs":{"40":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"55":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":2.23606797749979}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"n":{"d":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"e":{"df":10,"docs":{"5":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"i":{"c":{"df":2,"docs":{"27":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"1":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"40":{"tf":1.0},"51":{"tf":1.0},"69":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":2,"docs":{"16":{"tf":1.4142135623730951},"17":{"tf":1.0}},"e":{"c":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":4,"docs":{"19":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.4142135623730951},"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":6,"docs":{"17":{"tf":1.0},"19":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"g":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"43":{"tf":1.0}}}},"w":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"27":{"tf":1.0},"62":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.0},"42":{"tf":1.0}}}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":6,"docs":{"1":{"tf":1.0},"23":{"tf":1.0},"3":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}}}},"df":4,"docs":{"55":{"tf":1.0},"56":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"1":{"tf":1.4142135623730951},"10":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":5,"docs":{"10":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"6":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"54":{"tf":1.0},"56":{"tf":1.0}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"16":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0}}},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"f":{"df":1,"docs":{"34":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"a":{"d":{"c":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"34":{"tf":1.0}}},"df":0,"docs":{}}},"df":2,"docs":{"5":{"tf":1.0},"56":{"tf":1.7320508075688772}}}}}},"c":{"/":{"c":{"+":{"+":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"a":{"df":1,"docs":{"34":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"12":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":8,"docs":{"10":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"27":{"tf":1.0},"35":{"tf":1.0},"43":{"tf":1.4142135623730951},"6":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":6,"docs":{"15":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":2.6457513110645907}}}}},"df":0,"docs":{}},"p":{"a":{"c":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"d":{"df":2,"docs":{"20":{"tf":1.0},"28":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"e":{"df":2,"docs":{"16":{"tf":1.4142135623730951},"20":{"tf":1.0}}},"t":{"df":1,"docs":{"15":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"36":{"tf":1.0}}}}},"b":{"c":{"_":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"28":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}},"r":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"1":{"tf":1.0},"27":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"6":{"tf":1.0},"9":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"13":{"tf":2.0},"33":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":1.7320508075688772}}}},"n":{"df":0,"docs":{},"g":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0},"9":{"tf":1.0}}}},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"10":{"tf":1.0}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"0":{"tf":1.0}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"6":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"33":{"tf":1.0},"34":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"s":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"13":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":2,"docs":{"31":{"tf":1.0},"52":{"tf":1.4142135623730951}}}}}},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"9":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":1,"docs":{"67":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":2,"docs":{"34":{"tf":1.0},"43":{"tf":1.0}}},"df":9,"docs":{"29":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"34":{"tf":1.7320508075688772},"35":{"tf":1.7320508075688772},"37":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.0}},"’":{"df":1,"docs":{"33":{"tf":1.0}}}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"23":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.7320508075688772},"34":{"tf":1.4142135623730951}}}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":4,"docs":{"3":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}},"m":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"67":{"tf":1.0},"68":{"tf":3.872983346207417}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"3":{"tf":1.0}}}},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"34":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}},"s":{"df":2,"docs":{"36":{"tf":1.0},"43":{"tf":1.0}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":3,"docs":{"25":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":6,"docs":{"1":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"33":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"58":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"69":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"65":{"tf":1.0},"68":{"tf":1.7320508075688772},"69":{"tf":2.449489742783178}},"e":{"d":{"\"":{",":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\"":{":":{"0":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"50":{"tf":1.0},"54":{"tf":1.0},"58":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"8":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"62":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":2,"docs":{"6":{"tf":1.0},"8":{"tf":2.449489742783178}}}}}},"i":{"d":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"17":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":7,"docs":{"13":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":1.0},"46":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.0},"61":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":12,"docs":{"0":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":6,"docs":{"12":{"tf":1.0},"15":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.0},"44":{"tf":1.0},"9":{"tf":1.0}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":4,"docs":{"23":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"47":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":5,"docs":{"20":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"39":{"tf":1.0},"9":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"27":{"tf":1.4142135623730951},"30":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":8,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.0},"13":{"tf":1.7320508075688772},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":2.6457513110645907},"59":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}}},"p":{"df":0,"docs":{},"u":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"67":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":10,"docs":{"17":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":2.0},"34":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"61":{"tf":1.0},"9":{"tf":1.0}},"e":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"42":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"8":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":2,"docs":{"31":{"tf":1.0},"6":{"tf":1.0}},"i":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":9,"docs":{"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":11,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.0},"4":{"tf":1.4142135623730951},"59":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}}}},"y":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"_":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.0}}}},"b":{"a":{"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":11,"docs":{"13":{"tf":2.449489742783178},"19":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":2.449489742783178},"28":{"tf":1.0},"31":{"tf":1.0},"35":{"tf":1.4142135623730951},"40":{"tf":1.0},"51":{"tf":1.7320508075688772},"52":{"tf":1.4142135623730951},"9":{"tf":2.449489742783178}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.7320508075688772},"30":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"68":{"tf":1.7320508075688772},"69":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}}}},"y":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"i":{"d":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"48":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"37":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"52":{"tf":1.0}}}}}}},"l":{"a":{"df":0,"docs":{},"y":{"df":3,"docs":{"6":{"tf":1.7320508075688772},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"27":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}}}}}}},"n":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"36":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":2,"docs":{"68":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":3,"docs":{"13":{"tf":1.0},"31":{"tf":1.0},"9":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":4,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"42":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":3,"docs":{"0":{"tf":1.0},"19":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":3,"docs":{"1":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"25":{"tf":1.0},"8":{"tf":1.0}}}}}}}},"i":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"34":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"10":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"5":{"tf":2.23606797749979},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"s":{"c":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"13":{"tf":1.0},"35":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"k":{"df":1,"docs":{"20":{"tf":1.0}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"28":{"tf":1.0},"5":{"tf":2.23606797749979},"67":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"0":{"tf":1.0},"31":{"tf":1.0}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"29":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.4142135623730951}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"1":{"tf":1.0}}}},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":2.0}}}}}},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"13":{"tf":1.7320508075688772},"27":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"36":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"g":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}},"1":{"df":1,"docs":{"13":{"tf":1.0}}},"2":{"df":1,"docs":{"13":{"tf":1.0}}},"3":{"df":1,"docs":{"13":{"tf":1.0}}},"4":{"df":1,"docs":{"13":{"tf":1.0}}},"5":{"df":1,"docs":{"13":{"tf":1.0}}},"a":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"19":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"28":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"d":{"2":{"5":{"5":{"1":{"9":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":1,"docs":{"13":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"28":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}}},"g":{"df":2,"docs":{"48":{"tf":1.0},"49":{"tf":1.0}}},"l":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":1,"docs":{"68":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":1,"docs":{"34":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"n":{"c":{"df":0,"docs":{},"o":{"d":{"df":11,"docs":{"13":{"tf":1.0},"42":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":2.23606797749979},"31":{"tf":2.0}}}}}}},"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"48":{"tf":1.0},"49":{"tf":1.0}}}}}}}},"df":1,"docs":{"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"36":{"tf":1.0},"39":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"29":{"tf":1.0},"35":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":10,"docs":{"13":{"tf":2.0},"16":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":2.449489742783178},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"p":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"4":{"tf":1.0}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"28":{"tf":1.0},"40":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"0":{"tf":1.0},"3":{"tf":1.0}}}}}},"t":{"c":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}},"x":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"1":{"tf":1.0},"13":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":18,"docs":{"14":{"tf":1.0},"19":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"68":{"tf":1.0},"9":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"\"":{":":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"\"":{":":{"1":{",":{"\"":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"\"":{":":{"[":{"3":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"5":{"0":{",":{"4":{"8":{",":{"5":{"3":{",":{"4":{"8":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"8":{"4":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"9":{"0":{",":{"2":{"5":{"2":{",":{"1":{"0":{",":{"7":{",":{"2":{"8":{",":{"2":{"4":{"6":{",":{"1":{"4":{"0":{",":{"8":{"8":{",":{"1":{"7":{"7":{",":{"9":{"8":{",":{"8":{"2":{",":{"1":{"0":{",":{"2":{"2":{"7":{",":{"8":{"9":{",":{"8":{"1":{",":{"1":{"8":{",":{"3":{"0":{",":{"1":{"9":{"4":{",":{"1":{"0":{"1":{",":{"1":{"9":{"9":{",":{"1":{"6":{",":{"1":{"1":{",":{"7":{"3":{",":{"1":{"3":{"3":{",":{"2":{"0":{",":{"2":{"4":{"6":{",":{"6":{"2":{",":{"1":{"1":{"4":{",":{"3":{"9":{",":{"2":{"0":{",":{"1":{"1":{"3":{",":{"1":{"8":{"9":{",":{"3":{"2":{",":{"5":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"4":{"7":{",":{"1":{"5":{",":{"3":{"6":{",":{"1":{"0":{"2":{",":{"1":{"6":{"7":{",":{"8":{"3":{",":{"2":{"2":{"5":{",":{"4":{"2":{",":{"1":{"3":{"3":{",":{"1":{"2":{"7":{",":{"8":{"2":{",":{"3":{"4":{",":{"3":{"6":{",":{"2":{"2":{"4":{",":{"2":{"0":{"7":{",":{"1":{"3":{"0":{",":{"1":{"0":{"9":{",":{"2":{"3":{"0":{",":{"2":{"2":{"4":{",":{"1":{"8":{"8":{",":{"1":{"6":{"3":{",":{"3":{"3":{",":{"2":{"1":{"3":{",":{"1":{"3":{",":{"5":{",":{"1":{"1":{"7":{",":{"2":{"1":{"1":{",":{"2":{"5":{"1":{",":{"6":{"5":{",":{"1":{"5":{"9":{",":{"1":{"9":{"7":{",":{"5":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{"df":0,"docs":{},"}":{",":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\"":{":":{"0":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":12,"docs":{"1":{"tf":1.0},"3":{"tf":1.4142135623730951},"33":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"40":{"tf":2.23606797749979},"41":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"56":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"1":{"tf":1.0},"20":{"tf":1.4142135623730951},"42":{"tf":1.0},"9":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"28":{"tf":1.0},"68":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"r":{"df":2,"docs":{"13":{"tf":1.0},"16":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.0}}}}}}}},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.7320508075688772}}}},"r":{"df":1,"docs":{"6":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.7320508075688772}},"l":{"df":1,"docs":{"1":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"51":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"3":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"l":{"df":2,"docs":{"12":{"tf":1.0},"16":{"tf":1.7320508075688772}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.4142135623730951},"3":{"tf":1.0}}}},"d":{"df":3,"docs":{"25":{"tf":1.0},"46":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"13":{"tf":1.0},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}}},"l":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":3.4641016151377544}}},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"13":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"19":{"tf":1.7320508075688772}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":9,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.0},"4":{"tf":1.0},"40":{"tf":1.0},"46":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0}}}}}},"r":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":2,"docs":{"13":{"tf":2.449489742783178},"16":{"tf":1.0}}},"m":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"45":{"tf":1.0},"51":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"1":{"tf":1.0},"25":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"3":{"tf":1.0},"37":{"tf":1.0}},"i":{"df":1,"docs":{"61":{"tf":1.0}}},"n":{"df":0,"docs":{},"o":{"d":{"df":6,"docs":{"10":{"tf":1.7320508075688772},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"3":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":5,"docs":{"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.7320508075688772},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"0":{"tf":1.0},"1":{"tf":1.0}}}}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"4":{"tf":1.4142135623730951},"44":{"tf":1.0},"58":{"tf":1.0}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"1":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.4142135623730951},"15":{"tf":1.0},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.0},"51":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":2,"docs":{"50":{"tf":1.0},"56":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"50":{"tf":1.0},"55":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"50":{"tf":1.0},"57":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":2,"docs":{"50":{"tf":1.0},"58":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"50":{"tf":1.0},"59":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"a":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"5":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"47":{"tf":1.0}},"n":{"df":4,"docs":{"19":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0}}}},"df":1,"docs":{"31":{"tf":1.0}},"e":{"df":1,"docs":{"1":{"tf":1.0}}},"o":{"d":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951}}}}}}},"p":{"df":0,"docs":{},"u":{"df":4,"docs":{"20":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"40":{"tf":1.0},"43":{"tf":2.23606797749979}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"h":{".":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"h":{"df":10,"docs":{"11":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.7320508075688772},"27":{"tf":2.0},"28":{"tf":1.0},"31":{"tf":3.3166247903554},"52":{"tf":1.4142135623730951},"57":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":2.449489742783178}}}}},"df":10,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"69":{"tf":3.3166247903554}},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"6":{"tf":1.7320508075688772},"9":{"tf":1.4142135623730951}}}}}},"l":{"df":0,"docs":{},"p":{"df":1,"docs":{"69":{"tf":4.898979485566356}}}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"25":{"tf":1.0},"6":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"5":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}},"i":{"df":4,"docs":{"28":{"tf":1.0},"7":{"tf":1.4142135623730951},"8":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"35":{"tf":1.0}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"o":{"d":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{":":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{":":{"/":{"/":{"1":{"9":{"2":{".":{"1":{"6":{"8":{".":{"1":{".":{"8":{"8":{":":{"8":{"8":{"9":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"8":{"8":{"9":{"9":{"df":9,"docs":{"48":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"47":{"tf":1.0},"48":{"tf":1.0},"51":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{".":{"df":1,"docs":{"34":{"tf":1.0}}},"d":{"\"":{":":{"1":{"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":7,"docs":{"51":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.7320508075688772}},"e":{"a":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":2.23606797749979},"31":{"tf":4.123105625617661}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":2,"docs":{"35":{"tf":1.0},"51":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"68":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":6,"docs":{"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"8":{"tf":1.4142135623730951}}}}}}},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"3":{"tf":1.0},"36":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"34":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"i":{"c":{"df":2,"docs":{"56":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"56":{"tf":1.0},"69":{"tf":4.69041575982343}}}}}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"31":{"tf":1.0},"43":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"19":{"tf":1.0},"20":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"47":{"tf":1.0}}},"df":0,"docs":{}},"t":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":1,"docs":{"30":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"1":{"tf":1.7320508075688772},"3":{"tf":1.7320508075688772},"36":{"tf":2.0},"37":{"tf":1.4142135623730951},"4":{"tf":1.0},"40":{"tf":1.0},"43":{"tf":1.4142135623730951},"52":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"42":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":9,"docs":{"51":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.0},"59":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.0},"37":{"tf":1.0},"47":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"a":{"c":{"df":4,"docs":{"37":{"tf":1.0},"42":{"tf":1.4142135623730951},"47":{"tf":1.0},"67":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.4142135623730951},"34":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"33":{"tf":1.0},"6":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"51":{"tf":1.0}}},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"41":{"tf":1.0}}}}}}},"t":{"'":{"df":5,"docs":{"13":{"tf":1.0},"27":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":4,"docs":{"31":{"tf":1.0},"37":{"tf":1.4142135623730951},"43":{"tf":1.0},"5":{"tf":1.0}}}}}}}},"j":{".":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"47":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"b":{"df":2,"docs":{"13":{"tf":1.0},"19":{"tf":1.0}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"46":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":4,"docs":{"47":{"tf":1.4142135623730951},"51":{"tf":2.23606797749979},"53":{"tf":1.0},"56":{"tf":1.0}},"r":{"df":0,"docs":{},"p":{"c":{"\"":{":":{"\"":{"2":{".":{"0":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":4,"docs":{"57":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"\"":{":":{"\"":{"2":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"6":{"c":{"b":{"8":{"df":0,"docs":{},"v":{"a":{"a":{"d":{"9":{"3":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"6":{"df":0,"docs":{},"v":{"d":{"8":{"df":0,"docs":{},"p":{"6":{"7":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"q":{"df":0,"docs":{},"z":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"x":{"4":{"7":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"g":{"9":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"1":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"3":{"a":{"2":{"df":0,"docs":{},"g":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"7":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"j":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"y":{"9":{"b":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"5":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"v":{"df":0,"docs":{},"z":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"x":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"9":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"j":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"j":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"8":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"q":{"df":0,"docs":{},"p":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"4":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"3":{"df":0,"docs":{},"z":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"z":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"v":{"6":{"df":0,"docs":{},"u":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"k":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"w":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"7":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"3":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"7":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"9":{"df":0,"docs":{},"j":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"2":{"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"w":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"3":{"df":0,"docs":{},"z":{"6":{"9":{"df":0,"docs":{},"x":{"df":0,"docs":{},"m":{"1":{"df":0,"docs":{},"z":{"df":0,"docs":{},"e":{"3":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"6":{"df":0,"docs":{},"j":{"c":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"0":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"55":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"6":{"8":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"54":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"{":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"\"":{":":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"\"":{":":{"1":{",":{"\"":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"\"":{":":{"[":{"3":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"5":{"0":{",":{"4":{"8":{",":{"5":{"3":{",":{"4":{"8":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"8":{"4":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"9":{"0":{",":{"2":{"5":{"2":{",":{"1":{"0":{",":{"7":{",":{"2":{"8":{",":{"2":{"4":{"6":{",":{"1":{"4":{"0":{",":{"8":{"8":{",":{"1":{"7":{"7":{",":{"9":{"8":{",":{"8":{"2":{",":{"1":{"0":{",":{"2":{"2":{"7":{",":{"8":{"9":{",":{"8":{"1":{",":{"1":{"8":{",":{"3":{"0":{",":{"1":{"9":{"4":{",":{"1":{"0":{"1":{",":{"1":{"9":{"9":{",":{"1":{"6":{",":{"1":{"1":{",":{"7":{"3":{",":{"1":{"3":{"3":{",":{"2":{"0":{",":{"2":{"4":{"6":{",":{"6":{"2":{",":{"1":{"1":{"4":{",":{"3":{"9":{",":{"2":{"0":{",":{"1":{"1":{"3":{",":{"1":{"8":{"9":{",":{"3":{"2":{",":{"5":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"4":{"7":{",":{"1":{"5":{",":{"3":{"6":{",":{"1":{"0":{"2":{",":{"1":{"6":{"7":{",":{"8":{"3":{",":{"2":{"2":{"5":{",":{"4":{"2":{",":{"1":{"3":{"3":{",":{"1":{"2":{"7":{",":{"8":{"2":{",":{"3":{"4":{",":{"3":{"6":{",":{"2":{"2":{"4":{",":{"2":{"0":{"7":{",":{"1":{"3":{"0":{",":{"1":{"0":{"9":{",":{"2":{"3":{"0":{",":{"2":{"2":{"4":{",":{"1":{"8":{"8":{",":{"1":{"6":{"3":{",":{"3":{"3":{",":{"2":{"1":{"3":{",":{"1":{"3":{",":{"5":{",":{"1":{"1":{"7":{",":{"2":{"1":{"1":{",":{"2":{"5":{"1":{",":{"6":{"5":{",":{"1":{"5":{"9":{",":{"1":{"9":{"7":{",":{"5":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{"df":0,"docs":{},"}":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"56":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":5,"docs":{"51":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"25":{"tf":1.0}}}}}}}}},"k":{"df":3,"docs":{"15":{"tf":2.0},"17":{"tf":1.0},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":2,"docs":{"16":{"tf":1.0},"27":{"tf":1.0}}}},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}}},"y":{"df":13,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"35":{"tf":1.4142135623730951},"37":{"tf":1.0},"4":{"tf":1.4142135623730951},"41":{"tf":1.0},"5":{"tf":1.0},"52":{"tf":1.7320508075688772},"63":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"p":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":4,"docs":{"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"4":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"4":{"tf":1.0}}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"35":{"tf":1.0}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"5":{"tf":1.0}},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"l":{".":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"1":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"2":{"df":1,"docs":{"13":{"tf":2.0}}},"3":{"df":1,"docs":{"13":{"tf":2.8284271247461903}}},"4":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"g":{"df":3,"docs":{"1":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"15":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"31":{"tf":1.0},"57":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.7320508075688772},"3":{"tf":1.0}}},"df":13,"docs":{"10":{"tf":2.6457513110645907},"11":{"tf":1.7320508075688772},"12":{"tf":3.1622776601683795},"13":{"tf":3.4641016151377544},"15":{"tf":1.4142135623730951},"16":{"tf":2.23606797749979},"17":{"tf":2.23606797749979},"20":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"4":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":12,"docs":{"13":{"tf":1.7320508075688772},"20":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"3":{"tf":2.23606797749979},"31":{"tf":1.7320508075688772},"45":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0}}}}}},"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"25":{"tf":1.7320508075688772}}}}}},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"47":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"3":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"1":{"tf":1.0}}}},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"19":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"67":{"tf":1.0}}},"k":{"df":2,"docs":{"13":{"tf":1.0},"9":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"37":{"tf":1.4142135623730951},"58":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"31":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"df":1,"docs":{"34":{"tf":1.0}}}}},"o":{"a":{"d":{"df":5,"docs":{"19":{"tf":2.6457513110645907},"34":{"tf":1.0},"35":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"56":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"58":{"tf":1.0}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"15":{"tf":1.0}}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"31":{"tf":1.0},"44":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.0},"39":{"tf":1.0}}}}}},"m":{"a":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"1":{"tf":1.0},"3":{"tf":1.0},"38":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{}}}},"k":{"df":0,"docs":{},"e":{"df":4,"docs":{"1":{"tf":1.0},"19":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"51":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"i":{"df":2,"docs":{"30":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}}}}},"p":{"df":2,"docs":{"39":{"tf":1.0},"41":{"tf":1.0}}},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"3":{"tf":1.0},"51":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"46":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.0}}}}}},"x":{"df":1,"docs":{"69":{"tf":1.0}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"19":{"tf":1.0},"27":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"y":{"b":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}},"df":4,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"15":{"tf":2.6457513110645907},"17":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"n":{"df":3,"docs":{"1":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"0":{"tf":1.0},"1":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"10":{"tf":1.0},"8":{"tf":2.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"36":{"tf":2.23606797749979},"37":{"tf":1.0},"38":{"tf":1.0},"43":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"\"":{":":{"\"":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"51":{"tf":1.0},"55":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"66":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"'":{"df":1,"docs":{"5":{"tf":1.0}}},"df":6,"docs":{"47":{"tf":1.0},"5":{"tf":2.0},"50":{"tf":1.0},"51":{"tf":1.4142135623730951},"58":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"4":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"29":{"tf":1.0}}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"27":{"tf":1.0},"8":{"tf":1.0}}}}},"p":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"4":{"tf":1.0}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":2,"docs":{"10":{"tf":1.0},"20":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":3,"docs":{"40":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"8":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":6,"docs":{"3":{"tf":1.0},"31":{"tf":1.7320508075688772},"5":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0},"8":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"42":{"tf":2.0}}}}},"u":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":7,"docs":{"25":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"43":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.0}}}}}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":2,"docs":{"17":{"tf":1.7320508075688772},"8":{"tf":1.0}}}}},"c":{"df":0,"docs":{},"p":{"df":1,"docs":{"23":{"tf":1.0}}}},"df":3,"docs":{"11":{"tf":1.4142135623730951},"17":{"tf":1.0},"69":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"37":{"tf":1.0},"5":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"d":{"df":9,"docs":{"13":{"tf":1.0},"19":{"tf":1.0},"27":{"tf":1.0},"31":{"tf":1.4142135623730951},"42":{"tf":1.0},"43":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":14,"docs":{"1":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"13":{"tf":2.23606797749979},"15":{"tf":1.0},"17":{"tf":1.0},"20":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"27":{"tf":2.0},"28":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951},"6":{"tf":1.0},"69":{"tf":1.7320508075688772}}}}}}},"w":{"df":7,"docs":{"13":{"tf":1.4142135623730951},"17":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"58":{"tf":1.4142135623730951},"61":{"tf":1.0},"8":{"tf":1.0}}},"x":{"df":0,"docs":{},"t":{"df":5,"docs":{"10":{"tf":1.0},"12":{"tf":1.4142135623730951},"16":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"o":{"d":{"df":0,"docs":{},"e":{"df":15,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":2.0},"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"17":{"tf":2.23606797749979},"23":{"tf":1.0},"25":{"tf":1.7320508075688772},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"47":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"69":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}},"e":{"df":2,"docs":{"57":{"tf":1.0},"59":{"tf":1.0}}}},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"43":{"tf":1.0},"58":{"tf":1.0}}},"h":{"df":1,"docs":{"0":{"tf":1.0}}},"i":{"df":0,"docs":{},"f":{"df":4,"docs":{"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}}},"w":{"df":1,"docs":{"6":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"3":{"tf":1.0}}}},"u":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":1,"docs":{"27":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"df":7,"docs":{"17":{"tf":2.23606797749979},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"56":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}},"df":1,"docs":{"69":{"tf":2.23606797749979}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":5,"docs":{"12":{"tf":1.4142135623730951},"13":{"tf":2.23606797749979},"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}},"c":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":4,"docs":{"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"19":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"c":{"df":5,"docs":{"13":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"5":{"tf":1.0},"62":{"tf":1.0}}},"df":12,"docs":{"13":{"tf":1.0},"15":{"tf":1.0},"19":{"tf":2.0},"20":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"5":{"tf":2.0},"58":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":1.0}}},"p":{"a":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"15":{"tf":1.0},"19":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.7320508075688772}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"19":{"tf":1.0},"28":{"tf":1.0}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"12":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":2.8284271247461903}}}}}}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"16":{"tf":1.0},"28":{"tf":1.0},"51":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"16":{"tf":1.0},"25":{"tf":1.0},"6":{"tf":1.0},"8":{"tf":1.0}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"20":{"tf":1.0},"51":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"9":{"tf":1.0}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":2,"docs":{"25":{"tf":1.0},"7":{"tf":1.0}}}}}}}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"35":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"56":{"tf":1.0}}}}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":2,"docs":{"40":{"tf":1.0},"41":{"tf":1.0}}}},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"8":{"tf":1.0},"9":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":2,"docs":{"3":{"tf":1.0},"40":{"tf":1.0}},"i":{"df":0,"docs":{},"z":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"m":{"df":3,"docs":{"51":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":13,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}}},"s":{"\"":{":":{"[":{"\"":{"2":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"6":{"c":{"b":{"8":{"df":0,"docs":{},"v":{"a":{"a":{"d":{"9":{"3":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"6":{"df":0,"docs":{},"v":{"d":{"8":{"df":0,"docs":{},"p":{"6":{"7":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"q":{"df":0,"docs":{},"z":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"x":{"4":{"7":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"g":{"9":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"1":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"3":{"a":{"2":{"df":0,"docs":{},"g":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"7":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"j":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"y":{"9":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"g":{"df":0,"docs":{},"v":{"df":0,"docs":{},"k":{"df":0,"docs":{},"y":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"h":{"b":{"2":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"n":{"3":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"f":{"df":0,"docs":{},"k":{"df":0,"docs":{},"x":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"h":{"df":0,"docs":{},"p":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"56":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}}},"5":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"v":{"df":0,"docs":{},"z":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"x":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"9":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"j":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"j":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"8":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"q":{"df":0,"docs":{},"p":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"4":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"3":{"df":0,"docs":{},"z":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"z":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"v":{"6":{"df":0,"docs":{},"u":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"k":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"w":{"df":2,"docs":{"54":{"tf":1.0},"58":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"8":{"3":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"t":{"2":{"df":0,"docs":{},"h":{"5":{"df":0,"docs":{},"u":{"1":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"d":{"df":0,"docs":{},"q":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"6":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"y":{"3":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"51":{"tf":1.0},"55":{"tf":1.0},"60":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"m":{"7":{"8":{"c":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"8":{"df":0,"docs":{},"o":{"3":{"df":0,"docs":{},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"h":{"df":0,"docs":{},"x":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"z":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"4":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"df":0,"docs":{},"k":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"x":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"k":{"df":0,"docs":{},"n":{"df":0,"docs":{},"h":{"1":{"2":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"0":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}}},"[":{"6":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"29":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}},"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"t":{"df":3,"docs":{"13":{"tf":1.0},"15":{"tf":1.7320508075688772},"16":{"tf":1.4142135623730951}}}}}},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"34":{"tf":1.0},"35":{"tf":1.0},"43":{"tf":1.0},"6":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"i":{"d":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{".":{"df":0,"docs":{},"o":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.7320508075688772}}}},"y":{"df":2,"docs":{"68":{"tf":2.449489742783178},"69":{"tf":1.7320508075688772}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":10,"docs":{"1":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"27":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.4142135623730951},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"0":{"tf":1.0},"1":{"tf":1.7320508075688772},"19":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"11":{"tf":1.0},"27":{"tf":1.0},"31":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"35":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}}}}},"t":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.0},"35":{"tf":1.4142135623730951}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":1,"docs":{"27":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"19":{"tf":2.6457513110645907},"20":{"tf":1.7320508075688772},"40":{"tf":1.0}}}}}}}},"l":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"23":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"h":{"'":{"df":1,"docs":{"9":{"tf":1.0}}},"df":8,"docs":{"11":{"tf":1.0},"12":{"tf":2.0},"13":{"tf":3.0},"17":{"tf":2.0},"28":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"8":{"tf":1.4142135623730951},"9":{"tf":1.7320508075688772}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"13":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"41":{"tf":1.4142135623730951},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"41":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.0},"29":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":4,"docs":{"28":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.23606797749979}}}},"t":{"df":3,"docs":{"48":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"5":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"d":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"df":10,"docs":{"51":{"tf":1.7320508075688772},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"4":{"tf":1.0}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"8":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":3,"docs":{"13":{"tf":1.0},"27":{"tf":1.4142135623730951},"58":{"tf":1.0}}}}}},"v":{"df":1,"docs":{"13":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":1,"docs":{"13":{"tf":2.0}},"s":{"df":2,"docs":{"34":{"tf":1.0},"58":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":4.795831523312719}}}}},"o":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"25":{"tf":1.4142135623730951}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"68":{"tf":3.0},"69":{"tf":2.449489742783178}}},"df":0,"docs":{}}},"df":9,"docs":{"11":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.0},"21":{"tf":1.0},"25":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":3.0},"58":{"tf":1.0},"69":{"tf":1.7320508075688772}},"i":{"d":{"df":1,"docs":{"68":{"tf":2.23606797749979}}},"df":0,"docs":{}}}}}},"d":{"df":0,"docs":{},"u":{"c":{"df":3,"docs":{"10":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"'":{"df":1,"docs":{"41":{"tf":1.0}}},"_":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"68":{"tf":1.0}}},"df":0,"docs":{}}},"df":15,"docs":{"1":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951},"35":{"tf":2.0},"37":{"tf":2.23606797749979},"38":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.7320508075688772},"43":{"tf":2.23606797749979},"56":{"tf":1.7320508075688772},"58":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.0}}}}}}}}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"13":{"tf":1.0},"19":{"tf":1.0}}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.4142135623730951},"28":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"f":{"df":8,"docs":{"10":{"tf":1.0},"27":{"tf":2.23606797749979},"28":{"tf":2.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.6457513110645907},"7":{"tf":1.4142135623730951},"8":{"tf":2.8284271247461903}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":5,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"5":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}}},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"30":{"tf":1.0},"31":{"tf":1.0},"8":{"tf":1.0}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"e":{"df":3,"docs":{"29":{"tf":1.0},"31":{"tf":1.0},"9":{"tf":1.0}}},"i":{"d":{"df":7,"docs":{"29":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}},"x":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}}},"u":{"b":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":11,"docs":{"3":{"tf":1.4142135623730951},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"52":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"63":{"tf":1.0},"68":{"tf":4.242640687119285},"69":{"tf":3.3166247903554}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":8,"docs":{"27":{"tf":1.0},"3":{"tf":1.4142135623730951},"35":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.0},"52":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.4142135623730951}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":2,"docs":{"49":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"55":{"tf":1.0},"60":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"55":{"tf":1.0},"56":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}}}}}}}},"r":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":5,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"31":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"df":0,"docs":{},"k":{"df":2,"docs":{"15":{"tf":1.0},"16":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"13":{"tf":1.0},"19":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"12":{"tf":1.0},"15":{"tf":1.4142135623730951}}}},"d":{"df":2,"docs":{"35":{"tf":1.4142135623730951},"56":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}},"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"v":{"df":3,"docs":{"60":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.7320508075688772}}}}},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"d":{"df":3,"docs":{"25":{"tf":1.0},"28":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}},"df":1,"docs":{"15":{"tf":1.0}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"25":{"tf":1.0},"46":{"tf":1.0},"53":{"tf":1.0}}}}},"g":{"a":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"1":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"1":{"tf":1.0}}}}},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"z":{"df":0,"docs":{},"v":{"df":1,"docs":{"69":{"tf":1.0}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"i":{"c":{"df":8,"docs":{"25":{"tf":1.4142135623730951},"27":{"tf":2.23606797749979},"28":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":3.0},"5":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":3,"docs":{"13":{"tf":1.7320508075688772},"56":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"50":{"tf":1.0},"60":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":17,"docs":{"35":{"tf":1.0},"47":{"tf":1.0},"51":{"tf":3.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"r":{"df":9,"docs":{"13":{"tf":1.0},"27":{"tf":2.23606797749979},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.7320508075688772},"5":{"tf":1.0},"68":{"tf":2.8284271247461903},"69":{"tf":2.0},"8":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"4":{"tf":1.4142135623730951}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"v":{"df":2,"docs":{"16":{"tf":1.0},"9":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":4,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0},"51":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":1,"docs":{"5":{"tf":1.0}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":16,"docs":{"11":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"36":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.7320508075688772},"57":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"59":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951},"63":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"65":{"tf":1.7320508075688772},"66":{"tf":1.4142135623730951}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"58":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":7,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"68":{"tf":3.872983346207417}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":2.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"o":{"a":{"d":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.0},"12":{"tf":1.4142135623730951},"13":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"n":{"d":{"df":4,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"t":{"df":2,"docs":{"39":{"tf":1.0},"6":{"tf":1.0}}}}},"p":{"c":{"df":7,"docs":{"47":{"tf":1.7320508075688772},"48":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.4142135623730951},"53":{"tf":1.0},"62":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"40":{"tf":1.0}}}},"n":{"df":3,"docs":{"10":{"tf":1.0},"19":{"tf":1.0},"44":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":7,"docs":{"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"43":{"tf":2.23606797749979}}}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":3,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"58":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":11,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":2.449489742783178},"5":{"tf":2.449489742783178},"6":{"tf":1.0}}},"p":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"13":{"tf":1.0}}}},"w":{"df":2,"docs":{"31":{"tf":1.0},"8":{"tf":1.0}}}},"c":{"a":{"df":0,"docs":{},"l":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"25":{"tf":1.0}}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":3,"docs":{"12":{"tf":2.23606797749979},"17":{"tf":1.4142135623730951},"4":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":4,"docs":{"13":{"tf":2.0},"16":{"tf":1.7320508075688772},"42":{"tf":1.4142135623730951},"43":{"tf":1.7320508075688772}},"e":{"c":{"df":1,"docs":{"69":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"d":{"df":10,"docs":{"1":{"tf":1.4142135623730951},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"3":{"tf":1.0},"31":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"4":{"tf":1.0},"68":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"46":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"6":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"d":{"df":3,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"31":{"tf":2.0}}},"df":1,"docs":{"1":{"tf":1.0}},"m":{"df":1,"docs":{"5":{"tf":1.0}}}},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"25":{"tf":1.0},"27":{"tf":1.4142135623730951}}}}}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.0},"11":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"d":{"df":6,"docs":{"16":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"51":{"tf":1.4142135623730951},"68":{"tf":2.23606797749979},"69":{"tf":3.605551275463989}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"50":{"tf":1.0},"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":2,"docs":{"19":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"9":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":4,"docs":{"1":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"51":{"tf":1.0}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"19":{"tf":1.0},"35":{"tf":1.0}}}}}},"h":{"a":{"df":2,"docs":{"52":{"tf":1.0},"6":{"tf":1.0}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"34":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"34":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"44":{"tf":1.0}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":12,"docs":{"1":{"tf":1.0},"11":{"tf":1.4142135623730951},"31":{"tf":2.8284271247461903},"37":{"tf":1.0},"52":{"tf":1.4142135623730951},"54":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"65":{"tf":1.4142135623730951},"68":{"tf":3.605551275463989},"69":{"tf":3.4641016151377544}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"65":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":8,"docs":{"3":{"tf":1.0},"31":{"tf":1.7320508075688772},"52":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.0}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"36":{"tf":1.0},"58":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":3,"docs":{"10":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":10,"docs":{"13":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"4":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0}}}}},"z":{"df":0,"docs":{},"e":{"df":1,"docs":{"28":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}},"df":2,"docs":{"13":{"tf":1.4142135623730951},"29":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":3.4641016151377544},"15":{"tf":1.0},"16":{"tf":2.0},"17":{"tf":1.0},"4":{"tf":1.0}}},"w":{"df":1,"docs":{"9":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"25":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"15":{"tf":1.0},"16":{"tf":1.0},"5":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"o":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"l":{"a":{"df":0,"docs":{},"n":{"a":{"'":{"df":6,"docs":{"1":{"tf":1.4142135623730951},"25":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":2.0}}},"df":22,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":2.23606797749979},"28":{"tf":1.7320508075688772},"3":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":2.0},"35":{"tf":1.0},"4":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.7320508075688772},"5":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.0},"67":{"tf":1.4142135623730951},"68":{"tf":3.872983346207417},"69":{"tf":4.69041575982343},"8":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.0}}}},"v":{"df":1,"docs":{"25":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"27":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"a":{"c":{"df":0,"docs":{},"e":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":8,"docs":{"0":{"tf":1.0},"11":{"tf":1.0},"31":{"tf":2.0},"38":{"tf":1.0},"47":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":5,"docs":{"13":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.0},"68":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"43":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"9":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":2,"docs":{"19":{"tf":1.7320508075688772},"40":{"tf":1.0}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}},"k":{"df":0,"docs":{},"e":{"df":5,"docs":{"10":{"tf":1.0},"12":{"tf":1.0},"15":{"tf":1.0},"29":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951}}}},"n":{"d":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.0},"8":{"tf":1.0}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"50":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":3,"docs":{"13":{"tf":1.0},"37":{"tf":1.7320508075688772},"38":{"tf":1.0}}},"u":{"df":2,"docs":{"54":{"tf":1.0},"58":{"tf":1.4142135623730951}},"s":{"df":1,"docs":{"58":{"tf":1.4142135623730951}}}}},"y":{"df":1,"docs":{"28":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"19":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"26":{"tf":1.0},"27":{"tf":1.7320508075688772},"31":{"tf":1.4142135623730951},"35":{"tf":1.7320508075688772},"8":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":4,"docs":{"27":{"tf":1.0},"29":{"tf":1.4142135623730951},"31":{"tf":1.0},"37":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"16":{"tf":1.0},"19":{"tf":1.0},"28":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"56":{"tf":1.0}}}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"5":{"tf":1.0}}}},"n":{"df":0,"docs":{},"g":{"df":11,"docs":{"11":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"58":{"tf":1.7320508075688772},"60":{"tf":2.0},"61":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"65":{"tf":1.4142135623730951}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"u":{"b":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"(":{"df":1,"docs":{"69":{"tf":1.0}}},"df":1,"docs":{"69":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":1,"docs":{"56":{"tf":1.0}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"27":{"tf":1.0},"31":{"tf":2.0},"34":{"tf":1.0},"62":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":6,"docs":{"50":{"tf":1.0},"62":{"tf":1.7320508075688772},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":1,"docs":{"58":{"tf":1.0}},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"51":{"tf":1.0},"58":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"37":{"tf":1.0},"43":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}},"d":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"25":{"tf":1.0},"27":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{},"x":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":6,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"28":{"tf":1.0},"35":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"5":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"12":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}}},"y":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"28":{"tf":1.0}}}}}}},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"28":{"tf":1.0},"5":{"tf":2.449489742783178}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":1,"docs":{"8":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":5,"docs":{"10":{"tf":1.4142135623730951},"28":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":2.449489742783178},"68":{"tf":1.0}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{":":{":":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"42":{"tf":1.0},"43":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"31":{"tf":1.0},"41":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"34":{"tf":1.4142135623730951}}}}}}},"df":3,"docs":{"12":{"tf":2.449489742783178},"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":3,"docs":{"1":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}}},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":2,"docs":{"2":{"tf":1.0},"4":{"tf":1.0}}}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.4142135623730951},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"19":{"tf":1.4142135623730951}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":4,"docs":{"31":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"43":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"r":{"d":{"df":3,"docs":{"19":{"tf":1.4142135623730951},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":4,"docs":{"27":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"37":{"tf":1.0}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"s":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":3,"docs":{"15":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":1.0}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":2.23606797749979}}}}}}}}}},"u":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":2.23606797749979},"13":{"tf":3.872983346207417},"15":{"tf":1.0},"16":{"tf":1.7320508075688772},"17":{"tf":1.7320508075688772},"3":{"tf":2.23606797749979}}}},"df":1,"docs":{"31":{"tf":1.4142135623730951}},"m":{"df":0,"docs":{},"e":{"df":9,"docs":{"10":{"tf":1.0},"13":{"tf":2.449489742783178},"27":{"tf":1.0},"4":{"tf":1.4142135623730951},"5":{"tf":2.6457513110645907},"6":{"tf":1.0},"68":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"16":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":3,"docs":{"6":{"tf":1.4142135623730951},"68":{"tf":2.6457513110645907},"69":{"tf":3.0}}}}},"df":0,"docs":{}}}}}},"l":{"df":1,"docs":{"69":{"tf":1.0}}},"o":{"d":{"df":0,"docs":{},"o":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":10,"docs":{"29":{"tf":1.0},"3":{"tf":1.0},"35":{"tf":1.4142135623730951},"38":{"tf":1.0},"42":{"tf":1.4142135623730951},"43":{"tf":1.0},"56":{"tf":1.4142135623730951},"60":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"9":{"tf":1.0}}},"l":{"df":2,"docs":{"19":{"tf":1.0},"67":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"28":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"p":{"df":2,"docs":{"1":{"tf":1.7320508075688772},"3":{"tf":1.0}},"u":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}}},"r":{"a":{"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"16":{"tf":1.0},"3":{"tf":1.0},"9":{"tf":1.0}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":25,"docs":{"1":{"tf":2.449489742783178},"12":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.0},"3":{"tf":2.449489742783178},"36":{"tf":1.0},"37":{"tf":2.0},"39":{"tf":1.0},"40":{"tf":2.23606797749979},"41":{"tf":1.0},"43":{"tf":1.7320508075688772},"44":{"tf":1.0},"5":{"tf":1.7320508075688772},"52":{"tf":1.0},"54":{"tf":2.0},"58":{"tf":3.0},"59":{"tf":1.0},"6":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.7320508075688772},"65":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"69":{"tf":3.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"68":{"tf":2.449489742783178},"69":{"tf":3.0}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"t":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"25":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":2,"docs":{"13":{"tf":1.0},"25":{"tf":1.0}}}},"i":{"df":1,"docs":{"5":{"tf":1.0}}},"u":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":1,"docs":{"54":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"27":{"tf":1.0},"5":{"tf":1.4142135623730951},"6":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"6":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"u":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}}},"w":{"df":0,"docs":{},"o":{"df":4,"docs":{"13":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0},"5":{"tf":1.0}}}},"x":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"68":{"tf":3.3166247903554}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":10,"docs":{"37":{"tf":1.0},"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"i":{"c":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"10":{"tf":1.0},"30":{"tf":1.0}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"5":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"51":{"tf":1.0}}}},"t":{"df":4,"docs":{"19":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"3":{"tf":1.0}}}},"k":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"58":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":2.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":1,"docs":{"59":{"tf":1.0}}}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"65":{"tf":1.0},"66":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.0},"9":{"tf":1.0}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}},"p":{"df":8,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"11":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"40":{"tf":1.0},"5":{"tf":1.0}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}},"s":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":3.4641016151377544}}}},"d":{"df":2,"docs":{"27":{"tf":1.0},"30":{"tf":1.0}}},"df":23,"docs":{"1":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.8284271247461903},"35":{"tf":1.0},"4":{"tf":2.0},"42":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.4142135623730951},"5":{"tf":1.7320508075688772},"51":{"tf":1.0},"6":{"tf":2.23606797749979},"62":{"tf":1.0},"68":{"tf":1.0},"8":{"tf":2.0},"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":6,"docs":{"37":{"tf":1.0},"40":{"tf":1.0},"56":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":3,"docs":{"35":{"tf":1.0},"4":{"tf":1.0},"42":{"tf":1.7320508075688772}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"'":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"29":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":14,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":2.23606797749979},"15":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"22":{"tf":1.0},"25":{"tf":2.0},"27":{"tf":1.7320508075688772},"29":{"tf":1.7320508075688772},"3":{"tf":1.4142135623730951},"31":{"tf":2.0},"4":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":2,"docs":{"31":{"tf":1.0},"51":{"tf":1.0}}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"f":{"df":3,"docs":{"6":{"tf":1.0},"8":{"tf":1.7320508075688772},"9":{"tf":2.8284271247461903}}}},"df":3,"docs":{"16":{"tf":1.0},"17":{"tf":1.0},"69":{"tf":3.3166247903554}},"e":{"c":{"<":{"df":0,"docs":{},"u":{"8":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}},"f":{"df":5,"docs":{"1":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":7,"docs":{"28":{"tf":1.4142135623730951},"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":1.0},"6":{"tf":2.0},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"42":{"tf":1.0},"69":{"tf":4.69041575982343}}}}}}}},"i":{"a":{"df":2,"docs":{"11":{"tf":1.0},"12":{"tf":1.0}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"13":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0}}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":1.4142135623730951},"13":{"tf":2.449489742783178},"15":{"tf":1.7320508075688772},"16":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.0},"69":{"tf":1.0}}}},"l":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"68":{"tf":1.0}}},"df":3,"docs":{"67":{"tf":1.0},"68":{"tf":3.872983346207417},"69":{"tf":4.69041575982343}}}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"r":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"h":{"/":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"19":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":2.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":2.23606797749979}}}}},"y":{"df":5,"docs":{"19":{"tf":1.0},"28":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.7320508075688772},"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"'":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.0}}}},"r":{"df":1,"docs":{"5":{"tf":1.0}}}},"b":{"3":{".":{"df":0,"docs":{},"j":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"49":{"tf":1.0},"50":{"tf":1.0},"62":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"15":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"l":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":2,"docs":{"20":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"37":{"tf":1.0},"5":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"37":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"40":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"d":{"df":1,"docs":{"3":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":4,"docs":{"25":{"tf":1.0},"29":{"tf":1.0},"44":{"tf":1.0},"8":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":3,"docs":{"20":{"tf":1.0},"35":{"tf":1.4142135623730951},"58":{"tf":1.0}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"33":{"tf":1.0},"35":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"s":{":":{"/":{"/":{"<":{"a":{"d":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"8":{"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"df":10,"docs":{"13":{"tf":1.7320508075688772},"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}},"x":{"df":1,"docs":{"13":{"tf":1.0}}}},"y":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"'":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"z":{"df":2,"docs":{"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":1,"docs":{"43":{"tf":1.0}}}}}}}},"breadcrumbs":{"root":{"0":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},".":{"1":{"1":{".":{"0":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"40":{"tf":1.0},"61":{"tf":6.48074069840786}}},"1":{"/":{"1":{"0":{"0":{"0":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"15":{"tf":1.4142135623730951}}},"1":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"2":{"1":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":2,"docs":{"61":{"tf":1.0},"68":{"tf":3.0}}},"4":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"7":{".":{"0":{".":{"0":{".":{"1":{":":{"8":{"0":{"0":{"1":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":2,"docs":{"61":{"tf":1.0},"68":{"tf":1.7320508075688772}}},"3":{"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"4":{",":{"0":{"0":{"0":{"df":2,"docs":{"28":{"tf":1.0},"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"5":{"0":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"6":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"7":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":2,"docs":{"5":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"8":{"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"9":{"0":{"df":1,"docs":{"61":{"tf":1.0}}},"1":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"2":{".":{"1":{"6":{"8":{".":{"1":{".":{"8":{"8":{":":{"8":{"8":{"9":{"9":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"4":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"1":{"df":1,"docs":{"5":{"tf":1.0}}},"4":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":7,"docs":{"30":{"tf":1.0},"31":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}},"g":{"b":{"df":0,"docs":{},"p":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}},"k":{"df":1,"docs":{"31":{"tf":1.7320508075688772}}},"m":{"b":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}},"t":{"b":{"df":1,"docs":{"31":{"tf":2.0}}},"df":0,"docs":{}}},"2":{".":{"0":{"\"":{",":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"47":{"tf":1.0},"51":{"tf":1.0}}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"1":{"tf":1.0}}},"1":{"7":{"df":1,"docs":{"8":{"tf":1.0}}},"8":{"df":1,"docs":{"68":{"tf":1.7320508075688772}}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"1":{"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"2":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"3":{"4":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"5":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"4":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{},"t":{"2":{"3":{":":{"5":{"9":{":":{"0":{"0":{"df":1,"docs":{"68":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"68":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"5":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":2,"docs":{"52":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{".":{"4":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.0}}},"df":4,"docs":{"13":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"61":{"tf":1.0}}},"3":{"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"2":{"df":2,"docs":{"56":{"tf":1.4142135623730951},"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"4":{"0":{"0":{"0":{"df":1,"docs":{"9":{"tf":1.0}}},"df":1,"docs":{"1":{"tf":1.0}}},"df":3,"docs":{"1":{"tf":1.0},"5":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"df":2,"docs":{"27":{"tf":1.0},"51":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":2,"docs":{"27":{"tf":1.0},"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"df":2,"docs":{"60":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"8":{"df":9,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}},"6":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"4":{"df":4,"docs":{"55":{"tf":1.0},"56":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":3,"docs":{"31":{"tf":1.0},"61":{"tf":1.4142135623730951},"8":{"tf":1.0}}},"7":{"0":{"df":1,"docs":{"61":{"tf":1.0}}},"1":{"0":{",":{"0":{"0":{"0":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"8":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"9":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"9":{"0":{"df":1,"docs":{"15":{"tf":1.0}}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"a":{"'":{"df":1,"docs":{"16":{"tf":1.0}}},"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"28":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"v":{"df":5,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"34":{"tf":1.0},"9":{"tf":1.0}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"47":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"35":{"tf":1.0},"5":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"40":{"tf":1.0}}},":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":15,"docs":{"3":{"tf":1.0},"35":{"tf":2.6457513110645907},"37":{"tf":1.4142135623730951},"38":{"tf":2.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"42":{"tf":2.0},"43":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":2.449489742783178},"58":{"tf":1.0},"60":{"tf":1.0},"63":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"66":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"63":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"63":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"64":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"u":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":2,"docs":{"27":{"tf":1.0},"36":{"tf":1.4142135623730951}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":2,"docs":{"25":{"tf":1.0},"62":{"tf":1.0}}}}}},"d":{"d":{"df":2,"docs":{"19":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"3":{"tf":1.0},"37":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":2.0}}}}}}},"df":3,"docs":{"19":{"tf":1.4142135623730951},"25":{"tf":1.0},"58":{"tf":1.0}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}}},"o":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"27":{"tf":1.0}}},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":3,"docs":{"60":{"tf":1.4142135623730951},"68":{"tf":1.4142135623730951},"69":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"df":4,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"5":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}}}}}}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":3,"docs":{"36":{"tf":2.23606797749979},"37":{"tf":1.0},"43":{"tf":1.0}}},"df":0,"docs":{},"w":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.0},"42":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"27":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"6":{"tf":1.0},"8":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":5,"docs":{"19":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"9":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"i":{"df":2,"docs":{"47":{"tf":1.4142135623730951},"53":{"tf":1.4142135623730951}}},"p":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":24,"docs":{"46":{"tf":1.4142135623730951},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":9,"docs":{"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":4,"docs":{"12":{"tf":1.0},"47":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.7320508075688772}}},"df":2,"docs":{"5":{"tf":1.0},"69":{"tf":1.0}}}},"r":{"df":0,"docs":{},"o":{"a":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"5":{"tf":1.0},"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"v":{"df":1,"docs":{"31":{"tf":1.0}}},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}}}}}}}},"r":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":2.23606797749979},"3":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}}},"g":{"df":1,"docs":{"69":{"tf":2.6457513110645907}},"u":{"df":1,"docs":{"9":{"tf":1.0}}}},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}},"y":{"df":4,"docs":{"41":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.7320508075688772},"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"k":{"df":1,"docs":{"9":{"tf":1.0}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":4,"docs":{"40":{"tf":1.7320508075688772},"42":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772},"56":{"tf":1.4142135623730951}}}}},"o":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"37":{"tf":1.0},"42":{"tf":1.0},"56":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"58":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":2,"docs":{"3":{"tf":1.0},"36":{"tf":1.0}}}},"t":{"a":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"31":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"0":{"tf":1.0}}},"df":4,"docs":{"1":{"tf":1.0},"68":{"tf":1.4142135623730951},"69":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"65":{"tf":1.0}}}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"30":{"tf":1.0},"5":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"25":{"tf":1.7320508075688772},"26":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0}}}},"df":0,"docs":{}}},"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}}}},"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"13":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":5,"docs":{"40":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"55":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":2.23606797749979}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"n":{"d":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"e":{"df":10,"docs":{"5":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"i":{"c":{"df":2,"docs":{"27":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"1":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"40":{"tf":1.0},"51":{"tf":1.0},"69":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":2,"docs":{"16":{"tf":1.4142135623730951},"17":{"tf":1.0}},"e":{"c":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":4,"docs":{"19":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.4142135623730951},"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":6,"docs":{"17":{"tf":1.0},"19":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"g":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"43":{"tf":1.0}}}},"w":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"27":{"tf":1.0},"62":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.0},"42":{"tf":1.0}}}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":6,"docs":{"1":{"tf":1.0},"23":{"tf":1.0},"3":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}}}},"df":4,"docs":{"55":{"tf":1.0},"56":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"1":{"tf":1.4142135623730951},"10":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":5,"docs":{"10":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"6":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"54":{"tf":1.0},"56":{"tf":1.0}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"16":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0}}},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"f":{"df":1,"docs":{"34":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"a":{"d":{"c":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"34":{"tf":1.0}}},"df":0,"docs":{}}},"df":2,"docs":{"5":{"tf":1.0},"56":{"tf":1.7320508075688772}}}}}},"c":{"/":{"c":{"+":{"+":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"a":{"df":1,"docs":{"34":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"12":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":8,"docs":{"10":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"27":{"tf":1.0},"35":{"tf":1.0},"43":{"tf":1.4142135623730951},"6":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":6,"docs":{"15":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":2.6457513110645907}}}}},"df":0,"docs":{}},"p":{"a":{"c":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"d":{"df":2,"docs":{"20":{"tf":1.0},"28":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"e":{"df":2,"docs":{"16":{"tf":1.4142135623730951},"20":{"tf":1.0}}},"t":{"df":1,"docs":{"15":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"36":{"tf":1.0}}}}},"b":{"c":{"_":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"28":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}},"r":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"1":{"tf":1.0},"27":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"6":{"tf":1.0},"9":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":14,"docs":{"13":{"tf":2.0},"33":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":1.7320508075688772}}}},"n":{"df":0,"docs":{},"g":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0},"9":{"tf":1.0}}}},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"10":{"tf":1.0}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"0":{"tf":1.0}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"6":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"33":{"tf":1.0},"34":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"s":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"13":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":2,"docs":{"31":{"tf":1.0},"52":{"tf":1.4142135623730951}}}}}},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"9":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":1,"docs":{"67":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":2,"docs":{"34":{"tf":1.0},"43":{"tf":1.0}}},"df":9,"docs":{"29":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"34":{"tf":2.0},"35":{"tf":1.7320508075688772},"37":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.0}},"’":{"df":1,"docs":{"33":{"tf":1.0}}}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"23":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.7320508075688772},"34":{"tf":1.4142135623730951}}}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":4,"docs":{"3":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}},"m":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"67":{"tf":1.0},"68":{"tf":3.872983346207417}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"3":{"tf":1.0}}}},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"34":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}},"s":{"df":2,"docs":{"36":{"tf":1.0},"43":{"tf":1.0}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":3,"docs":{"25":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":6,"docs":{"1":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"33":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"58":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"69":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"65":{"tf":1.0},"68":{"tf":1.7320508075688772},"69":{"tf":2.449489742783178}},"e":{"d":{"\"":{",":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\"":{":":{"0":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"50":{"tf":1.0},"54":{"tf":1.4142135623730951},"58":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"8":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"62":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":2,"docs":{"6":{"tf":1.0},"8":{"tf":2.6457513110645907}}}}}},"i":{"d":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"17":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":7,"docs":{"13":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":1.0},"46":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.0},"61":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":12,"docs":{"0":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":6,"docs":{"12":{"tf":1.0},"15":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.0},"44":{"tf":1.0},"9":{"tf":1.0}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":4,"docs":{"23":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"47":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":5,"docs":{"20":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"39":{"tf":1.0},"9":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"27":{"tf":1.4142135623730951},"30":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":8,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.0},"13":{"tf":1.7320508075688772},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":2.6457513110645907},"59":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}}},"p":{"df":0,"docs":{},"u":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"67":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":10,"docs":{"17":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":2.0},"34":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"61":{"tf":1.0},"9":{"tf":1.0}},"e":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"42":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"8":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":2,"docs":{"31":{"tf":1.0},"6":{"tf":1.0}},"i":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":9,"docs":{"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":11,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"30":{"tf":1.0},"4":{"tf":1.4142135623730951},"59":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}}}},"y":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"_":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.0}}}},"b":{"a":{"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":11,"docs":{"13":{"tf":2.449489742783178},"19":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":2.449489742783178},"28":{"tf":1.0},"31":{"tf":1.0},"35":{"tf":1.4142135623730951},"40":{"tf":1.0},"51":{"tf":1.7320508075688772},"52":{"tf":1.4142135623730951},"9":{"tf":2.449489742783178}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.7320508075688772},"30":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"68":{"tf":1.7320508075688772},"69":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}}}},"y":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"i":{"d":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"48":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"37":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}}}}}},"l":{"a":{"df":0,"docs":{},"y":{"df":3,"docs":{"6":{"tf":1.7320508075688772},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"27":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}}}}}}},"n":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"36":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":2,"docs":{"68":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":3,"docs":{"13":{"tf":1.0},"31":{"tf":1.0},"9":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":4,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"42":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":3,"docs":{"0":{"tf":1.0},"19":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":3,"docs":{"1":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"25":{"tf":1.0},"8":{"tf":1.0}}}}}}}},"i":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"34":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"10":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"5":{"tf":2.23606797749979},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"s":{"c":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"13":{"tf":1.0},"35":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"k":{"df":1,"docs":{"20":{"tf":1.0}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"28":{"tf":1.0},"5":{"tf":2.23606797749979},"67":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"0":{"tf":1.0},"31":{"tf":1.0}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"29":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.4142135623730951}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"1":{"tf":1.0}}}},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":2.0}}}}}},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"13":{"tf":1.7320508075688772},"27":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"36":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"g":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}},"1":{"df":1,"docs":{"13":{"tf":1.0}}},"2":{"df":1,"docs":{"13":{"tf":1.0}}},"3":{"df":1,"docs":{"13":{"tf":1.0}}},"4":{"df":1,"docs":{"13":{"tf":1.0}}},"5":{"df":1,"docs":{"13":{"tf":1.0}}},"a":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"19":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"28":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"d":{"2":{"5":{"5":{"1":{"9":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":1,"docs":{"13":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"28":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}}},"g":{"df":2,"docs":{"48":{"tf":1.0},"49":{"tf":1.0}}},"l":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":1,"docs":{"68":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":1,"docs":{"34":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"n":{"c":{"df":0,"docs":{},"o":{"d":{"df":11,"docs":{"13":{"tf":1.0},"42":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":2.23606797749979},"31":{"tf":2.0}}}}}}},"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"48":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951}}}}}}}},"df":1,"docs":{"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"36":{"tf":1.0},"39":{"tf":1.7320508075688772}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"29":{"tf":1.0},"35":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":10,"docs":{"13":{"tf":2.0},"16":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":2.449489742783178},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.7320508075688772},"57":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"p":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"4":{"tf":1.0}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"28":{"tf":1.0},"40":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"0":{"tf":1.0},"3":{"tf":1.0}}}}}},"t":{"c":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}},"x":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"1":{"tf":1.0},"13":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":18,"docs":{"14":{"tf":1.4142135623730951},"19":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"68":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"\"":{":":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"\"":{":":{"1":{",":{"\"":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"\"":{":":{"[":{"3":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"5":{"0":{",":{"4":{"8":{",":{"5":{"3":{",":{"4":{"8":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"8":{"4":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"9":{"0":{",":{"2":{"5":{"2":{",":{"1":{"0":{",":{"7":{",":{"2":{"8":{",":{"2":{"4":{"6":{",":{"1":{"4":{"0":{",":{"8":{"8":{",":{"1":{"7":{"7":{",":{"9":{"8":{",":{"8":{"2":{",":{"1":{"0":{",":{"2":{"2":{"7":{",":{"8":{"9":{",":{"8":{"1":{",":{"1":{"8":{",":{"3":{"0":{",":{"1":{"9":{"4":{",":{"1":{"0":{"1":{",":{"1":{"9":{"9":{",":{"1":{"6":{",":{"1":{"1":{",":{"7":{"3":{",":{"1":{"3":{"3":{",":{"2":{"0":{",":{"2":{"4":{"6":{",":{"6":{"2":{",":{"1":{"1":{"4":{",":{"3":{"9":{",":{"2":{"0":{",":{"1":{"1":{"3":{",":{"1":{"8":{"9":{",":{"3":{"2":{",":{"5":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"4":{"7":{",":{"1":{"5":{",":{"3":{"6":{",":{"1":{"0":{"2":{",":{"1":{"6":{"7":{",":{"8":{"3":{",":{"2":{"2":{"5":{",":{"4":{"2":{",":{"1":{"3":{"3":{",":{"1":{"2":{"7":{",":{"8":{"2":{",":{"3":{"4":{",":{"3":{"6":{",":{"2":{"2":{"4":{",":{"2":{"0":{"7":{",":{"1":{"3":{"0":{",":{"1":{"0":{"9":{",":{"2":{"3":{"0":{",":{"2":{"2":{"4":{",":{"1":{"8":{"8":{",":{"1":{"6":{"3":{",":{"3":{"3":{",":{"2":{"1":{"3":{",":{"1":{"3":{",":{"5":{",":{"1":{"1":{"7":{",":{"2":{"1":{"1":{",":{"2":{"5":{"1":{",":{"6":{"5":{",":{"1":{"5":{"9":{",":{"1":{"9":{"7":{",":{"5":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{"df":0,"docs":{},"}":{",":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\"":{":":{"0":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":12,"docs":{"1":{"tf":1.0},"3":{"tf":1.4142135623730951},"33":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"40":{"tf":2.449489742783178},"41":{"tf":1.4142135623730951},"43":{"tf":1.0},"5":{"tf":1.0},"56":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"1":{"tf":1.0},"20":{"tf":1.4142135623730951},"42":{"tf":1.0},"9":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"28":{"tf":1.0},"68":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"r":{"df":2,"docs":{"13":{"tf":1.0},"16":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.0}}}}}}}},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.7320508075688772}}}},"r":{"df":1,"docs":{"6":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.7320508075688772}},"l":{"df":1,"docs":{"1":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"51":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"3":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"l":{"df":2,"docs":{"12":{"tf":1.0},"16":{"tf":1.7320508075688772}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.4142135623730951},"3":{"tf":1.0}}}},"d":{"df":3,"docs":{"25":{"tf":1.0},"46":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"13":{"tf":1.0},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}}},"l":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":3.4641016151377544}}},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"13":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"19":{"tf":1.7320508075688772}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":9,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.0},"4":{"tf":1.0},"40":{"tf":1.0},"46":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0}}}}}},"r":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":2,"docs":{"13":{"tf":2.6457513110645907},"16":{"tf":1.0}}},"m":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"45":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"63":{"tf":1.0},"65":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"1":{"tf":1.0},"25":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"3":{"tf":1.0},"37":{"tf":1.0}},"i":{"df":1,"docs":{"61":{"tf":1.0}}},"n":{"df":0,"docs":{},"o":{"d":{"df":10,"docs":{"10":{"tf":1.7320508075688772},"18":{"tf":1.4142135623730951},"19":{"tf":1.0},"20":{"tf":1.7320508075688772},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"27":{"tf":1.7320508075688772},"3":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":5,"docs":{"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.7320508075688772},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"0":{"tf":1.0},"1":{"tf":1.0}}}}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"4":{"tf":1.7320508075688772},"44":{"tf":1.4142135623730951},"58":{"tf":1.0}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"1":{"tf":1.0},"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"13":{"tf":1.4142135623730951},"15":{"tf":1.0},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.0},"51":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":2,"docs":{"50":{"tf":1.0},"56":{"tf":1.4142135623730951}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"50":{"tf":1.0},"55":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"50":{"tf":1.0},"57":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":2,"docs":{"50":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"50":{"tf":1.0},"59":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"a":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"5":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"47":{"tf":1.0}},"n":{"df":4,"docs":{"19":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0}}}},"df":1,"docs":{"31":{"tf":1.0}},"e":{"df":1,"docs":{"1":{"tf":1.0}}},"o":{"d":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951}}}}}}},"p":{"df":0,"docs":{},"u":{"df":4,"docs":{"20":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"40":{"tf":1.0},"43":{"tf":2.23606797749979}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"h":{".":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"h":{"df":10,"docs":{"11":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.7320508075688772},"27":{"tf":2.0},"28":{"tf":1.0},"31":{"tf":3.3166247903554},"52":{"tf":1.4142135623730951},"57":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":2.449489742783178}}}}},"df":10,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"69":{"tf":3.3166247903554}},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"6":{"tf":1.7320508075688772},"9":{"tf":1.4142135623730951}}}}}},"l":{"df":0,"docs":{},"p":{"df":1,"docs":{"69":{"tf":4.898979485566356}}}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"25":{"tf":1.0},"6":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"5":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}},"i":{"df":4,"docs":{"28":{"tf":1.0},"7":{"tf":1.7320508075688772},"8":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"35":{"tf":1.0}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"o":{"d":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{":":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{":":{"/":{"/":{"1":{"9":{"2":{".":{"1":{"6":{"8":{".":{"1":{".":{"8":{"8":{":":{"8":{"8":{"9":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"8":{"8":{"9":{"9":{"df":9,"docs":{"48":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"47":{"tf":1.0},"48":{"tf":1.4142135623730951},"51":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{".":{"df":1,"docs":{"34":{"tf":1.0}}},"d":{"\"":{":":{"1":{"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":7,"docs":{"51":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.7320508075688772}},"e":{"a":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":2.23606797749979},"31":{"tf":4.123105625617661}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":2,"docs":{"35":{"tf":1.0},"51":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"68":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":6,"docs":{"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"8":{"tf":1.4142135623730951}}}}}}},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"3":{"tf":1.0},"36":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"34":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"i":{"c":{"df":2,"docs":{"56":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"56":{"tf":1.0},"69":{"tf":4.69041575982343}}}}}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"31":{"tf":1.0},"43":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"19":{"tf":1.0},"20":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"47":{"tf":1.0}}},"df":0,"docs":{}},"t":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":1,"docs":{"30":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"1":{"tf":1.7320508075688772},"3":{"tf":1.7320508075688772},"36":{"tf":2.0},"37":{"tf":1.4142135623730951},"4":{"tf":1.0},"40":{"tf":1.0},"43":{"tf":1.4142135623730951},"52":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"42":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":9,"docs":{"51":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.0},"59":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.4142135623730951},"37":{"tf":1.0},"47":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"a":{"c":{"df":4,"docs":{"37":{"tf":1.0},"42":{"tf":1.7320508075688772},"47":{"tf":1.0},"67":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.4142135623730951},"34":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"6":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"51":{"tf":1.0}}},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"41":{"tf":1.4142135623730951}}}}}}},"t":{"'":{"df":5,"docs":{"13":{"tf":1.0},"27":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":4,"docs":{"31":{"tf":1.0},"37":{"tf":1.4142135623730951},"43":{"tf":1.0},"5":{"tf":1.0}}}}}}}},"j":{".":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"47":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"b":{"df":2,"docs":{"13":{"tf":1.0},"19":{"tf":1.0}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"46":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":4,"docs":{"47":{"tf":1.7320508075688772},"51":{"tf":2.23606797749979},"53":{"tf":1.4142135623730951},"56":{"tf":1.0}},"r":{"df":0,"docs":{},"p":{"c":{"\"":{":":{"\"":{"2":{".":{"0":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":4,"docs":{"57":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"\"":{":":{"\"":{"2":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"6":{"c":{"b":{"8":{"df":0,"docs":{},"v":{"a":{"a":{"d":{"9":{"3":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"6":{"df":0,"docs":{},"v":{"d":{"8":{"df":0,"docs":{},"p":{"6":{"7":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"q":{"df":0,"docs":{},"z":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"x":{"4":{"7":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"g":{"9":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"1":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"3":{"a":{"2":{"df":0,"docs":{},"g":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"7":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"j":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"y":{"9":{"b":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"5":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"v":{"df":0,"docs":{},"z":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"x":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"9":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"j":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"j":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"8":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"q":{"df":0,"docs":{},"p":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"4":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"3":{"df":0,"docs":{},"z":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"z":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"v":{"6":{"df":0,"docs":{},"u":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"k":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"w":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"7":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"3":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"7":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"9":{"df":0,"docs":{},"j":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"2":{"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"w":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"3":{"df":0,"docs":{},"z":{"6":{"9":{"df":0,"docs":{},"x":{"df":0,"docs":{},"m":{"1":{"df":0,"docs":{},"z":{"df":0,"docs":{},"e":{"3":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"6":{"df":0,"docs":{},"j":{"c":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"0":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"55":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"6":{"8":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"54":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"{":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"\"":{":":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"\"":{":":{"1":{",":{"\"":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"\"":{":":{"[":{"3":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"5":{"0":{",":{"4":{"8":{",":{"5":{"3":{",":{"4":{"8":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"8":{"4":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"9":{"0":{",":{"2":{"5":{"2":{",":{"1":{"0":{",":{"7":{",":{"2":{"8":{",":{"2":{"4":{"6":{",":{"1":{"4":{"0":{",":{"8":{"8":{",":{"1":{"7":{"7":{",":{"9":{"8":{",":{"8":{"2":{",":{"1":{"0":{",":{"2":{"2":{"7":{",":{"8":{"9":{",":{"8":{"1":{",":{"1":{"8":{",":{"3":{"0":{",":{"1":{"9":{"4":{",":{"1":{"0":{"1":{",":{"1":{"9":{"9":{",":{"1":{"6":{",":{"1":{"1":{",":{"7":{"3":{",":{"1":{"3":{"3":{",":{"2":{"0":{",":{"2":{"4":{"6":{",":{"6":{"2":{",":{"1":{"1":{"4":{",":{"3":{"9":{",":{"2":{"0":{",":{"1":{"1":{"3":{",":{"1":{"8":{"9":{",":{"3":{"2":{",":{"5":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"4":{"7":{",":{"1":{"5":{",":{"3":{"6":{",":{"1":{"0":{"2":{",":{"1":{"6":{"7":{",":{"8":{"3":{",":{"2":{"2":{"5":{",":{"4":{"2":{",":{"1":{"3":{"3":{",":{"1":{"2":{"7":{",":{"8":{"2":{",":{"3":{"4":{",":{"3":{"6":{",":{"2":{"2":{"4":{",":{"2":{"0":{"7":{",":{"1":{"3":{"0":{",":{"1":{"0":{"9":{",":{"2":{"3":{"0":{",":{"2":{"2":{"4":{",":{"1":{"8":{"8":{",":{"1":{"6":{"3":{",":{"3":{"3":{",":{"2":{"1":{"3":{",":{"1":{"3":{",":{"5":{",":{"1":{"1":{"7":{",":{"2":{"1":{"1":{",":{"2":{"5":{"1":{",":{"6":{"5":{",":{"1":{"5":{"9":{",":{"1":{"9":{"7":{",":{"5":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{"df":0,"docs":{},"}":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"56":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":5,"docs":{"51":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"24":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"25":{"tf":1.0}}}}}}}}},"k":{"df":3,"docs":{"15":{"tf":2.0},"17":{"tf":1.0},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":2,"docs":{"16":{"tf":1.0},"27":{"tf":1.0}}}},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}}},"y":{"df":13,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"35":{"tf":1.4142135623730951},"37":{"tf":1.0},"4":{"tf":1.4142135623730951},"41":{"tf":1.0},"5":{"tf":1.0},"52":{"tf":1.7320508075688772},"63":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"p":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":4,"docs":{"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"4":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"4":{"tf":1.0}}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"35":{"tf":1.0}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"5":{"tf":1.0}},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"l":{".":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"1":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"2":{"df":1,"docs":{"13":{"tf":2.0}}},"3":{"df":1,"docs":{"13":{"tf":2.8284271247461903}}},"4":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"g":{"df":3,"docs":{"1":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"15":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"31":{"tf":1.0},"57":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.7320508075688772},"3":{"tf":1.0}}},"df":13,"docs":{"10":{"tf":2.8284271247461903},"11":{"tf":2.0},"12":{"tf":3.3166247903554},"13":{"tf":3.4641016151377544},"15":{"tf":1.4142135623730951},"16":{"tf":2.449489742783178},"17":{"tf":2.23606797749979},"20":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"4":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":12,"docs":{"13":{"tf":1.7320508075688772},"20":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"3":{"tf":2.23606797749979},"31":{"tf":1.7320508075688772},"45":{"tf":1.4142135623730951},"57":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0}}}}}},"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"25":{"tf":1.7320508075688772}}}}}},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"47":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"3":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"1":{"tf":1.0}}}},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"19":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"67":{"tf":1.0}}},"k":{"df":2,"docs":{"13":{"tf":1.0},"9":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"37":{"tf":1.4142135623730951},"58":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"31":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"df":1,"docs":{"34":{"tf":1.0}}}}},"o":{"a":{"d":{"df":5,"docs":{"19":{"tf":2.6457513110645907},"34":{"tf":1.0},"35":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"56":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"58":{"tf":1.0}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"15":{"tf":1.0}}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"31":{"tf":1.0},"44":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.0},"39":{"tf":1.0}}}}}},"m":{"a":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"1":{"tf":1.0},"3":{"tf":1.0},"38":{"tf":1.4142135623730951},"5":{"tf":1.0}}}}},"df":0,"docs":{}}}},"k":{"df":0,"docs":{},"e":{"df":4,"docs":{"1":{"tf":1.0},"19":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"51":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"i":{"df":2,"docs":{"30":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}}}}},"p":{"df":2,"docs":{"39":{"tf":1.0},"41":{"tf":1.4142135623730951}}},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"3":{"tf":1.0},"51":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"46":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.0}}}}}},"x":{"df":1,"docs":{"69":{"tf":1.0}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"19":{"tf":1.0},"27":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"y":{"b":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}},"df":4,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"15":{"tf":2.6457513110645907},"17":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"n":{"df":3,"docs":{"1":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"0":{"tf":1.0},"1":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"10":{"tf":1.0},"8":{"tf":2.23606797749979}}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"36":{"tf":2.23606797749979},"37":{"tf":1.0},"38":{"tf":1.0},"43":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"\"":{":":{"\"":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"51":{"tf":1.0},"55":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"66":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"'":{"df":1,"docs":{"5":{"tf":1.0}}},"df":6,"docs":{"47":{"tf":1.0},"5":{"tf":2.0},"50":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"58":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"4":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"29":{"tf":1.0}}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"27":{"tf":1.0},"8":{"tf":1.0}}}}},"p":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"4":{"tf":1.0}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":2,"docs":{"10":{"tf":1.0},"20":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":3,"docs":{"40":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"8":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":6,"docs":{"3":{"tf":1.0},"31":{"tf":1.7320508075688772},"5":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0},"8":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"42":{"tf":2.0}}}}},"u":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":7,"docs":{"25":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"43":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.0}}}}}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":2,"docs":{"17":{"tf":1.7320508075688772},"8":{"tf":1.0}}}}},"c":{"df":0,"docs":{},"p":{"df":1,"docs":{"23":{"tf":1.4142135623730951}}}},"df":3,"docs":{"11":{"tf":1.4142135623730951},"17":{"tf":1.0},"69":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"37":{"tf":1.0},"5":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"d":{"df":9,"docs":{"13":{"tf":1.0},"19":{"tf":1.0},"27":{"tf":1.0},"31":{"tf":1.4142135623730951},"42":{"tf":1.0},"43":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":14,"docs":{"1":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"13":{"tf":2.23606797749979},"15":{"tf":1.0},"17":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"27":{"tf":2.0},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"31":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951},"6":{"tf":1.0},"69":{"tf":1.7320508075688772}}}}}}},"w":{"df":7,"docs":{"13":{"tf":1.4142135623730951},"17":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"58":{"tf":1.4142135623730951},"61":{"tf":1.0},"8":{"tf":1.0}}},"x":{"df":0,"docs":{},"t":{"df":5,"docs":{"10":{"tf":1.0},"12":{"tf":1.4142135623730951},"16":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"o":{"d":{"df":0,"docs":{},"e":{"df":15,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":2.0},"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"17":{"tf":2.23606797749979},"23":{"tf":1.0},"25":{"tf":1.7320508075688772},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"47":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"69":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}},"e":{"df":2,"docs":{"57":{"tf":1.0},"59":{"tf":1.0}}}},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"58":{"tf":1.0}}},"h":{"df":1,"docs":{"0":{"tf":1.0}}},"i":{"df":0,"docs":{},"f":{"df":4,"docs":{"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}}},"w":{"df":1,"docs":{"6":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"3":{"tf":1.0}}}},"u":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":1,"docs":{"27":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"df":7,"docs":{"17":{"tf":2.23606797749979},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"56":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}},"df":1,"docs":{"69":{"tf":2.23606797749979}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":5,"docs":{"12":{"tf":1.4142135623730951},"13":{"tf":2.23606797749979},"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}},"c":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":4,"docs":{"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"19":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"c":{"df":5,"docs":{"13":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"5":{"tf":1.0},"62":{"tf":1.0}}},"df":12,"docs":{"13":{"tf":1.0},"15":{"tf":1.0},"19":{"tf":2.0},"20":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"5":{"tf":2.0},"58":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":1.0}}},"p":{"a":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"15":{"tf":1.0},"19":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.7320508075688772}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"19":{"tf":1.0},"28":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"12":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":2.8284271247461903}}}}}}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"16":{"tf":1.0},"28":{"tf":1.0},"51":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"16":{"tf":1.0},"25":{"tf":1.0},"6":{"tf":1.0},"8":{"tf":1.0}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"20":{"tf":1.0},"51":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"9":{"tf":1.0}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":2,"docs":{"25":{"tf":1.0},"7":{"tf":1.0}}}}}}}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"35":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"56":{"tf":1.0}}}}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":2,"docs":{"40":{"tf":1.0},"41":{"tf":1.0}}}},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"8":{"tf":1.0},"9":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":2,"docs":{"3":{"tf":1.0},"40":{"tf":1.0}},"i":{"df":0,"docs":{},"z":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"m":{"df":3,"docs":{"51":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":13,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}}},"s":{"\"":{":":{"[":{"\"":{"2":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"6":{"c":{"b":{"8":{"df":0,"docs":{},"v":{"a":{"a":{"d":{"9":{"3":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"6":{"df":0,"docs":{},"v":{"d":{"8":{"df":0,"docs":{},"p":{"6":{"7":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"q":{"df":0,"docs":{},"z":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"x":{"4":{"7":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"g":{"9":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"1":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"3":{"a":{"2":{"df":0,"docs":{},"g":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"7":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"j":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"y":{"9":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"g":{"df":0,"docs":{},"v":{"df":0,"docs":{},"k":{"df":0,"docs":{},"y":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"h":{"b":{"2":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"n":{"3":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"f":{"df":0,"docs":{},"k":{"df":0,"docs":{},"x":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"h":{"df":0,"docs":{},"p":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"56":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}}},"5":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"v":{"df":0,"docs":{},"z":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"x":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"9":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"j":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"j":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"8":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"q":{"df":0,"docs":{},"p":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"4":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"3":{"df":0,"docs":{},"z":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"z":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"v":{"6":{"df":0,"docs":{},"u":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"k":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"w":{"df":2,"docs":{"54":{"tf":1.0},"58":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"8":{"3":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"t":{"2":{"df":0,"docs":{},"h":{"5":{"df":0,"docs":{},"u":{"1":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"d":{"df":0,"docs":{},"q":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"6":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"y":{"3":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"51":{"tf":1.0},"55":{"tf":1.0},"60":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"m":{"7":{"8":{"c":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"8":{"df":0,"docs":{},"o":{"3":{"df":0,"docs":{},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"h":{"df":0,"docs":{},"x":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"z":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"4":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"df":0,"docs":{},"k":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"x":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"k":{"df":0,"docs":{},"n":{"df":0,"docs":{},"h":{"1":{"2":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"0":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}}},"[":{"6":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"29":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}},"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"t":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"15":{"tf":2.0},"16":{"tf":1.4142135623730951}}}}}},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"34":{"tf":1.0},"35":{"tf":1.0},"43":{"tf":1.0},"6":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"i":{"d":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{".":{"df":0,"docs":{},"o":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.7320508075688772}}}},"y":{"df":2,"docs":{"68":{"tf":2.449489742783178},"69":{"tf":1.7320508075688772}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":10,"docs":{"1":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"27":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.4142135623730951},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"0":{"tf":1.0},"1":{"tf":1.7320508075688772},"19":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"11":{"tf":1.0},"27":{"tf":1.0},"31":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"35":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}}}}},"t":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.0},"35":{"tf":1.7320508075688772}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":1,"docs":{"27":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"19":{"tf":2.8284271247461903},"20":{"tf":2.0},"40":{"tf":1.0}}}}}}}},"l":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"23":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"h":{"'":{"df":1,"docs":{"9":{"tf":1.0}}},"df":8,"docs":{"11":{"tf":1.0},"12":{"tf":2.0},"13":{"tf":3.0},"17":{"tf":2.0},"28":{"tf":1.7320508075688772},"31":{"tf":3.1622776601683795},"8":{"tf":1.4142135623730951},"9":{"tf":1.7320508075688772}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"13":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"41":{"tf":1.7320508075688772},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"41":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.0},"29":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":4,"docs":{"28":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.23606797749979}}}},"t":{"df":3,"docs":{"48":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"5":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"d":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"df":10,"docs":{"51":{"tf":1.7320508075688772},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"4":{"tf":1.0}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"8":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":3,"docs":{"13":{"tf":1.0},"27":{"tf":1.4142135623730951},"58":{"tf":1.0}}}}}},"v":{"df":1,"docs":{"13":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":1,"docs":{"13":{"tf":2.0}},"s":{"df":2,"docs":{"34":{"tf":1.0},"58":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":4.795831523312719}}}}},"o":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"25":{"tf":1.4142135623730951}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"68":{"tf":3.0},"69":{"tf":2.449489742783178}}},"df":0,"docs":{}}},"df":9,"docs":{"11":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.0},"21":{"tf":1.4142135623730951},"25":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":3.0},"58":{"tf":1.0},"69":{"tf":1.7320508075688772}},"i":{"d":{"df":1,"docs":{"68":{"tf":2.23606797749979}}},"df":0,"docs":{}}}}}},"d":{"df":0,"docs":{},"u":{"c":{"df":3,"docs":{"10":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"'":{"df":1,"docs":{"41":{"tf":1.4142135623730951}}},"_":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"68":{"tf":1.0}}},"df":0,"docs":{}}},"df":19,"docs":{"1":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951},"35":{"tf":2.0},"36":{"tf":1.0},"37":{"tf":2.449489742783178},"38":{"tf":1.7320508075688772},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"41":{"tf":1.7320508075688772},"42":{"tf":2.0},"43":{"tf":2.449489742783178},"44":{"tf":1.0},"45":{"tf":1.0},"56":{"tf":1.7320508075688772},"58":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.0}}}}}}}}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"13":{"tf":1.0},"19":{"tf":1.0}}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.4142135623730951},"28":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"f":{"df":8,"docs":{"10":{"tf":1.0},"27":{"tf":2.23606797749979},"28":{"tf":2.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.6457513110645907},"7":{"tf":1.7320508075688772},"8":{"tf":2.8284271247461903}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":5,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"5":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}}},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"e":{"df":3,"docs":{"29":{"tf":1.0},"31":{"tf":1.0},"9":{"tf":1.0}}},"i":{"d":{"df":7,"docs":{"29":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}},"x":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}}},"u":{"b":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":11,"docs":{"3":{"tf":1.4142135623730951},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"52":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"63":{"tf":1.0},"68":{"tf":4.242640687119285},"69":{"tf":3.3166247903554}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":8,"docs":{"27":{"tf":1.0},"3":{"tf":1.4142135623730951},"35":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.4142135623730951},"52":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.4142135623730951}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":2,"docs":{"49":{"tf":1.4142135623730951},"62":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"55":{"tf":1.0},"60":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"55":{"tf":1.0},"56":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}}}}}}}},"r":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":5,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"31":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"df":0,"docs":{},"k":{"df":2,"docs":{"15":{"tf":1.0},"16":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"13":{"tf":1.0},"19":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"12":{"tf":1.0},"15":{"tf":1.4142135623730951}}}},"d":{"df":2,"docs":{"35":{"tf":1.4142135623730951},"56":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}},"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"v":{"df":3,"docs":{"60":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.7320508075688772}}}}},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"d":{"df":3,"docs":{"25":{"tf":1.0},"28":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}},"df":1,"docs":{"15":{"tf":1.0}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"25":{"tf":1.0},"46":{"tf":1.0},"53":{"tf":1.4142135623730951}}}}},"g":{"a":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"1":{"tf":1.0},"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}}}}}}}}}}},"df":0,"docs":{}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"1":{"tf":1.0}}}}},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"z":{"df":0,"docs":{},"v":{"df":1,"docs":{"69":{"tf":1.0}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"i":{"c":{"df":9,"docs":{"25":{"tf":1.7320508075688772},"26":{"tf":1.0},"27":{"tf":2.449489742783178},"28":{"tf":1.7320508075688772},"29":{"tf":1.7320508075688772},"30":{"tf":1.4142135623730951},"31":{"tf":3.3166247903554},"5":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":3,"docs":{"13":{"tf":1.7320508075688772},"56":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"50":{"tf":1.0},"60":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"df":17,"docs":{"35":{"tf":1.0},"47":{"tf":1.0},"51":{"tf":3.1622776601683795},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"r":{"df":9,"docs":{"13":{"tf":1.0},"27":{"tf":2.23606797749979},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.7320508075688772},"5":{"tf":1.0},"68":{"tf":2.8284271247461903},"69":{"tf":2.0},"8":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"4":{"tf":1.7320508075688772}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"v":{"df":2,"docs":{"16":{"tf":1.0},"9":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":4,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0},"51":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":1,"docs":{"5":{"tf":1.0}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":16,"docs":{"11":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"36":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.7320508075688772},"57":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"59":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951},"63":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"65":{"tf":1.7320508075688772},"66":{"tf":1.4142135623730951}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"58":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":7,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"68":{"tf":3.872983346207417}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":2.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"o":{"a":{"d":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.4142135623730951},"12":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"n":{"d":{"df":4,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"t":{"df":2,"docs":{"39":{"tf":1.0},"6":{"tf":1.0}}}}},"p":{"c":{"df":7,"docs":{"47":{"tf":2.0},"48":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"53":{"tf":1.4142135623730951},"62":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"40":{"tf":1.0}}}},"n":{"df":3,"docs":{"10":{"tf":1.0},"19":{"tf":1.0},"44":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":7,"docs":{"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":2.0},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"43":{"tf":2.23606797749979}}}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":3,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"58":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":11,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":2.449489742783178},"5":{"tf":2.449489742783178},"6":{"tf":1.0}}},"p":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"13":{"tf":1.0}}}},"w":{"df":2,"docs":{"31":{"tf":1.0},"8":{"tf":1.0}}}},"c":{"a":{"df":0,"docs":{},"l":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"25":{"tf":1.0}}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":3,"docs":{"12":{"tf":2.23606797749979},"17":{"tf":1.4142135623730951},"4":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}}},"df":4,"docs":{"13":{"tf":2.0},"16":{"tf":1.7320508075688772},"42":{"tf":1.4142135623730951},"43":{"tf":1.7320508075688772}},"e":{"c":{"df":1,"docs":{"69":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"d":{"df":10,"docs":{"1":{"tf":1.4142135623730951},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"3":{"tf":1.0},"31":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"4":{"tf":1.0},"68":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"46":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"6":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"d":{"df":3,"docs":{"11":{"tf":2.0},"12":{"tf":1.4142135623730951},"31":{"tf":2.0}}},"df":1,"docs":{"1":{"tf":1.0}},"m":{"df":1,"docs":{"5":{"tf":1.0}}}},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"25":{"tf":1.0},"27":{"tf":1.4142135623730951}}}}}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.0},"11":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"d":{"df":6,"docs":{"16":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"51":{"tf":1.4142135623730951},"68":{"tf":2.23606797749979},"69":{"tf":3.605551275463989}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"50":{"tf":1.0},"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":2,"docs":{"19":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"9":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":4,"docs":{"1":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"51":{"tf":1.0}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"19":{"tf":1.0},"35":{"tf":1.0}}}}}},"h":{"a":{"df":2,"docs":{"52":{"tf":1.0},"6":{"tf":1.0}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"34":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"34":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"44":{"tf":1.0}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":12,"docs":{"1":{"tf":1.0},"11":{"tf":1.4142135623730951},"31":{"tf":2.8284271247461903},"37":{"tf":1.0},"52":{"tf":1.4142135623730951},"54":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"65":{"tf":1.4142135623730951},"68":{"tf":3.605551275463989},"69":{"tf":3.4641016151377544}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"65":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"65":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"66":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":8,"docs":{"3":{"tf":1.0},"31":{"tf":1.7320508075688772},"52":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.0}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"36":{"tf":1.0},"58":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":3,"docs":{"10":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":10,"docs":{"13":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"4":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0}}}}},"z":{"df":0,"docs":{},"e":{"df":1,"docs":{"28":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}},"df":2,"docs":{"13":{"tf":1.4142135623730951},"29":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":3.4641016151377544},"15":{"tf":1.0},"16":{"tf":2.0},"17":{"tf":1.0},"4":{"tf":1.0}}},"w":{"df":1,"docs":{"9":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"25":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"15":{"tf":1.4142135623730951},"16":{"tf":1.0},"5":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"o":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"l":{"a":{"df":0,"docs":{},"n":{"a":{"'":{"df":6,"docs":{"1":{"tf":1.4142135623730951},"25":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":2.0}}},"df":22,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":2.23606797749979},"28":{"tf":1.7320508075688772},"3":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":2.23606797749979},"35":{"tf":1.0},"4":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.7320508075688772},"5":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.0},"67":{"tf":1.7320508075688772},"68":{"tf":3.872983346207417},"69":{"tf":4.69041575982343},"8":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.0}}}},"v":{"df":1,"docs":{"25":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"27":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"a":{"c":{"df":0,"docs":{},"e":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":8,"docs":{"0":{"tf":1.0},"11":{"tf":1.0},"31":{"tf":2.0},"38":{"tf":1.4142135623730951},"47":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":5,"docs":{"13":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.0},"68":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"43":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"9":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":2,"docs":{"19":{"tf":1.7320508075688772},"40":{"tf":1.0}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}},"k":{"df":0,"docs":{},"e":{"df":5,"docs":{"10":{"tf":1.0},"12":{"tf":1.0},"15":{"tf":1.0},"29":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951}}}},"n":{"d":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.0},"8":{"tf":1.0}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"50":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":3,"docs":{"13":{"tf":1.0},"37":{"tf":2.0},"38":{"tf":1.4142135623730951}}},"u":{"df":2,"docs":{"54":{"tf":1.0},"58":{"tf":1.4142135623730951}},"s":{"df":1,"docs":{"58":{"tf":1.4142135623730951}}}}},"y":{"df":1,"docs":{"28":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"19":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"26":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"31":{"tf":1.4142135623730951},"35":{"tf":2.0},"8":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":4,"docs":{"27":{"tf":1.0},"29":{"tf":1.4142135623730951},"31":{"tf":1.0},"37":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"16":{"tf":1.0},"19":{"tf":1.0},"28":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"56":{"tf":1.0}}}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"5":{"tf":1.0}}}},"n":{"df":0,"docs":{},"g":{"df":11,"docs":{"11":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"58":{"tf":1.7320508075688772},"60":{"tf":2.0},"61":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"65":{"tf":1.4142135623730951}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"u":{"b":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"(":{"df":1,"docs":{"69":{"tf":1.0}}},"df":1,"docs":{"69":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":1,"docs":{"56":{"tf":1.0}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"27":{"tf":1.0},"31":{"tf":2.0},"34":{"tf":1.0},"62":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":6,"docs":{"50":{"tf":1.0},"62":{"tf":2.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":1,"docs":{"58":{"tf":1.0}},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"51":{"tf":1.0},"58":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"37":{"tf":1.0},"43":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}},"d":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"25":{"tf":1.0},"27":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{},"x":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":6,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"28":{"tf":1.0},"35":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"5":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"12":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}}},"y":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"28":{"tf":1.0}}}}}}},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":14,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"15":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"28":{"tf":1.0},"5":{"tf":2.6457513110645907},"6":{"tf":1.0},"7":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":1,"docs":{"8":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":5,"docs":{"10":{"tf":1.4142135623730951},"28":{"tf":1.0},"42":{"tf":1.4142135623730951},"5":{"tf":2.449489742783178},"68":{"tf":1.0}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{":":{":":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"42":{"tf":1.0},"43":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"31":{"tf":1.0},"41":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"34":{"tf":1.4142135623730951}}}}}}},"df":3,"docs":{"12":{"tf":2.449489742783178},"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":3,"docs":{"1":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}}}}}}},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":2,"docs":{"2":{"tf":1.4142135623730951},"4":{"tf":1.4142135623730951}}}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.4142135623730951},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"19":{"tf":1.4142135623730951}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":4,"docs":{"31":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"43":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"r":{"d":{"df":3,"docs":{"19":{"tf":1.4142135623730951},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":4,"docs":{"27":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"37":{"tf":1.0}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"s":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":3,"docs":{"15":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":1.0}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":2.23606797749979}}}}}}}}}},"u":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":2.23606797749979},"13":{"tf":3.872983346207417},"15":{"tf":1.0},"16":{"tf":1.7320508075688772},"17":{"tf":1.7320508075688772},"3":{"tf":2.23606797749979}}}},"df":1,"docs":{"31":{"tf":1.4142135623730951}},"m":{"df":0,"docs":{},"e":{"df":9,"docs":{"10":{"tf":1.0},"13":{"tf":2.449489742783178},"27":{"tf":1.0},"4":{"tf":1.4142135623730951},"5":{"tf":2.6457513110645907},"6":{"tf":1.0},"68":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"16":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":3,"docs":{"6":{"tf":1.4142135623730951},"68":{"tf":2.6457513110645907},"69":{"tf":3.0}}}}},"df":0,"docs":{}}}}}},"l":{"df":1,"docs":{"69":{"tf":1.0}}},"o":{"d":{"df":0,"docs":{},"o":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":10,"docs":{"29":{"tf":1.0},"3":{"tf":1.0},"35":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"42":{"tf":1.4142135623730951},"43":{"tf":1.0},"56":{"tf":1.4142135623730951},"60":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"9":{"tf":1.0}}},"l":{"df":2,"docs":{"19":{"tf":1.0},"67":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"28":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"p":{"df":2,"docs":{"1":{"tf":1.7320508075688772},"3":{"tf":1.0}},"u":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}}},"r":{"a":{"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"16":{"tf":1.0},"3":{"tf":1.0},"9":{"tf":1.0}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":25,"docs":{"1":{"tf":2.449489742783178},"12":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"25":{"tf":1.0},"29":{"tf":1.0},"3":{"tf":2.449489742783178},"36":{"tf":1.0},"37":{"tf":2.0},"39":{"tf":1.4142135623730951},"40":{"tf":2.23606797749979},"41":{"tf":1.0},"43":{"tf":1.7320508075688772},"44":{"tf":1.0},"5":{"tf":1.7320508075688772},"52":{"tf":1.0},"54":{"tf":2.0},"58":{"tf":3.0},"59":{"tf":1.0},"6":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.7320508075688772},"65":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"69":{"tf":3.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"68":{"tf":2.449489742783178},"69":{"tf":3.0}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"t":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"25":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":2,"docs":{"13":{"tf":1.0},"25":{"tf":1.0}}}},"i":{"df":1,"docs":{"5":{"tf":1.0}}},"u":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":1,"docs":{"54":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"27":{"tf":1.0},"5":{"tf":1.4142135623730951},"6":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"6":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"u":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}}},"w":{"df":0,"docs":{},"o":{"df":4,"docs":{"13":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0},"5":{"tf":1.0}}}},"x":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"68":{"tf":3.3166247903554}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":10,"docs":{"37":{"tf":1.0},"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"i":{"c":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"10":{"tf":1.0},"30":{"tf":1.0}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"5":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"51":{"tf":1.0}}}},"t":{"df":4,"docs":{"19":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"3":{"tf":1.0}}}},"k":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"58":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":2.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":1,"docs":{"59":{"tf":1.0}}}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"65":{"tf":1.0},"66":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.0},"9":{"tf":1.0}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}},"p":{"df":8,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"11":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"40":{"tf":1.0},"5":{"tf":1.0}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}},"s":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":3.605551275463989}}}},"d":{"df":2,"docs":{"27":{"tf":1.0},"30":{"tf":1.0}}},"df":23,"docs":{"1":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":2.8284271247461903},"35":{"tf":1.0},"4":{"tf":2.23606797749979},"42":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.4142135623730951},"5":{"tf":1.7320508075688772},"51":{"tf":1.0},"6":{"tf":2.23606797749979},"62":{"tf":1.0},"68":{"tf":1.0},"8":{"tf":2.0},"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":6,"docs":{"37":{"tf":1.0},"40":{"tf":1.0},"56":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":3,"docs":{"35":{"tf":1.0},"4":{"tf":1.0},"42":{"tf":1.7320508075688772}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"'":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"29":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":14,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":2.23606797749979},"15":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"25":{"tf":2.0},"27":{"tf":1.7320508075688772},"29":{"tf":1.7320508075688772},"3":{"tf":1.4142135623730951},"31":{"tf":2.23606797749979},"4":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":2,"docs":{"31":{"tf":1.0},"51":{"tf":1.0}}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"f":{"df":3,"docs":{"6":{"tf":1.4142135623730951},"8":{"tf":1.7320508075688772},"9":{"tf":3.0}}}},"df":3,"docs":{"16":{"tf":1.0},"17":{"tf":1.0},"69":{"tf":3.3166247903554}},"e":{"c":{"<":{"df":0,"docs":{},"u":{"8":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}},"f":{"df":5,"docs":{"1":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":7,"docs":{"28":{"tf":1.4142135623730951},"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":1.0},"6":{"tf":2.0},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"42":{"tf":1.0},"69":{"tf":4.69041575982343}}}}}}}},"i":{"a":{"df":2,"docs":{"11":{"tf":1.0},"12":{"tf":1.0}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"13":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0}}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":1.4142135623730951},"13":{"tf":2.449489742783178},"15":{"tf":1.7320508075688772},"16":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.0},"69":{"tf":1.0}}}},"l":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"68":{"tf":1.0}}},"df":3,"docs":{"67":{"tf":1.4142135623730951},"68":{"tf":3.872983346207417},"69":{"tf":4.69041575982343}}}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"r":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"h":{"/":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"19":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":2.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":2.23606797749979}}}}},"y":{"df":5,"docs":{"19":{"tf":1.0},"28":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.7320508075688772},"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"'":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.0}}}},"r":{"df":1,"docs":{"5":{"tf":1.0}}}},"b":{"3":{".":{"df":0,"docs":{},"j":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"49":{"tf":1.4142135623730951},"50":{"tf":1.0},"62":{"tf":2.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"15":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"l":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"5":{"tf":1.0},"6":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":2,"docs":{"20":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"37":{"tf":1.0},"5":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"37":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"40":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"d":{"df":1,"docs":{"3":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":4,"docs":{"25":{"tf":1.0},"29":{"tf":1.0},"44":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":3,"docs":{"20":{"tf":1.0},"35":{"tf":1.4142135623730951},"58":{"tf":1.0}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"33":{"tf":1.0},"35":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"s":{":":{"/":{"/":{"<":{"a":{"d":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"8":{"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"df":10,"docs":{"13":{"tf":1.7320508075688772},"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}},"x":{"df":1,"docs":{"13":{"tf":1.0}}}},"y":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"'":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"z":{"df":2,"docs":{"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":1,"docs":{"43":{"tf":1.0}}}}}}}},"title":{"root":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"38":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":2,"docs":{"47":{"tf":1.0},"53":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"46":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"67":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"52":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"48":{"tf":1.0},"49":{"tf":1.0}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"39":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"41":{"tf":1.0}}}}}},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"14":{"tf":1.0},"68":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"40":{"tf":1.0},"41":{"tf":1.0}}}}},"df":0,"docs":{}}}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"13":{"tf":1.0}}},"m":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"45":{"tf":1.0},"51":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"d":{"df":2,"docs":{"18":{"tf":1.0},"20":{"tf":1.0}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"4":{"tf":1.0},"44":{"tf":1.0}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"11":{"tf":1.0}}}}},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"55":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"7":{"tf":1.0}}}}}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":1,"docs":{"48":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"a":{"c":{"df":1,"docs":{"42":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"33":{"tf":1.0},"6":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"41":{"tf":1.0}}}}}}}},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"47":{"tf":1.0},"53":{"tf":1.0}},"r":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"l":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"16":{"tf":1.0}}}}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"45":{"tf":1.0}}}}}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{}}}},"p":{"df":1,"docs":{"41":{"tf":1.0}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"50":{"tf":1.0}}},"df":0,"docs":{}}}}}},"n":{"c":{"df":0,"docs":{},"p":{"df":1,"docs":{"23":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":2,"docs":{"17":{"tf":1.0},"29":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"43":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":1.0}}}}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"15":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"19":{"tf":1.0},"20":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"h":{"df":1,"docs":{"28":{"tf":1.0}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"41":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"21":{"tf":1.0}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"'":{"df":1,"docs":{"41":{"tf":1.0}}},"df":2,"docs":{"38":{"tf":1.0},"41":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"f":{"df":1,"docs":{"7":{"tf":1.0}}}},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"41":{"tf":1.0}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"u":{"b":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"53":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":2,"docs":{"8":{"tf":1.0},"9":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"25":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":1,"docs":{"51":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"4":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.0},"12":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"c":{"df":4,"docs":{"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"53":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"s":{"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"d":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{}},"n":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"66":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"a":{"df":3,"docs":{"32":{"tf":1.0},"34":{"tf":1.0},"67":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{}}},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"26":{"tf":1.0},"35":{"tf":1.0}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"42":{"tf":1.0}}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}}},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":2,"docs":{"2":{"tf":1.0},"4":{"tf":1.0}}}}}}}}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"21":{"tf":1.0},"22":{"tf":1.0},"39":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"21":{"tf":1.0},"22":{"tf":1.0}}}}},"s":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":2,"docs":{"3":{"tf":1.0},"4":{"tf":1.0}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"22":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"f":{"df":2,"docs":{"6":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"67":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"49":{"tf":1.0},"62":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"38":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"44":{"tf":1.0}}}}}}}}},"pipeline":["trimmer","stopWordFilter","stemmer"],"ref":"id","version":"0.9.5"},"results_options":{"limit_results":30,"teaser_word_count":30},"search_options":{"bool":"OR","expand":true,"fields":{"body":{"boost":1},"breadcrumbs":{"boost":1},"title":{"boost":2}}}} \ No newline at end of file diff --git a/book/service.rs b/book/service.rs new file mode 100644 index 00000000000000..a882181bc4b469 --- /dev/null +++ b/book/service.rs @@ -0,0 +1,30 @@ +//! The `service` module implements a trait used by services and stages. +//! +//! A Service is any object that implements its functionality on a separate thread. It implements a +//! `join()` method, which can be used to wait for that thread to close. +//! +//! The Service trait may also be used to implement a pipeline stage. Like a service, its +//! functionality is also implemented by a thread, but unlike a service, a stage isn't a server +//! that replies to client requests. Instead, a stage acts more like a pure function. It's a oneway +//! street. It processes messages from its input channel and then sends the processed data to an +//! output channel. Stages can be composed to form a linear chain called a pipeline. +//! +//! The approach to creating a pipeline stage in Rust may be unique to Solana. We haven't seen the +//! same technique used in other Rust projects and there may be better ways to do it. The Solana +//! approach defines a stage as an object that communicates to its previous stage and the next +//! stage using channels. By convention, each stage accepts a *receiver* for input and creates a +//! second output channel. The second channel is used to pass data to the next stage, and so its +//! sender is moved into the stage's thread and the receiver is returned from its constructor. +//! +//! A well-written stage should create a thread and call a short `run()` method. The method should +//! read input from its input channel, call a function from another module that processes it, and +//! then send the output to the output channel. The functionality in the second module will likely +//! not use threads or channels. + +use std::thread::Result; + +pub trait Service { + type JoinReturnType; + + fn join(self) -> Result; +} diff --git a/book/signature.rs b/book/signature.rs new file mode 100644 index 00000000000000..fa283ff885ac5f --- /dev/null +++ b/book/signature.rs @@ -0,0 +1,67 @@ +//! The `signature` module provides functionality for public, and private keys. + +use rand::{ChaChaRng, Rng, SeedableRng}; +use rayon::prelude::*; +use untrusted::Input; + +pub use solana_sdk::signature::*; + +pub struct GenKeys { + generator: ChaChaRng, +} + +impl GenKeys { + pub fn new(seed: [u8; 32]) -> GenKeys { + let generator = ChaChaRng::from_seed(seed); + GenKeys { generator } + } + + fn gen_seed(&mut self) -> [u8; 32] { + let mut seed = [0u8; 32]; + self.generator.fill(&mut seed); + seed + } + + fn gen_n_seeds(&mut self, n: u64) -> Vec<[u8; 32]> { + (0..n).map(|_| self.gen_seed()).collect() + } + + pub fn gen_n_keypairs(&mut self, n: u64) -> Vec { + self.gen_n_seeds(n) + .into_par_iter() + .map(|seed| Keypair::from_seed_unchecked(Input::from(&seed)).unwrap()) + .collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + pub use solana_sdk::pubkey::Pubkey; + use std::collections::HashSet; + + #[test] + fn test_new_key_is_deterministic() { + let seed = [0u8; 32]; + let mut gen0 = GenKeys::new(seed); + let mut gen1 = GenKeys::new(seed); + + for _ in 0..100 { + assert_eq!(gen0.gen_seed().to_vec(), gen1.gen_seed().to_vec()); + } + } + + fn gen_n_pubkeys(seed: [u8; 32], n: u64) -> HashSet { + GenKeys::new(seed) + .gen_n_keypairs(n) + .into_iter() + .map(|x| x.pubkey()) + .collect() + } + + #[test] + fn test_gen_n_pubkeys_deterministic() { + let seed = [0u8; 32]; + assert_eq!(gen_n_pubkeys(seed, 50), gen_n_pubkeys(seed, 50)); + } +} diff --git a/book/sigverify.rs b/book/sigverify.rs new file mode 100644 index 00000000000000..301b18bf2fa20c --- /dev/null +++ b/book/sigverify.rs @@ -0,0 +1,473 @@ +//! The `sigverify` module provides digital signature verification functions. +//! By default, signatures are verified in parallel using all available CPU +//! cores. When `--features=cuda` is enabled, signature verification is +//! offloaded to the GPU. +//! + +use byteorder::{LittleEndian, ReadBytesExt}; +use counter::Counter; +use log::Level; +use packet::{Packet, SharedPackets}; +use result::Result; +use signature::Signature; +use solana_sdk::pubkey::Pubkey; +use std::io; +use std::mem::size_of; +use std::sync::atomic::AtomicUsize; +#[cfg(test)] +use transaction::Transaction; + +pub const TX_OFFSET: usize = 0; + +type TxOffsets = (Vec, Vec, Vec, Vec, Vec>); + +#[cfg(feature = "cuda")] +#[repr(C)] +struct Elems { + elems: *const Packet, + num: u32, +} + +#[cfg(feature = "cuda")] +#[link(name = "cuda-crypt")] +extern "C" { + fn ed25519_init() -> bool; + fn ed25519_set_verbose(val: bool); + fn ed25519_verify_many( + vecs: *const Elems, + num: u32, //number of vecs + message_size: u32, //size of each element inside the elems field of the vec + total_packets: u32, + total_signatures: u32, + message_lens: *const u32, + pubkey_offsets: *const u32, + signature_offsets: *const u32, + signed_message_offsets: *const u32, + out: *mut u8, //combined length of all the items in vecs + ) -> u32; + + pub fn chacha_cbc_encrypt_many_sample( + input: *const u8, + sha_state: *mut u8, + in_len: usize, + keys: *const u8, + ivec: *mut u8, + num_keys: u32, + samples: *const u64, + num_samples: u32, + starting_block: u64, + time_us: *mut f32, + ); + + pub fn chacha_init_sha_state(sha_state: *mut u8, num_keys: u32); + pub fn chacha_end_sha_state(sha_state_in: *const u8, out: *mut u8, num_keys: u32); +} + +#[cfg(not(feature = "cuda"))] +pub fn init() { + // stub +} + +fn verify_packet(packet: &Packet) -> u8 { + use ring::signature; + use signature::Signature; + use solana_sdk::pubkey::Pubkey; + use untrusted; + + let (sig_len, sig_start, msg_start, pubkey_start) = get_packet_offsets(packet, 0); + let mut sig_start = sig_start as usize; + let mut pubkey_start = pubkey_start as usize; + let msg_start = msg_start as usize; + + if packet.meta.size <= msg_start { + return 0; + } + + let msg_end = packet.meta.size; + for _ in 0..sig_len { + let pubkey_end = pubkey_start as usize + size_of::(); + let sig_end = sig_start as usize + size_of::(); + + if pubkey_end >= packet.meta.size || sig_end >= packet.meta.size { + return 0; + } + + if signature::verify( + &signature::ED25519, + untrusted::Input::from(&packet.data[pubkey_start..pubkey_end]), + untrusted::Input::from(&packet.data[msg_start..msg_end]), + untrusted::Input::from(&packet.data[sig_start..sig_end]), + ).is_err() + { + return 0; + } + pubkey_start += size_of::(); + sig_start += size_of::(); + } + 1 +} + +fn verify_packet_disabled(_packet: &Packet) -> u8 { + warn!("signature verification is disabled"); + 1 +} + +fn batch_size(batches: &[SharedPackets]) -> usize { + batches + .iter() + .map(|p| p.read().unwrap().packets.len()) + .sum() +} + +#[cfg(not(feature = "cuda"))] +pub fn ed25519_verify(batches: &[SharedPackets]) -> Vec> { + ed25519_verify_cpu(batches) +} + +pub fn get_packet_offsets(packet: &Packet, current_offset: u32) -> (u32, u32, u32, u32) { + // Read in u64 as the size of signatures array + let mut rdr = io::Cursor::new(&packet.data[TX_OFFSET..size_of::()]); + let sig_len = rdr.read_u64::().unwrap() as u32; + + let msg_start_offset = + current_offset + size_of::() as u32 + sig_len * size_of::() as u32; + let pubkey_offset = msg_start_offset + size_of::() as u32; + + let sig_start = TX_OFFSET as u32 + size_of::() as u32; + + (sig_len, sig_start, msg_start_offset, pubkey_offset) +} + +pub fn generate_offsets(batches: &[SharedPackets]) -> Result { + let mut signature_offsets: Vec<_> = Vec::new(); + let mut pubkey_offsets: Vec<_> = Vec::new(); + let mut msg_start_offsets: Vec<_> = Vec::new(); + let mut msg_sizes: Vec<_> = Vec::new(); + let mut current_packet = 0; + let mut v_sig_lens = Vec::new(); + batches.into_iter().for_each(|p| { + let mut sig_lens = Vec::new(); + p.read().unwrap().packets.iter().for_each(|packet| { + let current_offset = current_packet as u32 * size_of::() as u32; + + let (sig_len, _sig_start, msg_start_offset, pubkey_offset) = + get_packet_offsets(packet, current_offset); + let mut pubkey_offset = pubkey_offset; + + sig_lens.push(sig_len); + + trace!("pubkey_offset: {}", pubkey_offset); + let mut sig_offset = current_offset + size_of::() as u32; + for _ in 0..sig_len { + signature_offsets.push(sig_offset); + sig_offset += size_of::() as u32; + + pubkey_offsets.push(pubkey_offset); + pubkey_offset += size_of::() as u32; + + msg_start_offsets.push(msg_start_offset); + + msg_sizes.push(current_offset + (packet.meta.size as u32) - msg_start_offset); + } + current_packet += 1; + }); + v_sig_lens.push(sig_lens); + }); + Ok(( + signature_offsets, + pubkey_offsets, + msg_start_offsets, + msg_sizes, + v_sig_lens, + )) +} + +pub fn ed25519_verify_cpu(batches: &[SharedPackets]) -> Vec> { + use rayon::prelude::*; + let count = batch_size(batches); + info!("CPU ECDSA for {}", batch_size(batches)); + let rv = batches + .into_par_iter() + .map(|p| { + p.read() + .unwrap() + .packets + .par_iter() + .map(verify_packet) + .collect() + }).collect(); + inc_new_counter_info!("ed25519_verify_cpu", count); + rv +} + +pub fn ed25519_verify_disabled(batches: &[SharedPackets]) -> Vec> { + use rayon::prelude::*; + let count = batch_size(batches); + info!("disabled ECDSA for {}", batch_size(batches)); + let rv = batches + .into_par_iter() + .map(|p| { + p.read() + .unwrap() + .packets + .par_iter() + .map(verify_packet_disabled) + .collect() + }).collect(); + inc_new_counter_info!("ed25519_verify_disabled", count); + rv +} + +#[cfg(feature = "cuda")] +pub fn init() { + unsafe { + ed25519_set_verbose(true); + if !ed25519_init() { + panic!("ed25519_init() failed"); + } + ed25519_set_verbose(false); + } +} + +#[cfg(feature = "cuda")] +pub fn ed25519_verify(batches: &[SharedPackets]) -> Vec> { + use packet::PACKET_DATA_SIZE; + let count = batch_size(batches); + + // micro-benchmarks show GPU time for smallest batch around 15-20ms + // and CPU speed for 64-128 sigverifies around 10-20ms. 64 is a nice + // power-of-two number around that accounting for the fact that the CPU + // may be busy doing other things while being a real fullnode + // TODO: dynamically adjust this crossover + if count < 64 { + return ed25519_verify_cpu(batches); + } + + let (signature_offsets, pubkey_offsets, msg_start_offsets, msg_sizes, sig_lens) = + generate_offsets(batches).unwrap(); + + info!("CUDA ECDSA for {}", batch_size(batches)); + let mut out = Vec::new(); + let mut elems = Vec::new(); + let mut locks = Vec::new(); + let mut rvs = Vec::new(); + + for packets in batches { + locks.push(packets.read().unwrap()); + } + let mut num_packets = 0; + for p in locks { + elems.push(Elems { + elems: p.packets.as_ptr(), + num: p.packets.len() as u32, + }); + let mut v = Vec::new(); + v.resize(p.packets.len(), 0); + rvs.push(v); + num_packets += p.packets.len(); + } + out.resize(signature_offsets.len(), 0); + trace!("Starting verify num packets: {}", num_packets); + trace!("elem len: {}", elems.len() as u32); + trace!("packet sizeof: {}", size_of::() as u32); + trace!("len offset: {}", PACKET_DATA_SIZE as u32); + unsafe { + let res = ed25519_verify_many( + elems.as_ptr(), + elems.len() as u32, + size_of::() as u32, + num_packets as u32, + signature_offsets.len() as u32, + msg_sizes.as_ptr(), + pubkey_offsets.as_ptr(), + signature_offsets.as_ptr(), + msg_start_offsets.as_ptr(), + out.as_mut_ptr(), + ); + if res != 0 { + trace!("RETURN!!!: {}", res); + } + } + trace!("done verify"); + let mut num = 0; + for (vs, sig_vs) in rvs.iter_mut().zip(sig_lens.iter()) { + for (mut v, sig_v) in vs.iter_mut().zip(sig_vs.iter()) { + let mut vout = 1; + for _ in 0..*sig_v { + if 0 == out[num] { + vout = 0; + } + num += 1; + } + *v = vout; + if *v != 0 { + trace!("VERIFIED PACKET!!!!!"); + } + } + } + inc_new_counter_info!("ed25519_verify_gpu", count); + rvs +} + +#[cfg(test)] +pub fn make_packet_from_transaction(tx: Transaction) -> Packet { + use bincode::serialize; + + let tx_bytes = serialize(&tx).unwrap(); + let mut packet = Packet::default(); + packet.meta.size = tx_bytes.len(); + packet.data[..packet.meta.size].copy_from_slice(&tx_bytes); + return packet; +} + +#[cfg(test)] +mod tests { + use bincode::serialize; + use budget_program::BudgetState; + use packet::{Packet, SharedPackets}; + use signature::{Keypair, KeypairUtil}; + use sigverify; + use solana_sdk::hash::Hash; + use solana_sdk::system_instruction::SystemInstruction; + use system_program::SystemProgram; + use system_transaction::{memfind, test_tx}; + use transaction; + use transaction::Transaction; + + #[test] + fn test_layout() { + let tx = test_tx(); + let tx_bytes = serialize(&tx).unwrap(); + let packet = serialize(&tx).unwrap(); + assert_matches!(memfind(&packet, &tx_bytes), Some(sigverify::TX_OFFSET)); + assert_matches!(memfind(&packet, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), None); + } + + #[test] + fn test_get_packet_offsets() { + let tx = test_tx(); + let packet = sigverify::make_packet_from_transaction(tx); + let (sig_len, sig_start, msg_start_offset, pubkey_offset) = + sigverify::get_packet_offsets(&packet, 0); + assert_eq!(sig_len, 1); + assert_eq!(sig_start, 8); + assert_eq!(msg_start_offset, 72); + assert_eq!(pubkey_offset, 80); + } + + fn generate_packet_vec( + packet: &Packet, + num_packets_per_batch: usize, + num_batches: usize, + ) -> Vec { + // generate packet vector + let batches: Vec<_> = (0..num_batches) + .map(|_| { + let packets = SharedPackets::default(); + packets + .write() + .unwrap() + .packets + .resize(0, Default::default()); + for _ in 0..num_packets_per_batch { + packets.write().unwrap().packets.push(packet.clone()); + } + assert_eq!(packets.read().unwrap().packets.len(), num_packets_per_batch); + packets + }).collect(); + assert_eq!(batches.len(), num_batches); + + batches + } + + fn test_verify_n(n: usize, modify_data: bool) { + let tx = test_tx(); + let mut packet = sigverify::make_packet_from_transaction(tx); + + // jumble some data to test failure + if modify_data { + packet.data[20] = packet.data[20].wrapping_add(10); + } + + let batches = generate_packet_vec(&packet, n, 2); + + // verify packets + let ans = sigverify::ed25519_verify(&batches); + + // check result + let ref_ans = if modify_data { 0u8 } else { 1u8 }; + assert_eq!(ans, vec![vec![ref_ans; n], vec![ref_ans; n]]); + } + + #[test] + fn test_verify_zero() { + test_verify_n(0, false); + } + + #[test] + fn test_verify_one() { + test_verify_n(1, false); + } + + #[test] + fn test_verify_seventy_one() { + test_verify_n(71, false); + } + + #[test] + fn test_verify_multi_sig() { + use logger; + logger::setup(); + let keypair0 = Keypair::new(); + let keypair1 = Keypair::new(); + let keypairs = vec![&keypair0, &keypair1]; + let tokens = 5; + let fee = 2; + let last_id = Hash::default(); + + let keys = vec![keypair0.pubkey(), keypair1.pubkey()]; + + let system_instruction = SystemInstruction::Move { tokens }; + + let program_ids = vec![SystemProgram::id(), BudgetState::id()]; + + let instructions = vec![transaction::Instruction::new( + 0, + &system_instruction, + vec![0, 1], + )]; + + let tx = Transaction::new_with_instructions( + &keypairs, + &keys, + last_id, + fee, + program_ids, + instructions, + ); + + let mut packet = sigverify::make_packet_from_transaction(tx); + + let n = 4; + let num_batches = 3; + let batches = generate_packet_vec(&packet, n, num_batches); + + packet.data[40] = packet.data[40].wrapping_add(8); + + batches[0].write().unwrap().packets.push(packet); + + // verify packets + let ans = sigverify::ed25519_verify(&batches); + + // check result + let ref_ans = 1u8; + let mut ref_vec = vec![vec![ref_ans; n]; num_batches]; + ref_vec[0].push(0u8); + assert_eq!(ans, ref_vec); + } + + #[test] + fn test_verify_fail() { + test_verify_n(5, true); + } +} diff --git a/book/sigverify_stage.rs b/book/sigverify_stage.rs new file mode 100644 index 00000000000000..b2c28fe19324fa --- /dev/null +++ b/book/sigverify_stage.rs @@ -0,0 +1,156 @@ +//! The `sigverify_stage` implements the signature verification stage of the TPU. It +//! receives a list of lists of packets and outputs the same list, but tags each +//! top-level list with a list of booleans, telling the next stage whether the +//! signature in that packet is valid. It assumes each packet contains one +//! transaction. All processing is done on the CPU by default and on a GPU +//! if the `cuda` feature is enabled with `--features=cuda`. + +use counter::Counter; + +use log::Level; +use packet::SharedPackets; +use rand::{thread_rng, Rng}; +use result::{Error, Result}; +use service::Service; +use sigverify; +use solana_metrics::{influxdb, submit}; +use solana_sdk::timing; +use std::sync::atomic::AtomicUsize; +use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender}; +use std::sync::{Arc, Mutex}; +use std::thread::{self, spawn, JoinHandle}; +use std::time::Instant; +use streamer::{self, PacketReceiver}; + +pub type VerifiedPackets = Vec<(SharedPackets, Vec)>; + +pub struct SigVerifyStage { + thread_hdls: Vec>, +} + +impl SigVerifyStage { + pub fn new( + packet_receiver: Receiver, + sigverify_disabled: bool, + ) -> (Self, Receiver) { + sigverify::init(); + let (verified_sender, verified_receiver) = channel(); + let thread_hdls = + Self::verifier_services(packet_receiver, verified_sender, sigverify_disabled); + (SigVerifyStage { thread_hdls }, verified_receiver) + } + + fn verify_batch(batch: Vec, sigverify_disabled: bool) -> VerifiedPackets { + let r = if sigverify_disabled { + sigverify::ed25519_verify_disabled(&batch) + } else { + sigverify::ed25519_verify(&batch) + }; + batch.into_iter().zip(r).collect() + } + + fn verifier( + recvr: &Arc>, + sendr: &Arc>>, + sigverify_disabled: bool, + ) -> Result<()> { + let (batch, len, recv_time) = + streamer::recv_batch(&recvr.lock().expect("'recvr' lock in fn verifier"))?; + inc_new_counter_info!("sigverify_stage-entries_received", len); + + let now = Instant::now(); + let batch_len = batch.len(); + let rand_id = thread_rng().gen_range(0, 100); + info!( + "@{:?} verifier: verifying: {} id: {}", + timing::timestamp(), + batch.len(), + rand_id + ); + + let verified_batch = Self::verify_batch(batch, sigverify_disabled); + inc_new_counter_info!( + "sigverify_stage-verified_entries_send", + verified_batch.len() + ); + + if sendr + .lock() + .expect("lock in fn verify_batch in tpu") + .send(verified_batch) + .is_err() + { + return Err(Error::SendError); + } + + let total_time_ms = timing::duration_as_ms(&now.elapsed()); + let total_time_s = timing::duration_as_s(&now.elapsed()); + inc_new_counter_info!( + "sigverify_stage-time_ms", + (total_time_ms + recv_time) as usize + ); + info!( + "@{:?} verifier: done. batches: {} total verify time: {:?} id: {} verified: {} v/s {}", + timing::timestamp(), + batch_len, + total_time_ms, + rand_id, + len, + (len as f32 / total_time_s) + ); + + submit( + influxdb::Point::new("sigverify_stage-total_verify_time") + .add_field("batch_len", influxdb::Value::Integer(batch_len as i64)) + .add_field("len", influxdb::Value::Integer(len as i64)) + .add_field( + "total_time_ms", + influxdb::Value::Integer(total_time_ms as i64), + ).to_owned(), + ); + + Ok(()) + } + + fn verifier_service( + packet_receiver: Arc>, + verified_sender: Arc>>, + sigverify_disabled: bool, + ) -> JoinHandle<()> { + spawn(move || loop { + if let Err(e) = Self::verifier(&packet_receiver, &verified_sender, sigverify_disabled) { + match e { + Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => break, + Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), + Error::SendError => { + break; + } + _ => error!("{:?}", e), + } + } + }) + } + + fn verifier_services( + packet_receiver: PacketReceiver, + verified_sender: Sender, + sigverify_disabled: bool, + ) -> Vec> { + let sender = Arc::new(Mutex::new(verified_sender)); + let receiver = Arc::new(Mutex::new(packet_receiver)); + (0..4) + .map(|_| Self::verifier_service(receiver.clone(), sender.clone(), sigverify_disabled)) + .collect() + } +} + +impl Service for SigVerifyStage { + type JoinReturnType = (); + + fn join(self) -> thread::Result<()> { + for thread_hdl in self.thread_hdls { + thread_hdl.join()?; + } + Ok(()) + } +} diff --git a/book/storage.html b/book/storage.html new file mode 100644 index 00000000000000..c42fcff0269929 --- /dev/null +++ b/book/storage.html @@ -0,0 +1,296 @@ + + + + + + Storage - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

Storage

+

Background

+

At full capacity on a 1gbps network Solana would generate 4 petabytes of data +per year. If each fullnode was required to store the full ledger, the cost of +storage would discourage fullnode participation, thus centralizing the network +around those that could afford it. Solana aims to keep the cost of a fullnode +below $5,000 USD to maximize participation. To achieve that, the network needs +to minimize redundant storage while at the same time ensuring the validity and +availability of each copy.

+

To trust storage of ledger segments, Solana has replicators periodically +submit proofs to the network that the data was replicated. Each proof is called +a Proof of Replication. The basic idea of it is to encrypt a dataset with a +public symmetric key and then hash the encrypted dataset. Solana uses CBC +encryption. +To prevent a malicious replicator from deleting the data as soon as it's +hashed, a replicator is required hash random segments of the dataset. +Alternatively, Solana could require hashing the reverse of the encrypted data, +but random sampling is sufficient and much faster. Either solution ensures +that all the data is present during the generation of the proof and also +requires the validator to have the entirety of the encrypted data present for +verification of every proof of every identity. The space required to validate +is:

+

number_of_proofs * data_size

+

Optimization with PoH

+

Solana is not the only distribute systems project using Proof of Replication, +but it might be the most efficient implementation because of its ability to +synchronize nodes with its Proof of History. With PoH, Solana is able to record +a hash of the PoRep samples in the ledger. Thus the blocks stay in the exact +same order for every PoRep and verification can stream the data and verify all +the proofs in a single batch. This way Solana can verify multiple proofs +concurrently, each one on its own GPU core. With the current generation of +graphics cards our network can support up to 14,000 replication identities or +symmetric keys. The total space required for verification is:

+

2 CBC_blocks * number_of_identities

+

with core count of equal to (Number of Identities). A CBC block is expected to +be 1MB in size.

+

Network

+

Validators for PoRep are the same validators that are verifying transactions. +They have some stake that they have put up as collateral that ensures that +their work is honest. If you can prove that a validator verified a fake PoRep, +then the validator's stake is slashed.

+

Replicators are specialized light clients. They download a part of the ledger +and store it and provide proofs of storing the ledger. For each verified proof, +replicators are rewarded tokens from the mining pool.

+

Constraints

+

Solana's PoRep protocol instroduces the following constraints:

+
    +
  • At most 14,000 replication identities can be used, because that is how many GPU +cores are currently available to a computer costing under $5,000 USD.
  • +
  • Verification requires generating the CBC blocks. That requires space of 2 +blocks per identity, and 1 GPU core per identity for the same dataset. As +many identities at once are batched with as many proofs for those identities +verified concurrently for the same dataset.
  • +
+

Validation and Replication Protocol

+
    +
  1. The network sets a replication target number, let's say 1k. 1k PoRep +identities are created from signatures of a PoH hash. They are tied to a +specific PoH hash. It doesn't matter who creates them, or it could simply be +the last 1k validation signatures we saw for the ledger at that count. This is +maybe just the initial batch of identities, because we want to stagger identity +rotation.
  2. +
  3. Any client can use any of these identities to create PoRep proofs. +Replicator identities are the CBC encryption keys.
  4. +
  5. Periodically at a specific PoH count, a replicator that wants to create +PoRep proofs signs the PoH hash at that count. That signature is the seed +used to pick the block and identity to replicate. A block is 1TB of ledger.
  6. +
  7. Periodically at a specific PoH count, a replicator submits PoRep proofs for +their selected block. A signature of the PoH hash at that count is the seed +used to sample the 1TB encrypted block, and hash it. This is done faster than +it takes to encrypt the 1TB block with the original identity.
  8. +
  9. Replicators must submit some number of fake proofs, which they can prove to +be fake by providing the seed for the hash result.
  10. +
  11. Periodically at a specific PoH count, validators sign the hash and use the +signature to select the 1TB block that they need to validate. They batch all +the identities and proofs and submit approval for all the verified ones.
  12. +
  13. After #6, replicator client submit the proofs of fake proofs.
  14. +
+

For any random seed, Solana requires everyone to use a signature that is +derived from a PoH hash. Every node uses the same count so that the same PoH +hash is signed by every participant. The signatures are then each +cryptographically tied to the keypair, which prevents a leader from grinding on +the resulting value for more than 1 identity.

+

Key rotation is staggered. Once going, the next identity is generated by +hashing itself with a PoH hash.

+

Since there are many more client identities then encryption identities, the +reward is split amont multiple clients to prevent Sybil attacks from generating +many clients to acquire the same block of data. To remain BFT, the network +needs to avoid a single human entity from storing all the replications of a +single chunk of the ledger.

+

Solana's solution to this is to require clients to continue using the same +identity. If the first round is used to acquire the same block for many client +identities, the second round for the same client identities will require a +redistribution of the signatures, and therefore PoRep identities and blocks. +Thus to get a reward for storage, clients are not rewarded for storage of the +first block. The network rewards long-lived client identities more than new +ones.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/storage_program.rs b/book/storage_program.rs new file mode 100644 index 00000000000000..bced49441a89fe --- /dev/null +++ b/book/storage_program.rs @@ -0,0 +1,75 @@ +//! storage program +//! Receive mining proofs from miners, validate the answers +//! and give reward for good proofs. + +use bincode::deserialize; +use solana_sdk::account::Account; +use solana_sdk::hash::Hash; +use solana_sdk::pubkey::Pubkey; +use transaction::Transaction; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum StorageProgram { + SubmitMiningProof { sha_state: Hash }, +} + +pub enum StorageError { + InvalidUserData, +} + +const STORAGE_PROGRAM_ID: [u8; 32] = [ + 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +]; + +impl StorageProgram { + pub fn check_id(program_id: &Pubkey) -> bool { + program_id.as_ref() == STORAGE_PROGRAM_ID + } + + pub fn id() -> Pubkey { + Pubkey::new(&STORAGE_PROGRAM_ID) + } + + pub fn get_balance(account: &Account) -> u64 { + account.tokens + } + + pub fn process_transaction( + tx: &Transaction, + pix: usize, + _accounts: &mut [&mut Account], + ) -> Result<(), StorageError> { + if let Ok(syscall) = deserialize(tx.userdata(pix)) { + match syscall { + StorageProgram::SubmitMiningProof { sha_state } => { + info!("Mining proof submitted with state {:?}", sha_state); + return Ok(()); + } + } + } else { + return Err(StorageError::InvalidUserData); + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use signature::Keypair; + use signature::KeypairUtil; + + #[test] + fn test_storage_tx() { + let keypair = Keypair::new(); + let tx = Transaction::new( + &keypair, + &[], + StorageProgram::id(), + &(), + Default::default(), + 0, + ); + assert!(StorageProgram::process_transaction(&tx, 0, &mut []).is_err()); + } +} diff --git a/book/storage_stage.rs b/book/storage_stage.rs new file mode 100644 index 00000000000000..5199c5635181e6 --- /dev/null +++ b/book/storage_stage.rs @@ -0,0 +1,456 @@ +// A stage that handles generating the keys used to encrypt the ledger and sample it +// for storage mining. Replicators submit storage proofs, validator then bundles them +// to submit its proof for mining to be rewarded. + +#[cfg(all(feature = "chacha", feature = "cuda"))] +use chacha_cuda::chacha_cbc_encrypt_file_many_keys; +use entry::EntryReceiver; +use rand::{ChaChaRng, Rng, SeedableRng}; +use result::{Error, Result}; +use service::Service; +use signature::Keypair; +use signature::Signature; +use solana_sdk::hash::Hash; +use solana_sdk::pubkey::Pubkey; +use std::mem::size_of; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc::RecvTimeoutError; +use std::sync::{Arc, RwLock}; +use std::thread::{self, Builder, JoinHandle}; +use std::time::Duration; +use vote_program::VoteProgram; + +// Block of hash answers to validate against +// Vec of [ledger blocks] x [keys] +type StorageResults = Vec; +type StorageKeys = Vec; + +#[derive(Default)] +pub struct StorageState { + storage_results: Arc>, + storage_keys: Arc>, +} + +pub struct StorageStage { + t_storage_mining_verifier: JoinHandle<()>, +} + +macro_rules! cross_boundary { + ($start:expr, $len:expr, $boundary:expr) => { + (($start + $len) & !($boundary - 1)) > $start & !($boundary - 1) + }; +} + +const NUM_HASHES_FOR_STORAGE_ROTATE: u64 = 1024; +// TODO: some way to dynamically size NUM_IDENTITIES +const NUM_IDENTITIES: usize = 1024; +const NUM_SAMPLES: usize = 4; +pub const ENTRIES_PER_SLICE: u64 = 16; +const KEY_SIZE: usize = 64; + +fn get_identity_index_from_pubkey(key: &Pubkey) -> usize { + let rkey = key.as_ref(); + let mut res: usize = (rkey[0] as usize) + | ((rkey[1] as usize) << 8) + | ((rkey[2] as usize) << 16) + | ((rkey[3] as usize) << 24); + res &= NUM_IDENTITIES - 1; + res +} + +impl StorageState { + pub fn new() -> Self { + let storage_keys = Arc::new(RwLock::new(vec![0u8; KEY_SIZE * NUM_IDENTITIES])); + let storage_results = Arc::new(RwLock::new(vec![Hash::default(); NUM_IDENTITIES])); + + StorageState { + storage_keys, + storage_results, + } + } + + pub fn get_mining_key(&self, key: &Pubkey) -> Vec { + let idx = get_identity_index_from_pubkey(key); + self.storage_keys.read().unwrap()[idx..idx + KEY_SIZE].to_vec() + } + + pub fn get_mining_result(&self, key: &Pubkey) -> Hash { + let idx = get_identity_index_from_pubkey(key); + self.storage_results.read().unwrap()[idx] + } +} + +impl StorageStage { + pub fn new( + storage_state: &StorageState, + storage_entry_receiver: EntryReceiver, + ledger_path: Option<&str>, + keypair: Arc, + exit: Arc, + entry_height: u64, + ) -> Self { + let storage_keys_ = storage_state.storage_keys.clone(); + let storage_results_ = storage_state.storage_results.clone(); + let ledger_path = ledger_path.map(String::from); + let t_storage_mining_verifier = Builder::new() + .name("solana-storage-mining-verify-stage".to_string()) + .spawn(move || { + let exit = exit.clone(); + let mut poh_height = 0; + let mut current_key = 0; + let mut entry_height = entry_height; + loop { + if let Some(ref ledger_path_str) = ledger_path { + if let Err(e) = Self::process_entries( + &keypair, + &storage_keys_, + &storage_results_, + &storage_entry_receiver, + ledger_path_str, + &mut poh_height, + &mut entry_height, + &mut current_key, + ) { + match e { + Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => break, + Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), + _ => info!("Error from process_entries: {:?}", e), + } + } + } + if exit.load(Ordering::Relaxed) { + break; + } + } + }).unwrap(); + + StorageStage { + t_storage_mining_verifier, + } + } + + pub fn process_entry_crossing( + _storage_results: &Arc>, + _storage_keys: &Arc>, + keypair: &Arc, + _ledger_path: &str, + entry_id: Hash, + entry_height: u64, + ) -> Result<()> { + let mut seed = [0u8; 32]; + let signature = keypair.sign(&entry_id.as_ref()); + + seed.copy_from_slice(&signature.as_ref()[..32]); + + let mut rng = ChaChaRng::from_seed(seed); + + // Regenerate the answers + let num_slices = (entry_height / ENTRIES_PER_SLICE) as usize; + if num_slices == 0 { + info!("Ledger has 0 slices!"); + return Ok(()); + } + // TODO: what if the validator does not have this slice + let slice = signature.as_ref()[0] as usize % num_slices; + + debug!( + "storage verifying: slice: {} identities: {}", + slice, NUM_IDENTITIES, + ); + + let mut samples = vec![]; + for _ in 0..NUM_SAMPLES { + samples.push(rng.gen_range(0, 10)); + } + debug!("generated samples: {:?}", samples); + // TODO: cuda required to generate the reference values + // but if it is missing, then we need to take care not to + // process storage mining results. + #[cfg(all(feature = "chacha", feature = "cuda"))] + { + let mut storage_results = _storage_results.write().unwrap(); + + // Lock the keys, since this is the IV memory, + // it will be updated in-place by the encryption. + // Should be overwritten by the vote signatures which replace the + // key values by the time it runs again. + let mut storage_keys = _storage_keys.write().unwrap(); + + match chacha_cbc_encrypt_file_many_keys( + _ledger_path, + slice as u64, + &mut storage_keys, + &samples, + ) { + Ok(hashes) => { + debug!("Success! encrypted ledger slice: {}", slice); + storage_results.copy_from_slice(&hashes); + } + Err(e) => { + info!("error encrypting file: {:?}", e); + Err(e)?; + } + } + } + // TODO: bundle up mining submissions from replicators + // and submit them in a tx to the leader to get reward. + Ok(()) + } + + pub fn process_entries( + keypair: &Arc, + storage_keys: &Arc>, + storage_results: &Arc>, + entry_receiver: &EntryReceiver, + ledger_path: &str, + poh_height: &mut u64, + entry_height: &mut u64, + current_key_idx: &mut usize, + ) -> Result<()> { + let timeout = Duration::new(1, 0); + let entries = entry_receiver.recv_timeout(timeout)?; + + for entry in entries { + // Go through the transactions, find votes, and use them to update + // the storage_keys with their signatures. + for tx in entry.transactions { + for program_id in tx.program_ids { + if VoteProgram::check_id(&program_id) { + debug!( + "generating storage_keys from votes current_key_idx: {}", + *current_key_idx + ); + let mut storage_keys = storage_keys.write().unwrap(); + storage_keys[*current_key_idx..*current_key_idx + size_of::()] + .copy_from_slice(tx.signatures[0].as_ref()); + *current_key_idx += size_of::(); + *current_key_idx %= storage_keys.len(); + } + } + } + if cross_boundary!(*poh_height, entry.num_hashes, NUM_HASHES_FOR_STORAGE_ROTATE) { + info!( + "crosses sending at poh_height: {} entry_height: {}! hashes: {}", + *poh_height, entry_height, entry.num_hashes + ); + Self::process_entry_crossing( + &storage_results, + &storage_keys, + &keypair, + &ledger_path, + entry.id, + *entry_height, + )?; + } + *entry_height += 1; + *poh_height += entry.num_hashes; + } + Ok(()) + } +} + +impl Service for StorageStage { + type JoinReturnType = (); + + fn join(self) -> thread::Result<()> { + self.t_storage_mining_verifier.join() + } +} + +#[cfg(test)] +mod tests { + use entry::Entry; + use ledger::make_tiny_test_entries; + use ledger::{create_tmp_sample_ledger, LedgerWriter}; + use logger; + use service::Service; + use signature::{Keypair, KeypairUtil}; + use solana_sdk::hash::Hash; + use std::cmp::{max, min}; + use std::fs::remove_dir_all; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::mpsc::channel; + use std::sync::Arc; + use std::thread::sleep; + use std::time::Duration; + use storage_stage::StorageState; + use storage_stage::NUM_IDENTITIES; + use storage_stage::{get_identity_index_from_pubkey, StorageStage}; + use transaction::Transaction; + use vote_program::Vote; + use vote_transaction::VoteTransaction; + + #[test] + fn test_storage_stage_none_ledger() { + let keypair = Arc::new(Keypair::new()); + let exit = Arc::new(AtomicBool::new(false)); + + let (_storage_entry_sender, storage_entry_receiver) = channel(); + let storage_state = StorageState::new(); + let storage_stage = StorageStage::new( + &storage_state, + storage_entry_receiver, + None, + keypair, + exit.clone(), + 0, + ); + exit.store(true, Ordering::Relaxed); + storage_stage.join().unwrap(); + } + + #[test] + fn test_storage_stage_process_entries() { + logger::setup(); + let keypair = Arc::new(Keypair::new()); + let exit = Arc::new(AtomicBool::new(false)); + + let (_mint, ledger_path, _genesis) = create_tmp_sample_ledger( + "storage_stage_process_entries", + 1000, + 1, + Keypair::new().pubkey(), + 1, + ); + + let entries = make_tiny_test_entries(128); + { + let mut writer = LedgerWriter::open(&ledger_path, true).unwrap(); + writer.write_entries(&entries.clone()).unwrap(); + // drops writer, flushes buffers + } + + let (storage_entry_sender, storage_entry_receiver) = channel(); + let storage_state = StorageState::new(); + let storage_stage = StorageStage::new( + &storage_state, + storage_entry_receiver, + Some(&ledger_path), + keypair, + exit.clone(), + 0, + ); + storage_entry_sender.send(entries.clone()).unwrap(); + + let keypair = Keypair::new(); + let mut result = storage_state.get_mining_result(&keypair.pubkey()); + assert_eq!(result, Hash::default()); + + for _ in 0..9 { + storage_entry_sender.send(entries.clone()).unwrap(); + } + for _ in 0..5 { + result = storage_state.get_mining_result(&keypair.pubkey()); + if result != Hash::default() { + info!("found result = {:?} sleeping..", result); + break; + } + info!("result = {:?} sleeping..", result); + sleep(Duration::new(1, 0)); + } + + info!("joining..?"); + exit.store(true, Ordering::Relaxed); + storage_stage.join().unwrap(); + + #[cfg(not(all(feature = "cuda", feature = "chacha")))] + assert_eq!(result, Hash::default()); + + #[cfg(all(feature = "cuda", feature = "chacha"))] + assert_ne!(result, Hash::default()); + + remove_dir_all(ledger_path).unwrap(); + } + + #[test] + fn test_storage_stage_process_vote_entries() { + logger::setup(); + let keypair = Arc::new(Keypair::new()); + let exit = Arc::new(AtomicBool::new(false)); + + let (_mint, ledger_path, _genesis) = create_tmp_sample_ledger( + "storage_stage_process_entries", + 1000, + 1, + Keypair::new().pubkey(), + 1, + ); + + let entries = make_tiny_test_entries(128); + { + let mut writer = LedgerWriter::open(&ledger_path, true).unwrap(); + writer.write_entries(&entries.clone()).unwrap(); + // drops writer, flushes buffers + } + + let (storage_entry_sender, storage_entry_receiver) = channel(); + let storage_state = StorageState::new(); + let storage_stage = StorageStage::new( + &storage_state, + storage_entry_receiver, + Some(&ledger_path), + keypair, + exit.clone(), + 0, + ); + storage_entry_sender.send(entries.clone()).unwrap(); + + let mut reference_keys; + { + let keys = storage_state.storage_keys.read().unwrap(); + reference_keys = vec![0; keys.len()]; + reference_keys.copy_from_slice(&keys); + } + let mut vote_txs: Vec = Vec::new(); + let vote = Vote { + tick_height: 123456, + }; + let keypair = Keypair::new(); + let vote_tx = VoteTransaction::vote_new(&keypair, vote, Hash::default(), 1); + vote_txs.push(vote_tx); + let vote_entries = vec![Entry::new(&Hash::default(), 1, vote_txs)]; + storage_entry_sender.send(vote_entries).unwrap(); + + for _ in 0..5 { + { + let keys = storage_state.storage_keys.read().unwrap(); + if keys[..] != *reference_keys.as_slice() { + break; + } + } + + sleep(Duration::new(1, 0)); + } + + debug!("joining..?"); + exit.store(true, Ordering::Relaxed); + storage_stage.join().unwrap(); + + { + let keys = storage_state.storage_keys.read().unwrap(); + assert_ne!(keys[..], *reference_keys); + } + + remove_dir_all(ledger_path).unwrap(); + } + + #[test] + fn test_pubkey_distribution() { + logger::setup(); + // See that pub keys have an even-ish distribution.. + let mut hist = [0; NUM_IDENTITIES]; + for _ in 0..(128 * 256) { + let keypair = Keypair::new(); + let ix = get_identity_index_from_pubkey(&keypair.pubkey()); + hist[ix] += 1; + } + let mut hist_max = 0; + let mut hist_min = NUM_IDENTITIES; + for x in hist.iter() { + hist_max = max(*x, hist_max); + hist_min = min(*x, hist_min); + } + info!("min: {} max: {}", hist_min, hist_max); + assert_ne!(hist_min, 0); + } +} diff --git a/book/storage_transaction.rs b/book/storage_transaction.rs new file mode 100644 index 00000000000000..70682c04f25068 --- /dev/null +++ b/book/storage_transaction.rs @@ -0,0 +1,22 @@ +use signature::{Keypair, KeypairUtil}; +use solana_sdk::hash::Hash; +use storage_program::StorageProgram; +use transaction::Transaction; + +pub trait StorageTransaction { + fn storage_new_mining_proof(from_keypair: &Keypair, sha_state: Hash, last_id: Hash) -> Self; +} + +impl StorageTransaction for Transaction { + fn storage_new_mining_proof(from_keypair: &Keypair, sha_state: Hash, last_id: Hash) -> Self { + let program = StorageProgram::SubmitMiningProof { sha_state }; + Transaction::new( + from_keypair, + &[from_keypair.pubkey()], + StorageProgram::id(), + &program, + last_id, + 0, + ) + } +} diff --git a/book/store_ledger_stage.rs b/book/store_ledger_stage.rs new file mode 100644 index 00000000000000..4c618e0cf2f424 --- /dev/null +++ b/book/store_ledger_stage.rs @@ -0,0 +1,72 @@ +//! The `store_ledger` stores the ledger from received entries for storage nodes + +use counter::Counter; +use entry::EntryReceiver; +use ledger::LedgerWriter; +use log::Level; +use result::{Error, Result}; +use service::Service; +use std::sync::atomic::AtomicUsize; +use std::sync::mpsc::RecvTimeoutError; +use std::thread::{self, Builder, JoinHandle}; +use std::time::Duration; + +pub struct StoreLedgerStage { + thread_hdls: Vec>, +} + +impl StoreLedgerStage { + /// Process entries, already in order + fn store_requests( + window_receiver: &EntryReceiver, + ledger_writer: Option<&mut LedgerWriter>, + ) -> Result<()> { + let timer = Duration::new(1, 0); + let mut entries = window_receiver.recv_timeout(timer)?; + while let Ok(mut more) = window_receiver.try_recv() { + entries.append(&mut more); + } + + inc_new_counter_info!( + "store-transactions", + entries.iter().map(|x| x.transactions.len()).sum() + ); + + if let Some(ledger_writer) = ledger_writer { + ledger_writer.write_entries(&entries)?; + } + + Ok(()) + } + + pub fn new(window_receiver: EntryReceiver, ledger_path: Option<&str>) -> Self { + let mut ledger_writer = ledger_path.map(|p| LedgerWriter::open(p, true).unwrap()); + + let t_store_requests = Builder::new() + .name("solana-store-ledger-stage".to_string()) + .spawn(move || loop { + if let Err(e) = Self::store_requests(&window_receiver, ledger_writer.as_mut()) { + match e { + Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => break, + Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), + _ => error!("{:?}", e), + } + } + }).unwrap(); + + let thread_hdls = vec![t_store_requests]; + + StoreLedgerStage { thread_hdls } + } +} + +impl Service for StoreLedgerStage { + type JoinReturnType = (); + + fn join(self) -> thread::Result<()> { + for thread_hdl in self.thread_hdls { + thread_hdl.join()?; + } + Ok(()) + } +} diff --git a/book/streamer.rs b/book/streamer.rs new file mode 100644 index 00000000000000..74b0e1d8ad385a --- /dev/null +++ b/book/streamer.rs @@ -0,0 +1,200 @@ +//! The `streamer` module defines a set of services for efficiently pulling data from UDP sockets. +//! + +use packet::{Blob, SharedBlobs, SharedPackets}; +use result::{Error, Result}; +use solana_metrics::{influxdb, submit}; +use solana_sdk::timing::duration_as_ms; +use std::net::UdpSocket; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc::{Receiver, RecvTimeoutError, Sender}; +use std::sync::Arc; +use std::thread::{Builder, JoinHandle}; +use std::time::{Duration, Instant}; + +pub type PacketReceiver = Receiver; +pub type PacketSender = Sender; +pub type BlobSender = Sender; +pub type BlobReceiver = Receiver; + +fn recv_loop( + sock: &UdpSocket, + exit: &Arc, + channel: &PacketSender, + channel_tag: &'static str, +) -> Result<()> { + loop { + let msgs = SharedPackets::default(); + loop { + // Check for exit signal, even if socket is busy + // (for instance the leader trasaction socket) + if exit.load(Ordering::Relaxed) { + return Ok(()); + } + if msgs.write().unwrap().recv_from(sock).is_ok() { + let len = msgs.read().unwrap().packets.len(); + submit( + influxdb::Point::new(channel_tag) + .add_field("count", influxdb::Value::Integer(len as i64)) + .to_owned(), + ); + channel.send(msgs)?; + break; + } + } + } +} + +pub fn receiver( + sock: Arc, + exit: Arc, + packet_sender: PacketSender, + sender_tag: &'static str, +) -> JoinHandle<()> { + let res = sock.set_read_timeout(Some(Duration::new(1, 0))); + if res.is_err() { + panic!("streamer::receiver set_read_timeout error"); + } + Builder::new() + .name("solana-receiver".to_string()) + .spawn(move || { + let _ = recv_loop(&sock, &exit, &packet_sender, sender_tag); + () + }).unwrap() +} + +fn recv_send(sock: &UdpSocket, r: &BlobReceiver) -> Result<()> { + let timer = Duration::new(1, 0); + let msgs = r.recv_timeout(timer)?; + Blob::send_to(sock, msgs)?; + Ok(()) +} + +pub fn recv_batch(recvr: &PacketReceiver) -> Result<(Vec, usize, u64)> { + let timer = Duration::new(1, 0); + let msgs = recvr.recv_timeout(timer)?; + let recv_start = Instant::now(); + trace!("got msgs"); + let mut len = msgs.read().unwrap().packets.len(); + let mut batch = vec![msgs]; + while let Ok(more) = recvr.try_recv() { + trace!("got more msgs"); + len += more.read().unwrap().packets.len(); + batch.push(more); + + if len > 100_000 { + break; + } + } + trace!("batch len {}", batch.len()); + Ok((batch, len, duration_as_ms(&recv_start.elapsed()))) +} + +pub fn responder(name: &'static str, sock: Arc, r: BlobReceiver) -> JoinHandle<()> { + Builder::new() + .name(format!("solana-responder-{}", name)) + .spawn(move || loop { + if let Err(e) = recv_send(&sock, &r) { + match e { + Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => break, + Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), + _ => warn!("{} responder error: {:?}", name, e), + } + } + }).unwrap() +} + +//TODO, we would need to stick block authentication before we create the +//window. +fn recv_blobs(sock: &UdpSocket, s: &BlobSender) -> Result<()> { + trace!("recv_blobs: receiving on {}", sock.local_addr().unwrap()); + let dq = Blob::recv_from(sock)?; + if !dq.is_empty() { + s.send(dq)?; + } + Ok(()) +} + +pub fn blob_receiver(sock: Arc, exit: Arc, s: BlobSender) -> JoinHandle<()> { + //DOCUMENTED SIDE-EFFECT + //1 second timeout on socket read + let timer = Duration::new(1, 0); + sock.set_read_timeout(Some(timer)) + .expect("set socket timeout"); + Builder::new() + .name("solana-blob_receiver".to_string()) + .spawn(move || loop { + if exit.load(Ordering::Relaxed) { + break; + } + let _ = recv_blobs(&sock, &s); + }).unwrap() +} + +#[cfg(test)] +mod test { + use packet::{Blob, Packet, Packets, SharedBlob, PACKET_DATA_SIZE}; + use std::io; + use std::io::Write; + use std::net::UdpSocket; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::mpsc::channel; + use std::sync::Arc; + use std::time::Duration; + use streamer::PacketReceiver; + use streamer::{receiver, responder}; + + fn get_msgs(r: PacketReceiver, num: &mut usize) { + for _t in 0..5 { + let timer = Duration::new(1, 0); + match r.recv_timeout(timer) { + Ok(m) => *num += m.read().unwrap().packets.len(), + _ => info!("get_msgs error"), + } + if *num == 10 { + break; + } + } + } + #[test] + pub fn streamer_debug() { + write!(io::sink(), "{:?}", Packet::default()).unwrap(); + write!(io::sink(), "{:?}", Packets::default()).unwrap(); + write!(io::sink(), "{:?}", Blob::default()).unwrap(); + } + #[test] + pub fn streamer_send_test() { + let read = UdpSocket::bind("127.0.0.1:0").expect("bind"); + read.set_read_timeout(Some(Duration::new(1, 0))).unwrap(); + + let addr = read.local_addr().unwrap(); + let send = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let exit = Arc::new(AtomicBool::new(false)); + let (s_reader, r_reader) = channel(); + let t_receiver = receiver(Arc::new(read), exit.clone(), s_reader, "streamer-test"); + let t_responder = { + let (s_responder, r_responder) = channel(); + let t_responder = responder("streamer_send_test", Arc::new(send), r_responder); + let mut msgs = Vec::new(); + for i in 0..10 { + let mut b = SharedBlob::default(); + { + let mut w = b.write().unwrap(); + w.data[0] = i as u8; + w.meta.size = PACKET_DATA_SIZE; + w.meta.set_addr(&addr); + } + msgs.push(b); + } + s_responder.send(msgs).expect("send"); + t_responder + }; + + let mut num = 0; + get_msgs(r_reader, &mut num); + assert_eq!(num, 10); + exit.store(true, Ordering::Relaxed); + t_receiver.join().expect("join"); + t_responder.join().expect("join"); + } +} diff --git a/book/synchronization.html b/book/synchronization.html new file mode 100644 index 00000000000000..24f4918763437f --- /dev/null +++ b/book/synchronization.html @@ -0,0 +1,235 @@ + + + + + + Synchronization - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

Synchronization

+

It's possible for a centralized database to process 710,000 transactions per +second on a standard gigabit network if the transactions are, on average, no +more than 176 bytes. A centralized database can also replicate itself and +maintain high availability without significantly compromising that transaction +rate using the distributed system technique known as Optimistic Concurrency +Control [H.T.Kung, J.T.Robinson +(1981)]. At +Solana, we're demonstrating that these same theoretical limits apply just as +well to blockchain on an adversarial network. The key ingredient? Finding a way +to share time when nodes can't trust one-another. Once nodes can trust time, +suddenly ~40 years of distributed systems research becomes applicable to +blockchain!

+
+

Perhaps the most striking difference between algorithms obtained by our +method and ones based upon timeout is that using timeout produces a +traditional distributed algorithm in which the processes operate +asynchronously, while our method produces a globally synchronous one in which +every process does the same thing at (approximately) the same time. Our +method seems to contradict the whole purpose of distributed processing, which +is to permit different processes to operate independently and perform +different functions. However, if a distributed system is really a single +system, then the processes must be synchronized in some way. Conceptually, +the easiest way to synchronize processes is to get them all to do the same +thing at the same time. Therefore, our method is used to implement a kernel +that performs the necessary synchronization--for example, making sure that +two different processes do not try to modify a file at the same time. +Processes might spend only a small fraction of their time executing the +synchronizing kernel; the rest of the time, they can operate +independently--e.g., accessing different files. This is an approach we have +advocated even when fault-tolerance is not required. The method's basic +simplicity makes it easier to understand the precise properties of a system, +which is crucial if one is to know just how fault-tolerant the system is. +[L.Lamport +(1984)]

+
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/system_program.rs b/book/system_program.rs new file mode 100644 index 00000000000000..ce9698a1f7af6f --- /dev/null +++ b/book/system_program.rs @@ -0,0 +1,306 @@ +//! system program + +use bincode::deserialize; +use solana_sdk::account::Account; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::system_instruction::SystemInstruction; +use std; +use transaction::Transaction; + +#[derive(Debug)] +pub enum Error { + InvalidArgument, + AssignOfUnownedAccount, + AccountNotFinalized, + ResultWithNegativeTokens(u8), +} +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "error") + } +} +impl std::error::Error for Error {} + +pub type Result = std::result::Result; + +pub struct SystemProgram {} + +pub const SYSTEM_PROGRAM_ID: [u8; 32] = [0u8; 32]; + +impl SystemProgram { + pub fn check_id(program_id: &Pubkey) -> bool { + program_id.as_ref() == SYSTEM_PROGRAM_ID + } + + pub fn id() -> Pubkey { + Pubkey::new(&SYSTEM_PROGRAM_ID) + } + pub fn get_balance(account: &Account) -> u64 { + account.tokens + } + pub fn process_transaction( + tx: &Transaction, + pix: usize, + accounts: &mut [&mut Account], + ) -> Result<()> { + if let Ok(syscall) = deserialize(tx.userdata(pix)) { + trace!("process_transaction: {:?}", syscall); + match syscall { + SystemInstruction::CreateAccount { + tokens, + space, + program_id, + } => { + if !Self::check_id(&accounts[0].owner) { + info!("Invalid account[0] owner"); + Err(Error::InvalidArgument)?; + } + + if space > 0 + && (!accounts[1].userdata.is_empty() || !Self::check_id(&accounts[1].owner)) + { + info!("Invalid account[1]"); + Err(Error::InvalidArgument)?; + } + if tokens > accounts[0].tokens { + info!("Insufficient tokens in account[0]"); + Err(Error::ResultWithNegativeTokens(pix as u8))?; + } + accounts[0].tokens -= tokens; + accounts[1].tokens += tokens; + accounts[1].owner = program_id; + accounts[1].userdata = vec![0; space as usize]; + accounts[1].executable = false; + accounts[1].loader = Pubkey::default(); + } + SystemInstruction::Assign { program_id } => { + if !Self::check_id(&accounts[0].owner) { + Err(Error::AssignOfUnownedAccount)?; + } + accounts[0].owner = program_id; + } + SystemInstruction::Move { tokens } => { + //bank should be verifying correctness + if tokens > accounts[0].tokens { + info!("Insufficient tokens in account[0]"); + Err(Error::ResultWithNegativeTokens(pix as u8))?; + } + accounts[0].tokens -= tokens; + accounts[1].tokens += tokens; + } + SystemInstruction::Spawn => { + if !accounts[0].executable || accounts[0].loader != Pubkey::default() { + Err(Error::AccountNotFinalized)?; + } + accounts[0].loader = accounts[0].owner; + accounts[0].owner = tx.account_keys[0]; + } + } + Ok(()) + } else { + info!("Invalid transaction userdata: {:?}", tx.userdata(pix)); + Err(Error::InvalidArgument) + } + } +} +#[cfg(test)] +mod test { + use super::*; + use signature::{Keypair, KeypairUtil}; + use solana_sdk::account::Account; + use solana_sdk::hash::Hash; + use solana_sdk::pubkey::Pubkey; + use system_program::SystemProgram; + use system_transaction::SystemTransaction; + use transaction::Transaction; + + fn process_transaction(tx: &Transaction, accounts: &mut [Account]) -> Result<()> { + let mut refs: Vec<&mut Account> = accounts.iter_mut().collect(); + SystemProgram::process_transaction(&tx, 0, &mut refs[..]) + } + + #[test] + fn test_create_noop() { + let from = Keypair::new(); + let to = Keypair::new(); + let mut accounts = vec![Account::default(), Account::default()]; + let tx = Transaction::system_new(&from, to.pubkey(), 0, Hash::default()); + process_transaction(&tx, &mut accounts).unwrap(); + assert_eq!(accounts[0].tokens, 0); + assert_eq!(accounts[1].tokens, 0); + } + #[test] + fn test_create_spend() { + let from = Keypair::new(); + let to = Keypair::new(); + let mut accounts = vec![Account::default(), Account::default()]; + accounts[0].tokens = 1; + let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default()); + process_transaction(&tx, &mut accounts).unwrap(); + assert_eq!(accounts[0].tokens, 0); + assert_eq!(accounts[1].tokens, 1); + } + + #[test] + fn test_create_spend_wrong_source() { + let from = Keypair::new(); + let to = Keypair::new(); + let mut accounts = vec![Account::default(), Account::default()]; + accounts[0].tokens = 1; + accounts[0].owner = from.pubkey(); + let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default()); + if let Ok(()) = process_transaction(&tx, &mut accounts) { + panic!("Account not owned by SystemProgram"); + } + assert_eq!(accounts[0].tokens, 1); + assert_eq!(accounts[1].tokens, 0); + } + #[test] + fn test_create_assign_and_allocate() { + let from = Keypair::new(); + let to = Keypair::new(); + let mut accounts = vec![Account::default(), Account::default()]; + let tx = + Transaction::system_create(&from, to.pubkey(), Hash::default(), 0, 1, to.pubkey(), 0); + process_transaction(&tx, &mut accounts).unwrap(); + assert!(accounts[0].userdata.is_empty()); + assert_eq!(accounts[1].userdata.len(), 1); + assert_eq!(accounts[1].owner, to.pubkey()); + } + #[test] + fn test_create_allocate_wrong_dest_program() { + let from = Keypair::new(); + let to = Keypair::new(); + let mut accounts = vec![Account::default(), Account::default()]; + accounts[1].owner = to.pubkey(); + let tx = Transaction::system_create( + &from, + to.pubkey(), + Hash::default(), + 0, + 1, + Pubkey::default(), + 0, + ); + assert!(process_transaction(&tx, &mut accounts).is_err()); + assert!(accounts[1].userdata.is_empty()); + } + #[test] + fn test_create_allocate_wrong_source_program() { + let from = Keypair::new(); + let to = Keypair::new(); + let mut accounts = vec![Account::default(), Account::default()]; + accounts[0].owner = to.pubkey(); + let tx = Transaction::system_create( + &from, + to.pubkey(), + Hash::default(), + 0, + 1, + Pubkey::default(), + 0, + ); + assert!(process_transaction(&tx, &mut accounts).is_err()); + assert!(accounts[1].userdata.is_empty()); + } + #[test] + fn test_create_allocate_already_allocated() { + let from = Keypair::new(); + let to = Keypair::new(); + let mut accounts = vec![Account::default(), Account::default()]; + accounts[1].userdata = vec![0, 0, 0]; + let tx = Transaction::system_create( + &from, + to.pubkey(), + Hash::default(), + 0, + 2, + Pubkey::default(), + 0, + ); + assert!(process_transaction(&tx, &mut accounts).is_err()); + assert_eq!(accounts[1].userdata.len(), 3); + } + #[test] + fn test_create_assign() { + let from = Keypair::new(); + let program = Keypair::new(); + let mut accounts = vec![Account::default()]; + let tx = Transaction::system_assign(&from, Hash::default(), program.pubkey(), 0); + process_transaction(&tx, &mut accounts).unwrap(); + assert_eq!(accounts[0].owner, program.pubkey()); + } + #[test] + fn test_move() { + let from = Keypair::new(); + let to = Keypair::new(); + let mut accounts = vec![Account::default(), Account::default()]; + accounts[0].tokens = 1; + let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default()); + process_transaction(&tx, &mut accounts).unwrap(); + assert_eq!(accounts[0].tokens, 0); + assert_eq!(accounts[1].tokens, 1); + } + /// Detect binary changes in the serialized program userdata, which could have a downstream + /// affect on SDKs and DApps + #[test] + fn test_sdk_serialize() { + let keypair = Keypair::new(); + use budget_program::BudgetState; + + // CreateAccount + let tx = Transaction::system_create( + &keypair, + keypair.pubkey(), + Hash::default(), + 111, + 222, + BudgetState::id(), + 0, + ); + + assert_eq!( + tx.userdata(0).to_vec(), + vec![ + 0, 0, 0, 0, 111, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 0, 0, 0, 0, 129, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + // CreateAccount + let tx = Transaction::system_create( + &keypair, + keypair.pubkey(), + Hash::default(), + 111, + 222, + Pubkey::default(), + 0, + ); + + assert_eq!( + tx.userdata(0).to_vec(), + vec![ + 0, 0, 0, 0, 111, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + // Assign + let tx = Transaction::system_assign(&keypair, Hash::default(), BudgetState::id(), 0); + assert_eq!( + tx.userdata(0).to_vec(), + vec![ + 1, 0, 0, 0, 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + // Move + let tx = Transaction::system_move(&keypair, keypair.pubkey(), 123, Hash::default(), 0); + assert_eq!( + tx.userdata(0).to_vec(), + vec![2, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0] + ); + } +} diff --git a/book/system_transaction.rs b/book/system_transaction.rs new file mode 100644 index 00000000000000..fcd0cc29e76e6d --- /dev/null +++ b/book/system_transaction.rs @@ -0,0 +1,221 @@ +//! The `system_transaction` module provides functionality for creating system transactions. + +use signature::{Keypair, KeypairUtil}; +use solana_sdk::hash::Hash; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::system_instruction::SystemInstruction; +use system_program::SystemProgram; + +use transaction::{Instruction, Transaction}; + +pub trait SystemTransaction { + fn system_create( + from_keypair: &Keypair, + to: Pubkey, + last_id: Hash, + tokens: u64, + space: u64, + program_id: Pubkey, + fee: u64, + ) -> Self; + + fn system_assign(from_keypair: &Keypair, last_id: Hash, program_id: Pubkey, fee: u64) -> Self; + + fn system_new(from_keypair: &Keypair, to: Pubkey, tokens: u64, last_id: Hash) -> Self; + + fn system_move( + from_keypair: &Keypair, + to: Pubkey, + tokens: u64, + last_id: Hash, + fee: u64, + ) -> Self; + + fn system_move_many( + from_keypair: &Keypair, + moves: &[(Pubkey, u64)], + last_id: Hash, + fee: u64, + ) -> Self; + + fn system_spawn(from_keypair: &Keypair, last_id: Hash, fee: u64) -> Self; +} + +impl SystemTransaction for Transaction { + /// Create and sign new SystemInstruction::CreateAccount transaction + fn system_create( + from_keypair: &Keypair, + to: Pubkey, + last_id: Hash, + tokens: u64, + space: u64, + program_id: Pubkey, + fee: u64, + ) -> Self { + let create = SystemInstruction::CreateAccount { + tokens, //TODO, the tokens to allocate might need to be higher then 0 in the future + space, + program_id, + }; + Transaction::new( + from_keypair, + &[to], + SystemProgram::id(), + &create, + last_id, + fee, + ) + } + /// Create and sign new SystemInstruction::Assign transaction + fn system_assign(from_keypair: &Keypair, last_id: Hash, program_id: Pubkey, fee: u64) -> Self { + let assign = SystemInstruction::Assign { program_id }; + Transaction::new( + from_keypair, + &[], + SystemProgram::id(), + &assign, + last_id, + fee, + ) + } + /// Create and sign new SystemInstruction::CreateAccount transaction with some defaults + fn system_new(from_keypair: &Keypair, to: Pubkey, tokens: u64, last_id: Hash) -> Self { + Transaction::system_create(from_keypair, to, last_id, tokens, 0, Pubkey::default(), 0) + } + /// Create and sign new SystemInstruction::Move transaction + fn system_move( + from_keypair: &Keypair, + to: Pubkey, + tokens: u64, + last_id: Hash, + fee: u64, + ) -> Self { + let move_tokens = SystemInstruction::Move { tokens }; + Transaction::new( + from_keypair, + &[to], + SystemProgram::id(), + &move_tokens, + last_id, + fee, + ) + } + /// Create and sign new SystemInstruction::Move transaction to many destinations + fn system_move_many(from: &Keypair, moves: &[(Pubkey, u64)], last_id: Hash, fee: u64) -> Self { + let instructions: Vec<_> = moves + .iter() + .enumerate() + .map(|(i, (_, amount))| { + let spend = SystemInstruction::Move { tokens: *amount }; + Instruction::new(0, &spend, vec![0, i as u8 + 1]) + }).collect(); + let to_keys: Vec<_> = moves.iter().map(|(to_key, _)| *to_key).collect(); + + Transaction::new_with_instructions( + &[from], + &to_keys, + last_id, + fee, + vec![SystemProgram::id()], + instructions, + ) + } + /// Create and sign new SystemInstruction::Spawn transaction + fn system_spawn(from_keypair: &Keypair, last_id: Hash, fee: u64) -> Self { + let spawn = SystemInstruction::Spawn; + Transaction::new(from_keypair, &[], SystemProgram::id(), &spawn, last_id, fee) + } +} + +pub fn test_tx() -> Transaction { + let keypair1 = Keypair::new(); + let pubkey1 = keypair1.pubkey(); + let zero = Hash::default(); + Transaction::system_new(&keypair1, pubkey1, 42, zero) +} + +#[cfg(test)] +pub fn memfind(a: &[A], b: &[A]) -> Option { + assert!(a.len() >= b.len()); + let end = a.len() - b.len() + 1; + for i in 0..end { + if a[i..i + b.len()] == b[..] { + return Some(i); + } + } + None +} + +#[cfg(test)] +mod tests { + use super::*; + use bincode::{deserialize, serialize}; + use packet::PACKET_DATA_SIZE; + use sigverify; + use transaction::SIG_OFFSET; + + #[test] + fn test_layout() { + let tx = test_tx(); + let tx_bytes = serialize(&tx).unwrap(); + let sign_data = tx.get_sign_data(); + let packet = sigverify::make_packet_from_transaction(tx.clone()); + + let (sig_len, sig_start, msg_start_offset, pubkey_offset) = + sigverify::get_packet_offsets(&packet, 0); + + assert_eq!( + memfind(&tx_bytes, &tx.signatures[0].as_ref()), + Some(SIG_OFFSET) + ); + assert_eq!( + memfind(&tx_bytes, &tx.account_keys[0].as_ref()), + Some(pubkey_offset as usize) + ); + assert_eq!( + memfind(&tx_bytes, &sign_data), + Some(msg_start_offset as usize) + ); + assert_eq!( + memfind(&tx_bytes, &tx.signatures[0].as_ref()), + Some(sig_start as usize) + ); + assert_eq!(sig_len, 1); + assert!(tx.verify_signature()); + } + + #[test] + fn test_userdata_layout() { + let mut tx0 = test_tx(); + tx0.instructions[0].userdata = vec![1, 2, 3]; + let sign_data0a = tx0.get_sign_data(); + let tx_bytes = serialize(&tx0).unwrap(); + assert!(tx_bytes.len() < PACKET_DATA_SIZE); + assert_eq!( + memfind(&tx_bytes, &tx0.signatures[0].as_ref()), + Some(SIG_OFFSET) + ); + let tx1 = deserialize(&tx_bytes).unwrap(); + assert_eq!(tx0, tx1); + assert_eq!(tx1.instructions[0].userdata, vec![1, 2, 3]); + + tx0.instructions[0].userdata = vec![1, 2, 4]; + let sign_data0b = tx0.get_sign_data(); + assert_ne!(sign_data0a, sign_data0b); + } + #[test] + fn test_move_many() { + let from = Keypair::new(); + let t1 = Keypair::new(); + let t2 = Keypair::new(); + let moves = vec![(t1.pubkey(), 1), (t2.pubkey(), 2)]; + + let tx = Transaction::system_move_many(&from, &moves, Default::default(), 0); + assert_eq!(tx.account_keys[0], from.pubkey()); + assert_eq!(tx.account_keys[1], t1.pubkey()); + assert_eq!(tx.account_keys[2], t2.pubkey()); + assert_eq!(tx.instructions.len(), 2); + assert_eq!(tx.instructions[0].accounts, vec![0, 1]); + assert_eq!(tx.instructions[1].accounts, vec![0, 2]); + } +} diff --git a/book/terminology.html b/book/terminology.html new file mode 100644 index 00000000000000..89ead15c8f9cf5 --- /dev/null +++ b/book/terminology.html @@ -0,0 +1,230 @@ + + + + + + Terminology - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

Terminology

+

Teminology Currently in Use

+

The following list contains words commonly used throughout the Solana architecture.

+
    +
  • account - a persistent file addressed by pubkey and with tokens tracking its lifetime
  • +
  • cluster - a set of fullnodes maintaining a single ledger
  • +
  • finality - the wallclock duration between a leader creating a tick entry and recoginizing +a supermajority of validator votes with a ledger interpretation that matches the leader's
  • +
  • fullnode - a full participant in the cluster - either a leader or validator node
  • +
  • entry - an entry on the ledger - either a tick or a transactions entry
  • +
  • instruction - the smallest unit of a program that a client can include in a transaction
  • +
  • keypair - a public and secret key
  • +
  • node count - the number of fullnodes participating in a cluster
  • +
  • program - the code that interprets instructions
  • +
  • pubkey - the public key of a keypair
  • +
  • tick - a ledger entry that estimates wallclock duration
  • +
  • tick height - the Nth tick in the ledger
  • +
  • tps - transactions per second
  • +
  • transaction - one or more instructions signed by the client and executed atomically
  • +
  • transactions entry - a set of transactions that may be executed in parallel
  • +
+

Terminology Reserved for Future Use

+

The following keywords do not have any functionality but are reserved by Solana +for potential future use.

+
    +
  • epoch - the time in which a leader schedule is valid
  • +
  • mips - millions of instructions per second
  • +
  • public key - We currently use pubkey
  • +
  • slot - the time in which a single leader may produce entries
  • +
  • secret key - Users currently only use keypair
  • +
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/thin_client.rs b/book/thin_client.rs new file mode 100644 index 00000000000000..c49ab1c8273afc --- /dev/null +++ b/book/thin_client.rs @@ -0,0 +1,739 @@ +//! The `thin_client` module is a client-side object that interfaces with +//! a server-side TPU. Client code should use this object instead of writing +//! messages to the network directly. The binary encoding of its messages are +//! unstable and may change in future releases. + +use bank::Bank; +use bincode::serialize; +use bs58; +use cluster_info::{ClusterInfo, ClusterInfoError, NodeInfo}; +use log::Level; +use ncp::Ncp; +use packet::PACKET_DATA_SIZE; +use result::{Error, Result}; +use rpc_request::RpcRequest; +use serde_json; +use signature::{Keypair, Signature}; +use solana_metrics; +use solana_metrics::influxdb; +use solana_sdk::account::Account; +use solana_sdk::hash::Hash; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::timing; +use std; +use std::collections::HashMap; +use std::io; +use std::net::{SocketAddr, UdpSocket}; +use std::sync::atomic::AtomicBool; +use std::sync::{Arc, RwLock}; +use std::thread::sleep; +use std::time::Duration; +use std::time::Instant; +use system_transaction::SystemTransaction; +use transaction::Transaction; + +/// An object for querying and sending transactions to the network. +pub struct ThinClient { + rpc_addr: SocketAddr, + transactions_addr: SocketAddr, + transactions_socket: UdpSocket, + last_id: Option, + transaction_count: u64, + balances: HashMap, + signature_status: bool, + finality: Option, +} + +impl ThinClient { + /// Create a new ThinClient that will interface with the Rpc at `rpc_addr` using TCP + /// and the Tpu at `transactions_addr` over `transactions_socket` using UDP. + pub fn new( + rpc_addr: SocketAddr, + transactions_addr: SocketAddr, + transactions_socket: UdpSocket, + ) -> Self { + ThinClient { + rpc_addr, + transactions_addr, + transactions_socket, + last_id: None, + transaction_count: 0, + balances: HashMap::new(), + signature_status: false, + finality: None, + } + } + + /// Send a signed Transaction to the server for processing. This method + /// does not wait for a response. + pub fn transfer_signed(&self, tx: &Transaction) -> io::Result { + let data = serialize(&tx).expect("serialize Transaction in pub fn transfer_signed"); + assert!(data.len() < PACKET_DATA_SIZE); + self.transactions_socket + .send_to(&data, &self.transactions_addr)?; + Ok(tx.signatures[0]) + } + + /// Retry a sending a signed Transaction to the server for processing. + pub fn retry_transfer( + &mut self, + keypair: &Keypair, + tx: &mut Transaction, + tries: usize, + ) -> io::Result { + for x in 0..tries { + tx.sign(&[&keypair], self.get_last_id()); + let data = serialize(&tx).expect("serialize Transaction in pub fn transfer_signed"); + self.transactions_socket + .send_to(&data, &self.transactions_addr)?; + if self.poll_for_signature(&tx.signatures[0]).is_ok() { + return Ok(tx.signatures[0]); + } + info!("{} tries failed transfer to {}", x, self.transactions_addr); + } + Err(io::Error::new( + io::ErrorKind::Other, + "retry_transfer failed", + )) + } + + /// Creates, signs, and processes a Transaction. Useful for writing unit-tests. + pub fn transfer( + &self, + n: u64, + keypair: &Keypair, + to: Pubkey, + last_id: &Hash, + ) -> io::Result { + let now = Instant::now(); + let tx = Transaction::system_new(keypair, to, n, *last_id); + let result = self.transfer_signed(&tx); + solana_metrics::submit( + influxdb::Point::new("thinclient") + .add_tag("op", influxdb::Value::String("transfer".to_string())) + .add_field( + "duration_ms", + influxdb::Value::Integer(timing::duration_as_ms(&now.elapsed()) as i64), + ).to_owned(), + ); + result + } + + pub fn get_account_userdata(&mut self, pubkey: &Pubkey) -> io::Result>> { + let params = json!([format!("{}", pubkey)]); + let rpc_string = format!("http://{}", self.rpc_addr.to_string()); + let resp = RpcRequest::GetAccountInfo.make_rpc_request(&rpc_string, 1, Some(params)); + if let Ok(account_json) = resp { + let account: Account = + serde_json::from_value(account_json).expect("deserialize account"); + return Ok(Some(account.userdata)); + } + Err(io::Error::new( + io::ErrorKind::Other, + "get_account_userdata failed", + )) + } + + /// Request the balance of the user holding `pubkey`. This method blocks + /// until the server sends a response. If the response packet is dropped + /// by the network, this method will hang indefinitely. + pub fn get_balance(&mut self, pubkey: &Pubkey) -> io::Result { + trace!("get_balance sending request to {}", self.rpc_addr); + let params = json!([format!("{}", pubkey)]); + let rpc_string = format!("http://{}", self.rpc_addr.to_string()); + let resp = RpcRequest::GetAccountInfo.make_rpc_request(&rpc_string, 1, Some(params)); + if let Ok(account_json) = resp { + let account: Account = + serde_json::from_value(account_json).expect("deserialize account"); + trace!("Response account {:?} {:?}", pubkey, account); + self.balances.insert(*pubkey, account.clone()); + } else { + debug!("Response account {}: None ", pubkey); + self.balances.remove(&pubkey); + } + trace!("get_balance {:?}", self.balances.get(pubkey)); + // TODO: This is a hard coded call to introspect the balance of a budget_dsl contract + // In the future custom contracts would need their own introspection + self.balances + .get(pubkey) + .map(Bank::read_balance) + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "AccountNotFound")) + } + + /// Request the finality from the leader node + pub fn get_finality(&mut self) -> usize { + trace!("get_finality"); + let mut done = false; + let rpc_string = format!("http://{}", self.rpc_addr.to_string()); + while !done { + debug!("get_finality send_to {}", &self.rpc_addr); + let resp = RpcRequest::GetFinality.make_rpc_request(&rpc_string, 1, None); + + if let Ok(value) = resp { + done = true; + let finality = value.as_u64().unwrap() as usize; + self.finality = Some(finality); + } else { + debug!("thin_client get_finality error: {:?}", resp); + } + } + self.finality.expect("some finality") + } + + /// Request the transaction count. If the response packet is dropped by the network, + /// this method will try again 5 times. + pub fn transaction_count(&mut self) -> u64 { + debug!("transaction_count"); + let mut tries_left = 5; + let rpc_string = format!("http://{}", self.rpc_addr.to_string()); + while tries_left > 0 { + let resp = RpcRequest::GetTransactionCount.make_rpc_request(&rpc_string, 1, None); + + if let Ok(value) = resp { + debug!("transaction_count recv_response: {:?}", value); + tries_left = 0; + let transaction_count = value.as_u64().unwrap(); + self.transaction_count = transaction_count; + } else { + tries_left -= 1; + } + } + self.transaction_count + } + + /// Request the last Entry ID from the server. This method blocks + /// until the server sends a response. + pub fn get_last_id(&mut self) -> Hash { + trace!("get_last_id"); + let mut done = false; + let rpc_string = format!("http://{}", self.rpc_addr.to_string()); + while !done { + debug!("get_last_id send_to {}", &self.rpc_addr); + let resp = RpcRequest::GetLastId.make_rpc_request(&rpc_string, 1, None); + + if let Ok(value) = resp { + done = true; + let last_id_str = value.as_str().unwrap(); + let last_id_vec = bs58::decode(last_id_str).into_vec().unwrap(); + let last_id = Hash::new(&last_id_vec); + self.last_id = Some(last_id); + } else { + debug!("thin_client get_last_id error: {:?}", resp); + } + } + self.last_id.expect("some last_id") + } + + pub fn submit_poll_balance_metrics(elapsed: &Duration) { + solana_metrics::submit( + influxdb::Point::new("thinclient") + .add_tag("op", influxdb::Value::String("get_balance".to_string())) + .add_field( + "duration_ms", + influxdb::Value::Integer(timing::duration_as_ms(elapsed) as i64), + ).to_owned(), + ); + } + + pub fn poll_balance_with_timeout( + &mut self, + pubkey: &Pubkey, + polling_frequency: &Duration, + timeout: &Duration, + ) -> io::Result { + let now = Instant::now(); + loop { + match self.get_balance(&pubkey) { + Ok(bal) => { + ThinClient::submit_poll_balance_metrics(&now.elapsed()); + return Ok(bal); + } + Err(e) => { + sleep(*polling_frequency); + if now.elapsed() > *timeout { + ThinClient::submit_poll_balance_metrics(&now.elapsed()); + return Err(e); + } + } + }; + } + } + + pub fn poll_get_balance(&mut self, pubkey: &Pubkey) -> io::Result { + self.poll_balance_with_timeout(pubkey, &Duration::from_millis(100), &Duration::from_secs(1)) + } + + /// Poll the server to confirm a transaction. + pub fn poll_for_signature(&mut self, signature: &Signature) -> io::Result<()> { + let now = Instant::now(); + while !self.check_signature(signature) { + if now.elapsed().as_secs() > 1 { + // TODO: Return a better error. + return Err(io::Error::new(io::ErrorKind::Other, "signature not found")); + } + sleep(Duration::from_millis(100)); + } + Ok(()) + } + + /// Check a signature in the bank. This method blocks + /// until the server sends a response. + pub fn check_signature(&mut self, signature: &Signature) -> bool { + trace!("check_signature"); + let params = json!([format!("{}", signature)]); + let now = Instant::now(); + let rpc_string = format!("http://{}", self.rpc_addr.to_string()); + let mut done = false; + while !done { + let resp = RpcRequest::ConfirmTransaction.make_rpc_request( + &rpc_string, + 1, + Some(params.clone()), + ); + + if let Ok(confirmation) = resp { + done = true; + self.signature_status = confirmation.as_bool().unwrap(); + if self.signature_status { + trace!("Response found signature"); + } else { + trace!("Response signature not found"); + } + } + } + solana_metrics::submit( + influxdb::Point::new("thinclient") + .add_tag("op", influxdb::Value::String("check_signature".to_string())) + .add_field( + "duration_ms", + influxdb::Value::Integer(timing::duration_as_ms(&now.elapsed()) as i64), + ).to_owned(), + ); + self.signature_status + } +} + +impl Drop for ThinClient { + fn drop(&mut self) { + solana_metrics::flush(); + } +} + +pub fn poll_gossip_for_leader(leader_ncp: SocketAddr, timeout: Option) -> Result { + let exit = Arc::new(AtomicBool::new(false)); + let (node, gossip_socket) = ClusterInfo::spy_node(); + let my_addr = gossip_socket.local_addr().unwrap(); + let cluster_info = Arc::new(RwLock::new(ClusterInfo::new(node))); + let window = Arc::new(RwLock::new(vec![])); + let ncp = Ncp::new( + &cluster_info.clone(), + window, + None, + gossip_socket, + exit.clone(), + ); + + let leader_entry_point = NodeInfo::new_entry_point(&leader_ncp); + cluster_info + .write() + .unwrap() + .insert_info(leader_entry_point); + + sleep(Duration::from_millis(100)); + + let deadline = match timeout { + Some(timeout) => Duration::new(timeout, 0), + None => Duration::new(std::u64::MAX, 0), + }; + let now = Instant::now(); + // Block until leader's correct contact info is received + let leader; + + loop { + trace!("polling {:?} for leader from {:?}", leader_ncp, my_addr); + + if let Some(l) = cluster_info.read().unwrap().get_gossip_top_leader() { + leader = Some(l.clone()); + break; + } + + if log_enabled!(Level::Trace) { + trace!("{}", cluster_info.read().unwrap().node_info_trace()); + } + + if now.elapsed() > deadline { + return Err(Error::ClusterInfoError(ClusterInfoError::NoLeader)); + } + + sleep(Duration::from_millis(100)); + } + + ncp.close()?; + + if log_enabled!(Level::Trace) { + trace!("{}", cluster_info.read().unwrap().node_info_trace()); + } + + Ok(leader.unwrap().clone()) +} + +pub fn retry_get_balance( + client: &mut ThinClient, + bob_pubkey: &Pubkey, + expected_balance: Option, +) -> Option { + const LAST: usize = 30; + for run in 0..LAST { + let balance_result = client.poll_get_balance(bob_pubkey); + if expected_balance.is_none() { + return balance_result.ok(); + } + trace!( + "retry_get_balance[{}] {:?} {:?}", + run, + balance_result, + expected_balance + ); + if let (Some(expected_balance), Ok(balance_result)) = (expected_balance, balance_result) { + if expected_balance == balance_result { + return Some(balance_result); + } + } + } + None +} + +#[cfg(test)] +mod tests { + use super::*; + use bank::Bank; + use bincode::deserialize; + use cluster_info::Node; + use fullnode::Fullnode; + use leader_scheduler::LeaderScheduler; + use ledger::create_tmp_ledger_with_mint; + use logger; + use mint::Mint; + use signature::{Keypair, KeypairUtil}; + use solana_sdk::system_instruction::SystemInstruction; + use std::fs::remove_dir_all; + use vote_program::VoteProgram; + use vote_transaction::VoteTransaction; + + #[test] + fn test_thin_client() { + logger::setup(); + let leader_keypair = Arc::new(Keypair::new()); + let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); + let leader_data = leader.info.clone(); + + let alice = Mint::new(10_000); + let mut bank = Bank::new(&alice); + let bob_pubkey = Keypair::new().pubkey(); + let ledger_path = create_tmp_ledger_with_mint("thin_client", &alice); + + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + leader_data.id, + ))); + bank.leader_scheduler = leader_scheduler; + let vote_account_keypair = Arc::new(Keypair::new()); + let last_id = bank.last_id(); + let server = Fullnode::new_with_bank( + leader_keypair, + vote_account_keypair, + bank, + 0, + &last_id, + leader, + None, + &ledger_path, + false, + None, + ); + sleep(Duration::from_millis(900)); + + let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + + let mut client = ThinClient::new(leader_data.rpc, leader_data.tpu, transactions_socket); + let transaction_count = client.transaction_count(); + assert_eq!(transaction_count, 0); + let finality = client.get_finality(); + assert_eq!(finality, 18446744073709551615); + let last_id = client.get_last_id(); + let signature = client + .transfer(500, &alice.keypair(), bob_pubkey, &last_id) + .unwrap(); + client.poll_for_signature(&signature).unwrap(); + let balance = client.get_balance(&bob_pubkey); + assert_eq!(balance.unwrap(), 500); + let transaction_count = client.transaction_count(); + assert_eq!(transaction_count, 1); + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); + } + + // sleep(Duration::from_millis(300)); is unstable + #[test] + #[ignore] + fn test_bad_sig() { + logger::setup(); + let leader_keypair = Arc::new(Keypair::new()); + let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); + let alice = Mint::new(10_000); + let mut bank = Bank::new(&alice); + let bob_pubkey = Keypair::new().pubkey(); + let leader_data = leader.info.clone(); + let ledger_path = create_tmp_ledger_with_mint("bad_sig", &alice); + + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + leader_data.id, + ))); + bank.leader_scheduler = leader_scheduler; + let vote_account_keypair = Arc::new(Keypair::new()); + let last_id = bank.last_id(); + let server = Fullnode::new_with_bank( + leader_keypair, + vote_account_keypair, + bank, + 0, + &last_id, + leader, + None, + &ledger_path, + false, + None, + ); + //TODO: remove this sleep, or add a retry so CI is stable + sleep(Duration::from_millis(300)); + + let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + let mut client = ThinClient::new(leader_data.rpc, leader_data.tpu, transactions_socket); + let last_id = client.get_last_id(); + + let tx = Transaction::system_new(&alice.keypair(), bob_pubkey, 500, last_id); + + let _sig = client.transfer_signed(&tx).unwrap(); + + let last_id = client.get_last_id(); + + let mut tr2 = Transaction::system_new(&alice.keypair(), bob_pubkey, 501, last_id); + let mut instruction2 = deserialize(tr2.userdata(0)).unwrap(); + if let SystemInstruction::Move { ref mut tokens } = instruction2 { + *tokens = 502; + } + tr2.instructions[0].userdata = serialize(&instruction2).unwrap(); + let signature = client.transfer_signed(&tr2).unwrap(); + client.poll_for_signature(&signature).unwrap(); + + let balance = client.get_balance(&bob_pubkey); + assert_eq!(balance.unwrap(), 500); + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); + } + + #[test] + fn test_client_check_signature() { + logger::setup(); + let leader_keypair = Arc::new(Keypair::new()); + let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); + let alice = Mint::new(10_000); + let mut bank = Bank::new(&alice); + let bob_pubkey = Keypair::new().pubkey(); + let leader_data = leader.info.clone(); + let ledger_path = create_tmp_ledger_with_mint("client_check_signature", &alice); + + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + leader_data.id, + ))); + bank.leader_scheduler = leader_scheduler; + let vote_account_keypair = Arc::new(Keypair::new()); + let entry_height = alice.create_entries().len() as u64; + let last_id = bank.last_id(); + let server = Fullnode::new_with_bank( + leader_keypair, + vote_account_keypair, + bank, + entry_height, + &last_id, + leader, + None, + &ledger_path, + false, + None, + ); + sleep(Duration::from_millis(300)); + + let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + let mut client = ThinClient::new(leader_data.rpc, leader_data.tpu, transactions_socket); + let last_id = client.get_last_id(); + let signature = client + .transfer(500, &alice.keypair(), bob_pubkey, &last_id) + .unwrap(); + + assert!(client.poll_for_signature(&signature).is_ok()); + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); + } + + #[test] + fn test_register_vote_account() { + logger::setup(); + let leader_keypair = Arc::new(Keypair::new()); + let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); + let mint = Mint::new(10_000); + let mut bank = Bank::new(&mint); + let leader_data = leader.info.clone(); + let ledger_path = create_tmp_ledger_with_mint("client_check_signature", &mint); + + let genesis_entries = &mint.create_entries(); + let entry_height = genesis_entries.len() as u64; + + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + leader_data.id, + ))); + bank.leader_scheduler = leader_scheduler; + let leader_vote_account_keypair = Arc::new(Keypair::new()); + let server = Fullnode::new_with_bank( + leader_keypair, + leader_vote_account_keypair.clone(), + bank, + entry_height, + &genesis_entries.last().unwrap().id, + leader, + None, + &ledger_path, + false, + None, + ); + sleep(Duration::from_millis(300)); + + let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + let mut client = ThinClient::new(leader_data.rpc, leader_data.tpu, transactions_socket); + + // Create the validator account, transfer some tokens to that account + let validator_keypair = Keypair::new(); + let last_id = client.get_last_id(); + let signature = client + .transfer(500, &mint.keypair(), validator_keypair.pubkey(), &last_id) + .unwrap(); + + assert!(client.poll_for_signature(&signature).is_ok()); + + // Create the vote account + let validator_vote_account_keypair = Keypair::new(); + let vote_account_id = validator_vote_account_keypair.pubkey(); + let last_id = client.get_last_id(); + + let transaction = + VoteTransaction::vote_account_new(&validator_keypair, vote_account_id, last_id, 1); + let signature = client.transfer_signed(&transaction).unwrap(); + assert!(client.poll_for_signature(&signature).is_ok()); + + let balance = retry_get_balance(&mut client, &vote_account_id, Some(1)) + .expect("Expected balance for new account to exist"); + assert_eq!(balance, 1); + + // Register the vote account to the validator + let last_id = client.get_last_id(); + let transaction = + VoteTransaction::vote_account_register(&validator_keypair, vote_account_id, last_id, 0); + let signature = client.transfer_signed(&transaction).unwrap(); + assert!(client.poll_for_signature(&signature).is_ok()); + + const LAST: usize = 30; + for run in 0..=LAST { + println!("Checking for account registered: {}", run); + let account_user_data = client + .get_account_userdata(&vote_account_id) + .expect("Expected valid response for account userdata") + .expect("Expected valid account userdata to exist after account creation"); + + let vote_state = VoteProgram::deserialize(&account_user_data); + + if vote_state.map(|vote_state| vote_state.node_id) == Ok(validator_keypair.pubkey()) { + break; + } + + if run == LAST { + panic!("Expected successful vote account registration"); + } + sleep(Duration::from_millis(900)); + } + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); + } + + #[test] + fn test_transaction_count() { + // set a bogus address, see that we don't hang + logger::setup(); + let addr = "0.0.0.0:1234".parse().unwrap(); + let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + let mut client = ThinClient::new(addr, addr, transactions_socket); + assert_eq!(client.transaction_count(), 0); + } + + #[test] + fn test_zero_balance_after_nonzero() { + logger::setup(); + let leader_keypair = Arc::new(Keypair::new()); + let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); + let alice = Mint::new(10_000); + let mut bank = Bank::new(&alice); + let bob_keypair = Keypair::new(); + let leader_data = leader.info.clone(); + let ledger_path = create_tmp_ledger_with_mint("zero_balance_check", &alice); + + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + leader_data.id, + ))); + bank.leader_scheduler = leader_scheduler; + let vote_account_keypair = Arc::new(Keypair::new()); + let last_id = bank.last_id(); + let entry_height = alice.create_entries().len() as u64; + let server = Fullnode::new_with_bank( + leader_keypair, + vote_account_keypair, + bank, + entry_height, + &last_id, + leader, + None, + &ledger_path, + false, + None, + ); + sleep(Duration::from_millis(900)); + + let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + let mut client = ThinClient::new(leader_data.rpc, leader_data.tpu, transactions_socket); + let last_id = client.get_last_id(); + + // give bob 500 tokens + let signature = client + .transfer(500, &alice.keypair(), bob_keypair.pubkey(), &last_id) + .unwrap(); + assert!(client.poll_for_signature(&signature).is_ok()); + + let balance = client.poll_get_balance(&bob_keypair.pubkey()); + assert!(balance.is_ok()); + assert_eq!(balance.unwrap(), 500); + + // take them away + let signature = client + .transfer(500, &bob_keypair, alice.keypair().pubkey(), &last_id) + .unwrap(); + assert!(client.poll_for_signature(&signature).is_ok()); + + // should get an error when bob's account is purged + let balance = client.poll_get_balance(&bob_keypair.pubkey()); + assert!(balance.is_err()); + + server + .close() + .unwrap_or_else(|e| panic!("close() failed! {:?}", e)); + remove_dir_all(ledger_path).unwrap(); + } +} diff --git a/book/token_program.rs b/book/token_program.rs new file mode 100644 index 00000000000000..e18f51fc03d723 --- /dev/null +++ b/book/token_program.rs @@ -0,0 +1,24 @@ +//! ERC20-like Token program +use native_loader; +use solana_sdk::account::Account; +use solana_sdk::pubkey::Pubkey; + +const ERC20_NAME: &str = "solana_erc20"; +const ERC20_PROGRAM_ID: [u8; 32] = [ + 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +]; + +pub fn id() -> Pubkey { + Pubkey::new(&ERC20_PROGRAM_ID) +} + +pub fn account() -> Account { + Account { + tokens: 1, + owner: id(), + userdata: ERC20_NAME.as_bytes().to_vec(), + executable: true, + loader: native_loader::id(), + } +} diff --git a/book/tomorrow-night.css b/book/tomorrow-night.css new file mode 100644 index 00000000000000..9788e0846b0ce3 --- /dev/null +++ b/book/tomorrow-night.css @@ -0,0 +1,96 @@ +/* Tomorrow Night Theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment { + color: #969896; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #cc6666; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #de935f; +} + +/* Tomorrow Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #f0c674; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.hljs-name, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #b5bd68; +} + +/* Tomorrow Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #8abeb7; +} + +/* Tomorrow Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #81a2be; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #b294bb; +} + +.hljs { + display: block; + overflow-x: auto; + background: #1d1f21; + color: #c5c8c6; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/book/tpu.html b/book/tpu.html new file mode 100644 index 00000000000000..d33267ea2b9bd9 --- /dev/null +++ b/book/tpu.html @@ -0,0 +1,201 @@ + + + + + + Tpu - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

The Transaction Processing Unit

+

Tpu block diagram

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/tpu.rs b/book/tpu.rs new file mode 100644 index 00000000000000..9678c6ef11cb1f --- /dev/null +++ b/book/tpu.rs @@ -0,0 +1,96 @@ +//! The `tpu` module implements the Transaction Processing Unit, a +//! 5-stage transaction processing pipeline in software. + +use bank::Bank; +use banking_stage::{BankingStage, BankingStageReturnType}; +use entry::Entry; +use fetch_stage::FetchStage; +use ledger_write_stage::LedgerWriteStage; +use poh_service::Config; +use service::Service; +use sigverify_stage::SigVerifyStage; +use solana_sdk::hash::Hash; +use std::net::UdpSocket; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc::Receiver; +use std::sync::Arc; +use std::thread; + +pub enum TpuReturnType { + LeaderRotation, +} + +pub struct Tpu { + fetch_stage: FetchStage, + sigverify_stage: SigVerifyStage, + banking_stage: BankingStage, + ledger_write_stage: LedgerWriteStage, + exit: Arc, +} + +impl Tpu { + pub fn new( + bank: &Arc, + tick_duration: Config, + transactions_sockets: Vec, + ledger_path: &str, + sigverify_disabled: bool, + max_tick_height: Option, + last_entry_id: &Hash, + ) -> (Self, Receiver>, Arc) { + let exit = Arc::new(AtomicBool::new(false)); + + let (fetch_stage, packet_receiver) = FetchStage::new(transactions_sockets, exit.clone()); + + let (sigverify_stage, verified_receiver) = + SigVerifyStage::new(packet_receiver, sigverify_disabled); + + let (banking_stage, entry_receiver) = BankingStage::new( + &bank, + verified_receiver, + tick_duration, + last_entry_id, + max_tick_height, + ); + + let (ledger_write_stage, entry_forwarder) = + LedgerWriteStage::new(Some(ledger_path), entry_receiver); + + let tpu = Tpu { + fetch_stage, + sigverify_stage, + banking_stage, + ledger_write_stage, + exit: exit.clone(), + }; + + (tpu, entry_forwarder, exit) + } + + pub fn exit(&self) { + self.exit.store(true, Ordering::Relaxed); + } + + pub fn is_exited(&self) -> bool { + self.exit.load(Ordering::Relaxed) + } + + pub fn close(self) -> thread::Result> { + self.fetch_stage.close(); + self.join() + } +} + +impl Service for Tpu { + type JoinReturnType = Option; + + fn join(self) -> thread::Result<(Option)> { + self.fetch_stage.join()?; + self.sigverify_stage.join()?; + self.ledger_write_stage.join()?; + match self.banking_stage.join()? { + Some(BankingStageReturnType::LeaderRotation) => Ok(Some(TpuReturnType::LeaderRotation)), + _ => Ok(None), + } + } +} diff --git a/book/tpu_forwarder.rs b/book/tpu_forwarder.rs new file mode 100644 index 00000000000000..cd30ac1e07aea3 --- /dev/null +++ b/book/tpu_forwarder.rs @@ -0,0 +1,198 @@ +//! The `tpu_forwarder` module implements a validator's +//! transaction processing unit responsibility, which +//! forwards received packets to the current leader + +use cluster_info::ClusterInfo; +use contact_info::ContactInfo; +use counter::Counter; +use log::Level; +use result::Result; +use service::Service; +use std::net::UdpSocket; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::mpsc::channel; +use std::sync::{Arc, RwLock}; +use std::thread::{self, Builder, JoinHandle}; +use streamer::{self, PacketReceiver}; + +pub struct TpuForwarder { + exit: Arc, + thread_hdls: Vec>, +} + +impl TpuForwarder { + fn forward(receiver: &PacketReceiver, cluster_info: &Arc>) -> Result<()> { + let socket = UdpSocket::bind("0.0.0.0:0")?; + + let my_id = cluster_info + .read() + .expect("cluster_info.read() in TpuForwarder::forward()") + .id(); + + loop { + let msgs = receiver.recv()?; + + inc_new_counter_info!( + "tpu_forwarder-msgs_received", + msgs.read().unwrap().packets.len() + ); + + if let Some(leader_data) = cluster_info + .read() + .expect("cluster_info.read() in TpuForwarder::forward()") + .leader_data() + .cloned() + { + if leader_data.id == my_id || !ContactInfo::is_valid_address(&leader_data.tpu) { + // weird cases, but we don't want to broadcast, send to ANY, or + // induce an infinite loop, but this shouldn't happen, or shouldn't be true for long... + continue; + } + + for m in msgs.write().unwrap().packets.iter_mut() { + m.meta.set_addr(&leader_data.tpu); + } + msgs.read().unwrap().send_to(&socket)? + } + } + } + + pub fn new(sockets: Vec, cluster_info: Arc>) -> Self { + let exit = Arc::new(AtomicBool::new(false)); + let (sender, receiver) = channel(); + + let mut thread_hdls: Vec<_> = sockets + .into_iter() + .map(|socket| { + streamer::receiver( + Arc::new(socket), + exit.clone(), + sender.clone(), + "tpu-forwarder", + ) + }).collect(); + + let thread_hdl = Builder::new() + .name("solana-tpu_forwarder".to_string()) + .spawn(move || { + let _ignored = Self::forward(&receiver, &cluster_info); + () + }).unwrap(); + + thread_hdls.push(thread_hdl); + + TpuForwarder { exit, thread_hdls } + } + + pub fn close(&self) { + self.exit.store(true, Ordering::Relaxed); + } +} + +impl Service for TpuForwarder { + type JoinReturnType = (); + + fn join(self) -> thread::Result<()> { + self.close(); + for thread_hdl in self.thread_hdls { + thread_hdl.join()?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use cluster_info::ClusterInfo; + use contact_info::ContactInfo; + use netutil::bind_in_range; + use solana_sdk::pubkey::Pubkey; + use std::net::UdpSocket; + use std::net::{Ipv4Addr, SocketAddr}; + use std::thread::sleep; + use std::time::Duration; + + #[test] + #[ignore] + pub fn test_tpu_forwarder() { + let nodes: Vec<_> = (0..3) + .map(|_| { + let (port, s) = bind_in_range((8000, 10000)).unwrap(); + s.set_nonblocking(true).unwrap(); + ( + s, + ContactInfo::new_with_socketaddr(&socketaddr!([127, 0, 0, 1], port)), + ) + }).collect(); + + let mut cluster_info = ClusterInfo::new(nodes[0].1.clone()); + + cluster_info.insert_info(nodes[1].1.clone()); + cluster_info.insert_info(nodes[2].1.clone()); + cluster_info.insert_info(Default::default()); + + let cluster_info = Arc::new(RwLock::new(cluster_info)); + + let (transaction_port, transaction_socket) = bind_in_range((8000, 10000)).unwrap(); + let transaction_addr = socketaddr!([127, 0, 0, 1], transaction_port); + + let tpu_forwarder = TpuForwarder::new(vec![transaction_socket], cluster_info.clone()); + + let test_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + + // no leader set in cluster_info, drop the "transaction" + test_socket + .send_to(b"alice pays bob", &transaction_addr) + .unwrap(); + sleep(Duration::from_millis(100)); + + let mut data = vec![0u8; 64]; + // should be nothing on any socket + assert!(nodes[0].0.recv_from(&mut data).is_err()); + assert!(nodes[1].0.recv_from(&mut data).is_err()); + assert!(nodes[2].0.recv_from(&mut data).is_err()); + + // set leader to host with no tpu + cluster_info.write().unwrap().set_leader(Pubkey::default()); + test_socket + .send_to(b"alice pays bart", &transaction_addr) + .unwrap(); + sleep(Duration::from_millis(100)); + + let mut data = vec![0u8; 64]; + // should be nothing on any socket ncp + assert!(nodes[0].0.recv_from(&mut data).is_err()); + assert!(nodes[1].0.recv_from(&mut data).is_err()); + assert!(nodes[2].0.recv_from(&mut data).is_err()); + + cluster_info.write().unwrap().set_leader(nodes[0].1.id); // set leader to myself, bytes get dropped :-( + + test_socket + .send_to(b"alice pays bill", &transaction_addr) + .unwrap(); + sleep(Duration::from_millis(100)); + + // should *still* be nothing on any socket + assert!(nodes[0].0.recv_from(&mut data).is_err()); + assert!(nodes[1].0.recv_from(&mut data).is_err()); + assert!(nodes[2].0.recv_from(&mut data).is_err()); + + cluster_info.write().unwrap().set_leader(nodes[1].1.id); // set leader to node[1] + + test_socket + .send_to(b"alice pays chuck", &transaction_addr) + .unwrap(); + sleep(Duration::from_millis(100)); + + // should only be data on node[1]'s socket + assert!(nodes[0].0.recv_from(&mut data).is_err()); + assert!(nodes[2].0.recv_from(&mut data).is_err()); + + assert!(nodes[1].0.recv_from(&mut data).is_ok()); + assert_eq!(&data[..b"alice pays chuck".len()], b"alice pays chuck"); + + assert!(tpu_forwarder.join().is_ok()); + } + +} diff --git a/book/transaction.rs b/book/transaction.rs new file mode 100644 index 00000000000000..e70dc793af8442 --- /dev/null +++ b/book/transaction.rs @@ -0,0 +1 @@ +pub use solana_sdk::transaction::*; diff --git a/book/tvu.html b/book/tvu.html new file mode 100644 index 00000000000000..59c9571b313779 --- /dev/null +++ b/book/tvu.html @@ -0,0 +1,201 @@ + + + + + + Tvu - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

The Transaction Validation Unit

+

Tvu block diagram

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/tvu.rs b/book/tvu.rs new file mode 100644 index 00000000000000..45a9da86f406b5 --- /dev/null +++ b/book/tvu.rs @@ -0,0 +1,350 @@ +//! The `tvu` module implements the Transaction Validation Unit, a +//! 3-stage transaction validation pipeline in software. +//! +//! 1. Fetch Stage +//! - Incoming blobs are picked up from the replicate socket and repair socket. +//! 2. SharedWindow Stage +//! - Blobs are windowed until a contiguous chunk is available. This stage also repairs and +//! retransmits blobs that are in the queue. +//! 3. Replicate Stage +//! - Transactions in blobs are processed and applied to the bank. +//! - TODO We need to verify the signatures in the blobs. + +use bank::Bank; +use blob_fetch_stage::BlobFetchStage; +use cluster_info::ClusterInfo; +use ledger_write_stage::LedgerWriteStage; +use replicate_stage::{ReplicateStage, ReplicateStageReturnType}; +use retransmit_stage::RetransmitStage; +use service::Service; +use signature::Keypair; +use solana_sdk::hash::Hash; +use std::net::UdpSocket; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, RwLock}; +use std::thread; +use storage_stage::{StorageStage, StorageState}; +use window::SharedWindow; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum TvuReturnType { + LeaderRotation(u64, u64, Hash), +} + +pub struct Tvu { + replicate_stage: ReplicateStage, + fetch_stage: BlobFetchStage, + retransmit_stage: RetransmitStage, + ledger_write_stage: LedgerWriteStage, + storage_stage: StorageStage, + exit: Arc, +} + +impl Tvu { + /// This service receives messages from a leader in the network and processes the transactions + /// on the bank state. + /// # Arguments + /// * `bank` - The bank state. + /// * `keypair` - Node's key pair for signing + /// * `vote_account_keypair` - Vote key pair + /// * `entry_height` - Initial ledger height + /// * `cluster_info` - The cluster_info state. + /// * `window` - The window state. + /// * `replicate_socket` - my replicate socket + /// * `repair_socket` - my repair socket + /// * `retransmit_socket` - my retransmit socket + /// * `ledger_path` - path to the ledger file + #[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] + pub fn new( + keypair: Arc, + vote_account_keypair: Arc, + bank: &Arc, + entry_height: u64, + last_entry_id: Hash, + cluster_info: Arc>, + window: SharedWindow, + replicate_sockets: Vec, + repair_socket: UdpSocket, + retransmit_socket: UdpSocket, + ledger_path: Option<&str>, + ) -> Self { + let exit = Arc::new(AtomicBool::new(false)); + + let repair_socket = Arc::new(repair_socket); + let mut blob_sockets: Vec> = + replicate_sockets.into_iter().map(Arc::new).collect(); + blob_sockets.push(repair_socket.clone()); + let (fetch_stage, blob_fetch_receiver) = + BlobFetchStage::new_multi_socket(blob_sockets, exit.clone()); + //TODO + //the packets coming out of blob_receiver need to be sent to the GPU and verified + //then sent to the window, which does the erasure coding reconstruction + let (retransmit_stage, blob_window_receiver) = RetransmitStage::new( + &cluster_info, + window, + bank.tick_height(), + entry_height, + Arc::new(retransmit_socket), + repair_socket, + blob_fetch_receiver, + bank.leader_scheduler.clone(), + ); + + let (replicate_stage, ledger_entry_receiver) = ReplicateStage::new( + keypair.clone(), + vote_account_keypair, + bank.clone(), + cluster_info, + blob_window_receiver, + exit.clone(), + entry_height, + last_entry_id, + ); + + let (ledger_write_stage, storage_entry_receiver) = + LedgerWriteStage::new(ledger_path, ledger_entry_receiver); + + let storage_state = StorageState::new(); + let storage_stage = StorageStage::new( + &storage_state, + storage_entry_receiver, + ledger_path, + keypair, + exit.clone(), + entry_height, + ); + + Tvu { + replicate_stage, + fetch_stage, + retransmit_stage, + ledger_write_stage, + storage_stage, + exit, + } + } + + pub fn is_exited(&self) -> bool { + self.exit.load(Ordering::Relaxed) + } + + pub fn exit(&self) { + self.exit.store(true, Ordering::Relaxed); + } + + pub fn close(self) -> thread::Result> { + self.fetch_stage.close(); + self.join() + } +} + +impl Service for Tvu { + type JoinReturnType = Option; + + fn join(self) -> thread::Result> { + self.retransmit_stage.join()?; + self.fetch_stage.join()?; + self.ledger_write_stage.join()?; + self.storage_stage.join()?; + match self.replicate_stage.join()? { + Some(ReplicateStageReturnType::LeaderRotation( + tick_height, + entry_height, + last_entry_id, + )) => Ok(Some(TvuReturnType::LeaderRotation( + tick_height, + entry_height, + last_entry_id, + ))), + _ => Ok(None), + } + } +} + +#[cfg(test)] +pub mod tests { + use bank::Bank; + use bincode::serialize; + use cluster_info::{ClusterInfo, Node}; + use entry::Entry; + use leader_scheduler::LeaderScheduler; + use logger; + use mint::Mint; + use ncp::Ncp; + use packet::SharedBlob; + use service::Service; + use signature::{Keypair, KeypairUtil}; + use solana_sdk::hash::Hash; + use std::net::UdpSocket; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::mpsc::channel; + use std::sync::{Arc, RwLock}; + use std::time::Duration; + use streamer; + use system_transaction::SystemTransaction; + use transaction::Transaction; + use tvu::Tvu; + use window::{self, SharedWindow}; + + fn new_ncp( + cluster_info: Arc>, + gossip: UdpSocket, + exit: Arc, + ) -> (Ncp, SharedWindow) { + let window = Arc::new(RwLock::new(window::default_window())); + let ncp = Ncp::new(&cluster_info, window.clone(), None, gossip, exit); + (ncp, window) + } + + /// Test that message sent from leader to target1 and replicated to target2 + #[test] + #[ignore] + fn test_replicate() { + logger::setup(); + let leader = Node::new_localhost(); + let target1_keypair = Keypair::new(); + let target1 = Node::new_localhost_with_pubkey(target1_keypair.pubkey()); + let target2 = Node::new_localhost(); + let exit = Arc::new(AtomicBool::new(false)); + + //start cluster_info_l + let mut cluster_info_l = ClusterInfo::new(leader.info.clone()); + cluster_info_l.set_leader(leader.info.id); + + let cref_l = Arc::new(RwLock::new(cluster_info_l)); + let dr_l = new_ncp(cref_l, leader.sockets.gossip, exit.clone()); + + //start cluster_info2 + let mut cluster_info2 = ClusterInfo::new(target2.info.clone()); + cluster_info2.insert_info(leader.info.clone()); + cluster_info2.set_leader(leader.info.id); + let leader_id = leader.info.id; + let cref2 = Arc::new(RwLock::new(cluster_info2)); + let dr_2 = new_ncp(cref2, target2.sockets.gossip, exit.clone()); + + // setup some blob services to send blobs into the socket + // to simulate the source peer and get blobs out of the socket to + // simulate target peer + let (s_reader, r_reader) = channel(); + let blob_sockets: Vec> = target2 + .sockets + .replicate + .into_iter() + .map(Arc::new) + .collect(); + + let t_receiver = streamer::blob_receiver(blob_sockets[0].clone(), exit.clone(), s_reader); + + // simulate leader sending messages + let (s_responder, r_responder) = channel(); + let t_responder = streamer::responder( + "test_replicate", + Arc::new(leader.sockets.retransmit), + r_responder, + ); + + let starting_balance = 10_000; + let mint = Mint::new(starting_balance); + let replicate_addr = target1.info.tvu; + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + leader_id, + ))); + let mut bank = Bank::new(&mint); + bank.leader_scheduler = leader_scheduler; + let bank = Arc::new(bank); + + //start cluster_info1 + let mut cluster_info1 = ClusterInfo::new(target1.info.clone()); + cluster_info1.insert_info(leader.info.clone()); + cluster_info1.set_leader(leader.info.id); + let cref1 = Arc::new(RwLock::new(cluster_info1)); + let dr_1 = new_ncp(cref1.clone(), target1.sockets.gossip, exit.clone()); + + let vote_account_keypair = Arc::new(Keypair::new()); + let mut cur_hash = Hash::default(); + let tvu = Tvu::new( + Arc::new(target1_keypair), + vote_account_keypair, + &bank, + 0, + cur_hash, + cref1, + dr_1.1, + target1.sockets.replicate, + target1.sockets.repair, + target1.sockets.retransmit, + None, + ); + + let mut alice_ref_balance = starting_balance; + let mut msgs = Vec::new(); + let mut blob_idx = 0; + let num_transfers = 10; + let transfer_amount = 501; + let bob_keypair = Keypair::new(); + for i in 0..num_transfers { + let entry0 = Entry::new(&cur_hash, i, vec![]); + cur_hash = entry0.id; + bank.register_tick(&cur_hash); + let entry_tick0 = Entry::new(&cur_hash, i + 1, vec![]); + cur_hash = entry_tick0.id; + + let tx0 = Transaction::system_new( + &mint.keypair(), + bob_keypair.pubkey(), + transfer_amount, + cur_hash, + ); + bank.register_tick(&cur_hash); + let entry_tick1 = Entry::new(&cur_hash, i + 1, vec![]); + cur_hash = entry_tick1.id; + let entry1 = Entry::new(&cur_hash, i + num_transfers, vec![tx0]); + bank.register_tick(&entry1.id); + let entry_tick2 = Entry::new(&entry1.id, i + 1, vec![]); + cur_hash = entry_tick2.id; + + alice_ref_balance -= transfer_amount; + + for entry in vec![entry0, entry_tick0, entry_tick1, entry1, entry_tick2] { + let mut b = SharedBlob::default(); + { + let mut w = b.write().unwrap(); + w.set_index(blob_idx).unwrap(); + blob_idx += 1; + w.set_id(&leader_id).unwrap(); + + let serialized_entry = serialize(&entry).unwrap(); + + w.data_mut()[..serialized_entry.len()].copy_from_slice(&serialized_entry); + w.set_size(serialized_entry.len()); + w.meta.set_addr(&replicate_addr); + } + msgs.push(b); + } + } + + // send the blobs into the socket + s_responder.send(msgs).expect("send"); + drop(s_responder); + + // receive retransmitted messages + let timer = Duration::new(1, 0); + while let Ok(_msg) = r_reader.recv_timeout(timer) { + trace!("got msg"); + } + + let alice_balance = bank.get_balance(&mint.keypair().pubkey()); + assert_eq!(alice_balance, alice_ref_balance); + + let bob_balance = bank.get_balance(&bob_keypair.pubkey()); + assert_eq!(bob_balance, starting_balance - alice_ref_balance); + + tvu.close().expect("close"); + exit.store(true, Ordering::Relaxed); + dr_l.0.join().expect("join"); + dr_2.0.join().expect("join"); + dr_1.0.join().expect("join"); + t_receiver.join().expect("join"); + t_responder.join().expect("join"); + } +} diff --git a/book/vdf.html b/book/vdf.html new file mode 100644 index 00000000000000..5ea4f5442901e5 --- /dev/null +++ b/book/vdf.html @@ -0,0 +1,214 @@ + + + + + + Introduction to VDFs - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

Introduction to VDFs

+

A Verifiable Delay Function is conceptually a water clock where its water marks +can be recorded and later verified that the water most certainly passed +through. Anatoly describes the water clock analogy in detail here:

+

water clock analogy

+

The same technique has been used in Bitcoin since day one. The Bitcoin feature +is called nLocktime and it can be used to postdate transactions using block +height instead of a timestamp. As a Bitcoin client, you'd use block height +instead of a timestamp if you don't trust the network. Block height turns out +to be an instance of what's being called a Verifiable Delay Function in +cryptography circles. It's a cryptographically secure way to say time has +passed. In Solana, we use a far more granular verifiable delay function, a SHA +256 hash chain, to checkpoint the ledger and coordinate consensus. With it, we +implement Optimistic Concurrency Control and are now well en route towards that +theoretical limit of 710,000 transactions per second.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/vote_program.rs b/book/vote_program.rs new file mode 100644 index 00000000000000..1c7edf97364882 --- /dev/null +++ b/book/vote_program.rs @@ -0,0 +1,175 @@ +//! Vote program +//! Receive and processes votes from validators + +use bincode::{deserialize, serialize}; +use byteorder::{ByteOrder, LittleEndian}; +use solana_sdk::account::Account; +use solana_sdk::pubkey::Pubkey; +use std; +use std::collections::VecDeque; +use std::mem; +use transaction::Transaction; + +// Maximum number of votes to keep around +const MAX_VOTE_HISTORY: usize = 32; + +#[derive(Debug, PartialEq)] +pub enum Error { + UserdataDeserializeFailure, + InvalidArguments, + InvalidUserdata, + UserdataTooSmall, +} +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "error") + } +} +pub type Result = std::result::Result; + +#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)] +pub struct Vote { + // TODO: add signature of the state here as well + /// A vote for height tick_height + pub tick_height: u64, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub enum VoteInstruction { + /// Register a new "vote account" to represent a particular validator in the Vote Contract, + /// and initialize the VoteState for this "vote account" + /// * Transaction::keys[0] - the validator id + /// * Transaction::keys[1] - the new "vote account" to be associated with the validator + /// identified by keys[0] for voting + RegisterAccount, + NewVote(Vote), +} + +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +pub struct VoteProgram { + pub votes: VecDeque, + pub node_id: Pubkey, +} + +const VOTE_PROGRAM_ID: [u8; 32] = [ + 132, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +]; + +impl VoteProgram { + pub fn check_id(program_id: &Pubkey) -> bool { + program_id.as_ref() == VOTE_PROGRAM_ID + } + + pub fn id() -> Pubkey { + Pubkey::new(&VOTE_PROGRAM_ID) + } + + pub fn deserialize(input: &[u8]) -> Result { + let len = LittleEndian::read_u16(&input[0..2]) as usize; + + if len == 0 || input.len() < len + 2 { + Err(Error::InvalidUserdata) + } else { + deserialize(&input[2..=len + 1]).map_err(|err| { + error!("Unable to deserialize vote state: {:?}", err); + Error::InvalidUserdata + }) + } + } + + pub fn serialize(self: &VoteProgram, output: &mut [u8]) -> Result<()> { + let self_serialized = serialize(self).unwrap(); + + if output.len() + 2 < self_serialized.len() { + warn!( + "{} bytes required to serialize but only have {} bytes", + self_serialized.len(), + output.len() + 2, + ); + return Err(Error::UserdataTooSmall); + } + + let serialized_len = self_serialized.len() as u16; + LittleEndian::write_u16(&mut output[0..2], serialized_len); + output[2..=serialized_len as usize + 1].clone_from_slice(&self_serialized); + Ok(()) + } + + pub fn process_transaction( + tx: &Transaction, + instruction_index: usize, + accounts: &mut [&mut Account], + ) -> Result<()> { + match deserialize(tx.userdata(instruction_index)) { + Ok(VoteInstruction::RegisterAccount) => { + // TODO: a single validator could register multiple "vote accounts" + // which would clutter the "accounts" structure. See github issue 1654. + accounts[1].owner = Self::id(); + + let mut vote_state = VoteProgram { + votes: VecDeque::new(), + node_id: *tx.from(), + }; + + vote_state.serialize(&mut accounts[1].userdata)?; + + Ok(()) + } + Ok(VoteInstruction::NewVote(vote)) => { + if !Self::check_id(&accounts[0].owner) { + error!("accounts[0] is not assigned to the VOTE_PROGRAM"); + Err(Error::InvalidArguments)?; + } + + let mut vote_state = Self::deserialize(&accounts[0].userdata)?; + + // TODO: Integrity checks + // a) Verify the vote's bank hash matches what is expected + // b) Verify vote is older than previous votes + + // Only keep around the most recent MAX_VOTE_HISTORY votes + if vote_state.votes.len() == MAX_VOTE_HISTORY { + vote_state.votes.pop_front(); + } + + vote_state.votes.push_back(vote); + vote_state.serialize(&mut accounts[0].userdata)?; + + Ok(()) + } + Err(_) => { + info!( + "Invalid vote transaction userdata: {:?}", + tx.userdata(instruction_index) + ); + Err(Error::UserdataDeserializeFailure) + } + } + } + + pub fn get_max_size() -> usize { + // Upper limit on the size of the Vote State. Equal to + // sizeof(VoteProgram) + MAX_VOTE_HISTORY * sizeof(Vote) + + // 32 (the size of the Pubkey) + 2 (2 bytes for the size) + mem::size_of::() + + MAX_VOTE_HISTORY * mem::size_of::() + + mem::size_of::() + + mem::size_of::() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_serde() -> Result<()> { + let mut buffer: Vec = vec![0; VoteProgram::get_max_size()]; + let mut vote_program = VoteProgram::default(); + vote_program.votes = (0..MAX_VOTE_HISTORY).map(|_| Vote::default()).collect(); + vote_program.serialize(&mut buffer).unwrap(); + assert_eq!(VoteProgram::deserialize(&buffer).unwrap(), vote_program); + Ok(()) + } +} diff --git a/book/vote_stage.rs b/book/vote_stage.rs new file mode 100644 index 00000000000000..430ed2f04a8785 --- /dev/null +++ b/book/vote_stage.rs @@ -0,0 +1,82 @@ +//! The `vote_stage` votes on the `last_id` of the bank at a regular cadence + +use bank::Bank; +use bincode::serialize; +use cluster_info::ClusterInfo; +use counter::Counter; +use log::Level; +use packet::SharedBlob; +use result::{Error, Result}; +use signature::Keypair; +use solana_sdk::hash::Hash; +use std::net::SocketAddr; +use std::sync::atomic::AtomicUsize; +use std::sync::{Arc, RwLock}; +use streamer::BlobSender; +use transaction::Transaction; +use vote_program::Vote; +use vote_transaction::VoteTransaction; + +#[derive(Debug, PartialEq, Eq)] +pub enum VoteError { + NoValidSupermajority, + NoLeader, + LeaderInfoNotFound, +} + +// TODO: Change voting to be on fixed tick intervals based on bank state +pub fn create_new_signed_vote_blob( + last_id: &Hash, + vote_account: &Keypair, + bank: &Arc, + cluster_info: &Arc>, +) -> Result { + let shared_blob = SharedBlob::default(); + let tick_height = bank.tick_height(); + + let leader_tpu = get_leader_tpu(&bank, cluster_info)?; + //TODO: doesn't seem like there is a synchronous call to get height and id + debug!("voting on {:?}", &last_id.as_ref()[..8]); + let vote = Vote { tick_height }; + let tx = Transaction::vote_new(&vote_account, vote, *last_id, 0); + { + let mut blob = shared_blob.write().unwrap(); + let bytes = serialize(&tx)?; + let len = bytes.len(); + blob.data[..len].copy_from_slice(&bytes); + blob.meta.set_addr(&leader_tpu); + blob.meta.size = len; + }; + + Ok(shared_blob) +} + +fn get_leader_tpu(bank: &Bank, cluster_info: &Arc>) -> Result { + let leader_id = match bank.get_current_leader() { + Some((leader_id, _)) => leader_id, + None => return Err(Error::VoteError(VoteError::NoLeader)), + }; + + let rcluster_info = cluster_info.read().unwrap(); + let leader_tpu = rcluster_info.lookup(leader_id).map(|leader| leader.tpu); + if let Some(leader_tpu) = leader_tpu { + Ok(leader_tpu) + } else { + Err(Error::VoteError(VoteError::LeaderInfoNotFound)) + } +} + +pub fn send_validator_vote( + bank: &Arc, + vote_account: &Keypair, + cluster_info: &Arc>, + vote_blob_sender: &BlobSender, +) -> Result<()> { + let last_id = bank.last_id(); + + let shared_blob = create_new_signed_vote_blob(&last_id, vote_account, bank, cluster_info)?; + inc_new_counter_info!("replicate-vote_sent", 1); + vote_blob_sender.send(vec![shared_blob])?; + + Ok(()) +} diff --git a/book/vote_transaction.rs b/book/vote_transaction.rs new file mode 100644 index 00000000000000..c53b6dc9020780 --- /dev/null +++ b/book/vote_transaction.rs @@ -0,0 +1,118 @@ +//! The `vote_transaction` module provides functionality for creating vote transactions. + +#[cfg(test)] +use bank::Bank; +use bincode::deserialize; +#[cfg(test)] +use result::Result; +use signature::Keypair; +#[cfg(test)] +use signature::KeypairUtil; +use solana_sdk::hash::Hash; +use solana_sdk::pubkey::Pubkey; +use system_transaction::SystemTransaction; +use transaction::Transaction; +use vote_program::{Vote, VoteInstruction, VoteProgram}; + +pub trait VoteTransaction { + fn vote_new(vote_account: &Keypair, vote: Vote, last_id: Hash, fee: u64) -> Self; + fn vote_account_new( + validator_id: &Keypair, + new_vote_account_id: Pubkey, + last_id: Hash, + num_tokens: u64, + ) -> Self; + fn vote_account_register( + validator_id: &Keypair, + vote_account_id: Pubkey, + last_id: Hash, + fee: u64, + ) -> Self; + fn get_votes(&self) -> Vec<(Pubkey, Vote, Hash)>; +} + +impl VoteTransaction for Transaction { + fn vote_new(vote_account: &Keypair, vote: Vote, last_id: Hash, fee: u64) -> Self { + let instruction = VoteInstruction::NewVote(vote); + Transaction::new( + vote_account, + &[], + VoteProgram::id(), + &instruction, + last_id, + fee, + ) + } + + fn vote_account_new( + validator_id: &Keypair, + new_vote_account_id: Pubkey, + last_id: Hash, + num_tokens: u64, + ) -> Self { + Transaction::system_create( + validator_id, + new_vote_account_id, + last_id, + num_tokens, + VoteProgram::get_max_size() as u64, + VoteProgram::id(), + 0, + ) + } + + fn vote_account_register( + validator_id: &Keypair, + vote_account_id: Pubkey, + last_id: Hash, + fee: u64, + ) -> Self { + let register_tx = VoteInstruction::RegisterAccount; + Transaction::new( + validator_id, + &[vote_account_id], + VoteProgram::id(), + ®ister_tx, + last_id, + fee, + ) + } + + fn get_votes(&self) -> Vec<(Pubkey, Vote, Hash)> { + let mut votes = vec![]; + for i in 0..self.instructions.len() { + let tx_program_id = self.program_id(i); + if VoteProgram::check_id(&tx_program_id) { + if let Ok(Some(VoteInstruction::NewVote(vote))) = deserialize(&self.userdata(i)) { + votes.push((self.account_keys[0], vote, self.last_id)) + } + } + } + votes + } +} + +#[cfg(test)] +pub fn create_vote_account( + node_keypair: &Keypair, + bank: &Bank, + num_tokens: u64, + last_id: Hash, +) -> Result { + let new_vote_account = Keypair::new(); + + // Create the new vote account + let tx = + Transaction::vote_account_new(node_keypair, new_vote_account.pubkey(), last_id, num_tokens); + bank.process_transaction(&tx)?; + + // Register the vote account to the validator + let tx = + Transaction::vote_account_register(node_keypair, new_vote_account.pubkey(), last_id, 0); + bank.process_transaction(&tx)?; + + Ok(new_vote_account) +} + +#[cfg(test)] +mod tests {} diff --git a/book/wallet.html b/book/wallet.html new file mode 100644 index 00000000000000..a866d614004c99 --- /dev/null +++ b/book/wallet.html @@ -0,0 +1,473 @@ + + + + + + solana-wallet CLI - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+

solana-wallet CLI

+

The solana crate is distributed with a command-line interface tool

+

Examples

+

Get Pubkey

+
// Command
+$ solana-wallet address
+
+// Return
+<PUBKEY>
+
+

Airdrop Tokens

+
// Command
+$ solana-wallet airdrop 123
+
+// Return
+"Your balance is: 123"
+
+

Get Balance

+
// Command
+$ solana-wallet balance
+
+// Return
+"Your balance is: 123"
+
+

Confirm Transaction

+
// Command
+$ solana-wallet confirm <TX_SIGNATURE>
+
+// Return
+"Confirmed" / "Not found"
+
+

Deploy program

+
// Command
+$ solana-wallet deploy <PATH>
+
+// Return
+<PROGRAM_ID>
+
+

Unconditional Immediate Transfer

+
// Command
+$ solana-wallet pay <PUBKEY> 123
+
+// Return
+<TX_SIGNATURE>
+
+

Post-Dated Transfer

+
// Command
+$ solana-wallet pay <PUBKEY> 123 \
+    --after 2018-12-24T23:59:00 --require-timestamp-from <PUBKEY>
+
+// Return
+{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
+
+

require-timestamp-from is optional. If not provided, the transaction will expect a timestamp signed by this wallet's secret key

+

Authorized Transfer

+

A third party must send a signature to unlock the tokens.

+
// Command
+$ solana-wallet pay <PUBKEY> 123 \
+    --require-signature-from <PUBKEY>
+
+// Return
+{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
+
+

Post-Dated and Authorized Transfer

+
// Command
+$ solana-wallet pay <PUBKEY> 123 \
+    --after 2018-12-24T23:59 --require-timestamp-from <PUBKEY> \
+    --require-signature-from <PUBKEY>
+
+// Return
+{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
+
+

Multiple Witnesses

+
// Command
+$ solana-wallet pay <PUBKEY> 123 \
+    --require-signature-from <PUBKEY> \
+    --require-signature-from <PUBKEY>
+
+// Return
+{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
+
+

Cancelable Transfer

+
// Command
+$ solana-wallet pay <PUBKEY> 123 \
+    --require-signature-from <PUBKEY> \
+    --cancelable
+
+// Return
+{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
+
+

Cancel Transfer

+
// Command
+$ solana-wallet cancel <PROCESS_ID>
+
+// Return
+<TX_SIGNATURE>
+
+

Send Signature

+
// Command
+$ solana-wallet send-signature <PUBKEY> <PROCESS_ID>
+
+// Return
+<TX_SIGNATURE>
+
+

Indicate Elapsed Time

+

Use the current system time:

+
// Command
+$ solana-wallet send-timestamp <PUBKEY> <PROCESS_ID>
+
+// Return
+<TX_SIGNATURE>
+
+

Or specify some other arbitrary timestamp:

+
// Command
+$ solana-wallet send-timestamp <PUBKEY> <PROCESS_ID> --date 2018-12-24T23:59:00
+
+// Return
+<TX_SIGNATURE>
+
+

Usage

+
solana-wallet 0.11.0
+
+USAGE:
+    solana-wallet [OPTIONS] [SUBCOMMAND]
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+OPTIONS:
+    -k, --keypair <PATH>         /path/to/id.json
+    -n, --network <HOST:PORT>    Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001
+        --proxy <URL>            Address of TLS proxy
+        --port <NUM>             Optional rpc-port configuration to connect to non-default nodes
+        --timeout <SECS>         Max seconds to wait to get necessary gossip from the network
+
+SUBCOMMANDS:
+    address                  Get your public key
+    airdrop                  Request a batch of tokens
+    balance                  Get your balance
+    cancel                   Cancel a transfer
+    confirm                  Confirm transaction by signature
+    deploy                   Deploy a program
+    get-transaction-count    Get current transaction count
+    help                     Prints this message or the help of the given subcommand(s)
+    pay                      Send a payment
+    send-signature           Send a signature to authorize a transfer
+    send-timestamp           Send a timestamp to unlock a transfer
+
+
solana-wallet-address 
+Get your public key
+
+USAGE:
+    solana-wallet address
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+
solana-wallet-airdrop 
+Request a batch of tokens
+
+USAGE:
+    solana-wallet airdrop <NUM>
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+ARGS:
+    <NUM>    The number of tokens to request
+
+
solana-wallet-balance 
+Get your balance
+
+USAGE:
+    solana-wallet balance
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+
solana-wallet-cancel 
+Cancel a transfer
+
+USAGE:
+    solana-wallet cancel <PROCESS_ID>
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+ARGS:
+    <PROCESS_ID>    The process id of the transfer to cancel
+
+
solana-wallet-confirm 
+Confirm transaction by signature
+
+USAGE:
+    solana-wallet confirm <SIGNATURE>
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+ARGS:
+    <SIGNATURE>    The transaction signature to confirm
+
+
solana-wallet-deploy 
+Deploy a program
+
+USAGE:
+    solana-wallet deploy <PATH>
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+ARGS:
+    <PATH>    /path/to/program.o
+
+
solana-wallet-get-transaction-count 
+Get current transaction count
+
+USAGE:
+    solana-wallet get-transaction-count
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+
solana-wallet-pay 
+Send a payment
+
+USAGE:
+    solana-wallet pay [FLAGS] [OPTIONS] <PUBKEY> <NUM>
+
+FLAGS:
+        --cancelable    
+    -h, --help          Prints help information
+    -V, --version       Prints version information
+
+OPTIONS:
+        --after <DATETIME>                      A timestamp after which transaction will execute
+        --require-timestamp-from <PUBKEY>       Require timestamp from this third party
+        --require-signature-from <PUBKEY>...    Any third party signatures required to unlock the tokens
+
+ARGS:
+    <PUBKEY>    The pubkey of recipient
+    <NUM>       The number of tokens to send
+
+
solana-wallet-send-signature 
+Send a signature to authorize a transfer
+
+USAGE:
+    solana-wallet send-signature <PUBKEY> <PROCESS_ID>
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+ARGS:
+    <PUBKEY>        The pubkey of recipient
+    <PROCESS_ID>    The process id of the transfer to authorize
+
+
solana-wallet-send-timestamp 
+Send a timestamp to unlock a transfer
+
+USAGE:
+    solana-wallet send-timestamp [OPTIONS] <PUBKEY> <PROCESS_ID>
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+OPTIONS:
+        --date <DATETIME>    Optional arbitrary timestamp to apply
+
+ARGS:
+    <PUBKEY>        The pubkey of recipient
+    <PROCESS_ID>    The process id of the transfer to unlock
+
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/wallet.rs b/book/wallet.rs new file mode 100644 index 00000000000000..b7b451be0df9ec --- /dev/null +++ b/book/wallet.rs @@ -0,0 +1,1610 @@ +use bincode::serialize; +use bpf_loader; +use bs58; +use budget_program::BudgetState; +use budget_transaction::BudgetTransaction; +use chrono::prelude::*; +use clap::ArgMatches; +use elf; +use fullnode::Config; +use loader_transaction::LoaderTransaction; +use ring::rand::SystemRandom; +use ring::signature::Ed25519KeyPair; +use rpc::RpcSignatureStatus; +use rpc_request::RpcRequest; +use serde_json; +use signature::{Keypair, KeypairUtil, Signature}; +use solana_drone::drone::{request_airdrop_transaction, DRONE_PORT}; +use solana_sdk::hash::Hash; +use solana_sdk::pubkey::Pubkey; +use std::fs::{self, File}; +use std::io::Write; +use std::net::{Ipv4Addr, SocketAddr}; +use std::path::Path; +use std::str::FromStr; +use std::{error, fmt, mem}; +use system_transaction::SystemTransaction; +use thin_client::poll_gossip_for_leader; +use transaction::Transaction; + +const PLATFORM_SECTION_C: &str = ".text.entrypoint"; +const USERDATA_CHUNK_SIZE: usize = 256; + +#[derive(Debug, PartialEq)] +pub enum WalletCommand { + Address, + AirDrop(u64), + Balance, + Cancel(Pubkey), + Confirm(Signature), + Deploy(String), + GetTransactionCount, + // Pay(tokens, to, timestamp, timestamp_pubkey, witness(es), cancelable) + Pay( + u64, + Pubkey, + Option>, + Option, + Option>, + Option, + ), + // TimeElapsed(to, process_id, timestamp) + TimeElapsed(Pubkey, Pubkey, DateTime), + // Witness(to, process_id) + Witness(Pubkey, Pubkey), +} + +#[derive(Debug, Clone)] +pub enum WalletError { + CommandNotRecognized(String), + BadParameter(String), + DynamicProgramError(String), + RpcRequestError(String), +} + +impl fmt::Display for WalletError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "invalid") + } +} + +impl error::Error for WalletError { + fn description(&self) -> &str { + "invalid" + } + + fn cause(&self) -> Option<&error::Error> { + // Generic error, underlying cause isn't tracked. + None + } +} + +pub struct WalletConfig { + pub id: Keypair, + pub command: WalletCommand, + pub network: SocketAddr, + pub timeout: Option, + pub proxy: Option, + pub drone_port: Option, +} + +impl Default for WalletConfig { + fn default() -> WalletConfig { + let default_addr = socketaddr!(0, 8000); + WalletConfig { + id: Keypair::new(), + command: WalletCommand::Balance, + network: default_addr, + timeout: None, + proxy: None, + drone_port: None, + } + } +} + +impl WalletConfig { + pub fn drone_addr(&self, tpu_addr: SocketAddr) -> SocketAddr { + let mut drone_addr = tpu_addr; + drone_addr.set_port(self.drone_port.unwrap_or(DRONE_PORT)); + drone_addr + } + + pub fn rpc_addr(&self, rpc_addr: SocketAddr) -> String { + let rpc_addr_str = format!("http://{}", rpc_addr.to_string()); + self.proxy.clone().unwrap_or(rpc_addr_str) + } +} + +pub fn parse_command( + pubkey: Pubkey, + matches: &ArgMatches, +) -> Result> { + let response = match matches.subcommand() { + ("address", Some(_address_matches)) => Ok(WalletCommand::Address), + ("airdrop", Some(airdrop_matches)) => { + let tokens = airdrop_matches.value_of("tokens").unwrap().parse()?; + Ok(WalletCommand::AirDrop(tokens)) + } + ("balance", Some(_balance_matches)) => Ok(WalletCommand::Balance), + ("cancel", Some(cancel_matches)) => { + let pubkey_vec = bs58::decode(cancel_matches.value_of("process-id").unwrap()) + .into_vec() + .expect("base58-encoded public key"); + + if pubkey_vec.len() != mem::size_of::() { + eprintln!("{}", cancel_matches.usage()); + Err(WalletError::BadParameter("Invalid public key".to_string()))?; + } + let process_id = Pubkey::new(&pubkey_vec); + Ok(WalletCommand::Cancel(process_id)) + } + ("confirm", Some(confirm_matches)) => { + let signatures = bs58::decode(confirm_matches.value_of("signature").unwrap()) + .into_vec() + .expect("base58-encoded signature"); + + if signatures.len() == mem::size_of::() { + let signature = Signature::new(&signatures); + Ok(WalletCommand::Confirm(signature)) + } else { + eprintln!("{}", confirm_matches.usage()); + Err(WalletError::BadParameter("Invalid signature".to_string())) + } + } + ("deploy", Some(deploy_matches)) => Ok(WalletCommand::Deploy( + deploy_matches + .value_of("program-location") + .unwrap() + .to_string(), + )), + ("get-transaction-count", Some(_matches)) => Ok(WalletCommand::GetTransactionCount), + ("pay", Some(pay_matches)) => { + let tokens = pay_matches.value_of("tokens").unwrap().parse()?; + let to = if pay_matches.is_present("to") { + let pubkey_vec = bs58::decode(pay_matches.value_of("to").unwrap()) + .into_vec() + .expect("base58-encoded public key"); + + if pubkey_vec.len() != mem::size_of::() { + eprintln!("{}", pay_matches.usage()); + Err(WalletError::BadParameter( + "Invalid to public key".to_string(), + ))?; + } + Pubkey::new(&pubkey_vec) + } else { + pubkey + }; + let timestamp = if pay_matches.is_present("timestamp") { + // Parse input for serde_json + let date_string = if !pay_matches.value_of("timestamp").unwrap().contains('Z') { + format!("\"{}Z\"", pay_matches.value_of("timestamp").unwrap()) + } else { + format!("\"{}\"", pay_matches.value_of("timestamp").unwrap()) + }; + Some(serde_json::from_str(&date_string)?) + } else { + None + }; + let timestamp_pubkey = if pay_matches.is_present("timestamp-pubkey") { + let pubkey_vec = bs58::decode(pay_matches.value_of("timestamp-pubkey").unwrap()) + .into_vec() + .expect("base58-encoded public key"); + + if pubkey_vec.len() != mem::size_of::() { + eprintln!("{}", pay_matches.usage()); + Err(WalletError::BadParameter( + "Invalid timestamp public key".to_string(), + ))?; + } + Some(Pubkey::new(&pubkey_vec)) + } else { + None + }; + let witness_vec = if pay_matches.is_present("witness") { + let witnesses = pay_matches.values_of("witness").unwrap(); + let mut collection = Vec::new(); + for witness in witnesses { + let pubkey_vec = bs58::decode(witness) + .into_vec() + .expect("base58-encoded public key"); + + if pubkey_vec.len() != mem::size_of::() { + eprintln!("{}", pay_matches.usage()); + Err(WalletError::BadParameter( + "Invalid witness public key".to_string(), + ))?; + } + collection.push(Pubkey::new(&pubkey_vec)); + } + Some(collection) + } else { + None + }; + let cancelable = if pay_matches.is_present("cancelable") { + Some(pubkey) + } else { + None + }; + + Ok(WalletCommand::Pay( + tokens, + to, + timestamp, + timestamp_pubkey, + witness_vec, + cancelable, + )) + } + ("send-signature", Some(sig_matches)) => { + let pubkey_vec = bs58::decode(sig_matches.value_of("to").unwrap()) + .into_vec() + .expect("base58-encoded public key"); + + if pubkey_vec.len() != mem::size_of::() { + eprintln!("{}", sig_matches.usage()); + Err(WalletError::BadParameter("Invalid public key".to_string()))?; + } + let to = Pubkey::new(&pubkey_vec); + + let pubkey_vec = bs58::decode(sig_matches.value_of("process-id").unwrap()) + .into_vec() + .expect("base58-encoded public key"); + + if pubkey_vec.len() != mem::size_of::() { + eprintln!("{}", sig_matches.usage()); + Err(WalletError::BadParameter("Invalid public key".to_string()))?; + } + let process_id = Pubkey::new(&pubkey_vec); + Ok(WalletCommand::Witness(to, process_id)) + } + ("send-timestamp", Some(timestamp_matches)) => { + let pubkey_vec = bs58::decode(timestamp_matches.value_of("to").unwrap()) + .into_vec() + .expect("base58-encoded public key"); + + if pubkey_vec.len() != mem::size_of::() { + eprintln!("{}", timestamp_matches.usage()); + Err(WalletError::BadParameter("Invalid public key".to_string()))?; + } + let to = Pubkey::new(&pubkey_vec); + + let pubkey_vec = bs58::decode(timestamp_matches.value_of("process-id").unwrap()) + .into_vec() + .expect("base58-encoded public key"); + + if pubkey_vec.len() != mem::size_of::() { + eprintln!("{}", timestamp_matches.usage()); + Err(WalletError::BadParameter("Invalid public key".to_string()))?; + } + let process_id = Pubkey::new(&pubkey_vec); + let dt = if timestamp_matches.is_present("datetime") { + // Parse input for serde_json + let date_string = if !timestamp_matches + .value_of("datetime") + .unwrap() + .contains('Z') + { + format!("\"{}Z\"", timestamp_matches.value_of("datetime").unwrap()) + } else { + format!("\"{}\"", timestamp_matches.value_of("datetime").unwrap()) + }; + serde_json::from_str(&date_string)? + } else { + Utc::now() + }; + Ok(WalletCommand::TimeElapsed(to, process_id, dt)) + } + ("", None) => { + eprintln!("{}", matches.usage()); + Err(WalletError::CommandNotRecognized( + "no subcommand given".to_string(), + )) + } + _ => unreachable!(), + }?; + Ok(response) +} + +pub fn process_command(config: &WalletConfig) -> Result> { + if let WalletCommand::Address = config.command { + // Get address of this client + return Ok(format!("{}", config.id.pubkey())); + } + + let leader = poll_gossip_for_leader(config.network, config.timeout)?; + let tpu_addr = leader.tpu; + let drone_addr = config.drone_addr(tpu_addr); + let rpc_addr = config.rpc_addr(leader.rpc); + + match config.command { + // Get address of this client + WalletCommand::Address => unreachable!(), + // Request an airdrop from Solana Drone; + WalletCommand::AirDrop(tokens) => { + println!( + "Requesting airdrop of {:?} tokens from {}", + tokens, drone_addr + ); + let params = json!([format!("{}", config.id.pubkey())]); + let previous_balance = match RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params))? + .as_u64() + { + Some(tokens) => tokens, + None => Err(WalletError::RpcRequestError( + "Received result of an unexpected type".to_string(), + ))?, + }; + + let last_id = get_last_id(&rpc_addr)?; + let transaction = + request_airdrop_transaction(&drone_addr, &config.id.pubkey(), tokens, last_id)?; + send_and_confirm_tx(&rpc_addr, &transaction)?; + + let params = json!([format!("{}", config.id.pubkey())]); + let current_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params))? + .as_u64() + .unwrap_or(previous_balance); + + if current_balance - previous_balance < tokens { + Err("Airdrop failed!")?; + } + Ok(format!("Your balance is: {:?}", current_balance)) + } + // Check client balance + WalletCommand::Balance => { + println!("Balance requested..."); + let params = json!([format!("{}", config.id.pubkey())]); + let balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params))? + .as_u64(); + match balance { + Some(0) => Ok("No account found! Request an airdrop to get started.".to_string()), + Some(tokens) => Ok(format!("Your balance is: {:?}", tokens)), + None => Err(WalletError::RpcRequestError( + "Received result of an unexpected type".to_string(), + ))?, + } + } + // Cancel a contract by contract Pubkey + WalletCommand::Cancel(pubkey) => { + let last_id = get_last_id(&rpc_addr)?; + let tx = + Transaction::budget_new_signature(&config.id, pubkey, config.id.pubkey(), last_id); + let signature_str = send_tx(&rpc_addr, &tx)?; + Ok(signature_str.to_string()) + } + // Confirm the last client transaction by signature + WalletCommand::Confirm(signature) => { + let params = json!([format!("{}", signature)]); + let confirmation = RpcRequest::ConfirmTransaction + .make_rpc_request(&rpc_addr, 1, Some(params))? + .as_bool(); + match confirmation { + Some(b) => { + if b { + Ok("Confirmed".to_string()) + } else { + Ok("Not found".to_string()) + } + } + None => Err(WalletError::RpcRequestError( + "Received result of an unexpected type".to_string(), + ))?, + } + } + // Deploy a custom program to the chain + WalletCommand::Deploy(ref program_location) => { + let params = json!([format!("{}", config.id.pubkey())]); + let balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params))? + .as_u64(); + if let Some(tokens) = balance { + if tokens < 1 { + Err(WalletError::DynamicProgramError( + "Insufficient funds".to_string(), + ))? + } + } + + let last_id = get_last_id(&rpc_addr)?; + let program = Keypair::new(); + let program_userdata = elf::File::open_path(program_location) + .map_err(|_| { + WalletError::DynamicProgramError("Could not parse program file".to_string()) + })?.get_section(PLATFORM_SECTION_C) + .ok_or_else(|| { + WalletError::DynamicProgramError( + "Could not find entrypoint in program file".to_string(), + ) + })?.data + .clone(); + + let tx = Transaction::system_create( + &config.id, + program.pubkey(), + last_id, + 1, + program_userdata.len() as u64, + bpf_loader::id(), + 0, + ); + send_and_confirm_tx(&rpc_addr, &tx).map_err(|_| { + WalletError::DynamicProgramError("Program allocate space failed".to_string()) + })?; + + let mut offset = 0; + for chunk in program_userdata.chunks(USERDATA_CHUNK_SIZE) { + let tx = Transaction::loader_write( + &program, + bpf_loader::id(), + offset, + chunk.to_vec(), + last_id, + 0, + ); + send_and_confirm_tx(&rpc_addr, &tx).map_err(|_| { + WalletError::DynamicProgramError(format!( + "Program write failed at offset {:?}", + offset + )) + })?; + offset += USERDATA_CHUNK_SIZE as u32; + } + + let last_id = get_last_id(&rpc_addr)?; + let tx = Transaction::loader_finalize(&program, bpf_loader::id(), last_id, 0); + send_and_confirm_tx(&rpc_addr, &tx).map_err(|_| { + WalletError::DynamicProgramError("Program finalize transaction failed".to_string()) + })?; + + let tx = Transaction::system_spawn(&program, last_id, 0); + send_and_confirm_tx(&rpc_addr, &tx).map_err(|_| { + WalletError::DynamicProgramError("Program spawn failed".to_string()) + })?; + + Ok(json!({ + "programId": format!("{}", program.pubkey()), + }).to_string()) + } + WalletCommand::GetTransactionCount => { + let transaction_count = RpcRequest::GetTransactionCount + .make_rpc_request(&rpc_addr, 1, None)? + .as_u64(); + match transaction_count { + Some(count) => Ok(count.to_string()), + None => Err(WalletError::RpcRequestError( + "Received result of an unexpected type".to_string(), + ))?, + } + } + // If client has positive balance, pay tokens to another address + WalletCommand::Pay(tokens, to, timestamp, timestamp_pubkey, ref witnesses, cancelable) => { + let last_id = get_last_id(&rpc_addr)?; + + if timestamp == None && *witnesses == None { + let tx = Transaction::system_new(&config.id, to, tokens, last_id); + let signature_str = send_tx(&rpc_addr, &tx)?; + Ok(signature_str.to_string()) + } else if *witnesses == None { + let dt = timestamp.unwrap(); + let dt_pubkey = match timestamp_pubkey { + Some(pubkey) => pubkey, + None => config.id.pubkey(), + }; + + let contract_funds = Keypair::new(); + let contract_state = Keypair::new(); + let budget_program_id = BudgetState::id(); + + // Create account for contract funds + let tx = Transaction::system_create( + &config.id, + contract_funds.pubkey(), + last_id, + tokens, + 0, + budget_program_id, + 0, + ); + let _signature_str = send_tx(&rpc_addr, &tx)?; + + // Create account for contract state + let tx = Transaction::system_create( + &config.id, + contract_state.pubkey(), + last_id, + 1, + 196, + budget_program_id, + 0, + ); + let _signature_str = send_tx(&rpc_addr, &tx)?; + + // Initializing contract + let tx = Transaction::budget_new_on_date( + &contract_funds, + to, + contract_state.pubkey(), + dt, + dt_pubkey, + cancelable, + tokens, + last_id, + ); + let signature_str = send_tx(&rpc_addr, &tx)?; + + Ok(json!({ + "signature": signature_str, + "processId": format!("{}", contract_state.pubkey()), + }).to_string()) + } else if timestamp == None { + let last_id = get_last_id(&rpc_addr)?; + + let witness = if let Some(ref witness_vec) = *witnesses { + witness_vec[0] + } else { + Err(WalletError::BadParameter( + "Could not parse required signature pubkey(s)".to_string(), + ))? + }; + + let contract_funds = Keypair::new(); + let contract_state = Keypair::new(); + let budget_program_id = BudgetState::id(); + + // Create account for contract funds + let tx = Transaction::system_create( + &config.id, + contract_funds.pubkey(), + last_id, + tokens, + 0, + budget_program_id, + 0, + ); + let _signature_str = send_tx(&rpc_addr, &tx)?; + + // Create account for contract state + let tx = Transaction::system_create( + &config.id, + contract_state.pubkey(), + last_id, + 1, + 196, + budget_program_id, + 0, + ); + let _signature_str = send_tx(&rpc_addr, &tx)?; + + // Initializing contract + let tx = Transaction::budget_new_when_signed( + &contract_funds, + to, + contract_state.pubkey(), + witness, + cancelable, + tokens, + last_id, + ); + let signature_str = send_tx(&rpc_addr, &tx)?; + + Ok(json!({ + "signature": signature_str, + "processId": format!("{}", contract_state.pubkey()), + }).to_string()) + } else { + Ok("Combo transactions not yet handled".to_string()) + } + } + // Apply time elapsed to contract + WalletCommand::TimeElapsed(to, pubkey, dt) => { + let params = json!(format!("{}", config.id.pubkey())); + let balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params))? + .as_u64(); + + if let Some(0) = balance { + let params = json!([format!("{}", config.id.pubkey()), 1]); + RpcRequest::RequestAirdrop + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap(); + } + + let last_id = get_last_id(&rpc_addr)?; + + let tx = Transaction::budget_new_timestamp(&config.id, pubkey, to, dt, last_id); + let signature_str = send_tx(&rpc_addr, &tx)?; + + Ok(signature_str.to_string()) + } + // Apply witness signature to contract + WalletCommand::Witness(to, pubkey) => { + let params = json!([format!("{}", config.id.pubkey())]); + let balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params))? + .as_u64(); + + if let Some(0) = balance { + let params = json!([format!("{}", config.id.pubkey()), 1]); + RpcRequest::RequestAirdrop + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap(); + } + + let last_id = get_last_id(&rpc_addr)?; + let tx = Transaction::budget_new_signature(&config.id, pubkey, to, last_id); + let signature_str = send_tx(&rpc_addr, &tx)?; + + Ok(signature_str.to_string()) + } + } +} + +pub fn read_leader(path: &str) -> Result { + let file = File::open(path.to_string()).or_else(|err| { + Err(WalletError::BadParameter(format!( + "{}: Unable to open leader file: {}", + err, path + ))) + })?; + + serde_json::from_reader(file).or_else(|err| { + Err(WalletError::BadParameter(format!( + "{}: Failed to parse leader file: {}", + err, path + ))) + }) +} + +pub fn gen_keypair_file(outfile: String) -> Result> { + let rnd = SystemRandom::new(); + let pkcs8_bytes = Ed25519KeyPair::generate_pkcs8(&rnd)?; + let serialized = serde_json::to_string(&pkcs8_bytes.to_vec())?; + + if outfile != "-" { + if let Some(outdir) = Path::new(&outfile).parent() { + fs::create_dir_all(outdir)?; + } + let mut f = File::create(outfile)?; + f.write_all(&serialized.clone().into_bytes())?; + } + Ok(serialized) +} + +fn get_last_id(rpc_addr: &str) -> Result> { + let result = RpcRequest::GetLastId.make_rpc_request(&rpc_addr, 1, None)?; + if result.as_str().is_none() { + Err(WalletError::RpcRequestError( + "Received bad last_id".to_string(), + ))? + } + let last_id_str = result.as_str().unwrap(); + let last_id_vec = bs58::decode(last_id_str) + .into_vec() + .map_err(|_| WalletError::RpcRequestError("Received bad last_id".to_string()))?; + Ok(Hash::new(&last_id_vec)) +} + +fn send_tx(rpc_addr: &str, tx: &Transaction) -> Result> { + let serialized = serialize(tx).unwrap(); + let params = json!([serialized]); + let signature = RpcRequest::SendTransaction.make_rpc_request(&rpc_addr, 2, Some(params))?; + if signature.as_str().is_none() { + Err(WalletError::RpcRequestError( + "Received result of an unexpected type".to_string(), + ))? + } + Ok(signature.as_str().unwrap().to_string()) +} + +fn confirm_tx(rpc_addr: &str, signature: &str) -> Result> { + let params = json!([signature.to_string()]); + let signature_status = + RpcRequest::GetSignatureStatus.make_rpc_request(&rpc_addr, 1, Some(params))?; + if let Some(status) = signature_status.as_str() { + let rpc_status = RpcSignatureStatus::from_str(status).map_err(|_| { + WalletError::RpcRequestError("Unable to parse signature status".to_string()) + })?; + Ok(rpc_status) + } else { + Err(WalletError::RpcRequestError( + "Received result of an unexpected type".to_string(), + ))? + } +} + +fn send_and_confirm_tx(rpc_addr: &str, tx: &Transaction) -> Result<(), Box> { + let mut send_retries = 3; + while send_retries > 0 { + let mut status_retries = 4; + let signature_str = send_tx(rpc_addr, tx)?; + let status = loop { + let status = confirm_tx(rpc_addr, &signature_str)?; + if status == RpcSignatureStatus::SignatureNotFound { + status_retries -= 1; + if status_retries == 0 { + break status; + } + } else { + break status; + } + }; + match status { + RpcSignatureStatus::AccountInUse => { + send_retries -= 1; + } + RpcSignatureStatus::Confirmed => { + return Ok(()); + } + _ => { + return Err(WalletError::RpcRequestError(format!( + "Transaction {:?} failed: {:?}", + signature_str, status + )))?; + } + } + } + Err(WalletError::RpcRequestError(format!( + "AccountInUse after 3 retries: {:?}", + tx.account_keys[0] + )))? +} + +#[cfg(test)] +mod tests { + use super::*; + use bank::Bank; + use clap::{App, Arg, SubCommand}; + use cluster_info::Node; + use fullnode::Fullnode; + use leader_scheduler::LeaderScheduler; + use ledger::create_tmp_genesis; + use serde_json::Value; + use signature::{read_keypair, read_pkcs8, Keypair, KeypairUtil}; + use solana_drone::drone::run_local_drone; + use std::fs::remove_dir_all; + use std::sync::mpsc::channel; + use std::sync::{Arc, RwLock}; + use std::thread::sleep; + use std::time::Duration; + + #[test] + fn test_wallet_parse_command() { + let test_commands = App::new("test") + .subcommand(SubCommand::with_name("address").about("Get your public key")) + .subcommand( + SubCommand::with_name("airdrop") + .about("Request a batch of tokens") + .arg( + Arg::with_name("tokens") + .index(1) + .value_name("NUM") + .takes_value(true) + .required(true) + .help("The number of tokens to request"), + ), + ).subcommand(SubCommand::with_name("balance").about("Get your balance")) + .subcommand( + SubCommand::with_name("cancel") + .about("Cancel a transfer") + .arg( + Arg::with_name("process-id") + .index(1) + .value_name("PROCESS_ID") + .takes_value(true) + .required(true) + .help("The process id of the transfer to cancel"), + ), + ).subcommand( + SubCommand::with_name("confirm") + .about("Confirm transaction by signature") + .arg( + Arg::with_name("signature") + .index(1) + .value_name("SIGNATURE") + .takes_value(true) + .required(true) + .help("The transaction signature to confirm"), + ), + ).subcommand( + SubCommand::with_name("deploy") + .about("Deploy a program") + .arg( + Arg::with_name("program-location") + .index(1) + .value_name("PATH") + .takes_value(true) + .required(true) + .help("/path/to/program.o"), + ), // TODO: Add "loader" argument; current default is bpf_loader + ).subcommand( + SubCommand::with_name("get-transaction-count") + .about("Get current transaction count"), + ).subcommand( + SubCommand::with_name("pay") + .about("Send a payment") + .arg( + Arg::with_name("to") + .index(1) + .value_name("PUBKEY") + .takes_value(true) + .required(true) + .help("The pubkey of recipient"), + ).arg( + Arg::with_name("tokens") + .index(2) + .value_name("NUM") + .takes_value(true) + .required(true) + .help("The number of tokens to send"), + ).arg( + Arg::with_name("timestamp") + .long("after") + .value_name("DATETIME") + .takes_value(true) + .help("A timestamp after which transaction will execute"), + ).arg( + Arg::with_name("timestamp-pubkey") + .long("require-timestamp-from") + .value_name("PUBKEY") + .takes_value(true) + .requires("timestamp") + .help("Require timestamp from this third party"), + ).arg( + Arg::with_name("witness") + .long("require-signature-from") + .value_name("PUBKEY") + .takes_value(true) + .multiple(true) + .use_delimiter(true) + .help("Any third party signatures required to unlock the tokens"), + ).arg( + Arg::with_name("cancelable") + .long("cancelable") + .takes_value(false), + ), + ).subcommand( + SubCommand::with_name("send-signature") + .about("Send a signature to authorize a transfer") + .arg( + Arg::with_name("to") + .index(1) + .value_name("PUBKEY") + .takes_value(true) + .required(true) + .help("The pubkey of recipient"), + ).arg( + Arg::with_name("process-id") + .index(2) + .value_name("PROCESS_ID") + .takes_value(true) + .required(true) + .help("The process id of the transfer to authorize"), + ), + ).subcommand( + SubCommand::with_name("send-timestamp") + .about("Send a timestamp to unlock a transfer") + .arg( + Arg::with_name("to") + .index(1) + .value_name("PUBKEY") + .takes_value(true) + .required(true) + .help("The pubkey of recipient"), + ).arg( + Arg::with_name("process-id") + .index(2) + .value_name("PROCESS_ID") + .takes_value(true) + .required(true) + .help("The process id of the transfer to unlock"), + ).arg( + Arg::with_name("datetime") + .long("date") + .value_name("DATETIME") + .takes_value(true) + .help("Optional arbitrary timestamp to apply"), + ), + ); + let pubkey = Keypair::new().pubkey(); + let pubkey_string = format!("{}", pubkey); + let witness0 = Keypair::new().pubkey(); + let witness0_string = format!("{}", witness0); + let witness1 = Keypair::new().pubkey(); + let witness1_string = format!("{}", witness1); + let dt = Utc.ymd(2018, 9, 19).and_hms(17, 30, 59); + + // Test Airdrop Subcommand + let test_airdrop = test_commands + .clone() + .get_matches_from(vec!["test", "airdrop", "50"]); + assert_eq!( + parse_command(pubkey, &test_airdrop).unwrap(), + WalletCommand::AirDrop(50) + ); + let test_bad_airdrop = test_commands + .clone() + .get_matches_from(vec!["test", "airdrop", "notint"]); + assert!(parse_command(pubkey, &test_bad_airdrop).is_err()); + + // Test Cancel Subcommand + let test_cancel = + test_commands + .clone() + .get_matches_from(vec!["test", "cancel", &pubkey_string]); + assert_eq!( + parse_command(pubkey, &test_cancel).unwrap(), + WalletCommand::Cancel(pubkey) + ); + + // Test Confirm Subcommand + let signature = Signature::new(&vec![1; 64]); + let signature_string = format!("{:?}", signature); + let test_confirm = + test_commands + .clone() + .get_matches_from(vec!["test", "confirm", &signature_string]); + assert_eq!( + parse_command(pubkey, &test_confirm).unwrap(), + WalletCommand::Confirm(signature) + ); + let test_bad_signature = test_commands + .clone() + .get_matches_from(vec!["test", "confirm", "deadbeef"]); + assert!(parse_command(pubkey, &test_bad_signature).is_err()); + + // Test Deploy Subcommand + let test_deploy = + test_commands + .clone() + .get_matches_from(vec!["test", "deploy", "/Users/test/program.o"]); + assert_eq!( + parse_command(pubkey, &test_deploy).unwrap(), + WalletCommand::Deploy("/Users/test/program.o".to_string()) + ); + + // Test Simple Pay Subcommand + let test_pay = + test_commands + .clone() + .get_matches_from(vec!["test", "pay", &pubkey_string, "50"]); + assert_eq!( + parse_command(pubkey, &test_pay).unwrap(), + WalletCommand::Pay(50, pubkey, None, None, None, None) + ); + let test_bad_pubkey = test_commands + .clone() + .get_matches_from(vec!["test", "pay", "deadbeef", "50"]); + assert!(parse_command(pubkey, &test_bad_pubkey).is_err()); + + // Test Pay Subcommand w/ Witness + let test_pay_multiple_witnesses = test_commands.clone().get_matches_from(vec![ + "test", + "pay", + &pubkey_string, + "50", + "--require-signature-from", + &witness0_string, + "--require-signature-from", + &witness1_string, + ]); + assert_eq!( + parse_command(pubkey, &test_pay_multiple_witnesses).unwrap(), + WalletCommand::Pay(50, pubkey, None, None, Some(vec![witness0, witness1]), None) + ); + let test_pay_single_witness = test_commands.clone().get_matches_from(vec![ + "test", + "pay", + &pubkey_string, + "50", + "--require-signature-from", + &witness0_string, + ]); + assert_eq!( + parse_command(pubkey, &test_pay_single_witness).unwrap(), + WalletCommand::Pay(50, pubkey, None, None, Some(vec![witness0]), None) + ); + + // Test Pay Subcommand w/ Timestamp + let test_pay_timestamp = test_commands.clone().get_matches_from(vec![ + "test", + "pay", + &pubkey_string, + "50", + "--after", + "2018-09-19T17:30:59", + "--require-timestamp-from", + &witness0_string, + ]); + assert_eq!( + parse_command(pubkey, &test_pay_timestamp).unwrap(), + WalletCommand::Pay(50, pubkey, Some(dt), Some(witness0), None, None) + ); + + // Test Send-Signature Subcommand + let test_send_signature = test_commands.clone().get_matches_from(vec![ + "test", + "send-signature", + &pubkey_string, + &pubkey_string, + ]); + assert_eq!( + parse_command(pubkey, &test_send_signature).unwrap(), + WalletCommand::Witness(pubkey, pubkey) + ); + let test_pay_multiple_witnesses = test_commands.clone().get_matches_from(vec![ + "test", + "pay", + &pubkey_string, + "50", + "--after", + "2018-09-19T17:30:59", + "--require-signature-from", + &witness0_string, + "--require-timestamp-from", + &witness0_string, + "--require-signature-from", + &witness1_string, + ]); + assert_eq!( + parse_command(pubkey, &test_pay_multiple_witnesses).unwrap(), + WalletCommand::Pay( + 50, + pubkey, + Some(dt), + Some(witness0), + Some(vec![witness0, witness1]), + None + ) + ); + + // Test Send-Timestamp Subcommand + let test_send_timestamp = test_commands.clone().get_matches_from(vec![ + "test", + "send-timestamp", + &pubkey_string, + &pubkey_string, + "--date", + "2018-09-19T17:30:59", + ]); + assert_eq!( + parse_command(pubkey, &test_send_timestamp).unwrap(), + WalletCommand::TimeElapsed(pubkey, pubkey, dt) + ); + let test_bad_timestamp = test_commands.clone().get_matches_from(vec![ + "test", + "send-timestamp", + &pubkey_string, + &pubkey_string, + "--date", + "20180919T17:30:59", + ]); + assert!(parse_command(pubkey, &test_bad_timestamp).is_err()); + } + #[test] + #[ignore] + fn test_wallet_process_command() { + let bob_pubkey = Keypair::new().pubkey(); + + let leader_keypair = Arc::new(Keypair::new()); + let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); + let leader_data = leader.info.clone(); + let (alice, ledger_path) = + create_tmp_genesis("wallet_process_command", 10_000_000, leader_data.id, 1000); + let mut bank = Bank::new(&alice); + + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + leader_data.id, + ))); + bank.leader_scheduler = leader_scheduler; + let vote_account_keypair = Arc::new(Keypair::new()); + let last_id = bank.last_id(); + + let server = Fullnode::new_with_bank( + leader_keypair, + vote_account_keypair, + bank, + 0, + &last_id, + leader, + None, + &ledger_path, + false, + None, + ); + sleep(Duration::from_millis(900)); + + let (sender, receiver) = channel(); + run_local_drone(alice.keypair(), sender); + let drone_addr = receiver.recv().unwrap(); + + let mut config = WalletConfig::default(); + config.network = leader_data.ncp; + config.drone_port = Some(drone_addr.port()); + + let tokens = 50; + config.command = WalletCommand::AirDrop(tokens); + assert_eq!( + process_command(&config).unwrap(), + format!("Your balance is: {:?}", tokens) + ); + + config.command = WalletCommand::Balance; + assert_eq!( + process_command(&config).unwrap(), + format!("Your balance is: {:?}", tokens) + ); + + config.command = WalletCommand::Pay(10, bob_pubkey, None, None, None, None); + let sig_response = process_command(&config); + assert!(sig_response.is_ok()); + + let signatures = bs58::decode(sig_response.unwrap()) + .into_vec() + .expect("base58-encoded signature"); + let signature = Signature::new(&signatures); + config.command = WalletCommand::Confirm(signature); + assert_eq!(process_command(&config).unwrap(), "Confirmed"); + + config.command = WalletCommand::Balance; + assert_eq!( + process_command(&config).unwrap(), + format!("Your balance is: {:?}", tokens - 10) + ); + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); + } + #[test] + #[ignore] + fn test_wallet_request_airdrop() { + let leader_keypair = Arc::new(Keypair::new()); + let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); + let leader_data = leader.info.clone(); + let (alice, ledger_path) = + create_tmp_genesis("wallet_request_airdrop", 10_000_000, leader_data.id, 1000); + let mut bank = Bank::new(&alice); + + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + leader_data.id, + ))); + bank.leader_scheduler = leader_scheduler; + let vote_account_keypair = Arc::new(Keypair::new()); + let last_id = bank.last_id(); + let entry_height = alice.create_entries().len() as u64; + let server = Fullnode::new_with_bank( + leader_keypair, + vote_account_keypair, + bank, + entry_height, + &last_id, + leader, + None, + &ledger_path, + false, + None, + ); + sleep(Duration::from_millis(900)); + + let (sender, receiver) = channel(); + run_local_drone(alice.keypair(), sender); + let drone_addr = receiver.recv().unwrap(); + + let mut bob_config = WalletConfig::default(); + bob_config.network = leader_data.ncp; + bob_config.drone_port = Some(drone_addr.port()); + bob_config.command = WalletCommand::AirDrop(50); + + let sig_response = process_command(&bob_config); + assert!(sig_response.is_ok()); + + let rpc_addr = format!("http://{}", leader_data.rpc.to_string()); + let params = json!([format!("{}", bob_config.id.pubkey())]); + let balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(balance, 50); + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); + } + + fn tmp_file_path(name: &str) -> String { + use std::env; + let out_dir = env::var("OUT_DIR").unwrap_or_else(|_| "target".to_string()); + let keypair = Keypair::new(); + + format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey()).to_string() + } + + #[test] + fn test_wallet_gen_keypair_file() { + let outfile = tmp_file_path("test_gen_keypair_file.json"); + let serialized_keypair = gen_keypair_file(outfile.to_string()).unwrap(); + let keypair_vec: Vec = serde_json::from_str(&serialized_keypair).unwrap(); + assert!(Path::new(&outfile).exists()); + assert_eq!(keypair_vec, read_pkcs8(&outfile).unwrap()); + assert!(read_keypair(&outfile).is_ok()); + assert_eq!( + read_keypair(&outfile).unwrap().pubkey().as_ref().len(), + mem::size_of::() + ); + fs::remove_file(&outfile).unwrap(); + assert!(!Path::new(&outfile).exists()); + } + #[test] + #[ignore] + fn test_wallet_timestamp_tx() { + let bob_pubkey = Keypair::new().pubkey(); + + let leader_keypair = Arc::new(Keypair::new()); + let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); + let leader_data = leader.info.clone(); + let (alice, ledger_path) = + create_tmp_genesis("wallet_timestamp_tx", 10_000_000, leader_data.id, 1000); + let mut bank = Bank::new(&alice); + + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + leader_data.id, + ))); + bank.leader_scheduler = leader_scheduler; + let vote_account_keypair = Arc::new(Keypair::new()); + let last_id = bank.last_id(); + let server = Fullnode::new_with_bank( + leader_keypair, + vote_account_keypair, + bank, + 0, + &last_id, + leader, + None, + &ledger_path, + false, + None, + ); + sleep(Duration::from_millis(900)); + + let (sender, receiver) = channel(); + run_local_drone(alice.keypair(), sender); + let drone_addr = receiver.recv().unwrap(); + + let rpc_addr = format!("http://{}", leader_data.rpc.to_string()); + + let mut config_payer = WalletConfig::default(); + config_payer.network = leader_data.ncp; + config_payer.drone_port = Some(drone_addr.port()); + + let mut config_witness = WalletConfig::default(); + config_witness.network = leader_data.ncp; + config_witness.drone_port = Some(drone_addr.port()); + + assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey()); + + let last_id = get_last_id(&rpc_addr).unwrap(); + let transaction = + request_airdrop_transaction(&drone_addr, &config_payer.id.pubkey(), 50, last_id) + .unwrap(); + send_and_confirm_tx(&rpc_addr, &transaction).unwrap(); + + // Make transaction (from config_payer to bob_pubkey) requiring timestamp from config_witness + let date_string = "\"2018-09-19T17:30:59Z\""; + let dt: DateTime = serde_json::from_str(&date_string).unwrap(); + config_payer.command = WalletCommand::Pay( + 10, + bob_pubkey, + Some(dt), + Some(config_witness.id.pubkey()), + None, + None, + ); + let sig_response = process_command(&config_payer); + assert!(sig_response.is_ok()); + + let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap(); + let process_id_str = object.get("processId").unwrap().as_str().unwrap(); + let process_id_vec = bs58::decode(process_id_str) + .into_vec() + .expect("base58-encoded public key"); + let process_id = Pubkey::new(&process_id_vec); + + let params = json!([format!("{}", config_payer.id.pubkey())]); + let config_payer_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(config_payer_balance, 39); + let params = json!([format!("{}", process_id)]); + let contract_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(contract_balance, 11); + let params = json!([format!("{}", bob_pubkey)]); + let recipient_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(recipient_balance, 0); + + // Sign transaction by config_witness + config_witness.command = WalletCommand::TimeElapsed(bob_pubkey, process_id, dt); + let sig_response = process_command(&config_witness); + assert!(sig_response.is_ok()); + + let params = json!([format!("{}", config_payer.id.pubkey())]); + let config_payer_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(config_payer_balance, 39); + let params = json!([format!("{}", process_id)]); + let contract_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(contract_balance, 1); + let params = json!([format!("{}", bob_pubkey)]); + let recipient_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(recipient_balance, 10); + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); + } + #[test] + #[ignore] + fn test_wallet_witness_tx() { + let bob_pubkey = Keypair::new().pubkey(); + let leader_keypair = Arc::new(Keypair::new()); + let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); + let leader_data = leader.info.clone(); + let (alice, ledger_path) = + create_tmp_genesis("wallet_witness_tx", 10_000_000, leader_data.id, 1000); + let mut bank = Bank::new(&alice); + + let mut config_payer = WalletConfig::default(); + let mut config_witness = WalletConfig::default(); + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + leader_data.id, + ))); + bank.leader_scheduler = leader_scheduler; + let vote_account_keypair = Arc::new(Keypair::new()); + let last_id = bank.last_id(); + let server = Fullnode::new_with_bank( + leader_keypair, + vote_account_keypair, + bank, + 0, + &last_id, + leader, + None, + &ledger_path, + false, + None, + ); + sleep(Duration::from_millis(900)); + + let (sender, receiver) = channel(); + run_local_drone(alice.keypair(), sender); + let drone_addr = receiver.recv().unwrap(); + + let rpc_addr = format!("http://{}", leader_data.rpc.to_string()); + + assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey()); + + let last_id = get_last_id(&rpc_addr).unwrap(); + let transaction = + request_airdrop_transaction(&drone_addr, &config_payer.id.pubkey(), 50, last_id) + .unwrap(); + send_and_confirm_tx(&rpc_addr, &transaction).unwrap(); + + // Make transaction (from config_payer to bob_pubkey) requiring witness signature from config_witness + config_payer.command = WalletCommand::Pay( + 10, + bob_pubkey, + None, + None, + Some(vec![config_witness.id.pubkey()]), + None, + ); + let sig_response = process_command(&config_payer); + assert!(sig_response.is_ok()); + + let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap(); + let process_id_str = object.get("processId").unwrap().as_str().unwrap(); + let process_id_vec = bs58::decode(process_id_str) + .into_vec() + .expect("base58-encoded public key"); + let process_id = Pubkey::new(&process_id_vec); + + let params = json!([format!("{}", config_payer.id.pubkey())]); + let config_payer_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(config_payer_balance, 39); + let params = json!([format!("{}", process_id)]); + let contract_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(contract_balance, 11); + let params = json!([format!("{}", bob_pubkey)]); + let recipient_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(recipient_balance, 0); + + // Sign transaction by config_witness + config_witness.command = WalletCommand::Witness(bob_pubkey, process_id); + let sig_response = process_command(&config_witness); + assert!(sig_response.is_ok()); + + let params = json!([format!("{}", config_payer.id.pubkey())]); + let config_payer_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(config_payer_balance, 39); + let params = json!([format!("{}", process_id)]); + let contract_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(contract_balance, 1); + let params = json!([format!("{}", bob_pubkey)]); + let recipient_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(recipient_balance, 10); + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); + } + #[test] + #[ignore] + fn test_wallet_cancel_tx() { + let bob_pubkey = Keypair::new().pubkey(); + let leader_keypair = Arc::new(Keypair::new()); + let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); + let leader_data = leader.info.clone(); + + let (alice, ledger_path) = + create_tmp_genesis("wallet_cancel_tx", 10_000_000, leader_data.id, 1000); + let mut bank = Bank::new(&alice); + + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + leader_data.id, + ))); + bank.leader_scheduler = leader_scheduler; + let vote_account_keypair = Arc::new(Keypair::new()); + let last_id = bank.last_id(); + let server = Fullnode::new_with_bank( + leader_keypair, + vote_account_keypair, + bank, + 0, + &last_id, + leader, + None, + &ledger_path, + false, + None, + ); + sleep(Duration::from_millis(900)); + + let (sender, receiver) = channel(); + run_local_drone(alice.keypair(), sender); + let drone_addr = receiver.recv().unwrap(); + + let rpc_addr = format!("http://{}", leader_data.rpc.to_string()); + + let mut config_payer = WalletConfig::default(); + config_payer.network = leader_data.ncp; + config_payer.drone_port = Some(drone_addr.port()); + + let mut config_witness = WalletConfig::default(); + config_witness.network = leader_data.ncp; + config_witness.drone_port = Some(drone_addr.port()); + + assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey()); + + let last_id = get_last_id(&rpc_addr).unwrap(); + let transaction = + request_airdrop_transaction(&drone_addr, &config_payer.id.pubkey(), 50, last_id) + .unwrap(); + send_and_confirm_tx(&rpc_addr, &transaction).unwrap(); + + // Make transaction (from config_payer to bob_pubkey) requiring witness signature from config_witness + config_payer.command = WalletCommand::Pay( + 10, + bob_pubkey, + None, + None, + Some(vec![config_witness.id.pubkey()]), + Some(config_payer.id.pubkey()), + ); + let sig_response = process_command(&config_payer); + assert!(sig_response.is_ok()); + + let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap(); + let process_id_str = object.get("processId").unwrap().as_str().unwrap(); + let process_id_vec = bs58::decode(process_id_str) + .into_vec() + .expect("base58-encoded public key"); + let process_id = Pubkey::new(&process_id_vec); + + let params = json!([format!("{}", config_payer.id.pubkey())]); + let config_payer_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(config_payer_balance, 39); + let params = json!([format!("{}", process_id)]); + let contract_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(contract_balance, 11); + let params = json!([format!("{}", bob_pubkey)]); + let recipient_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(recipient_balance, 0); + + // Sign transaction by config_witness + config_payer.command = WalletCommand::Cancel(process_id); + let sig_response = process_command(&config_payer); + assert!(sig_response.is_ok()); + + let params = json!([format!("{}", config_payer.id.pubkey())]); + let config_payer_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(config_payer_balance, 49); + let params = json!([format!("{}", process_id)]); + let contract_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(contract_balance, 1); + let params = json!([format!("{}", bob_pubkey)]); + let recipient_balance = RpcRequest::GetBalance + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(recipient_balance, 0); + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); + } +} diff --git a/book/window.rs b/book/window.rs new file mode 100644 index 00000000000000..bafdead363617f --- /dev/null +++ b/book/window.rs @@ -0,0 +1,555 @@ +//! The `window` module defines data structure for storing the tail of the ledger. +//! +use cluster_info::ClusterInfo; +use counter::Counter; +use entry::reconstruct_entries_from_blobs; +use entry::Entry; +#[cfg(feature = "erasure")] +use erasure; +use leader_scheduler::LeaderScheduler; +use log::Level; +use packet::SharedBlob; +use solana_sdk::pubkey::Pubkey; +use std::cmp; +use std::mem; +use std::net::SocketAddr; +use std::sync::atomic::AtomicUsize; +use std::sync::{Arc, RwLock}; + +#[derive(Default, Clone)] +pub struct WindowSlot { + pub data: Option, + pub coding: Option, + pub leader_unknown: bool, +} + +impl WindowSlot { + fn blob_index(&self) -> Option { + match self.data { + Some(ref blob) => blob.read().unwrap().index().ok(), + None => None, + } + } + + fn clear_data(&mut self) { + self.data.take(); + } +} + +type Window = Vec; +pub type SharedWindow = Arc>; + +#[derive(Debug)] +pub struct WindowIndex { + pub data: u64, + pub coding: u64, +} + +pub trait WindowUtil { + /// Finds available slots, clears them, and returns their indices. + fn clear_slots(&mut self, consumed: u64, received: u64) -> Vec; + + fn window_size(&self) -> u64; + + fn repair( + &mut self, + cluster_info: &Arc>, + id: &Pubkey, + times: usize, + consumed: u64, + received: u64, + tick_height: u64, + max_entry_height: u64, + leader_scheduler_option: &Arc>, + ) -> Vec<(SocketAddr, Vec)>; + + fn print(&self, id: &Pubkey, consumed: u64) -> String; + + fn process_blob( + &mut self, + id: &Pubkey, + blob: SharedBlob, + pix: u64, + consume_queue: &mut Vec, + consumed: &mut u64, + tick_height: &mut u64, + leader_unknown: bool, + pending_retransmits: &mut bool, + ); + + fn blob_idx_in_window(&self, id: &Pubkey, pix: u64, consumed: u64, received: &mut u64) -> bool; +} + +impl WindowUtil for Window { + fn clear_slots(&mut self, consumed: u64, received: u64) -> Vec { + (consumed..received) + .filter_map(|pix| { + let i = (pix % self.window_size()) as usize; + if let Some(blob_idx) = self[i].blob_index() { + if blob_idx == pix { + return None; + } + } + self[i].clear_data(); + Some(pix) + }).collect() + } + + fn blob_idx_in_window(&self, id: &Pubkey, pix: u64, consumed: u64, received: &mut u64) -> bool { + // Prevent receive window from running over + // Got a blob which has already been consumed, skip it + // probably from a repair window request + if pix < consumed { + trace!( + "{}: received: {} but older than consumed: {} skipping..", + id, + pix, + consumed + ); + false + } else { + // received always has to be updated even if we don't accept the packet into + // the window. The worst case here is the server *starts* outside + // the window, none of the packets it receives fits in the window + // and repair requests (which are based on received) are never generated + *received = cmp::max(pix, *received); + + if pix >= consumed + self.window_size() { + trace!( + "{}: received: {} will overrun window: {} skipping..", + id, + pix, + consumed + self.window_size() + ); + false + } else { + true + } + } + } + + fn window_size(&self) -> u64 { + self.len() as u64 + } + + fn repair( + &mut self, + cluster_info: &Arc>, + id: &Pubkey, + times: usize, + consumed: u64, + received: u64, + tick_height: u64, + max_entry_height: u64, + leader_scheduler_option: &Arc>, + ) -> Vec<(SocketAddr, Vec)> { + let rcluster_info = cluster_info.read().unwrap(); + let mut is_next_leader = false; + { + let ls_lock = leader_scheduler_option.read().unwrap(); + if !ls_lock.use_only_bootstrap_leader { + // Calculate the next leader rotation height and check if we are the leader + if let Some(next_leader_rotation_height) = + ls_lock.max_height_for_leader(tick_height) + { + match ls_lock.get_scheduled_leader(next_leader_rotation_height) { + Some((leader_id, _)) if leader_id == *id => is_next_leader = true, + // In the case that we are not in the current scope of the leader schedule + // window then either: + // + // 1) The replicate stage hasn't caught up to the "consumed" entries we sent, + // in which case it will eventually catch up + // + // 2) We are on the border between seed_rotation_intervals, so the + // schedule won't be known until the entry on that cusp is received + // by the replicate stage (which comes after this stage). Hence, the next + // leader at the beginning of that next epoch will not know they are the + // leader until they receive that last "cusp" entry. The leader also won't ask for repairs + // for that entry because "is_next_leader" won't be set here. In this case, + // everybody will be blocking waiting for that "cusp" entry instead of repairing, + // until the leader hits "times" >= the max times in calculate_max_repair(). + // The impact of this, along with the similar problem from broadcast for the transitioning + // leader, can be observed in the multinode test, test_full_leader_validator_network(), + None => (), + _ => (), + } + } + } + } + + let num_peers = rcluster_info.tvu_peers().len() as u64; + let max_repair = if max_entry_height == 0 { + calculate_max_repair( + num_peers, + consumed, + received, + times, + is_next_leader, + self.window_size(), + ) + } else { + max_entry_height + 1 + }; + + let idxs = self.clear_slots(consumed, max_repair); + let reqs: Vec<_> = idxs + .into_iter() + .filter_map(|pix| rcluster_info.window_index_request(pix).ok()) + .collect(); + + drop(rcluster_info); + + inc_new_counter_info!("streamer-repair_window-repair", reqs.len()); + + if log_enabled!(Level::Trace) { + trace!( + "{}: repair_window counter times: {} consumed: {} received: {} max_repair: {} missing: {}", + id, + times, + consumed, + received, + max_repair, + reqs.len() + ); + for (to, _) in &reqs { + trace!("{}: repair_window request to {}", id, to); + } + } + reqs + } + + fn print(&self, id: &Pubkey, consumed: u64) -> String { + let pointer: Vec<_> = self + .iter() + .enumerate() + .map(|(i, _v)| { + if i == (consumed % self.window_size()) as usize { + "V" + } else { + " " + } + }).collect(); + + let buf: Vec<_> = self + .iter() + .map(|v| { + if v.data.is_none() && v.coding.is_none() { + "O" + } else if v.data.is_some() && v.coding.is_some() { + "D" + } else if v.data.is_some() { + // coding.is_none() + "d" + } else { + // data.is_none() + "c" + } + }).collect(); + format!( + "\n{}: WINDOW ({}): {}\n{}: WINDOW ({}): {}", + id, + consumed, + pointer.join(""), + id, + consumed, + buf.join("") + ) + } + + /// process a blob: Add blob to the window. If a continuous set of blobs + /// starting from consumed is thereby formed, add that continuous + /// range of blobs to a queue to be sent on to the next stage. + /// + /// * `self` - the window we're operating on + /// * `id` - this node's id + /// * `blob` - the blob to be processed into the window and rebroadcast + /// * `pix` - the index of the blob, corresponds to + /// the entry height of this blob + /// * `consume_queue` - output, blobs to be rebroadcast are placed here + /// * `consumed` - input/output, the entry-height to which this + /// node has populated and rebroadcast entries + fn process_blob( + &mut self, + id: &Pubkey, + blob: SharedBlob, + pix: u64, + consume_queue: &mut Vec, + consumed: &mut u64, + tick_height: &mut u64, + leader_unknown: bool, + pending_retransmits: &mut bool, + ) { + let w = (pix % self.window_size()) as usize; + + let is_coding = blob.read().unwrap().is_coding(); + + // insert a newly received blob into a window slot, clearing out and recycling any previous + // blob unless the incoming blob is a duplicate (based on idx) + // returns whether the incoming is a duplicate blob + fn insert_blob_is_dup( + id: &Pubkey, + blob: SharedBlob, + pix: u64, + window_slot: &mut Option, + c_or_d: &str, + ) -> bool { + if let Some(old) = mem::replace(window_slot, Some(blob)) { + let is_dup = old.read().unwrap().index().unwrap() == pix; + trace!( + "{}: occupied {} window slot {:}, is_dup: {}", + id, + c_or_d, + pix, + is_dup + ); + is_dup + } else { + trace!("{}: empty {} window slot {:}", id, c_or_d, pix); + false + } + } + + // insert the new blob into the window, overwrite and recycle old (or duplicate) entry + let is_duplicate = if is_coding { + insert_blob_is_dup(id, blob, pix, &mut self[w].coding, "coding") + } else { + insert_blob_is_dup(id, blob, pix, &mut self[w].data, "data") + }; + + if is_duplicate { + return; + } + + self[w].leader_unknown = leader_unknown; + *pending_retransmits = true; + + #[cfg(feature = "erasure")] + { + let window_size = self.window_size(); + if erasure::recover(id, self, *consumed, (*consumed % window_size) as usize).is_err() { + trace!("{}: erasure::recover failed", id); + } + } + + // push all contiguous blobs into consumed queue, increment consumed + loop { + let k = (*consumed % self.window_size()) as usize; + trace!("{}: k: {} consumed: {}", id, k, *consumed,); + + let k_data_blob; + let k_data_slot = &mut self[k].data; + if let Some(blob) = k_data_slot { + if blob.read().unwrap().index().unwrap() < *consumed { + // window wrap-around, end of received + break; + } + k_data_blob = (*blob).clone(); + } else { + // self[k].data is None, end of received + break; + } + + // Check that we can get the entries from this blob + match reconstruct_entries_from_blobs(vec![k_data_blob]) { + Ok((entries, num_ticks)) => { + consume_queue.extend(entries); + *tick_height += num_ticks; + } + Err(_) => { + // If the blob can't be deserialized, then remove it from the + // window and exit. *k_data_slot cannot be None at this point, + // so it's safe to unwrap. + k_data_slot.take(); + break; + } + } + + *consumed += 1; + } + } +} + +fn calculate_max_repair( + num_peers: u64, + consumed: u64, + received: u64, + times: usize, + is_next_leader: bool, + window_size: u64, +) -> u64 { + // Calculate the highest blob index that this node should have already received + // via avalanche. The avalanche splits data stream into nodes and each node retransmits + // the data to their peer nodes. So there's a possibility that a blob (with index lower + // than current received index) is being retransmitted by a peer node. + let max_repair = if times >= 8 || is_next_leader { + // if repair backoff is getting high, or if we are the next leader, + // don't wait for avalanche + cmp::max(consumed, received) + } else { + cmp::max(consumed, received.saturating_sub(num_peers)) + }; + + // This check prevents repairing a blob that will cause window to roll over. Even if + // the highes_lost blob is actually missing, asking to repair it might cause our + // current window to move past other missing blobs + cmp::min(consumed + window_size - 1, max_repair) +} + +pub fn new_window(window_size: usize) -> Window { + (0..window_size).map(|_| WindowSlot::default()).collect() +} + +pub fn default_window() -> Window { + (0..2048).map(|_| WindowSlot::default()).collect() +} + +#[cfg(test)] +mod test { + use packet::{Blob, Packet, Packets, SharedBlob, PACKET_DATA_SIZE}; + use solana_sdk::pubkey::Pubkey; + use std::io; + use std::io::Write; + use std::net::UdpSocket; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::mpsc::channel; + use std::sync::Arc; + use std::time::Duration; + use streamer::{receiver, responder, PacketReceiver}; + use window::{calculate_max_repair, new_window, Window, WindowUtil}; + + fn get_msgs(r: PacketReceiver, num: &mut usize) { + for _t in 0..5 { + let timer = Duration::new(1, 0); + match r.recv_timeout(timer) { + Ok(m) => *num += m.read().unwrap().packets.len(), + e => info!("error {:?}", e), + } + if *num == 10 { + break; + } + } + } + #[test] + pub fn streamer_debug() { + write!(io::sink(), "{:?}", Packet::default()).unwrap(); + write!(io::sink(), "{:?}", Packets::default()).unwrap(); + write!(io::sink(), "{:?}", Blob::default()).unwrap(); + } + #[test] + pub fn streamer_send_test() { + let read = UdpSocket::bind("127.0.0.1:0").expect("bind"); + read.set_read_timeout(Some(Duration::new(1, 0))).unwrap(); + + let addr = read.local_addr().unwrap(); + let send = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let exit = Arc::new(AtomicBool::new(false)); + let (s_reader, r_reader) = channel(); + let t_receiver = receiver( + Arc::new(read), + exit.clone(), + s_reader, + "window-streamer-test", + ); + let t_responder = { + let (s_responder, r_responder) = channel(); + let t_responder = responder("streamer_send_test", Arc::new(send), r_responder); + let mut msgs = Vec::new(); + for i in 0..10 { + let mut b = SharedBlob::default(); + { + let mut w = b.write().unwrap(); + w.data[0] = i as u8; + w.meta.size = PACKET_DATA_SIZE; + w.meta.set_addr(&addr); + } + msgs.push(b); + } + s_responder.send(msgs).expect("send"); + t_responder + }; + + let mut num = 0; + get_msgs(r_reader, &mut num); + assert_eq!(num, 10); + exit.store(true, Ordering::Relaxed); + t_receiver.join().expect("join"); + t_responder.join().expect("join"); + } + + #[test] + pub fn test_calculate_max_repair() { + const WINDOW_SIZE: u64 = 200; + + assert_eq!(calculate_max_repair(0, 10, 90, 0, false, WINDOW_SIZE), 90); + assert_eq!(calculate_max_repair(15, 10, 90, 32, false, WINDOW_SIZE), 90); + assert_eq!(calculate_max_repair(15, 10, 90, 0, false, WINDOW_SIZE), 75); + assert_eq!(calculate_max_repair(90, 10, 90, 0, false, WINDOW_SIZE), 10); + assert_eq!(calculate_max_repair(90, 10, 50, 0, false, WINDOW_SIZE), 10); + assert_eq!(calculate_max_repair(90, 10, 99, 0, false, WINDOW_SIZE), 10); + assert_eq!(calculate_max_repair(90, 10, 101, 0, false, WINDOW_SIZE), 11); + assert_eq!( + calculate_max_repair(90, 10, 95 + WINDOW_SIZE, 0, false, WINDOW_SIZE), + WINDOW_SIZE + 5 + ); + assert_eq!( + calculate_max_repair(90, 10, 99 + WINDOW_SIZE, 0, false, WINDOW_SIZE), + WINDOW_SIZE + 9 + ); + assert_eq!( + calculate_max_repair(90, 10, 100 + WINDOW_SIZE, 0, false, WINDOW_SIZE), + WINDOW_SIZE + 9 + ); + assert_eq!( + calculate_max_repair(90, 10, 120 + WINDOW_SIZE, 0, false, WINDOW_SIZE), + WINDOW_SIZE + 9 + ); + assert_eq!( + calculate_max_repair(50, 100, 50 + WINDOW_SIZE, 0, false, WINDOW_SIZE), + WINDOW_SIZE + ); + assert_eq!( + calculate_max_repair(50, 100, 50 + WINDOW_SIZE, 0, true, WINDOW_SIZE), + 50 + WINDOW_SIZE + ); + } + + fn wrap_blob_idx_in_window( + window: &Window, + id: &Pubkey, + pix: u64, + consumed: u64, + received: u64, + ) -> (bool, u64) { + let mut received = received; + let is_in_window = window.blob_idx_in_window(&id, pix, consumed, &mut received); + (is_in_window, received) + } + #[test] + pub fn test_blob_idx_in_window() { + let id = Pubkey::default(); + const WINDOW_SIZE: u64 = 200; + let window = new_window(WINDOW_SIZE as usize); + + assert_eq!( + wrap_blob_idx_in_window(&window, &id, 90 + WINDOW_SIZE, 90, 100), + (false, 90 + WINDOW_SIZE) + ); + assert_eq!( + wrap_blob_idx_in_window(&window, &id, 91 + WINDOW_SIZE, 90, 100), + (false, 91 + WINDOW_SIZE) + ); + assert_eq!( + wrap_blob_idx_in_window(&window, &id, 89, 90, 100), + (false, 100) + ); + + assert_eq!( + wrap_blob_idx_in_window(&window, &id, 91, 90, 100), + (true, 100) + ); + assert_eq!( + wrap_blob_idx_in_window(&window, &id, 101, 90, 100), + (true, 101) + ); + } +} diff --git a/book/window_service.rs b/book/window_service.rs new file mode 100644 index 00000000000000..abfc9293bb13f0 --- /dev/null +++ b/book/window_service.rs @@ -0,0 +1,626 @@ +//! The `window_service` provides a thread for maintaining a window (tail of the ledger). +//! +use cluster_info::{ClusterInfo, NodeInfo}; +use counter::Counter; +use entry::EntrySender; + +use leader_scheduler::LeaderScheduler; +use log::Level; +use packet::SharedBlob; +use rand::{thread_rng, Rng}; +use result::{Error, Result}; +use solana_metrics::{influxdb, submit}; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::timing::duration_as_ms; +use std::net::UdpSocket; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::mpsc::RecvTimeoutError; +use std::sync::{Arc, RwLock}; +use std::thread::{Builder, JoinHandle}; +use std::time::{Duration, Instant}; +use streamer::{BlobReceiver, BlobSender}; +use window::{SharedWindow, WindowUtil}; + +pub const MAX_REPAIR_BACKOFF: usize = 128; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum WindowServiceReturnType { + LeaderRotation(u64), +} + +fn repair_backoff(last: &mut u64, times: &mut usize, consumed: u64) -> bool { + //exponential backoff + if *last != consumed { + //start with a 50% chance of asking for repairs + *times = 1; + } + *last = consumed; + *times += 1; + + // Experiment with capping repair request duration. + // Once nodes are too far behind they can spend many + // seconds without asking for repair + if *times > MAX_REPAIR_BACKOFF { + // 50% chance that a request will fire between 64 - 128 tries + *times = MAX_REPAIR_BACKOFF / 2; + } + + //if we get lucky, make the request, which should exponentially get less likely + thread_rng().gen_range(0, *times as u64) == 0 +} + +fn add_block_to_retransmit_queue( + b: &SharedBlob, + leader_id: Pubkey, + retransmit_queue: &mut Vec, +) { + let p = b.read().unwrap(); + //TODO this check isn't safe against adverserial packets + //we need to maintain a sequence window + trace!( + "idx: {} addr: {:?} id: {:?} leader: {:?}", + p.index() + .expect("get_index in fn add_block_to_retransmit_queue"), + p.id() + .expect("get_id in trace! fn add_block_to_retransmit_queue"), + p.meta.addr(), + leader_id + ); + if p.id().expect("get_id in fn add_block_to_retransmit_queue") == leader_id { + //TODO + //need to copy the retransmitted blob + //otherwise we get into races with which thread + //should do the recycling + // + let nv = SharedBlob::default(); + { + let mut mnv = nv.write().unwrap(); + let sz = p.meta.size; + mnv.meta.size = sz; + mnv.data[..sz].copy_from_slice(&p.data[..sz]); + } + retransmit_queue.push(nv); + } +} + +fn retransmit_all_leader_blocks( + window: &SharedWindow, + maybe_leader: Option, + dq: &[SharedBlob], + id: &Pubkey, + consumed: u64, + received: u64, + retransmit: &BlobSender, + pending_retransmits: &mut bool, +) -> Result<()> { + let mut retransmit_queue: Vec = Vec::new(); + if let Some(leader) = maybe_leader { + let leader_id = leader.id; + for b in dq { + add_block_to_retransmit_queue(b, leader_id, &mut retransmit_queue); + } + + if *pending_retransmits { + for w in window + .write() + .expect("Window write failed in retransmit_all_leader_blocks") + .iter_mut() + { + *pending_retransmits = false; + if w.leader_unknown { + if let Some(ref b) = w.data { + add_block_to_retransmit_queue(b, leader_id, &mut retransmit_queue); + w.leader_unknown = false; + } + } + } + } + submit( + influxdb::Point::new("retransmit-queue") + .add_field( + "count", + influxdb::Value::Integer(retransmit_queue.len() as i64), + ).to_owned(), + ); + } else { + warn!("{}: no leader to retransmit from", id); + } + if !retransmit_queue.is_empty() { + trace!( + "{}: RECV_WINDOW {} {}: retransmit {}", + id, + consumed, + received, + retransmit_queue.len(), + ); + inc_new_counter_info!("streamer-recv_window-retransmit", retransmit_queue.len()); + retransmit.send(retransmit_queue)?; + } + Ok(()) +} + +#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] +fn recv_window( + window: &SharedWindow, + id: &Pubkey, + cluster_info: &Arc>, + consumed: &mut u64, + received: &mut u64, + tick_height: &mut u64, + max_ix: u64, + r: &BlobReceiver, + s: &EntrySender, + retransmit: &BlobSender, + pending_retransmits: &mut bool, + done: &Arc, +) -> Result<()> { + let timer = Duration::from_millis(200); + let mut dq = r.recv_timeout(timer)?; + let maybe_leader: Option = cluster_info + .read() + .expect("'cluster_info' read lock in fn recv_window") + .leader_data() + .cloned(); + let leader_unknown = maybe_leader.is_none(); + while let Ok(mut nq) = r.try_recv() { + dq.append(&mut nq) + } + let now = Instant::now(); + inc_new_counter_info!("streamer-recv_window-recv", dq.len(), 100); + + submit( + influxdb::Point::new("recv-window") + .add_field("count", influxdb::Value::Integer(dq.len() as i64)) + .to_owned(), + ); + + trace!( + "{}: RECV_WINDOW {} {}: got packets {}", + id, + *consumed, + *received, + dq.len(), + ); + + retransmit_all_leader_blocks( + window, + maybe_leader, + &dq, + id, + *consumed, + *received, + retransmit, + pending_retransmits, + )?; + + let mut pixs = Vec::new(); + //send a contiguous set of blocks + let mut consume_queue = Vec::new(); + for b in dq { + let (pix, meta_size) = { + let p = b.read().unwrap(); + (p.index()?, p.meta.size) + }; + pixs.push(pix); + + if !window + .read() + .unwrap() + .blob_idx_in_window(&id, pix, *consumed, received) + { + continue; + } + + // For downloading storage blobs, + // we only want up to a certain index + // then stop + if max_ix != 0 && pix > max_ix { + continue; + } + + trace!("{} window pix: {} size: {}", id, pix, meta_size); + + window.write().unwrap().process_blob( + id, + b, + pix, + &mut consume_queue, + consumed, + tick_height, + leader_unknown, + pending_retransmits, + ); + + // Send a signal when we hit the max entry_height + if max_ix != 0 && *consumed == (max_ix + 1) { + done.store(true, Ordering::Relaxed); + } + } + if log_enabled!(Level::Trace) { + trace!("{}", window.read().unwrap().print(id, *consumed)); + trace!( + "{}: consumed: {} received: {} sending consume.len: {} pixs: {:?} took {} ms", + id, + *consumed, + *received, + consume_queue.len(), + pixs, + duration_as_ms(&now.elapsed()) + ); + } + if !consume_queue.is_empty() { + inc_new_counter_info!("streamer-recv_window-consume", consume_queue.len()); + s.send(consume_queue)?; + } + Ok(()) +} + +#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] +pub fn window_service( + cluster_info: Arc>, + window: SharedWindow, + tick_height: u64, + entry_height: u64, + max_entry_height: u64, + r: BlobReceiver, + s: EntrySender, + retransmit: BlobSender, + repair_socket: Arc, + leader_scheduler: Arc>, + done: Arc, +) -> JoinHandle<()> { + Builder::new() + .name("solana-window".to_string()) + .spawn(move || { + let mut tick_height_ = tick_height; + let mut consumed = entry_height; + let mut received = entry_height; + let mut last = entry_height; + let mut times = 0; + let id = cluster_info.read().unwrap().my_data().id; + let mut pending_retransmits = false; + trace!("{}: RECV_WINDOW started", id); + loop { + // Check if leader rotation was configured + if let Err(e) = recv_window( + &window, + &id, + &cluster_info, + &mut consumed, + &mut received, + &mut tick_height_, + max_entry_height, + &r, + &s, + &retransmit, + &mut pending_retransmits, + &done, + ) { + match e { + Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => break, + Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), + _ => { + inc_new_counter_info!("streamer-window-error", 1, 1); + error!("window error: {:?}", e); + } + } + } + + submit( + influxdb::Point::new("window-stage") + .add_field("consumed", influxdb::Value::Integer(consumed as i64)) + .to_owned(), + ); + + if received <= consumed { + trace!( + "{} we have everything received:{} consumed:{}", + id, + received, + consumed + ); + continue; + } + + //exponential backoff + if !repair_backoff(&mut last, &mut times, consumed) { + trace!("{} !repair_backoff() times = {}", id, times); + continue; + } + trace!("{} let's repair! times = {}", id, times); + + let mut window = window.write().unwrap(); + let reqs = window.repair( + &cluster_info, + &id, + times, + consumed, + received, + tick_height_, + max_entry_height, + &leader_scheduler, + ); + for (to, req) in reqs { + repair_socket.send_to(&req, to).unwrap_or_else(|e| { + info!("{} repair req send_to({}) error {:?}", id, to, e); + 0 + }); + } + } + }).unwrap() +} + +#[cfg(test)] +mod test { + use cluster_info::{ClusterInfo, Node}; + use entry::Entry; + use leader_scheduler::LeaderScheduler; + use logger; + use packet::{make_consecutive_blobs, SharedBlob, PACKET_DATA_SIZE}; + use solana_sdk::hash::Hash; + use std::net::UdpSocket; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::mpsc::{channel, Receiver}; + use std::sync::{Arc, RwLock}; + use std::time::Duration; + use streamer::{blob_receiver, responder}; + use window::default_window; + use window_service::{repair_backoff, window_service}; + + fn get_entries(r: Receiver>, num: &mut usize) { + for _t in 0..5 { + let timer = Duration::new(1, 0); + match r.recv_timeout(timer) { + Ok(m) => { + *num += m.len(); + } + e => info!("error {:?}", e), + } + if *num == 10 { + break; + } + } + } + + #[test] + pub fn window_send_test() { + logger::setup(); + let tn = Node::new_localhost(); + let exit = Arc::new(AtomicBool::new(false)); + let mut cluster_info_me = ClusterInfo::new(tn.info.clone()); + let me_id = cluster_info_me.my_data().id; + cluster_info_me.set_leader(me_id); + let subs = Arc::new(RwLock::new(cluster_info_me)); + + let (s_reader, r_reader) = channel(); + let t_receiver = blob_receiver(Arc::new(tn.sockets.gossip), exit.clone(), s_reader); + let (s_window, r_window) = channel(); + let (s_retransmit, r_retransmit) = channel(); + let win = Arc::new(RwLock::new(default_window())); + let done = Arc::new(AtomicBool::new(false)); + let t_window = window_service( + subs, + win, + 0, + 0, + 0, + r_reader, + s_window, + s_retransmit, + Arc::new(tn.sockets.repair), + Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader(me_id))), + done, + ); + let t_responder = { + let (s_responder, r_responder) = channel(); + let blob_sockets: Vec> = + tn.sockets.replicate.into_iter().map(Arc::new).collect(); + + let t_responder = responder("window_send_test", blob_sockets[0].clone(), r_responder); + let mut num_blobs_to_make = 10; + let gossip_address = &tn.info.ncp; + let msgs = make_consecutive_blobs( + me_id, + num_blobs_to_make, + 0, + Hash::default(), + &gossip_address, + ).into_iter() + .rev() + .collect();; + s_responder.send(msgs).expect("send"); + t_responder + }; + + let mut num = 0; + get_entries(r_window, &mut num); + assert_eq!(num, 10); + let mut q = r_retransmit.recv().unwrap(); + while let Ok(mut nq) = r_retransmit.try_recv() { + q.append(&mut nq); + } + assert_eq!(q.len(), 10); + exit.store(true, Ordering::Relaxed); + t_receiver.join().expect("join"); + t_responder.join().expect("join"); + t_window.join().expect("join"); + } + + #[test] + pub fn window_send_no_leader_test() { + logger::setup(); + let tn = Node::new_localhost(); + let exit = Arc::new(AtomicBool::new(false)); + let cluster_info_me = ClusterInfo::new(tn.info.clone()); + let me_id = cluster_info_me.my_data().id; + let subs = Arc::new(RwLock::new(cluster_info_me)); + + let (s_reader, r_reader) = channel(); + let t_receiver = blob_receiver(Arc::new(tn.sockets.gossip), exit.clone(), s_reader); + let (s_window, _r_window) = channel(); + let (s_retransmit, r_retransmit) = channel(); + let win = Arc::new(RwLock::new(default_window())); + let done = Arc::new(AtomicBool::new(false)); + let t_window = window_service( + subs.clone(), + win, + 0, + 0, + 0, + r_reader, + s_window, + s_retransmit, + Arc::new(tn.sockets.repair), + // TODO: For now, the window still checks the ClusterInfo for the current leader + // to determine whether to retransmit a block. In the future when we rely on + // the LeaderScheduler for retransmits, this test will need to be rewritten + // because a leader should only be unknown in the window when the write stage + // hasn't yet calculated the leaders for slots in the next epoch (on entries + // at heights that are multiples of seed_rotation_interval in LeaderScheduler) + Arc::new(RwLock::new(LeaderScheduler::default())), + done, + ); + let t_responder = { + let (s_responder, r_responder) = channel(); + let blob_sockets: Vec> = + tn.sockets.replicate.into_iter().map(Arc::new).collect(); + let t_responder = responder("window_send_test", blob_sockets[0].clone(), r_responder); + let mut msgs = Vec::new(); + for v in 0..10 { + let i = 9 - v; + let b = SharedBlob::default(); + { + let mut w = b.write().unwrap(); + w.set_index(i).unwrap(); + w.set_id(&me_id).unwrap(); + assert_eq!(i, w.index().unwrap()); + w.meta.size = PACKET_DATA_SIZE; + w.meta.set_addr(&tn.info.ncp); + } + msgs.push(b); + } + s_responder.send(msgs).expect("send"); + t_responder + }; + + assert!(r_retransmit.recv_timeout(Duration::new(3, 0)).is_err()); + exit.store(true, Ordering::Relaxed); + t_receiver.join().expect("join"); + t_responder.join().expect("join"); + t_window.join().expect("join"); + } + + #[test] + pub fn window_send_late_leader_test() { + logger::setup(); + let tn = Node::new_localhost(); + let exit = Arc::new(AtomicBool::new(false)); + let cluster_info_me = ClusterInfo::new(tn.info.clone()); + let me_id = cluster_info_me.my_data().id; + let subs = Arc::new(RwLock::new(cluster_info_me)); + + let (s_reader, r_reader) = channel(); + let t_receiver = blob_receiver(Arc::new(tn.sockets.gossip), exit.clone(), s_reader); + let (s_window, _r_window) = channel(); + let (s_retransmit, r_retransmit) = channel(); + let win = Arc::new(RwLock::new(default_window())); + let done = Arc::new(AtomicBool::new(false)); + let t_window = window_service( + subs.clone(), + win, + 0, + 0, + 0, + r_reader, + s_window, + s_retransmit, + Arc::new(tn.sockets.repair), + // TODO: For now, the window still checks the ClusterInfo for the current leader + // to determine whether to retransmit a block. In the future when we rely on + // the LeaderScheduler for retransmits, this test will need to be rewritten + // becasue a leader should only be unknown in the window when the write stage + // hasn't yet calculated the leaders for slots in the next epoch (on entries + // at heights that are multiples of seed_rotation_interval in LeaderScheduler) + Arc::new(RwLock::new(LeaderScheduler::default())), + done, + ); + let t_responder = { + let (s_responder, r_responder) = channel(); + let blob_sockets: Vec> = + tn.sockets.replicate.into_iter().map(Arc::new).collect(); + let t_responder = responder("window_send_test", blob_sockets[0].clone(), r_responder); + let mut msgs = Vec::new(); + for v in 0..10 { + let i = 9 - v; + let b = SharedBlob::default(); + { + let mut w = b.write().unwrap(); + w.set_index(i).unwrap(); + w.set_id(&me_id).unwrap(); + assert_eq!(i, w.index().unwrap()); + w.meta.size = PACKET_DATA_SIZE; + w.meta.set_addr(&tn.info.ncp); + } + msgs.push(b); + } + s_responder.send(msgs).expect("send"); + + assert!(r_retransmit.recv_timeout(Duration::new(3, 0)).is_err()); + + subs.write().unwrap().set_leader(me_id); + + let mut msgs1 = Vec::new(); + for v in 1..5 { + let i = 9 + v; + let b = SharedBlob::default(); + { + let mut w = b.write().unwrap(); + w.set_index(i).unwrap(); + w.set_id(&me_id).unwrap(); + assert_eq!(i, w.index().unwrap()); + w.meta.size = PACKET_DATA_SIZE; + w.meta.set_addr(&tn.info.ncp); + } + msgs1.push(b); + } + s_responder.send(msgs1).expect("send"); + t_responder + }; + let mut q = r_retransmit.recv().unwrap(); + while let Ok(mut nq) = r_retransmit.recv_timeout(Duration::from_millis(100)) { + q.append(&mut nq); + } + assert!(q.len() > 10); + exit.store(true, Ordering::Relaxed); + t_receiver.join().expect("join"); + t_responder.join().expect("join"); + t_window.join().expect("join"); + } + + #[test] + pub fn test_repair_backoff() { + let num_tests = 100; + let res: usize = (0..num_tests) + .map(|_| { + let mut last = 0; + let mut times = 0; + let total: usize = (0..127) + .map(|x| { + let rv = repair_backoff(&mut last, &mut times, 1) as usize; + assert_eq!(times, x + 2); + rv + }).sum(); + assert_eq!(times, 128); + assert_eq!(last, 1); + repair_backoff(&mut last, &mut times, 1); + assert_eq!(times, 64); + repair_backoff(&mut last, &mut times, 2); + assert_eq!(times, 2); + assert_eq!(last, 2); + total + }).sum(); + let avg = res / num_tests; + assert!(avg >= 3); + assert!(avg <= 5); + } +} diff --git a/ci/docker-llvm/build.sh b/ci/docker-llvm/build.sh index 47a35f0a2775b3..abc953acc0512b 100755 --- a/ci/docker-llvm/build.sh +++ b/ci/docker-llvm/build.sh @@ -4,4 +4,4 @@ set -ex cd "$(dirname "$0")" docker build -t solanalabs/llvm . -# docker push solanalabs/llvm +docker push solanalabs/llvm diff --git a/programs/bpf/c/sdk/bpf.mk b/programs/bpf/c/sdk/bpf.mk index c457503655759a..10f418d54509cf 100644 --- a/programs/bpf/c/sdk/bpf.mk +++ b/programs/bpf/c/sdk/bpf.mk @@ -12,8 +12,8 @@ SRC_DIR ?= ./src TEST_DIR ?= ./test OUT_DIR ?= ./out -ifeq ($(USE_DOCKER),1) -LLVM_DIR = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/llvm-wrappers +ifeq ($(DOCKER),1) +LLVM_DIR = $(LOCAL_PATH)llvm/llvm-docker else OS=$(shell uname) ifeq ($(OS),Darwin) @@ -108,6 +108,8 @@ help: @echo ' - The following setting are overridable on the command line, default values shown:' @echo ' - Show commands while building:' @echo ' V=1' + @echo ' - Use LLVM from docker:' + @echo ' DOCKER=1' @echo ' - List of include directories:' @echo ' INC_DIRS=$(INC_DIRS)' @echo ' - List of system include directories:' diff --git a/programs/bpf/c/sdk/llvm-wrappers/bin/clang b/programs/bpf/c/sdk/llvm-wrappers/bin/clang deleted file mode 100755 index c13cfbd58dbc07..00000000000000 --- a/programs/bpf/c/sdk/llvm-wrappers/bin/clang +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -set -ex - - -docker run --workdir /solana_sdk --volume /Users/jack/sandbox/git/solana/programs/bpf/c:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" diff --git a/programs/bpf/c/sdk/llvm-wrappers/bin/clang++ b/programs/bpf/c/sdk/llvm-wrappers/bin/clang++ deleted file mode 100755 index c13cfbd58dbc07..00000000000000 --- a/programs/bpf/c/sdk/llvm-wrappers/bin/clang++ +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -set -ex - - -docker run --workdir /solana_sdk --volume /Users/jack/sandbox/git/solana/programs/bpf/c:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" diff --git a/programs/bpf/c/sdk/llvm-wrappers/bin/llc b/programs/bpf/c/sdk/llvm-wrappers/bin/llc deleted file mode 100755 index c13cfbd58dbc07..00000000000000 --- a/programs/bpf/c/sdk/llvm-wrappers/bin/llc +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -set -ex - - -docker run --workdir /solana_sdk --volume /Users/jack/sandbox/git/solana/programs/bpf/c:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" diff --git a/programs/bpf/c/sdk/llvm-wrappers/bin/llvm-objdump b/programs/bpf/c/sdk/llvm-wrappers/bin/llvm-objdump deleted file mode 100755 index c13cfbd58dbc07..00000000000000 --- a/programs/bpf/c/sdk/llvm-wrappers/bin/llvm-objdump +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -set -ex - - -docker run --workdir /solana_sdk --volume /Users/jack/sandbox/git/solana/programs/bpf/c:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" diff --git a/programs/bpf/c/sdk/llvm-wrappers/utils/copy.sh b/programs/bpf/c/sdk/llvm-wrappers/utils/copy.sh deleted file mode 100755 index 0d003cf4e74492..00000000000000 --- a/programs/bpf/c/sdk/llvm-wrappers/utils/copy.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -mkdir -p ../bin -cp llvm.sh ../bin/clang -cp llvm.sh ../bin/clang++ -cp llvm.sh ../bin/llc -cp llvm.sh ../bin/llvm-objdump diff --git a/programs/bpf/c/sdk/llvm-wrappers/utils/llvm.sh b/programs/bpf/c/sdk/llvm-wrappers/utils/llvm.sh deleted file mode 100755 index c13cfbd58dbc07..00000000000000 --- a/programs/bpf/c/sdk/llvm-wrappers/utils/llvm.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -set -ex - - -docker run --workdir /solana_sdk --volume /Users/jack/sandbox/git/solana/programs/bpf/c:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" diff --git a/programs/bpf/c/sdk/llvm/llvm-docker/bin/clang b/programs/bpf/c/sdk/llvm/llvm-docker/bin/clang new file mode 100755 index 00000000000000..a4e86ef94f7935 --- /dev/null +++ b/programs/bpf/c/sdk/llvm/llvm-docker/bin/clang @@ -0,0 +1,3 @@ +#!/usr/bin/env bash -ex +SDKPATH="$( cd "$(dirname "$0")" ; pwd -P )"/../../../.. +docker run --workdir /solana_sdk --volume $SDKPATH:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" diff --git a/programs/bpf/c/sdk/llvm/llvm-docker/bin/clang++ b/programs/bpf/c/sdk/llvm/llvm-docker/bin/clang++ new file mode 100755 index 00000000000000..a4e86ef94f7935 --- /dev/null +++ b/programs/bpf/c/sdk/llvm/llvm-docker/bin/clang++ @@ -0,0 +1,3 @@ +#!/usr/bin/env bash -ex +SDKPATH="$( cd "$(dirname "$0")" ; pwd -P )"/../../../.. +docker run --workdir /solana_sdk --volume $SDKPATH:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" diff --git a/programs/bpf/c/sdk/llvm/llvm-docker/bin/llc b/programs/bpf/c/sdk/llvm/llvm-docker/bin/llc new file mode 100755 index 00000000000000..a4e86ef94f7935 --- /dev/null +++ b/programs/bpf/c/sdk/llvm/llvm-docker/bin/llc @@ -0,0 +1,3 @@ +#!/usr/bin/env bash -ex +SDKPATH="$( cd "$(dirname "$0")" ; pwd -P )"/../../../.. +docker run --workdir /solana_sdk --volume $SDKPATH:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" diff --git a/programs/bpf/c/sdk/llvm/llvm-docker/bin/llvm-objdump b/programs/bpf/c/sdk/llvm/llvm-docker/bin/llvm-objdump new file mode 100755 index 00000000000000..a4e86ef94f7935 --- /dev/null +++ b/programs/bpf/c/sdk/llvm/llvm-docker/bin/llvm-objdump @@ -0,0 +1,3 @@ +#!/usr/bin/env bash -ex +SDKPATH="$( cd "$(dirname "$0")" ; pwd -P )"/../../../.. +docker run --workdir /solana_sdk --volume $SDKPATH:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" diff --git a/programs/bpf/c/sdk/llvm/llvm-docker/generate.sh b/programs/bpf/c/sdk/llvm/llvm-docker/generate.sh new file mode 100755 index 00000000000000..9bb705270502b3 --- /dev/null +++ b/programs/bpf/c/sdk/llvm/llvm-docker/generate.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +read -r -d '' SCRIPT << 'EOM' +#!/usr/bin/env bash -ex +SDKPATH="$( cd "$(dirname "$0")" ; pwd -P )"/../../../.. +docker run --workdir /solana_sdk --volume $SDKPATH:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" +EOM + +echo "$SCRIPT" > bin/clang +echo "$SCRIPT" > bin/clang++ +echo "$SCRIPT" > bin/llc +echo "$SCRIPT" > bin/llvm-objdump From a3fbfcfcdb2bdf093fa8a6d7269b3247bd737db2 Mon Sep 17 00:00:00 2001 From: Jack May Date: Tue, 27 Nov 2018 16:50:51 -0800 Subject: [PATCH 4/8] cleanup --- book/.nojekyll | 1 - book/FontAwesome/css/font-awesome.css | 4 - book/FontAwesome/fonts/FontAwesome.ttf | Bin 138204 -> 0 bytes .../FontAwesome/fonts/fontawesome-webfont.eot | Bin 68875 -> 0 bytes .../FontAwesome/fonts/fontawesome-webfont.svg | 640 ----- .../FontAwesome/fonts/fontawesome-webfont.ttf | Bin 138204 -> 0 bytes .../fonts/fontawesome-webfont.woff | Bin 81284 -> 0 bytes .../fonts/fontawesome-webfont.woff2 | Bin 64464 -> 0 bytes book/appendix.html | 202 -- book/avalanche.html | 217 -- book/ayu-highlight.css | 71 - book/bank.rs | 2403 ----------------- book/banking_stage.rs | 449 --- book/bin/bench-streamer.rs | 124 - book/bin/bench-tps.rs | 873 ------ book/bin/fullnode-config.rs | 81 - book/bin/fullnode.rs | 213 -- book/bin/genesis.rs | 85 - book/bin/keygen.rs | 37 - book/bin/ledger-tool.rs | 162 -- book/bin/replicator.rs | 153 -- book/bin/upload-perf.rs | 116 - book/bin/wallet.rs | 235 -- book/blob_fetch_stage.rs | 47 - book/bloom.rs | 105 - book/book.js | 600 ---- book/bpf_loader.rs | 24 - book/broadcast_stage.rs | 299 -- book/budget_expr.rs | 232 -- book/budget_instruction.rs | 24 - book/budget_program.rs | 641 ----- book/budget_transaction.rs | 346 --- book/chacha.rs | 92 - book/chacha_cuda.rs | 212 -- book/client.rs | 8 - book/clipboard.min.js | 7 - book/cluster_info.rs | 1346 --------- book/compute_leader_finality_service.rs | 205 -- book/contact_info.rs | 237 -- book/counter.rs | 183 -- book/crds.rs | 351 --- book/crds_gossip.rs | 486 ---- book/crds_gossip_error.rs | 7 - book/crds_gossip_pull.rs | 378 --- book/crds_gossip_push.rs | 459 ---- book/crds_traits_impls.rs | 26 - book/crds_value.rs | 147 - book/css/chrome.css | 417 --- book/css/general.css | 144 - book/css/print.css | 54 - book/css/variables.css | 210 -- book/db_ledger.rs | 634 ----- book/db_window.rs | 578 ---- book/elasticlunr.min.js | 10 - book/entry.rs | 379 --- book/erasure.rs | 943 ------- book/favicon.png | Bin 5679 -> 0 bytes book/fetch_stage.rs | 48 - book/fullnode.html | 223 -- book/fullnode.rs | 1040 ------- book/highlight.css | 69 - book/highlight.js | 2 - book/img/fullnode.svg | 278 -- book/img/leader-scheduler.svg | 330 --- book/img/runtime.svg | 211 -- book/img/sdk-tools.svg | 237 -- book/img/tpu.svg | 323 --- book/img/tvu.svg | 323 --- book/index.html | 215 -- book/introduction.html | 223 -- book/jsonrpc-api.html | 514 ---- book/jsonrpc-service.html | 200 -- book/leader-scheduler.html | 286 -- book/leader_scheduler.rs | 1407 ---------- book/ledger.html | 200 -- book/ledger.rs | 1007 ------- book/ledger_write_stage.rs | 92 - book/lib.rs | 143 - book/loader_transaction.rs | 49 - book/logger.rs | 16 - book/mark.min.js | 7 - book/mint.rs | 163 -- book/native_loader.rs | 127 - book/ncp.html | 201 -- book/ncp.rs | 86 - book/netutil.rs | 304 --- book/packet.rs | 569 ---- book/payment_plan.rs | 27 - book/poh.html | 235 -- book/poh.rs | 116 - book/poh_recorder.rs | 149 - book/poh_service.rs | 94 - book/print.html | 1282 --------- book/programs.html | 226 -- book/recvmmsg.rs | 184 -- book/replicate_stage.rs | 683 ----- book/replicator.rs | 337 --- book/result.rs | 187 -- book/retransmit_stage.rs | 127 - book/rpc.rs | 771 ------ book/rpc_pubsub.rs | 632 ----- book/rpc_request.rs | 191 -- book/runtime.html | 279 -- book/searcher.js | 477 ---- book/searchindex.js | 1 - book/searchindex.json | 1 - book/service.rs | 30 - book/signature.rs | 67 - book/sigverify.rs | 473 ---- book/sigverify_stage.rs | 156 -- book/storage.html | 296 -- book/storage_program.rs | 75 - book/storage_stage.rs | 456 ---- book/storage_transaction.rs | 22 - book/store_ledger_stage.rs | 72 - book/streamer.rs | 200 -- book/synchronization.html | 235 -- book/system_program.rs | 306 --- book/system_transaction.rs | 221 -- book/terminology.html | 230 -- book/thin_client.rs | 739 ----- book/token_program.rs | 24 - book/tomorrow-night.css | 96 - book/tpu.html | 201 -- book/tpu.rs | 96 - book/tpu_forwarder.rs | 198 -- book/transaction.rs | 1 - book/tvu.html | 201 -- book/tvu.rs | 350 --- book/vdf.html | 214 -- book/vote_program.rs | 175 -- book/vote_stage.rs | 82 - book/vote_transaction.rs | 118 - book/wallet.html | 473 ---- book/wallet.rs | 1610 ----------- book/window.rs | 555 ---- book/window_service.rs | 626 ----- 137 files changed, 39387 deletions(-) delete mode 100644 book/.nojekyll delete mode 100644 book/FontAwesome/css/font-awesome.css delete mode 100644 book/FontAwesome/fonts/FontAwesome.ttf delete mode 100644 book/FontAwesome/fonts/fontawesome-webfont.eot delete mode 100644 book/FontAwesome/fonts/fontawesome-webfont.svg delete mode 100644 book/FontAwesome/fonts/fontawesome-webfont.ttf delete mode 100644 book/FontAwesome/fonts/fontawesome-webfont.woff delete mode 100644 book/FontAwesome/fonts/fontawesome-webfont.woff2 delete mode 100644 book/appendix.html delete mode 100644 book/avalanche.html delete mode 100644 book/ayu-highlight.css delete mode 100644 book/bank.rs delete mode 100644 book/banking_stage.rs delete mode 100644 book/bin/bench-streamer.rs delete mode 100644 book/bin/bench-tps.rs delete mode 100644 book/bin/fullnode-config.rs delete mode 100644 book/bin/fullnode.rs delete mode 100644 book/bin/genesis.rs delete mode 100644 book/bin/keygen.rs delete mode 100644 book/bin/ledger-tool.rs delete mode 100644 book/bin/replicator.rs delete mode 100644 book/bin/upload-perf.rs delete mode 100644 book/bin/wallet.rs delete mode 100644 book/blob_fetch_stage.rs delete mode 100644 book/bloom.rs delete mode 100644 book/book.js delete mode 100644 book/bpf_loader.rs delete mode 100644 book/broadcast_stage.rs delete mode 100644 book/budget_expr.rs delete mode 100644 book/budget_instruction.rs delete mode 100644 book/budget_program.rs delete mode 100644 book/budget_transaction.rs delete mode 100644 book/chacha.rs delete mode 100644 book/chacha_cuda.rs delete mode 100644 book/client.rs delete mode 100644 book/clipboard.min.js delete mode 100644 book/cluster_info.rs delete mode 100644 book/compute_leader_finality_service.rs delete mode 100644 book/contact_info.rs delete mode 100644 book/counter.rs delete mode 100644 book/crds.rs delete mode 100644 book/crds_gossip.rs delete mode 100644 book/crds_gossip_error.rs delete mode 100644 book/crds_gossip_pull.rs delete mode 100644 book/crds_gossip_push.rs delete mode 100644 book/crds_traits_impls.rs delete mode 100644 book/crds_value.rs delete mode 100644 book/css/chrome.css delete mode 100644 book/css/general.css delete mode 100644 book/css/print.css delete mode 100644 book/css/variables.css delete mode 100644 book/db_ledger.rs delete mode 100644 book/db_window.rs delete mode 100644 book/elasticlunr.min.js delete mode 100644 book/entry.rs delete mode 100644 book/erasure.rs delete mode 100644 book/favicon.png delete mode 100644 book/fetch_stage.rs delete mode 100644 book/fullnode.html delete mode 100644 book/fullnode.rs delete mode 100644 book/highlight.css delete mode 100644 book/highlight.js delete mode 100644 book/img/fullnode.svg delete mode 100644 book/img/leader-scheduler.svg delete mode 100644 book/img/runtime.svg delete mode 100644 book/img/sdk-tools.svg delete mode 100644 book/img/tpu.svg delete mode 100644 book/img/tvu.svg delete mode 100644 book/index.html delete mode 100644 book/introduction.html delete mode 100644 book/jsonrpc-api.html delete mode 100644 book/jsonrpc-service.html delete mode 100644 book/leader-scheduler.html delete mode 100644 book/leader_scheduler.rs delete mode 100644 book/ledger.html delete mode 100644 book/ledger.rs delete mode 100644 book/ledger_write_stage.rs delete mode 100644 book/lib.rs delete mode 100644 book/loader_transaction.rs delete mode 100644 book/logger.rs delete mode 100644 book/mark.min.js delete mode 100644 book/mint.rs delete mode 100644 book/native_loader.rs delete mode 100644 book/ncp.html delete mode 100644 book/ncp.rs delete mode 100644 book/netutil.rs delete mode 100644 book/packet.rs delete mode 100644 book/payment_plan.rs delete mode 100644 book/poh.html delete mode 100644 book/poh.rs delete mode 100644 book/poh_recorder.rs delete mode 100644 book/poh_service.rs delete mode 100644 book/print.html delete mode 100644 book/programs.html delete mode 100644 book/recvmmsg.rs delete mode 100644 book/replicate_stage.rs delete mode 100644 book/replicator.rs delete mode 100644 book/result.rs delete mode 100644 book/retransmit_stage.rs delete mode 100644 book/rpc.rs delete mode 100644 book/rpc_pubsub.rs delete mode 100644 book/rpc_request.rs delete mode 100644 book/runtime.html delete mode 100644 book/searcher.js delete mode 100644 book/searchindex.js delete mode 100644 book/searchindex.json delete mode 100644 book/service.rs delete mode 100644 book/signature.rs delete mode 100644 book/sigverify.rs delete mode 100644 book/sigverify_stage.rs delete mode 100644 book/storage.html delete mode 100644 book/storage_program.rs delete mode 100644 book/storage_stage.rs delete mode 100644 book/storage_transaction.rs delete mode 100644 book/store_ledger_stage.rs delete mode 100644 book/streamer.rs delete mode 100644 book/synchronization.html delete mode 100644 book/system_program.rs delete mode 100644 book/system_transaction.rs delete mode 100644 book/terminology.html delete mode 100644 book/thin_client.rs delete mode 100644 book/token_program.rs delete mode 100644 book/tomorrow-night.css delete mode 100644 book/tpu.html delete mode 100644 book/tpu.rs delete mode 100644 book/tpu_forwarder.rs delete mode 100644 book/transaction.rs delete mode 100644 book/tvu.html delete mode 100644 book/tvu.rs delete mode 100644 book/vdf.html delete mode 100644 book/vote_program.rs delete mode 100644 book/vote_stage.rs delete mode 100644 book/vote_transaction.rs delete mode 100644 book/wallet.html delete mode 100644 book/wallet.rs delete mode 100644 book/window.rs delete mode 100644 book/window_service.rs diff --git a/book/.nojekyll b/book/.nojekyll deleted file mode 100644 index 8631215945962e..00000000000000 --- a/book/.nojekyll +++ /dev/null @@ -1 +0,0 @@ -This file makes sure that Github Pages doesn't process mdBook's output. \ No newline at end of file diff --git a/book/FontAwesome/css/font-awesome.css b/book/FontAwesome/css/font-awesome.css deleted file mode 100644 index ee4e9782bf8b32..00000000000000 --- a/book/FontAwesome/css/font-awesome.css +++ /dev/null @@ -1,4 +0,0 @@ -/*! - * Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.4.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.4.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.4.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.4.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.4.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.4.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"} diff --git a/book/FontAwesome/fonts/FontAwesome.ttf b/book/FontAwesome/fonts/FontAwesome.ttf deleted file mode 100644 index d7994e13086b1ac1a216bd754c93e1bccd65f237..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138204 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 diff --git a/book/FontAwesome/fonts/fontawesome-webfont.eot b/book/FontAwesome/fonts/fontawesome-webfont.eot deleted file mode 100644 index a30335d748c65c0bab5880b4e6dba53f5c79206c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68875 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

7r8$?kb-D3z%-c} zLGfT`Wgm|$rwl&#jtEO8m)B!}oJ%(Y(1ZpeX!jfRK-wF?K|$LJuR~GdFpZL6EFp`H zFKc0?nf7)Jf~F8p9HP&6>OukC5dGx?Lbp8aZlyokWnzO{9f)9Eq=#VZ7oiJ19s_!U zKW^~F>qJP)$b+)$=5eqeuG%y_w~>W__r-D==WEwAxVHj#)B_QUqxOXBKA6BVKtLV$ zeYs+6ok?ZcBZ_E1nA7T;NjXlMlK3JMiknHuDCa2YDNa?#w8DpW+T2cSC2M~TY-&wp zU=khxHW;gbNOh@tL0WYr7+)8f*BopgUOjD}9Sue!X}rYPSzzq`X6Jr9J^El!nt7rV z-_LH88z|i8Lf(KFYzaW0B#NadwasYMt8x{fU74SMic0x(f<}NeWU2xUzMvPuQlu^W z0H(G%lz`WhgCVEdN1-&y%W8{_2{ggKk(d32qf0jMy*XA;L`zXPgJ=&K3E8Hl5-dQw zYQV(9u;^tEc=1P+CI+eu?p|QD(P+jL$ekSt-ql0w(gO@4M}h)q)&}d|3_!rXg}SO zNrzoRU12}4XW<~;c*q6wOIJih1VWbs-|gw$+;G&(?Hva3U%)z=Vh`p2;zsw{Hia)# zA#g}8ml%R60_?+hRS2l4a4$KYl)Ar6n>>S|?D|w-aL1fcG9nG7sr zTsw*AJG|Ot+~KTnGQA$0gs|wP60!-?EDjgUs=(5%o3HZAv%UlZTETO4?{?>IU^*c$ zfI|HiFZLfT*?tJjLjJKzEz1;a__-+ROUle%X|Srh0}`8Aj*dpURv9Y}D~%N~Jt|-< ztFc(?yokf2zSQEgU4vSB1^L4&cCo%Cs4sz(S3$BalWL$y}7Ymr_P(^@sQPB(NB&YK}P)MVu%NjiN0U^T{=6 zuS3%ou{xqv054t-X;k2$#}2uVv;ZVZ$qM9f1Pwe=2>tcwlQhdOypTc9CvkuayHdcn z?cQHu@yNNnk6J*e7KI}R;;@6(k{MnT1tV}p*H`1=gdlI;KroJR{d1w1c%Z<>;Fr$$ zs~90Ny7d$SuD78XKdMr2NEFSr5~W9sXq9Vu-{^0563Au-`^3zbOaY3z>Hn@Zfb4Vu z0vg(ibV4S=RWdkhXl9HOTqp$%L?T3UJ9sZNfOm6_G+1&Z;*!bXNn#N|Pb7-Ts3UwQ zlBN5KkHZ?Uu;26>j4v4(hfJe{BrX&)v5zCy46fxA;*~QI-Cl|W#u5mLj-~E)QKvSw zOOwMx{})jtMuUEhEr~mXgD(_GZ*&m323pEfy~k0lv?5}Fvx2unbibC6goRL|a%8nu z=*Q^2BR0hUy;^`y2E0jS21cpCNS%Z2M@zjqG(t_%z{;6R{yoI6_J4+g+TTFUm&lSns6m zq4GMm<~1lyAz(q0@V~M9JRA9en=atSBLeaV&5|?7T&A$5*E~ku>Se*PK@F4J-of3p zf~ygQi3`DA@C44^I%LxJ7y)YA!v9AESFFiht%#6SCSSKbfek0%ejZyN8^m$aKU?8$ zcjacpKYtPLq@Kf&zA>70>DFUyErOR_`|yPCaTR!BU(U^o(j%Kfkg%r`A~;@>bJdA= z5qTVKdeXKw1MYMYTOMdc%QTJsC@VIfbm0vP>MVm@SSV^mxu3Q-#H7#JOyGKum3p-c zAVeAc_ztmuUAH~7dZScBmu;za+5`?ik}!aX!d9}{FSAU&Wn!%+)%RQNb zT_Xye1j{iwDhEY!jB`%A6T+Ka(!P1O+`#6UfNR7DQ~#EvmO>FqoYLNr~%f zs#%lQ)PV-=$0~k4X>DgE>2Q~&+~uwM)>KNDr(q5ufV4i*%1QsZQz{%4zL|UH&*fN> zf(?GPYfb=nOgs(wG5lYvr8uXQdnE&!HF`xt4nU@iaZfV6C57t=1ljdfgph9_d+^8q z(y<*q^!66w^iZBre=<3`;8`#sVuA^{89TAE6ATz`9X#(jR5dgqK7EaWG}F+YoCY!N z`;_JGRWmbEPRL;rs;qqj}L8pX>m zEwAIf4GtC#>rV*KCAU5*TaAyOE(Bn0glhjI==&aL<`-jCu{)*Tqyos291*VDcpaGB z0$$9Kyaa4z-@t&NT*LNT@Jz&z$J~~>__hQKJp6Zoe9+K=gJjAO;1gGq$sUvC$f-HJ zP>R!Eq(NI><#-6P%1^Is)DaI1&oc8POdmv@yVeP6KNanDP9Z0!um?Z zc5slMebvf6YIx@ChBH+t=`PN5m4o0slgMbI7X1%oqLD~o6&dU;+l{(MgejrWOMtkT zmZcDZku1>I0;a(kqPGVH!SDlnOW=~-Is4S6?O31kvhr}@StWb@iqR$5mY=AB6nsm~Nb5t$9St z@eYSL5kh5A2)VEVYlfSJdbV%rWZcNJ9AnUe*S#N{t@b6!KBQ3OqP& zUx|4l$L*A~mO|JNL9V0FpT{iniWdzS#IQBfc(N5v!QMD1^SmfwAOm9naPgjwf$t)l z`m1{tO_`T*Q$kW`nGhK9p_X~vlSTMwhZ6l?u3Q(vv^wPm0Q_=r2pah~F`+5jhIHgZ z8!V!L)DztZ^W6z{YBml5vUOX57)z3cf8JKr8_@j9xyM$5EhIvV$a^^*dBy884CWJ? zU=rY|LIWU zdBFpUnN_6q$a+dnT%%G^{Y+C<^wp%|VFlmHiCe}O>V87Z2s$vjP#jVhCW@w8B>UK) zb1r+kijSezY^24mTH|%LrW;+o%T3c3M1$2ei4PZQAXjYY z@HpNqnxL{%JW2pl=mP=|jwU6Zff~Kc6rO~OA$TdqBXa*Z(%KDx)ksig&FLhatrf5S zp7O`6w+(y`Hv=|w902p$Vq86I=J}xXiOUh<1Ye06ZJP6*wq{@JhzD`A=bQL6wQnN)%L;ny86~&w(e6lpf6rgSMlK($cT7ZDxHy!-$NZ z;8RHh_@mL~;va@!^AfcGw%rJ~52_#3I%;=RF^rp+{e7Nt8l}U?I2ARzS)(+@u*ayy zV6QGW`1Fbj1W&gbCRQZ0g+{5Nh#|i11$3yAfAGW1AVl6hhZ zQY+R)U5<;guJ=AsmFf)*9-hbp;!wm!CCf4KWo|4STIYr^)in2Jp5%sr4{u)#C+%09 z&VYEaHx&b{H8BQx(i)OmQ%17S(L9b}5L|N@VeW~P=+Ybwb3KcteJme*66AuP0bO&+ z1qGc)mtFXcax{h9UDs~4XZ-s48Ffh9mx52Iqn;ko@>^0px$=WIWR2ushg`eLTqM*u z8U&H-_DZH}UvM1VQf_X40*tRMpX<*XM>W%=9D?wF5t{f#6yv1AQP8cyVZb^*wUWNs zJ?48?7M@otux$tctK54-&d&zj;%x3(PB7BII}Y^0tX$d+F3QUCh2x*Q)hdS=USu08 z>>tsjNey`}5UjvlpeAV-Ix34#2D4uhK;zi?nA#BIA)x+|=Kah&yaI*Uq76#HkXkr5 zvZ~)_HSF=bX-&r`v!SR9(|TQf%q#%oi70t({vz5d#QTZIwRNT27Nir>OV3?`~heshF0py}zPek+rr5>cmZOn;jN=P8kG&r-ObOMse zDP~Dvn6cj*?Cw2cSx?os_tHvT<^&~;;Px%HU4?hO3NZSGtRM?&=?TSQ@A6&fUF{20 zy6KX|S|CU)UB2AUj4g4m=JB%@2dB&dQm8{eagfplfC&wAy+ff<=Ob9oN< zJRsjeh_oweHD+~)o^FyWc>FLpVrOycmN-p52o8ntgH@IGwBL1*H(b_e{E^`vvbLYs zgPY$TWB{8dYYZlgv?GMIuGgqqUCFt=zWT#LU9X*V&pYxH5GWM?hzU&WrCygo6=H9J zs!g@a*XER-h`nby-V$>A4Y@4Ss5QySDPdf^6Pqac=K_vZaML*ZL;wUfO)F_-f~M!t z1AvqA|EK64{`pP-W6u%LK=WD^v5C2s0tE&iRi32A!Yr?*|KnxS+dNzp9UF}T*l3a&_Cj0-Ok z30BYpB9R%4Jz%py0!deR%^EP|>o@nJN!81B7;4HgWK>!blIn3UfmAtjQnMu1tfDLzFG-WP|_Sz7*N^2 zGu$?)ROl6z9WGeua1I#m&ht<6>v?sOHf1#Lis-eR?!ypl;z@7@?xZnLvjBx)Hi9a; znU}K*Hi(q)hZa0O!JxW)DUQoGRx#MwE5w{thSo`oVlVEWQTD@yQs?gf1V808s>9ml zsEwOyRC(YSFYcy92ez1kxzF$K&@%W0F+nt12LQ$TjM4f=m&Zp1Ocj<4LppWFk8!ad z?gjm%1-`*hs}_Fhdl(Th8rnHP;5si&S*iR<4fBHVJJubn>I<-7dtE*W#VTlwV)wX} z*~Ytx63Q)LTP&yu4&zEe%ljq@y7x0kw`=P?2S6n*S*%7XL^8`LWZtyvk&>`2R-tz* zB%s|H!xrDzqI@bRodF&tsC!F5oG>O_$qvFOOHv!s9=`Qw-5E`TP{dw=#Pj)bN4$R0 zbEg&*jF3O&xH(a$x;0Awk=kg<`M%`yd_o>5?Bwg?f&_TTqa#69Fs74$IKusCdxZg~ zGL*^y0Qj~P(9(EBCeFGvuUGd3V+I8T2Ib|;!+5&l;JQ*yO+BJFIRQyafGB}>wFf|& zK#w-U#;W1*uzP=wl%@etoDi&>yCDeW>Eu;640Zet*KCPQq)#%-Ui>=vA#Rsm&EUEZ zUBluAjdI0oScHG^L2!M^U7-sADVr5fBQ4BaZJ?+s2$<4rTN9` zA>>P3A8n%;77miy@5N2{~_ul&~<^3`%Uu zf}j{8PxGM&kL=IkUV2(ma3!v(Q6KH-kJR-5S3|YDGUsA!WI$+q@-`(Cc>(mm&rle! z<&woxb>T6H4QDLf0gF=~csU?S!(|drODqh@vG$>u4G0;c8osP}N>c)foMNL3Q=W@L zQj9c;=Fl#(OrZ`ou^Cm?;JB3eYcAg7kH^~Z9X8qZwUK*1Aj)Ckl({9T(F&yhZ*;NG zveM(U5f4+;rW|OHNhutQ0fIrU#5rNOVL5W+IETcE*QG@;Q5H|=TENP4MzI_E10P46 z^q@wn3W;Isn#yLtB0Ud(`dcjDX7abxd&_ZbhM+Uihl76QL91bOv_oA8de_f5uUl6| zJC`4AkYy3T%yf|H#Q?KF zc>|D!QUZe57A?+B4zGMt_{?pzX2D!jeKn>%FnHlVxKWn6q(0 zz^qZiN)4oRXt)*%$YMN*X^5pV?T)i%Kqp=r6D{Y`S#N12mMr7)K}i;!f#txTF9m)n za&wS|l7=K$r#tzB=l~1(D5Mi6bx@vu8l@B@rJ>^(1#Iz22?l^zfd|l_-rF<-Z8w4# z`*lDcGLan|piQ(paY%7>*8MFY^JN>=L^B<4+aAf(3wc!oKi#H`3z}h-8f-m-+alLl z0HAO}4~#8Jc|K`zCG2D!muGE( zpoM+XExtwX#OgsrYKA7s?PMdm61z=SvRFY5{)xX=a8XtqdlzPt@Q^($mV;|-kyvGX znn(buMZ`2la-vvp*KO&3F@a_*ZNfX(gHY^TfF8y82Pj#?I2LmCxhOshlbw+uj_8F@ zRV4FI$$!b`cfk5Yg*cN*0!{OvbKVymfoM4mhzRdqkX0;#P51^KmS|Cy$dcU;^o}gm zn$d6FdScdCgdKAZ_unA;o<7=}8#J()$s42`R@kKYD1ui?Xw_TMQCwp)Wx49kFW#;I zL_oX0X{o-zTzAD(xcIzZG$WZHI5ZhFH!R~GpXD~eTTRC`f|9cCz&AIG#dq{{7U(QV z%OGES*-MBPIYF@@&=RLeHxL#g4{UA8h=2SF5ks-5iTiGxWHL4dckua~h{73TQ;l>N zZZ4vntRzX@XeZRT3r{C|2ASJwA);D*5qKN~KHmc>G|xxxkzMBeVU$7LlXn^vb(RL7B00FD9kM!;Vc(&G6@)D z=mR+z7oysFLeZ1o4I#z?fHyG9ZS9dbeV0|WaC}ChQ*f} zDg>8(>;2*GIO%R@PlOkoqnU~H8;uxtyO0KxvCCQ-ze%A0&DCKF5xkR12#z7~-0Imz zCsk5jhq-ycveW@DyBwV*(%@ilBxTRdBe29UD3D4G2MHP(25^-fTktw1H9M|73@s`wqfCjwVb?fn zi{ey4n7TL&nU|fa17a}UxhQB5{6xXoYdQu9bLcDvTn0);*N2JKFihv3CBtA|`+|Ps zxKv&TA`*B@o#DaMR~a3XNO5nGy5S_@Zz>ZwWkE&@)jtmk=D65ELKb|da}jzQUU=I| zYle}r!-i#IKel8(OtL81EpwBWX#CdXEecJGH3^~AaUxk+i>3{N#(pX!5(@F+4U5qu z3pHdaT{7fdFd@JYl-|r=`USwU;VmrN6p!fmPUOG3?aUqEQWnBuwk5&v+W;xL8F#*N zP!AKz97%42zIYI*b2MZraa?^%n(f2CA>KDaL^Y}7V)Zf%>@BJu6pS4eBHIWUXh}oQ zdQEpi0<*Mu8)bDzTd{clcnwP(SLb+O70^F@2^nv9B9)b@o5$#z4L1Xg*U`%l;nuT~ zMiV^f;*BEqQ~Jd`^jsGy+ur zc)SrgxpTM2+|Ax8;YUl$2=B`Xm^>+eP;@y}Dt(hT+k^-z`1^!h2>am$uI#ayEHrAO z3mK6kc94CaW$0#EhyZCy;ONyOC=h4D&kk7nJ!zom!MLA0Yy{WRixS65ri1R#^79tN zFi97UdnXkhyl_L*A}L24hjDW)%D=fdEd)JcLI z3%4;_F~{3a>W;=WYYkw^K(ImeG&F=Z_iavcWG1Xx+@;#MU*Ic6Xnrh=E<50I!oe;? zpsYoz&o`ja1c+PKM2A@y1`+6;vj&IcJN=XC(Dl1HmDlG>(C~8# zCr`=B0BS_ljF(VNp&`8Nv>}ROI|M8f=nWCe3I?A*A!Lz`wp2zGeaSu0oZrBp0P?*L z-ogyHa8jXf0%K@nRjgibYe10LsgF7Q{z5@9wTMKA8GOElKW%2`jGz_a()K&ujX!3V zWSv)DgJD+DKS>@OZjc!(CejMO_!oyx?$L*&hPc5^W`J3LYXMEv@`Nd4W0TlhiUol) z)E8o5PM%4p+O>o*@vEo;LK=?r1|&s|$^3nw~wpz>4s6 zJ`%@)DLvS6e3&EY1)=`Xfw0 z2!ME9Xnjwfdtp^dl~w66n$1io2|=vx8`0bdwu5W~ZcB;iPydvHypJHq&$mEpiKl9z z(Dn#ITWB+c07f&!aA$OzGJ5fvM9gP2Jk0%QBdOwp%4DU{`wdl$dq| zn>9gPRKT;d{z;Y|HqLGKO-_XbbmAK7So?5}MzDlIyhvylvLJVi#fZplgDO4PEnMf2 zdU3e~`!xS7bF?fYNR}fRkO+g%)P0iQV$L$1b@XXUCG+INR#w|&*$n;GYLiZ;_S1N& z)q5^c9V##Zurw&>$!d!QLT}=!OcD^gx!N-naOyOIUGP50UTXFhf=p5r0+*Di{N62Z z;s;3_L-Rky8Og6Zay`)+l$Zw^uq8@>w07MQuxYJL0wcW@dv~%2>@ux+A(7ZS$vnTl zj+%WtudH%MAa&=>FR%>sldQ^S``Qgtu(Z;7I_kR)!36`?rr(M`%}ab&qoRpMH=*Kl z3zM3-5~UH66Ko^FNid1$Jmy;0gLR-ub!<+~N%0%EqbQK_lHlxZpYSa=T;v#=G)U~u z@*D_~tl`HTEps^ZZMh2%TH0aBXRI?7Y-5c_&_NnRQcn`&$HeKxW`GCzLAWb`hnu`O z3xy#oIF|y->4S`To>nFTB0uwcawgAa^w_dp#UUT-lmpskAYxYuN2p(ClW9Z4vU+p> z5G)dJ$YvA}nLmIOafAh~-*WUbN>KTJ=HLiKL`2WNb&(peqh=*8p9a@eRe9eGHZ#>w z_Z3oALz>+|-=er)p-^2z=Rggud}d@@sRncP!ucAObXGv;wWgx&H6lQT2w_IWpitr1 zEMa0IAZl3*0t6`dQ1xgdoJzdZqfc0(tA=`we*A<>)oH@$so_2!?HTX`(Gyz$WHkM`f@eO>9sGuVn3;L)7 z(6fnQt71xc!Ci?kP^Q<0up=8+v~T*@5=C!91Scq%TN?twj4tNfElc5cJlOm93o+!- zYQTU+MM(ge2xJ>tzm_U8Nr7b~fUepp{Kia1yn6z^Y&DiJ3FMse{^9>xDo4o4Nr_

MjT~HDem)#YNV}!)%NKBV=*$fkx6QQ6i^s@BkxFILM`8jk0 zXfbG4v}Z)>x$wz^PH_GfGtqXHRL40&M7JO~)rSEaEZ0E@6$9`JxSP^s64mfytiXHk zA6&_+{8+6;s+y1njZeo*P%_N>eI9ogXDBVGbyoQ}_rcx#l9(k25m?v$fQE`1ztn2Q`2oKv>Do9)hPk<^Qx$>9&lE>b2tCthjiiX{sD8i#ETOtCPf*vJ< zO8LANSRS4Q&Y934kDrsV$KiMkAPUHl`TULmIzOyG8~!wdj3)F3MX*A!;0p9;f>;CI zA(ny=3Zy5K4Ve!9?ocPK!;TV|St)lI!J@5P#{Gpj);bVufO_N%3KrF(0BDj!@{;=1 zm5_+|75R#bi%e8k>pv{G&pRXxSyBD4=D%|k*!5`?fSdb)nQI|q-zffG6JpxdO4Zp& z28pAg3@;u}5~1AvH+m%F>XB1&R3^7o3y^>^+$Ucul)CulvZ!K}R);CP+DLU-U>%bN zh!3hxug<4g7)MzFF)((8%_QiH(F`T(tSz|BY-BUE$aZziC^!O|n^R91`_C{OInEyS znDS;$emf+ji3p>}s9iBIgWVj712V~)qY)t(3han(m8)EXgV9VTw6bpiYBumb}v z^fd?=vU8-_G%~pYgwpL#gKk3s8+G2n4Bp7sx)?e`62bg?HFW}#T>RC65VIMy`PBj} zFwB5H5<3U(pJ43ygM%a2Ss;biZk3M;&_RLW%0(f*w{~?RtJMcViaUEieVjEx&Scu? zh7}$6E+9qZlhV2ld$dE^IwVg8O`zaPunQk$1B!YXf>bHV8HW74XEOIm_4n#neiQKq zK#PU*qEUpMac2T-FR^#t6pMHrY#p1rdc`6!A@llYd^Pn-g&gX_sc{K(^WhLWBH^U7 zNwkO^y>6(gmGOK?MI7AZe3vA;JGVuV*KS3M``}*_FM^gI#vbq>Ew@@p_qIuyd?E_O&%p3At>mU$1_F3Cq_eN z8^1-TQYa!a0t9Jcm5lg&#BAsaHzUVbXcz7R@Vz&`#LOSc;rjAMyIv z=zK3}n*y(gHmIaMm0VYuqrO7kkSM0H=`pS%0qGn3{NL=jA1N@&UBpHk4~mUM@!-tx zBY+8ybkD;AYDAOafD&Wfpr?F4zemSwgyvZP!qB3nL6b+$6CaHPcSmWj`ErD|Vzt%t zF=)gZe%K+I+-)f>w3$*bwWW?qiIqx5_{3}jU&f4y?Sc6;(8%nt!v=~3w3P|eiAt9= zA?e0aa2C)5;7y;7hT)o)T15R|H+m0$bBh(1`SzU3%%7y>mcXxKFcVOTgE` zh>K=j_6rKcUjkpoj4j}Vil*im>~uj#f+z)*ibv@vz>m2>@q~tVLO>3*teBBb$bqiabdai1T>>cAiMEsB3 z@JEL~ZSxpMSP|TG9-tOQvL7dam>l)Y$U6JfzwE3hks68=z4R<}9hQM);B7sBva0VJ zJ7}@de%u)@ydolpi7m*|>r(><;qqvB5fK=AbT9tAwI)Ly54N~hJOnN8m;U_0HZ)&i z^G?svl|AX)wx)?yFKz?w-)|kJY<9utmRvyt5v#28z(09<9!`}YB-$}?;M!I~Ps>7w zs&p4I=#=;rDsb(j+Q_ZXe(a6@h+aj->6xvH^rEODpmq1e zN)=JZPfR7(Awtu)F_jj)mzr+`6{XDyLx&Sgd_T$QW>_5-L4zQfc!0f;#n4PL;A)IK zEVFk4ru|uljvfi%D)`<3pcOVzlD-wCbV8~ffSG9^=o^}B8)wWeUW#m6@eyDbzi=%` z0|!VE!Y>>PKS%7Fb^buPHJ!i%>@13cDFx+~n^zz-a@WAPxwz%>D5@Knp?xm2klrdu z3`iCLAV#>VSvU9-n=e!zFt5j(-~%dE&*%8&f`B4Mj8c&0?2(TKq@cVFJMRVGc?S3I zTGt=O;Hc>ND}|;btA@MfpM87iptJoj*<@KvzZg`-P^ZgX;Be5E(k?{r%3Q3uLJnHX z0U;6kPPQ^XB8sa)>6Fa`nF3rvRY=Xct|{`L)+((5_a;xX7nRuqEyi|yL=Gw8R}k5h zTS(26Ese-GhItUiidK=vqgV1#GKLX0|5RcN`nC}Wx@MU#6`Z691FBjHP=zcSijGc2 z6UsX%*5o?~HM_^iMdG-w?Cb$SHH~cePnaXbItaCCTo6K0S?zlkNwFie5A|W1DWRDV zLGJo96Mxns&}LPtqa zn35OqH7_=QY7*#}-(KWvY0#f&4wTzL=#ThV&C;=YC)R>HoxPs|M#{-;43EKZq1w039W82tKZmwu(mK_L< z;AA8LS!|=!<~vkzJSc+e2?5S=;rJlMw;Sh!K0?3&gD4~0Pz2-fsDbVYMy2(Ee^FL2 zLX~kXf#r4#@sI~l(C2gw+Tah2HuX}zl#e(ZC{js_zA+=VFCMRCS2UvzW}OL0rc#s| zCZB|l)n2apHu8v*11q5Clh)yPDM2#KH3Qx8U%x=i8l+TGW8i=uhR`O zmWC6RNrLSm;W8#rA)W`21*?|`w#;%kluqj6j9F+5-1E#8l)+!N+)>s&+FN1uyLXIc z3nVMXn$_a-x%%~*N)K)g2kcznu zM-DS|Av{UJjVw6<5~Aq1b+o9Pb?JmMQ!=HI6sS~Z)q5UWHQpHwxvv`e1i&7F z?wd?|g;OVQu>jT>OC(-!fy%H9pA$u2{?Zvj5fn%#m?)%#kB5$1FeC=d+vt^5WGgrk zp*#e46CdRb=rs$J$o85a8=t?x%0;y}p*t+hnW zcE^F0xD1)8!Y^4t*_4}$ihC6ipA zjH^sKPYXFY^gWInz`<`5{~FMS^))*QX%~I^;l-_q0NJ)k5@Gsd5i{}T?wCZ{f%b?` zQve@aoi0^h+tR|66AwItc{!+K1u70mqKN<+9R)y@FAo=!Nu86k;<2X%`Cc61+2Ywpi0vC{nLTe}zfdMLiQZz?CW5s`4LgL9$w4p6eg!il& zJwYX!iMXlh$s$vqVjS+V&l*?qn#3Ghz>u0O7b^HR7n5JMFz8E*P!g1MB!$JRBuA)P zk~LUy$gS_(Z;Z$p=O=6$9t$lQ373mp^M5)-4M@r?;Bnpg+D07UhfrLtI?ZQrn1w5b zu&mRmB2b0gJP^qcU0}pO0VKN&5F#Q0%{lgi*rjz0EFUItTv~FEQ{1dMAHOd)s4CX@o)TcJV2q;iB>k)?@nf&i_2%Dr^@yz&hw2P13Uk9`MAi;Et^ zf=F9`Wz~V}3I+#%1$>K`99mA#Bm!v_-Vu4wKGw^+yCrHSB?1UrRiWvT47#*VDDqDaCau6|%j6Ox zg4P4U?Cc>SuP}E!xd3ZdQyAA*<$0kjoKZvUOIuPE`_s)YRaHFXLU!6i$^@3DhSlmE zB!q>W02xG28I_O030ZX>aM&m$W{vT}u|3{7Kt z3E5GQkr;^H{7hmjI8nwPq`j0Ug)$O(ex5!tI3gwovJa|>7!rrk>j1TAW6cG1!2ONH z3oo&gj6zAv9nb73A=0C;#->Si2NgD+cdDdFPr^<^67$%ejV^F* zGgryb9ga9)*tIx1Si+956{auxQ5GKS$TvE@q*X@VUr&tK9Cg6~_R>zY&@1Du#tUuM z!v%B;1Z)TU{F2dlLSNd0?oriMQasyhUEy6FmG|b;9^=YNQZ?~kFdv!x$w6|Wvh==H zMb5MJZo^bnfNZ4}$e}Dg5J=m+p{+psAi_DCZY`l12pNQBU@0Q2H5-~9_zCvPLJh_) znNR{PjjrbYXzD8q4q2=HL*Ji=ZkBwJE~k5kneV=#A3YbJ6jdcC;v|2|l9biwN3S!+ zQw4k(u9DD%N+)Niip`Ip*r<<1jIijJA*S8el&M53gP%dCDQNX_-7}Jpr?_(3R;20? zDjE7UvwbhElfuOzvhmOOwF()|C$pbXR2ScoY+C9l$ryTjt~UYE{>ET3=|#<;pUO(Y z0zOqN2ExLfZqi9XG9jjdGoCo;V@tA`?d%|#(hwrFl#1TrM#SwM-BagV;p~z(u89I0 z^q!r{ydORY1-eR>L`LA?E_>(X%*0o6r=&jwYVQ3@*IfJ+p`e4Iz%8B4m7@DTAaEJ> z!okWTY$DgNq%9MSBd#D4&YzkIL)1fHnNIJH}U2FK{*W% zQ8AZ;r)_1aRNJpAU9=+$Wu$R^lz<<>pxZZBoou2JIo;@o8BmnEj2s7-9To@oVik>M zYJ;l9U0Za$4+Yxy*!w#zJZ~ z!$#}ucehBeon4(~pX~Vq^H2+d*<`U_sK7Rd!UPdG-7r9OnH2YTu)$Y^CQC($MiWNR zd!>5c^{FcB$JcisVBf}8e!nsbEMSJ=?4hC-4`As>M6gkfd2eKc`wM{RYcw#Fl$4MG z-LiPxTx2SA_%abgfQ{9gMjAC{u~p?rt`c?gUK|9>B4R3v+an^ zO%&=Xc{Dy^jx{4D_DqN5OE?7Qu<3K52`Rx+i)7`j2*kiG1+Uh$)Z^({mNndvPH}${ zGPZ2OZ+D`firapIrfe9abD$*ZYa%+Q><>(evBeaZM8cSz4XE}h_>NNnoB+ins2GVG zFHRfXL4>mstX(S3h&V>m6m~RM*8t|=&Ag8agFotrkJH`~Y|O9uxl5eGhM1!Msr`cu zNk%|dhTSe1?HqMFKrv06+aTR;tqEsbm4TNZ=zclneHnI%@y!0`4V5-21iyRVGl_ypspc2>nW(41D{ zUl`F?7(W}*!5Ba+Z}S6)`3#cIZ6&|0ORmPjYY`Km{^1&F{mN1T>ZrY z2?g(%&C>&PeFsb~hC>Cs!_15G?sy5@%5Q6EQy|&DvkFjVZ9DQnG>Mtk(uMBG=;~7c zHl3Fi;SL%A1(s?lw(us1*Re9fs5Fdbrk)}XI?b-(5T@}5N)|~;Rz#FL_T`QxlzGv% z2J^)(d5o`H%!|H7rE)??M#J8fbM$~D>^L)LjqPSc%2Nnw6m_mEzo_&`sPy(%w{+-f=q2U>kNU)ii~|9YKDmJP9QG2 zbLWO^hjmMhhPTIf?D32Z7y`AJR)j%j3ML71^rsM!ZQ^n~y+Sr~JUkL`ivDRN#E`m6 z`^_p$(c#}t8+byeLCUo=hA`$gn-bvQ`YG^~d`C1=7r(eSZqG1Y&dj{%9$wgKg85_j zM9$1AGPF`~5k(p$HY8GzP~mlvQ)A08I@E44=0lWTdawPXtqccngJ*z zoM;6(m?Q`I(@a8QWkMLg36ioy5`%UMpfqtul0y!piX4YnK_?*BAY)mq)8sSAKtx1y zj)L(-J+pR3EJXg>gDDZbykUv(g3IY*s60-wv2w_U(8^5NSvn@uFsI8XZ3QqSt|6-yZC&M&+0ZdF{ z8G&KSx$vhI@rq)KjD*NCDEcq))Hjc0S%`a*uDKU zRYxh?0pZ=UUuU0!0Lq=sq`+clQ}g6~(u!uu1*kOgmoBF6M*x!Ptt_iSUzP2S)b(f2 zFnfCnu-J)^mYLZGnJ$h*yFR2QR4o8hAOWwcoEJ$YQp&%;-Z6yIhX}0ZhbV zD#v^yb{vIeIBuTxQYvI3xrPF{6CIs`=B>MrWL6E*=+_EaLfv0bz9lZbRaez?h54DQ z5nN^C-Y}WypA;j=o>}NpzO5iKX#tu>5?`KmsBUU@_oZw9-rsmNJ^%p$m%tfhSl2gdQm`)(qc@8DlZ=KoB64pbI0!>5Aqa`45Vi zYzoaJ#s;0wuA$1cB#blCk`gPlxB*J;&r8LL?k_K3&xotMo29xa|KA|%%3rLejcgEw zEk`ZdlMpn%pr30^xxxGsD~CgolCo~tpx{vz?(-by(HMyx9s z<}G9>cKprDxEkpKx5iETC7OlsEzk(#Xr#n`3ennZ*6GlVT2t1bGuXmXbvPn28wZwd z-6!(O@@NLkv&N%1uS}jg@i`E?TooAewy2lVP0qD~m&212pk1iRhD*Z4_>oI!#tGN`H#sxf$r=+U49+c*#%Kj8h3PO7H&UU&QpRY^(6mN??< zo0)iIg-xu6w|-i;vJs(A-DmDLj?Z9X1!nIa1SMA|qIHteU`Mx8*XSY3;3e_o*_8W? zcTL5F2yBWU@0g$h`#cHw^dT;y7~O&hP7N$qE2&opaCkIo5Jh)3xgs5xzh@$rX%fV1 zpMa=DH_2_Xi9j8cFofT`iM?IyJv)6GzB_l66E{q(4rQUjjx*9CuqoIYWk2emHv-+l zQz^AtlqFlf^J}vuK>%|~R>0aFq!z^xOJsJ-u7C1@EVdbpPC#w~1`Xygpos-m$AY-B zdCA)6Et*QJ@M=3_`>W!x3+A-J+jWEJus(D;2cP(fhr`7REp;xLZI$u@=^u{OU5EbL4PV0s@#}X{FoQV;>pRxfo8o zvyyWNT-%)1tojCfEtEkg#ej`X#tq`J(*{!fCHzK#Yjs)X;LZ`fLniipi8}Z%1lfu8td;b02`3Zvbu*lr&Vg!dvy*F_AnQngfp_h}~Ih8QmkQ2P6q~r#5 zg^s3en{zs*LOcVup*9k)YP|nxP|ceX{2ateEhuK7pav1z<<+cm9BLsZ6llI;JaeVsjQJX+R`lye8%rqiilD$q_$U z0=HH-x08vmJ?j#*Ru&ki0kniP1*?3glu8>8)%R-OjxT$u(ZA9Xh_R7)gk>%#6bLKP z7LLg)%q#CwiQopr81I|$vRfbdhbHSih{|)5MMgfAnb;2qgM;Px8{6T*moC;R87z`Y z_@+c6KHh);9}8Pb(2#?G#8pDh)qt6=rbRj19!T2SR(S)oCmqOMuw|c}IX#l#w*lQH+q6y#c%8rf343x^8^&7c7R*?r6OP~_(cza8M-Zl`Q{sSR z7=oBVSv40(gombT3w}G0^(7!y>trJf0sCxvV#q}}Vk<(F3loVDc^;ZP2yhq<78CF3 zFn;4t&l7KLKz7;j3QAK=Z*jm9(bcp29vFd+q>T9UipEeO{ndYXvz0VR8ykA{0sv|5 ze^iAdsf!K$1}hDlg1M+vXFr?dNFiy66VTSYik3fz9wun9#-B%;U&Mgm#P@1=X~?&3 zFff<$}KEPxyR0#q46WuT+;)9QD;5J-e4di%kI8d|iSIW|+MsLL?VQ0ny}W43n$ zb{(`Lax0=4L#(_s*v8I3%HE@V=w+i2aULN*!UKRSat$4=kgTfZb!>3lL?;OS{ep9M z234m}DDGEmI5v4lp2$I-xM=sAW8zrDeS$|@d?I1tl&_k&4&*E(pTot%JPYAPVr_MQ zzVc0d+#JOCFHEZ&oHZcp$_@l+@$osfnnv&>r>Cb~yvQJA-yaUvuvjEU3*UkP#Wb9F zTH`?nW5S}1bT~HxcLWZ{`?kOF^{aG|*`QZ3O7oY+dgguuHq@X3B~@5P4QpOd9&mw& zm+|AnyX@ba7d>9m+0Vk0;foZi6lYiNSqK2;R)OT2-r|aQY$o#ksf^LQbBr8Au5+bK z#36LXGB78WK%}XilU5mQ+IV8VoCG=~qvQ^YPP5wg16jRL#P4VO43FNHGgItTz_e5j zAoC#)Ki@Yu4ey-B1_oQO=wj|}-ku7bRT{1k^&K{$@N>Ii5?O%LC6DX{o%h}0!}C+0 zDjDrMLm+V+41t6eNy6%S{R zif2+nv7LSZzm87egrI`o)8c|rwO3PXF6^kxrbHW5jSD9y1&@VFPJtz{)rIV+fZ3v> zOA!8?*BbEoBv&eS2Bg)oOE;oB5;-=iZA1xMYrL?{bY4cy8Dof=L9pPMK5}c5=Gc~q z>SdqOM$5{0zgco`xx^$QrU2hFub!3USo)AkVO&j=#S$k-&;_O2eWqxTCP4hDmn!ax zrCVpr6?Ds3-MLJJ?yE{Y9Gd?*kxk2?n`Hp9Afh5XP?-)Q`zT8p5+>q zhaiL$s_tp0AHpmv{|U$dZXhR;BSixn@CBgp$+g*jL%TjWPu-QXP#O=7wc6p-4?>HL zXZs1GqaV}&

s!SOc7+5FcpeKCY8xc4`o}xcEr`@y^k=4I~Pzq%F|^L#>(H`6jPP z>6mktB%u^ch>c0}T;LaQAq;s#xO91MrwV8$f8RcJpb!BSNpKi!J5Y)<6@zYequgh# z8mIG66UEw5RS~{1_UcNT;ucLXU-1+J*ikU&(hpXdPT~}(p0^cHzK(prM;%@j+AdI7 z=6`<6nPK=i&KF5{Xrt1-^lZ|~Ft?JNmy3@Ngw8wysHq8ZjFpjYT-f?8g7pAtt54fVdi1fKpT?$KrWg>^5ReU<}AsISR{e&`A!1;zkm} zb<;n}C?y{7W*EG%1V=R*(~EI6n~seC@%8)vfHiH z=Skk>0BC|1t>s)e3wCG>s7M$8o@WY$Y11?8Z{Td**h8B+n|2pRtaA%`gp zAZ_4G$qUiZ3~_HR~kU{DcA^uADTx(5<&wzfUlFxJ}*KG*(7gVP8;4yDc5` zk(QbBg=<4+rnJI{2b_cprRH#qUafPf2cmJ01n#!A{>2*O;MKP33JCTIMoUD8a>I(= zEuLmZm6U98+=9VW0`$U|eR}(U;!dum(l?G4!p^Hk9vMUWr~ZGbvF~kE6R;@i=`hJe|lgPfw4d?JRmKedh@%4Y#&&?&R~7 zvShjlA9gT%>6%O`H~-+&B2l7E z)-k*J1&sP0TnMtp3{gd^vBz}OkxUZ})|eN>P*TY`eQfT=@VXNa2i$Wm&n%bEo>k*a zuepyUCT~B|fP`~rX?_bvalAKreN2mh3kW%vG3xor+66$aJ>BCvgx;O2zs_fTsIhTd z4-PCm(3-|CWlODS6Ak=7nq(qc>5p9mi;KK`(lFX0fmp&KA2wLF8 zCEW|7cE9n{e6N7AwX%04CrkDO<7{)uWpz%_d(vdjusKzVK!E2bmJjGSjiDAz%nYWk zC0#s+`q6B(FfAa@==OSxl5p-iY8_&ihp+K~7A)d+^AdUu`$*_@NJ*_KfGd%eGCxq% zlQKCy)5L1>X$-T-_o~F_#cTwoEKsStb-zmiK*IhSHOk44^WgqQ0zR*W$D0JAV5R^q z#+V**nFpx|606`VO?Uw#HTVrlYFnuFGU$bDIJ-sI&k2 zjFWso*&*dZPnbrVVxJQvFe69-7cIH`njjxdV-75^wjdw@k~`_H-OAhS-etWo$GKv` zUnxY>wJ7YNfh9Ykkf6RBMy~I5X@^b^6avtH6V_>Ae& z;1`RcskBD`HF9j(n8K zGaaq<8mQWzbJh?We1tz!46QJx9Gs&>ik^Z$xK0z9eNf@h(J3`i%E_tH+?L4Z7;7u`{@w-4-Z#|D^t z`3;Wp02>Al!Y}$j6Bbc@>;V!enR|K3du<jKI!iK=BGe9ATKofx$AS>P=E1 ztbri`!VwmQB|2@r6qCY(*WHx(m;rozY_aJUvW2SY4ffzg`kCAA=Qq|B%p->1Cjtk) z1|w~BR%T%rTMw=>DQlNu#3NW5))EF~5j)1l=d<(RK5A%{LE~aV2SMFc#D6a#scC88 z8hS&u`y#HfzI%yL)aL_`kY}U&!Wa_ah)1E81d2SE4DTEogofhoKon%&IxvU{#E9M; z;j$_mcY_8FNB)e~D5+GacHUzlpbG=sElaXz{=ETMa%Cp-G+2ML^=A@4h5Wbd3g{!D zsnK%o6~hsOEJ=i|7QY|}!b%$WP$mx4!jdZ@V3ZufL5`TBP%(ssh?W5g7Mh%W8sIOV zQ#G}Nv3LAJK9(I4eS5tYllScoNb^)78$v21o!5PFCNB(XWZHe=(7}R-R{z;^>BW~G z0f#j)pifgZ?wF7LiiO9lj7G?22G1i(px_3A!>%21i3#HkNIC>w7YiJ9RRic*YyPr0 za)4Y3<7^S{HMIsRRqDp&lu&B2Eo-3aZ*xHKgTV+>5dB#+KxP<5Y-5O3!IEjT5TX=I znR23|XNK+PRB zBK1*_CyNBYaqSrrho7)9tN zQC-_w(_1jt<`{&ALJO8+mGGBPsf1!@_EiTkciMTX+E;ZH92gQyB?M{@9V)d#Ov5nC zpo{LMDsEbn(3QT_SpYoU1dyT4t><^%h--MA=6m5OzgU2M|?#O!Jy}7!G2_4`soOKX@5!WuB=A6yEpKN7B!Iw4+`E> zlU8}{_=CC3o?n?NxyAE$774BGPURG*qstBzdnWRBPNd;DC_}k32OY2iL>rDO4C#Xz z^DJe@X_di@)vwZn8e<&P6%YmcGZ3|@<5f5WvltNU@X~J;OgAQ2jZ(iT=r%yi$^_$% zzYJRYD3g?r$T^0n;t;!*mq)#==+@X2^Nczduxida8mI_3vzQIcFBG+RFu3_ zF#@^x0k=Ry;HY8+YCf+g?SY<-l66Zw7fgo)a|@V*0flnwF1GhQ78nX39HikY)Ok~L z)j{J%*bPCW;IHvg?#Dh4rl>is&>_+0XbwlDKTeFz)n>RcPG^A|j%Xw)x9q+)NDOtX z0a_Du0ZTXufad%?2vq3=1Gvq1443{n&H%Gl$be<36f6Q~u%Fb!A1Dt0&56@!B;S_X zxqIMdT9w<-p~D(3$#(Hd&8I}~@elO%LGGy%RS=xGxlSNmbrkv^ctX{j$00KS+?Xm)155#m;|n7>o952u zYNaN~jb~)0Ar+l$FYOo=W3K#*BdCf*a1%%O@9j^K&@ti^ENXIA`EM~~?KPyVdK~l< zY@wM;rgBMk(KcDbn%v+2V(do^b<%TV_Y9njN2v(vYGbmpK6IA_^VcL8wEr)7cg_)?k3ON)Uj5$?RtI z6Z%mBX6f8Vg;hBGE=CO~gcW#lM1OV{pRnJA6*DIa#(wlhOy59bVl&BqUWig{n9o>4 zU|PW#M)gi;+X2Y$gUuuj0?##d19%L`?9qSK2jNLwCJ!W;9GYHW_Kc1kz{czE5As8go)Hx8AlINJ+=g1=2q!tRMy^IbtH z6c8nehl&Q2DJiN{d&7c;%0Z0rMUtYveUF^DRXzofjEBV~omb~p6W2;V&_3`LXQaod zuXq=&gRB6M!sXgXxq&1wZ7+{PX75_Z%z!bC|L3l1k$U33t^ObxAD89~KtL>p*9|I!H%iwEWz_U5vt>u>Neml;<_2U8m zuAUvXR&QYGo~?L(kVYpk)niZtRY^#80qE2me(wR5G{j(8cIyG+aLY*Mo-i_CRh0AlP9jYfRq@lvBZ zBHuKlP)$h$*;4E3EbVq1Y(3} z1RDfT1o8w=1U&@4gsBBi1!n~l1&D+|1dIf~3y%re2JZ(z1^}gq5zIg!KvL0QmxCG) z;NTP@=riEJg5(QGJ3x#<0RkTc{0X2Ea3ElM!S@6X4qzj2Mu3(9)+mUgAYDOz4ZIcL zGO$xYU<#NautWf;fr5dX0b~O32WSj{0j&#C^b&x|0yqXJ4&Vzg3_vqLjeyhykQbmf zfv5%88(<6oWPrQ?-~dzh-+ccM_eadX3j9^@x5uA3d`IwC)1OlPdHQ$EUxIzF^;gK> zOZ>(9U(p{R{Tty&r(PQQvEg5!{Pgf^>gT6EhiIVWOh87QDZmaFpeY5W}{n+i=>})PZjHn#cbBoN(CS(_c z7Ox_NfQbi_;5H^mB)%NMzF`BnD%g4hl02c_`lQ|roug7f6g2D%0B#l>i-yBZX(T%Z zwKzzkpwVVe>CojCv4(yrBalVJaf4q2NFvKC}EE z8mk%P(E}&wkVRainrlRG+06k~Ac7mU@2(V)5N6z{rU9%Gb(xGi`puPCPY!?iY+wI} zFBRYh3o!#hMj|hz${c|Pv9%r)fY)-7@@6L^|14l%hyg>(_(s|!rWO@{Frn<9nwT`P zY=Yma_EK=Ld!Q1FD6QKs*u1+ANGctFn0f0YREUJ=*C-9V9+*S(|873oho2AOeXphw zt$~GJ`b~lk(Fj%%C1D}upp3i|-(bJWY-)Ix5U1ePfJYR8|F_Q&Jp7%=ADVt`tX{Lp z;%n!KP@QOk4GBqk3Fv>PbZ-Fc*?9m775B0=18YU(>{h#lAgtX@N zk~J$og{ZwZRi4Z$ZLTz0o?2>sg17J<0Jro=ODu&n0O z7|16&1mXxBI&b@fq*R&6-)C|G79*Uj4zllfL)os&{Dh`fS%ZkGPJC=!a`K34q!fb( z)q;@}spjUN$0-6E^hYTIK{^0X7hSr5n@4ryJ}Dl~BIHtAoB@(U4b2c3B&1GpU{I;h zWC=N5%1LJHs^pH#u;~(CgzqZi#|h4}xE~}uHvXg1bV9=-N_hU3tlR30FBs@m@>Ll` zfuKbmizY>nVdw->87CB6T{K*9)fNtvUt)9VQ?!{7Zn}w4k>NlfX}QP1CCI)2(=Yfq zL*a~y5!s-@$vAt_k%4^jPDulLXsIQDFqKwPiMFTPD-yQaZ27Ggd>0eIFpffW#FW5} z<)0n&%*%wodL=SRLoDx+AJ26Y#Y zOHHbooE$BK@Ml68N*4p^UIv!9M2hZ`LEuc@91P5*u17=H>CMWlkB#JKDa*)&SOv&d z`x`^*(?MgIx}%Zgch~wihzi#&0^OT%K@~&t#ieB<8=UNXdHP5;I>4lGt8QK|DX{oE zDw1YLUt->-ksPW?J^I3sKr{KKY@l zKCu5HrZEKbA(9c$@qf@MMhMHWK>^hLJk|d1)x5XD-(IeHDEYs7;G#PgWk@J$S`a z+_B6fcXEzo(HNI1U2zRH&m0fD@{bLRZ{Vw>mI(EE z6Ze(cAfZ%Ua6$mW2sjDEyhN2PfOCQTNKk4JX9G2WpGp1}{{D<{w#89zuvgStN_?!V zfPlEaEm*k7G<&TqgGTE_;6h*+HGYT_)Q5B?r{98HkGSN_CIx?#96;Z$8Ly zxe%EPg%^3)tfik|>CmwLwGm}nc5W8}VTCsL2}I7_4wC|y!+B4`B_mg{oG~7aKkK$Q z8CHgL8yg^^zoE#t3%qe{LAFc`=#E)M(c z1<0@-)LGDP%1`Z(3F+uj@#_YW!D;XmtSN;Qp{dJH96(kYxXrw!1yh;E6vrs8ZCHJa zp})bJ>iXvWT|nVMsnQz7l7RwK@5l=~Hy?06Nm1|a30Uj5GE+67P{!NZL+j+3z__Sd zwyGN(ME;KfWS%WFm<3C2ixWX`4akTkh;u&C&)Zau#~9o`9cd(GFq(&AlhVWm!VHe% z^GT5=7oZBtZK5hHoa3;Bi<5-4JgA1J9x;-t8!xkZxfGSfT(K!0bwY{Bg@~B{n~#IU z56s|eJ5~Vy9@+u#hE0ejoSYdC&0t{+?J#6LQJUt`0};;#TN??st4L0pqX(!a3$@0{ zYqtlR5E69sevQKP6BKAw71%qwLEojF49S+7VcBP;>i2xAurdeM(SXyABBO?Oy9xF2lBgA3d!i@dTEdMcF9jXE% z7ie9NdMzWMK^Eapm>HB)>U4LExC@fji`ZpwVRf|xWZANGLRO<1R@gAH3;VKmX>V^O zs*t(@iDd*NP4`AKm<$}y+&dYEhr8nB@Z<|MZ(Z{=A9!s^yK>zV=Zl5NOu;Kyh<@)Q zabA$<6c?y{tB!8w_%Z-95Ol{BD$sUznhl;sG&Q7bUagogU05@Z6qGYucL24}_x1QX z4}uW*l&LqFe@lMMX&fO*p4%qzy>~j~&Far~6K>r*F%5Zy01NQFuHIhKpCw;sAT5q! z%JeOJu(hs2(zpvk*ewDSB+FDj*qY%Pt3qkqX;827&V+h4{*B+EScESjl~p1Rm?2c? zLVje{Sk%q|CiV^8eKbkS7LgiQ94r;p19NiTuC=5Az;9Yz6_BLD2ELw-!2tg~5Sp1K z3bPi9uOYG#ZTVS)W~WmPgix4LQe*6m$oir>5kyEL_u*j_95AFBd^-g{K+$1M#Dy^q z5I8WTpn{Nq3N%faIadEaU<^LL&+oGIx5M%8VFTKmw&B$GfVN#u*mMhF#4Seiw7Bs_ zJV92?BRYoLq}hXNrNU~#viRFSHr#8X8K8>|q`ePYnQ#N3TbQskgw&^{yPi{?lsryY zL1+%8>#WlEgq)dJgR2wLyzZ?fs$5cn3HEAzs+(nnj*kQ#QtZ+j(wBE<4d_dovWD~} z&Dg_w66WEtDbCVqvfc&|)d}4)N=vwxEnr^_PPEdcoD1Qp(#{3&)aZItmXC23SitR= zi)o_D_!8t%C0q$^Xmg4bJqF?gr+`a`ooOIS7zfB6$`}N=In#0EkauwIPQWF>&a+PB z>;haI$u|Ih2QqFsk_~PcNtgj;m)V7uRQ;6AzzSvw{15(_fIEdU;bfVE9C>AsR|d>O zcvB>t0h}pQVN{S+aH>bZ7s8beDv|I7aHUB20(erUl9?E$;XI3jCkUFunrig%lGbv- zi-yw!1SbAJ%PAa;B$0!L()tDj|D{)iRwwcztNBC*6Z@4gkw~^#+eN_$cP0P;00000 F002TuuHFCu diff --git a/book/FontAwesome/fonts/fontawesome-webfont.svg b/book/FontAwesome/fonts/fontawesome-webfont.svg deleted file mode 100644 index 6fd19abcb9ec83..00000000000000 --- a/book/FontAwesome/fonts/fontawesome-webfont.svg +++ /dev/null @@ -1,640 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/book/FontAwesome/fonts/fontawesome-webfont.ttf b/book/FontAwesome/fonts/fontawesome-webfont.ttf deleted file mode 100644 index d7994e13086b1ac1a216bd754c93e1bccd65f237..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138204 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 diff --git a/book/FontAwesome/fonts/fontawesome-webfont.woff b/book/FontAwesome/fonts/fontawesome-webfont.woff deleted file mode 100644 index 6fd4ede0f30f170eecb4156beb7235bf01fff00b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81284 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äY>7uxS6S37IVP_>DW-kg%dn+sFHLnFhvXTU%&ox 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 ZUtABnb?tu62nVk>KX+wfW(Hek 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 o-IM%8o*ehr=#Iq=Q0BvwUBdJ3D - - - - - Appendix - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- -
- -
- - - - - - - - - - -
-
-

Appendix

-

The following sections contain reference material you may find useful in your -Solana journey.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/avalanche.html b/book/avalanche.html deleted file mode 100644 index 0e5d36f9733cbb..00000000000000 --- a/book/avalanche.html +++ /dev/null @@ -1,217 +0,0 @@ - - - - - - Avalanche replication - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

Avalanche replication

-

The Avalance explainer video is -a conceptual overview of how a Solana leader can continuously process a gigabit -of transaction data per second and then get that same data, after being -recorded on the ledger, out to multiple validators on a single gigabit -backplane.

-

In practice, we found that just one level of the Avalanche validator tree is -sufficient for at least 150 validators. We anticipate adding the second level -to solve one of two problems:

-
    -
  1. To transmit ledger segments to slower "replicator" nodes.
  2. -
  3. To scale up the number of validators nodes.
  4. -
-

Both problems justify the additional level, but you won't find it implemented -in the reference design just yet, because Solana's gossip implementation is -currently the bottleneck on the number of nodes per Solana cluster. That work -is being actively developed here:

-

Scalable Gossip

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/ayu-highlight.css b/book/ayu-highlight.css deleted file mode 100644 index 786063fc4db925..00000000000000 --- a/book/ayu-highlight.css +++ /dev/null @@ -1,71 +0,0 @@ -/* -Based off of the Ayu theme -Original by Dempfi (https://github.com/dempfi/ayu) -*/ - -.hljs { - display: block; - overflow-x: auto; - background: #191f26; - color: #e6e1cf; - padding: 0.5em; -} - -.hljs-comment, -.hljs-quote, -.hljs-meta { - color: #5c6773; - font-style: italic; -} - -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-attr, -.hljs-regexp, -.hljs-link, -.hljs-selector-id, -.hljs-selector-class { - color: #ff7733; -} - -.hljs-number, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #ffee99; -} - -.hljs-string, -.hljs-bullet { - color: #b8cc52; -} - -.hljs-title, -.hljs-built_in, -.hljs-section { - color: #ffb454; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-symbol { - color: #ff7733; -} - -.hljs-name { - color: #36a3d9; -} - -.hljs-tag { - color: #00568d; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/book/bank.rs b/book/bank.rs deleted file mode 100644 index bd95935f39f428..00000000000000 --- a/book/bank.rs +++ /dev/null @@ -1,2403 +0,0 @@ -//! The `bank` module tracks client accounts and the progress of smart -//! contracts. It offers a high-level API that signs transactions -//! on behalf of the caller, and a low-level API for when they have -//! already been signed and verified. - -use bincode::deserialize; -use bincode::serialize; -use bpf_loader; -use budget_program::BudgetState; -use counter::Counter; -use entry::Entry; -use itertools::Itertools; -use jsonrpc_macros::pubsub::Sink; -use leader_scheduler::LeaderScheduler; -use ledger::Block; -use log::Level; -use mint::Mint; -use native_loader; -use payment_plan::Payment; -use poh_recorder::PohRecorder; -use poh_service::NUM_TICKS_PER_SECOND; -use rayon::prelude::*; -use rpc::RpcSignatureStatus; -use signature::Keypair; -use signature::Signature; -use solana_sdk::account::{create_keyed_accounts, Account, KeyedAccount}; -use solana_sdk::hash::{hash, Hash}; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::system_instruction::SystemInstruction; -use solana_sdk::timing::{duration_as_us, timestamp}; -use std; -use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; -use std::result; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::{Arc, Mutex, RwLock}; -use std::time::Instant; -use storage_program::StorageProgram; -use system_program::{Error, SystemProgram}; -use system_transaction::SystemTransaction; -use token_program; -use tokio::prelude::Future; -use transaction::Transaction; -use vote_program::VoteProgram; - -/// The number of most recent `last_id` values that the bank will track the signatures -/// of. Once the bank discards a `last_id`, it will reject any transactions that use -/// that `last_id` in a transaction. Lowering this value reduces memory consumption, -/// but requires clients to update its `last_id` more frequently. Raising the value -/// lengthens the time a client must wait to be certain a missing transaction will -/// not be processed by the network. -pub const MAX_ENTRY_IDS: usize = NUM_TICKS_PER_SECOND * 120; - -pub const VERIFY_BLOCK_SIZE: usize = 16; - -/// Reasons a transaction might be rejected. -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum BankError { - /// This Pubkey is being processed in another transaction - AccountInUse, - - /// Attempt to debit from `Pubkey`, but no found no record of a prior credit. - AccountNotFound, - - /// The from `Pubkey` does not have sufficient balance to pay the fee to schedule the transaction - InsufficientFundsForFee, - - /// The bank has seen `Signature` before. This can occur under normal operation - /// when a UDP packet is duplicated, as a user error from a client not updating - /// its `last_id`, or as a double-spend attack. - DuplicateSignature, - - /// The bank has not seen the given `last_id` or the transaction is too old and - /// the `last_id` has been discarded. - LastIdNotFound, - - /// The bank has not seen a transaction with the given `Signature` or the transaction is - /// too old and has been discarded. - SignatureNotFound, - - /// A transaction with this signature has been received but not yet executed - SignatureReserved, - - /// Proof of History verification failed. - LedgerVerificationFailed, - - /// Contract's instruction token balance does not equal the balance after the instruction - UnbalancedInstruction(u8), - - /// Contract's transactions resulted in an account with a negative balance - /// The difference from InsufficientFundsForFee is that the transaction was executed by the - /// contract - ResultWithNegativeTokens(u8), - - /// Contract id is unknown - UnknownContractId(u8), - - /// Contract modified an accounts contract id - ModifiedContractId(u8), - - /// Contract spent the tokens of an account that doesn't belong to it - ExternalAccountTokenSpend(u8), - - /// The program returned an error - ProgramRuntimeError(u8), - - /// Recoding into PoH failed - RecordFailure, - - /// Loader call chain too deep - CallChainTooDeep, - - /// Transaction has a fee but has no signature present - MissingSignatureForFee, -} - -pub type Result = result::Result; -type SignatureStatusMap = HashMap>; - -#[derive(Default)] -struct ErrorCounters { - account_not_found: usize, - account_in_use: usize, - last_id_not_found: usize, - reserve_last_id: usize, - insufficient_funds: usize, - duplicate_signature: usize, -} - -pub trait Checkpoint { - /// add a checkpoint to this data at current state - fn checkpoint(&mut self); - - /// rollback to previous state, panics if no prior checkpoint - fn rollback(&mut self); - - /// cull checkpoints to depth, that is depth of zero means - /// no checkpoints, only current state - fn purge(&mut self, depth: usize); - - /// returns the number of checkpoints - fn depth(&self) -> usize; -} - -/// a record of a tick, from register_tick -#[derive(Clone)] -pub struct LastIdEntry { - /// when the id was registered, according to network time - tick_height: u64, - - /// timestamp when this id was registered, used for stats/finality - timestamp: u64, - - /// a map of signature status, used for duplicate detection - signature_status: SignatureStatusMap, -} - -pub struct LastIds { - /// A FIFO queue of `last_id` items, where each item is a set of signatures - /// that have been processed using that `last_id`. Rejected `last_id` - /// values are so old that the `last_id` has been pulled out of the queue. - - /// updated whenever an id is registered, at each tick ;) - tick_height: u64, - - /// last tick to be registered - last_id: Option, - - /// Mapping of hashes to signature sets along with timestamp and what tick_height - /// was when the id was added. The bank uses this data to - /// reject transactions with signatures it's seen before and to reject - /// transactions that are too old (nth is too small) - entries: HashMap, - - checkpoints: VecDeque<(u64, Option, HashMap)>, -} - -impl Default for LastIds { - fn default() -> Self { - LastIds { - tick_height: 0, - last_id: None, - entries: HashMap::new(), - checkpoints: VecDeque::new(), - } - } -} - -impl Checkpoint for LastIds { - fn checkpoint(&mut self) { - self.checkpoints - .push_front((self.tick_height, self.last_id, self.entries.clone())); - } - fn rollback(&mut self) { - let (tick_height, last_id, entries) = self.checkpoints.pop_front().unwrap(); - self.tick_height = tick_height; - self.last_id = last_id; - self.entries = entries; - } - fn purge(&mut self, depth: usize) { - while self.depth() > depth { - self.checkpoints.pop_back().unwrap(); - } - } - fn depth(&self) -> usize { - self.checkpoints.len() - } -} - -#[derive(Default)] -pub struct Accounts { - // TODO: implement values() or something? take this back to private - // from the voting/leader/finality code - // issue #1701 - pub accounts: HashMap, - - /// The number of transactions the bank has processed without error since the - /// start of the ledger. - transaction_count: u64, - - /// list of prior states - checkpoints: VecDeque<(HashMap, u64)>, -} - -impl Accounts { - fn load(&self, pubkey: &Pubkey) -> Option<&Account> { - if let Some(account) = self.accounts.get(pubkey) { - return Some(account); - } - - for (accounts, _) in &self.checkpoints { - if let Some(account) = accounts.get(pubkey) { - return Some(account); - } - } - None - } - - fn store(&mut self, pubkey: &Pubkey, account: &Account) { - // purge if balance is 0 and no checkpoints - if account.tokens == 0 && self.checkpoints.is_empty() { - self.accounts.remove(pubkey); - } else { - self.accounts.insert(pubkey.clone(), account.clone()); - } - } - - fn increment_transaction_count(&mut self, tx_count: usize) { - self.transaction_count += tx_count as u64; - } - fn transaction_count(&self) -> u64 { - self.transaction_count - } -} - -impl Checkpoint for Accounts { - fn checkpoint(&mut self) { - let mut accounts = HashMap::new(); - std::mem::swap(&mut self.accounts, &mut accounts); - - self.checkpoints - .push_front((accounts, self.transaction_count)); - } - fn rollback(&mut self) { - let (accounts, transaction_count) = self.checkpoints.pop_front().unwrap(); - self.accounts = accounts; - self.transaction_count = transaction_count; - } - - fn purge(&mut self, depth: usize) { - fn merge(into: &mut HashMap, purge: &mut HashMap) { - purge.retain(|pubkey, _| !into.contains_key(pubkey)); - into.extend(purge.drain()); - into.retain(|_, account| account.tokens != 0); - } - - while self.depth() > depth { - let (mut purge, _) = self.checkpoints.pop_back().unwrap(); - - if let Some((into, _)) = self.checkpoints.back_mut() { - merge(into, &mut purge); - continue; - } - merge(&mut self.accounts, &mut purge); - } - } - fn depth(&self) -> usize { - self.checkpoints.len() - } -} - -/// Manager for the state of all accounts and contracts after processing its entries. -pub struct Bank { - /// A map of account public keys to the balance in that account. - pub accounts: RwLock, - - /// FIFO queue of `last_id` items - last_ids: RwLock, - - /// set of accounts which are currently in the pipeline - account_locks: Mutex>, - - // The latest finality time for the network - finality_time: AtomicUsize, - - // Mapping of account ids to Subscriber ids and sinks to notify on userdata update - account_subscriptions: RwLock>>>, - - // Mapping of signatures to Subscriber ids and sinks to notify on confirmation - signature_subscriptions: RwLock>>>, - - /// Tracks and updates the leader schedule based on the votes and account stakes - /// processed by the bank - pub leader_scheduler: Arc>, -} - -impl Default for Bank { - fn default() -> Self { - Bank { - accounts: RwLock::new(Accounts::default()), - last_ids: RwLock::new(LastIds::default()), - account_locks: Mutex::new(HashSet::new()), - finality_time: AtomicUsize::new(std::usize::MAX), - account_subscriptions: RwLock::new(HashMap::new()), - signature_subscriptions: RwLock::new(HashMap::new()), - leader_scheduler: Arc::new(RwLock::new(LeaderScheduler::default())), - } - } -} - -impl Bank { - /// Create an Bank with built-in programs. - pub fn new_with_builtin_programs() -> Self { - let bank = Self::default(); - bank.add_builtin_programs(); - bank - } - - /// Create an Bank using a deposit. - pub fn new_from_deposits(deposits: &[Payment]) -> Self { - let bank = Self::default(); - for deposit in deposits { - let mut accounts = bank.accounts.write().unwrap(); - - let mut account = Account::default(); - account.tokens += deposit.tokens; - - accounts.store(&deposit.to, &account); - } - bank.add_builtin_programs(); - bank - } - - pub fn checkpoint(&self) { - self.accounts.write().unwrap().checkpoint(); - self.last_ids.write().unwrap().checkpoint(); - } - pub fn purge(&self, depth: usize) { - self.accounts.write().unwrap().purge(depth); - self.last_ids.write().unwrap().purge(depth); - } - - pub fn rollback(&self) { - let rolled_back_pubkeys: Vec = self - .accounts - .read() - .unwrap() - .accounts - .keys() - .cloned() - .collect(); - - self.accounts.write().unwrap().rollback(); - - rolled_back_pubkeys.iter().for_each(|pubkey| { - if let Some(account) = self.accounts.read().unwrap().load(&pubkey) { - self.check_account_subscriptions(&pubkey, account) - } - }); - - self.last_ids.write().unwrap().rollback(); - } - pub fn checkpoint_depth(&self) -> usize { - self.accounts.read().unwrap().depth() - } - - /// Create an Bank with only a Mint. Typically used by unit tests. - pub fn new(mint: &Mint) -> Self { - let mint_tokens = if mint.bootstrap_leader_id != Pubkey::default() { - mint.tokens - mint.bootstrap_leader_tokens - } else { - mint.tokens - }; - - let mint_deposit = Payment { - to: mint.pubkey(), - tokens: mint_tokens, - }; - - let deposits = if mint.bootstrap_leader_id != Pubkey::default() { - let leader_deposit = Payment { - to: mint.bootstrap_leader_id, - tokens: mint.bootstrap_leader_tokens, - }; - vec![mint_deposit, leader_deposit] - } else { - vec![mint_deposit] - }; - let bank = Self::new_from_deposits(&deposits); - bank.register_tick(&mint.last_id()); - bank - } - - fn add_builtin_programs(&self) { - let mut accounts = self.accounts.write().unwrap(); - - // Preload Bpf Loader account - accounts.store(&bpf_loader::id(), &bpf_loader::account()); - - // Preload Erc20 token program - accounts.store(&token_program::id(), &token_program::account()); - } - - /// Return the last entry ID registered. - pub fn last_id(&self) -> Hash { - self.last_ids - .read() - .unwrap() - .last_id - .expect("no last_id has been set") - } - - /// Store the given signature. The bank will reject any transaction with the same signature. - fn reserve_signature(signatures: &mut SignatureStatusMap, signature: &Signature) -> Result<()> { - if let Some(_result) = signatures.get(signature) { - return Err(BankError::DuplicateSignature); - } - signatures.insert(*signature, Err(BankError::SignatureReserved)); - Ok(()) - } - - /// Forget all signatures. Useful for benchmarking. - pub fn clear_signatures(&self) { - for entry in &mut self.last_ids.write().unwrap().entries.values_mut() { - entry.signature_status.clear(); - } - } - - /// Check if the age of the entry_id is within the max_age - /// return false for any entries with an age equal to or above max_age - fn check_entry_id_age(last_ids: &LastIds, entry_id: Hash, max_age: usize) -> bool { - let entry = last_ids.entries.get(&entry_id); - - match entry { - Some(entry) => last_ids.tick_height - entry.tick_height < max_age as u64, - _ => false, - } - } - - fn reserve_signature_with_last_id( - last_ids: &mut LastIds, - last_id: &Hash, - sig: &Signature, - ) -> Result<()> { - if let Some(entry) = last_ids.entries.get_mut(last_id) { - if last_ids.tick_height - entry.tick_height < MAX_ENTRY_IDS as u64 { - return Self::reserve_signature(&mut entry.signature_status, sig); - } - } - Err(BankError::LastIdNotFound) - } - - #[cfg(test)] - fn reserve_signature_with_last_id_test(&self, sig: &Signature, last_id: &Hash) -> Result<()> { - let mut last_ids = self.last_ids.write().unwrap(); - Self::reserve_signature_with_last_id(&mut last_ids, last_id, sig) - } - - fn update_signature_status_with_last_id( - last_ids_sigs: &mut HashMap, - signature: &Signature, - result: &Result<()>, - last_id: &Hash, - ) { - if let Some(entry) = last_ids_sigs.get_mut(last_id) { - entry.signature_status.insert(*signature, result.clone()); - } - } - - fn update_transaction_statuses(&self, txs: &[Transaction], res: &[Result<()>]) { - let mut last_ids = self.last_ids.write().unwrap(); - for (i, tx) in txs.iter().enumerate() { - Self::update_signature_status_with_last_id( - &mut last_ids.entries, - &tx.signatures[0], - &res[i], - &tx.last_id, - ); - if res[i] != Err(BankError::SignatureNotFound) { - let status = match res[i] { - Ok(_) => RpcSignatureStatus::Confirmed, - Err(BankError::AccountInUse) => RpcSignatureStatus::AccountInUse, - Err(BankError::ProgramRuntimeError(_)) => { - RpcSignatureStatus::ProgramRuntimeError - } - Err(_) => RpcSignatureStatus::GenericFailure, - }; - if status != RpcSignatureStatus::SignatureNotFound { - self.check_signature_subscriptions(&tx.signatures[0], status); - } - } - } - } - - /// Maps a tick height to a timestamp - fn tick_height_to_timestamp(last_ids: &LastIds, tick_height: u64) -> Option { - for entry in last_ids.entries.values() { - if entry.tick_height == tick_height { - return Some(entry.timestamp); - } - } - None - } - - /// Look through the last_ids and find all the valid ids - /// This is batched to avoid holding the lock for a significant amount of time - /// - /// Return a vec of tuple of (valid index, timestamp) - /// index is into the passed ids slice to avoid copying hashes - pub fn count_valid_ids(&self, ids: &[Hash]) -> Vec<(usize, u64)> { - let last_ids = self.last_ids.read().unwrap(); - let mut ret = Vec::new(); - for (i, id) in ids.iter().enumerate() { - if let Some(entry) = last_ids.entries.get(id) { - if last_ids.tick_height - entry.tick_height < MAX_ENTRY_IDS as u64 { - ret.push((i, entry.timestamp)); - } - } - } - ret - } - - /// Looks through a list of tick heights and stakes, and finds the latest - /// tick that has achieved finality - pub fn get_finality_timestamp( - &self, - ticks_and_stakes: &mut [(u64, u64)], - supermajority_stake: u64, - ) -> Option { - // Sort by tick height - ticks_and_stakes.sort_by(|a, b| a.0.cmp(&b.0)); - let last_ids = self.last_ids.read().unwrap(); - let current_tick_height = last_ids.tick_height; - let mut total = 0; - for (tick_height, stake) in ticks_and_stakes.iter() { - if ((current_tick_height - tick_height) as usize) < MAX_ENTRY_IDS { - total += stake; - if total > supermajority_stake { - return Self::tick_height_to_timestamp(&last_ids, *tick_height); - } - } - } - None - } - - /// Tell the bank which Entry IDs exist on the ledger. This function - /// assumes subsequent calls correspond to later entries, and will boot - /// the oldest ones once its internal cache is full. Once boot, the - /// bank will reject transactions using that `last_id`. - pub fn register_tick(&self, last_id: &Hash) { - let mut last_ids = self.last_ids.write().unwrap(); - - last_ids.tick_height += 1; - let tick_height = last_ids.tick_height; - - // this clean up can be deferred until sigs gets larger - // because we verify entry.nth every place we check for validity - if last_ids.entries.len() >= MAX_ENTRY_IDS as usize { - last_ids - .entries - .retain(|_, entry| tick_height - entry.tick_height <= MAX_ENTRY_IDS as u64); - } - - last_ids.entries.insert( - *last_id, - LastIdEntry { - tick_height, - timestamp: timestamp(), - signature_status: HashMap::new(), - }, - ); - - last_ids.last_id = Some(*last_id); - - inc_new_counter_info!("bank-register_tick-registered", 1); - } - - /// Process a Transaction. This is used for unit tests and simply calls the vector Bank::process_transactions method. - pub fn process_transaction(&self, tx: &Transaction) -> Result<()> { - let txs = vec![tx.clone()]; - match self.process_transactions(&txs)[0] { - Err(ref e) => { - info!("process_transaction error: {:?}", e); - Err((*e).clone()) - } - Ok(_) => Ok(()), - } - } - - fn lock_account( - account_locks: &mut HashSet, - keys: &[Pubkey], - error_counters: &mut ErrorCounters, - ) -> Result<()> { - // Copy all the accounts - for k in keys { - if account_locks.contains(k) { - error_counters.account_in_use += 1; - return Err(BankError::AccountInUse); - } - } - for k in keys { - account_locks.insert(*k); - } - Ok(()) - } - - fn unlock_account(tx: &Transaction, result: &Result<()>, account_locks: &mut HashSet) { - match result { - Err(BankError::AccountInUse) => (), - _ => { - for k in &tx.account_keys { - account_locks.remove(k); - } - } - } - } - - fn load_account( - &self, - tx: &Transaction, - accounts: &Accounts, - last_ids: &mut LastIds, - max_age: usize, - error_counters: &mut ErrorCounters, - ) -> Result> { - // Copy all the accounts - if tx.signatures.is_empty() && tx.fee != 0 { - Err(BankError::MissingSignatureForFee) - } else if accounts.load(&tx.account_keys[0]).is_none() { - error_counters.account_not_found += 1; - Err(BankError::AccountNotFound) - } else if accounts.load(&tx.account_keys[0]).unwrap().tokens < tx.fee { - error_counters.insufficient_funds += 1; - Err(BankError::InsufficientFundsForFee) - } else { - if !Self::check_entry_id_age(&last_ids, tx.last_id, max_age) { - error_counters.last_id_not_found += 1; - return Err(BankError::LastIdNotFound); - } - - // There is no way to predict what contract will execute without an error - // If a fee can pay for execution then the contract will be scheduled - let err = - Self::reserve_signature_with_last_id(last_ids, &tx.last_id, &tx.signatures[0]); - if let Err(BankError::LastIdNotFound) = err { - error_counters.reserve_last_id += 1; - } else if let Err(BankError::DuplicateSignature) = err { - error_counters.duplicate_signature += 1; - } - err?; - - let mut called_accounts: Vec = tx - .account_keys - .iter() - .map(|key| accounts.load(key).cloned().unwrap_or_default()) - .collect(); - called_accounts[0].tokens -= tx.fee; - Ok(called_accounts) - } - } - - /// This function will prevent multiple threads from modifying the same account state at the - /// same time - #[must_use] - fn lock_accounts(&self, txs: &[Transaction]) -> Vec> { - let mut account_locks = self.account_locks.lock().unwrap(); - let mut error_counters = ErrorCounters::default(); - let rv = txs - .iter() - .map(|tx| Self::lock_account(&mut account_locks, &tx.account_keys, &mut error_counters)) - .collect(); - if error_counters.account_in_use != 0 { - inc_new_counter_info!( - "bank-process_transactions-account_in_use", - error_counters.account_in_use - ); - } - rv - } - - /// Once accounts are unlocked, new transactions that modify that state can enter the pipeline - fn unlock_accounts(&self, txs: &[Transaction], results: &[Result<()>]) { - debug!("bank unlock accounts"); - let mut account_locks = self.account_locks.lock().unwrap(); - txs.iter() - .zip(results.iter()) - .for_each(|(tx, result)| Self::unlock_account(tx, result, &mut account_locks)); - } - - fn load_accounts( - &self, - txs: &[Transaction], - results: Vec>, - max_age: usize, - error_counters: &mut ErrorCounters, - ) -> Vec<(Result>)> { - let accounts = self.accounts.read().unwrap(); - let mut last_ids = self.last_ids.write().unwrap(); - txs.iter() - .zip(results.into_iter()) - .map(|etx| match etx { - (tx, Ok(())) => { - self.load_account(tx, &accounts, &mut last_ids, max_age, error_counters) - } - (_, Err(e)) => Err(e), - }).collect() - } - - pub fn verify_instruction( - instruction_index: usize, - program_id: &Pubkey, - pre_program_id: &Pubkey, - pre_tokens: u64, - account: &Account, - ) -> Result<()> { - // Verify the transaction - - // Make sure that program_id is still the same or this was just assigned by the system call contract - if *pre_program_id != account.owner && !SystemProgram::check_id(&program_id) { - return Err(BankError::ModifiedContractId(instruction_index as u8)); - } - // For accounts unassigned to the contract, the individual balance of each accounts cannot decrease. - if *program_id != account.owner && pre_tokens > account.tokens { - return Err(BankError::ExternalAccountTokenSpend( - instruction_index as u8, - )); - } - Ok(()) - } - - /// Execute a function with a subset of accounts as writable references. - /// Since the subset can point to the same references, in any order there is no way - /// for the borrow checker to track them with regards to the original set. - fn with_subset(accounts: &mut [Account], ixes: &[u8], func: F) -> A - where - F: Fn(&mut [&mut Account]) -> A, - { - let mut subset: Vec<&mut Account> = ixes - .iter() - .map(|ix| { - let ptr = &mut accounts[*ix as usize] as *mut Account; - // lifetime of this unsafe is only within the scope of the closure - // there is no way to reorder them without breaking borrow checker rules - unsafe { &mut *ptr } - }).collect(); - func(&mut subset) - } - - fn load_executable_accounts(&self, mut program_id: Pubkey) -> Result> { - let mut accounts = Vec::new(); - let mut depth = 0; - loop { - if native_loader::check_id(&program_id) { - // at the root of the chain, ready to dispatch - break; - } - - if depth >= 5 { - return Err(BankError::CallChainTooDeep); - } - depth += 1; - - let program = match self.get_account(&program_id) { - Some(program) => program, - None => return Err(BankError::AccountNotFound), - }; - if !program.executable || program.loader == Pubkey::default() { - return Err(BankError::AccountNotFound); - } - - // add loader to chain - accounts.insert(0, (program_id, program.clone())); - - program_id = program.loader; - } - Ok(accounts) - } - - /// Execute an instruction - /// This method calls the instruction's program entry pont method and verifies that the result of - /// the call does not violate the bank's accounting rules. - /// The accounts are committed back to the bank only if this function returns Ok(_). - fn execute_instruction( - &self, - tx: &Transaction, - instruction_index: usize, - program_accounts: &mut [&mut Account], - ) -> Result<()> { - let program_id = tx.program_id(instruction_index); - // TODO: the runtime should be checking read/write access to memory - // we are trusting the hard coded contracts not to clobber or allocate - let pre_total: u64 = program_accounts.iter().map(|a| a.tokens).sum(); - let pre_data: Vec<_> = program_accounts - .iter_mut() - .map(|a| (a.owner, a.tokens)) - .collect(); - - // Call the contract method - // It's up to the contract to implement its own rules on moving funds - if SystemProgram::check_id(&program_id) { - if let Err(err) = - SystemProgram::process_transaction(&tx, instruction_index, program_accounts) - { - let err = match err { - Error::ResultWithNegativeTokens(i) => BankError::ResultWithNegativeTokens(i), - _ => BankError::ProgramRuntimeError(instruction_index as u8), - }; - return Err(err); - } - } else if BudgetState::check_id(&program_id) { - if BudgetState::process_transaction(&tx, instruction_index, program_accounts).is_err() { - return Err(BankError::ProgramRuntimeError(instruction_index as u8)); - } - } else if StorageProgram::check_id(&program_id) { - if StorageProgram::process_transaction(&tx, instruction_index, program_accounts) - .is_err() - { - return Err(BankError::ProgramRuntimeError(instruction_index as u8)); - } - } else if VoteProgram::check_id(&program_id) { - if VoteProgram::process_transaction(&tx, instruction_index, program_accounts).is_err() { - return Err(BankError::ProgramRuntimeError(instruction_index as u8)); - } - } else { - let mut accounts = self.load_executable_accounts(tx.program_ids[instruction_index])?; - let mut keyed_accounts = create_keyed_accounts(&mut accounts); - let mut keyed_accounts2: Vec<_> = tx.instructions[instruction_index] - .accounts - .iter() - .map(|&index| &tx.account_keys[index as usize]) - .zip(program_accounts.iter_mut()) - .map(|(key, account)| KeyedAccount { key, account }) - .collect(); - keyed_accounts.append(&mut keyed_accounts2); - - if !native_loader::process_instruction( - &program_id, - &mut keyed_accounts, - &tx.instructions[instruction_index].userdata, - self.tick_height(), - ) { - return Err(BankError::ProgramRuntimeError(instruction_index as u8)); - } - } - - // Verify the instruction - for ((pre_program_id, pre_tokens), post_account) in - pre_data.iter().zip(program_accounts.iter()) - { - Self::verify_instruction( - instruction_index, - &program_id, - pre_program_id, - *pre_tokens, - post_account, - )?; - } - // The total sum of all the tokens in all the accounts cannot change. - let post_total: u64 = program_accounts.iter().map(|a| a.tokens).sum(); - if pre_total != post_total { - Err(BankError::UnbalancedInstruction(instruction_index as u8)) - } else { - Ok(()) - } - } - - /// Execute a transaction. - /// This method calls each instruction in the transaction over the set of loaded Accounts - /// The accounts are committed back to the bank only if every instruction succeeds - fn execute_transaction(&self, tx: &Transaction, tx_accounts: &mut [Account]) -> Result<()> { - for (instruction_index, instruction) in tx.instructions.iter().enumerate() { - Self::with_subset(tx_accounts, &instruction.accounts, |program_accounts| { - self.execute_instruction(tx, instruction_index, program_accounts) - })?; - } - Ok(()) - } - - pub fn store_accounts( - &self, - txs: &[Transaction], - res: &[Result<()>], - loaded: &[Result>], - ) { - let mut accounts = self.accounts.write().unwrap(); - for (i, racc) in loaded.iter().enumerate() { - if res[i].is_err() || racc.is_err() { - continue; - } - - let tx = &txs[i]; - let acc = racc.as_ref().unwrap(); - for (key, account) in tx.account_keys.iter().zip(acc.iter()) { - accounts.store(key, account); - } - } - } - - pub fn process_and_record_transactions( - &self, - txs: &[Transaction], - poh: &PohRecorder, - ) -> Result<()> { - let now = Instant::now(); - // Once accounts are locked, other threads cannot encode transactions that will modify the - // same account state - let locked_accounts = self.lock_accounts(txs); - let lock_time = now.elapsed(); - let now = Instant::now(); - // Use a shorter maximum age when adding transactions into the pipeline. This will reduce - // the likelyhood of any single thread getting starved and processing old ids. - // TODO: Banking stage threads should be prioritized to complete faster then this queue - // expires. - let results = - self.execute_and_commit_transactions(txs, locked_accounts, MAX_ENTRY_IDS as usize / 2); - let process_time = now.elapsed(); - let now = Instant::now(); - self.record_transactions(txs, &results, poh)?; - let record_time = now.elapsed(); - let now = Instant::now(); - // Once the accounts are unlocked new transactions can enter the pipeline to process them - self.unlock_accounts(&txs, &results); - let unlock_time = now.elapsed(); - debug!( - "lock: {}us process: {}us record: {}us unlock: {}us txs_len={}", - duration_as_us(&lock_time), - duration_as_us(&process_time), - duration_as_us(&record_time), - duration_as_us(&unlock_time), - txs.len(), - ); - Ok(()) - } - - fn record_transactions( - &self, - txs: &[Transaction], - results: &[Result<()>], - poh: &PohRecorder, - ) -> Result<()> { - let processed_transactions: Vec<_> = results - .iter() - .zip(txs.iter()) - .filter_map(|(r, x)| match r { - Ok(_) => Some(x.clone()), - Err(ref e) => { - debug!("process transaction failed {:?}", e); - None - } - }).collect(); - // unlock all the accounts with errors which are filtered by the above `filter_map` - if !processed_transactions.is_empty() { - let hash = Transaction::hash(&processed_transactions); - debug!("processed ok: {} {}", processed_transactions.len(), hash); - // record and unlock will unlock all the successfull transactions - poh.record(hash, processed_transactions).map_err(|e| { - warn!("record failure: {:?}", e); - BankError::RecordFailure - })?; - } - Ok(()) - } - - /// Process a batch of transactions. - #[must_use] - pub fn execute_and_commit_transactions( - &self, - txs: &[Transaction], - locked_accounts: Vec>, - max_age: usize, - ) -> Vec> { - debug!("processing transactions: {}", txs.len()); - let mut error_counters = ErrorCounters::default(); - let now = Instant::now(); - let mut loaded_accounts = - self.load_accounts(txs, locked_accounts.clone(), max_age, &mut error_counters); - - let load_elapsed = now.elapsed(); - let now = Instant::now(); - let executed: Vec> = loaded_accounts - .iter_mut() - .zip(txs.iter()) - .map(|(acc, tx)| match acc { - Err(e) => Err(e.clone()), - Ok(ref mut accounts) => self.execute_transaction(tx, accounts), - }).collect(); - let execution_elapsed = now.elapsed(); - let now = Instant::now(); - self.store_accounts(txs, &executed, &loaded_accounts); - - // Check account subscriptions and send notifications - self.send_account_notifications(txs, locked_accounts); - - // once committed there is no way to unroll - let write_elapsed = now.elapsed(); - debug!( - "load: {}us execute: {}us store: {}us txs_len={}", - duration_as_us(&load_elapsed), - duration_as_us(&execution_elapsed), - duration_as_us(&write_elapsed), - txs.len(), - ); - self.update_transaction_statuses(txs, &executed); - let mut tx_count = 0; - let mut err_count = 0; - for (r, tx) in executed.iter().zip(txs.iter()) { - if r.is_ok() { - tx_count += 1; - } else { - if err_count == 0 { - info!("tx error: {:?} {:?}", r, tx); - } - err_count += 1; - } - } - if err_count > 0 { - info!("{} errors of {} txs", err_count, err_count + tx_count); - inc_new_counter_info!( - "bank-process_transactions-account_not_found", - error_counters.account_not_found - ); - inc_new_counter_info!("bank-process_transactions-error_count", err_count); - } - - self.accounts - .write() - .unwrap() - .increment_transaction_count(tx_count); - - inc_new_counter_info!("bank-process_transactions-txs", tx_count); - if 0 != error_counters.last_id_not_found { - inc_new_counter_info!( - "bank-process_transactions-error-last_id_not_found", - error_counters.last_id_not_found - ); - } - if 0 != error_counters.reserve_last_id { - inc_new_counter_info!( - "bank-process_transactions-error-reserve_last_id", - error_counters.reserve_last_id - ); - } - if 0 != error_counters.duplicate_signature { - inc_new_counter_info!( - "bank-process_transactions-error-duplicate_signature", - error_counters.duplicate_signature - ); - } - if 0 != error_counters.insufficient_funds { - inc_new_counter_info!( - "bank-process_transactions-error-insufficient_funds", - error_counters.insufficient_funds - ); - } - executed - } - - #[must_use] - pub fn process_transactions(&self, txs: &[Transaction]) -> Vec> { - let locked_accounts = self.lock_accounts(txs); - let results = self.execute_and_commit_transactions(txs, locked_accounts, MAX_ENTRY_IDS); - self.unlock_accounts(txs, &results); - results - } - - pub fn process_entry(&self, entry: &Entry) -> Result<()> { - if !entry.is_tick() { - for result in self.process_transactions(&entry.transactions) { - result?; - } - } else { - self.register_tick(&entry.id); - self.leader_scheduler - .write() - .unwrap() - .update_height(self.tick_height(), self); - } - - Ok(()) - } - - /// Process an ordered list of entries. - pub fn process_entries(&self, entries: &[Entry]) -> Result<()> { - self.par_process_entries(entries) - } - - pub fn first_err(results: &[Result<()>]) -> Result<()> { - for r in results { - r.clone()?; - } - Ok(()) - } - pub fn par_execute_entries(&self, entries: &[(&Entry, Vec>)]) -> Result<()> { - inc_new_counter_info!("bank-par_execute_entries-count", entries.len()); - let results: Vec> = entries - .into_par_iter() - .map(|(e, locks)| { - let results = self.execute_and_commit_transactions( - &e.transactions, - locks.to_vec(), - MAX_ENTRY_IDS, - ); - self.unlock_accounts(&e.transactions, &results); - Self::first_err(&results) - }).collect(); - Self::first_err(&results) - } - - /// process entries in parallel - /// 1. In order lock accounts for each entry while the lock succeeds, up to a Tick entry - /// 2. Process the locked group in parallel - /// 3. Register the `Tick` if it's available, goto 1 - pub fn par_process_entries(&self, entries: &[Entry]) -> Result<()> { - // accumulator for entries that can be processed in parallel - let mut mt_group = vec![]; - for entry in entries { - if entry.is_tick() { - // if its a tick, execute the group and register the tick - self.par_execute_entries(&mt_group)?; - self.register_tick(&entry.id); - mt_group = vec![]; - continue; - } - // try to lock the accounts - let locked = self.lock_accounts(&entry.transactions); - // if any of the locks error out - // execute the current group - if Self::first_err(&locked).is_err() { - self.par_execute_entries(&mt_group)?; - mt_group = vec![]; - //reset the lock and push the entry - let locked = self.lock_accounts(&entry.transactions); - mt_group.push((entry, locked)); - } else { - // push the entry to the mt_group - mt_group.push((entry, locked)); - } - } - self.par_execute_entries(&mt_group)?; - Ok(()) - } - - /// Process an ordered list of entries, populating a circular buffer "tail" - /// as we go. - fn process_block(&self, entries: &[Entry]) -> Result<()> { - for entry in entries { - self.process_entry(entry)?; - } - - Ok(()) - } - - /// Append entry blocks to the ledger, verifying them along the way. - fn process_ledger_blocks( - &self, - start_hash: Hash, - entry_height: u64, - entries: I, - ) -> Result<(u64, Hash)> - where - I: IntoIterator, - { - // these magic numbers are from genesis of the mint, could pull them - // back out of this loop. - let mut entry_height = entry_height; - let mut last_id = start_hash; - - // Ledger verification needs to be parallelized, but we can't pull the whole - // thing into memory. We therefore chunk it. - for block in &entries.into_iter().chunks(VERIFY_BLOCK_SIZE) { - let block: Vec<_> = block.collect(); - - if !block.verify(&last_id) { - warn!("Ledger proof of history failed at entry: {}", entry_height); - return Err(BankError::LedgerVerificationFailed); - } - - self.process_block(&block)?; - - last_id = block.last().unwrap().id; - entry_height += block.len() as u64; - } - Ok((entry_height, last_id)) - } - - /// Process a full ledger. - pub fn process_ledger(&self, entries: I) -> Result<(u64, Hash)> - where - I: IntoIterator, - { - let mut entries = entries.into_iter(); - - // The first item in the ledger is required to be an entry with zero num_hashes, - // which implies its id can be used as the ledger's seed. - let entry0 = entries.next().expect("invalid ledger: empty"); - - // The second item in the ledger consists of a transaction with - // two special instructions: - // 1) The first is a special move instruction where the to and from - // fields are the same. That entry should be treated as a deposit, not a - // transfer to oneself. - // 2) The second is a move instruction that acts as a payment to the first - // leader from the mint. This bootstrap leader will stay in power during the - // bootstrapping period of the network - let entry1 = entries - .next() - .expect("invalid ledger: need at least 2 entries"); - - // genesis should conform to PoH - assert!(entry1.verify(&entry0.id)); - - { - // Process the first transaction - let tx = &entry1.transactions[0]; - assert!(SystemProgram::check_id(tx.program_id(0)), "Invalid ledger"); - assert!(SystemProgram::check_id(tx.program_id(1)), "Invalid ledger"); - let mut instruction: SystemInstruction = deserialize(tx.userdata(0)).unwrap(); - let mint_deposit = if let SystemInstruction::Move { tokens } = instruction { - Some(tokens) - } else { - None - }.expect("invalid ledger, needs to start with mint deposit"); - - instruction = deserialize(tx.userdata(1)).unwrap(); - let leader_payment = if let SystemInstruction::Move { tokens } = instruction { - Some(tokens) - } else { - None - }.expect("invalid ledger, bootstrap leader payment expected"); - - assert!(leader_payment <= mint_deposit); - assert!(leader_payment > 0); - - { - // 1) Deposit into the mint - let mut accounts = self.accounts.write().unwrap(); - - let mut account = accounts - .load(&tx.account_keys[0]) - .cloned() - .unwrap_or_default(); - account.tokens += mint_deposit - leader_payment; - accounts.store(&tx.account_keys[0], &account); - trace!( - "applied genesis payment {:?} => {:?}", - mint_deposit - leader_payment, - account - ); - - // 2) Transfer tokens to the bootstrap leader. The first two - // account keys will both be the mint (because the mint is the source - // for this transaction and the first move instruction is to the the - // mint itself), so we look at the third account key to find the first - // leader id. - let bootstrap_leader_id = tx.account_keys[2]; - let mut account = accounts - .load(&bootstrap_leader_id) - .cloned() - .unwrap_or_default(); - account.tokens += leader_payment; - accounts.store(&bootstrap_leader_id, &account); - - self.leader_scheduler.write().unwrap().bootstrap_leader = bootstrap_leader_id; - - trace!( - "applied genesis payment to bootstrap leader {:?} => {:?}", - leader_payment, - account - ); - } - } - - Ok(self.process_ledger_blocks(entry1.id, 2, entries)?) - } - - /// Create, sign, and process a Transaction from `keypair` to `to` of - /// `n` tokens where `last_id` is the last Entry ID observed by the client. - pub fn transfer( - &self, - n: u64, - keypair: &Keypair, - to: Pubkey, - last_id: Hash, - ) -> Result { - let tx = Transaction::system_new(keypair, to, n, last_id); - let signature = tx.signatures[0]; - self.process_transaction(&tx).map(|_| signature) - } - - pub fn read_balance(account: &Account) -> u64 { - if SystemProgram::check_id(&account.owner) { - SystemProgram::get_balance(account) - } else if BudgetState::check_id(&account.owner) { - BudgetState::get_balance(account) - } else { - account.tokens - } - } - /// Each contract would need to be able to introspect its own state - /// this is hard coded to the budget contract language - pub fn get_balance(&self, pubkey: &Pubkey) -> u64 { - self.get_account(pubkey) - .map(|x| Self::read_balance(&x)) - .unwrap_or(0) - } - - /// TODO: Need to implement a real staking contract to hold node stake. - /// Right now this just gets the account balances. See github issue #1655. - pub fn get_stake(&self, pubkey: &Pubkey) -> u64 { - self.get_balance(pubkey) - } - - pub fn get_account(&self, pubkey: &Pubkey) -> Option { - let accounts = self - .accounts - .read() - .expect("'accounts' read lock in get_balance"); - accounts.load(pubkey).cloned() - } - - pub fn transaction_count(&self) -> u64 { - self.accounts.read().unwrap().transaction_count() - } - - pub fn get_signature_status(&self, signature: &Signature) -> Result<()> { - let last_ids = self.last_ids.read().unwrap(); - for entry in last_ids.entries.values() { - if let Some(res) = entry.signature_status.get(signature) { - return res.clone(); - } - } - Err(BankError::SignatureNotFound) - } - - pub fn has_signature(&self, signature: &Signature) -> bool { - self.get_signature_status(signature) != Err(BankError::SignatureNotFound) - } - - pub fn get_signature(&self, last_id: &Hash, signature: &Signature) -> Option> { - self.last_ids - .read() - .unwrap() - .entries - .get(last_id) - .and_then(|entry| entry.signature_status.get(signature).cloned()) - } - - /// Hash the `accounts` HashMap. This represents a validator's interpretation - /// of the delta of the ledger since the last vote and up to now - pub fn hash_internal_state(&self) -> Hash { - let mut ordered_accounts = BTreeMap::new(); - - // only hash internal state of the part being voted upon, i.e. since last - // checkpoint - for (pubkey, account) in &self.accounts.read().unwrap().accounts { - ordered_accounts.insert(*pubkey, account.clone()); - } - - hash(&serialize(&ordered_accounts).unwrap()) - } - - pub fn finality(&self) -> usize { - self.finality_time.load(Ordering::Relaxed) - } - - pub fn set_finality(&self, finality: usize) { - self.finality_time.store(finality, Ordering::Relaxed); - } - - fn send_account_notifications(&self, txs: &[Transaction], locked_accounts: Vec>) { - let accounts = self.accounts.read().unwrap(); - txs.iter() - .zip(locked_accounts.into_iter()) - .filter(|(_, result)| result.is_ok()) - .flat_map(|(tx, _)| &tx.account_keys) - .for_each(|pubkey| { - let account = accounts.load(pubkey).cloned().unwrap_or_default(); - self.check_account_subscriptions(&pubkey, &account); - }); - } - pub fn add_account_subscription( - &self, - bank_sub_id: Pubkey, - pubkey: Pubkey, - sink: Sink, - ) { - let mut subscriptions = self.account_subscriptions.write().unwrap(); - if let Some(current_hashmap) = subscriptions.get_mut(&pubkey) { - current_hashmap.insert(bank_sub_id, sink); - return; - } - let mut hashmap = HashMap::new(); - hashmap.insert(bank_sub_id, sink); - subscriptions.insert(pubkey, hashmap); - } - - pub fn remove_account_subscription(&self, bank_sub_id: &Pubkey, pubkey: &Pubkey) -> bool { - let mut subscriptions = self.account_subscriptions.write().unwrap(); - match subscriptions.get_mut(pubkey) { - Some(ref current_hashmap) if current_hashmap.len() == 1 => {} - Some(current_hashmap) => { - return current_hashmap.remove(bank_sub_id).is_some(); - } - None => { - return false; - } - } - subscriptions.remove(pubkey).is_some() - } - - pub fn get_current_leader(&self) -> Option<(Pubkey, u64)> { - self.leader_scheduler - .read() - .unwrap() - .get_scheduled_leader(self.tick_height()) - } - - pub fn tick_height(&self) -> u64 { - self.last_ids.read().unwrap().tick_height - } - - fn check_account_subscriptions(&self, pubkey: &Pubkey, account: &Account) { - let subscriptions = self.account_subscriptions.read().unwrap(); - if let Some(hashmap) = subscriptions.get(pubkey) { - for (_bank_sub_id, sink) in hashmap.iter() { - sink.notify(Ok(account.clone())).wait().unwrap(); - } - } - } - - pub fn add_signature_subscription( - &self, - bank_sub_id: Pubkey, - signature: Signature, - sink: Sink, - ) { - let mut subscriptions = self.signature_subscriptions.write().unwrap(); - if let Some(current_hashmap) = subscriptions.get_mut(&signature) { - current_hashmap.insert(bank_sub_id, sink); - return; - } - let mut hashmap = HashMap::new(); - hashmap.insert(bank_sub_id, sink); - subscriptions.insert(signature, hashmap); - } - - pub fn remove_signature_subscription( - &self, - bank_sub_id: &Pubkey, - signature: &Signature, - ) -> bool { - let mut subscriptions = self.signature_subscriptions.write().unwrap(); - match subscriptions.get_mut(signature) { - Some(ref current_hashmap) if current_hashmap.len() == 1 => {} - Some(current_hashmap) => { - return current_hashmap.remove(bank_sub_id).is_some(); - } - None => { - return false; - } - } - subscriptions.remove(signature).is_some() - } - - fn check_signature_subscriptions(&self, signature: &Signature, status: RpcSignatureStatus) { - let mut subscriptions = self.signature_subscriptions.write().unwrap(); - if let Some(hashmap) = subscriptions.get(signature) { - for (_bank_sub_id, sink) in hashmap.iter() { - sink.notify(Ok(status)).wait().unwrap(); - } - } - subscriptions.remove(&signature); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bincode::serialize; - use budget_program::BudgetState; - use entry::next_entry; - use entry::Entry; - use jsonrpc_macros::pubsub::{Subscriber, SubscriptionId}; - use ledger; - use signature::Keypair; - use signature::{GenKeys, KeypairUtil}; - use solana_sdk::hash::hash; - use std; - use system_transaction::SystemTransaction; - use tokio::prelude::{Async, Stream}; - use transaction::Instruction; - - #[test] - fn test_bank_new() { - let mint = Mint::new(10_000); - let bank = Bank::new(&mint); - assert_eq!(bank.get_balance(&mint.pubkey()), 10_000); - } - - #[test] - fn test_bank_new_with_leader() { - let dummy_leader_id = Keypair::new().pubkey(); - let dummy_leader_tokens = 1; - let mint = Mint::new_with_leader(10_000, dummy_leader_id, dummy_leader_tokens); - let bank = Bank::new(&mint); - assert_eq!(bank.get_balance(&mint.pubkey()), 9999); - assert_eq!(bank.get_balance(&dummy_leader_id), 1); - } - - #[test] - fn test_two_payments_to_one_party() { - let mint = Mint::new(10_000); - let pubkey = Keypair::new().pubkey(); - let bank = Bank::new(&mint); - assert_eq!(bank.last_id(), mint.last_id()); - - bank.transfer(1_000, &mint.keypair(), pubkey, mint.last_id()) - .unwrap(); - assert_eq!(bank.get_balance(&pubkey), 1_000); - - bank.transfer(500, &mint.keypair(), pubkey, mint.last_id()) - .unwrap(); - assert_eq!(bank.get_balance(&pubkey), 1_500); - assert_eq!(bank.transaction_count(), 2); - } - - #[test] - fn test_one_source_two_tx_one_batch() { - let mint = Mint::new(1); - let key1 = Keypair::new().pubkey(); - let key2 = Keypair::new().pubkey(); - let bank = Bank::new(&mint); - assert_eq!(bank.last_id(), mint.last_id()); - - let t1 = Transaction::system_move(&mint.keypair(), key1, 1, mint.last_id(), 0); - let t2 = Transaction::system_move(&mint.keypair(), key2, 1, mint.last_id(), 0); - let res = bank.process_transactions(&vec![t1.clone(), t2.clone()]); - assert_eq!(res.len(), 2); - assert_eq!(res[0], Ok(())); - assert_eq!(res[1], Err(BankError::AccountInUse)); - assert_eq!(bank.get_balance(&mint.pubkey()), 0); - assert_eq!(bank.get_balance(&key1), 1); - assert_eq!(bank.get_balance(&key2), 0); - assert_eq!( - bank.get_signature(&t1.last_id, &t1.signatures[0]), - Some(Ok(())) - ); - // TODO: Transactions that fail to pay a fee could be dropped silently - assert_eq!( - bank.get_signature(&t2.last_id, &t2.signatures[0]), - Some(Err(BankError::AccountInUse)) - ); - } - - #[test] - fn test_one_tx_two_out_atomic_fail() { - let mint = Mint::new(1); - let key1 = Keypair::new().pubkey(); - let key2 = Keypair::new().pubkey(); - let bank = Bank::new(&mint); - let spend = SystemInstruction::Move { tokens: 1 }; - let instructions = vec![ - Instruction { - program_ids_index: 0, - userdata: serialize(&spend).unwrap(), - accounts: vec![0, 1], - }, - Instruction { - program_ids_index: 0, - userdata: serialize(&spend).unwrap(), - accounts: vec![0, 2], - }, - ]; - - let t1 = Transaction::new_with_instructions( - &[&mint.keypair()], - &[key1, key2], - mint.last_id(), - 0, - vec![SystemProgram::id()], - instructions, - ); - let res = bank.process_transactions(&vec![t1.clone()]); - assert_eq!(res.len(), 1); - assert_eq!(res[0], Err(BankError::ResultWithNegativeTokens(1))); - assert_eq!(bank.get_balance(&mint.pubkey()), 1); - assert_eq!(bank.get_balance(&key1), 0); - assert_eq!(bank.get_balance(&key2), 0); - assert_eq!( - bank.get_signature(&t1.last_id, &t1.signatures[0]), - Some(Err(BankError::ResultWithNegativeTokens(1))) - ); - } - - #[test] - fn test_one_tx_two_out_atomic_pass() { - let mint = Mint::new(2); - let key1 = Keypair::new().pubkey(); - let key2 = Keypair::new().pubkey(); - let bank = Bank::new(&mint); - let t1 = Transaction::system_move_many( - &mint.keypair(), - &[(key1, 1), (key2, 1)], - mint.last_id(), - 0, - ); - let res = bank.process_transactions(&vec![t1.clone()]); - assert_eq!(res.len(), 1); - assert_eq!(res[0], Ok(())); - assert_eq!(bank.get_balance(&mint.pubkey()), 0); - assert_eq!(bank.get_balance(&key1), 1); - assert_eq!(bank.get_balance(&key2), 1); - assert_eq!( - bank.get_signature(&t1.last_id, &t1.signatures[0]), - Some(Ok(())) - ); - } - - // TODO: This test demonstrates that fees are not paid when a program fails. - // See github issue 1157 (https://github.com/solana-labs/solana/issues/1157) - #[test] - fn test_detect_failed_duplicate_transactions_issue_1157() { - let mint = Mint::new(1); - let bank = Bank::new(&mint); - let dest = Keypair::new(); - - // source with 0 contract context - let tx = Transaction::system_create( - &mint.keypair(), - dest.pubkey(), - mint.last_id(), - 2, - 0, - Pubkey::default(), - 1, - ); - let signature = tx.signatures[0]; - assert!(!bank.has_signature(&signature)); - let res = bank.process_transaction(&tx); - - // Result failed, but signature is registered - assert!(res.is_err()); - assert!(bank.has_signature(&signature)); - assert_matches!( - bank.get_signature_status(&signature), - Err(BankError::ResultWithNegativeTokens(0)) - ); - - // The tokens didn't move, but the from address paid the transaction fee. - assert_eq!(bank.get_balance(&dest.pubkey()), 0); - - // BUG: This should be the original balance minus the transaction fee. - //assert_eq!(bank.get_balance(&mint.pubkey()), 0); - } - - #[test] - fn test_account_not_found() { - let mint = Mint::new(1); - let bank = Bank::new(&mint); - let keypair = Keypair::new(); - assert_eq!( - bank.transfer(1, &keypair, mint.pubkey(), mint.last_id()), - Err(BankError::AccountNotFound) - ); - assert_eq!(bank.transaction_count(), 0); - } - - #[test] - fn test_insufficient_funds() { - let mint = Mint::new(11_000); - let bank = Bank::new(&mint); - let pubkey = Keypair::new().pubkey(); - bank.transfer(1_000, &mint.keypair(), pubkey, mint.last_id()) - .unwrap(); - assert_eq!(bank.transaction_count(), 1); - assert_eq!(bank.get_balance(&pubkey), 1_000); - assert_matches!( - bank.transfer(10_001, &mint.keypair(), pubkey, mint.last_id()), - Err(BankError::ResultWithNegativeTokens(0)) - ); - assert_eq!(bank.transaction_count(), 1); - - let mint_pubkey = mint.keypair().pubkey(); - assert_eq!(bank.get_balance(&mint_pubkey), 10_000); - assert_eq!(bank.get_balance(&pubkey), 1_000); - } - - #[test] - fn test_transfer_to_newb() { - let mint = Mint::new(10_000); - let bank = Bank::new(&mint); - let pubkey = Keypair::new().pubkey(); - bank.transfer(500, &mint.keypair(), pubkey, mint.last_id()) - .unwrap(); - assert_eq!(bank.get_balance(&pubkey), 500); - } - - #[test] - fn test_duplicate_transaction_signature() { - let mint = Mint::new(1); - let bank = Bank::new(&mint); - let signature = Signature::default(); - assert_eq!( - bank.reserve_signature_with_last_id_test(&signature, &mint.last_id()), - Ok(()) - ); - assert_eq!( - bank.reserve_signature_with_last_id_test(&signature, &mint.last_id()), - Err(BankError::DuplicateSignature) - ); - } - - #[test] - fn test_clear_signatures() { - let mint = Mint::new(1); - let bank = Bank::new(&mint); - let signature = Signature::default(); - bank.reserve_signature_with_last_id_test(&signature, &mint.last_id()) - .unwrap(); - bank.clear_signatures(); - assert_eq!( - bank.reserve_signature_with_last_id_test(&signature, &mint.last_id()), - Ok(()) - ); - } - - #[test] - fn test_get_signature_status() { - let mint = Mint::new(1); - let bank = Bank::new(&mint); - let signature = Signature::default(); - bank.reserve_signature_with_last_id_test(&signature, &mint.last_id()) - .expect("reserve signature"); - assert_eq!( - bank.get_signature_status(&signature), - Err(BankError::SignatureReserved) - ); - } - - #[test] - fn test_has_signature() { - let mint = Mint::new(1); - let bank = Bank::new(&mint); - let signature = Signature::default(); - bank.reserve_signature_with_last_id_test(&signature, &mint.last_id()) - .expect("reserve signature"); - assert!(bank.has_signature(&signature)); - } - - #[test] - fn test_reject_old_last_id() { - let mint = Mint::new(1); - let bank = Bank::new(&mint); - let signature = Signature::default(); - for i in 0..MAX_ENTRY_IDS { - let last_id = hash(&serialize(&i).unwrap()); // Unique hash - bank.register_tick(&last_id); - } - // Assert we're no longer able to use the oldest entry ID. - assert_eq!( - bank.reserve_signature_with_last_id_test(&signature, &mint.last_id()), - Err(BankError::LastIdNotFound) - ); - } - - #[test] - fn test_count_valid_ids() { - let mint = Mint::new(1); - let bank = Bank::new(&mint); - let ids: Vec<_> = (0..MAX_ENTRY_IDS) - .map(|i| { - let last_id = hash(&serialize(&i).unwrap()); // Unique hash - bank.register_tick(&last_id); - last_id - }).collect(); - assert_eq!(bank.count_valid_ids(&[]).len(), 0); - assert_eq!(bank.count_valid_ids(&[mint.last_id()]).len(), 0); - for (i, id) in bank.count_valid_ids(&ids).iter().enumerate() { - assert_eq!(id.0, i); - } - } - - #[test] - fn test_debits_before_credits() { - let mint = Mint::new(2); - let bank = Bank::new(&mint); - let keypair = Keypair::new(); - let tx0 = Transaction::system_new(&mint.keypair(), keypair.pubkey(), 2, mint.last_id()); - let tx1 = Transaction::system_new(&keypair, mint.pubkey(), 1, mint.last_id()); - let txs = vec![tx0, tx1]; - let results = bank.process_transactions(&txs); - assert!(results[1].is_err()); - - // Assert bad transactions aren't counted. - assert_eq!(bank.transaction_count(), 1); - } - - #[test] - fn test_process_empty_entry_is_registered() { - let mint = Mint::new(1); - let bank = Bank::new(&mint); - let keypair = Keypair::new(); - let entry = next_entry(&mint.last_id(), 1, vec![]); - let tx = Transaction::system_new(&mint.keypair(), keypair.pubkey(), 1, entry.id); - - // First, ensure the TX is rejected because of the unregistered last ID - assert_eq!( - bank.process_transaction(&tx), - Err(BankError::LastIdNotFound) - ); - - // Now ensure the TX is accepted despite pointing to the ID of an empty entry. - bank.process_entries(&[entry]).unwrap(); - assert_eq!(bank.process_transaction(&tx), Ok(())); - } - - #[test] - fn test_process_genesis() { - let dummy_leader_id = Keypair::new().pubkey(); - let dummy_leader_tokens = 1; - let mint = Mint::new_with_leader(5, dummy_leader_id, dummy_leader_tokens); - let genesis = mint.create_entries(); - let bank = Bank::default(); - bank.process_ledger(genesis).unwrap(); - assert_eq!(bank.get_balance(&mint.pubkey()), 4); - assert_eq!(bank.get_balance(&dummy_leader_id), 1); - assert_eq!( - bank.leader_scheduler.read().unwrap().bootstrap_leader, - dummy_leader_id - ); - } - - fn create_sample_block_with_next_entries_using_keypairs( - mint: &Mint, - keypairs: &[Keypair], - ) -> impl Iterator { - let mut last_id = mint.last_id(); - let mut hash = mint.last_id(); - let mut entries: Vec = vec![]; - let num_hashes = 1; - for k in keypairs { - let txs = vec![Transaction::system_new( - &mint.keypair(), - k.pubkey(), - 1, - last_id, - )]; - let mut e = ledger::next_entries(&hash, 0, txs); - entries.append(&mut e); - hash = entries.last().unwrap().id; - let tick = Entry::new(&hash, num_hashes, vec![]); - hash = tick.id; - last_id = hash; - entries.push(tick); - } - entries.into_iter() - } - - // create a ledger with tick entries every `ticks` entries - fn create_sample_block_with_ticks( - mint: &Mint, - length: usize, - ticks: usize, - ) -> impl Iterator { - let mut entries = Vec::with_capacity(length); - let mut hash = mint.last_id(); - let mut last_id = mint.last_id(); - let num_hashes = 1; - for i in 0..length { - let keypair = Keypair::new(); - let tx = Transaction::system_new(&mint.keypair(), keypair.pubkey(), 1, last_id); - let entry = Entry::new(&hash, num_hashes, vec![tx]); - hash = entry.id; - entries.push(entry); - if (i + 1) % ticks == 0 { - let tick = Entry::new(&hash, num_hashes, vec![]); - hash = tick.id; - last_id = hash; - entries.push(tick); - } - } - entries.into_iter() - } - - fn create_sample_ledger(length: usize) -> (impl Iterator, Pubkey) { - let dummy_leader_id = Keypair::new().pubkey(); - let dummy_leader_tokens = 1; - let mint = Mint::new_with_leader( - length as u64 + 1 + dummy_leader_tokens, - dummy_leader_id, - dummy_leader_tokens, - ); - let genesis = mint.create_entries(); - let block = create_sample_block_with_ticks(&mint, length, length); - (genesis.into_iter().chain(block), mint.pubkey()) - } - - fn create_sample_ledger_with_mint_and_keypairs( - mint: &Mint, - keypairs: &[Keypair], - ) -> impl Iterator { - let genesis = mint.create_entries(); - let block = create_sample_block_with_next_entries_using_keypairs(mint, keypairs); - genesis.into_iter().chain(block) - } - - #[test] - fn test_process_ledger_simple() { - let (ledger, pubkey) = create_sample_ledger(1); - let bank = Bank::default(); - let (ledger_height, last_id) = bank.process_ledger(ledger).unwrap(); - assert_eq!(bank.get_balance(&pubkey), 1); - assert_eq!(ledger_height, 5); - assert_eq!(bank.tick_height(), 2); - assert_eq!(bank.last_id(), last_id); - } - - #[test] - fn test_hash_internal_state() { - let dummy_leader_id = Keypair::new().pubkey(); - let dummy_leader_tokens = 1; - let mint = Mint::new_with_leader(2_000, dummy_leader_id, dummy_leader_tokens); - - let seed = [0u8; 32]; - let mut rnd = GenKeys::new(seed); - let keypairs = rnd.gen_n_keypairs(5); - let ledger0 = create_sample_ledger_with_mint_and_keypairs(&mint, &keypairs); - let ledger1 = create_sample_ledger_with_mint_and_keypairs(&mint, &keypairs); - - let bank0 = Bank::default(); - bank0.process_ledger(ledger0).unwrap(); - let bank1 = Bank::default(); - bank1.process_ledger(ledger1).unwrap(); - - let initial_state = bank0.hash_internal_state(); - - assert_eq!(bank1.hash_internal_state(), initial_state); - - let pubkey = keypairs[0].pubkey(); - bank0 - .transfer(1_000, &mint.keypair(), pubkey, mint.last_id()) - .unwrap(); - assert_ne!(bank0.hash_internal_state(), initial_state); - bank1 - .transfer(1_000, &mint.keypair(), pubkey, mint.last_id()) - .unwrap(); - assert_eq!(bank0.hash_internal_state(), bank1.hash_internal_state()); - } - #[test] - fn test_finality() { - let def_bank = Bank::default(); - assert_eq!(def_bank.finality(), std::usize::MAX); - def_bank.set_finality(90); - assert_eq!(def_bank.finality(), 90); - } - #[test] - fn test_interleaving_locks() { - let mint = Mint::new(3); - let bank = Bank::new(&mint); - let alice = Keypair::new(); - let bob = Keypair::new(); - - let tx1 = Transaction::system_new(&mint.keypair(), alice.pubkey(), 1, mint.last_id()); - let pay_alice = vec![tx1]; - - let locked_alice = bank.lock_accounts(&pay_alice); - let results_alice = - bank.execute_and_commit_transactions(&pay_alice, locked_alice, MAX_ENTRY_IDS); - assert_eq!(results_alice[0], Ok(())); - - // try executing an interleaved transfer twice - assert_eq!( - bank.transfer(1, &mint.keypair(), bob.pubkey(), mint.last_id()), - Err(BankError::AccountInUse) - ); - // the second time shoudl fail as well - // this verifies that `unlock_accounts` doesn't unlock `AccountInUse` accounts - assert_eq!( - bank.transfer(1, &mint.keypair(), bob.pubkey(), mint.last_id()), - Err(BankError::AccountInUse) - ); - - bank.unlock_accounts(&pay_alice, &results_alice); - - assert_matches!( - bank.transfer(2, &mint.keypair(), bob.pubkey(), mint.last_id()), - Ok(_) - ); - } - #[test] - fn test_bank_account_subscribe() { - let mint = Mint::new(100); - let bank = Bank::new(&mint); - let alice = Keypair::new(); - let bank_sub_id = Keypair::new().pubkey(); - let last_id = bank.last_id(); - let tx = Transaction::system_create( - &mint.keypair(), - alice.pubkey(), - last_id, - 1, - 16, - BudgetState::id(), - 0, - ); - bank.process_transaction(&tx).unwrap(); - - let (subscriber, _id_receiver, mut transport_receiver) = - Subscriber::new_test("accountNotification"); - let sub_id = SubscriptionId::Number(0 as u64); - let sink = subscriber.assign_id(sub_id.clone()).unwrap(); - bank.add_account_subscription(bank_sub_id, alice.pubkey(), sink); - - assert!( - bank.account_subscriptions - .write() - .unwrap() - .contains_key(&alice.pubkey()) - ); - - let account = bank.get_account(&alice.pubkey()).unwrap(); - bank.check_account_subscriptions(&alice.pubkey(), &account); - let string = transport_receiver.poll(); - assert!(string.is_ok()); - if let Async::Ready(Some(response)) = string.unwrap() { - let expected = format!(r#"{{"jsonrpc":"2.0","method":"accountNotification","params":{{"result":{{"executable":false,"loader":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"owner":[129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"tokens":1,"userdata":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},"subscription":0}}}}"#); - assert_eq!(expected, response); - } - - bank.remove_account_subscription(&bank_sub_id, &alice.pubkey()); - assert!( - !bank - .account_subscriptions - .write() - .unwrap() - .contains_key(&alice.pubkey()) - ); - } - #[test] - fn test_bank_signature_subscribe() { - let mint = Mint::new(100); - let bank = Bank::new(&mint); - let alice = Keypair::new(); - let bank_sub_id = Keypair::new().pubkey(); - let last_id = bank.last_id(); - let tx = Transaction::system_move(&mint.keypair(), alice.pubkey(), 20, last_id, 0); - let signature = tx.signatures[0]; - bank.process_transaction(&tx).unwrap(); - - let (subscriber, _id_receiver, mut transport_receiver) = - Subscriber::new_test("signatureNotification"); - let sub_id = SubscriptionId::Number(0 as u64); - let sink = subscriber.assign_id(sub_id.clone()).unwrap(); - bank.add_signature_subscription(bank_sub_id, signature, sink); - - assert!( - bank.signature_subscriptions - .write() - .unwrap() - .contains_key(&signature) - ); - - bank.check_signature_subscriptions(&signature, RpcSignatureStatus::Confirmed); - let string = transport_receiver.poll(); - assert!(string.is_ok()); - if let Async::Ready(Some(response)) = string.unwrap() { - let expected = format!(r#"{{"jsonrpc":"2.0","method":"signatureNotification","params":{{"result":"Confirmed","subscription":0}}}}"#); - assert_eq!(expected, response); - } - - bank.remove_signature_subscription(&bank_sub_id, &signature); - assert!( - !bank - .signature_subscriptions - .write() - .unwrap() - .contains_key(&signature) - ); - } - #[test] - fn test_first_err() { - assert_eq!(Bank::first_err(&[Ok(())]), Ok(())); - assert_eq!( - Bank::first_err(&[Ok(()), Err(BankError::DuplicateSignature)]), - Err(BankError::DuplicateSignature) - ); - assert_eq!( - Bank::first_err(&[ - Ok(()), - Err(BankError::DuplicateSignature), - Err(BankError::AccountInUse) - ]), - Err(BankError::DuplicateSignature) - ); - assert_eq!( - Bank::first_err(&[ - Ok(()), - Err(BankError::AccountInUse), - Err(BankError::DuplicateSignature) - ]), - Err(BankError::AccountInUse) - ); - assert_eq!( - Bank::first_err(&[ - Err(BankError::AccountInUse), - Ok(()), - Err(BankError::DuplicateSignature) - ]), - Err(BankError::AccountInUse) - ); - } - #[test] - fn test_par_process_entries_tick() { - let mint = Mint::new(1000); - let bank = Bank::new(&mint); - - // ensure bank can process a tick - let tick = next_entry(&mint.last_id(), 1, vec![]); - assert_eq!(bank.par_process_entries(&[tick.clone()]), Ok(())); - assert_eq!(bank.last_id(), tick.id); - } - #[test] - fn test_par_process_entries_2_entries_collision() { - let mint = Mint::new(1000); - let bank = Bank::new(&mint); - let keypair1 = Keypair::new(); - let keypair2 = Keypair::new(); - - let last_id = bank.last_id(); - - // ensure bank can process 2 entries that have a common account and no tick is registered - let tx = Transaction::system_new(&mint.keypair(), keypair1.pubkey(), 2, bank.last_id()); - let entry_1 = next_entry(&last_id, 1, vec![tx]); - let tx = Transaction::system_new(&mint.keypair(), keypair2.pubkey(), 2, bank.last_id()); - let entry_2 = next_entry(&entry_1.id, 1, vec![tx]); - assert_eq!(bank.par_process_entries(&[entry_1, entry_2]), Ok(())); - assert_eq!(bank.get_balance(&keypair1.pubkey()), 2); - assert_eq!(bank.get_balance(&keypair2.pubkey()), 2); - assert_eq!(bank.last_id(), last_id); - } - #[test] - fn test_par_process_entries_2_entries_par() { - let mint = Mint::new(1000); - let bank = Bank::new(&mint); - let keypair1 = Keypair::new(); - let keypair2 = Keypair::new(); - let keypair3 = Keypair::new(); - let keypair4 = Keypair::new(); - - //load accounts - let tx = Transaction::system_new(&mint.keypair(), keypair1.pubkey(), 1, bank.last_id()); - assert_eq!(bank.process_transaction(&tx), Ok(())); - let tx = Transaction::system_new(&mint.keypair(), keypair2.pubkey(), 1, bank.last_id()); - assert_eq!(bank.process_transaction(&tx), Ok(())); - - // ensure bank can process 2 entries that do not have a common account and no tick is registered - let last_id = bank.last_id(); - let tx = Transaction::system_new(&keypair1, keypair3.pubkey(), 1, bank.last_id()); - let entry_1 = next_entry(&last_id, 1, vec![tx]); - let tx = Transaction::system_new(&keypair2, keypair4.pubkey(), 1, bank.last_id()); - let entry_2 = next_entry(&entry_1.id, 1, vec![tx]); - assert_eq!(bank.par_process_entries(&[entry_1, entry_2]), Ok(())); - assert_eq!(bank.get_balance(&keypair3.pubkey()), 1); - assert_eq!(bank.get_balance(&keypair4.pubkey()), 1); - assert_eq!(bank.last_id(), last_id); - } - #[test] - fn test_par_process_entries_2_entries_tick() { - let mint = Mint::new(1000); - let bank = Bank::new(&mint); - let keypair1 = Keypair::new(); - let keypair2 = Keypair::new(); - let keypair3 = Keypair::new(); - let keypair4 = Keypair::new(); - - //load accounts - let tx = Transaction::system_new(&mint.keypair(), keypair1.pubkey(), 1, bank.last_id()); - assert_eq!(bank.process_transaction(&tx), Ok(())); - let tx = Transaction::system_new(&mint.keypair(), keypair2.pubkey(), 1, bank.last_id()); - assert_eq!(bank.process_transaction(&tx), Ok(())); - - let last_id = bank.last_id(); - - // ensure bank can process 2 entries that do not have a common account and tick is registered - let tx = Transaction::system_new(&keypair2, keypair3.pubkey(), 1, bank.last_id()); - let entry_1 = next_entry(&last_id, 1, vec![tx]); - let new_tick = next_entry(&entry_1.id, 1, vec![]); - let tx = Transaction::system_new(&keypair1, keypair4.pubkey(), 1, new_tick.id); - let entry_2 = next_entry(&new_tick.id, 1, vec![tx]); - assert_eq!( - bank.par_process_entries(&[entry_1.clone(), new_tick.clone(), entry_2]), - Ok(()) - ); - assert_eq!(bank.get_balance(&keypair3.pubkey()), 1); - assert_eq!(bank.get_balance(&keypair4.pubkey()), 1); - assert_eq!(bank.last_id(), new_tick.id); - // ensure that errors are returned - assert_eq!( - bank.par_process_entries(&[entry_1]), - Err(BankError::AccountNotFound) - ); - } - - #[test] - fn test_program_ids() { - let system = Pubkey::new(&[ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, - ]); - let native = Pubkey::new(&[ - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, - ]); - let bpf = Pubkey::new(&[ - 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, - ]); - let budget = Pubkey::new(&[ - 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, - ]); - let storage = Pubkey::new(&[ - 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, - ]); - let token = Pubkey::new(&[ - 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, - ]); - let vote = Pubkey::new(&[ - 132, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, - ]); - - assert_eq!(SystemProgram::id(), system); - assert_eq!(native_loader::id(), native); - assert_eq!(bpf_loader::id(), bpf); - assert_eq!(BudgetState::id(), budget); - assert_eq!(StorageProgram::id(), storage); - assert_eq!(token_program::id(), token); - assert_eq!(VoteProgram::id(), vote); - } - - #[test] - fn test_program_id_uniqueness() { - let mut unique = HashSet::new(); - let ids = vec![ - SystemProgram::id(), - native_loader::id(), - bpf_loader::id(), - BudgetState::id(), - StorageProgram::id(), - token_program::id(), - VoteProgram::id(), - ]; - assert!(ids.into_iter().all(move |id| unique.insert(id))); - } - - #[test] - fn test_bank_purge() { - let alice = Mint::new(10_000); - let bank = Bank::new(&alice); - let bob = Keypair::new(); - let charlie = Keypair::new(); - - // bob should have 500 - bank.transfer(500, &alice.keypair(), bob.pubkey(), alice.last_id()) - .unwrap(); - assert_eq!(bank.get_balance(&bob.pubkey()), 500); - - bank.transfer(500, &alice.keypair(), charlie.pubkey(), alice.last_id()) - .unwrap(); - assert_eq!(bank.get_balance(&charlie.pubkey()), 500); - - bank.checkpoint(); - bank.checkpoint(); - assert_eq!(bank.checkpoint_depth(), 2); - assert_eq!(bank.get_balance(&bob.pubkey()), 500); - assert_eq!(bank.get_balance(&alice.pubkey()), 9_000); - assert_eq!(bank.get_balance(&charlie.pubkey()), 500); - assert_eq!(bank.transaction_count(), 2); - - // transfer money back, so bob has zero - bank.transfer(500, &bob, alice.keypair().pubkey(), alice.last_id()) - .unwrap(); - // this has to be stored as zero in the top accounts hashmap ;) - assert!(bank.accounts.read().unwrap().load(&bob.pubkey()).is_some()); - assert_eq!(bank.get_balance(&bob.pubkey()), 0); - // double-checks - assert_eq!(bank.get_balance(&alice.pubkey()), 9_500); - assert_eq!(bank.get_balance(&charlie.pubkey()), 500); - assert_eq!(bank.transaction_count(), 3); - bank.purge(1); - - assert_eq!(bank.get_balance(&bob.pubkey()), 0); - // double-checks - assert_eq!(bank.get_balance(&alice.pubkey()), 9_500); - assert_eq!(bank.get_balance(&charlie.pubkey()), 500); - assert_eq!(bank.transaction_count(), 3); - assert_eq!(bank.checkpoint_depth(), 1); - - bank.purge(0); - - // bob should still have 0, alice should have 10_000 - assert_eq!(bank.get_balance(&bob.pubkey()), 0); - assert!(bank.accounts.read().unwrap().load(&bob.pubkey()).is_none()); - // double-checks - assert_eq!(bank.get_balance(&alice.pubkey()), 9_500); - assert_eq!(bank.get_balance(&charlie.pubkey()), 500); - assert_eq!(bank.transaction_count(), 3); - assert_eq!(bank.checkpoint_depth(), 0); - } - - #[test] - fn test_bank_checkpoint_rollback() { - let alice = Mint::new(10_000); - let bank = Bank::new(&alice); - let bob = Keypair::new(); - let charlie = Keypair::new(); - - // bob should have 500 - bank.transfer(500, &alice.keypair(), bob.pubkey(), alice.last_id()) - .unwrap(); - assert_eq!(bank.get_balance(&bob.pubkey()), 500); - bank.transfer(500, &alice.keypair(), charlie.pubkey(), alice.last_id()) - .unwrap(); - assert_eq!(bank.get_balance(&charlie.pubkey()), 500); - assert_eq!(bank.checkpoint_depth(), 0); - - bank.checkpoint(); - bank.checkpoint(); - assert_eq!(bank.checkpoint_depth(), 2); - assert_eq!(bank.get_balance(&bob.pubkey()), 500); - assert_eq!(bank.get_balance(&charlie.pubkey()), 500); - assert_eq!(bank.transaction_count(), 2); - - // transfer money back, so bob has zero - bank.transfer(500, &bob, alice.keypair().pubkey(), alice.last_id()) - .unwrap(); - // this has to be stored as zero in the top accounts hashmap ;) - assert_eq!(bank.get_balance(&bob.pubkey()), 0); - assert_eq!(bank.get_balance(&charlie.pubkey()), 500); - assert_eq!(bank.transaction_count(), 3); - bank.rollback(); - - // bob should have 500 again - assert_eq!(bank.get_balance(&bob.pubkey()), 500); - assert_eq!(bank.get_balance(&charlie.pubkey()), 500); - assert_eq!(bank.transaction_count(), 2); - assert_eq!(bank.checkpoint_depth(), 1); - - let signature = Signature::default(); - for i in 0..MAX_ENTRY_IDS + 1 { - let last_id = hash(&serialize(&i).unwrap()); // Unique hash - bank.register_tick(&last_id); - } - assert_eq!(bank.tick_height(), MAX_ENTRY_IDS as u64 + 2); - assert_eq!( - bank.reserve_signature_with_last_id_test(&signature, &alice.last_id()), - Err(BankError::LastIdNotFound) - ); - bank.rollback(); - assert_eq!(bank.tick_height(), 1); - assert_eq!( - bank.reserve_signature_with_last_id_test(&signature, &alice.last_id()), - Ok(()) - ); - bank.checkpoint(); - assert_eq!( - bank.reserve_signature_with_last_id_test(&signature, &alice.last_id()), - Err(BankError::DuplicateSignature) - ); - } - - #[test] - #[should_panic] - fn test_bank_rollback_panic() { - let alice = Mint::new(10_000); - let bank = Bank::new(&alice); - bank.rollback(); - } - -} diff --git a/book/banking_stage.rs b/book/banking_stage.rs deleted file mode 100644 index b96b38c6297b85..00000000000000 --- a/book/banking_stage.rs +++ /dev/null @@ -1,449 +0,0 @@ -//! The `banking_stage` processes Transaction messages. It is intended to be used -//! to contruct a software pipeline. The stage uses all available CPU cores and -//! can do its processing in parallel with signature verification on the GPU. - -use bank::Bank; -use bincode::deserialize; -use compute_leader_finality_service::ComputeLeaderFinalityService; -use counter::Counter; -use entry::Entry; -use log::Level; -use packet::Packets; -use poh_recorder::{PohRecorder, PohRecorderError}; -use poh_service::{Config, PohService}; -use result::{Error, Result}; -use service::Service; -use sigverify_stage::VerifiedPackets; -use solana_sdk::hash::Hash; -use solana_sdk::timing; -use std::net::SocketAddr; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::mpsc::{channel, Receiver, RecvTimeoutError}; -use std::sync::{Arc, Mutex}; -use std::thread::{self, Builder, JoinHandle}; -use std::time::Duration; -use std::time::Instant; -use transaction::Transaction; - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum BankingStageReturnType { - LeaderRotation, - ChannelDisconnected, -} - -// number of threads is 1 until mt bank is ready -pub const NUM_THREADS: usize = 10; - -/// Stores the stage's thread handle and output receiver. -pub struct BankingStage { - /// Handle to the stage's thread. - bank_thread_hdls: Vec>>, - poh_service: PohService, - compute_finality_service: ComputeLeaderFinalityService, -} - -impl BankingStage { - /// Create the stage using `bank`. Exit when `verified_receiver` is dropped. - pub fn new( - bank: &Arc, - verified_receiver: Receiver, - config: Config, - last_entry_id: &Hash, - max_tick_height: Option, - ) -> (Self, Receiver>) { - let (entry_sender, entry_receiver) = channel(); - let shared_verified_receiver = Arc::new(Mutex::new(verified_receiver)); - let poh_recorder = - PohRecorder::new(bank.clone(), entry_sender, *last_entry_id, max_tick_height); - - // Single thread to generate entries from many banks. - // This thread talks to poh_service and broadcasts the entries once they have been recorded. - // Once an entry has been recorded, its last_id is registered with the bank. - let poh_service = PohService::new(poh_recorder.clone(), config); - - // Single thread to compute finality - let compute_finality_service = - ComputeLeaderFinalityService::new(bank.clone(), poh_service.poh_exit.clone()); - - // Many banks that process transactions in parallel. - let bank_thread_hdls: Vec>> = (0..NUM_THREADS) - .map(|_| { - let thread_bank = bank.clone(); - let thread_verified_receiver = shared_verified_receiver.clone(); - let thread_poh_recorder = poh_recorder.clone(); - let thread_banking_exit = poh_service.poh_exit.clone(); - Builder::new() - .name("solana-banking-stage-tx".to_string()) - .spawn(move || { - let return_result = loop { - if let Err(e) = Self::process_packets( - &thread_bank, - &thread_verified_receiver, - &thread_poh_recorder, - ) { - debug!("got error {:?}", e); - match e { - Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), - Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => { - break Some(BankingStageReturnType::ChannelDisconnected); - } - Error::RecvError(_) => { - break Some(BankingStageReturnType::ChannelDisconnected); - } - Error::SendError => { - break Some(BankingStageReturnType::ChannelDisconnected); - } - Error::PohRecorderError(PohRecorderError::MaxHeightReached) => { - break Some(BankingStageReturnType::LeaderRotation); - } - _ => error!("solana-banking-stage-tx {:?}", e), - } - } - if thread_banking_exit.load(Ordering::Relaxed) { - break None; - } - }; - thread_banking_exit.store(true, Ordering::Relaxed); - return_result - }).unwrap() - }).collect(); - - ( - BankingStage { - bank_thread_hdls, - poh_service, - compute_finality_service, - }, - entry_receiver, - ) - } - - /// Convert the transactions from a blob of binary data to a vector of transactions and - /// an unused `SocketAddr` that could be used to send a response. - fn deserialize_transactions(p: &Packets) -> Vec> { - p.packets - .iter() - .map(|x| { - deserialize(&x.data[0..x.meta.size]) - .map(|req| (req, x.meta.addr())) - .ok() - }).collect() - } - - fn process_transactions( - bank: &Arc, - transactions: &[Transaction], - poh: &PohRecorder, - ) -> Result<()> { - debug!("transactions: {}", transactions.len()); - let mut chunk_start = 0; - while chunk_start != transactions.len() { - let chunk_end = chunk_start + Entry::num_will_fit(&transactions[chunk_start..]); - - bank.process_and_record_transactions(&transactions[chunk_start..chunk_end], poh)?; - - chunk_start = chunk_end; - } - debug!("done process_transactions"); - Ok(()) - } - - /// Process the incoming packets and send output `Signal` messages to `signal_sender`. - /// Discard packets via `packet_recycler`. - pub fn process_packets( - bank: &Arc, - verified_receiver: &Arc>>, - poh: &PohRecorder, - ) -> Result<()> { - let recv_start = Instant::now(); - let mms = verified_receiver - .lock() - .unwrap() - .recv_timeout(Duration::from_millis(100))?; - let mut reqs_len = 0; - let mms_len = mms.len(); - info!( - "@{:?} process start stalled for: {:?}ms batches: {}", - timing::timestamp(), - timing::duration_as_ms(&recv_start.elapsed()), - mms.len(), - ); - inc_new_counter_info!("banking_stage-entries_received", mms_len); - let count = mms.iter().map(|x| x.1.len()).sum(); - let proc_start = Instant::now(); - let mut new_tx_count = 0; - for (msgs, vers) in mms { - let transactions = Self::deserialize_transactions(&msgs.read().unwrap()); - reqs_len += transactions.len(); - - debug!("transactions received {}", transactions.len()); - - let transactions: Vec<_> = transactions - .into_iter() - .zip(vers) - .filter_map(|(tx, ver)| match tx { - None => None, - Some((tx, _addr)) => { - if tx.verify_refs() && ver != 0 { - Some(tx) - } else { - None - } - } - }).collect(); - debug!("verified transactions {}", transactions.len()); - Self::process_transactions(bank, &transactions, poh)?; - new_tx_count += transactions.len(); - } - - inc_new_counter_info!( - "banking_stage-time_ms", - timing::duration_as_ms(&proc_start.elapsed()) as usize - ); - let total_time_s = timing::duration_as_s(&proc_start.elapsed()); - let total_time_ms = timing::duration_as_ms(&proc_start.elapsed()); - info!( - "@{:?} done processing transaction batches: {} time: {:?}ms reqs: {} reqs/s: {}", - timing::timestamp(), - mms_len, - total_time_ms, - reqs_len, - (reqs_len as f32) / (total_time_s) - ); - inc_new_counter_info!("banking_stage-process_packets", count); - inc_new_counter_info!("banking_stage-process_transactions", new_tx_count); - Ok(()) - } -} - -impl Service for BankingStage { - type JoinReturnType = Option; - - fn join(self) -> thread::Result> { - let mut return_value = None; - - for bank_thread_hdl in self.bank_thread_hdls { - let thread_return_value = bank_thread_hdl.join()?; - if thread_return_value.is_some() { - return_value = thread_return_value; - } - } - - self.compute_finality_service.join()?; - - let poh_return_value = self.poh_service.join()?; - match poh_return_value { - Ok(_) => (), - Err(Error::PohRecorderError(PohRecorderError::MaxHeightReached)) => { - return_value = Some(BankingStageReturnType::LeaderRotation); - } - Err(Error::SendError) => { - return_value = Some(BankingStageReturnType::ChannelDisconnected); - } - Err(_) => (), - } - - Ok(return_value) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bank::Bank; - use banking_stage::BankingStageReturnType; - use ledger::Block; - use mint::Mint; - use packet::to_packets; - use signature::{Keypair, KeypairUtil}; - use std::thread::sleep; - use system_transaction::SystemTransaction; - use transaction::Transaction; - - #[test] - fn test_banking_stage_shutdown1() { - let bank = Arc::new(Bank::new(&Mint::new(2))); - let (verified_sender, verified_receiver) = channel(); - let (banking_stage, _entry_receiver) = BankingStage::new( - &bank, - verified_receiver, - Default::default(), - &bank.last_id(), - None, - ); - drop(verified_sender); - assert_eq!( - banking_stage.join().unwrap(), - Some(BankingStageReturnType::ChannelDisconnected) - ); - } - - #[test] - fn test_banking_stage_shutdown2() { - let bank = Arc::new(Bank::new(&Mint::new(2))); - let (_verified_sender, verified_receiver) = channel(); - let (banking_stage, entry_receiver) = BankingStage::new( - &bank, - verified_receiver, - Default::default(), - &bank.last_id(), - None, - ); - drop(entry_receiver); - assert_eq!( - banking_stage.join().unwrap(), - Some(BankingStageReturnType::ChannelDisconnected) - ); - } - - #[test] - fn test_banking_stage_tick() { - let bank = Arc::new(Bank::new(&Mint::new(2))); - let start_hash = bank.last_id(); - let (verified_sender, verified_receiver) = channel(); - let (banking_stage, entry_receiver) = BankingStage::new( - &bank, - verified_receiver, - Config::Sleep(Duration::from_millis(1)), - &bank.last_id(), - None, - ); - sleep(Duration::from_millis(500)); - drop(verified_sender); - - let entries: Vec<_> = entry_receiver.iter().flat_map(|x| x).collect(); - assert!(entries.len() != 0); - assert!(entries.verify(&start_hash)); - assert_eq!(entries[entries.len() - 1].id, bank.last_id()); - assert_eq!( - banking_stage.join().unwrap(), - Some(BankingStageReturnType::ChannelDisconnected) - ); - } - - #[test] - fn test_banking_stage_entries_only() { - let mint = Mint::new(2); - let bank = Arc::new(Bank::new(&mint)); - let start_hash = bank.last_id(); - let (verified_sender, verified_receiver) = channel(); - let (banking_stage, entry_receiver) = BankingStage::new( - &bank, - verified_receiver, - Default::default(), - &bank.last_id(), - None, - ); - - // good tx - let keypair = mint.keypair(); - let tx = Transaction::system_new(&keypair, keypair.pubkey(), 1, start_hash); - - // good tx, but no verify - let tx_no_ver = Transaction::system_new(&keypair, keypair.pubkey(), 1, start_hash); - - // bad tx, AccountNotFound - let keypair = Keypair::new(); - let tx_anf = Transaction::system_new(&keypair, keypair.pubkey(), 1, start_hash); - - // send 'em over - let packets = to_packets(&[tx, tx_no_ver, tx_anf]); - - // glad they all fit - assert_eq!(packets.len(), 1); - verified_sender // tx, no_ver, anf - .send(vec![(packets[0].clone(), vec![1u8, 0u8, 1u8])]) - .unwrap(); - - drop(verified_sender); - - //receive entries + ticks - let entries: Vec<_> = entry_receiver.iter().map(|x| x).collect(); - assert!(entries.len() >= 1); - - let mut last_id = start_hash; - entries.iter().for_each(|entries| { - assert_eq!(entries.len(), 1); - assert!(entries.verify(&last_id)); - last_id = entries.last().unwrap().id; - }); - drop(entry_receiver); - assert_eq!( - banking_stage.join().unwrap(), - Some(BankingStageReturnType::ChannelDisconnected) - ); - } - #[test] - fn test_banking_stage_entryfication() { - // In this attack we'll demonstrate that a verifier can interpret the ledger - // differently if either the server doesn't signal the ledger to add an - // Entry OR if the verifier tries to parallelize across multiple Entries. - let mint = Mint::new(2); - let bank = Arc::new(Bank::new(&mint)); - let (verified_sender, verified_receiver) = channel(); - let (banking_stage, entry_receiver) = BankingStage::new( - &bank, - verified_receiver, - Default::default(), - &bank.last_id(), - None, - ); - - // Process a batch that includes a transaction that receives two tokens. - let alice = Keypair::new(); - let tx = Transaction::system_new(&mint.keypair(), alice.pubkey(), 2, mint.last_id()); - - let packets = to_packets(&[tx]); - verified_sender - .send(vec![(packets[0].clone(), vec![1u8])]) - .unwrap(); - - // Process a second batch that spends one of those tokens. - let tx = Transaction::system_new(&alice, mint.pubkey(), 1, mint.last_id()); - let packets = to_packets(&[tx]); - verified_sender - .send(vec![(packets[0].clone(), vec![1u8])]) - .unwrap(); - drop(verified_sender); - assert_eq!( - banking_stage.join().unwrap(), - Some(BankingStageReturnType::ChannelDisconnected) - ); - - // Collect the ledger and feed it to a new bank. - let entries: Vec<_> = entry_receiver.iter().flat_map(|x| x).collect(); - // same assertion as running through the bank, really... - assert!(entries.len() >= 2); - - // Assert the user holds one token, not two. If the stage only outputs one - // entry, then the second transaction will be rejected, because it drives - // the account balance below zero before the credit is added. - let bank = Bank::new(&mint); - for entry in entries { - bank.process_transactions(&entry.transactions) - .iter() - .for_each(|x| assert_eq!(*x, Ok(()))); - } - assert_eq!(bank.get_balance(&alice.pubkey()), 1); - } - - // Test that when the max_tick_height is reached, the banking stage exits - // with reason BankingStageReturnType::LeaderRotation - #[test] - fn test_max_tick_height_shutdown() { - let bank = Arc::new(Bank::new(&Mint::new(2))); - let (_verified_sender_, verified_receiver) = channel(); - let max_tick_height = 10; - let (banking_stage, _entry_receiver) = BankingStage::new( - &bank, - verified_receiver, - Default::default(), - &bank.last_id(), - Some(max_tick_height), - ); - assert_eq!( - banking_stage.join().unwrap(), - Some(BankingStageReturnType::LeaderRotation) - ); - } -} diff --git a/book/bin/bench-streamer.rs b/book/bin/bench-streamer.rs deleted file mode 100644 index 149cc97d5084ea..00000000000000 --- a/book/bin/bench-streamer.rs +++ /dev/null @@ -1,124 +0,0 @@ -extern crate clap; -extern crate solana; - -use clap::{App, Arg}; -use solana::netutil::bind_to; -use solana::packet::{Packet, SharedPackets, BLOB_SIZE, PACKET_DATA_SIZE}; -use solana::result::Result; -use solana::streamer::{receiver, PacketReceiver}; -use std::cmp::max; -use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket}; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::sync::mpsc::channel; -use std::sync::Arc; -use std::thread::sleep; -use std::thread::{spawn, JoinHandle}; -use std::time::Duration; -use std::time::SystemTime; - -fn producer(addr: &SocketAddr, exit: Arc) -> JoinHandle<()> { - let send = UdpSocket::bind("0.0.0.0:0").unwrap(); - let msgs = SharedPackets::default(); - let msgs_ = msgs.clone(); - msgs.write().unwrap().packets.resize(10, Packet::default()); - for w in &mut msgs.write().unwrap().packets { - w.meta.size = PACKET_DATA_SIZE; - w.meta.set_addr(&addr); - } - spawn(move || loop { - if exit.load(Ordering::Relaxed) { - return; - } - let mut num = 0; - for p in &msgs_.read().unwrap().packets { - let a = p.meta.addr(); - assert!(p.meta.size < BLOB_SIZE); - send.send_to(&p.data[..p.meta.size], &a).unwrap(); - num += 1; - } - assert_eq!(num, 10); - }) -} - -fn sink(exit: Arc, rvs: Arc, r: PacketReceiver) -> JoinHandle<()> { - spawn(move || loop { - if exit.load(Ordering::Relaxed) { - return; - } - let timer = Duration::new(1, 0); - if let Ok(msgs) = r.recv_timeout(timer) { - rvs.fetch_add(msgs.read().unwrap().packets.len(), Ordering::Relaxed); - } - }) -} - -fn main() -> Result<()> { - let mut num_sockets = 1usize; - - let matches = App::new("solana-bench-streamer") - .arg( - Arg::with_name("num-recv-sockets") - .long("num-recv-sockets") - .value_name("NUM") - .takes_value(true) - .help("Use NUM receive sockets"), - ).get_matches(); - - if let Some(n) = matches.value_of("num-recv-sockets") { - num_sockets = max(num_sockets, n.to_string().parse().expect("integer")); - } - - let mut port = 0; - let mut addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0); - - let exit = Arc::new(AtomicBool::new(false)); - - let mut read_channels = Vec::new(); - let mut read_threads = Vec::new(); - for _ in 0..num_sockets { - let read = bind_to(port, false).unwrap(); - read.set_read_timeout(Some(Duration::new(1, 0))).unwrap(); - - addr = read.local_addr().unwrap(); - port = addr.port(); - - let (s_reader, r_reader) = channel(); - read_channels.push(r_reader); - read_threads.push(receiver( - Arc::new(read), - exit.clone(), - s_reader, - "bench-streamer", - )); - } - - let t_producer1 = producer(&addr, exit.clone()); - let t_producer2 = producer(&addr, exit.clone()); - let t_producer3 = producer(&addr, exit.clone()); - - let rvs = Arc::new(AtomicUsize::new(0)); - let sink_threads: Vec<_> = read_channels - .into_iter() - .map(|r_reader| sink(exit.clone(), rvs.clone(), r_reader)) - .collect(); - let start = SystemTime::now(); - let start_val = rvs.load(Ordering::Relaxed); - sleep(Duration::new(5, 0)); - let elapsed = start.elapsed().unwrap(); - let end_val = rvs.load(Ordering::Relaxed); - let time = elapsed.as_secs() * 10_000_000_000 + u64::from(elapsed.subsec_nanos()); - let ftime = (time as f64) / 10_000_000_000_f64; - let fcount = (end_val - start_val) as f64; - println!("performance: {:?}", fcount / ftime); - exit.store(true, Ordering::Relaxed); - for t_reader in read_threads { - t_reader.join()?; - } - t_producer1.join()?; - t_producer2.join()?; - t_producer3.join()?; - for t_sink in sink_threads { - t_sink.join()?; - } - Ok(()) -} diff --git a/book/bin/bench-tps.rs b/book/bin/bench-tps.rs deleted file mode 100644 index 7c5e5a70aa2bbc..00000000000000 --- a/book/bin/bench-tps.rs +++ /dev/null @@ -1,873 +0,0 @@ -extern crate bincode; -#[macro_use] -extern crate clap; -extern crate rand; -extern crate rayon; -#[macro_use] -extern crate log; -extern crate serde_json; -#[macro_use] -extern crate solana; -extern crate solana_drone; -extern crate solana_metrics; -extern crate solana_sdk; - -use clap::{App, Arg}; - -use rand::{thread_rng, Rng}; -use rayon::prelude::*; -use solana::client::mk_client; -use solana::cluster_info::{ClusterInfo, NodeInfo}; -use solana::logger; -use solana::ncp::Ncp; -use solana::service::Service; -use solana::signature::{read_keypair, GenKeys, Keypair, KeypairUtil}; -use solana::system_transaction::SystemTransaction; -use solana::thin_client::{poll_gossip_for_leader, ThinClient}; -use solana::transaction::Transaction; -use solana::window::default_window; -use solana_drone::drone::{request_airdrop_transaction, DRONE_PORT}; -use solana_metrics::influxdb; -use solana_sdk::hash::Hash; -use solana_sdk::timing::timestamp; -use solana_sdk::timing::{duration_as_ms, duration_as_s}; -use std::cmp; -use std::collections::VecDeque; -use std::net::SocketAddr; -use std::process::exit; -use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering}; -use std::sync::{Arc, RwLock}; -use std::thread::sleep; -use std::thread::Builder; -use std::time::Duration; -use std::time::Instant; - -pub struct NodeStats { - pub tps: f64, // Maximum TPS reported by this node - pub tx: u64, // Total transactions reported by this node -} - -fn metrics_submit_token_balance(token_balance: u64) { - println!("Token balance: {}", token_balance); - solana_metrics::submit( - influxdb::Point::new("bench-tps") - .add_tag("op", influxdb::Value::String("token_balance".to_string())) - .add_field("balance", influxdb::Value::Integer(token_balance as i64)) - .to_owned(), - ); -} - -fn sample_tx_count( - exit_signal: &Arc, - maxes: &Arc>>, - first_tx_count: u64, - v: &NodeInfo, - sample_period: u64, -) { - let mut client = mk_client(&v); - let mut now = Instant::now(); - let mut initial_tx_count = client.transaction_count(); - let mut max_tps = 0.0; - let mut total; - - let log_prefix = format!("{:21}:", v.tpu.to_string()); - - loop { - let tx_count = client.transaction_count(); - assert!( - tx_count >= initial_tx_count, - "expected tx_count({}) >= initial_tx_count({})", - tx_count, - initial_tx_count - ); - let duration = now.elapsed(); - now = Instant::now(); - let sample = tx_count - initial_tx_count; - initial_tx_count = tx_count; - - let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos()); - let tps = (sample * 1_000_000_000) as f64 / ns as f64; - if tps > max_tps { - max_tps = tps; - } - if tx_count > first_tx_count { - total = tx_count - first_tx_count; - } else { - total = 0; - } - println!( - "{} {:9.2} TPS, Transactions: {:6}, Total transactions: {}", - log_prefix, tps, sample, total - ); - sleep(Duration::new(sample_period, 0)); - - if exit_signal.load(Ordering::Relaxed) { - println!("{} Exiting validator thread", log_prefix); - let stats = NodeStats { - tps: max_tps, - tx: total, - }; - maxes.write().unwrap().push((v.tpu, stats)); - break; - } - } -} - -/// Send loopback payment of 0 tokens and confirm the network processed it -fn send_barrier_transaction(barrier_client: &mut ThinClient, last_id: &mut Hash, id: &Keypair) { - let transfer_start = Instant::now(); - - let mut poll_count = 0; - loop { - if poll_count > 0 && poll_count % 8 == 0 { - println!( - "polling for barrier transaction confirmation, attempt {}", - poll_count - ); - } - - *last_id = barrier_client.get_last_id(); - let signature = barrier_client - .transfer(0, &id, id.pubkey(), last_id) - .expect("Unable to send barrier transaction"); - - let confirmatiom = barrier_client.poll_for_signature(&signature); - let duration_ms = duration_as_ms(&transfer_start.elapsed()); - if confirmatiom.is_ok() { - println!("barrier transaction confirmed in {} ms", duration_ms); - - solana_metrics::submit( - influxdb::Point::new("bench-tps") - .add_tag( - "op", - influxdb::Value::String("send_barrier_transaction".to_string()), - ).add_field("poll_count", influxdb::Value::Integer(poll_count)) - .add_field("duration", influxdb::Value::Integer(duration_ms as i64)) - .to_owned(), - ); - - // Sanity check that the client balance is still 1 - let balance = barrier_client - .poll_balance_with_timeout( - &id.pubkey(), - &Duration::from_millis(100), - &Duration::from_secs(10), - ).expect("Failed to get balance"); - if balance != 1 { - panic!("Expected an account balance of 1 (balance: {}", balance); - } - break; - } - - // Timeout after 3 minutes. When running a CPU-only leader+validator+drone+bench-tps on a dev - // machine, some batches of transactions can take upwards of 1 minute... - if duration_ms > 1000 * 60 * 3 { - println!("Error: Couldn't confirm barrier transaction!"); - exit(1); - } - - let new_last_id = barrier_client.get_last_id(); - if new_last_id == *last_id { - if poll_count > 0 && poll_count % 8 == 0 { - println!("last_id is not advancing, still at {:?}", *last_id); - } - } else { - *last_id = new_last_id; - } - - poll_count += 1; - } -} - -type SharedTransactions = Arc>>>; -fn generate_txs( - shared_txs: &SharedTransactions, - source: &[Keypair], - dest: &[Keypair], - threads: usize, - reclaim: bool, - leader: &NodeInfo, -) { - let mut client = mk_client(leader); - let last_id = client.get_last_id(); - info!("last_id: {} {:?}", last_id, Instant::now()); - let tx_count = source.len(); - println!("Signing transactions... {} (reclaim={})", tx_count, reclaim); - let signing_start = Instant::now(); - - let pairs: Vec<_> = if !reclaim { - source.iter().zip(dest.iter()).collect() - } else { - dest.iter().zip(source.iter()).collect() - }; - let transactions: Vec<_> = pairs - .par_iter() - .map(|(id, keypair)| { - ( - Transaction::system_new(id, keypair.pubkey(), 1, last_id), - timestamp(), - ) - }).collect(); - - let duration = signing_start.elapsed(); - let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos()); - let bsps = (tx_count) as f64 / ns as f64; - let nsps = ns as f64 / (tx_count) as f64; - println!( - "Done. {:.2} thousand signatures per second, {:.2} us per signature, {} ms total time, {}", - bsps * 1_000_000_f64, - nsps / 1_000_f64, - duration_as_ms(&duration), - last_id, - ); - solana_metrics::submit( - influxdb::Point::new("bench-tps") - .add_tag("op", influxdb::Value::String("generate_txs".to_string())) - .add_field( - "duration", - influxdb::Value::Integer(duration_as_ms(&duration) as i64), - ).to_owned(), - ); - - let sz = transactions.len() / threads; - let chunks: Vec<_> = transactions.chunks(sz).collect(); - { - let mut shared_txs_wl = shared_txs.write().unwrap(); - for chunk in chunks { - shared_txs_wl.push_back(chunk.to_vec()); - } - } -} - -fn do_tx_transfers( - exit_signal: &Arc, - shared_txs: &SharedTransactions, - leader: &NodeInfo, - shared_tx_thread_count: &Arc, - total_tx_sent_count: &Arc, -) { - let client = mk_client(&leader); - loop { - let txs; - { - let mut shared_txs_wl = shared_txs.write().unwrap(); - txs = shared_txs_wl.pop_front(); - } - if let Some(txs0) = txs { - shared_tx_thread_count.fetch_add(1, Ordering::Relaxed); - println!( - "Transferring 1 unit {} times... to {}", - txs0.len(), - leader.tpu - ); - let tx_len = txs0.len(); - let transfer_start = Instant::now(); - for tx in txs0 { - let now = timestamp(); - if now > tx.1 && now - tx.1 > 1000 * 30 { - continue; - } - client.transfer_signed(&tx.0).unwrap(); - } - shared_tx_thread_count.fetch_add(-1, Ordering::Relaxed); - total_tx_sent_count.fetch_add(tx_len, Ordering::Relaxed); - println!( - "Tx send done. {} ms {} tps", - duration_as_ms(&transfer_start.elapsed()), - tx_len as f32 / duration_as_s(&transfer_start.elapsed()), - ); - solana_metrics::submit( - influxdb::Point::new("bench-tps") - .add_tag("op", influxdb::Value::String("do_tx_transfers".to_string())) - .add_field( - "duration", - influxdb::Value::Integer(duration_as_ms(&transfer_start.elapsed()) as i64), - ).add_field("count", influxdb::Value::Integer(tx_len as i64)) - .to_owned(), - ); - } - if exit_signal.load(Ordering::Relaxed) { - break; - } - } -} - -const MAX_SPENDS_PER_TX: usize = 4; -fn verify_transfer(client: &mut ThinClient, tx: &Transaction) -> bool { - if client.poll_for_signature(&tx.signatures[0]).is_err() { - println!("no signature"); - return false; - } - for a in &tx.account_keys[1..] { - if client.poll_get_balance(a).unwrap_or(0) == 0 { - println!( - "no balance {} source bal: {} {:?}", - a, - client.poll_get_balance(&tx.account_keys[0]).unwrap_or(0), - tx - ); - return false; - } - } - true -} -/// fund the dests keys by spending all of the source keys into MAX_SPENDS_PER_TX -/// on every iteration. This allows us to replay the transfers because the source is either empty, -/// or full -fn fund_keys(client: &mut ThinClient, source: &Keypair, dests: &[Keypair], tokens: u64) { - let total = tokens * dests.len() as u64; - let mut funded: Vec<(&Keypair, u64)> = vec![(source, total)]; - let mut notfunded: Vec<&Keypair> = dests.iter().collect(); - - println!("funding keys {}", dests.len()); - while !notfunded.is_empty() { - let mut new_funded: Vec<(&Keypair, u64)> = vec![]; - let mut to_fund = vec![]; - println!("creating from... {}", funded.len()); - for f in &mut funded { - let max_units = cmp::min(notfunded.len(), MAX_SPENDS_PER_TX); - if max_units == 0 { - break; - } - let start = notfunded.len() - max_units; - let per_unit = f.1 / (max_units as u64); - let moves: Vec<_> = notfunded[start..] - .iter() - .map(|k| (k.pubkey(), per_unit)) - .collect(); - notfunded[start..] - .iter() - .for_each(|k| new_funded.push((k, per_unit))); - notfunded.truncate(start); - if !moves.is_empty() { - to_fund.push((f.0, moves)); - } - } - println!("sending... {}", to_fund.len()); - // try to transfer a few at a time with recent last_id - to_fund.chunks(10_000).for_each(|chunk| { - loop { - let last_id = client.get_last_id(); - println!("generating... {} {}", chunk.len(), last_id); - let mut to_fund_txs: Vec<_> = chunk - .par_iter() - .map(|(k, m)| Transaction::system_move_many(k, &m, last_id, 0)) - .collect(); - // with randomly distributed the failures - // most of the account pairs should have some funding in one of the pairs - // durring generate_tx step - thread_rng().shuffle(&mut to_fund_txs); - println!("transfering... {}", chunk.len()); - to_fund_txs.iter().for_each(|tx| { - let _ = client.transfer_signed(&tx).expect("transfer"); - }); - // randomly sample some of the transfers - thread_rng().shuffle(&mut to_fund_txs); - let max = cmp::min(10, to_fund_txs.len()); - if to_fund_txs[..max] - .iter() - .all(|tx| verify_transfer(client, tx)) - { - break; - } - } - }); - println!("funded: {} left: {}", new_funded.len(), notfunded.len()); - funded = new_funded; - } -} - -fn airdrop_tokens(client: &mut ThinClient, drone_addr: &SocketAddr, id: &Keypair, tx_count: u64) { - let starting_balance = client.poll_get_balance(&id.pubkey()).unwrap_or(0); - metrics_submit_token_balance(starting_balance); - println!("starting balance {}", starting_balance); - - if starting_balance < tx_count { - let airdrop_amount = tx_count - starting_balance; - println!( - "Airdropping {:?} tokens from {} for {}", - airdrop_amount, - drone_addr, - id.pubkey(), - ); - - let last_id = client.get_last_id(); - match request_airdrop_transaction(&drone_addr, &id.pubkey(), airdrop_amount, last_id) { - Ok(transaction) => { - let signature = client.transfer_signed(&transaction).unwrap(); - client.poll_for_signature(&signature).unwrap(); - } - Err(err) => { - panic!( - "Error requesting airdrop: {:?} to addr: {:?} amount: {}", - err, drone_addr, airdrop_amount - ); - } - }; - - let current_balance = client.poll_get_balance(&id.pubkey()).unwrap_or_else(|e| { - println!("airdrop error {}", e); - starting_balance - }); - println!("current balance {}...", current_balance); - - metrics_submit_token_balance(current_balance); - if current_balance - starting_balance != airdrop_amount { - println!( - "Airdrop failed! {} {} {}", - id.pubkey(), - current_balance, - starting_balance - ); - exit(1); - } - } -} - -fn compute_and_report_stats( - maxes: &Arc>>, - sample_period: u64, - tx_send_elapsed: &Duration, - total_tx_send_count: usize, -) { - // Compute/report stats - let mut max_of_maxes = 0.0; - let mut max_tx_count = 0; - let mut nodes_with_zero_tps = 0; - let mut total_maxes = 0.0; - println!(" Node address | Max TPS | Total Transactions"); - println!("---------------------+---------------+--------------------"); - - for (sock, stats) in maxes.read().unwrap().iter() { - let maybe_flag = match stats.tx { - 0 => "!!!!!", - _ => "", - }; - - println!( - "{:20} | {:13.2} | {} {}", - (*sock).to_string(), - stats.tps, - stats.tx, - maybe_flag - ); - - if stats.tps == 0.0 { - nodes_with_zero_tps += 1; - } - total_maxes += stats.tps; - - if stats.tps > max_of_maxes { - max_of_maxes = stats.tps; - } - if stats.tx > max_tx_count { - max_tx_count = stats.tx; - } - } - - if total_maxes > 0.0 { - let num_nodes_with_tps = maxes.read().unwrap().len() - nodes_with_zero_tps; - let average_max = total_maxes / num_nodes_with_tps as f64; - println!( - "\nAverage max TPS: {:.2}, {} nodes had 0 TPS", - average_max, nodes_with_zero_tps - ); - } - - println!( - "\nHighest TPS: {:.2} sampling period {}s max transactions: {} clients: {} drop rate: {:.2}", - max_of_maxes, - sample_period, - max_tx_count, - maxes.read().unwrap().len(), - (total_tx_send_count as u64 - max_tx_count) as f64 / total_tx_send_count as f64, - ); - println!( - "\tAverage TPS: {}", - max_tx_count as f32 / duration_as_s(tx_send_elapsed) - ); -} - -// First transfer 3/4 of the tokens to the dest accounts -// then ping-pong 1/4 of the tokens back to the other account -// this leaves 1/4 token buffer in each account -fn should_switch_directions(num_tokens_per_account: u64, i: u64) -> bool { - i % (num_tokens_per_account / 4) == 0 && (i >= (3 * num_tokens_per_account) / 4) -} - -fn main() { - logger::setup(); - solana_metrics::set_panic_hook("bench-tps"); - - let matches = App::new("solana-bench-tps") - .version(crate_version!()) - .arg( - Arg::with_name("network") - .short("n") - .long("network") - .value_name("HOST:PORT") - .takes_value(true) - .help("Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001"), - ) - .arg( - Arg::with_name("drone") - .short("d") - .long("drone") - .value_name("HOST:PORT") - .takes_value(true) - .help("Location of the drone; defaults to network:DRONE_PORT"), - ) - .arg( - Arg::with_name("identity") - .short("i") - .long("identity") - .value_name("PATH") - .takes_value(true) - .required(true) - .help("File containing a client identity (keypair)"), - ) - .arg( - Arg::with_name("num-nodes") - .short("N") - .long("num-nodes") - .value_name("NUM") - .takes_value(true) - .help("Wait for NUM nodes to converge"), - ) - .arg( - Arg::with_name("reject-extra-nodes") - .long("reject-extra-nodes") - .help("Require exactly `num-nodes` on convergence. Appropriate only for internal networks"), - ) - .arg( - Arg::with_name("threads") - .short("t") - .long("threads") - .value_name("NUM") - .takes_value(true) - .help("Number of threads"), - ) - .arg( - Arg::with_name("duration") - .long("duration") - .value_name("SECS") - .takes_value(true) - .help("Seconds to run benchmark, then exit; default is forever"), - ) - .arg( - Arg::with_name("converge-only") - .long("converge-only") - .help("Exit immediately after converging"), - ) - .arg( - Arg::with_name("sustained") - .long("sustained") - .help("Use sustained performance mode vs. peak mode. This overlaps the tx generation with transfers."), - ) - .arg( - Arg::with_name("tx_count") - .long("tx_count") - .value_name("NUM") - .takes_value(true) - .help("Number of transactions to send per batch") - ) - .get_matches(); - - let network = if let Some(addr) = matches.value_of("network") { - addr.parse().unwrap_or_else(|e| { - eprintln!("failed to parse network: {}", e); - exit(1) - }) - } else { - socketaddr!("127.0.0.1:8001") - }; - - let drone_addr = if let Some(addr) = matches.value_of("drone") { - addr.parse().unwrap_or_else(|e| { - eprintln!("failed to parse drone address: {}", e); - exit(1) - }) - } else { - let mut addr = network; - addr.set_port(DRONE_PORT); - addr - }; - - let id = - read_keypair(matches.value_of("identity").unwrap()).expect("can't read client identity"); - - let threads = if let Some(t) = matches.value_of("threads") { - t.to_string().parse().expect("can't parse threads") - } else { - 4usize - }; - - let num_nodes = if let Some(n) = matches.value_of("num-nodes") { - n.to_string().parse().expect("can't parse num-nodes") - } else { - 1usize - }; - - let duration = if let Some(s) = matches.value_of("duration") { - Duration::new(s.to_string().parse().expect("can't parse duration"), 0) - } else { - Duration::new(std::u64::MAX, 0) - }; - - let tx_count = if let Some(s) = matches.value_of("tx_count") { - s.to_string().parse().expect("can't parse tx_count") - } else { - 500_000 - }; - - let sustained = matches.is_present("sustained"); - - println!("Looking for leader at {:?}", network); - let leader = poll_gossip_for_leader(network, None).expect("unable to find leader on network"); - - let exit_signal = Arc::new(AtomicBool::new(false)); - let (nodes, leader, ncp) = converge(&leader, &exit_signal, num_nodes); - - if nodes.len() < num_nodes { - println!( - "Error: Insufficient nodes discovered. Expecting {} or more", - num_nodes - ); - exit(1); - } - if matches.is_present("reject-extra-nodes") && nodes.len() > num_nodes { - println!( - "Error: Extra nodes discovered. Expecting exactly {}", - num_nodes - ); - exit(1); - } - - if leader.is_none() { - println!("no leader"); - exit(1); - } - - if matches.is_present("converge-only") { - return; - } - - let leader = leader.unwrap(); - - println!("leader RPC is at {} {}", leader.rpc, leader.id); - let mut client = mk_client(&leader); - let mut barrier_client = mk_client(&leader); - - let mut seed = [0u8; 32]; - seed.copy_from_slice(&id.public_key_bytes()[..32]); - let mut rnd = GenKeys::new(seed); - - println!("Creating {} keypairs...", tx_count * 2); - let mut total_keys = 0; - let mut target = tx_count * 2; - while target > 0 { - total_keys += target; - target /= MAX_SPENDS_PER_TX; - } - let gen_keypairs = rnd.gen_n_keypairs(total_keys as u64); - let barrier_id = rnd.gen_n_keypairs(1).pop().unwrap(); - - println!("Get tokens..."); - let num_tokens_per_account = 20; - - // Sample the first keypair, see if it has tokens, if so then resume - // to avoid token loss - let keypair0_balance = client - .poll_get_balance(&gen_keypairs.last().unwrap().pubkey()) - .unwrap_or(0); - - if num_tokens_per_account > keypair0_balance { - let extra = num_tokens_per_account - keypair0_balance; - let total = extra * (gen_keypairs.len() as u64); - airdrop_tokens(&mut client, &drone_addr, &id, total); - println!("adding more tokens {}", extra); - fund_keys(&mut client, &id, &gen_keypairs, extra); - } - let start = gen_keypairs.len() - (tx_count * 2) as usize; - let keypairs = &gen_keypairs[start..]; - airdrop_tokens(&mut barrier_client, &drone_addr, &barrier_id, 1); - - println!("Get last ID..."); - let mut last_id = client.get_last_id(); - println!("Got last ID {:?}", last_id); - - let first_tx_count = client.transaction_count(); - println!("Initial transaction count {}", first_tx_count); - - // Setup a thread per validator to sample every period - // collect the max transaction rate and total tx count seen - let maxes = Arc::new(RwLock::new(Vec::new())); - let sample_period = 1; // in seconds - println!("Sampling TPS every {} second...", sample_period); - let v_threads: Vec<_> = nodes - .into_iter() - .map(|v| { - let exit_signal = exit_signal.clone(); - let maxes = maxes.clone(); - Builder::new() - .name("solana-client-sample".to_string()) - .spawn(move || { - sample_tx_count(&exit_signal, &maxes, first_tx_count, &v, sample_period); - }).unwrap() - }).collect(); - - let shared_txs: SharedTransactions = Arc::new(RwLock::new(VecDeque::new())); - - let shared_tx_active_thread_count = Arc::new(AtomicIsize::new(0)); - let total_tx_sent_count = Arc::new(AtomicUsize::new(0)); - - let s_threads: Vec<_> = (0..threads) - .map(|_| { - let exit_signal = exit_signal.clone(); - let shared_txs = shared_txs.clone(); - let leader = leader.clone(); - let shared_tx_active_thread_count = shared_tx_active_thread_count.clone(); - let total_tx_sent_count = total_tx_sent_count.clone(); - Builder::new() - .name("solana-client-sender".to_string()) - .spawn(move || { - do_tx_transfers( - &exit_signal, - &shared_txs, - &leader, - &shared_tx_active_thread_count, - &total_tx_sent_count, - ); - }).unwrap() - }).collect(); - - // generate and send transactions for the specified duration - let start = Instant::now(); - let mut reclaim_tokens_back_to_source_account = false; - let mut i = keypair0_balance; - while start.elapsed() < duration { - let balance = client.poll_get_balance(&id.pubkey()).unwrap_or(0); - metrics_submit_token_balance(balance); - - // ping-pong between source and destination accounts for each loop iteration - // this seems to be faster than trying to determine the balance of individual - // accounts - let len = tx_count as usize; - generate_txs( - &shared_txs, - &keypairs[..len], - &keypairs[len..], - threads, - reclaim_tokens_back_to_source_account, - &leader, - ); - // In sustained mode overlap the transfers with generation - // this has higher average performance but lower peak performance - // in tested environments. - if !sustained { - while shared_tx_active_thread_count.load(Ordering::Relaxed) > 0 { - sleep(Duration::from_millis(100)); - } - } - // It's not feasible (would take too much time) to confirm each of the `tx_count / 2` - // transactions sent by `generate_txs()` so instead send and confirm a single transaction - // to validate the network is still functional. - send_barrier_transaction(&mut barrier_client, &mut last_id, &barrier_id); - - i += 1; - if should_switch_directions(num_tokens_per_account, i) { - reclaim_tokens_back_to_source_account = !reclaim_tokens_back_to_source_account; - } - } - - // Stop the sampling threads so it will collect the stats - exit_signal.store(true, Ordering::Relaxed); - - println!("Waiting for validator threads..."); - for t in v_threads { - if let Err(err) = t.join() { - println!(" join() failed with: {:?}", err); - } - } - - // join the tx send threads - println!("Waiting for transmit threads..."); - for t in s_threads { - if let Err(err) = t.join() { - println!(" join() failed with: {:?}", err); - } - } - - let balance = client.poll_get_balance(&id.pubkey()).unwrap_or(0); - metrics_submit_token_balance(balance); - - compute_and_report_stats( - &maxes, - sample_period, - &start.elapsed(), - total_tx_sent_count.load(Ordering::Relaxed), - ); - - // join the cluster_info client threads - ncp.join().unwrap(); -} - -fn converge( - leader: &NodeInfo, - exit_signal: &Arc, - num_nodes: usize, -) -> (Vec, Option, Ncp) { - //lets spy on the network - let (node, gossip_socket) = ClusterInfo::spy_node(); - let mut spy_cluster_info = ClusterInfo::new(node); - spy_cluster_info.insert_info(leader.clone()); - spy_cluster_info.set_leader(leader.id); - let spy_ref = Arc::new(RwLock::new(spy_cluster_info)); - let window = Arc::new(RwLock::new(default_window())); - let ncp = Ncp::new(&spy_ref, window, None, gossip_socket, exit_signal.clone()); - let mut v: Vec = vec![]; - // wait for the network to converge, 30 seconds should be plenty - for _ in 0..30 { - { - let spy_ref = spy_ref.read().unwrap(); - - println!("{}", spy_ref.node_info_trace()); - - if spy_ref.leader_data().is_some() { - v = spy_ref.rpc_peers(); - if v.len() >= num_nodes { - println!("CONVERGED!"); - break; - } else { - println!( - "{} node(s) discovered (looking for {} or more)", - v.len(), - num_nodes - ); - } - } - } - sleep(Duration::new(1, 0)); - } - let leader = spy_ref.read().unwrap().leader_data().cloned(); - (v, leader, ncp) -} - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn test_switch_directions() { - assert_eq!(should_switch_directions(20, 0), false); - assert_eq!(should_switch_directions(20, 1), false); - assert_eq!(should_switch_directions(20, 14), false); - assert_eq!(should_switch_directions(20, 15), true); - assert_eq!(should_switch_directions(20, 16), false); - assert_eq!(should_switch_directions(20, 19), false); - assert_eq!(should_switch_directions(20, 20), true); - assert_eq!(should_switch_directions(20, 21), false); - assert_eq!(should_switch_directions(20, 99), false); - assert_eq!(should_switch_directions(20, 100), true); - assert_eq!(should_switch_directions(20, 101), false); - } -} diff --git a/book/bin/fullnode-config.rs b/book/bin/fullnode-config.rs deleted file mode 100644 index 26ad46e352c584..00000000000000 --- a/book/bin/fullnode-config.rs +++ /dev/null @@ -1,81 +0,0 @@ -#[macro_use] -extern crate clap; -extern crate dirs; -extern crate ring; -extern crate serde_json; -extern crate solana; - -use clap::{App, Arg}; -use ring::rand::SystemRandom; -use ring::signature::Ed25519KeyPair; -use solana::cluster_info::FULLNODE_PORT_RANGE; -use solana::fullnode::Config; -use solana::logger; -use solana::netutil::{get_ip_addr, get_public_ip_addr, parse_port_or_addr}; -use solana::signature::read_pkcs8; -use std::io; -use std::net::SocketAddr; - -fn main() { - logger::setup(); - let matches = App::new("fullnode-config") - .version(crate_version!()) - .arg( - Arg::with_name("local") - .short("l") - .long("local") - .takes_value(false) - .help("Detect network address from local machine configuration"), - ).arg( - Arg::with_name("keypair") - .short("k") - .long("keypair") - .value_name("PATH") - .takes_value(true) - .help("/path/to/id.json"), - ).arg( - Arg::with_name("public") - .short("p") - .long("public") - .takes_value(false) - .help("Detect public network address using public servers"), - ).arg( - Arg::with_name("bind") - .short("b") - .long("bind") - .value_name("PORT") - .takes_value(true) - .help("Bind to port or address"), - ).get_matches(); - - let bind_addr: SocketAddr = { - let mut bind_addr = parse_port_or_addr(matches.value_of("bind"), FULLNODE_PORT_RANGE.0); - if matches.is_present("local") { - let ip = get_ip_addr().unwrap(); - bind_addr.set_ip(ip); - } - if matches.is_present("public") { - let ip = get_public_ip_addr().unwrap(); - bind_addr.set_ip(ip); - } - bind_addr - }; - - let mut path = dirs::home_dir().expect("home directory"); - let id_path = if matches.is_present("keypair") { - matches.value_of("keypair").unwrap() - } else { - path.extend(&[".config", "solana", "id.json"]); - path.to_str().unwrap() - }; - let pkcs8 = read_pkcs8(id_path).expect("client keypair"); - - let rnd = SystemRandom::new(); - let vote_account_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rnd).unwrap(); - - // we need all the receiving sockets to be bound within the expected - // port range that we open on aws - let config = Config::new(&bind_addr, pkcs8, vote_account_pkcs8.to_vec()); - let stdout = io::stdout(); - serde_json::to_writer(stdout, &config).expect("serialize"); -} diff --git a/book/bin/fullnode.rs b/book/bin/fullnode.rs deleted file mode 100644 index 67af9371dbf995..00000000000000 --- a/book/bin/fullnode.rs +++ /dev/null @@ -1,213 +0,0 @@ -#[macro_use] -extern crate clap; -extern crate getopts; -#[macro_use] -extern crate log; -extern crate serde_json; -#[macro_use] -extern crate solana; -extern crate solana_metrics; - -use clap::{App, Arg}; -use solana::client::mk_client; -use solana::cluster_info::{Node, FULLNODE_PORT_RANGE}; -use solana::fullnode::{Config, Fullnode, FullnodeReturnType}; -use solana::leader_scheduler::LeaderScheduler; -use solana::logger; -use solana::netutil::find_available_port_in_range; -use solana::signature::{Keypair, KeypairUtil}; -use solana::thin_client::poll_gossip_for_leader; -use solana::vote_program::VoteProgram; -use solana::vote_transaction::VoteTransaction; -use std::fs::File; -use std::net::{Ipv4Addr, SocketAddr}; -use std::process::exit; -use std::sync::Arc; -use std::thread::sleep; -use std::time::Duration; - -fn main() { - logger::setup(); - solana_metrics::set_panic_hook("fullnode"); - let matches = App::new("fullnode") - .version(crate_version!()) - .arg( - Arg::with_name("identity") - .short("i") - .long("identity") - .value_name("PATH") - .takes_value(true) - .help("Run with the identity found in FILE"), - ).arg( - Arg::with_name("network") - .short("n") - .long("network") - .value_name("HOST:PORT") - .takes_value(true) - .help("Rendezvous with the network at this gossip entry point"), - ).arg( - Arg::with_name("ledger") - .short("l") - .long("ledger") - .value_name("DIR") - .takes_value(true) - .required(true) - .help("Use DIR as persistent ledger location"), - ).arg( - Arg::with_name("rpc") - .long("rpc") - .value_name("PORT") - .takes_value(true) - .help("Custom RPC port for this node"), - ).get_matches(); - - let (keypair, vote_account_keypair, ncp) = if let Some(i) = matches.value_of("identity") { - let path = i.to_string(); - if let Ok(file) = File::open(path.clone()) { - let parse: serde_json::Result = serde_json::from_reader(file); - if let Ok(data) = parse { - ( - data.keypair(), - data.vote_account_keypair(), - data.node_info.ncp, - ) - } else { - eprintln!("failed to parse {}", path); - exit(1); - } - } else { - eprintln!("failed to read {}", path); - exit(1); - } - } else { - (Keypair::new(), Keypair::new(), socketaddr!(0, 8000)) - }; - - let ledger_path = matches.value_of("ledger").unwrap(); - - // socketaddr that is initial pointer into the network's gossip (ncp) - let network = matches - .value_of("network") - .map(|network| network.parse().expect("failed to parse network address")); - - let node = Node::new_with_external_ip(keypair.pubkey(), &ncp); - - // save off some stuff for airdrop - let mut node_info = node.info.clone(); - - let vote_account_keypair = Arc::new(vote_account_keypair); - let vote_account_id = vote_account_keypair.pubkey(); - let keypair = Arc::new(keypair); - let pubkey = keypair.pubkey(); - - let mut leader_scheduler = LeaderScheduler::default(); - - // Remove this line to enable leader rotation - leader_scheduler.use_only_bootstrap_leader = true; - - let rpc_port = if let Some(port) = matches.value_of("rpc") { - let port_number = port.to_string().parse().expect("integer"); - if port_number == 0 { - eprintln!("Invalid RPC port requested: {:?}", port); - exit(1); - } - Some(port_number) - } else { - match find_available_port_in_range(FULLNODE_PORT_RANGE) { - Ok(port) => Some(port), - Err(_) => None, - } - }; - - let leader = match network { - Some(network) => { - poll_gossip_for_leader(network, None).expect("can't find leader on network") - } - None => { - //self = leader - if rpc_port.is_some() { - node_info.rpc.set_port(rpc_port.unwrap()); - node_info.rpc_pubsub.set_port(rpc_port.unwrap() + 1); - } - node_info - } - }; - - let mut fullnode = Fullnode::new( - node, - ledger_path, - keypair.clone(), - vote_account_keypair.clone(), - network, - false, - leader_scheduler, - rpc_port, - ); - let mut client = mk_client(&leader); - - let balance = client.poll_get_balance(&pubkey).unwrap_or(0); - info!("balance is {}", balance); - if balance < 1 { - error!("insufficient tokens"); - exit(1); - } - - // Create the vote account if necessary - if client.poll_get_balance(&vote_account_id).unwrap_or(0) == 0 { - // Need at least two tokens as one token will be spent on a vote_account_new() transaction - if balance < 2 { - error!("insufficient tokens"); - exit(1); - } - loop { - let last_id = client.get_last_id(); - let transaction = - VoteTransaction::vote_account_new(&keypair, vote_account_id, last_id, 1); - if client.transfer_signed(&transaction).is_err() { - sleep(Duration::from_secs(2)); - continue; - } - - let balance = client.poll_get_balance(&vote_account_id).unwrap_or(0); - if balance > 0 { - break; - } - sleep(Duration::from_secs(2)); - } - } - - // Register the vote account to this node - loop { - let last_id = client.get_last_id(); - let transaction = - VoteTransaction::vote_account_register(&keypair, vote_account_id, last_id, 0); - if client.transfer_signed(&transaction).is_err() { - sleep(Duration::from_secs(2)); - continue; - } - - let account_user_data = client.get_account_userdata(&vote_account_id); - if let Ok(Some(account_user_data)) = account_user_data { - if let Ok(vote_state) = VoteProgram::deserialize(&account_user_data) { - if vote_state.node_id == pubkey { - break; - } - } - } - - sleep(Duration::from_secs(2)); - } - - loop { - let status = fullnode.handle_role_transition(); - match status { - Ok(Some(FullnodeReturnType::LeaderToValidatorRotation)) => (), - Ok(Some(FullnodeReturnType::ValidatorToLeaderRotation)) => (), - _ => { - // Fullnode tpu/tvu exited for some unexpected - // reason, so exit - exit(1); - } - } - } -} diff --git a/book/bin/genesis.rs b/book/bin/genesis.rs deleted file mode 100644 index e299b94ae4dce1..00000000000000 --- a/book/bin/genesis.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! A command-line executable for generating the chain's genesis block. - -extern crate atty; -#[macro_use] -extern crate clap; -extern crate serde_json; -extern crate solana; -extern crate untrusted; - -use clap::{App, Arg}; -use solana::fullnode::Config; -use solana::ledger::LedgerWriter; -use solana::mint::Mint; -use solana::signature::KeypairUtil; -use std::error; -use std::fs::File; -use std::path::Path; - -/** - * Bootstrap leader gets two tokens: - * - one token to create an instance of the vote_program with - * - one second token to keep the node identity public key valid - */ -pub const BOOTSTRAP_LEADER_TOKENS: u64 = 2; - -fn main() -> Result<(), Box> { - let matches = App::new("solana-genesis") - .version(crate_version!()) - .arg( - Arg::with_name("num_tokens") - .short("t") - .long("num_tokens") - .value_name("TOKENS") - .takes_value(true) - .required(true) - .help("Number of tokens to create in the mint"), - ).arg( - Arg::with_name("mint") - .short("m") - .long("mint") - .value_name("MINT") - .takes_value(true) - .required(true) - .help("Path to file containing keys of the mint"), - ).arg( - Arg::with_name("bootstrap_leader") - .short("b") - .long("bootstrap_leader") - .value_name("BOOTSTRAP LEADER") - .takes_value(true) - .required(true) - .help("Path to file containing keys of the bootstrap leader"), - ).arg( - Arg::with_name("ledger") - .short("l") - .long("ledger") - .value_name("DIR") - .takes_value(true) - .required(true) - .help("Use directory as persistent ledger location"), - ).get_matches(); - - // Parse the input leader configuration - let file = File::open(Path::new(&matches.value_of("bootstrap_leader").unwrap())).unwrap(); - let leader_config: Config = serde_json::from_reader(file).unwrap(); - let leader_keypair = leader_config.keypair(); - - // Parse the input mint configuration - let num_tokens = value_t_or_exit!(matches, "num_tokens", u64); - let file = File::open(Path::new(&matches.value_of("mint").unwrap())).unwrap(); - let pkcs8: Vec = serde_json::from_reader(&file)?; - let mint = Mint::new_with_pkcs8( - num_tokens, - pkcs8, - leader_keypair.pubkey(), - BOOTSTRAP_LEADER_TOKENS, - ); - - // Write the ledger entries - let ledger_path = matches.value_of("ledger").unwrap(); - let mut ledger_writer = LedgerWriter::open(&ledger_path, true)?; - ledger_writer.write_entries(&mint.create_entries())?; - - Ok(()) -} diff --git a/book/bin/keygen.rs b/book/bin/keygen.rs deleted file mode 100644 index 10a49ca48b5210..00000000000000 --- a/book/bin/keygen.rs +++ /dev/null @@ -1,37 +0,0 @@ -#[macro_use] -extern crate clap; -extern crate dirs; -extern crate ring; -extern crate serde_json; -extern crate solana; - -use clap::{App, Arg}; -use solana::wallet::gen_keypair_file; -use std::error; - -fn main() -> Result<(), Box> { - let matches = App::new("solana-keygen") - .version(crate_version!()) - .arg( - Arg::with_name("outfile") - .short("o") - .long("outfile") - .value_name("PATH") - .takes_value(true) - .help("Path to generated file"), - ).get_matches(); - - let mut path = dirs::home_dir().expect("home directory"); - let outfile = if matches.is_present("outfile") { - matches.value_of("outfile").unwrap() - } else { - path.extend(&[".config", "solana", "id.json"]); - path.to_str().unwrap() - }; - - let serialized_keypair = gen_keypair_file(outfile.to_string())?; - if outfile == "-" { - println!("{}", serialized_keypair); - } - Ok(()) -} diff --git a/book/bin/ledger-tool.rs b/book/bin/ledger-tool.rs deleted file mode 100644 index 541915a2b2eaa8..00000000000000 --- a/book/bin/ledger-tool.rs +++ /dev/null @@ -1,162 +0,0 @@ -#[macro_use] -extern crate clap; -extern crate serde_json; -extern crate solana; - -use clap::{App, Arg, SubCommand}; -use solana::bank::Bank; -use solana::ledger::{read_ledger, verify_ledger}; -use solana::logger; -use std::io::{stdout, Write}; -use std::process::exit; - -fn main() { - logger::setup(); - let matches = App::new("ledger-tool") - .version(crate_version!()) - .arg( - Arg::with_name("ledger") - .short("l") - .long("ledger") - .value_name("DIR") - .takes_value(true) - .required(true) - .help("Use directory for ledger location"), - ) - .arg( - Arg::with_name("head") - .short("n") - .long("head") - .value_name("NUM") - .takes_value(true) - .help("Limit to at most the first NUM entries in ledger\n (only applies to verify, print, json commands)"), - ) - .arg( - Arg::with_name("precheck") - .short("p") - .long("precheck") - .help("Use ledger_verify() to check internal ledger consistency before proceeding"), - ) - .arg( - Arg::with_name("continue") - .short("c") - .long("continue") - .help("Continue verify even if verification fails"), - ) - .subcommand(SubCommand::with_name("print").about("Print the ledger")) - .subcommand(SubCommand::with_name("json").about("Print the ledger in JSON format")) - .subcommand(SubCommand::with_name("verify").about("Verify the ledger's PoH")) - .get_matches(); - - let ledger_path = matches.value_of("ledger").unwrap(); - - if matches.is_present("precheck") { - if let Err(e) = verify_ledger(&ledger_path) { - eprintln!("ledger precheck failed, error: {:?} ", e); - exit(1); - } - } - - let entries = match read_ledger(ledger_path, true) { - Ok(entries) => entries, - Err(err) => { - eprintln!("Failed to open ledger at {}: {}", ledger_path, err); - exit(1); - } - }; - - let head = match matches.value_of("head") { - Some(head) => head.parse().expect("please pass a number for --head"), - None => ::max_value(), - }; - - match matches.subcommand() { - ("print", _) => { - let entries = match read_ledger(ledger_path, true) { - Ok(entries) => entries, - Err(err) => { - eprintln!("Failed to open ledger at {}: {}", ledger_path, err); - exit(1); - } - }; - for (i, entry) in entries.enumerate() { - if i >= head { - break; - } - let entry = entry.unwrap(); - println!("{:?}", entry); - } - } - ("json", _) => { - stdout().write_all(b"{\"ledger\":[\n").expect("open array"); - for (i, entry) in entries.enumerate() { - if i >= head { - break; - } - let entry = entry.unwrap(); - serde_json::to_writer(stdout(), &entry).expect("serialize"); - stdout().write_all(b",\n").expect("newline"); - } - stdout().write_all(b"\n]}\n").expect("close array"); - } - ("verify", _) => { - const NUM_GENESIS_ENTRIES: usize = 3; - if head < NUM_GENESIS_ENTRIES { - eprintln!( - "verify requires at least {} entries to run", - NUM_GENESIS_ENTRIES - ); - exit(1); - } - let bank = Bank::default(); - { - let genesis = match read_ledger(ledger_path, true) { - Ok(entries) => entries, - Err(err) => { - eprintln!("Failed to open ledger at {}: {}", ledger_path, err); - exit(1); - } - }; - - let genesis = genesis.take(NUM_GENESIS_ENTRIES).map(|e| e.unwrap()); - if let Err(e) = bank.process_ledger(genesis) { - eprintln!("verify failed at genesis err: {:?}", e); - if !matches.is_present("continue") { - exit(1); - } - } - } - let entries = entries.map(|e| e.unwrap()); - - let head = head - NUM_GENESIS_ENTRIES; - - let mut last_id = bank.last_id(); - - for (i, entry) in entries.skip(NUM_GENESIS_ENTRIES).enumerate() { - if i >= head { - break; - } - - if !entry.verify(&last_id) { - eprintln!("entry.verify() failed at entry[{}]", i + 2); - if !matches.is_present("continue") { - exit(1); - } - } - last_id = entry.id; - - if let Err(e) = bank.process_entry(&entry) { - eprintln!("verify failed at entry[{}], err: {:?}", i + 2, e); - if !matches.is_present("continue") { - exit(1); - } - } - } - } - ("", _) => { - eprintln!("{}", matches.usage()); - exit(1); - } - _ => unreachable!(), - }; -} diff --git a/book/bin/replicator.rs b/book/bin/replicator.rs deleted file mode 100644 index 165fa1c89b8227..00000000000000 --- a/book/bin/replicator.rs +++ /dev/null @@ -1,153 +0,0 @@ -#[macro_use] -extern crate clap; -extern crate getopts; -extern crate serde_json; -#[macro_use] -extern crate solana; -extern crate solana_drone; - -use clap::{App, Arg}; -use solana::chacha::{chacha_cbc_encrypt_file, CHACHA_BLOCK_SIZE}; -use solana::client::mk_client; -use solana::cluster_info::Node; -use solana::fullnode::Config; -use solana::ledger::LEDGER_DATA_FILE; -use solana::logger; -use solana::replicator::{sample_file, Replicator}; -use solana::signature::{Keypair, KeypairUtil}; -use solana::storage_transaction::StorageTransaction; -use solana::transaction::Transaction; -use solana_drone::drone::{request_airdrop_transaction, DRONE_PORT}; -use std::fs::File; -use std::net::{Ipv4Addr, SocketAddr}; -use std::path::Path; -use std::process::exit; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use std::thread::sleep; -use std::time::Duration; - -fn main() { - logger::setup(); - - let matches = App::new("replicator") - .version(crate_version!()) - .arg( - Arg::with_name("identity") - .short("i") - .long("identity") - .value_name("PATH") - .takes_value(true) - .help("Run with the identity found in FILE"), - ).arg( - Arg::with_name("network") - .short("n") - .long("network") - .value_name("HOST:PORT") - .takes_value(true) - .help("Rendezvous with the network at this gossip entry point"), - ).arg( - Arg::with_name("ledger") - .short("l") - .long("ledger") - .value_name("DIR") - .takes_value(true) - .required(true) - .help("use DIR as persistent ledger location"), - ).get_matches(); - - let ledger_path = matches.value_of("ledger"); - - let (keypair, ncp) = if let Some(i) = matches.value_of("identity") { - let path = i.to_string(); - if let Ok(file) = File::open(path.clone()) { - let parse: serde_json::Result = serde_json::from_reader(file); - if let Ok(data) = parse { - (data.keypair(), data.node_info.ncp) - } else { - eprintln!("failed to parse {}", path); - exit(1); - } - } else { - eprintln!("failed to read {}", path); - exit(1); - } - } else { - (Keypair::new(), socketaddr!([127, 0, 0, 1], 8700)) - }; - - let node = Node::new_with_external_ip(keypair.pubkey(), &ncp); - - println!( - "replicating the data with keypair: {:?} ncp:{:?}", - keypair.pubkey(), - ncp - ); - - let exit = Arc::new(AtomicBool::new(false)); - let done = Arc::new(AtomicBool::new(false)); - - let network_addr = matches - .value_of("network") - .map(|network| network.parse().expect("failed to parse network address")); - - // TODO: ask network what slice we should store - let entry_height = 0; - - let (replicator, leader_info) = Replicator::new( - entry_height, - 5, - &exit, - ledger_path, - node, - network_addr, - done.clone(), - ); - - while !done.load(Ordering::Relaxed) { - sleep(Duration::from_millis(100)); - } - - println!("Done downloading ledger"); - - let ledger_path = Path::new(ledger_path.unwrap()); - let ledger_data_file = ledger_path.join(LEDGER_DATA_FILE); - let ledger_data_file_encrypted = ledger_path.join(format!("{}.enc", LEDGER_DATA_FILE)); - let mut ivec = [0u8; CHACHA_BLOCK_SIZE]; - ivec[0..4].copy_from_slice(&[2, 3, 4, 5]); - - if let Err(e) = - chacha_cbc_encrypt_file(&ledger_data_file, &ledger_data_file_encrypted, &mut ivec) - { - println!("Error while encrypting ledger: {:?}", e); - return; - } - - println!("Done encrypting the ledger"); - - let sampling_offsets = [0, 1, 2, 3]; - - let mut client = mk_client(&leader_info); - - let mut drone_addr = leader_info.tpu; - drone_addr.set_port(DRONE_PORT); - let airdrop_amount = 5; - let last_id = client.get_last_id(); - let transaction = - request_airdrop_transaction(&drone_addr, &keypair.pubkey(), airdrop_amount, last_id) - .unwrap(); - let signature = client.transfer_signed(&transaction).unwrap(); - client.poll_for_signature(&signature).unwrap(); - - match sample_file(&ledger_data_file_encrypted, &sampling_offsets) { - Ok(hash) => { - let last_id = client.get_last_id(); - println!("sampled hash: {}", hash); - let tx = Transaction::storage_new_mining_proof(&keypair, hash, last_id); - client.transfer_signed(&tx).expect("transfer didn't work!"); - } - Err(e) => println!("Error occurred while sampling: {:?}", e), - } - - replicator.join(); -} diff --git a/book/bin/upload-perf.rs b/book/bin/upload-perf.rs deleted file mode 100644 index e658476e5bbae6..00000000000000 --- a/book/bin/upload-perf.rs +++ /dev/null @@ -1,116 +0,0 @@ -extern crate serde_json; -extern crate solana; -extern crate solana_metrics; - -use serde_json::Value; -use solana_metrics::influxdb; -use std::collections::HashMap; -use std::env; -use std::fs::File; -use std::io::{BufRead, BufReader}; -use std::process::Command; - -fn get_last_metrics(metric: &str, db: &str, name: &str, branch: &str) -> Result { - let query = format!( - r#"SELECT last("{}") FROM "{}"."autogen"."{}" WHERE "branch"='{}'"#, - metric, db, name, branch - ); - - let response = solana_metrics::query(&query)?; - - match serde_json::from_str(&response) { - Result::Ok(v) => { - let v: Value = v; - let data = &v["results"][0]["series"][0]["values"][0][1]; - if data.is_null() { - return Result::Err("Key not found".to_string()); - } - Result::Ok(data.to_string()) - } - Result::Err(err) => Result::Err(err.to_string()), - } -} - -fn main() { - let args: Vec = env::args().collect(); - // Open the path in read-only mode, returns `io::Result` - let fname = &args[1]; - let file = match File::open(fname) { - Err(why) => panic!("couldn't open {}: {:?}", fname, why), - Ok(file) => file, - }; - - let branch = &args[2]; - let upload_metrics = args.len() > 2; - - let git_output = Command::new("git") - .args(&["rev-parse", "HEAD"]) - .output() - .expect("failed to execute git rev-parse"); - let git_commit_hash = String::from_utf8_lossy(&git_output.stdout); - let trimmed_hash = git_commit_hash.trim().to_string(); - - let mut last_commit = None; - let mut results = HashMap::new(); - - let db = env::var("INFLUX_DATABASE").unwrap_or_else(|_| "scratch".to_string()); - - for line in BufReader::new(file).lines() { - if let Ok(v) = serde_json::from_str(&line.unwrap()) { - let v: Value = v; - if v["type"] == "bench" { - let name = v["name"].as_str().unwrap().trim_matches('\"').to_string(); - - last_commit = match get_last_metrics(&"commit".to_string(), &db, &name, &branch) { - Result::Ok(v) => Some(v), - Result::Err(_) => None, - }; - - let median = v["median"].to_string().parse().unwrap(); - let deviation = v["deviation"].to_string().parse().unwrap(); - if upload_metrics { - solana_metrics::submit( - influxdb::Point::new(&v["name"].as_str().unwrap().trim_matches('\"')) - .add_tag("test", influxdb::Value::String("bench".to_string())) - .add_tag("branch", influxdb::Value::String(branch.to_string())) - .add_field("median", influxdb::Value::Integer(median)) - .add_field("deviation", influxdb::Value::Integer(deviation)) - .add_field( - "commit", - influxdb::Value::String(git_commit_hash.trim().to_string()), - ).to_owned(), - ); - } - let last_median = get_last_metrics(&"median".to_string(), &db, &name, &branch) - .unwrap_or_default(); - let last_deviation = - get_last_metrics(&"deviation".to_string(), &db, &name, &branch) - .unwrap_or_default(); - - results.insert(name, (median, deviation, last_median, last_deviation)); - } - } - } - - if let Some(commit) = last_commit { - println!( - "Comparing current commits: {} against baseline {} on {} branch", - trimmed_hash, commit, branch - ); - println!("bench_name, median, last_median, deviation, last_deviation"); - for (entry, values) in results { - println!( - "{}, {}, {}, {}, {}", - entry, values.0, values.2, values.1, values.3 - ); - } - } else { - println!("No previous results found for {} branch", branch); - println!("hash: {}", trimmed_hash); - println!("bench_name, median, deviation"); - for (entry, values) in results { - println!("{}, {}, {}", entry, values.0, values.1); - } - } - solana_metrics::flush(); -} diff --git a/book/bin/wallet.rs b/book/bin/wallet.rs deleted file mode 100644 index 1d806fa8d6eb47..00000000000000 --- a/book/bin/wallet.rs +++ /dev/null @@ -1,235 +0,0 @@ -#[macro_use] -extern crate clap; -extern crate dirs; -#[macro_use] -extern crate solana; - -use clap::{App, Arg, ArgMatches, SubCommand}; -use solana::logger; -use solana::signature::{read_keypair, KeypairUtil}; -use solana::wallet::{gen_keypair_file, parse_command, process_command, WalletConfig, WalletError}; -use std::error; -use std::net::SocketAddr; - -pub fn parse_args(matches: &ArgMatches) -> Result> { - let network = if let Some(addr) = matches.value_of("network") { - addr.parse().or_else(|_| { - Err(WalletError::BadParameter( - "Invalid network location".to_string(), - )) - })? - } else { - socketaddr!("127.0.0.1:8001") - }; - let timeout = if let Some(secs) = matches.value_of("timeout") { - Some(secs.to_string().parse().expect("integer")) - } else { - None - }; - - let proxy = matches.value_of("proxy").map(|proxy| proxy.to_string()); - - let mut path = dirs::home_dir().expect("home directory"); - let id_path = if matches.is_present("keypair") { - matches.value_of("keypair").unwrap() - } else { - path.extend(&[".config", "solana", "id.json"]); - if !path.exists() { - gen_keypair_file(path.to_str().unwrap().to_string())?; - println!("New keypair generated at: {:?}", path.to_str().unwrap()); - } - - path.to_str().unwrap() - }; - let id = read_keypair(id_path).or_else(|err| { - Err(WalletError::BadParameter(format!( - "{}: Unable to open keypair file: {}", - err, id_path - ))) - })?; - - let command = parse_command(id.pubkey(), &matches)?; - - Ok(WalletConfig { - id, - command, - network, - timeout, - proxy, - drone_port: None, - }) -} - -fn main() -> Result<(), Box> { - logger::setup(); - let matches = App::new("solana-wallet") - .version(crate_version!()) - .arg( - Arg::with_name("network") - .short("n") - .long("network") - .value_name("HOST:PORT") - .takes_value(true) - .help("Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001"), - ).arg( - Arg::with_name("keypair") - .short("k") - .long("keypair") - .value_name("PATH") - .takes_value(true) - .help("/path/to/id.json"), - ).arg( - Arg::with_name("timeout") - .long("timeout") - .value_name("SECS") - .takes_value(true) - .help("Max seconds to wait to get necessary gossip from the network"), - ).arg( - Arg::with_name("proxy") - .long("proxy") - .takes_value(true) - .value_name("URL") - .help("Address of TLS proxy") - .conflicts_with("rpc-port") - ).subcommand(SubCommand::with_name("address").about("Get your public key")) - .subcommand( - SubCommand::with_name("airdrop") - .about("Request a batch of tokens") - .arg( - Arg::with_name("tokens") - .index(1) - .value_name("NUM") - .takes_value(true) - .required(true) - .help("The number of tokens to request"), - ), - ).subcommand(SubCommand::with_name("balance").about("Get your balance")) - .subcommand( - SubCommand::with_name("cancel") - .about("Cancel a transfer") - .arg( - Arg::with_name("process-id") - .index(1) - .value_name("PROCESS_ID") - .takes_value(true) - .required(true) - .help("The process id of the transfer to cancel"), - ), - ).subcommand( - SubCommand::with_name("confirm") - .about("Confirm transaction by signature") - .arg( - Arg::with_name("signature") - .index(1) - .value_name("SIGNATURE") - .takes_value(true) - .required(true) - .help("The transaction signature to confirm"), - ), - ).subcommand( - SubCommand::with_name("deploy") - .about("Deploy a program") - .arg( - Arg::with_name("program-location") - .index(1) - .value_name("PATH") - .takes_value(true) - .required(true) - .help("/path/to/program.o"), - ) - // TODO: Add "loader" argument; current default is bpf_loader - ).subcommand( - SubCommand::with_name("get-transaction-count") - .about("Get current transaction count") - ).subcommand( - SubCommand::with_name("pay") - .about("Send a payment") - .arg( - Arg::with_name("to") - .index(1) - .value_name("PUBKEY") - .takes_value(true) - .required(true) - .help("The pubkey of recipient"), - ).arg( - Arg::with_name("tokens") - .index(2) - .value_name("NUM") - .takes_value(true) - .required(true) - .help("The number of tokens to send"), - ).arg( - Arg::with_name("timestamp") - .long("after") - .value_name("DATETIME") - .takes_value(true) - .help("A timestamp after which transaction will execute"), - ).arg( - Arg::with_name("timestamp-pubkey") - .long("require-timestamp-from") - .value_name("PUBKEY") - .takes_value(true) - .requires("timestamp") - .help("Require timestamp from this third party"), - ).arg( - Arg::with_name("witness") - .long("require-signature-from") - .value_name("PUBKEY") - .takes_value(true) - .multiple(true) - .use_delimiter(true) - .help("Any third party signatures required to unlock the tokens"), - ).arg( - Arg::with_name("cancelable") - .long("cancelable") - .takes_value(false), - ), - ).subcommand( - SubCommand::with_name("send-signature") - .about("Send a signature to authorize a transfer") - .arg( - Arg::with_name("to") - .index(1) - .value_name("PUBKEY") - .takes_value(true) - .required(true) - .help("The pubkey of recipient"), - ).arg( - Arg::with_name("process-id") - .index(2) - .value_name("PROCESS_ID") - .takes_value(true) - .required(true) - .help("The process id of the transfer to authorize") - ) - ).subcommand( - SubCommand::with_name("send-timestamp") - .about("Send a timestamp to unlock a transfer") - .arg( - Arg::with_name("to") - .index(1) - .value_name("PUBKEY") - .takes_value(true) - .required(true) - .help("The pubkey of recipient"), - ).arg( - Arg::with_name("process-id") - .index(2) - .value_name("PROCESS_ID") - .takes_value(true) - .required(true) - .help("The process id of the transfer to unlock") - ).arg( - Arg::with_name("datetime") - .long("date") - .value_name("DATETIME") - .takes_value(true) - .help("Optional arbitrary timestamp to apply") - ) - ).get_matches(); - - let config = parse_args(&matches)?; - let result = process_command(&config)?; - println!("{}", result); - Ok(()) -} diff --git a/book/blob_fetch_stage.rs b/book/blob_fetch_stage.rs deleted file mode 100644 index 54badfa50315db..00000000000000 --- a/book/blob_fetch_stage.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! The `blob_fetch_stage` pulls blobs from UDP sockets and sends it to a channel. - -use service::Service; -use std::net::UdpSocket; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc::channel; -use std::sync::Arc; -use std::thread::{self, JoinHandle}; -use streamer::{self, BlobReceiver}; - -pub struct BlobFetchStage { - exit: Arc, - thread_hdls: Vec>, -} - -impl BlobFetchStage { - pub fn new(socket: Arc, exit: Arc) -> (Self, BlobReceiver) { - Self::new_multi_socket(vec![socket], exit) - } - pub fn new_multi_socket( - sockets: Vec>, - exit: Arc, - ) -> (Self, BlobReceiver) { - let (sender, receiver) = channel(); - let thread_hdls: Vec<_> = sockets - .into_iter() - .map(|socket| streamer::blob_receiver(socket, exit.clone(), sender.clone())) - .collect(); - - (BlobFetchStage { exit, thread_hdls }, receiver) - } - - pub fn close(&self) { - self.exit.store(true, Ordering::Relaxed); - } -} - -impl Service for BlobFetchStage { - type JoinReturnType = (); - - fn join(self) -> thread::Result<()> { - for thread_hdl in self.thread_hdls { - thread_hdl.join()?; - } - Ok(()) - } -} diff --git a/book/bloom.rs b/book/bloom.rs deleted file mode 100644 index 2f74a2f3e05e01..00000000000000 --- a/book/bloom.rs +++ /dev/null @@ -1,105 +0,0 @@ -//! Simple Bloom Filter -use bv::BitVec; -use rand::{self, Rng}; -use std::cmp; -use std::marker::PhantomData; - -/// Generate a stable hash of `self` for each `hash_index` -/// Best effort can be made for uniqueness of each hash. -pub trait BloomHashIndex { - fn hash(&self, hash_index: u64) -> u64; -} - -#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)] -pub struct Bloom { - pub keys: Vec, - pub bits: BitVec, - _phantom: PhantomData, -} - -impl Bloom { - /// create filter optimal for num size given the `false_rate` - /// the keys are randomized for picking data out of a collision resistant hash of size - /// `keysize` bytes - /// https://hur.st/bloomfilter/ - pub fn random(num: usize, false_rate: f64, max_bits: usize) -> Self { - let min_num_bits = ((num as f64 * false_rate.log(2f64)) - / (1f64 / 2f64.powf(2f64.log(2f64))).log(2f64)).ceil() - as usize; - let num_bits = cmp::max(1, cmp::min(min_num_bits, max_bits)); - let num_keys = ((num_bits as f64 / num as f64) * 2f64.log(2f64)).round() as usize; - let keys: Vec = (0..num_keys).map(|_| rand::thread_rng().gen()).collect(); - let bits = BitVec::new_fill(false, num_bits as u64); - Bloom { - keys, - bits, - _phantom: Default::default(), - } - } - fn pos(&self, key: &T, k: u64) -> u64 { - key.hash(k) % self.bits.len() - } - pub fn add(&mut self, key: &T) { - for k in &self.keys { - let pos = self.pos(key, *k); - self.bits.set(pos, true); - } - } - pub fn contains(&mut self, key: &T) -> bool { - for k in &self.keys { - let pos = self.pos(key, *k); - if !self.bits.get(pos) { - return false; - } - } - true - } -} - -#[cfg(test)] -mod test { - use super::*; - use solana_sdk::hash::{hash, Hash}; - - #[test] - fn test_bloom_filter() { - //empty - let bloom: Bloom = Bloom::random(0, 0.1, 100); - assert_eq!(bloom.keys.len(), 0); - assert_eq!(bloom.bits.len(), 1); - - //normal - let bloom: Bloom = Bloom::random(10, 0.1, 100); - assert_eq!(bloom.keys.len(), 3); - assert_eq!(bloom.bits.len(), 34); - - //saturated - let bloom: Bloom = Bloom::random(100, 0.1, 100); - assert_eq!(bloom.keys.len(), 1); - assert_eq!(bloom.bits.len(), 100); - } - #[test] - fn test_add_contains() { - let mut bloom: Bloom = Bloom::random(100, 0.1, 100); - //known keys to avoid false positives in the test - bloom.keys = vec![0, 1, 2, 3]; - - let key = hash(b"hello"); - assert!(!bloom.contains(&key)); - bloom.add(&key); - assert!(bloom.contains(&key)); - - let key = hash(b"world"); - assert!(!bloom.contains(&key)); - bloom.add(&key); - assert!(bloom.contains(&key)); - } - #[test] - fn test_random() { - let mut b1: Bloom = Bloom::random(10, 0.1, 100); - let mut b2: Bloom = Bloom::random(10, 0.1, 100); - b1.keys.sort(); - b2.keys.sort(); - assert_ne!(b1.keys, b2.keys); - } -} diff --git a/book/book.js b/book/book.js deleted file mode 100644 index c9c2f4d63c2653..00000000000000 --- a/book/book.js +++ /dev/null @@ -1,600 +0,0 @@ -"use strict"; - -// Fix back button cache problem -window.onunload = function () { }; - -// Global variable, shared between modules -function playpen_text(playpen) { - let code_block = playpen.querySelector("code"); - - if (window.ace && code_block.classList.contains("editable")) { - let editor = window.ace.edit(code_block); - return editor.getValue(); - } else { - return code_block.textContent; - } -} - -(function codeSnippets() { - // Hide Rust code lines prepended with a specific character - var hiding_character = "#"; - - function fetch_with_timeout(url, options, timeout = 6000) { - return Promise.race([ - fetch(url, options), - new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout)) - ]); - } - - var playpens = Array.from(document.querySelectorAll(".playpen")); - if (playpens.length > 0) { - fetch_with_timeout("https://play.rust-lang.org/meta/crates", { - headers: { - 'Content-Type': "application/json", - }, - method: 'POST', - mode: 'cors', - }) - .then(response => response.json()) - .then(response => { - // get list of crates available in the rust playground - let playground_crates = response.crates.map(item => item["id"]); - playpens.forEach(block => handle_crate_list_update(block, playground_crates)); - }); - } - - function handle_crate_list_update(playpen_block, playground_crates) { - // update the play buttons after receiving the response - update_play_button(playpen_block, playground_crates); - - // and install on change listener to dynamically update ACE editors - if (window.ace) { - let code_block = playpen_block.querySelector("code"); - if (code_block.classList.contains("editable")) { - let editor = window.ace.edit(code_block); - editor.addEventListener("change", function (e) { - update_play_button(playpen_block, playground_crates); - }); - } - } - } - - // updates the visibility of play button based on `no_run` class and - // used crates vs ones available on http://play.rust-lang.org - function update_play_button(pre_block, playground_crates) { - var play_button = pre_block.querySelector(".play-button"); - - // skip if code is `no_run` - if (pre_block.querySelector('code').classList.contains("no_run")) { - play_button.classList.add("hidden"); - return; - } - - // get list of `extern crate`'s from snippet - var txt = playpen_text(pre_block); - var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g; - var snippet_crates = []; - var item; - while (item = re.exec(txt)) { - snippet_crates.push(item[1]); - } - - // check if all used crates are available on play.rust-lang.org - var all_available = snippet_crates.every(function (elem) { - return playground_crates.indexOf(elem) > -1; - }); - - if (all_available) { - play_button.classList.remove("hidden"); - } else { - play_button.classList.add("hidden"); - } - } - - function run_rust_code(code_block) { - var result_block = code_block.querySelector(".result"); - if (!result_block) { - result_block = document.createElement('code'); - result_block.className = 'result hljs language-bash'; - - code_block.append(result_block); - } - - let text = playpen_text(code_block); - - var params = { - version: "stable", - optimize: "0", - code: text - }; - - if (text.indexOf("#![feature") !== -1) { - params.version = "nightly"; - } - - result_block.innerText = "Running..."; - - fetch_with_timeout("https://play.rust-lang.org/evaluate.json", { - headers: { - 'Content-Type': "application/json", - }, - method: 'POST', - mode: 'cors', - body: JSON.stringify(params) - }) - .then(response => response.json()) - .then(response => result_block.innerText = response.result) - .catch(error => result_block.innerText = "Playground Communication: " + error.message); - } - - // Syntax highlighting Configuration - hljs.configure({ - tabReplace: ' ', // 4 spaces - languages: [], // Languages used for auto-detection - }); - - if (window.ace) { - // language-rust class needs to be removed for editable - // blocks or highlightjs will capture events - Array - .from(document.querySelectorAll('code.editable')) - .forEach(function (block) { block.classList.remove('language-rust'); }); - - Array - .from(document.querySelectorAll('code:not(.editable)')) - .forEach(function (block) { hljs.highlightBlock(block); }); - } else { - Array - .from(document.querySelectorAll('code')) - .forEach(function (block) { hljs.highlightBlock(block); }); - } - - // Adding the hljs class gives code blocks the color css - // even if highlighting doesn't apply - Array - .from(document.querySelectorAll('code')) - .forEach(function (block) { block.classList.add('hljs'); }); - - Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) { - - var code_block = block; - var pre_block = block.parentNode; - // hide lines - var lines = code_block.innerHTML.split("\n"); - var first_non_hidden_line = false; - var lines_hidden = false; - var trimmed_line = ""; - - for (var n = 0; n < lines.length; n++) { - trimmed_line = lines[n].trim(); - if (trimmed_line[0] == hiding_character && trimmed_line[1] != hiding_character) { - if (first_non_hidden_line) { - lines[n] = "" + "\n" + lines[n].replace(/(\s*)# ?/, "$1") + ""; - } - else { - lines[n] = "" + lines[n].replace(/(\s*)# ?/, "$1") + "\n" + ""; - } - lines_hidden = true; - } - else if (first_non_hidden_line) { - lines[n] = "\n" + lines[n]; - } - else { - first_non_hidden_line = true; - } - if (trimmed_line[0] == hiding_character && trimmed_line[1] == hiding_character) { - lines[n] = lines[n].replace("##", "#") - } - } - code_block.innerHTML = lines.join(""); - - // If no lines were hidden, return - if (!lines_hidden) { return; } - - var buttons = document.createElement('div'); - buttons.className = 'buttons'; - buttons.innerHTML = ""; - - // add expand button - pre_block.insertBefore(buttons, pre_block.firstChild); - - pre_block.querySelector('.buttons').addEventListener('click', function (e) { - if (e.target.classList.contains('fa-expand')) { - var lines = pre_block.querySelectorAll('span.hidden'); - - e.target.classList.remove('fa-expand'); - e.target.classList.add('fa-compress'); - e.target.title = 'Hide lines'; - e.target.setAttribute('aria-label', e.target.title); - - Array.from(lines).forEach(function (line) { - line.classList.remove('hidden'); - line.classList.add('unhidden'); - }); - } else if (e.target.classList.contains('fa-compress')) { - var lines = pre_block.querySelectorAll('span.unhidden'); - - e.target.classList.remove('fa-compress'); - e.target.classList.add('fa-expand'); - e.target.title = 'Show hidden lines'; - e.target.setAttribute('aria-label', e.target.title); - - Array.from(lines).forEach(function (line) { - line.classList.remove('unhidden'); - line.classList.add('hidden'); - }); - } - }); - }); - - Array.from(document.querySelectorAll('pre code')).forEach(function (block) { - var pre_block = block.parentNode; - if (!pre_block.classList.contains('playpen')) { - var buttons = pre_block.querySelector(".buttons"); - if (!buttons) { - buttons = document.createElement('div'); - buttons.className = 'buttons'; - pre_block.insertBefore(buttons, pre_block.firstChild); - } - - var clipButton = document.createElement('button'); - clipButton.className = 'fa fa-copy clip-button'; - clipButton.title = 'Copy to clipboard'; - clipButton.setAttribute('aria-label', clipButton.title); - clipButton.innerHTML = ''; - - buttons.insertBefore(clipButton, buttons.firstChild); - } - }); - - // Process playpen code blocks - Array.from(document.querySelectorAll(".playpen")).forEach(function (pre_block) { - // Add play button - var buttons = pre_block.querySelector(".buttons"); - if (!buttons) { - buttons = document.createElement('div'); - buttons.className = 'buttons'; - pre_block.insertBefore(buttons, pre_block.firstChild); - } - - var runCodeButton = document.createElement('button'); - runCodeButton.className = 'fa fa-play play-button'; - runCodeButton.hidden = true; - runCodeButton.title = 'Run this code'; - runCodeButton.setAttribute('aria-label', runCodeButton.title); - - var copyCodeClipboardButton = document.createElement('button'); - copyCodeClipboardButton.className = 'fa fa-copy clip-button'; - copyCodeClipboardButton.innerHTML = ''; - copyCodeClipboardButton.title = 'Copy to clipboard'; - copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title); - - buttons.insertBefore(runCodeButton, buttons.firstChild); - buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild); - - runCodeButton.addEventListener('click', function (e) { - run_rust_code(pre_block); - }); - - let code_block = pre_block.querySelector("code"); - if (window.ace && code_block.classList.contains("editable")) { - var undoChangesButton = document.createElement('button'); - undoChangesButton.className = 'fa fa-history reset-button'; - undoChangesButton.title = 'Undo changes'; - undoChangesButton.setAttribute('aria-label', undoChangesButton.title); - - buttons.insertBefore(undoChangesButton, buttons.firstChild); - - undoChangesButton.addEventListener('click', function () { - let editor = window.ace.edit(code_block); - editor.setValue(editor.originalCode); - editor.clearSelection(); - }); - } - }); -})(); - -(function themes() { - var html = document.querySelector('html'); - var themeToggleButton = document.getElementById('theme-toggle'); - var themePopup = document.getElementById('theme-list'); - var themeColorMetaTag = document.querySelector('meta[name="theme-color"]'); - var stylesheets = { - ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"), - tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"), - highlight: document.querySelector("[href$='highlight.css']"), - }; - - function showThemes() { - themePopup.style.display = 'block'; - themeToggleButton.setAttribute('aria-expanded', true); - themePopup.querySelector("button#" + document.body.className).focus(); - } - - function hideThemes() { - themePopup.style.display = 'none'; - themeToggleButton.setAttribute('aria-expanded', false); - themeToggleButton.focus(); - } - - function set_theme(theme) { - let ace_theme; - - if (theme == 'coal' || theme == 'navy') { - stylesheets.ayuHighlight.disabled = true; - stylesheets.tomorrowNight.disabled = false; - stylesheets.highlight.disabled = true; - - ace_theme = "ace/theme/tomorrow_night"; - } else if (theme == 'ayu') { - stylesheets.ayuHighlight.disabled = false; - stylesheets.tomorrowNight.disabled = true; - stylesheets.highlight.disabled = true; - - ace_theme = "ace/theme/tomorrow_night"; - } else { - stylesheets.ayuHighlight.disabled = true; - stylesheets.tomorrowNight.disabled = true; - stylesheets.highlight.disabled = false; - - ace_theme = "ace/theme/dawn"; - } - - setTimeout(function () { - themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor; - }, 1); - - if (window.ace && window.editors) { - window.editors.forEach(function (editor) { - editor.setTheme(ace_theme); - }); - } - - var previousTheme; - try { previousTheme = localStorage.getItem('mdbook-theme'); } catch (e) { } - if (previousTheme === null || previousTheme === undefined) { previousTheme = 'light'; } - - try { localStorage.setItem('mdbook-theme', theme); } catch (e) { } - - document.body.className = theme; - html.classList.remove(previousTheme); - html.classList.add(theme); - } - - // Set theme - var theme; - try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { } - if (theme === null || theme === undefined) { theme = 'light'; } - - set_theme(theme); - - themeToggleButton.addEventListener('click', function () { - if (themePopup.style.display === 'block') { - hideThemes(); - } else { - showThemes(); - } - }); - - themePopup.addEventListener('click', function (e) { - var theme = e.target.id || e.target.parentElement.id; - set_theme(theme); - }); - - themePopup.addEventListener('focusout', function(e) { - // e.relatedTarget is null in Safari and Firefox on macOS (see workaround below) - if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) { - hideThemes(); - } - }); - - // Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang-nursery/mdBook/issues/628 - document.addEventListener('click', function(e) { - if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) { - hideThemes(); - } - }); - - document.addEventListener('keydown', function (e) { - if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } - if (!themePopup.contains(e.target)) { return; } - - switch (e.key) { - case 'Escape': - e.preventDefault(); - hideThemes(); - break; - case 'ArrowUp': - e.preventDefault(); - var li = document.activeElement.parentElement; - if (li && li.previousElementSibling) { - li.previousElementSibling.querySelector('button').focus(); - } - break; - case 'ArrowDown': - e.preventDefault(); - var li = document.activeElement.parentElement; - if (li && li.nextElementSibling) { - li.nextElementSibling.querySelector('button').focus(); - } - break; - case 'Home': - e.preventDefault(); - themePopup.querySelector('li:first-child button').focus(); - break; - case 'End': - e.preventDefault(); - themePopup.querySelector('li:last-child button').focus(); - break; - } - }); -})(); - -(function sidebar() { - var html = document.querySelector("html"); - var sidebar = document.getElementById("sidebar"); - var sidebarLinks = document.querySelectorAll('#sidebar a'); - var sidebarToggleButton = document.getElementById("sidebar-toggle"); - var firstContact = null; - - function showSidebar() { - html.classList.remove('sidebar-hidden') - html.classList.add('sidebar-visible'); - Array.from(sidebarLinks).forEach(function (link) { - link.setAttribute('tabIndex', 0); - }); - sidebarToggleButton.setAttribute('aria-expanded', true); - sidebar.setAttribute('aria-hidden', false); - try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { } - } - - function hideSidebar() { - html.classList.remove('sidebar-visible') - html.classList.add('sidebar-hidden'); - Array.from(sidebarLinks).forEach(function (link) { - link.setAttribute('tabIndex', -1); - }); - sidebarToggleButton.setAttribute('aria-expanded', false); - sidebar.setAttribute('aria-hidden', true); - try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { } - } - - // Toggle sidebar - sidebarToggleButton.addEventListener('click', function sidebarToggle() { - if (html.classList.contains("sidebar-hidden")) { - showSidebar(); - } else if (html.classList.contains("sidebar-visible")) { - hideSidebar(); - } else { - if (getComputedStyle(sidebar)['transform'] === 'none') { - hideSidebar(); - } else { - showSidebar(); - } - } - }); - - document.addEventListener('touchstart', function (e) { - firstContact = { - x: e.touches[0].clientX, - time: Date.now() - }; - }, { passive: true }); - - document.addEventListener('touchmove', function (e) { - if (!firstContact) - return; - - var curX = e.touches[0].clientX; - var xDiff = curX - firstContact.x, - tDiff = Date.now() - firstContact.time; - - if (tDiff < 250 && Math.abs(xDiff) >= 150) { - if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300)) - showSidebar(); - else if (xDiff < 0 && curX < 300) - hideSidebar(); - - firstContact = null; - } - }, { passive: true }); - - // Scroll sidebar to current active section - var activeSection = sidebar.querySelector(".active"); - if (activeSection) { - sidebar.scrollTop = activeSection.offsetTop; - } -})(); - -(function chapterNavigation() { - document.addEventListener('keydown', function (e) { - if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } - if (window.search && window.search.hasFocus()) { return; } - - switch (e.key) { - case 'ArrowRight': - e.preventDefault(); - var nextButton = document.querySelector('.nav-chapters.next'); - if (nextButton) { - window.location.href = nextButton.href; - } - break; - case 'ArrowLeft': - e.preventDefault(); - var previousButton = document.querySelector('.nav-chapters.previous'); - if (previousButton) { - window.location.href = previousButton.href; - } - break; - } - }); -})(); - -(function clipboard() { - var clipButtons = document.querySelectorAll('.clip-button'); - - function hideTooltip(elem) { - elem.firstChild.innerText = ""; - elem.className = 'fa fa-copy clip-button'; - } - - function showTooltip(elem, msg) { - elem.firstChild.innerText = msg; - elem.className = 'fa fa-copy tooltipped'; - } - - var clipboardSnippets = new Clipboard('.clip-button', { - text: function (trigger) { - hideTooltip(trigger); - let playpen = trigger.closest("pre"); - return playpen_text(playpen); - } - }); - - Array.from(clipButtons).forEach(function (clipButton) { - clipButton.addEventListener('mouseout', function (e) { - hideTooltip(e.currentTarget); - }); - }); - - clipboardSnippets.on('success', function (e) { - e.clearSelection(); - showTooltip(e.trigger, "Copied!"); - }); - - clipboardSnippets.on('error', function (e) { - showTooltip(e.trigger, "Clipboard error!"); - }); -})(); - -(function scrollToTop () { - var menuTitle = document.querySelector('.menu-title'); - - menuTitle.addEventListener('click', function () { - document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' }); - }); -})(); - -(function autoHideMenu() { - var menu = document.getElementById('menu-bar'); - - var previousScrollTop = document.scrollingElement.scrollTop; - - document.addEventListener('scroll', function () { - if (menu.classList.contains('folded') && document.scrollingElement.scrollTop < previousScrollTop) { - menu.classList.remove('folded'); - } else if (!menu.classList.contains('folded') && document.scrollingElement.scrollTop > previousScrollTop) { - menu.classList.add('folded'); - } - - if (!menu.classList.contains('bordered') && document.scrollingElement.scrollTop > 0) { - menu.classList.add('bordered'); - } - - if (menu.classList.contains('bordered') && document.scrollingElement.scrollTop === 0) { - menu.classList.remove('bordered'); - } - - previousScrollTop = document.scrollingElement.scrollTop; - }, { passive: true }); -})(); diff --git a/book/bpf_loader.rs b/book/bpf_loader.rs deleted file mode 100644 index 156c2110cd634a..00000000000000 --- a/book/bpf_loader.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! BPF loader -use native_loader; -use solana_sdk::account::Account; -use solana_sdk::pubkey::Pubkey; - -const BPF_LOADER_NAME: &str = "solana_bpf_loader"; -const BPF_LOADER_PROGRAM_ID: [u8; 32] = [ - 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, -]; - -pub fn id() -> Pubkey { - Pubkey::new(&BPF_LOADER_PROGRAM_ID) -} - -pub fn account() -> Account { - Account { - tokens: 1, - owner: id(), - userdata: BPF_LOADER_NAME.as_bytes().to_vec(), - executable: true, - loader: native_loader::id(), - } -} diff --git a/book/broadcast_stage.rs b/book/broadcast_stage.rs deleted file mode 100644 index 010fd58f3b025e..00000000000000 --- a/book/broadcast_stage.rs +++ /dev/null @@ -1,299 +0,0 @@ -//! The `broadcast_stage` broadcasts data from a leader node to validators -//! -use cluster_info::{ClusterInfo, ClusterInfoError, NodeInfo}; -use counter::Counter; -use entry::Entry; -#[cfg(feature = "erasure")] -use erasure; - -use ledger::Block; -use log::Level; -use packet::{index_blobs, SharedBlobs}; -use rayon::prelude::*; -use result::{Error, Result}; -use service::Service; -use solana_metrics::{influxdb, submit}; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::timing::duration_as_ms; -use std::net::UdpSocket; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::sync::mpsc::{Receiver, RecvTimeoutError}; -use std::sync::{Arc, RwLock}; -use std::thread::{self, Builder, JoinHandle}; -use std::time::{Duration, Instant}; -use window::{SharedWindow, WindowIndex, WindowUtil}; - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum BroadcastStageReturnType { - LeaderRotation, - ChannelDisconnected, -} - -#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] -fn broadcast( - max_tick_height: Option, - tick_height: &mut u64, - leader_id: Pubkey, - node_info: &NodeInfo, - broadcast_table: &[NodeInfo], - window: &SharedWindow, - receiver: &Receiver>, - sock: &UdpSocket, - transmit_index: &mut WindowIndex, - receive_index: &mut u64, - leader_slot: u64, -) -> Result<()> { - let id = node_info.id; - let timer = Duration::new(1, 0); - let entries = receiver.recv_timeout(timer)?; - let now = Instant::now(); - let mut num_entries = entries.len(); - let mut ventries = Vec::new(); - ventries.push(entries); - while let Ok(entries) = receiver.try_recv() { - num_entries += entries.len(); - ventries.push(entries); - } - inc_new_counter_info!("broadcast_stage-entries_received", num_entries); - - let to_blobs_start = Instant::now(); - let num_ticks: u64 = ventries - .iter() - .flatten() - .map(|entry| (entry.is_tick()) as u64) - .sum(); - - *tick_height += num_ticks; - - let dq: SharedBlobs = ventries - .into_par_iter() - .flat_map(|p| p.to_blobs()) - .collect(); - - let to_blobs_elapsed = duration_as_ms(&to_blobs_start.elapsed()); - - // flatten deque to vec - let blobs_vec: SharedBlobs = dq.into_iter().collect(); - - let blobs_chunking = Instant::now(); - // We could receive more blobs than window slots so - // break them up into window-sized chunks to process - let window_size = window.read().unwrap().window_size(); - let blobs_chunked = blobs_vec.chunks(window_size as usize).map(|x| x.to_vec()); - let chunking_elapsed = duration_as_ms(&blobs_chunking.elapsed()); - - let broadcast_start = Instant::now(); - for mut blobs in blobs_chunked { - let blobs_len = blobs.len(); - trace!("{}: broadcast blobs.len: {}", id, blobs_len); - - index_blobs(&blobs, &node_info.id, *receive_index, leader_slot); - - // keep the cache of blobs that are broadcast - inc_new_counter_info!("streamer-broadcast-sent", blobs.len()); - { - let mut win = window.write().unwrap(); - assert!(blobs.len() <= win.len()); - for b in &blobs { - let ix = b.read().unwrap().index().expect("blob index"); - let pos = (ix % window_size) as usize; - if let Some(x) = win[pos].data.take() { - trace!( - "{} popped {} at {}", - id, - x.read().unwrap().index().unwrap(), - pos - ); - } - if let Some(x) = win[pos].coding.take() { - trace!( - "{} popped {} at {}", - id, - x.read().unwrap().index().unwrap(), - pos - ); - } - - trace!("{} null {}", id, pos); - } - for b in &blobs { - let ix = b.read().unwrap().index().expect("blob index"); - let pos = (ix % window_size) as usize; - trace!("{} caching {} at {}", id, ix, pos); - assert!(win[pos].data.is_none()); - win[pos].data = Some(b.clone()); - } - } - - // Fill in the coding blob data from the window data blobs - #[cfg(feature = "erasure")] - { - erasure::generate_coding( - &id, - &mut window.write().unwrap(), - *receive_index, - blobs_len, - &mut transmit_index.coding, - )?; - } - - *receive_index += blobs_len as u64; - - // Send blobs out from the window - ClusterInfo::broadcast( - Some(*tick_height) == max_tick_height, - leader_id, - &node_info, - &broadcast_table, - &window, - &sock, - transmit_index, - *receive_index, - )?; - } - let broadcast_elapsed = duration_as_ms(&broadcast_start.elapsed()); - - inc_new_counter_info!( - "broadcast_stage-time_ms", - duration_as_ms(&now.elapsed()) as usize - ); - info!( - "broadcast: {} entries, blob time {} chunking time {} broadcast time {}", - num_entries, to_blobs_elapsed, chunking_elapsed, broadcast_elapsed - ); - - submit( - influxdb::Point::new("broadcast-stage") - .add_field( - "transmit-index", - influxdb::Value::Integer(transmit_index.data as i64), - ).to_owned(), - ); - - Ok(()) -} - -// Implement a destructor for the BroadcastStage thread to signal it exited -// even on panics -struct Finalizer { - exit_sender: Arc, -} - -impl Finalizer { - fn new(exit_sender: Arc) -> Self { - Finalizer { exit_sender } - } -} -// Implement a destructor for Finalizer. -impl Drop for Finalizer { - fn drop(&mut self) { - self.exit_sender.clone().store(true, Ordering::Relaxed); - } -} - -pub struct BroadcastStage { - thread_hdl: JoinHandle, -} - -impl BroadcastStage { - fn run( - sock: &UdpSocket, - cluster_info: &Arc>, - window: &SharedWindow, - entry_height: u64, - leader_slot: u64, - receiver: &Receiver>, - max_tick_height: Option, - tick_height: u64, - ) -> BroadcastStageReturnType { - let mut transmit_index = WindowIndex { - data: entry_height, - coding: entry_height, - }; - let mut receive_index = entry_height; - let me = cluster_info.read().unwrap().my_data().clone(); - let mut tick_height_ = tick_height; - loop { - let broadcast_table = cluster_info.read().unwrap().tvu_peers(); - let leader_id = cluster_info.read().unwrap().leader_id(); - if let Err(e) = broadcast( - max_tick_height, - &mut tick_height_, - leader_id, - &me, - &broadcast_table, - &window, - &receiver, - &sock, - &mut transmit_index, - &mut receive_index, - leader_slot, - ) { - match e { - Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => { - return BroadcastStageReturnType::ChannelDisconnected - } - Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), - Error::ClusterInfoError(ClusterInfoError::NoPeers) => (), // TODO: Why are the unit-tests throwing hundreds of these? - _ => { - inc_new_counter_info!("streamer-broadcaster-error", 1, 1); - error!("broadcaster error: {:?}", e); - } - } - } - } - } - - /// Service to broadcast messages from the leader to layer 1 nodes. - /// See `cluster_info` for network layer definitions. - /// # Arguments - /// * `sock` - Socket to send from. - /// * `exit` - Boolean to signal system exit. - /// * `cluster_info` - ClusterInfo structure - /// * `window` - Cache of blobs that we have broadcast - /// * `receiver` - Receive channel for blobs to be retransmitted to all the layer 1 nodes. - /// * `exit_sender` - Set to true when this stage exits, allows rest of Tpu to exit cleanly. Otherwise, - /// when a Tpu stage closes, it only closes the stages that come after it. The stages - /// that come before could be blocked on a receive, and never notice that they need to - /// exit. Now, if any stage of the Tpu closes, it will lead to closing the WriteStage (b/c - /// WriteStage is the last stage in the pipeline), which will then close Broadcast stage, - /// which will then close FetchStage in the Tpu, and then the rest of the Tpu, - /// completing the cycle. - pub fn new( - sock: UdpSocket, - cluster_info: Arc>, - window: SharedWindow, - entry_height: u64, - leader_slot: u64, - receiver: Receiver>, - max_tick_height: Option, - tick_height: u64, - exit_sender: Arc, - ) -> Self { - let thread_hdl = Builder::new() - .name("solana-broadcaster".to_string()) - .spawn(move || { - let _exit = Finalizer::new(exit_sender); - Self::run( - &sock, - &cluster_info, - &window, - entry_height, - leader_slot, - &receiver, - max_tick_height, - tick_height, - ) - }).unwrap(); - - BroadcastStage { thread_hdl } - } -} - -impl Service for BroadcastStage { - type JoinReturnType = BroadcastStageReturnType; - - fn join(self) -> thread::Result { - self.thread_hdl.join() - } -} diff --git a/book/budget_expr.rs b/book/budget_expr.rs deleted file mode 100644 index ef1a1fbebd81be..00000000000000 --- a/book/budget_expr.rs +++ /dev/null @@ -1,232 +0,0 @@ -//! The `budget_expr` module provides a domain-specific language for payment plans. Users create BudgetExpr objects that -//! are given to an interpreter. The interpreter listens for `Witness` transactions, -//! which it uses to reduce the payment plan. When the budget is reduced to a -//! `Payment`, the payment is executed. - -use chrono::prelude::*; -use payment_plan::{Payment, Witness}; -use solana_sdk::pubkey::Pubkey; -use std::mem; - -/// A data type representing a `Witness` that the payment plan is waiting on. -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub enum Condition { - /// Wait for a `Timestamp` `Witness` at or after the given `DateTime`. - Timestamp(DateTime, Pubkey), - - /// Wait for a `Signature` `Witness` from `Pubkey`. - Signature(Pubkey), -} - -impl Condition { - /// Return true if the given Witness satisfies this Condition. - pub fn is_satisfied(&self, witness: &Witness, from: &Pubkey) -> bool { - match (self, witness) { - (Condition::Signature(pubkey), Witness::Signature) => pubkey == from, - (Condition::Timestamp(dt, pubkey), Witness::Timestamp(last_time)) => { - pubkey == from && dt <= last_time - } - _ => false, - } - } -} - -/// A data type representing a payment plan. -#[repr(C)] -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub enum BudgetExpr { - /// Make a payment. - Pay(Payment), - - /// Make a payment after some condition. - After(Condition, Payment), - - /// Either make a payment after one condition or a different payment after another - /// condition, which ever condition is satisfied first. - Or((Condition, Payment), (Condition, Payment)), - - /// Make a payment after both of two conditions are satisfied - And(Condition, Condition, Payment), -} - -impl BudgetExpr { - /// Create the simplest budget - one that pays `tokens` to Pubkey. - pub fn new_payment(tokens: u64, to: Pubkey) -> Self { - BudgetExpr::Pay(Payment { tokens, to }) - } - - /// Create a budget that pays `tokens` to `to` after being witnessed by `from`. - pub fn new_authorized_payment(from: Pubkey, tokens: u64, to: Pubkey) -> Self { - BudgetExpr::After(Condition::Signature(from), Payment { tokens, to }) - } - - /// Create a budget that pays tokens` to `to` after being witnessed by 2x `from`s - pub fn new_2_2_multisig_payment(from0: Pubkey, from1: Pubkey, tokens: u64, to: Pubkey) -> Self { - BudgetExpr::And( - Condition::Signature(from0), - Condition::Signature(from1), - Payment { tokens, to }, - ) - } - - /// Create a budget that pays `tokens` to `to` after the given DateTime. - pub fn new_future_payment(dt: DateTime, from: Pubkey, tokens: u64, to: Pubkey) -> Self { - BudgetExpr::After(Condition::Timestamp(dt, from), Payment { tokens, to }) - } - - /// Create a budget that pays `tokens` to `to` after the given DateTime - /// unless cancelled by `from`. - pub fn new_cancelable_future_payment( - dt: DateTime, - from: Pubkey, - tokens: u64, - to: Pubkey, - ) -> Self { - BudgetExpr::Or( - (Condition::Timestamp(dt, from), Payment { tokens, to }), - (Condition::Signature(from), Payment { tokens, to: from }), - ) - } - - /// Return Payment if the budget requires no additional Witnesses. - pub fn final_payment(&self) -> Option { - match self { - BudgetExpr::Pay(payment) => Some(payment.clone()), - _ => None, - } - } - - /// Return true if the budget spends exactly `spendable_tokens`. - pub fn verify(&self, spendable_tokens: u64) -> bool { - match self { - BudgetExpr::Pay(payment) - | BudgetExpr::After(_, payment) - | BudgetExpr::And(_, _, payment) => payment.tokens == spendable_tokens, - BudgetExpr::Or(a, b) => { - a.1.tokens == spendable_tokens && b.1.tokens == spendable_tokens - } - } - } - - /// Apply a witness to the budget to see if the budget can be reduced. - /// If so, modify the budget in-place. - pub fn apply_witness(&mut self, witness: &Witness, from: &Pubkey) { - let new_expr = match self { - BudgetExpr::After(cond, payment) if cond.is_satisfied(witness, from) => { - Some(BudgetExpr::Pay(payment.clone())) - } - BudgetExpr::Or((cond, payment), _) if cond.is_satisfied(witness, from) => { - Some(BudgetExpr::Pay(payment.clone())) - } - BudgetExpr::Or(_, (cond, payment)) if cond.is_satisfied(witness, from) => { - Some(BudgetExpr::Pay(payment.clone())) - } - BudgetExpr::And(cond0, cond1, payment) => { - if cond0.is_satisfied(witness, from) { - Some(BudgetExpr::After(cond1.clone(), payment.clone())) - } else if cond1.is_satisfied(witness, from) { - Some(BudgetExpr::After(cond0.clone(), payment.clone())) - } else { - None - } - } - _ => None, - }; - if let Some(expr) = new_expr { - mem::replace(self, expr); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use signature::{Keypair, KeypairUtil}; - - #[test] - fn test_signature_satisfied() { - let from = Pubkey::default(); - assert!(Condition::Signature(from).is_satisfied(&Witness::Signature, &from)); - } - - #[test] - fn test_timestamp_satisfied() { - let dt1 = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); - let dt2 = Utc.ymd(2014, 11, 14).and_hms(10, 9, 8); - let from = Pubkey::default(); - assert!(Condition::Timestamp(dt1, from).is_satisfied(&Witness::Timestamp(dt1), &from)); - assert!(Condition::Timestamp(dt1, from).is_satisfied(&Witness::Timestamp(dt2), &from)); - assert!(!Condition::Timestamp(dt2, from).is_satisfied(&Witness::Timestamp(dt1), &from)); - } - - #[test] - fn test_verify() { - let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); - let from = Pubkey::default(); - let to = Pubkey::default(); - assert!(BudgetExpr::new_payment(42, to).verify(42)); - assert!(BudgetExpr::new_authorized_payment(from, 42, to).verify(42)); - assert!(BudgetExpr::new_future_payment(dt, from, 42, to).verify(42)); - assert!(BudgetExpr::new_cancelable_future_payment(dt, from, 42, to).verify(42)); - } - - #[test] - fn test_authorized_payment() { - let from = Pubkey::default(); - let to = Pubkey::default(); - - let mut expr = BudgetExpr::new_authorized_payment(from, 42, to); - expr.apply_witness(&Witness::Signature, &from); - assert_eq!(expr, BudgetExpr::new_payment(42, to)); - } - - #[test] - fn test_future_payment() { - let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); - let from = Keypair::new().pubkey(); - let to = Keypair::new().pubkey(); - - let mut expr = BudgetExpr::new_future_payment(dt, from, 42, to); - expr.apply_witness(&Witness::Timestamp(dt), &from); - assert_eq!(expr, BudgetExpr::new_payment(42, to)); - } - - #[test] - fn test_unauthorized_future_payment() { - // Ensure timestamp will only be acknowledged if it came from the - // whitelisted public key. - let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); - let from = Keypair::new().pubkey(); - let to = Keypair::new().pubkey(); - - let mut expr = BudgetExpr::new_future_payment(dt, from, 42, to); - let orig_expr = expr.clone(); - expr.apply_witness(&Witness::Timestamp(dt), &to); // <-- Attack! - assert_eq!(expr, orig_expr); - } - - #[test] - fn test_cancelable_future_payment() { - let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); - let from = Pubkey::default(); - let to = Pubkey::default(); - - let mut expr = BudgetExpr::new_cancelable_future_payment(dt, from, 42, to); - expr.apply_witness(&Witness::Timestamp(dt), &from); - assert_eq!(expr, BudgetExpr::new_payment(42, to)); - - let mut expr = BudgetExpr::new_cancelable_future_payment(dt, from, 42, to); - expr.apply_witness(&Witness::Signature, &from); - assert_eq!(expr, BudgetExpr::new_payment(42, from)); - } - #[test] - fn test_2_2_multisig_payment() { - let from0 = Keypair::new().pubkey(); - let from1 = Keypair::new().pubkey(); - let to = Pubkey::default(); - - let mut expr = BudgetExpr::new_2_2_multisig_payment(from0, from1, 42, to); - expr.apply_witness(&Witness::Signature, &from0); - assert_eq!(expr, BudgetExpr::new_authorized_payment(from1, 42, to)); - } -} diff --git a/book/budget_instruction.rs b/book/budget_instruction.rs deleted file mode 100644 index 78ca94e2ac358b..00000000000000 --- a/book/budget_instruction.rs +++ /dev/null @@ -1,24 +0,0 @@ -use budget_expr::BudgetExpr; -use chrono::prelude::{DateTime, Utc}; - -/// A smart contract. -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub struct Contract { - /// The number of tokens allocated to the `BudgetExpr` and any transaction fees. - pub tokens: u64, - pub budget_expr: BudgetExpr, -} - -/// An instruction to progress the smart contract. -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub enum Instruction { - /// Declare and instantiate `BudgetExpr`. - NewBudget(BudgetExpr), - - /// Tell a payment plan acknowledge the given `DateTime` has past. - ApplyTimestamp(DateTime), - - /// Tell the budget that the `NewBudget` with `Signature` has been - /// signed by the containing transaction's `Pubkey`. - ApplySignature, -} diff --git a/book/budget_program.rs b/book/budget_program.rs deleted file mode 100644 index 16603ddfcbd59f..00000000000000 --- a/book/budget_program.rs +++ /dev/null @@ -1,641 +0,0 @@ -//! budget program -use bincode::{self, deserialize, serialize_into, serialized_size}; -use budget_expr::BudgetExpr; -use budget_instruction::Instruction; -use chrono::prelude::{DateTime, Utc}; -use payment_plan::Witness; -use solana_sdk::account::Account; -use solana_sdk::pubkey::Pubkey; -use std::io; -use transaction::Transaction; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] -pub enum BudgetError { - InsufficientFunds, - ContractAlreadyExists, - ContractNotPending, - SourceIsPendingContract, - UninitializedContract, - NegativeTokens, - DestinationMissing, - FailedWitness, - UserdataTooSmall, - UserdataDeserializeFailure, - UnsignedKey, -} - -#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] -pub struct BudgetState { - pub initialized: bool, - pub pending_budget: Option, -} - -const BUDGET_PROGRAM_ID: [u8; 32] = [ - 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, -]; - -impl BudgetState { - fn is_pending(&self) -> bool { - self.pending_budget != None - } - pub fn id() -> Pubkey { - Pubkey::new(&BUDGET_PROGRAM_ID) - } - pub fn check_id(program_id: &Pubkey) -> bool { - program_id.as_ref() == BUDGET_PROGRAM_ID - } - - /// Process a Witness Signature. Any payment plans waiting on this signature - /// will progress one step. - fn apply_signature( - &mut self, - tx: &Transaction, - instruction_index: usize, - accounts: &mut [&mut Account], - ) -> Result<(), BudgetError> { - let mut final_payment = None; - if let Some(ref mut expr) = self.pending_budget { - let key = match tx.signed_key(instruction_index, 0) { - None => return Err(BudgetError::UnsignedKey), - Some(key) => key, - }; - expr.apply_witness(&Witness::Signature, key); - final_payment = expr.final_payment(); - } - - if let Some(payment) = final_payment { - if Some(&payment.to) != tx.key(instruction_index, 2) { - trace!("destination missing"); - return Err(BudgetError::DestinationMissing); - } - self.pending_budget = None; - accounts[1].tokens -= payment.tokens; - accounts[2].tokens += payment.tokens; - } - Ok(()) - } - - /// Process a Witness Timestamp. Any payment plans waiting on this timestamp - /// will progress one step. - fn apply_timestamp( - &mut self, - tx: &Transaction, - instruction_index: usize, - accounts: &mut [&mut Account], - dt: DateTime, - ) -> Result<(), BudgetError> { - // Check to see if any timelocked transactions can be completed. - let mut final_payment = None; - - if let Some(ref mut expr) = self.pending_budget { - let key = match tx.signed_key(instruction_index, 0) { - None => return Err(BudgetError::UnsignedKey), - Some(key) => key, - }; - expr.apply_witness(&Witness::Timestamp(dt), key); - final_payment = expr.final_payment(); - } - - if let Some(payment) = final_payment { - if Some(&payment.to) != tx.key(instruction_index, 2) { - trace!("destination missing"); - return Err(BudgetError::DestinationMissing); - } - self.pending_budget = None; - accounts[1].tokens -= payment.tokens; - accounts[2].tokens += payment.tokens; - } - Ok(()) - } - - fn apply_debits_to_budget_state( - tx: &Transaction, - instruction_index: usize, - accounts: &mut [&mut Account], - instruction: &Instruction, - ) -> Result<(), BudgetError> { - if !accounts[0].userdata.is_empty() { - trace!("source is pending"); - return Err(BudgetError::SourceIsPendingContract); - } - match instruction { - Instruction::NewBudget(expr) => { - let expr = expr.clone(); - if let Some(payment) = expr.final_payment() { - accounts[1].tokens += payment.tokens; - Ok(()) - } else { - let existing = Self::deserialize(&accounts[1].userdata).ok(); - if Some(true) == existing.map(|x| x.initialized) { - trace!("contract already exists"); - Err(BudgetError::ContractAlreadyExists) - } else { - let mut state = BudgetState::default(); - state.pending_budget = Some(expr); - accounts[1].tokens += accounts[0].tokens; - accounts[0].tokens = 0; - state.initialized = true; - state.serialize(&mut accounts[1].userdata) - } - } - } - Instruction::ApplyTimestamp(dt) => { - if let Ok(mut state) = Self::deserialize(&accounts[1].userdata) { - if !state.is_pending() { - Err(BudgetError::ContractNotPending) - } else if !state.initialized { - trace!("contract is uninitialized"); - Err(BudgetError::UninitializedContract) - } else { - trace!("apply timestamp"); - state.apply_timestamp(tx, instruction_index, accounts, *dt)?; - trace!("apply timestamp committed"); - state.serialize(&mut accounts[1].userdata) - } - } else { - Err(BudgetError::UninitializedContract) - } - } - Instruction::ApplySignature => { - if let Ok(mut state) = Self::deserialize(&accounts[1].userdata) { - if !state.is_pending() { - Err(BudgetError::ContractNotPending) - } else if !state.initialized { - trace!("contract is uninitialized"); - Err(BudgetError::UninitializedContract) - } else { - trace!("apply signature"); - state.apply_signature(tx, instruction_index, accounts)?; - trace!("apply signature committed"); - state.serialize(&mut accounts[1].userdata) - } - } else { - Err(BudgetError::UninitializedContract) - } - } - } - } - fn serialize(&self, output: &mut [u8]) -> Result<(), BudgetError> { - let len = serialized_size(self).unwrap() as u64; - if output.len() < len as usize { - warn!( - "{} bytes required to serialize, only have {} bytes", - len, - output.len() - ); - return Err(BudgetError::UserdataTooSmall); - } - { - let writer = io::BufWriter::new(&mut output[..8]); - serialize_into(writer, &len).unwrap(); - } - - { - let writer = io::BufWriter::new(&mut output[8..8 + len as usize]); - serialize_into(writer, self).unwrap(); - } - Ok(()) - } - - pub fn deserialize(input: &[u8]) -> bincode::Result { - if input.len() < 8 { - return Err(Box::new(bincode::ErrorKind::SizeLimit)); - } - let len: u64 = deserialize(&input[..8]).unwrap(); - if len < 2 { - return Err(Box::new(bincode::ErrorKind::SizeLimit)); - } - if input.len() < 8 + len as usize { - return Err(Box::new(bincode::ErrorKind::SizeLimit)); - } - deserialize(&input[8..8 + len as usize]) - } - - /// Budget DSL contract interface - /// * tx - the transaction - /// * accounts[0] - The source of the tokens - /// * accounts[1] - The contract context. Once the contract has been completed, the tokens can - /// be spent from this account . - pub fn process_transaction( - tx: &Transaction, - instruction_index: usize, - accounts: &mut [&mut Account], - ) -> Result<(), BudgetError> { - if let Ok(instruction) = deserialize(tx.userdata(instruction_index)) { - trace!("process_transaction: {:?}", instruction); - Self::apply_debits_to_budget_state(tx, instruction_index, accounts, &instruction) - } else { - info!( - "Invalid transaction userdata: {:?}", - tx.userdata(instruction_index) - ); - Err(BudgetError::UserdataDeserializeFailure) - } - } - - //TODO the contract needs to provide a "get_balance" introspection call of the userdata - pub fn get_balance(account: &Account) -> u64 { - if let Ok(state) = deserialize(&account.userdata) { - let state: BudgetState = state; - if state.is_pending() { - 0 - } else { - account.tokens - } - } else { - account.tokens - } - } -} -#[cfg(test)] -mod test { - use bincode::serialize; - use budget_program::{BudgetError, BudgetState}; - use budget_transaction::BudgetTransaction; - use chrono::prelude::{DateTime, NaiveDate, Utc}; - use signature::{GenKeys, Keypair, KeypairUtil}; - use solana_sdk::account::Account; - use solana_sdk::hash::Hash; - use solana_sdk::pubkey::Pubkey; - use transaction::Transaction; - - fn process_transaction(tx: &Transaction, accounts: &mut [Account]) -> Result<(), BudgetError> { - let mut refs: Vec<&mut Account> = accounts.iter_mut().collect(); - BudgetState::process_transaction(&tx, 0, &mut refs[..]) - } - #[test] - fn test_serializer() { - let mut a = Account::new(0, 512, BudgetState::id()); - let b = BudgetState::default(); - b.serialize(&mut a.userdata).unwrap(); - let buf = serialize(&b).unwrap(); - assert_eq!(a.userdata[8..8 + buf.len()], buf[0..]); - let c = BudgetState::deserialize(&a.userdata).unwrap(); - assert_eq!(b, c); - } - - #[test] - fn test_serializer_userdata_too_small() { - let mut a = Account::new(0, 1, BudgetState::id()); - let b = BudgetState::default(); - assert_eq!( - b.serialize(&mut a.userdata), - Err(BudgetError::UserdataTooSmall) - ); - } - #[test] - fn test_invalid_instruction() { - let mut accounts = vec![ - Account::new(1, 0, BudgetState::id()), - Account::new(0, 512, BudgetState::id()), - ]; - let from = Keypair::new(); - let contract = Keypair::new(); - let userdata = (1u8, 2u8, 3u8); - let tx = Transaction::new( - &from, - &[contract.pubkey()], - BudgetState::id(), - &userdata, - Hash::default(), - 0, - ); - assert!(process_transaction(&tx, &mut accounts).is_err()); - } - - #[test] - fn test_unsigned_witness_key() { - let mut accounts = vec![ - Account::new(1, 0, BudgetState::id()), - Account::new(0, 512, BudgetState::id()), - Account::new(0, 0, BudgetState::id()), - ]; - - // Initialize BudgetState - let from = Keypair::new(); - let contract = Keypair::new().pubkey(); - let to = Keypair::new().pubkey(); - let witness = Keypair::new().pubkey(); - let tx = Transaction::budget_new_when_signed( - &from, - to, - contract, - witness, - None, - 1, - Hash::default(), - ); - process_transaction(&tx, &mut accounts).unwrap(); - - // Attack! Part 1: Sign a witness transaction with a random key. - let rando = Keypair::new(); - let mut tx = Transaction::budget_new_signature(&rando, contract, to, Hash::default()); - - // Attack! Part 2: Point the instruction to the expected, but unsigned, key. - tx.account_keys.push(from.pubkey()); - tx.instructions[0].accounts[0] = 3; - - // Ensure the transaction fails because of the unsigned key. - assert_eq!( - process_transaction(&tx, &mut accounts), - Err(BudgetError::UnsignedKey) - ); - } - - #[test] - fn test_unsigned_timestamp() { - let mut accounts = vec![ - Account::new(1, 0, BudgetState::id()), - Account::new(0, 512, BudgetState::id()), - Account::new(0, 0, BudgetState::id()), - ]; - - // Initialize BudgetState - let from = Keypair::new(); - let contract = Keypair::new().pubkey(); - let to = Keypair::new().pubkey(); - let dt = Utc::now(); - let tx = Transaction::budget_new_on_date( - &from, - to, - contract, - dt, - from.pubkey(), - None, - 1, - Hash::default(), - ); - process_transaction(&tx, &mut accounts).unwrap(); - - // Attack! Part 1: Sign a timestamp transaction with a random key. - let rando = Keypair::new(); - let mut tx = Transaction::budget_new_timestamp(&rando, contract, to, dt, Hash::default()); - - // Attack! Part 2: Point the instruction to the expected, but unsigned, key. - tx.account_keys.push(from.pubkey()); - tx.instructions[0].accounts[0] = 3; - - // Ensure the transaction fails because of the unsigned key. - assert_eq!( - process_transaction(&tx, &mut accounts), - Err(BudgetError::UnsignedKey) - ); - } - - #[test] - fn test_transfer_on_date() { - let mut accounts = vec![ - Account::new(1, 0, BudgetState::id()), - Account::new(0, 512, BudgetState::id()), - Account::new(0, 0, BudgetState::id()), - ]; - let from_account = 0; - let contract_account = 1; - let to_account = 2; - let from = Keypair::new(); - let contract = Keypair::new(); - let to = Keypair::new(); - let rando = Keypair::new(); - let dt = Utc::now(); - let tx = Transaction::budget_new_on_date( - &from, - to.pubkey(), - contract.pubkey(), - dt, - from.pubkey(), - None, - 1, - Hash::default(), - ); - process_transaction(&tx, &mut accounts).unwrap(); - assert_eq!(accounts[from_account].tokens, 0); - assert_eq!(accounts[contract_account].tokens, 1); - let state = BudgetState::deserialize(&accounts[contract_account].userdata).unwrap(); - assert!(state.is_pending()); - - // Attack! Try to payout to a rando key - let tx = Transaction::budget_new_timestamp( - &from, - contract.pubkey(), - rando.pubkey(), - dt, - Hash::default(), - ); - assert_eq!( - process_transaction(&tx, &mut accounts), - Err(BudgetError::DestinationMissing) - ); - assert_eq!(accounts[from_account].tokens, 0); - assert_eq!(accounts[contract_account].tokens, 1); - assert_eq!(accounts[to_account].tokens, 0); - - let state = BudgetState::deserialize(&accounts[contract_account].userdata).unwrap(); - assert!(state.is_pending()); - - // Now, acknowledge the time in the condition occurred and - // that pubkey's funds are now available. - let tx = Transaction::budget_new_timestamp( - &from, - contract.pubkey(), - to.pubkey(), - dt, - Hash::default(), - ); - process_transaction(&tx, &mut accounts).unwrap(); - assert_eq!(accounts[from_account].tokens, 0); - assert_eq!(accounts[contract_account].tokens, 0); - assert_eq!(accounts[to_account].tokens, 1); - - let state = BudgetState::deserialize(&accounts[contract_account].userdata).unwrap(); - assert!(!state.is_pending()); - - // try to replay the timestamp contract - assert_eq!( - process_transaction(&tx, &mut accounts), - Err(BudgetError::ContractNotPending) - ); - assert_eq!(accounts[from_account].tokens, 0); - assert_eq!(accounts[contract_account].tokens, 0); - assert_eq!(accounts[to_account].tokens, 1); - } - #[test] - fn test_cancel_transfer() { - let mut accounts = vec![ - Account::new(1, 0, BudgetState::id()), - Account::new(0, 512, BudgetState::id()), - Account::new(0, 0, BudgetState::id()), - ]; - let from_account = 0; - let contract_account = 1; - let pay_account = 2; - let from = Keypair::new(); - let contract = Keypair::new(); - let to = Keypair::new(); - let dt = Utc::now(); - let tx = Transaction::budget_new_on_date( - &from, - to.pubkey(), - contract.pubkey(), - dt, - from.pubkey(), - Some(from.pubkey()), - 1, - Hash::default(), - ); - process_transaction(&tx, &mut accounts).unwrap(); - assert_eq!(accounts[from_account].tokens, 0); - assert_eq!(accounts[contract_account].tokens, 1); - let state = BudgetState::deserialize(&accounts[contract_account].userdata).unwrap(); - assert!(state.is_pending()); - - // Attack! try to put the tokens into the wrong account with cancel - let tx = - Transaction::budget_new_signature(&to, contract.pubkey(), to.pubkey(), Hash::default()); - // unit test hack, the `from account` is passed instead of the `to` account to avoid - // creating more account vectors - process_transaction(&tx, &mut accounts).unwrap(); - // nothing should be changed because apply witness didn't finalize a payment - assert_eq!(accounts[from_account].tokens, 0); - assert_eq!(accounts[contract_account].tokens, 1); - // this would be the `to.pubkey()` account - assert_eq!(accounts[pay_account].tokens, 0); - - // Now, cancel the transaction. from gets her funds back - let tx = Transaction::budget_new_signature( - &from, - contract.pubkey(), - from.pubkey(), - Hash::default(), - ); - process_transaction(&tx, &mut accounts).unwrap(); - assert_eq!(accounts[from_account].tokens, 0); - assert_eq!(accounts[contract_account].tokens, 0); - assert_eq!(accounts[pay_account].tokens, 1); - - // try to replay the signature contract - let tx = Transaction::budget_new_signature( - &from, - contract.pubkey(), - from.pubkey(), - Hash::default(), - ); - assert_eq!( - process_transaction(&tx, &mut accounts), - Err(BudgetError::ContractNotPending) - ); - assert_eq!(accounts[from_account].tokens, 0); - assert_eq!(accounts[contract_account].tokens, 0); - assert_eq!(accounts[pay_account].tokens, 1); - } - - #[test] - fn test_userdata_too_small() { - let mut accounts = vec![ - Account::new(1, 0, BudgetState::id()), - Account::new(1, 0, BudgetState::id()), // <== userdata is 0, which is not enough - Account::new(1, 0, BudgetState::id()), - ]; - let from = Keypair::new(); - let contract = Keypair::new(); - let to = Keypair::new(); - let tx = Transaction::budget_new_on_date( - &from, - to.pubkey(), - contract.pubkey(), - Utc::now(), - from.pubkey(), - None, - 1, - Hash::default(), - ); - - assert!(process_transaction(&tx, &mut accounts).is_err()); - assert!(BudgetState::deserialize(&accounts[1].userdata).is_err()); - - let tx = Transaction::budget_new_timestamp( - &from, - contract.pubkey(), - to.pubkey(), - Utc::now(), - Hash::default(), - ); - assert!(process_transaction(&tx, &mut accounts).is_err()); - assert!(BudgetState::deserialize(&accounts[1].userdata).is_err()); - - // Success if there was no panic... - } - - /// Detect binary changes in the serialized contract userdata, which could have a downstream - /// affect on SDKs and DApps - #[test] - fn test_sdk_serialize() { - let keypair = &GenKeys::new([0u8; 32]).gen_n_keypairs(1)[0]; - let to = Pubkey::new(&[ - 1, 1, 1, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 8, 7, 6, 5, 4, - 1, 1, 1, - ]); - let contract = Pubkey::new(&[ - 2, 2, 2, 4, 5, 6, 7, 8, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 8, 7, 6, 5, 4, - 2, 2, 2, - ]); - let date = - DateTime::::from_utc(NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11), Utc); - let date_iso8601 = "2016-07-08T09:10:11Z"; - - let tx = Transaction::budget_new(&keypair, to, 192, Hash::default()); - assert_eq!( - tx.userdata(0).to_vec(), - vec![2, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0] - ); - assert_eq!( - tx.userdata(1).to_vec(), - vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 4, 5, 6, 7, 8, 9, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 8, 7, 6, 5, 4, 1, 1, 1 - ] - ); - - let tx = Transaction::budget_new_on_date( - &keypair, - to, - contract, - date, - keypair.pubkey(), - Some(keypair.pubkey()), - 192, - Hash::default(), - ); - assert_eq!( - tx.userdata(0).to_vec(), - vec![ - 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 50, 48, 49, 54, 45, - 48, 55, 45, 48, 56, 84, 48, 57, 58, 49, 48, 58, 49, 49, 90, 32, 253, 186, 201, 177, - 11, 117, 135, 187, 167, 181, 188, 22, 59, 206, 105, 231, 150, 215, 30, 78, 212, 76, - 16, 252, 180, 72, 134, 137, 247, 161, 68, 192, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 4, 5, - 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 8, 7, 6, 5, 4, 1, 1, 1, 1, - 0, 0, 0, 32, 253, 186, 201, 177, 11, 117, 135, 187, 167, 181, 188, 22, 59, 206, - 105, 231, 150, 215, 30, 78, 212, 76, 16, 252, 180, 72, 134, 137, 247, 161, 68, 192, - 0, 0, 0, 0, 0, 0, 0, 32, 253, 186, 201, 177, 11, 117, 135, 187, 167, 181, 188, 22, - 59, 206, 105, 231, 150, 215, 30, 78, 212, 76, 16, 252, 180, 72, 134, 137, 247, 161, - 68 - ] - ); - - // ApplyTimestamp(date) - let tx = Transaction::budget_new_timestamp( - &keypair, - keypair.pubkey(), - to, - date, - Hash::default(), - ); - let mut expected_userdata = vec![1, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0]; - expected_userdata.extend(date_iso8601.as_bytes()); - assert_eq!(tx.userdata(0).to_vec(), expected_userdata); - - // ApplySignature - let tx = Transaction::budget_new_signature(&keypair, keypair.pubkey(), to, Hash::default()); - assert_eq!(tx.userdata(0).to_vec(), vec![2, 0, 0, 0]); - } -} diff --git a/book/budget_transaction.rs b/book/budget_transaction.rs deleted file mode 100644 index 7f664890cd574d..00000000000000 --- a/book/budget_transaction.rs +++ /dev/null @@ -1,346 +0,0 @@ -//! The `budget_transaction` module provides functionality for creating Budget transactions. - -use bincode::deserialize; -use budget_expr::{BudgetExpr, Condition}; -use budget_instruction::Instruction; -use budget_program::BudgetState; -use chrono::prelude::*; -use payment_plan::Payment; -use signature::{Keypair, KeypairUtil}; -use solana_sdk::hash::Hash; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::system_instruction::{SystemInstruction, SYSTEM_PROGRAM_ID}; -use transaction::{self, Transaction}; - -pub trait BudgetTransaction { - fn budget_new_taxed( - from_keypair: &Keypair, - to: Pubkey, - tokens: u64, - fee: u64, - last_id: Hash, - ) -> Self; - - fn budget_new(from_keypair: &Keypair, to: Pubkey, tokens: u64, last_id: Hash) -> Self; - - fn budget_new_timestamp( - from_keypair: &Keypair, - contract: Pubkey, - to: Pubkey, - dt: DateTime, - last_id: Hash, - ) -> Self; - - fn budget_new_signature( - from_keypair: &Keypair, - contract: Pubkey, - to: Pubkey, - last_id: Hash, - ) -> Self; - - fn budget_new_on_date( - from_keypair: &Keypair, - to: Pubkey, - contract: Pubkey, - dt: DateTime, - dt_pubkey: Pubkey, - cancelable: Option, - tokens: u64, - last_id: Hash, - ) -> Self; - - fn budget_new_when_signed( - from_keypair: &Keypair, - to: Pubkey, - contract: Pubkey, - witness: Pubkey, - cancelable: Option, - tokens: u64, - last_id: Hash, - ) -> Self; - - fn instruction(&self, program_index: usize) -> Option; - fn system_instruction(&self, program_index: usize) -> Option; - - fn verify_plan(&self) -> bool; -} - -impl BudgetTransaction for Transaction { - /// Create and sign a new Transaction. Used for unit-testing. - fn budget_new_taxed( - from_keypair: &Keypair, - to: Pubkey, - tokens: u64, - fee: u64, - last_id: Hash, - ) -> Self { - let contract = Keypair::new().pubkey(); - let keys = vec![from_keypair.pubkey(), contract]; - - let system_instruction = SystemInstruction::Move { tokens }; - - let payment = Payment { - tokens: tokens - fee, - to, - }; - let budget_instruction = Instruction::NewBudget(BudgetExpr::Pay(payment)); - - let program_ids = vec![Pubkey::new(&SYSTEM_PROGRAM_ID), BudgetState::id()]; - - let instructions = vec![ - transaction::Instruction::new(0, &system_instruction, vec![0, 1]), - transaction::Instruction::new(1, &budget_instruction, vec![1]), - ]; - - Self::new_with_instructions( - &[from_keypair], - &keys, - last_id, - fee, - program_ids, - instructions, - ) - } - - /// Create and sign a new Transaction. Used for unit-testing. - fn budget_new(from_keypair: &Keypair, to: Pubkey, tokens: u64, last_id: Hash) -> Self { - Self::budget_new_taxed(from_keypair, to, tokens, 0, last_id) - } - - /// Create and sign a new Witness Timestamp. Used for unit-testing. - fn budget_new_timestamp( - from_keypair: &Keypair, - contract: Pubkey, - to: Pubkey, - dt: DateTime, - last_id: Hash, - ) -> Self { - let instruction = Instruction::ApplyTimestamp(dt); - Self::new( - from_keypair, - &[contract, to], - BudgetState::id(), - &instruction, - last_id, - 0, - ) - } - - /// Create and sign a new Witness Signature. Used for unit-testing. - fn budget_new_signature( - from_keypair: &Keypair, - contract: Pubkey, - to: Pubkey, - last_id: Hash, - ) -> Self { - let instruction = Instruction::ApplySignature; - Self::new( - from_keypair, - &[contract, to], - BudgetState::id(), - &instruction, - last_id, - 0, - ) - } - - /// Create and sign a postdated Transaction. Used for unit-testing. - fn budget_new_on_date( - from_keypair: &Keypair, - to: Pubkey, - contract: Pubkey, - dt: DateTime, - dt_pubkey: Pubkey, - cancelable: Option, - tokens: u64, - last_id: Hash, - ) -> Self { - let expr = if let Some(from) = cancelable { - BudgetExpr::Or( - (Condition::Timestamp(dt, dt_pubkey), Payment { tokens, to }), - (Condition::Signature(from), Payment { tokens, to: from }), - ) - } else { - BudgetExpr::After(Condition::Timestamp(dt, dt_pubkey), Payment { tokens, to }) - }; - let instruction = Instruction::NewBudget(expr); - Self::new( - from_keypair, - &[contract], - BudgetState::id(), - &instruction, - last_id, - 0, - ) - } - /// Create and sign a multisig Transaction. - fn budget_new_when_signed( - from_keypair: &Keypair, - to: Pubkey, - contract: Pubkey, - witness: Pubkey, - cancelable: Option, - tokens: u64, - last_id: Hash, - ) -> Self { - let expr = if let Some(from) = cancelable { - BudgetExpr::Or( - (Condition::Signature(witness), Payment { tokens, to }), - (Condition::Signature(from), Payment { tokens, to: from }), - ) - } else { - BudgetExpr::After(Condition::Signature(witness), Payment { tokens, to }) - }; - let instruction = Instruction::NewBudget(expr); - Self::new( - from_keypair, - &[contract], - BudgetState::id(), - &instruction, - last_id, - 0, - ) - } - - fn instruction(&self, instruction_index: usize) -> Option { - deserialize(&self.userdata(instruction_index)).ok() - } - - fn system_instruction(&self, instruction_index: usize) -> Option { - deserialize(&self.userdata(instruction_index)).ok() - } - - /// Verify only the payment plan. - fn verify_plan(&self) -> bool { - if let Some(SystemInstruction::Move { tokens }) = self.system_instruction(0) { - if let Some(Instruction::NewBudget(expr)) = self.instruction(1) { - if !(self.fee <= tokens && expr.verify(tokens - self.fee)) { - return false; - } - } - } - true - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bincode::{deserialize, serialize}; - use signature::KeypairUtil; - use transaction; - - #[test] - fn test_claim() { - let keypair = Keypair::new(); - let zero = Hash::default(); - let tx0 = Transaction::budget_new(&keypair, keypair.pubkey(), 42, zero); - assert!(tx0.verify_plan()); - } - - #[test] - fn test_transfer() { - let zero = Hash::default(); - let keypair0 = Keypair::new(); - let keypair1 = Keypair::new(); - let pubkey1 = keypair1.pubkey(); - let tx0 = Transaction::budget_new(&keypair0, pubkey1, 42, zero); - assert!(tx0.verify_plan()); - } - - #[test] - fn test_transfer_with_fee() { - let zero = Hash::default(); - let keypair0 = Keypair::new(); - let pubkey1 = Keypair::new().pubkey(); - assert!(Transaction::budget_new_taxed(&keypair0, pubkey1, 1, 1, zero).verify_plan()); - } - - #[test] - fn test_serialize_claim() { - let expr = BudgetExpr::Pay(Payment { - tokens: 0, - to: Default::default(), - }); - let instruction = Instruction::NewBudget(expr); - let instructions = vec![transaction::Instruction::new(0, &instruction, vec![])]; - let claim0 = Transaction { - account_keys: vec![], - last_id: Default::default(), - signatures: vec![], - program_ids: vec![], - instructions, - fee: 0, - }; - let buf = serialize(&claim0).unwrap(); - let claim1: Transaction = deserialize(&buf).unwrap(); - assert_eq!(claim1, claim0); - } - - #[test] - fn test_token_attack() { - let zero = Hash::default(); - let keypair = Keypair::new(); - let pubkey = keypair.pubkey(); - let mut tx = Transaction::budget_new(&keypair, pubkey, 42, zero); - let mut system_instruction = tx.system_instruction(0).unwrap(); - if let SystemInstruction::Move { ref mut tokens } = system_instruction { - *tokens = 1_000_000; // <-- attack, part 1! - let mut instruction = tx.instruction(1).unwrap(); - if let Instruction::NewBudget(ref mut expr) = instruction { - if let BudgetExpr::Pay(ref mut payment) = expr { - payment.tokens = *tokens; // <-- attack, part 2! - } - } - tx.instructions[1].userdata = serialize(&instruction).unwrap(); - } - tx.instructions[0].userdata = serialize(&system_instruction).unwrap(); - assert!(tx.verify_plan()); - assert!(!tx.verify_signature()); - } - - #[test] - fn test_hijack_attack() { - let keypair0 = Keypair::new(); - let keypair1 = Keypair::new(); - let thief_keypair = Keypair::new(); - let pubkey1 = keypair1.pubkey(); - let zero = Hash::default(); - let mut tx = Transaction::budget_new(&keypair0, pubkey1, 42, zero); - let mut instruction = tx.instruction(1); - if let Some(Instruction::NewBudget(ref mut expr)) = instruction { - if let BudgetExpr::Pay(ref mut payment) = expr { - payment.to = thief_keypair.pubkey(); // <-- attack! - } - } - tx.instructions[1].userdata = serialize(&instruction).unwrap(); - assert!(tx.verify_plan()); - assert!(!tx.verify_signature()); - } - - #[test] - fn test_overspend_attack() { - let keypair0 = Keypair::new(); - let keypair1 = Keypair::new(); - let zero = Hash::default(); - let mut tx = Transaction::budget_new(&keypair0, keypair1.pubkey(), 1, zero); - let mut instruction = tx.instruction(1).unwrap(); - if let Instruction::NewBudget(ref mut expr) = instruction { - if let BudgetExpr::Pay(ref mut payment) = expr { - payment.tokens = 2; // <-- attack! - } - } - tx.instructions[1].userdata = serialize(&instruction).unwrap(); - assert!(!tx.verify_plan()); - - // Also, ensure all branchs of the plan spend all tokens - let mut instruction = tx.instruction(1).unwrap(); - if let Instruction::NewBudget(ref mut expr) = instruction { - if let BudgetExpr::Pay(ref mut payment) = expr { - payment.tokens = 0; // <-- whoops! - } - } - tx.instructions[1].userdata = serialize(&instruction).unwrap(); - assert!(!tx.verify_plan()); - } -} diff --git a/book/chacha.rs b/book/chacha.rs deleted file mode 100644 index 896a7d06f146ff..00000000000000 --- a/book/chacha.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::fs::File; -use std::io; -use std::io::Read; -use std::io::Write; -use std::io::{BufReader, BufWriter}; -use std::path::Path; - -pub const CHACHA_BLOCK_SIZE: usize = 64; -pub const CHACHA_KEY_SIZE: usize = 32; - -#[link(name = "cpu-crypt")] -extern "C" { - fn chacha20_cbc_encrypt( - input: *const u8, - output: *mut u8, - in_len: usize, - key: *const u8, - ivec: *mut u8, - ); -} - -pub fn chacha_cbc_encrypt(input: &[u8], output: &mut [u8], key: &[u8], ivec: &mut [u8]) { - unsafe { - chacha20_cbc_encrypt( - input.as_ptr(), - output.as_mut_ptr(), - input.len(), - key.as_ptr(), - ivec.as_mut_ptr(), - ); - } -} - -pub fn chacha_cbc_encrypt_file( - in_path: &Path, - out_path: &Path, - ivec: &mut [u8; CHACHA_BLOCK_SIZE], -) -> io::Result<()> { - let mut in_file = BufReader::new(File::open(in_path).expect("Can't open ledger data file")); - let mut out_file = - BufWriter::new(File::create(out_path).expect("Can't open ledger encrypted data file")); - let mut buffer = [0; 4 * 1024]; - let mut encrypted_buffer = [0; 4 * 1024]; - let key = [0; CHACHA_KEY_SIZE]; - - while let Ok(size) = in_file.read(&mut buffer) { - debug!("read {} bytes", size); - if size == 0 { - break; - } - chacha_cbc_encrypt(&buffer[..size], &mut encrypted_buffer[..size], &key, ivec); - if let Err(res) = out_file.write(&encrypted_buffer[..size]) { - println!("Error writing file! {:?}", res); - return Err(res); - } - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use chacha::chacha_cbc_encrypt_file; - use std::fs::remove_file; - use std::fs::File; - use std::io::Read; - use std::io::Write; - use std::path::Path; - - #[test] - fn test_encrypt_file() { - let in_path = Path::new("test_chacha_encrypt_file_input.txt"); - let out_path = Path::new("test_chacha_encrypt_file_output.txt.enc"); - { - let mut in_file = File::create(in_path).unwrap(); - in_file.write("123456foobar".as_bytes()).unwrap(); - } - let mut key = hex!( - "abcd1234abcd1234abcd1234abcd1234 abcd1234abcd1234abcd1234abcd1234 - abcd1234abcd1234abcd1234abcd1234 abcd1234abcd1234abcd1234abcd1234" - ); - assert!(chacha_cbc_encrypt_file(in_path, out_path, &mut key).is_ok()); - let mut out_file = File::open(out_path).unwrap(); - let mut buf = vec![]; - let size = out_file.read_to_end(&mut buf).unwrap(); - assert_eq!( - buf[..size], - [66, 54, 56, 212, 142, 110, 105, 158, 116, 82, 120, 53] - ); - remove_file(in_path).unwrap(); - remove_file(out_path).unwrap(); - } -} diff --git a/book/chacha_cuda.rs b/book/chacha_cuda.rs deleted file mode 100644 index b9dfb310423308..00000000000000 --- a/book/chacha_cuda.rs +++ /dev/null @@ -1,212 +0,0 @@ -use chacha::{CHACHA_BLOCK_SIZE, CHACHA_KEY_SIZE}; -use ledger::LedgerWindow; -use sigverify::{chacha_cbc_encrypt_many_sample, chacha_end_sha_state, chacha_init_sha_state}; -use solana_sdk::hash::Hash; -use std::io; -use std::mem::size_of; - -use storage_stage::ENTRIES_PER_SLICE; - -// Encrypt a file with multiple starting IV states, determined by ivecs.len() -// -// Then sample each block at the offsets provided by samples argument with sha256 -// and return the vec of sha states -pub fn chacha_cbc_encrypt_file_many_keys( - in_path: &str, - slice: u64, - ivecs: &mut [u8], - samples: &[u64], -) -> io::Result> { - if ivecs.len() % CHACHA_BLOCK_SIZE != 0 { - return Err(io::Error::new( - io::ErrorKind::Other, - format!( - "bad IV length({}) not divisible by {} ", - ivecs.len(), - CHACHA_BLOCK_SIZE, - ), - )); - } - - let mut ledger_window = LedgerWindow::open(in_path)?; - let mut buffer = [0; 8 * 1024]; - let num_keys = ivecs.len() / CHACHA_BLOCK_SIZE; - let mut sha_states = vec![0; num_keys * size_of::()]; - let mut int_sha_states = vec![0; num_keys * 112]; - let keys: Vec = vec![0; num_keys * CHACHA_KEY_SIZE]; // keys not used ATM, uniqueness comes from IV - let mut entry = slice; - let mut total_entries = 0; - let mut total_entry_len = 0; - let mut time: f32 = 0.0; - unsafe { - chacha_init_sha_state(int_sha_states.as_mut_ptr(), num_keys as u32); - } - loop { - match ledger_window.get_entries_bytes(entry, ENTRIES_PER_SLICE - total_entries, &mut buffer) - { - Ok((num_entries, entry_len)) => { - info!( - "encrypting slice: {} num_entries: {} entry_len: {}", - slice, num_entries, entry_len - ); - let entry_len_usz = entry_len as usize; - unsafe { - chacha_cbc_encrypt_many_sample( - buffer[..entry_len_usz].as_ptr(), - int_sha_states.as_mut_ptr(), - entry_len_usz, - keys.as_ptr(), - ivecs.as_mut_ptr(), - num_keys as u32, - samples.as_ptr(), - samples.len() as u32, - total_entry_len, - &mut time, - ); - } - - total_entry_len += entry_len; - total_entries += num_entries; - entry += num_entries; - debug!( - "total entries: {} entry: {} slice: {} entries_per_slice: {}", - total_entries, entry, slice, ENTRIES_PER_SLICE - ); - if (entry - slice) >= ENTRIES_PER_SLICE { - break; - } - } - Err(e) => { - info!("Error encrypting file: {:?}", e); - break; - } - } - } - unsafe { - chacha_end_sha_state( - int_sha_states.as_ptr(), - sha_states.as_mut_ptr(), - num_keys as u32, - ); - } - let mut res = Vec::new(); - for x in 0..num_keys { - let start = x * size_of::(); - let end = start + size_of::(); - res.push(Hash::new(&sha_states[start..end])); - } - Ok(res) -} - -#[cfg(test)] -mod tests { - use chacha::chacha_cbc_encrypt_file; - use chacha_cuda::chacha_cbc_encrypt_file_many_keys; - use ledger::LedgerWriter; - use ledger::{get_tmp_ledger_path, make_tiny_test_entries, LEDGER_DATA_FILE}; - use replicator::sample_file; - use solana_sdk::hash::Hash; - use std::fs::{remove_dir_all, remove_file}; - use std::path::Path; - - #[test] - fn test_encrypt_file_many_keys_single() { - use logger; - logger::setup(); - - let entries = make_tiny_test_entries(32); - let ledger_dir = "test_encrypt_file_many_keys_single"; - let ledger_path = get_tmp_ledger_path(ledger_dir); - { - let mut writer = LedgerWriter::open(&ledger_path, true).unwrap(); - writer.write_entries(&entries).unwrap(); - } - - let out_path = Path::new("test_chacha_encrypt_file_many_keys_single_output.txt.enc"); - - let samples = [0]; - let mut ivecs = hex!( - "abcd1234abcd1234abcd1234abcd1234 abcd1234abcd1234abcd1234abcd1234 - abcd1234abcd1234abcd1234abcd1234 abcd1234abcd1234abcd1234abcd1234" - ); - - let mut cpu_iv = ivecs.clone(); - assert!( - chacha_cbc_encrypt_file( - &Path::new(&ledger_path).join(LEDGER_DATA_FILE), - out_path, - &mut cpu_iv, - ).is_ok() - ); - - let ref_hash = sample_file(&out_path, &samples).unwrap(); - - let hashes = - chacha_cbc_encrypt_file_many_keys(&ledger_path, 0, &mut ivecs, &samples).unwrap(); - - assert_eq!(hashes[0], ref_hash); - - let _ignored = remove_dir_all(&ledger_path); - let _ignored = remove_file(out_path); - } - - #[test] - fn test_encrypt_file_many_keys_multiple_keys() { - use logger; - logger::setup(); - - let entries = make_tiny_test_entries(32); - let ledger_dir = "test_encrypt_file_many_keys_multiple"; - let ledger_path = get_tmp_ledger_path(ledger_dir); - { - let mut writer = LedgerWriter::open(&ledger_path, true).unwrap(); - writer.write_entries(&entries).unwrap(); - } - - let out_path = Path::new("test_chacha_encrypt_file_many_keys_multiple_output.txt.enc"); - - let samples = [0, 1, 3, 4, 5, 150]; - let mut ivecs = Vec::new(); - let mut ref_hashes: Vec = vec![]; - for i in 0..2 { - let mut ivec = hex!( - "abc123abc123abc123abc123abc123abc123abababababababababababababab - abc123abc123abc123abc123abc123abc123abababababababababababababab" - ); - ivec[0] = i; - ivecs.extend(ivec.clone().iter()); - assert!( - chacha_cbc_encrypt_file( - &Path::new(&ledger_path).join(LEDGER_DATA_FILE), - out_path, - &mut ivec, - ).is_ok() - ); - - ref_hashes.push(sample_file(&out_path, &samples).unwrap()); - info!( - "ivec: {:?} hash: {:?} ivecs: {:?}", - ivec.to_vec(), - ref_hashes.last(), - ivecs - ); - } - - let hashes = - chacha_cbc_encrypt_file_many_keys(&ledger_path, 0, &mut ivecs, &samples).unwrap(); - - assert_eq!(hashes, ref_hashes); - - let _ignored = remove_dir_all(&ledger_path); - let _ignored = remove_file(out_path); - } - - #[test] - fn test_encrypt_file_many_keys_bad_key_length() { - let mut keys = hex!("abc123"); - let ledger_dir = "test_encrypt_file_many_keys_bad_key_length"; - let ledger_path = get_tmp_ledger_path(ledger_dir); - let samples = [0]; - assert!(chacha_cbc_encrypt_file_many_keys(&ledger_path, 0, &mut keys, &samples,).is_err()); - } -} diff --git a/book/client.rs b/book/client.rs deleted file mode 100644 index 7d278eb3f56952..00000000000000 --- a/book/client.rs +++ /dev/null @@ -1,8 +0,0 @@ -use cluster_info::{NodeInfo, FULLNODE_PORT_RANGE}; -use netutil::bind_in_range; -use thin_client::ThinClient; - -pub fn mk_client(r: &NodeInfo) -> ThinClient { - let (_, transactions_socket) = bind_in_range(FULLNODE_PORT_RANGE).unwrap(); - ThinClient::new(r.rpc, r.tpu, transactions_socket) -} diff --git a/book/clipboard.min.js b/book/clipboard.min.js deleted file mode 100644 index 1993676f992881..00000000000000 --- a/book/clipboard.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * clipboard.js v1.6.1 - * https://zenorocha.github.io/clipboard.js - * - * Licensed MIT © Zeno Rocha - */ -!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.Clipboard=e()}}(function(){var e,t,n;return function e(t,n,o){function i(a,c){if(!n[a]){if(!t[a]){var l="function"==typeof require&&require;if(!c&&l)return l(a,!0);if(r)return r(a,!0);var u=new Error("Cannot find module '"+a+"'");throw u.code="MODULE_NOT_FOUND",u}var s=n[a]={exports:{}};t[a][0].call(s.exports,function(e){var n=t[a][1][e];return i(n?n:e)},s,s.exports,e,t,n,o)}return n[a].exports}for(var r="function"==typeof require&&require,a=0;a0&&void 0!==arguments[0]?arguments[0]:{};this.action=t.action,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function e(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function e(){var t=this,n="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=document.body.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[n?"right":"left"]="-9999px";var o=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=o+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,document.body.appendChild(this.fakeElem),this.selectedText=(0,i.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function e(){this.fakeHandler&&(document.body.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(document.body.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function e(){this.selectedText=(0,i.default)(this.target),this.copyText()}},{key:"copyText",value:function e(){var t=void 0;try{t=document.execCommand(this.action)}catch(e){t=!1}this.handleResult(t)}},{key:"handleResult",value:function e(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function e(){this.target&&this.target.blur(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function e(){this.removeFake()}},{key:"action",set:function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function e(){return this._action}},{key:"target",set:function e(t){if(void 0!==t){if(!t||"object"!==("undefined"==typeof t?"undefined":r(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function e(){return this._target}}]),e}();e.exports=c})},{select:5}],8:[function(t,n,o){!function(i,r){if("function"==typeof e&&e.amd)e(["module","./clipboard-action","tiny-emitter","good-listener"],r);else if("undefined"!=typeof o)r(n,t("./clipboard-action"),t("tiny-emitter"),t("good-listener"));else{var a={exports:{}};r(a,i.clipboardAction,i.tinyEmitter,i.goodListener),i.clipboard=a.exports}}(this,function(e,t,n,o){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function c(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function l(e,t){var n="data-clipboard-"+e;if(t.hasAttribute(n))return t.getAttribute(n)}var u=i(t),s=i(n),f=i(o),d=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText}},{key:"listenClick",value:function e(t){var n=this;this.listener=(0,f.default)(t,"click",function(e){return n.onClick(e)})}},{key:"onClick",value:function e(t){var n=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new u.default({action:this.action(n),target:this.target(n),text:this.text(n),trigger:n,emitter:this})}},{key:"defaultAction",value:function e(t){return l("action",t)}},{key:"defaultTarget",value:function e(t){var n=l("target",t);if(n)return document.querySelector(n)}},{key:"defaultText",value:function e(t){return l("text",t)}},{key:"destroy",value:function e(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],n="string"==typeof t?[t]:t,o=!!document.queryCommandSupported;return n.forEach(function(e){o=o&&!!document.queryCommandSupported(e)}),o}}]),t}(s.default);e.exports=h})},{"./clipboard-action":7,"good-listener":4,"tiny-emitter":6}]},{},[8])(8)}); \ No newline at end of file diff --git a/book/cluster_info.rs b/book/cluster_info.rs deleted file mode 100644 index 82e2922e13b74b..00000000000000 --- a/book/cluster_info.rs +++ /dev/null @@ -1,1346 +0,0 @@ -//! The `cluster_info` module defines a data structure that is shared by all the nodes in the network over -//! a gossip control plane. The goal is to share small bits of off-chain information and detect and -//! repair partitions. -//! -//! This CRDT only supports a very limited set of types. A map of Pubkey -> Versioned Struct. -//! The last version is always picked during an update. -//! -//! The network is arranged in layers: -//! -//! * layer 0 - Leader. -//! * layer 1 - As many nodes as we can fit -//! * layer 2 - Everyone else, if layer 1 is `2^10`, layer 2 should be able to fit `2^20` number of nodes. -//! -//! Bank needs to provide an interface for us to query the stake weight -use bincode::{deserialize, serialize}; -use bloom::Bloom; -use contact_info::ContactInfo; -use counter::Counter; -use crds_gossip::CrdsGossip; -use crds_gossip_pull::CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS; -use crds_value::{CrdsValue, CrdsValueLabel, LeaderId}; -use ledger::LedgerWindow; -use log::Level; -use netutil::{bind_in_range, bind_to, find_available_port_in_range, multi_bind_in_range}; -use packet::{to_blob, Blob, SharedBlob, BLOB_SIZE}; -use rand::{thread_rng, Rng}; -use rayon::prelude::*; -use result::Result; -use rpc::RPC_PORT; -use signature::{Keypair, KeypairUtil}; -use solana_sdk::hash::Hash; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::timing::{duration_as_ms, timestamp}; -use std::collections::HashMap; -use std::io; -use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket}; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::sync::{Arc, RwLock}; -use std::thread::{sleep, Builder, JoinHandle}; -use std::time::{Duration, Instant}; -use streamer::{BlobReceiver, BlobSender}; -use window::{SharedWindow, WindowIndex}; - -pub type NodeInfo = ContactInfo; - -pub const FULLNODE_PORT_RANGE: (u16, u16) = (8000, 10_000); - -/// milliseconds we sleep for between gossip requests -const GOSSIP_SLEEP_MILLIS: u64 = 100; - -#[derive(Debug, PartialEq, Eq)] -pub enum ClusterInfoError { - NoPeers, - NoLeader, - BadContactInfo, - BadNodeInfo, - BadGossipAddress, -} - -pub struct ClusterInfo { - /// The network - pub gossip: CrdsGossip, -} - -// TODO These messages should be signed, and go through the gpu pipeline for spam filtering -#[derive(Serialize, Deserialize, Debug)] -#[cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))] -enum Protocol { - /// Gosisp protocol messages - PullRequest(Bloom, CrdsValue), - PullResponse(Pubkey, Vec), - PushMessage(Pubkey, Vec), - PruneMessage(Pubkey, Vec), - - /// Window protocol messages - /// TODO: move this message to a different module - RequestWindowIndex(NodeInfo, u64), -} - -impl ClusterInfo { - pub fn new(node_info: NodeInfo) -> Self { - let mut me = ClusterInfo { - gossip: CrdsGossip::default(), - }; - let id = node_info.id; - me.gossip.set_self(id); - me.insert_info(node_info); - me.push_self(); - me - } - pub fn push_self(&mut self) { - let mut my_data = self.my_data(); - let now = timestamp(); - my_data.wallclock = now; - let entry = CrdsValue::ContactInfo(my_data); - self.gossip.refresh_push_active_set(); - self.gossip.process_push_message(&[entry], now); - } - pub fn insert_info(&mut self, node_info: NodeInfo) { - let value = CrdsValue::ContactInfo(node_info); - let _ = self.gossip.crds.insert(value, timestamp()); - } - pub fn id(&self) -> Pubkey { - self.gossip.id - } - pub fn lookup(&self, id: Pubkey) -> Option<&NodeInfo> { - let entry = CrdsValueLabel::ContactInfo(id); - self.gossip - .crds - .lookup(&entry) - .and_then(|x| x.contact_info()) - } - pub fn my_data(&self) -> NodeInfo { - self.lookup(self.id()).cloned().unwrap() - } - pub fn leader_id(&self) -> Pubkey { - let entry = CrdsValueLabel::LeaderId(self.id()); - self.gossip - .crds - .lookup(&entry) - .and_then(|v| v.leader_id()) - .map(|x| x.leader_id) - .unwrap_or_default() - } - pub fn leader_data(&self) -> Option<&NodeInfo> { - let leader_id = self.leader_id(); - if leader_id == Pubkey::default() { - return None; - } - self.lookup(leader_id) - } - pub fn node_info_trace(&self) -> String { - let leader_id = self.leader_id(); - let nodes: Vec<_> = self - .rpc_peers() - .into_iter() - .map(|node| { - format!( - " ncp: {:20} | {}{}\n \ - tpu: {:20} |\n \ - rpc: {:20} |\n", - node.ncp.to_string(), - node.id, - if node.id == leader_id { - " <==== leader" - } else { - "" - }, - node.tpu.to_string(), - node.rpc.to_string() - ) - }).collect(); - - format!( - " NodeInfo.contact_info | Node identifier\n\ - ---------------------------+------------------\n\ - {}\n \ - Nodes: {}", - nodes.join(""), - nodes.len() - ) - } - - pub fn set_leader(&mut self, key: Pubkey) -> () { - let prev = self.leader_id(); - let self_id = self.gossip.id; - let now = timestamp(); - let leader = LeaderId { - id: self_id, - leader_id: key, - wallclock: now, - }; - let entry = CrdsValue::LeaderId(leader); - warn!("{}: LEADER_UPDATE TO {} from {}", self_id, key, prev); - self.gossip.process_push_message(&[entry], now); - } - - pub fn purge(&mut self, now: u64) { - self.gossip.purge(now); - } - pub fn convergence(&self) -> usize { - self.ncp_peers().len() + 1 - } - pub fn rpc_peers(&self) -> Vec { - let me = self.my_data().id; - self.gossip - .crds - .table - .values() - .filter_map(|x| x.value.contact_info()) - .filter(|x| x.id != me) - .filter(|x| ContactInfo::is_valid_address(&x.rpc)) - .cloned() - .collect() - } - - pub fn ncp_peers(&self) -> Vec { - let me = self.my_data().id; - self.gossip - .crds - .table - .values() - .filter_map(|x| x.value.contact_info()) - .filter(|x| x.id != me) - .filter(|x| ContactInfo::is_valid_address(&x.ncp)) - .cloned() - .collect() - } - - /// compute broadcast table - pub fn tvu_peers(&self) -> Vec { - let me = self.my_data().id; - self.gossip - .crds - .table - .values() - .filter_map(|x| x.value.contact_info()) - .filter(|x| x.id != me) - .filter(|x| ContactInfo::is_valid_address(&x.tvu)) - .cloned() - .collect() - } - - /// compute broadcast table - pub fn tpu_peers(&self) -> Vec { - let me = self.my_data().id; - self.gossip - .crds - .table - .values() - .filter_map(|x| x.value.contact_info()) - .filter(|x| x.id != me) - .filter(|x| ContactInfo::is_valid_address(&x.tpu)) - .cloned() - .collect() - } - - /// broadcast messages from the leader to layer 1 nodes - /// # Remarks - /// We need to avoid having obj locked while doing any io, such as the `send_to` - pub fn broadcast( - contains_last_tick: bool, - leader_id: Pubkey, - me: &NodeInfo, - broadcast_table: &[NodeInfo], - window: &SharedWindow, - s: &UdpSocket, - transmit_index: &mut WindowIndex, - received_index: u64, - ) -> Result<()> { - if broadcast_table.is_empty() { - debug!("{}:not enough peers in cluster_info table", me.id); - inc_new_counter_info!("cluster_info-broadcast-not_enough_peers_error", 1); - Err(ClusterInfoError::NoPeers)?; - } - trace!( - "{} transmit_index: {:?} received_index: {} broadcast_len: {}", - me.id, - *transmit_index, - received_index, - broadcast_table.len() - ); - - let old_transmit_index = transmit_index.data; - - let orders = Self::create_broadcast_orders( - contains_last_tick, - window, - broadcast_table, - transmit_index, - received_index, - me, - ); - trace!("broadcast orders table {}", orders.len()); - - let errs = Self::send_orders(s, orders, me, leader_id); - - for e in errs { - if let Err(e) = &e { - trace!("broadcast result {:?}", e); - } - e?; - if transmit_index.data < received_index { - transmit_index.data += 1; - } - } - inc_new_counter_info!( - "cluster_info-broadcast-max_idx", - (transmit_index.data - old_transmit_index) as usize - ); - transmit_index.coding = transmit_index.data; - - Ok(()) - } - - /// retransmit messages from the leader to layer 1 nodes - /// # Remarks - /// We need to avoid having obj locked while doing any io, such as the `send_to` - pub fn retransmit(obj: &Arc>, blob: &SharedBlob, s: &UdpSocket) -> Result<()> { - let (me, orders): (NodeInfo, Vec) = { - // copy to avoid locking during IO - let s = obj.read().expect("'obj' read lock in pub fn retransmit"); - (s.my_data().clone(), s.tvu_peers()) - }; - blob.write() - .unwrap() - .set_id(&me.id) - .expect("set_id in pub fn retransmit"); - let rblob = blob.read().unwrap(); - trace!("retransmit orders {}", orders.len()); - let errs: Vec<_> = orders - .par_iter() - .map(|v| { - debug!( - "{}: retransmit blob {} to {} {}", - me.id, - rblob.index().unwrap(), - v.id, - v.tvu, - ); - //TODO profile this, may need multiple sockets for par_iter - assert!(rblob.meta.size <= BLOB_SIZE); - s.send_to(&rblob.data[..rblob.meta.size], &v.tvu) - }).collect(); - for e in errs { - if let Err(e) = &e { - inc_new_counter_info!("cluster_info-retransmit-send_to_error", 1, 1); - error!("retransmit result {:?}", e); - } - e?; - } - Ok(()) - } - - fn send_orders( - s: &UdpSocket, - orders: Vec<(Option, Vec<&NodeInfo>)>, - me: &NodeInfo, - leader_id: Pubkey, - ) -> Vec> { - orders - .into_iter() - .flat_map(|(b, vs)| { - // only leader should be broadcasting - assert!(vs.iter().find(|info| info.id == leader_id).is_none()); - let bl = b.unwrap(); - let blob = bl.read().unwrap(); - //TODO profile this, may need multiple sockets for par_iter - let ids_and_tvus = if log_enabled!(Level::Trace) { - let v_ids = vs.iter().map(|v| v.id); - let tvus = vs.iter().map(|v| v.tvu); - let ids_and_tvus = v_ids.zip(tvus).collect(); - - trace!( - "{}: BROADCAST idx: {} sz: {} to {:?} coding: {}", - me.id, - blob.index().unwrap(), - blob.meta.size, - ids_and_tvus, - blob.is_coding() - ); - - ids_and_tvus - } else { - vec![] - }; - - assert!(blob.meta.size <= BLOB_SIZE); - let send_errs_for_blob: Vec<_> = vs - .iter() - .map(move |v| { - let e = s.send_to(&blob.data[..blob.meta.size], &v.tvu); - trace!( - "{}: done broadcast {} to {:?}", - me.id, - blob.meta.size, - ids_and_tvus - ); - e - }).collect(); - send_errs_for_blob - }).collect() - } - - fn create_broadcast_orders<'a>( - contains_last_tick: bool, - window: &SharedWindow, - broadcast_table: &'a [NodeInfo], - transmit_index: &mut WindowIndex, - received_index: u64, - me: &NodeInfo, - ) -> Vec<(Option, Vec<&'a NodeInfo>)> { - // enumerate all the blobs in the window, those are the indices - // transmit them to nodes, starting from a different node. - let mut orders = Vec::with_capacity((received_index - transmit_index.data) as usize); - let window_l = window.read().unwrap(); - let mut br_idx = transmit_index.data as usize % broadcast_table.len(); - - for idx in transmit_index.data..received_index { - let w_idx = idx as usize % window_l.len(); - - trace!( - "{} broadcast order data w_idx {} br_idx {}", - me.id, - w_idx, - br_idx - ); - - // Broadcast the last tick to everyone on the network so it doesn't get dropped - // (Need to maximize probability the next leader in line sees this handoff tick - // despite packet drops) - let target = if idx == received_index - 1 && contains_last_tick { - // If we see a tick at max_tick_height, then we know it must be the last - // Blob in the window, at index == received_index. There cannot be an entry - // that got sent after the last tick, guaranteed by the PohService). - assert!(window_l[w_idx].data.is_some()); - ( - window_l[w_idx].data.clone(), - broadcast_table.iter().collect(), - ) - } else { - (window_l[w_idx].data.clone(), vec![&broadcast_table[br_idx]]) - }; - - orders.push(target); - br_idx += 1; - br_idx %= broadcast_table.len(); - } - - for idx in transmit_index.coding..received_index { - let w_idx = idx as usize % window_l.len(); - - // skip over empty slots - if window_l[w_idx].coding.is_none() { - continue; - } - - trace!( - "{} broadcast order coding w_idx: {} br_idx :{}", - me.id, - w_idx, - br_idx, - ); - - orders.push(( - window_l[w_idx].coding.clone(), - vec![&broadcast_table[br_idx]], - )); - br_idx += 1; - br_idx %= broadcast_table.len(); - } - - orders - } - - pub fn window_index_request(&self, ix: u64) -> Result<(SocketAddr, Vec)> { - // find a peer that appears to be accepting replication, as indicated - // by a valid tvu port location - let valid: Vec<_> = self.tvu_peers(); - if valid.is_empty() { - Err(ClusterInfoError::NoPeers)?; - } - let n = thread_rng().gen::() % valid.len(); - let addr = valid[n].ncp; // send the request to the peer's gossip port - let req = Protocol::RequestWindowIndex(self.my_data().clone(), ix); - let out = serialize(&req)?; - Ok((addr, out)) - } - fn new_pull_requests(&mut self) -> Vec<(SocketAddr, Protocol)> { - let now = timestamp(); - let pulls: Vec<_> = self.gossip.new_pull_request(now).ok().into_iter().collect(); - - let pr: Vec<_> = pulls - .into_iter() - .filter_map(|(peer, filter, self_info)| { - let peer_label = CrdsValueLabel::ContactInfo(peer); - self.gossip - .crds - .lookup(&peer_label) - .and_then(|v| v.contact_info()) - .map(|peer_info| (peer, filter, peer_info.ncp, self_info)) - }).collect(); - pr.into_iter() - .map(|(peer, filter, ncp, self_info)| { - self.gossip.mark_pull_request_creation_time(peer, now); - (ncp, Protocol::PullRequest(filter, self_info)) - }).collect() - } - fn new_push_requests(&mut self) -> Vec<(SocketAddr, Protocol)> { - let self_id = self.gossip.id; - let (_, peers, msgs) = self.gossip.new_push_messages(timestamp()); - peers - .into_iter() - .filter_map(|p| { - let peer_label = CrdsValueLabel::ContactInfo(p); - self.gossip - .crds - .lookup(&peer_label) - .and_then(|v| v.contact_info()) - .map(|p| p.ncp) - }).map(|peer| (peer, Protocol::PushMessage(self_id, msgs.clone()))) - .collect() - } - - fn gossip_request(&mut self) -> Vec<(SocketAddr, Protocol)> { - let pulls: Vec<_> = self.new_pull_requests(); - let pushes: Vec<_> = self.new_push_requests(); - vec![pulls, pushes].into_iter().flat_map(|x| x).collect() - } - - /// At random pick a node and try to get updated changes from them - fn run_gossip(obj: &Arc>, blob_sender: &BlobSender) -> Result<()> { - let reqs = obj.write().unwrap().gossip_request(); - let blobs = reqs - .into_iter() - .filter_map(|(remote_gossip_addr, req)| to_blob(req, remote_gossip_addr).ok()) - .collect(); - blob_sender.send(blobs)?; - Ok(()) - } - - pub fn get_gossip_top_leader(&self) -> Option<&NodeInfo> { - let mut table = HashMap::new(); - let def = Pubkey::default(); - let cur = self - .gossip - .crds - .table - .values() - .filter_map(|x| x.value.leader_id()) - .filter(|x| x.leader_id != def); - for v in cur { - let cnt = table.entry(&v.leader_id).or_insert(0); - *cnt += 1; - trace!("leader {} {}", v.leader_id, *cnt); - } - let mut sorted: Vec<(&Pubkey, usize)> = table.into_iter().collect(); - for x in &sorted { - trace!("{}: sorted leaders {} votes: {}", self.gossip.id, x.0, x.1); - } - sorted.sort_by_key(|a| a.1); - let top_leader = sorted.last().map(|a| *a.0); - - top_leader - .and_then(|x| { - let leader_label = CrdsValueLabel::ContactInfo(x); - self.gossip.crds.lookup(&leader_label) - }).and_then(|x| x.contact_info()) - } - - /// randomly pick a node and ask them for updates asynchronously - pub fn gossip( - obj: Arc>, - blob_sender: BlobSender, - exit: Arc, - ) -> JoinHandle<()> { - Builder::new() - .name("solana-gossip".to_string()) - .spawn(move || { - let mut last_push = timestamp(); - loop { - let start = timestamp(); - let _ = Self::run_gossip(&obj, &blob_sender); - if exit.load(Ordering::Relaxed) { - return; - } - obj.write().unwrap().purge(timestamp()); - //TODO: possibly tune this parameter - //we saw a deadlock passing an obj.read().unwrap().timeout into sleep - if start - last_push > CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS / 2 { - obj.write().unwrap().push_self(); - last_push = timestamp(); - } - let elapsed = timestamp() - start; - if GOSSIP_SLEEP_MILLIS > elapsed { - let time_left = GOSSIP_SLEEP_MILLIS - elapsed; - sleep(Duration::from_millis(time_left)); - } - } - }).unwrap() - } - fn run_window_request( - from: &NodeInfo, - from_addr: &SocketAddr, - window: &SharedWindow, - ledger_window: &mut Option<&mut LedgerWindow>, - me: &NodeInfo, - leader_id: Pubkey, - ix: u64, - ) -> Vec { - let pos = (ix as usize) % window.read().unwrap().len(); - if let Some(ref mut blob) = &mut window.write().unwrap()[pos].data { - let mut wblob = blob.write().unwrap(); - let blob_ix = wblob.index().expect("run_window_request index"); - if blob_ix == ix { - let num_retransmits = wblob.meta.num_retransmits; - wblob.meta.num_retransmits += 1; - // Setting the sender id to the requester id - // prevents the requester from retransmitting this response - // to other peers - let mut sender_id = from.id; - - // Allow retransmission of this response if the node - // is the leader and the number of repair requests equals - // a power of two - if leader_id == me.id && (num_retransmits == 0 || num_retransmits.is_power_of_two()) - { - sender_id = me.id - } - - let out = SharedBlob::default(); - - // copy to avoid doing IO inside the lock - { - let mut outblob = out.write().unwrap(); - let sz = wblob.meta.size; - outblob.meta.size = sz; - outblob.data[..sz].copy_from_slice(&wblob.data[..sz]); - outblob.meta.set_addr(from_addr); - outblob.set_id(&sender_id).expect("blob set_id"); - } - inc_new_counter_info!("cluster_info-window-request-pass", 1); - - return vec![out]; - } else { - inc_new_counter_info!("cluster_info-window-request-outside", 1); - trace!( - "requested ix {} != blob_ix {}, outside window!", - ix, - blob_ix - ); - // falls through to checking window_ledger - } - } - - if let Some(ledger_window) = ledger_window { - if let Ok(entry) = ledger_window.get_entry(ix) { - inc_new_counter_info!("cluster_info-window-request-ledger", 1); - - let out = entry.to_blob( - Some(ix), - Some(me.id), // causes retransmission if I'm the leader - Some(from_addr), - ); - - return vec![out]; - } - } - - inc_new_counter_info!("cluster_info-window-request-fail", 1); - trace!( - "{}: failed RequestWindowIndex {} {} {}", - me.id, - from.id, - ix, - pos, - ); - - vec![] - } - - //TODO we should first coalesce all the requests - fn handle_blob( - obj: &Arc>, - window: &SharedWindow, - ledger_window: &mut Option<&mut LedgerWindow>, - blob: &Blob, - ) -> Vec { - deserialize(&blob.data[..blob.meta.size]) - .into_iter() - .flat_map(|request| { - ClusterInfo::handle_protocol(obj, &blob.meta.addr(), request, window, ledger_window) - }).collect() - } - fn handle_pull_request( - me: &Arc>, - filter: Bloom, - caller: CrdsValue, - from_addr: &SocketAddr, - ) -> Vec { - let self_id = me.read().unwrap().gossip.id; - inc_new_counter_info!("cluster_info-pull_request", 1); - if caller.contact_info().is_none() { - return vec![]; - } - let mut from = caller.contact_info().cloned().unwrap(); - if from.id == self_id { - warn!( - "PullRequest ignored, I'm talking to myself: me={} remoteme={}", - self_id, from.id - ); - inc_new_counter_info!("cluster_info-window-request-loopback", 1); - return vec![]; - } - let now = timestamp(); - let data = me - .write() - .unwrap() - .gossip - .process_pull_request(caller, filter, now); - let len = data.len(); - trace!("get updates since response {}", len); - if data.is_empty() { - trace!("no updates me {}", self_id); - vec![] - } else { - let rsp = Protocol::PullResponse(self_id, data); - // the remote side may not know his public IP:PORT, record what he looks like to us - // this may or may not be correct for everybody but it's better than leaving him with - // an unspecified address in our table - if from.ncp.ip().is_unspecified() { - inc_new_counter_info!("cluster_info-window-request-updates-unspec-ncp", 1); - from.ncp = *from_addr; - } - inc_new_counter_info!("cluster_info-pull_request-rsp", len); - to_blob(rsp, from.ncp).ok().into_iter().collect() - } - } - fn handle_pull_response(me: &Arc>, from: Pubkey, data: Vec) { - let len = data.len(); - let now = Instant::now(); - let self_id = me.read().unwrap().gossip.id; - trace!("PullResponse me: {} len={}", self_id, len); - me.write() - .unwrap() - .gossip - .process_pull_response(from, data, timestamp()); - inc_new_counter_info!("cluster_info-pull_request_response", 1); - inc_new_counter_info!("cluster_info-pull_request_response-size", len); - - report_time_spent("ReceiveUpdates", &now.elapsed(), &format!(" len: {}", len)); - } - fn handle_push_message( - me: &Arc>, - from: Pubkey, - data: &[CrdsValue], - ) -> Vec { - let self_id = me.read().unwrap().gossip.id; - inc_new_counter_info!("cluster_info-push_message", 1); - let prunes: Vec<_> = me - .write() - .unwrap() - .gossip - .process_push_message(&data, timestamp()); - if !prunes.is_empty() { - let mut wme = me.write().unwrap(); - inc_new_counter_info!("cluster_info-push_message-prunes", prunes.len()); - let rsp = Protocol::PruneMessage(self_id, prunes); - let ci = wme.lookup(from).cloned(); - let pushes: Vec<_> = wme.new_push_requests(); - inc_new_counter_info!("cluster_info-push_message-pushes", pushes.len()); - let mut rsp: Vec<_> = ci - .and_then(|ci| to_blob(rsp, ci.ncp).ok()) - .into_iter() - .collect(); - let mut blobs: Vec<_> = pushes - .into_iter() - .filter_map(|(remote_gossip_addr, req)| to_blob(req, remote_gossip_addr).ok()) - .collect(); - rsp.append(&mut blobs); - rsp - } else { - vec![] - } - } - fn handle_request_window_index( - me: &Arc>, - from: &ContactInfo, - ix: u64, - from_addr: &SocketAddr, - window: &SharedWindow, - ledger_window: &mut Option<&mut LedgerWindow>, - ) -> Vec { - let now = Instant::now(); - - //TODO this doesn't depend on cluster_info module, could be moved - //but we are using the listen thread to service these request - //TODO verify from is signed - - let self_id = me.read().unwrap().gossip.id; - if from.id == me.read().unwrap().gossip.id { - warn!( - "{}: Ignored received RequestWindowIndex from ME {} {} ", - self_id, from.id, ix, - ); - inc_new_counter_info!("cluster_info-window-request-address-eq", 1); - return vec![]; - } - - me.write().unwrap().insert_info(from.clone()); - let leader_id = me.read().unwrap().leader_id(); - let my_info = me.read().unwrap().my_data().clone(); - inc_new_counter_info!("cluster_info-window-request-recv", 1); - trace!( - "{}: received RequestWindowIndex {} {} ", - self_id, - from.id, - ix, - ); - let res = Self::run_window_request( - &from, - &from_addr, - &window, - ledger_window, - &my_info, - leader_id, - ix, - ); - report_time_spent( - "RequestWindowIndex", - &now.elapsed(), - &format!(" ix: {}", ix), - ); - res - } - fn handle_protocol( - me: &Arc>, - from_addr: &SocketAddr, - request: Protocol, - window: &SharedWindow, - ledger_window: &mut Option<&mut LedgerWindow>, - ) -> Vec { - match request { - // TODO sigverify these - Protocol::PullRequest(filter, caller) => { - Self::handle_pull_request(me, filter, caller, from_addr) - } - Protocol::PullResponse(from, data) => { - Self::handle_pull_response(me, from, data); - vec![] - } - Protocol::PushMessage(from, data) => Self::handle_push_message(me, from, &data), - Protocol::PruneMessage(from, data) => { - inc_new_counter_info!("cluster_info-prune_message", 1); - inc_new_counter_info!("cluster_info-prune_message-size", data.len()); - me.write().unwrap().gossip.process_prune_msg(from, &data); - vec![] - } - Protocol::RequestWindowIndex(from, ix) => { - Self::handle_request_window_index(me, &from, ix, from_addr, window, ledger_window) - } - } - } - - /// Process messages from the network - fn run_listen( - obj: &Arc>, - window: &SharedWindow, - ledger_window: &mut Option<&mut LedgerWindow>, - requests_receiver: &BlobReceiver, - response_sender: &BlobSender, - ) -> Result<()> { - //TODO cache connections - let timeout = Duration::new(1, 0); - let mut reqs = requests_receiver.recv_timeout(timeout)?; - while let Ok(mut more) = requests_receiver.try_recv() { - reqs.append(&mut more); - } - let mut resps = Vec::new(); - for req in reqs { - let mut resp = Self::handle_blob(obj, window, ledger_window, &req.read().unwrap()); - resps.append(&mut resp); - } - response_sender.send(resps)?; - Ok(()) - } - pub fn listen( - me: Arc>, - window: SharedWindow, - ledger_path: Option<&str>, - requests_receiver: BlobReceiver, - response_sender: BlobSender, - exit: Arc, - ) -> JoinHandle<()> { - let mut ledger_window = ledger_path.map(|p| LedgerWindow::open(p).unwrap()); - - Builder::new() - .name("solana-listen".to_string()) - .spawn(move || loop { - let e = Self::run_listen( - &me, - &window, - &mut ledger_window.as_mut(), - &requests_receiver, - &response_sender, - ); - if exit.load(Ordering::Relaxed) { - return; - } - if e.is_err() { - let me = me.read().unwrap(); - debug!( - "{}: run_listen timeout, table size: {}", - me.gossip.id, - me.gossip.crds.table.len() - ); - } - }).unwrap() - } - - pub fn spy_node() -> (NodeInfo, UdpSocket) { - let (_, gossip_socket) = bind_in_range(FULLNODE_PORT_RANGE).unwrap(); - let pubkey = Keypair::new().pubkey(); - let daddr = socketaddr_any!(); - - let node = NodeInfo::new( - pubkey, - daddr, - daddr, - daddr, - daddr, - daddr, - daddr, - timestamp(), - ); - (node, gossip_socket) - } -} - -#[derive(Debug)] -pub struct Sockets { - pub gossip: UdpSocket, - pub replicate: Vec, - pub transaction: Vec, - pub broadcast: UdpSocket, - pub repair: UdpSocket, - pub retransmit: UdpSocket, -} - -#[derive(Debug)] -pub struct Node { - pub info: NodeInfo, - pub sockets: Sockets, -} - -impl Node { - pub fn new_localhost() -> Self { - let pubkey = Keypair::new().pubkey(); - Self::new_localhost_with_pubkey(pubkey) - } - pub fn new_localhost_with_pubkey(pubkey: Pubkey) -> Self { - let transaction = UdpSocket::bind("127.0.0.1:0").unwrap(); - let gossip = UdpSocket::bind("127.0.0.1:0").unwrap(); - let replicate = UdpSocket::bind("127.0.0.1:0").unwrap(); - let repair = UdpSocket::bind("127.0.0.1:0").unwrap(); - let rpc_port = find_available_port_in_range((1024, 65535)).unwrap(); - let rpc_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), rpc_port); - let rpc_pubsub_port = find_available_port_in_range((1024, 65535)).unwrap(); - let rpc_pubsub_addr = - SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), rpc_pubsub_port); - - let broadcast = UdpSocket::bind("0.0.0.0:0").unwrap(); - let retransmit = UdpSocket::bind("0.0.0.0:0").unwrap(); - let storage = UdpSocket::bind("0.0.0.0:0").unwrap(); - let info = NodeInfo::new( - pubkey, - gossip.local_addr().unwrap(), - replicate.local_addr().unwrap(), - transaction.local_addr().unwrap(), - storage.local_addr().unwrap(), - rpc_addr, - rpc_pubsub_addr, - timestamp(), - ); - Node { - info, - sockets: Sockets { - gossip, - replicate: vec![replicate], - transaction: vec![transaction], - broadcast, - repair, - retransmit, - }, - } - } - pub fn new_with_external_ip(pubkey: Pubkey, ncp: &SocketAddr) -> Node { - fn bind() -> (u16, UdpSocket) { - bind_in_range(FULLNODE_PORT_RANGE).expect("Failed to bind") - }; - - let (gossip_port, gossip) = if ncp.port() != 0 { - (ncp.port(), bind_to(ncp.port(), false).expect("ncp bind")) - } else { - bind() - }; - - let (replicate_port, replicate_sockets) = - multi_bind_in_range(FULLNODE_PORT_RANGE, 8).expect("tvu multi_bind"); - - let (transaction_port, transaction_sockets) = - multi_bind_in_range(FULLNODE_PORT_RANGE, 32).expect("tpu multi_bind"); - - let (_, repair) = bind(); - let (_, broadcast) = bind(); - let (_, retransmit) = bind(); - let (storage_port, _) = bind(); - - let info = NodeInfo::new( - pubkey, - SocketAddr::new(ncp.ip(), gossip_port), - SocketAddr::new(ncp.ip(), replicate_port), - SocketAddr::new(ncp.ip(), transaction_port), - SocketAddr::new(ncp.ip(), storage_port), - SocketAddr::new(ncp.ip(), RPC_PORT), - SocketAddr::new(ncp.ip(), RPC_PORT + 1), - 0, - ); - trace!("new NodeInfo: {:?}", info); - - Node { - info, - sockets: Sockets { - gossip, - replicate: replicate_sockets, - transaction: transaction_sockets, - broadcast, - repair, - retransmit, - }, - } - } -} - -fn report_time_spent(label: &str, time: &Duration, extra: &str) { - let count = duration_as_ms(time); - if count > 5 { - info!("{} took: {} ms {}", label, count, extra); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crds_value::CrdsValueLabel; - use entry::Entry; - use ledger::{get_tmp_ledger_path, LedgerWindow, LedgerWriter}; - use logger; - use packet::SharedBlob; - use result::Error; - use signature::{Keypair, KeypairUtil}; - use solana_sdk::hash::{hash, Hash}; - use std::fs::remove_dir_all; - use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - use std::sync::{Arc, RwLock}; - use window::default_window; - - #[test] - fn test_cluster_spy_gossip() { - //check that gossip doesn't try to push to invalid addresses - let node = Node::new_localhost(); - let (spy, _) = ClusterInfo::spy_node(); - let cluster_info = Arc::new(RwLock::new(ClusterInfo::new(node.info))); - cluster_info.write().unwrap().insert_info(spy); - cluster_info - .write() - .unwrap() - .gossip - .refresh_push_active_set(); - let reqs = cluster_info.write().unwrap().gossip_request(); - //assert none of the addrs are invalid. - reqs.iter().all(|(addr, _)| { - let res = ContactInfo::is_valid_address(addr); - assert!(res); - res - }); - } - - #[test] - fn test_cluster_info_new() { - let d = NodeInfo::new_localhost(Keypair::new().pubkey(), timestamp()); - let cluster_info = ClusterInfo::new(d.clone()); - assert_eq!(d.id, cluster_info.my_data().id); - } - - #[test] - fn insert_info_test() { - let d = NodeInfo::new_localhost(Keypair::new().pubkey(), timestamp()); - let mut cluster_info = ClusterInfo::new(d); - let d = NodeInfo::new_localhost(Keypair::new().pubkey(), timestamp()); - let label = CrdsValueLabel::ContactInfo(d.id); - cluster_info.insert_info(d); - assert!(cluster_info.gossip.crds.lookup(&label).is_some()); - } - #[test] - fn window_index_request() { - let me = NodeInfo::new_localhost(Keypair::new().pubkey(), timestamp()); - let mut cluster_info = ClusterInfo::new(me); - let rv = cluster_info.window_index_request(0); - assert_matches!(rv, Err(Error::ClusterInfoError(ClusterInfoError::NoPeers))); - - let ncp = socketaddr!([127, 0, 0, 1], 1234); - let nxt = NodeInfo::new( - Keypair::new().pubkey(), - ncp, - socketaddr!([127, 0, 0, 1], 1235), - socketaddr!([127, 0, 0, 1], 1236), - socketaddr!([127, 0, 0, 1], 1237), - socketaddr!([127, 0, 0, 1], 1238), - socketaddr!([127, 0, 0, 1], 1239), - 0, - ); - cluster_info.insert_info(nxt.clone()); - let rv = cluster_info.window_index_request(0).unwrap(); - assert_eq!(nxt.ncp, ncp); - assert_eq!(rv.0, nxt.ncp); - - let ncp2 = socketaddr!([127, 0, 0, 2], 1234); - let nxt = NodeInfo::new( - Keypair::new().pubkey(), - ncp2, - socketaddr!([127, 0, 0, 1], 1235), - socketaddr!([127, 0, 0, 1], 1236), - socketaddr!([127, 0, 0, 1], 1237), - socketaddr!([127, 0, 0, 1], 1238), - socketaddr!([127, 0, 0, 1], 1239), - 0, - ); - cluster_info.insert_info(nxt); - let mut one = false; - let mut two = false; - while !one || !two { - //this randomly picks an option, so eventually it should pick both - let rv = cluster_info.window_index_request(0).unwrap(); - if rv.0 == ncp { - one = true; - } - if rv.0 == ncp2 { - two = true; - } - } - assert!(one && two); - } - - /// test window requests respond with the right blob, and do not overrun - #[test] - fn run_window_request() { - logger::setup(); - let window = Arc::new(RwLock::new(default_window())); - let me = NodeInfo::new( - Keypair::new().pubkey(), - socketaddr!("127.0.0.1:1234"), - socketaddr!("127.0.0.1:1235"), - socketaddr!("127.0.0.1:1236"), - socketaddr!("127.0.0.1:1237"), - socketaddr!("127.0.0.1:1238"), - socketaddr!("127.0.0.1:1239"), - 0, - ); - let leader_id = me.id; - let rv = ClusterInfo::run_window_request( - &me, - &socketaddr_any!(), - &window, - &mut None, - &me, - leader_id, - 0, - ); - assert!(rv.is_empty()); - let out = SharedBlob::default(); - out.write().unwrap().meta.size = 200; - window.write().unwrap()[0].data = Some(out); - let rv = ClusterInfo::run_window_request( - &me, - &socketaddr_any!(), - &window, - &mut None, - &me, - leader_id, - 0, - ); - assert!(!rv.is_empty()); - let v = rv[0].clone(); - //test we copied the blob - assert_eq!(v.read().unwrap().meta.size, 200); - let len = window.read().unwrap().len() as u64; - let rv = ClusterInfo::run_window_request( - &me, - &socketaddr_any!(), - &window, - &mut None, - &me, - leader_id, - len, - ); - assert!(rv.is_empty()); - - fn tmp_ledger(name: &str) -> String { - let path = get_tmp_ledger_path(name); - - let mut writer = LedgerWriter::open(&path, true).unwrap(); - let zero = Hash::default(); - let one = hash(&zero.as_ref()); - writer - .write_entries( - &vec![ - Entry::new_tick(&zero, 0, &zero), - Entry::new_tick(&one, 0, &one), - ].to_vec(), - ).unwrap(); - path - } - - let ledger_path = tmp_ledger("run_window_request"); - let mut ledger_window = LedgerWindow::open(&ledger_path).unwrap(); - - let rv = ClusterInfo::run_window_request( - &me, - &socketaddr_any!(), - &window, - &mut Some(&mut ledger_window), - &me, - leader_id, - 1, - ); - assert!(!rv.is_empty()); - - remove_dir_all(ledger_path).unwrap(); - } - - /// test window requests respond with the right blob, and do not overrun - #[test] - fn run_window_request_with_backoff() { - let window = Arc::new(RwLock::new(default_window())); - - let me = NodeInfo::new_with_socketaddr(&socketaddr!("127.0.0.1:1234")); - let leader_id = me.id; - - let mock_peer = NodeInfo::new_with_socketaddr(&socketaddr!("127.0.0.1:1234")); - - // Simulate handling a repair request from mock_peer - let rv = ClusterInfo::run_window_request( - &mock_peer, - &socketaddr_any!(), - &window, - &mut None, - &me, - leader_id, - 0, - ); - assert!(rv.is_empty()); - let blob = SharedBlob::default(); - let blob_size = 200; - blob.write().unwrap().meta.size = blob_size; - window.write().unwrap()[0].data = Some(blob); - - let num_requests: u32 = 64; - for i in 0..num_requests { - let shared_blob = ClusterInfo::run_window_request( - &mock_peer, - &socketaddr_any!(), - &window, - &mut None, - &me, - leader_id, - 0, - )[0].clone(); - let blob = shared_blob.read().unwrap(); - // Test we copied the blob - assert_eq!(blob.meta.size, blob_size); - - let id = if i == 0 || i.is_power_of_two() { - me.id - } else { - mock_peer.id - }; - assert_eq!(blob.id().unwrap(), id); - } - } - - #[test] - fn test_default_leader() { - logger::setup(); - let node_info = NodeInfo::new_localhost(Keypair::new().pubkey(), 0); - let mut cluster_info = ClusterInfo::new(node_info); - let network_entry_point = NodeInfo::new_entry_point(&socketaddr!("127.0.0.1:1239")); - cluster_info.insert_info(network_entry_point); - assert!(cluster_info.leader_data().is_none()); - } - - #[test] - fn new_with_external_ip_test_random() { - let ip = Ipv4Addr::from(0); - let node = Node::new_with_external_ip(Keypair::new().pubkey(), &socketaddr!(ip, 0)); - assert_eq!(node.sockets.gossip.local_addr().unwrap().ip(), ip); - assert!(node.sockets.replicate.len() > 1); - for tx_socket in node.sockets.replicate.iter() { - assert_eq!(tx_socket.local_addr().unwrap().ip(), ip); - } - assert!(node.sockets.transaction.len() > 1); - for tx_socket in node.sockets.transaction.iter() { - assert_eq!(tx_socket.local_addr().unwrap().ip(), ip); - } - assert_eq!(node.sockets.repair.local_addr().unwrap().ip(), ip); - - assert!(node.sockets.gossip.local_addr().unwrap().port() >= FULLNODE_PORT_RANGE.0); - assert!(node.sockets.gossip.local_addr().unwrap().port() < FULLNODE_PORT_RANGE.1); - let tx_port = node.sockets.replicate[0].local_addr().unwrap().port(); - assert!(tx_port >= FULLNODE_PORT_RANGE.0); - assert!(tx_port < FULLNODE_PORT_RANGE.1); - for tx_socket in node.sockets.replicate.iter() { - assert_eq!(tx_socket.local_addr().unwrap().port(), tx_port); - } - let tx_port = node.sockets.transaction[0].local_addr().unwrap().port(); - assert!(tx_port >= FULLNODE_PORT_RANGE.0); - assert!(tx_port < FULLNODE_PORT_RANGE.1); - for tx_socket in node.sockets.transaction.iter() { - assert_eq!(tx_socket.local_addr().unwrap().port(), tx_port); - } - assert!(node.sockets.repair.local_addr().unwrap().port() >= FULLNODE_PORT_RANGE.0); - assert!(node.sockets.repair.local_addr().unwrap().port() < FULLNODE_PORT_RANGE.1); - } - - #[test] - fn new_with_external_ip_test_gossip() { - let ip = IpAddr::V4(Ipv4Addr::from(0)); - let node = Node::new_with_external_ip(Keypair::new().pubkey(), &socketaddr!(0, 8050)); - assert_eq!(node.sockets.gossip.local_addr().unwrap().ip(), ip); - assert!(node.sockets.replicate.len() > 1); - for tx_socket in node.sockets.replicate.iter() { - assert_eq!(tx_socket.local_addr().unwrap().ip(), ip); - } - assert!(node.sockets.transaction.len() > 1); - for tx_socket in node.sockets.transaction.iter() { - assert_eq!(tx_socket.local_addr().unwrap().ip(), ip); - } - assert_eq!(node.sockets.repair.local_addr().unwrap().ip(), ip); - - assert_eq!(node.sockets.gossip.local_addr().unwrap().port(), 8050); - let tx_port = node.sockets.replicate[0].local_addr().unwrap().port(); - assert!(tx_port >= FULLNODE_PORT_RANGE.0); - assert!(tx_port < FULLNODE_PORT_RANGE.1); - for tx_socket in node.sockets.replicate.iter() { - assert_eq!(tx_socket.local_addr().unwrap().port(), tx_port); - } - let tx_port = node.sockets.transaction[0].local_addr().unwrap().port(); - assert!(tx_port >= FULLNODE_PORT_RANGE.0); - assert!(tx_port < FULLNODE_PORT_RANGE.1); - for tx_socket in node.sockets.transaction.iter() { - assert_eq!(tx_socket.local_addr().unwrap().port(), tx_port); - } - assert!(node.sockets.repair.local_addr().unwrap().port() >= FULLNODE_PORT_RANGE.0); - assert!(node.sockets.repair.local_addr().unwrap().port() < FULLNODE_PORT_RANGE.1); - } -} diff --git a/book/compute_leader_finality_service.rs b/book/compute_leader_finality_service.rs deleted file mode 100644 index 0ab54e76e34abc..00000000000000 --- a/book/compute_leader_finality_service.rs +++ /dev/null @@ -1,205 +0,0 @@ -//! The `compute_leader_finality_service` module implements the tools necessary -//! to generate a thread which regularly calculates the last finality times -//! observed by the leader - -use bank::Bank; - -use service::Service; -use solana_metrics::{influxdb, submit}; -use solana_sdk::timing; -use std::result; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use std::thread::sleep; -use std::thread::{self, Builder, JoinHandle}; -use std::time::Duration; -use vote_program::VoteProgram; - -#[derive(Debug, PartialEq, Eq)] -pub enum FinalityError { - NoValidSupermajority, -} - -pub const COMPUTE_FINALITY_MS: u64 = 1000; - -pub struct ComputeLeaderFinalityService { - compute_finality_thread: JoinHandle<()>, -} - -impl ComputeLeaderFinalityService { - fn get_last_supermajority_timestamp( - bank: &Arc, - now: u64, - last_valid_validator_timestamp: u64, - ) -> result::Result { - let mut total_stake = 0; - - let mut ticks_and_stakes: Vec<(u64, u64)> = { - let bank_accounts = bank.accounts.read().unwrap(); - // TODO: Doesn't account for duplicates since a single validator could potentially register - // multiple vote accounts. Once that is no longer possible (see the TODO in vote_program.rs, - // process_transaction(), case VoteInstruction::RegisterAccount), this will be more accurate. - // See github issue 1654. - bank_accounts - .accounts - .values() - .filter_map(|account| { - // Filter out any accounts that don't belong to the VoteProgram - // by returning None - if VoteProgram::check_id(&account.owner) { - if let Ok(vote_state) = VoteProgram::deserialize(&account.userdata) { - let validator_stake = bank.get_stake(&vote_state.node_id); - total_stake += validator_stake; - // Filter out any validators that don't have at least one vote - // by returning None - return vote_state - .votes - .back() - .map(|vote| (vote.tick_height, validator_stake)); - } - } - - None - }).collect() - }; - - let super_majority_stake = (2 * total_stake) / 3; - - if let Some(last_valid_validator_timestamp) = - bank.get_finality_timestamp(&mut ticks_and_stakes, super_majority_stake) - { - return Ok(last_valid_validator_timestamp); - } - - if last_valid_validator_timestamp != 0 { - submit( - influxdb::Point::new(&"leader-finality") - .add_field( - "duration_ms", - influxdb::Value::Integer((now - last_valid_validator_timestamp) as i64), - ).to_owned(), - ); - } - - Err(FinalityError::NoValidSupermajority) - } - - pub fn compute_finality(bank: &Arc, last_valid_validator_timestamp: &mut u64) { - let now = timing::timestamp(); - if let Ok(super_majority_timestamp) = - Self::get_last_supermajority_timestamp(bank, now, *last_valid_validator_timestamp) - { - let finality_ms = now - super_majority_timestamp; - - *last_valid_validator_timestamp = super_majority_timestamp; - bank.set_finality((now - *last_valid_validator_timestamp) as usize); - - submit( - influxdb::Point::new(&"leader-finality") - .add_field("duration_ms", influxdb::Value::Integer(finality_ms as i64)) - .to_owned(), - ); - } - } - - /// Create a new ComputeLeaderFinalityService for computing finality. - pub fn new(bank: Arc, exit: Arc) -> Self { - let compute_finality_thread = Builder::new() - .name("solana-leader-finality-stage".to_string()) - .spawn(move || { - let mut last_valid_validator_timestamp = 0; - loop { - if exit.load(Ordering::Relaxed) { - break; - } - Self::compute_finality(&bank, &mut last_valid_validator_timestamp); - sleep(Duration::from_millis(COMPUTE_FINALITY_MS)); - } - }).unwrap(); - - (ComputeLeaderFinalityService { - compute_finality_thread, - }) - } -} - -impl Service for ComputeLeaderFinalityService { - type JoinReturnType = (); - - fn join(self) -> thread::Result<()> { - self.compute_finality_thread.join() - } -} - -#[cfg(test)] -pub mod tests { - use bank::Bank; - use bincode::serialize; - use compute_leader_finality_service::ComputeLeaderFinalityService; - use logger; - use mint::Mint; - use signature::{Keypair, KeypairUtil}; - use solana_sdk::hash::hash; - use std::sync::Arc; - use std::thread::sleep; - use std::time::Duration; - use transaction::Transaction; - use vote_program::Vote; - use vote_transaction::{create_vote_account, VoteTransaction}; - - #[test] - fn test_compute_finality() { - logger::setup(); - - let mint = Mint::new(1234); - let bank = Arc::new(Bank::new(&mint)); - // generate 10 validators, but only vote for the first 6 validators - let ids: Vec<_> = (0..10) - .map(|i| { - let last_id = hash(&serialize(&i).unwrap()); // Unique hash - bank.register_tick(&last_id); - // sleep to get a different timestamp in the bank - sleep(Duration::from_millis(1)); - last_id - }).collect(); - - // Create a total of 10 vote accounts, each will have a balance of 1 (after giving 1 to - // their vote account), for a total staking pool of 10 tokens. - let vote_accounts: Vec<_> = (0..10) - .map(|i| { - // Create new validator to vote - let validator_keypair = Keypair::new(); - let last_id = ids[i]; - - // Give the validator some tokens - bank.transfer(2, &mint.keypair(), validator_keypair.pubkey(), last_id) - .unwrap(); - let vote_account = create_vote_account(&validator_keypair, &bank, 1, last_id) - .expect("Expected successful creation of account"); - - if i < 6 { - let vote = Vote { - tick_height: (i + 1) as u64, - }; - let vote_tx = Transaction::vote_new(&vote_account, vote, last_id, 0); - bank.process_transaction(&vote_tx).unwrap(); - } - vote_account - }).collect(); - - // There isn't 2/3 consensus, so the bank's finality value should be the default - let mut last_finality_time = 0; - ComputeLeaderFinalityService::compute_finality(&bank, &mut last_finality_time); - assert_eq!(bank.finality(), std::usize::MAX); - - // Get another validator to vote, so we now have 2/3 consensus - let vote_account = &vote_accounts[7]; - let vote = Vote { tick_height: 7 }; - let vote_tx = Transaction::vote_new(&vote_account, vote, ids[6], 0); - bank.process_transaction(&vote_tx).unwrap(); - - ComputeLeaderFinalityService::compute_finality(&bank, &mut last_finality_time); - assert!(bank.finality() != std::usize::MAX); - assert!(last_finality_time > 0); - } -} diff --git a/book/contact_info.rs b/book/contact_info.rs deleted file mode 100644 index 56512031c924a0..00000000000000 --- a/book/contact_info.rs +++ /dev/null @@ -1,237 +0,0 @@ -use rpc::RPC_PORT; -use signature::{Keypair, KeypairUtil}; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::timing::timestamp; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - -/// Structure representing a node on the network -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct ContactInfo { - pub id: Pubkey, - /// gossip address - pub ncp: SocketAddr, - /// address to connect to for replication - pub tvu: SocketAddr, - /// transactions address - pub tpu: SocketAddr, - /// storage data address - pub storage_addr: SocketAddr, - /// address to which to send JSON-RPC requests - pub rpc: SocketAddr, - /// websocket for JSON-RPC push notifications - pub rpc_pubsub: SocketAddr, - /// latest wallclock picked - pub wallclock: u64, -} - -#[macro_export] -macro_rules! socketaddr { - ($ip:expr, $port:expr) => { - SocketAddr::from((Ipv4Addr::from($ip), $port)) - }; - ($str:expr) => {{ - let a: SocketAddr = $str.parse().unwrap(); - a - }}; -} -#[macro_export] -macro_rules! socketaddr_any { - () => { - socketaddr!(0, 0) - }; -} - -impl Default for ContactInfo { - fn default() -> Self { - ContactInfo { - id: Pubkey::default(), - ncp: socketaddr_any!(), - tvu: socketaddr_any!(), - tpu: socketaddr_any!(), - storage_addr: socketaddr_any!(), - rpc: socketaddr_any!(), - rpc_pubsub: socketaddr_any!(), - wallclock: 0, - } - } -} - -impl ContactInfo { - pub fn new( - id: Pubkey, - ncp: SocketAddr, - tvu: SocketAddr, - tpu: SocketAddr, - storage_addr: SocketAddr, - rpc: SocketAddr, - rpc_pubsub: SocketAddr, - now: u64, - ) -> Self { - ContactInfo { - id, - ncp, - tvu, - tpu, - storage_addr, - rpc, - rpc_pubsub, - wallclock: now, - } - } - - pub fn new_localhost(id: Pubkey, now: u64) -> Self { - Self::new( - id, - socketaddr!("127.0.0.1:1234"), - socketaddr!("127.0.0.1:1235"), - socketaddr!("127.0.0.1:1236"), - socketaddr!("127.0.0.1:1237"), - socketaddr!("127.0.0.1:1238"), - socketaddr!("127.0.0.1:1239"), - now, - ) - } - - #[cfg(test)] - /// ContactInfo with multicast addresses for adversarial testing. - pub fn new_multicast() -> Self { - let addr = socketaddr!("224.0.1.255:1000"); - assert!(addr.ip().is_multicast()); - Self::new( - Keypair::new().pubkey(), - addr, - addr, - addr, - addr, - addr, - addr, - 0, - ) - } - fn next_port(addr: &SocketAddr, nxt: u16) -> SocketAddr { - let mut nxt_addr = *addr; - nxt_addr.set_port(addr.port() + nxt); - nxt_addr - } - pub fn new_with_pubkey_socketaddr(pubkey: Pubkey, bind_addr: &SocketAddr) -> Self { - let transactions_addr = *bind_addr; - let gossip_addr = Self::next_port(&bind_addr, 1); - let replicate_addr = Self::next_port(&bind_addr, 2); - let rpc_addr = SocketAddr::new(bind_addr.ip(), RPC_PORT); - let rpc_pubsub_addr = SocketAddr::new(bind_addr.ip(), RPC_PORT + 1); - ContactInfo::new( - pubkey, - gossip_addr, - replicate_addr, - transactions_addr, - "0.0.0.0:0".parse().unwrap(), - rpc_addr, - rpc_pubsub_addr, - timestamp(), - ) - } - pub fn new_with_socketaddr(bind_addr: &SocketAddr) -> Self { - let keypair = Keypair::new(); - Self::new_with_pubkey_socketaddr(keypair.pubkey(), bind_addr) - } - // - pub fn new_entry_point(gossip_addr: &SocketAddr) -> Self { - let daddr: SocketAddr = socketaddr!("0.0.0.0:0"); - ContactInfo::new( - Pubkey::default(), - *gossip_addr, - daddr, - daddr, - daddr, - daddr, - daddr, - timestamp(), - ) - } - fn is_valid_ip(addr: IpAddr) -> bool { - !(addr.is_unspecified() || addr.is_multicast()) - // || (addr.is_loopback() && !cfg_test)) - // TODO: boot loopback in production networks - } - /// port must not be 0 - /// ip must be specified and not mulitcast - /// loopback ip is only allowed in tests - pub fn is_valid_address(addr: &SocketAddr) -> bool { - (addr.port() != 0) && Self::is_valid_ip(addr.ip()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_is_valid_address() { - assert!(cfg!(test)); - let bad_address_port = socketaddr!("127.0.0.1:0"); - assert!(!ContactInfo::is_valid_address(&bad_address_port)); - let bad_address_unspecified = socketaddr!(0, 1234); - assert!(!ContactInfo::is_valid_address(&bad_address_unspecified)); - let bad_address_multicast = socketaddr!([224, 254, 0, 0], 1234); - assert!(!ContactInfo::is_valid_address(&bad_address_multicast)); - let loopback = socketaddr!("127.0.0.1:1234"); - assert!(ContactInfo::is_valid_address(&loopback)); - // assert!(!ContactInfo::is_valid_ip_internal(loopback.ip(), false)); - } - #[test] - fn test_default() { - let ci = ContactInfo::default(); - assert!(ci.ncp.ip().is_unspecified()); - assert!(ci.tvu.ip().is_unspecified()); - assert!(ci.rpc.ip().is_unspecified()); - assert!(ci.rpc_pubsub.ip().is_unspecified()); - assert!(ci.tpu.ip().is_unspecified()); - assert!(ci.storage_addr.ip().is_unspecified()); - } - #[test] - fn test_multicast() { - let ci = ContactInfo::new_multicast(); - assert!(ci.ncp.ip().is_multicast()); - assert!(ci.tvu.ip().is_multicast()); - assert!(ci.rpc.ip().is_multicast()); - assert!(ci.rpc_pubsub.ip().is_multicast()); - assert!(ci.tpu.ip().is_multicast()); - assert!(ci.storage_addr.ip().is_multicast()); - } - #[test] - fn test_entry_point() { - let addr = socketaddr!("127.0.0.1:10"); - let ci = ContactInfo::new_entry_point(&addr); - assert_eq!(ci.ncp, addr); - assert!(ci.tvu.ip().is_unspecified()); - assert!(ci.rpc.ip().is_unspecified()); - assert!(ci.rpc_pubsub.ip().is_unspecified()); - assert!(ci.tpu.ip().is_unspecified()); - assert!(ci.storage_addr.ip().is_unspecified()); - } - #[test] - fn test_socketaddr() { - let addr = socketaddr!("127.0.0.1:10"); - let ci = ContactInfo::new_with_socketaddr(&addr); - assert_eq!(ci.tpu, addr); - assert_eq!(ci.ncp.port(), 11); - assert_eq!(ci.tvu.port(), 12); - assert_eq!(ci.rpc.port(), 8899); - assert_eq!(ci.rpc_pubsub.port(), 8900); - assert!(ci.storage_addr.ip().is_unspecified()); - } - #[test] - fn replicated_data_new_with_socketaddr_with_pubkey() { - let keypair = Keypair::new(); - let d1 = ContactInfo::new_with_pubkey_socketaddr( - keypair.pubkey().clone(), - &socketaddr!("127.0.0.1:1234"), - ); - assert_eq!(d1.id, keypair.pubkey()); - assert_eq!(d1.ncp, socketaddr!("127.0.0.1:1235")); - assert_eq!(d1.tvu, socketaddr!("127.0.0.1:1236")); - assert_eq!(d1.tpu, socketaddr!("127.0.0.1:1234")); - assert_eq!(d1.rpc, socketaddr!("127.0.0.1:8899")); - assert_eq!(d1.rpc_pubsub, socketaddr!("127.0.0.1:8900")); - } -} diff --git a/book/counter.rs b/book/counter.rs deleted file mode 100644 index fa6145ff004b25..00000000000000 --- a/book/counter.rs +++ /dev/null @@ -1,183 +0,0 @@ -use solana_metrics::{influxdb, submit}; -use solana_sdk::timing; -use std::env; -use std::sync::atomic::{AtomicUsize, Ordering}; - -const DEFAULT_LOG_RATE: usize = 1000; - -pub struct Counter { - pub name: &'static str, - /// total accumulated value - pub counts: AtomicUsize, - pub times: AtomicUsize, - /// last accumulated value logged - pub lastlog: AtomicUsize, - pub lograte: AtomicUsize, -} - -macro_rules! create_counter { - ($name:expr, $lograte:expr) => { - Counter { - name: $name, - counts: AtomicUsize::new(0), - times: AtomicUsize::new(0), - lastlog: AtomicUsize::new(0), - lograte: AtomicUsize::new($lograte), - } - }; -} - -macro_rules! inc_counter { - ($name:expr, $level:expr, $count:expr) => { - unsafe { $name.inc($level, $count) }; - }; -} - -macro_rules! inc_new_counter_info { - ($name:expr, $count:expr) => {{ - inc_new_counter!($name, $count, Level::Info, 0); - }}; - ($name:expr, $count:expr, $lograte:expr) => {{ - inc_new_counter!($name, $count, Level::Info, $lograte); - }}; -} - -macro_rules! inc_new_counter { - ($name:expr, $count:expr, $level:expr, $lograte:expr) => {{ - static mut INC_NEW_COUNTER: Counter = create_counter!($name, $lograte); - inc_counter!(INC_NEW_COUNTER, $level, $count); - }}; -} - -impl Counter { - fn default_log_rate() -> usize { - let v = env::var("SOLANA_DEFAULT_LOG_RATE") - .map(|x| x.parse().unwrap_or(DEFAULT_LOG_RATE)) - .unwrap_or(DEFAULT_LOG_RATE); - if v == 0 { - DEFAULT_LOG_RATE - } else { - v - } - } - pub fn inc(&mut self, level: log::Level, events: usize) { - let counts = self.counts.fetch_add(events, Ordering::Relaxed); - let times = self.times.fetch_add(1, Ordering::Relaxed); - let mut lograte = self.lograte.load(Ordering::Relaxed); - if lograte == 0 { - lograte = Counter::default_log_rate(); - self.lograte.store(lograte, Ordering::Relaxed); - } - if times % lograte == 0 && times > 0 && log_enabled!(level) { - info!( - "COUNTER:{{\"name\": \"{}\", \"counts\": {}, \"samples\": {}, \"now\": {}, \"events\": {}}}", - self.name, - counts + events, - times, - timing::timestamp(), - events, - ); - } - let lastlog = self.lastlog.load(Ordering::Relaxed); - let prev = self - .lastlog - .compare_and_swap(lastlog, counts, Ordering::Relaxed); - if prev == lastlog { - submit( - influxdb::Point::new(&format!("counter-{}", self.name)) - .add_field( - "count", - influxdb::Value::Integer(counts as i64 - lastlog as i64), - ).to_owned(), - ); - } - } -} -#[cfg(test)] -mod tests { - use counter::{Counter, DEFAULT_LOG_RATE}; - use log::Level; - use std::env; - use std::sync::atomic::{AtomicUsize, Ordering}; - use std::sync::{Once, RwLock, ONCE_INIT}; - - fn get_env_lock() -> &'static RwLock<()> { - static mut ENV_LOCK: Option> = None; - static INIT_HOOK: Once = ONCE_INIT; - - unsafe { - INIT_HOOK.call_once(|| { - ENV_LOCK = Some(RwLock::new(())); - }); - &ENV_LOCK.as_ref().unwrap() - } - } - - #[test] - fn test_counter() { - let _readlock = get_env_lock().read(); - static mut COUNTER: Counter = create_counter!("test", 1000); - let count = 1; - inc_counter!(COUNTER, Level::Info, count); - unsafe { - assert_eq!(COUNTER.counts.load(Ordering::Relaxed), 1); - assert_eq!(COUNTER.times.load(Ordering::Relaxed), 1); - assert_eq!(COUNTER.lograte.load(Ordering::Relaxed), 1000); - assert_eq!(COUNTER.lastlog.load(Ordering::Relaxed), 0); - assert_eq!(COUNTER.name, "test"); - } - for _ in 0..199 { - inc_counter!(COUNTER, Level::Info, 2); - } - unsafe { - assert_eq!(COUNTER.lastlog.load(Ordering::Relaxed), 397); - } - inc_counter!(COUNTER, Level::Info, 2); - unsafe { - assert_eq!(COUNTER.lastlog.load(Ordering::Relaxed), 399); - } - } - #[test] - fn test_inc_new_counter() { - let _readlock = get_env_lock().read(); - //make sure that macros are syntactically correct - //the variable is internal to the macro scope so there is no way to introspect it - inc_new_counter_info!("counter-1", 1); - inc_new_counter_info!("counter-2", 1, 2); - } - #[test] - fn test_lograte() { - let _readlock = get_env_lock().read(); - assert_eq!( - Counter::default_log_rate(), - DEFAULT_LOG_RATE, - "default_log_rate() is {}, expected {}, SOLANA_DEFAULT_LOG_RATE environment variable set?", - Counter::default_log_rate(), - DEFAULT_LOG_RATE, - ); - static mut COUNTER: Counter = create_counter!("test_lograte", 0); - inc_counter!(COUNTER, Level::Info, 2); - unsafe { - assert_eq!(COUNTER.lograte.load(Ordering::Relaxed), DEFAULT_LOG_RATE); - } - } - - #[test] - fn test_lograte_env() { - assert_ne!(DEFAULT_LOG_RATE, 0); - let _writelock = get_env_lock().write(); - static mut COUNTER: Counter = create_counter!("test_lograte_env", 0); - env::set_var("SOLANA_DEFAULT_LOG_RATE", "50"); - inc_counter!(COUNTER, Level::Info, 2); - unsafe { - assert_eq!(COUNTER.lograte.load(Ordering::Relaxed), 50); - } - - static mut COUNTER2: Counter = create_counter!("test_lograte_env", 0); - env::set_var("SOLANA_DEFAULT_LOG_RATE", "0"); - inc_counter!(COUNTER2, Level::Info, 2); - unsafe { - assert_eq!(COUNTER2.lograte.load(Ordering::Relaxed), DEFAULT_LOG_RATE); - } - } -} diff --git a/book/crds.rs b/book/crds.rs deleted file mode 100644 index 82e40320e012fd..00000000000000 --- a/book/crds.rs +++ /dev/null @@ -1,351 +0,0 @@ -//! This module implements Cluster Replicated Data Store for -//! asynchronous updates in a distributed network. -//! -//! Data is stored in the CrdsValue type, each type has a specific -//! CrdsValueLabel. Labels are semantically grouped into a single record -//! that is identified by a Pubkey. -//! * 1 Pubkey maps many CrdsValueLabels -//! * 1 CrdsValueLabel maps to 1 CrdsValue -//! The Label, the record Pubkey, and all the record labels can be derived -//! from a single CrdsValue. -//! -//! The actual data is stored in a single map of -//! `CrdsValueLabel(Pubkey) -> CrdsValue` This allows for partial record -//! updates to be propagated through the network. -//! -//! This means that full `Record` updates are not atomic. -//! -//! Additional labels can be added by appending them to the CrdsValueLabel, -//! CrdsValue enums. -//! -//! Merge strategy is implemented in: -//! impl PartialOrd for VersionedCrdsValue -//! -//! A value is updated to a new version if the labels match, and the value -//! wallclock is later, or the value hash is greater. - -use bincode::serialize; -use crds_value::{CrdsValue, CrdsValueLabel}; -use indexmap::map::IndexMap; -use solana_sdk::hash::{hash, Hash}; -use solana_sdk::pubkey::Pubkey; -use std::cmp; - -pub struct Crds { - /// Stores the map of labels and values - pub table: IndexMap, -} - -#[derive(PartialEq, Debug)] -pub enum CrdsError { - InsertFailed, -} - -/// This structure stores some local metadata assosciated with the CrdsValue -/// The implementation of PartialOrd ensures that the "highest" version is always picked to be -/// stored in the Crds -#[derive(PartialEq, Debug)] -pub struct VersionedCrdsValue { - pub value: CrdsValue, - /// local time when inserted - pub insert_timestamp: u64, - /// local time when updated - pub local_timestamp: u64, - /// value hash - pub value_hash: Hash, -} - -impl PartialOrd for VersionedCrdsValue { - fn partial_cmp(&self, other: &VersionedCrdsValue) -> Option { - if self.value.label() != other.value.label() { - None - } else if self.value.wallclock() == other.value.wallclock() { - Some(self.value_hash.cmp(&other.value_hash)) - } else { - Some(self.value.wallclock().cmp(&other.value.wallclock())) - } - } -} -impl VersionedCrdsValue { - pub fn new(local_timestamp: u64, value: CrdsValue) -> Self { - let value_hash = hash(&serialize(&value).unwrap()); - VersionedCrdsValue { - value, - insert_timestamp: local_timestamp, - local_timestamp, - value_hash, - } - } -} - -impl Default for Crds { - fn default() -> Self { - Crds { - table: IndexMap::new(), - } - } -} - -impl Crds { - /// must be called atomically with `insert_versioned` - pub fn new_versioned(&self, local_timestamp: u64, value: CrdsValue) -> VersionedCrdsValue { - VersionedCrdsValue::new(local_timestamp, value) - } - /// insert the new value, returns the old value if insert succeeds - pub fn insert_versioned( - &mut self, - new_value: VersionedCrdsValue, - ) -> Result, CrdsError> { - let label = new_value.value.label(); - let wallclock = new_value.value.wallclock(); - let do_insert = self - .table - .get(&label) - .map(|current| new_value > *current) - .unwrap_or(true); - if do_insert { - let old = self.table.insert(label, new_value); - Ok(old) - } else { - trace!("INSERT FAILED data: {} new.wallclock: {}", label, wallclock,); - Err(CrdsError::InsertFailed) - } - } - pub fn insert( - &mut self, - value: CrdsValue, - local_timestamp: u64, - ) -> Result, CrdsError> { - let new_value = self.new_versioned(local_timestamp, value); - self.insert_versioned(new_value) - } - pub fn lookup(&self, label: &CrdsValueLabel) -> Option<&CrdsValue> { - self.table.get(label).map(|x| &x.value) - } - - pub fn lookup_versioned(&self, label: &CrdsValueLabel) -> Option<&VersionedCrdsValue> { - self.table.get(label) - } - - fn update_label_timestamp(&mut self, id: &CrdsValueLabel, now: u64) { - if let Some(e) = self.table.get_mut(id) { - e.local_timestamp = cmp::max(e.local_timestamp, now); - } - } - - /// Update the timestamp's of all the labels that are assosciated with Pubkey - pub fn update_record_timestamp(&mut self, pubkey: Pubkey, now: u64) { - for label in &CrdsValue::record_labels(pubkey) { - self.update_label_timestamp(label, now); - } - } - - /// find all the keys that are older or equal to min_ts - pub fn find_old_labels(&self, min_ts: u64) -> Vec { - self.table - .iter() - .filter_map(|(k, v)| { - if v.local_timestamp <= min_ts { - Some(k) - } else { - None - } - }).cloned() - .collect() - } - - pub fn remove(&mut self, key: &CrdsValueLabel) { - self.table.remove(key); - } -} - -#[cfg(test)] -mod test { - use super::*; - use contact_info::ContactInfo; - use crds_value::LeaderId; - use signature::{Keypair, KeypairUtil}; - - #[test] - fn test_insert() { - let mut crds = Crds::default(); - let val = CrdsValue::LeaderId(LeaderId::default()); - assert_eq!(crds.insert(val.clone(), 0).ok(), Some(None)); - assert_eq!(crds.table.len(), 1); - assert!(crds.table.contains_key(&val.label())); - assert_eq!(crds.table[&val.label()].local_timestamp, 0); - } - #[test] - fn test_update_old() { - let mut crds = Crds::default(); - let val = CrdsValue::LeaderId(LeaderId::default()); - assert_eq!(crds.insert(val.clone(), 0), Ok(None)); - assert_eq!(crds.insert(val.clone(), 1), Err(CrdsError::InsertFailed)); - assert_eq!(crds.table[&val.label()].local_timestamp, 0); - } - #[test] - fn test_update_new() { - let mut crds = Crds::default(); - let original = CrdsValue::LeaderId(LeaderId::default()); - assert_matches!(crds.insert(original.clone(), 0), Ok(_)); - let val = CrdsValue::LeaderId(LeaderId { - id: Pubkey::default(), - leader_id: Pubkey::default(), - wallclock: 1, - }); - assert_eq!( - crds.insert(val.clone(), 1).unwrap().unwrap().value, - original - ); - assert_eq!(crds.table[&val.label()].local_timestamp, 1); - } - #[test] - fn test_update_timestsamp() { - let mut crds = Crds::default(); - let val = CrdsValue::LeaderId(LeaderId::default()); - assert_eq!(crds.insert(val.clone(), 0), Ok(None)); - - crds.update_label_timestamp(&val.label(), 1); - assert_eq!(crds.table[&val.label()].local_timestamp, 1); - assert_eq!(crds.table[&val.label()].insert_timestamp, 0); - - let val2 = CrdsValue::ContactInfo(ContactInfo::default()); - assert_eq!(val2.label().pubkey(), val.label().pubkey()); - assert_matches!(crds.insert(val2.clone(), 0), Ok(None)); - - crds.update_record_timestamp(val.label().pubkey(), 2); - assert_eq!(crds.table[&val.label()].local_timestamp, 2); - assert_eq!(crds.table[&val.label()].insert_timestamp, 0); - assert_eq!(crds.table[&val2.label()].local_timestamp, 2); - assert_eq!(crds.table[&val2.label()].insert_timestamp, 0); - - crds.update_record_timestamp(val.label().pubkey(), 1); - assert_eq!(crds.table[&val.label()].local_timestamp, 2); - assert_eq!(crds.table[&val.label()].insert_timestamp, 0); - - let mut ci = ContactInfo::default(); - ci.wallclock += 1; - let val3 = CrdsValue::ContactInfo(ci); - assert_matches!(crds.insert(val3.clone(), 3), Ok(Some(_))); - assert_eq!(crds.table[&val2.label()].local_timestamp, 3); - assert_eq!(crds.table[&val2.label()].insert_timestamp, 3); - } - #[test] - fn test_find_old_records() { - let mut crds = Crds::default(); - let val = CrdsValue::LeaderId(LeaderId::default()); - assert_eq!(crds.insert(val.clone(), 1), Ok(None)); - - assert!(crds.find_old_labels(0).is_empty()); - assert_eq!(crds.find_old_labels(1), vec![val.label()]); - assert_eq!(crds.find_old_labels(2), vec![val.label()]); - } - #[test] - fn test_remove() { - let mut crds = Crds::default(); - let val = CrdsValue::LeaderId(LeaderId::default()); - assert_matches!(crds.insert(val.clone(), 1), Ok(_)); - - assert_eq!(crds.find_old_labels(1), vec![val.label()]); - crds.remove(&val.label()); - assert!(crds.find_old_labels(1).is_empty()); - } - #[test] - fn test_equal() { - let key = Keypair::new(); - let v1 = VersionedCrdsValue::new( - 1, - CrdsValue::LeaderId(LeaderId { - id: key.pubkey(), - leader_id: Pubkey::default(), - wallclock: 0, - }), - ); - let v2 = VersionedCrdsValue::new( - 1, - CrdsValue::LeaderId(LeaderId { - id: key.pubkey(), - leader_id: Pubkey::default(), - wallclock: 0, - }), - ); - assert!(!(v1 != v2)); - assert!(v1 == v2); - } - #[test] - fn test_hash_order() { - let key = Keypair::new(); - let v1 = VersionedCrdsValue::new( - 1, - CrdsValue::LeaderId(LeaderId { - id: key.pubkey(), - leader_id: Pubkey::default(), - wallclock: 0, - }), - ); - let v2 = VersionedCrdsValue::new( - 1, - CrdsValue::LeaderId(LeaderId { - id: key.pubkey(), - leader_id: key.pubkey(), - wallclock: 0, - }), - ); - assert!(v1 != v2); - assert!(!(v1 == v2)); - if v1 > v2 { - assert!(v2 < v1) - } else { - assert!(v2 > v1) - } - } - #[test] - fn test_wallclock_order() { - let key = Keypair::new(); - let v1 = VersionedCrdsValue::new( - 1, - CrdsValue::LeaderId(LeaderId { - id: key.pubkey(), - leader_id: Pubkey::default(), - wallclock: 1, - }), - ); - let v2 = VersionedCrdsValue::new( - 1, - CrdsValue::LeaderId(LeaderId { - id: key.pubkey(), - leader_id: Pubkey::default(), - wallclock: 0, - }), - ); - assert!(v1 > v2); - assert!(!(v1 < v2)); - assert!(v1 != v2); - assert!(!(v1 == v2)); - } - #[test] - fn test_label_order() { - let v1 = VersionedCrdsValue::new( - 1, - CrdsValue::LeaderId(LeaderId { - id: Keypair::new().pubkey(), - leader_id: Pubkey::default(), - wallclock: 0, - }), - ); - let v2 = VersionedCrdsValue::new( - 1, - CrdsValue::LeaderId(LeaderId { - id: Keypair::new().pubkey(), - leader_id: Pubkey::default(), - wallclock: 0, - }), - ); - assert!(v1 != v2); - assert!(!(v1 == v2)); - assert!(!(v1 < v2)); - assert!(!(v1 > v2)); - assert!(!(v2 < v1)); - assert!(!(v2 > v1)); - } -} diff --git a/book/crds_gossip.rs b/book/crds_gossip.rs deleted file mode 100644 index 57fb21bbe17c71..00000000000000 --- a/book/crds_gossip.rs +++ /dev/null @@ -1,486 +0,0 @@ -//! Crds Gossip -//! This module ties together Crds and the push and pull gossip overlays. The interface is -//! designed to run with a simulator or over a UDP network connection with messages up to a -//! packet::BLOB_DATA_SIZE size. - -use bloom::Bloom; -use crds::Crds; -use crds_gossip_error::CrdsGossipError; -use crds_gossip_pull::CrdsGossipPull; -use crds_gossip_push::{CrdsGossipPush, CRDS_GOSSIP_NUM_ACTIVE}; -use crds_value::CrdsValue; -use solana_sdk::hash::Hash; -use solana_sdk::pubkey::Pubkey; - -pub struct CrdsGossip { - pub crds: Crds, - pub id: Pubkey, - push: CrdsGossipPush, - pull: CrdsGossipPull, -} - -impl Default for CrdsGossip { - fn default() -> Self { - CrdsGossip { - crds: Crds::default(), - id: Pubkey::default(), - push: CrdsGossipPush::default(), - pull: CrdsGossipPull::default(), - } - } -} - -impl CrdsGossip { - pub fn set_self(&mut self, id: Pubkey) { - self.id = id; - } - /// process a push message to the network - pub fn process_push_message(&mut self, values: &[CrdsValue], now: u64) -> Vec { - let results: Vec<_> = values - .iter() - .map(|val| { - self.push - .process_push_message(&mut self.crds, val.clone(), now) - }).collect(); - results - .into_iter() - .zip(values) - .filter_map(|(r, d)| { - if r == Err(CrdsGossipError::PushMessagePrune) { - Some(d.label().pubkey()) - } else if let Ok(Some(val)) = r { - self.pull - .record_old_hash(val.value_hash, val.local_timestamp); - None - } else { - None - } - }).collect() - } - - pub fn new_push_messages(&mut self, now: u64) -> (Pubkey, Vec, Vec) { - let (peers, values) = self.push.new_push_messages(&self.crds, now); - (self.id, peers, values) - } - - /// add the `from` to the peer's filter of nodes - pub fn process_prune_msg(&mut self, peer: Pubkey, origin: &[Pubkey]) { - self.push.process_prune_msg(peer, origin) - } - - /// refresh the push active set - /// * ratio - number of actives to rotate - pub fn refresh_push_active_set(&mut self) { - self.push.refresh_push_active_set( - &self.crds, - self.id, - self.pull.pull_request_time.len(), - CRDS_GOSSIP_NUM_ACTIVE, - ) - } - - /// generate a random request - pub fn new_pull_request( - &self, - now: u64, - ) -> Result<(Pubkey, Bloom, CrdsValue), CrdsGossipError> { - self.pull.new_pull_request(&self.crds, self.id, now) - } - - /// time when a request to `from` was initiated - /// This is used for weighted random selection durring `new_pull_request` - /// It's important to use the local nodes request creation time as the weight - /// instaad of the response received time otherwise failed nodes will increase their weight. - pub fn mark_pull_request_creation_time(&mut self, from: Pubkey, now: u64) { - self.pull.mark_pull_request_creation_time(from, now) - } - /// process a pull request and create a response - pub fn process_pull_request( - &mut self, - caller: CrdsValue, - filter: Bloom, - now: u64, - ) -> Vec { - self.pull - .process_pull_request(&mut self.crds, caller, filter, now) - } - /// process a pull response - pub fn process_pull_response( - &mut self, - from: Pubkey, - response: Vec, - now: u64, - ) -> usize { - self.pull - .process_pull_response(&mut self.crds, from, response, now) - } - pub fn purge(&mut self, now: u64) { - if now > self.push.msg_timeout { - let min = now - self.push.msg_timeout; - self.push.purge_old_pending_push_messages(&self.crds, min); - } - if now > 5 * self.push.msg_timeout { - let min = now - 5 * self.push.msg_timeout; - self.push.purge_old_pushed_once_messages(min); - } - if now > self.pull.crds_timeout { - let min = now - self.pull.crds_timeout; - self.pull.purge_active(&mut self.crds, self.id, min); - } - if now > 5 * self.pull.crds_timeout { - let min = now - 5 * self.pull.crds_timeout; - self.pull.purge_purged(min); - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use bincode::serialized_size; - use contact_info::ContactInfo; - use crds_gossip_push::CRDS_GOSSIP_PUSH_MSG_TIMEOUT_MS; - use crds_value::CrdsValueLabel; - use rayon::prelude::*; - use signature::{Keypair, KeypairUtil}; - use std::collections::HashMap; - use std::sync::{Arc, Mutex}; - - type Node = Arc>; - type Network = HashMap; - fn star_network_create(num: usize) -> Network { - let entry = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - let mut network: HashMap<_, _> = (1..num) - .map(|_| { - let new = - CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - let id = new.label().pubkey(); - let mut node = CrdsGossip::default(); - node.crds.insert(new.clone(), 0).unwrap(); - node.crds.insert(entry.clone(), 0).unwrap(); - node.set_self(id); - (new.label().pubkey(), Arc::new(Mutex::new(node))) - }).collect(); - let mut node = CrdsGossip::default(); - let id = entry.label().pubkey(); - node.crds.insert(entry.clone(), 0).unwrap(); - node.set_self(id); - network.insert(id, Arc::new(Mutex::new(node))); - network - } - - fn rstar_network_create(num: usize) -> Network { - let entry = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - let mut origin = CrdsGossip::default(); - let id = entry.label().pubkey(); - origin.crds.insert(entry.clone(), 0).unwrap(); - origin.set_self(id); - let mut network: HashMap<_, _> = (1..num) - .map(|_| { - let new = - CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - let id = new.label().pubkey(); - let mut node = CrdsGossip::default(); - node.crds.insert(new.clone(), 0).unwrap(); - origin.crds.insert(new.clone(), 0).unwrap(); - node.set_self(id); - (new.label().pubkey(), Arc::new(Mutex::new(node))) - }).collect(); - network.insert(id, Arc::new(Mutex::new(origin))); - network - } - - fn ring_network_create(num: usize) -> Network { - let mut network: HashMap<_, _> = (0..num) - .map(|_| { - let new = - CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - let id = new.label().pubkey(); - let mut node = CrdsGossip::default(); - node.crds.insert(new.clone(), 0).unwrap(); - node.set_self(id); - (new.label().pubkey(), Arc::new(Mutex::new(node))) - }).collect(); - let keys: Vec = network.keys().cloned().collect(); - for k in 0..keys.len() { - let start_info = { - let start = &network[&keys[k]]; - let start_id = start.lock().unwrap().id.clone(); - start - .lock() - .unwrap() - .crds - .lookup(&CrdsValueLabel::ContactInfo(start_id)) - .unwrap() - .clone() - }; - let end = network.get_mut(&keys[(k + 1) % keys.len()]).unwrap(); - end.lock().unwrap().crds.insert(start_info, 0).unwrap(); - } - network - } - - fn network_simulator_pull_only(network: &mut Network) { - let num = network.len(); - let (converged, bytes_tx) = network_run_pull(network, 0, num * 2, 0.9); - trace!( - "network_simulator_pull_{}: converged: {} total_bytes: {}", - num, - converged, - bytes_tx - ); - assert!(converged >= 0.9); - } - - fn network_simulator(network: &mut Network) { - let num = network.len(); - // run for a small amount of time - let (converged, bytes_tx) = network_run_pull(network, 0, 10, 1.0); - trace!("network_simulator_push_{}: converged: {}", num, converged); - // make sure there is someone in the active set - let network_values: Vec = network.values().cloned().collect(); - network_values.par_iter().for_each(|node| { - node.lock().unwrap().refresh_push_active_set(); - }); - let mut total_bytes = bytes_tx; - for second in 1..num { - let start = second * 10; - let end = (second + 1) * 10; - let now = (start * 100) as u64; - // push a message to the network - network_values.par_iter().for_each(|locked_node| { - let node = &mut locked_node.lock().unwrap(); - let mut m = node - .crds - .lookup(&CrdsValueLabel::ContactInfo(node.id)) - .and_then(|v| v.contact_info().cloned()) - .unwrap(); - m.wallclock = now; - node.process_push_message(&[CrdsValue::ContactInfo(m.clone())], now); - }); - // push for a bit - let (queue_size, bytes_tx) = network_run_push(network, start, end); - total_bytes += bytes_tx; - trace!( - "network_simulator_push_{}: queue_size: {} bytes: {}", - num, - queue_size, - bytes_tx - ); - // pull for a bit - let (converged, bytes_tx) = network_run_pull(network, start, end, 1.0); - total_bytes += bytes_tx; - trace!( - "network_simulator_push_{}: converged: {} bytes: {} total_bytes: {}", - num, - converged, - bytes_tx, - total_bytes - ); - if converged > 0.9 { - break; - } - } - } - - fn network_run_push(network: &mut Network, start: usize, end: usize) -> (usize, usize) { - let mut bytes: usize = 0; - let mut num_msgs: usize = 0; - let mut total: usize = 0; - let num = network.len(); - let mut prunes: usize = 0; - let mut delivered: usize = 0; - let network_values: Vec = network.values().cloned().collect(); - for t in start..end { - let now = t as u64 * 100; - let requests: Vec<_> = network_values - .par_iter() - .map(|node| { - node.lock().unwrap().purge(now); - node.lock().unwrap().new_push_messages(now) - }).collect(); - let transfered: Vec<_> = requests - .par_iter() - .map(|(from, peers, msgs)| { - let mut bytes: usize = 0; - let mut delivered: usize = 0; - let mut num_msgs: usize = 0; - let mut prunes: usize = 0; - for to in peers { - bytes += serialized_size(msgs).unwrap() as usize; - num_msgs += 1; - let rsps = network - .get(&to) - .map(|node| node.lock().unwrap().process_push_message(&msgs, now)) - .unwrap(); - bytes += serialized_size(&rsps).unwrap() as usize; - prunes += rsps.len(); - network - .get(&from) - .map(|node| node.lock().unwrap().process_prune_msg(*to, &rsps)) - .unwrap(); - delivered += rsps.is_empty() as usize; - } - (bytes, delivered, num_msgs, prunes) - }).collect(); - for (b, d, m, p) in transfered { - bytes += b; - delivered += d; - num_msgs += m; - prunes += p; - } - if now % CRDS_GOSSIP_PUSH_MSG_TIMEOUT_MS == 0 && now > 0 { - network_values.par_iter().for_each(|node| { - node.lock().unwrap().refresh_push_active_set(); - }); - } - total = network_values - .par_iter() - .map(|v| v.lock().unwrap().push.num_pending()) - .sum(); - trace!( - "network_run_push_{}: now: {} queue: {} bytes: {} num_msgs: {} prunes: {} delivered: {}", - num, - now, - total, - bytes, - num_msgs, - prunes, - delivered, - ); - } - (total, bytes) - } - - fn network_run_pull( - network: &mut Network, - start: usize, - end: usize, - max_convergance: f64, - ) -> (f64, usize) { - let mut bytes: usize = 0; - let mut msgs: usize = 0; - let mut overhead: usize = 0; - let mut convergance = 0f64; - let num = network.len(); - let network_values: Vec = network.values().cloned().collect(); - for t in start..end { - let now = t as u64 * 100; - let mut requests: Vec<_> = { - network_values - .par_iter() - .filter_map(|from| from.lock().unwrap().new_pull_request(now).ok()) - .collect() - }; - let transfered: Vec<_> = requests - .into_par_iter() - .map(|(to, request, caller_info)| { - let mut bytes: usize = 0; - let mut msgs: usize = 0; - let mut overhead: usize = 0; - let from = caller_info.label().pubkey(); - bytes += request.keys.len(); - bytes += (request.bits.len() / 8) as usize; - bytes += serialized_size(&caller_info).unwrap() as usize; - let rsp = network - .get(&to) - .map(|node| { - node.lock() - .unwrap() - .process_pull_request(caller_info, request, now) - }).unwrap(); - bytes += serialized_size(&rsp).unwrap() as usize; - msgs += rsp.len(); - network.get(&from).map(|node| { - node.lock() - .unwrap() - .mark_pull_request_creation_time(from, now); - overhead += node.lock().unwrap().process_pull_response(from, rsp, now); - }); - (bytes, msgs, overhead) - }).collect(); - for (b, m, o) in transfered { - bytes += b; - msgs += m; - overhead += o; - } - let total: usize = network_values - .par_iter() - .map(|v| v.lock().unwrap().crds.table.len()) - .sum(); - convergance = total as f64 / ((num * num) as f64); - if convergance > max_convergance { - break; - } - trace!( - "network_run_pull_{}: now: {} connections: {} convergance: {} bytes: {} msgs: {} overhead: {}", - num, - now, - total, - convergance, - bytes, - msgs, - overhead - ); - } - (convergance, bytes) - } - - #[test] - fn test_star_network_pull_50() { - let mut network = star_network_create(50); - network_simulator_pull_only(&mut network); - } - #[test] - fn test_star_network_pull_100() { - let mut network = star_network_create(100); - network_simulator_pull_only(&mut network); - } - #[test] - fn test_star_network_push_star_200() { - let mut network = star_network_create(200); - network_simulator(&mut network); - } - #[test] - fn test_star_network_push_rstar_200() { - let mut network = rstar_network_create(200); - network_simulator(&mut network); - } - #[test] - fn test_star_network_push_ring_200() { - let mut network = ring_network_create(200); - network_simulator(&mut network); - } - #[test] - #[ignore] - fn test_star_network_large_pull() { - use logger; - logger::setup(); - let mut network = star_network_create(2000); - network_simulator_pull_only(&mut network); - } - #[test] - #[ignore] - fn test_rstar_network_large_push() { - use logger; - logger::setup(); - let mut network = rstar_network_create(4000); - network_simulator(&mut network); - } - #[test] - #[ignore] - fn test_ring_network_large_push() { - use logger; - logger::setup(); - let mut network = ring_network_create(4001); - network_simulator(&mut network); - } - #[test] - #[ignore] - fn test_star_network_large_push() { - use logger; - logger::setup(); - let mut network = star_network_create(4002); - network_simulator(&mut network); - } -} diff --git a/book/crds_gossip_error.rs b/book/crds_gossip_error.rs deleted file mode 100644 index d9d00ce77c043f..00000000000000 --- a/book/crds_gossip_error.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[derive(PartialEq, Debug)] -pub enum CrdsGossipError { - NoPeers, - PushMessageTimeout, - PushMessagePrune, - PushMessageOldVersion, -} diff --git a/book/crds_gossip_pull.rs b/book/crds_gossip_pull.rs deleted file mode 100644 index 2b77fbfa1bb1f9..00000000000000 --- a/book/crds_gossip_pull.rs +++ /dev/null @@ -1,378 +0,0 @@ -//! Crds Gossip Pull overlay -//! This module implements the anti-entropy protocol for the network. -//! -//! The basic strategy is as follows: -//! 1. Construct a bloom filter of the local data set -//! 2. Randomly ask a node on the network for data that is not contained in the bloom filter. -//! -//! Bloom filters have a false positive rate. Each requests uses a different bloom filter -//! with random hash functions. So each subsequent request will have a different distribution -//! of false positives. - -use bincode::serialized_size; -use bloom::Bloom; -use crds::Crds; -use crds_gossip_error::CrdsGossipError; -use crds_value::{CrdsValue, CrdsValueLabel}; -use packet::BLOB_DATA_SIZE; -use rand; -use rand::distributions::{Distribution, Weighted, WeightedChoice}; -use solana_sdk::hash::Hash; -use solana_sdk::pubkey::Pubkey; -use std::cmp; -use std::collections::HashMap; -use std::collections::VecDeque; - -pub const CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS: u64 = 15000; - -pub struct CrdsGossipPull { - /// timestamp of last request - pub pull_request_time: HashMap, - /// hash and insert time - purged_values: VecDeque<(Hash, u64)>, - /// max bytes per message - pub max_bytes: usize, - pub crds_timeout: u64, -} - -impl Default for CrdsGossipPull { - fn default() -> Self { - Self { - purged_values: VecDeque::new(), - pull_request_time: HashMap::new(), - max_bytes: BLOB_DATA_SIZE, - crds_timeout: CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS, - } - } -} -impl CrdsGossipPull { - /// generate a random request - pub fn new_pull_request( - &self, - crds: &Crds, - self_id: Pubkey, - now: u64, - ) -> Result<(Pubkey, Bloom, CrdsValue), CrdsGossipError> { - let mut options: Vec<_> = crds - .table - .values() - .filter_map(|v| v.value.contact_info()) - .filter(|v| { - v.id != self_id && !v.ncp.ip().is_unspecified() && !v.ncp.ip().is_multicast() - }).map(|item| { - let req_time: u64 = *self.pull_request_time.get(&item.id).unwrap_or(&0); - let weight = cmp::max( - 1, - cmp::min(u64::from(u16::max_value()) - 1, (now - req_time) / 1024) as u32, - ); - Weighted { weight, item } - }).collect(); - if options.is_empty() { - return Err(CrdsGossipError::NoPeers); - } - let filter = self.build_crds_filter(crds); - let random = WeightedChoice::new(&mut options).sample(&mut rand::thread_rng()); - let self_info = crds - .lookup(&CrdsValueLabel::ContactInfo(self_id)) - .unwrap_or_else(|| panic!("self_id invalid {}", self_id)); - Ok((random.id, filter, self_info.clone())) - } - - /// time when a request to `from` was initiated - /// This is used for weighted random selection during `new_pull_request` - /// It's important to use the local nodes request creation time as the weight - /// instead of the response received time otherwise failed nodes will increase their weight. - pub fn mark_pull_request_creation_time(&mut self, from: Pubkey, now: u64) { - self.pull_request_time.insert(from, now); - } - - /// Store an old hash in the purged values set - pub fn record_old_hash(&mut self, hash: Hash, timestamp: u64) { - self.purged_values.push_back((hash, timestamp)) - } - - /// process a pull request and create a response - pub fn process_pull_request( - &mut self, - crds: &mut Crds, - caller: CrdsValue, - mut filter: Bloom, - now: u64, - ) -> Vec { - let rv = self.filter_crds_values(crds, &mut filter); - let key = caller.label().pubkey(); - let old = crds.insert(caller, now); - if let Some(val) = old.ok().and_then(|opt| opt) { - self.purged_values - .push_back((val.value_hash, val.local_timestamp)) - } - crds.update_record_timestamp(key, now); - rv - } - /// process a pull response - pub fn process_pull_response( - &mut self, - crds: &mut Crds, - from: Pubkey, - response: Vec, - now: u64, - ) -> usize { - let mut failed = 0; - for r in response { - let owner = r.label().pubkey(); - let old = crds.insert(r, now); - failed += old.is_err() as usize; - old.ok().map(|opt| { - crds.update_record_timestamp(owner, now); - opt.map(|val| { - self.purged_values - .push_back((val.value_hash, val.local_timestamp)) - }) - }); - } - crds.update_record_timestamp(from, now); - failed - } - /// build a filter of the current crds table - fn build_crds_filter(&self, crds: &Crds) -> Bloom { - let num = crds.table.values().count() + self.purged_values.len(); - let mut bloom = Bloom::random(num, 0.1, 4 * 1024 * 8 - 1); - for v in crds.table.values() { - bloom.add(&v.value_hash); - } - for (value_hash, _insert_timestamp) in &self.purged_values { - bloom.add(value_hash); - } - bloom - } - /// filter values that fail the bloom filter up to max_bytes - fn filter_crds_values(&self, crds: &Crds, filter: &mut Bloom) -> Vec { - let mut max_bytes = self.max_bytes as isize; - let mut ret = vec![]; - for v in crds.table.values() { - if filter.contains(&v.value_hash) { - continue; - } - max_bytes -= serialized_size(&v.value).unwrap() as isize; - if max_bytes < 0 { - break; - } - ret.push(v.value.clone()); - } - ret - } - /// Purge values from the crds that are older then `active_timeout` - /// The value_hash of an active item is put into self.purged_values queue - pub fn purge_active(&mut self, crds: &mut Crds, self_id: Pubkey, min_ts: u64) { - let old = crds.find_old_labels(min_ts); - let mut purged: VecDeque<_> = old - .iter() - .filter(|label| label.pubkey() != self_id) - .filter_map(|label| { - let rv = crds - .lookup_versioned(label) - .map(|val| (val.value_hash, val.local_timestamp)); - crds.remove(label); - rv - }).collect(); - self.purged_values.append(&mut purged); - } - /// Purge values from the `self.purged_values` queue that are older then purge_timeout - pub fn purge_purged(&mut self, min_ts: u64) { - let cnt = self - .purged_values - .iter() - .take_while(|v| v.1 < min_ts) - .count(); - self.purged_values.drain(..cnt); - } -} -#[cfg(test)] -mod test { - use super::*; - use contact_info::ContactInfo; - use crds_value::LeaderId; - use signature::{Keypair, KeypairUtil}; - - #[test] - fn test_new_pull_request() { - let mut crds = Crds::default(); - let entry = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - let id = entry.label().pubkey(); - let node = CrdsGossipPull::default(); - assert_eq!( - node.new_pull_request(&crds, id, 0), - Err(CrdsGossipError::NoPeers) - ); - - crds.insert(entry.clone(), 0).unwrap(); - assert_eq!( - node.new_pull_request(&crds, id, 0), - Err(CrdsGossipError::NoPeers) - ); - - let new = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - crds.insert(new.clone(), 0).unwrap(); - let req = node.new_pull_request(&crds, id, 0); - let (to, _, self_info) = req.unwrap(); - assert_eq!(to, new.label().pubkey()); - assert_eq!(self_info, entry); - } - - #[test] - fn test_new_mark_creation_time() { - let mut crds = Crds::default(); - let entry = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - let node_id = entry.label().pubkey(); - let mut node = CrdsGossipPull::default(); - crds.insert(entry.clone(), 0).unwrap(); - let old = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - crds.insert(old.clone(), 0).unwrap(); - let new = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - crds.insert(new.clone(), 0).unwrap(); - - // set request creation time to max_value - node.mark_pull_request_creation_time(new.label().pubkey(), u64::max_value()); - - // odds of getting the other request should be 1 in u64::max_value() - for _ in 0..10 { - let req = node.new_pull_request(&crds, node_id, u64::max_value()); - let (to, _, self_info) = req.unwrap(); - assert_eq!(to, old.label().pubkey()); - assert_eq!(self_info, entry); - } - } - - #[test] - fn test_process_pull_request() { - let mut node_crds = Crds::default(); - let entry = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - let node_id = entry.label().pubkey(); - let node = CrdsGossipPull::default(); - node_crds.insert(entry.clone(), 0).unwrap(); - let new = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - node_crds.insert(new.clone(), 0).unwrap(); - let req = node.new_pull_request(&node_crds, node_id, 0); - - let mut dest_crds = Crds::default(); - let mut dest = CrdsGossipPull::default(); - let (_, filter, caller) = req.unwrap(); - let rsp = dest.process_pull_request(&mut dest_crds, caller.clone(), filter, 1); - assert!(rsp.is_empty()); - assert!(dest_crds.lookup(&caller.label()).is_some()); - assert_eq!( - dest_crds - .lookup_versioned(&caller.label()) - .unwrap() - .insert_timestamp, - 1 - ); - assert_eq!( - dest_crds - .lookup_versioned(&caller.label()) - .unwrap() - .local_timestamp, - 1 - ); - } - #[test] - fn test_process_pull_request_response() { - let mut node_crds = Crds::default(); - let entry = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - let node_id = entry.label().pubkey(); - let mut node = CrdsGossipPull::default(); - node_crds.insert(entry.clone(), 0).unwrap(); - let new = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - node_crds.insert(new.clone(), 0).unwrap(); - - let mut dest = CrdsGossipPull::default(); - let mut dest_crds = Crds::default(); - let new = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - dest_crds.insert(new.clone(), 0).unwrap(); - - // node contains a key from the dest node, but at an older local timestamp - let dest_id = new.label().pubkey(); - let same_key = CrdsValue::LeaderId(LeaderId { - id: dest_id, - leader_id: dest_id, - wallclock: 1, - }); - node_crds.insert(same_key.clone(), 0).unwrap(); - assert_eq!( - node_crds - .lookup_versioned(&same_key.label()) - .unwrap() - .local_timestamp, - 0 - ); - let mut done = false; - for _ in 0..30 { - // there is a chance of a false positive with bloom filters - let req = node.new_pull_request(&node_crds, node_id, 0); - let (_, filter, caller) = req.unwrap(); - let rsp = dest.process_pull_request(&mut dest_crds, caller, filter, 0); - // if there is a false positive this is empty - // prob should be around 0.1 per iteration - if rsp.is_empty() { - continue; - } - - assert_eq!(rsp.len(), 1); - let failed = node.process_pull_response(&mut node_crds, node_id, rsp, 1); - assert_eq!(failed, 0); - assert_eq!( - node_crds - .lookup_versioned(&new.label()) - .unwrap() - .local_timestamp, - 1 - ); - // verify that the whole record was updated for dest since this is a response from dest - assert_eq!( - node_crds - .lookup_versioned(&same_key.label()) - .unwrap() - .local_timestamp, - 1 - ); - done = true; - break; - } - assert!(done); - } - #[test] - fn test_gossip_purge() { - let mut node_crds = Crds::default(); - let entry = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - let node_label = entry.label(); - let node_id = node_label.pubkey(); - let mut node = CrdsGossipPull::default(); - node_crds.insert(entry.clone(), 0).unwrap(); - let old = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - node_crds.insert(old.clone(), 0).unwrap(); - let value_hash = node_crds.lookup_versioned(&old.label()).unwrap().value_hash; - - //verify self is valid - assert_eq!(node_crds.lookup(&node_label).unwrap().label(), node_label); - - // purge - node.purge_active(&mut node_crds, node_id, 1); - - //verify self is still valid after purge - assert_eq!(node_crds.lookup(&node_label).unwrap().label(), node_label); - - assert_eq!(node_crds.lookup_versioned(&old.label()), None); - assert_eq!(node.purged_values.len(), 1); - for _ in 0..30 { - // there is a chance of a false positive with bloom filters - // assert that purged value is still in the set - // chance of 30 consecutive false positives is 0.1^30 - let mut filter = node.build_crds_filter(&node_crds); - assert!(filter.contains(&value_hash)); - } - - // purge the value - node.purge_purged(1); - assert_eq!(node.purged_values.len(), 0); - } -} diff --git a/book/crds_gossip_push.rs b/book/crds_gossip_push.rs deleted file mode 100644 index 00f50ba630c3b6..00000000000000 --- a/book/crds_gossip_push.rs +++ /dev/null @@ -1,459 +0,0 @@ -//! Crds Gossip Push overlay -//! This module is used to propagate recently created CrdsValues across the network -//! Eager push strategy is based on Plumtree -//! http://asc.di.fct.unl.pt/~jleitao/pdf/srds07-leitao.pdf -//! -//! Main differences are: -//! 1. There is no `max hop`. Messages are signed with a local wallclock. If they are outside of -//! the local nodes wallclock window they are drooped silently. -//! 2. The prune set is stored in a Bloom filter. - -use bincode::serialized_size; -use bloom::Bloom; -use contact_info::ContactInfo; -use crds::{Crds, VersionedCrdsValue}; -use crds_gossip_error::CrdsGossipError; -use crds_value::{CrdsValue, CrdsValueLabel}; -use indexmap::map::IndexMap; -use packet::BLOB_DATA_SIZE; -use rand::{self, Rng}; -use solana_sdk::hash::Hash; -use solana_sdk::pubkey::Pubkey; -use std::cmp; -use std::collections::HashMap; - -pub const CRDS_GOSSIP_NUM_ACTIVE: usize = 30; -pub const CRDS_GOSSIP_PUSH_FANOUT: usize = 6; -pub const CRDS_GOSSIP_PUSH_MSG_TIMEOUT_MS: u64 = 5000; - -pub struct CrdsGossipPush { - /// max bytes per message - pub max_bytes: usize, - /// active set of validators for push - active_set: IndexMap>, - /// push message queue - push_messages: HashMap, - pushed_once: HashMap, - pub num_active: usize, - pub push_fanout: usize, - pub msg_timeout: u64, -} - -impl Default for CrdsGossipPush { - fn default() -> Self { - Self { - max_bytes: BLOB_DATA_SIZE, - active_set: IndexMap::new(), - push_messages: HashMap::new(), - pushed_once: HashMap::new(), - num_active: CRDS_GOSSIP_NUM_ACTIVE, - push_fanout: CRDS_GOSSIP_PUSH_FANOUT, - msg_timeout: CRDS_GOSSIP_PUSH_MSG_TIMEOUT_MS, - } - } -} -impl CrdsGossipPush { - pub fn num_pending(&self) -> usize { - self.push_messages.len() - } - /// process a push message to the network - pub fn process_push_message( - &mut self, - crds: &mut Crds, - value: CrdsValue, - now: u64, - ) -> Result, CrdsGossipError> { - if now > value.wallclock() + self.msg_timeout { - return Err(CrdsGossipError::PushMessageTimeout); - } - if now + self.msg_timeout < value.wallclock() { - return Err(CrdsGossipError::PushMessageTimeout); - } - let label = value.label(); - - let new_value = crds.new_versioned(now, value); - let value_hash = new_value.value_hash; - if self.pushed_once.get(&value_hash).is_some() { - return Err(CrdsGossipError::PushMessagePrune); - } - let old = crds.insert_versioned(new_value); - if old.is_err() { - return Err(CrdsGossipError::PushMessageOldVersion); - } - self.push_messages.insert(label, value_hash); - self.pushed_once.insert(value_hash, now); - Ok(old.ok().and_then(|opt| opt)) - } - - /// New push message to broadcast to peers. - /// Returns a list of Pubkeys for the selected peers and a list of values to send to all the - /// peers. - /// The list of push messages is created such that all the randomly selected peers have not - /// pruned the source addresses. - pub fn new_push_messages(&mut self, crds: &Crds, now: u64) -> (Vec, Vec) { - let max = self.active_set.len(); - let mut nodes: Vec<_> = (0..max).collect(); - rand::thread_rng().shuffle(&mut nodes); - let peers: Vec = nodes - .into_iter() - .filter_map(|n| self.active_set.get_index(n)) - .take(self.push_fanout) - .map(|n| *n.0) - .collect(); - let mut total_bytes: usize = 0; - let mut values = vec![]; - for (label, hash) in &self.push_messages { - let mut failed = false; - for p in &peers { - let filter = self.active_set.get_mut(p); - failed |= filter.is_none() || filter.unwrap().contains(&label.pubkey()); - } - if failed { - continue; - } - let res = crds.lookup_versioned(label); - if res.is_none() { - continue; - } - let version = res.unwrap(); - if version.value_hash != *hash { - continue; - } - let value = &version.value; - if value.wallclock() > now || value.wallclock() + self.msg_timeout < now { - continue; - } - total_bytes += serialized_size(value).unwrap() as usize; - if total_bytes > self.max_bytes { - break; - } - values.push(value.clone()); - } - for v in &values { - self.push_messages.remove(&v.label()); - } - (peers, values) - } - - /// add the `from` to the peer's filter of nodes - pub fn process_prune_msg(&mut self, peer: Pubkey, origins: &[Pubkey]) { - for origin in origins { - if let Some(p) = self.active_set.get_mut(&peer) { - p.add(origin) - } - } - } - - fn compute_need(num_active: usize, active_set_len: usize, ratio: usize) -> usize { - let num = active_set_len / ratio; - cmp::min(num_active, (num_active - active_set_len) + num) - } - - /// refresh the push active set - /// * ratio - active_set.len()/ratio is the number of actives to rotate - pub fn refresh_push_active_set( - &mut self, - crds: &Crds, - self_id: Pubkey, - network_size: usize, - ratio: usize, - ) { - let need = Self::compute_need(self.num_active, self.active_set.len(), ratio); - let mut new_items = HashMap::new(); - let mut ixs: Vec<_> = (0..crds.table.len()).collect(); - rand::thread_rng().shuffle(&mut ixs); - - for ix in ixs { - let item = crds.table.get_index(ix); - if item.is_none() { - continue; - } - let val = item.unwrap(); - if val.0.pubkey() == self_id { - continue; - } - if self.active_set.get(&val.0.pubkey()).is_some() { - continue; - } - if new_items.get(&val.0.pubkey()).is_some() { - continue; - } - if let Some(contact) = val.1.value.contact_info() { - if !ContactInfo::is_valid_address(&contact.ncp) { - continue; - } - } - let bloom = Bloom::random(network_size, 0.1, 1024 * 8 * 4); - new_items.insert(val.0.pubkey(), bloom); - if new_items.len() == need { - break; - } - } - let mut keys: Vec = self.active_set.keys().cloned().collect(); - rand::thread_rng().shuffle(&mut keys); - let num = keys.len() / ratio; - for k in &keys[..num] { - self.active_set.remove(k); - } - for (k, v) in new_items { - self.active_set.insert(k, v); - } - } - - /// purge old pending push messages - pub fn purge_old_pending_push_messages(&mut self, crds: &Crds, min_time: u64) { - let old_msgs: Vec = self - .push_messages - .iter() - .filter_map(|(k, hash)| { - if let Some(versioned) = crds.lookup_versioned(k) { - if versioned.value.wallclock() < min_time || versioned.value_hash != *hash { - Some(k) - } else { - None - } - } else { - Some(k) - } - }).cloned() - .collect(); - for k in old_msgs { - self.push_messages.remove(&k); - } - } - /// purge old pushed_once messages - pub fn purge_old_pushed_once_messages(&mut self, min_time: u64) { - let old_msgs: Vec = self - .pushed_once - .iter() - .filter_map(|(k, v)| if *v < min_time { Some(k) } else { None }) - .cloned() - .collect(); - for k in old_msgs { - self.pushed_once.remove(&k); - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use contact_info::ContactInfo; - use signature::{Keypair, KeypairUtil}; - #[test] - fn test_process_push() { - let mut crds = Crds::default(); - let mut push = CrdsGossipPush::default(); - let value = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - let label = value.label(); - // push a new message - assert_eq!( - push.process_push_message(&mut crds, value.clone(), 0), - Ok(None) - ); - assert_eq!(crds.lookup(&label), Some(&value)); - - // push it again - assert_eq!( - push.process_push_message(&mut crds, value.clone(), 0), - Err(CrdsGossipError::PushMessagePrune) - ); - } - #[test] - fn test_process_push_old_version() { - let mut crds = Crds::default(); - let mut push = CrdsGossipPush::default(); - let mut ci = ContactInfo::new_localhost(Keypair::new().pubkey(), 0); - ci.wallclock = 1; - let value = CrdsValue::ContactInfo(ci.clone()); - - // push a new message - assert_eq!(push.process_push_message(&mut crds, value, 0), Ok(None)); - - // push an old version - ci.wallclock = 0; - let value = CrdsValue::ContactInfo(ci.clone()); - assert_eq!( - push.process_push_message(&mut crds, value, 0), - Err(CrdsGossipError::PushMessageOldVersion) - ); - } - #[test] - fn test_process_push_timeout() { - let mut crds = Crds::default(); - let mut push = CrdsGossipPush::default(); - let timeout = push.msg_timeout; - let mut ci = ContactInfo::new_localhost(Keypair::new().pubkey(), 0); - - // push a version to far in the future - ci.wallclock = timeout + 1; - let value = CrdsValue::ContactInfo(ci.clone()); - assert_eq!( - push.process_push_message(&mut crds, value, 0), - Err(CrdsGossipError::PushMessageTimeout) - ); - - // push a version to far in the past - ci.wallclock = 0; - let value = CrdsValue::ContactInfo(ci.clone()); - assert_eq!( - push.process_push_message(&mut crds, value, timeout + 1), - Err(CrdsGossipError::PushMessageTimeout) - ); - } - #[test] - fn test_process_push_update() { - let mut crds = Crds::default(); - let mut push = CrdsGossipPush::default(); - let mut ci = ContactInfo::new_localhost(Keypair::new().pubkey(), 0); - ci.wallclock = 0; - let value_old = CrdsValue::ContactInfo(ci.clone()); - - // push a new message - assert_eq!( - push.process_push_message(&mut crds, value_old.clone(), 0), - Ok(None) - ); - - // push an old version - ci.wallclock = 1; - let value = CrdsValue::ContactInfo(ci.clone()); - assert_eq!( - push.process_push_message(&mut crds, value, 0) - .unwrap() - .unwrap() - .value, - value_old - ); - } - #[test] - fn test_compute_need() { - assert_eq!(CrdsGossipPush::compute_need(30, 0, 10), 30); - assert_eq!(CrdsGossipPush::compute_need(30, 1, 10), 29); - assert_eq!(CrdsGossipPush::compute_need(30, 30, 10), 3); - assert_eq!(CrdsGossipPush::compute_need(30, 29, 10), 3); - } - #[test] - fn test_refresh_active_set() { - use logger; - logger::setup(); - let mut crds = Crds::default(); - let mut push = CrdsGossipPush::default(); - let value1 = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - - assert_eq!(crds.insert(value1.clone(), 0), Ok(None)); - push.refresh_push_active_set(&crds, Pubkey::default(), 1, 1); - - assert!(push.active_set.get(&value1.label().pubkey()).is_some()); - let value2 = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - assert!(push.active_set.get(&value2.label().pubkey()).is_none()); - assert_eq!(crds.insert(value2.clone(), 0), Ok(None)); - for _ in 0..30 { - push.refresh_push_active_set(&crds, Pubkey::default(), 1, 1); - if push.active_set.get(&value2.label().pubkey()).is_some() { - break; - } - } - assert!(push.active_set.get(&value2.label().pubkey()).is_some()); - - for _ in 0..push.num_active { - let value2 = - CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - assert_eq!(crds.insert(value2.clone(), 0), Ok(None)); - } - push.refresh_push_active_set(&crds, Pubkey::default(), 1, 1); - assert_eq!(push.active_set.len(), push.num_active); - } - #[test] - fn test_new_push_messages() { - let mut crds = Crds::default(); - let mut push = CrdsGossipPush::default(); - let peer = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - assert_eq!(crds.insert(peer.clone(), 0), Ok(None)); - push.refresh_push_active_set(&crds, Pubkey::default(), 1, 1); - - let new_msg = - CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - assert_eq!( - push.process_push_message(&mut crds, new_msg.clone(), 0), - Ok(None) - ); - assert_eq!(push.active_set.len(), 1); - assert_eq!( - push.new_push_messages(&crds, 0), - (vec![peer.label().pubkey()], vec![new_msg]) - ); - } - #[test] - fn test_process_prune() { - let mut crds = Crds::default(); - let mut push = CrdsGossipPush::default(); - let peer = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - assert_eq!(crds.insert(peer.clone(), 0), Ok(None)); - push.refresh_push_active_set(&crds, Pubkey::default(), 1, 1); - - let new_msg = - CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - assert_eq!( - push.process_push_message(&mut crds, new_msg.clone(), 0), - Ok(None) - ); - push.process_prune_msg(peer.label().pubkey(), &[new_msg.label().pubkey()]); - assert_eq!( - push.new_push_messages(&crds, 0), - (vec![peer.label().pubkey()], vec![]) - ); - } - #[test] - fn test_purge_old_pending_push_messages() { - let mut crds = Crds::default(); - let mut push = CrdsGossipPush::default(); - let peer = CrdsValue::ContactInfo(ContactInfo::new_localhost(Keypair::new().pubkey(), 0)); - assert_eq!(crds.insert(peer.clone(), 0), Ok(None)); - push.refresh_push_active_set(&crds, Pubkey::default(), 1, 1); - - let mut ci = ContactInfo::new_localhost(Keypair::new().pubkey(), 0); - ci.wallclock = 1; - let new_msg = CrdsValue::ContactInfo(ci.clone()); - assert_eq!( - push.process_push_message(&mut crds, new_msg.clone(), 1), - Ok(None) - ); - push.purge_old_pending_push_messages(&crds, 0); - assert_eq!( - push.new_push_messages(&crds, 0), - (vec![peer.label().pubkey()], vec![]) - ); - } - - #[test] - fn test_purge_old_pushed_once_messages() { - let mut crds = Crds::default(); - let mut push = CrdsGossipPush::default(); - let mut ci = ContactInfo::new_localhost(Keypair::new().pubkey(), 0); - ci.wallclock = 0; - let value = CrdsValue::ContactInfo(ci.clone()); - let label = value.label(); - // push a new message - assert_eq!( - push.process_push_message(&mut crds, value.clone(), 0), - Ok(None) - ); - assert_eq!(crds.lookup(&label), Some(&value)); - - // push it again - assert_eq!( - push.process_push_message(&mut crds, value.clone(), 0), - Err(CrdsGossipError::PushMessagePrune) - ); - - // purge the old pushed - push.purge_old_pushed_once_messages(1); - - // push it again - assert_eq!( - push.process_push_message(&mut crds, value.clone(), 0), - Err(CrdsGossipError::PushMessageOldVersion) - ); - } -} diff --git a/book/crds_traits_impls.rs b/book/crds_traits_impls.rs deleted file mode 100644 index 382a8d50f6b004..00000000000000 --- a/book/crds_traits_impls.rs +++ /dev/null @@ -1,26 +0,0 @@ -use bloom::BloomHashIndex; -use solana_sdk::hash::Hash; -use solana_sdk::pubkey::Pubkey; - -fn slice_hash(slice: &[u8], hash_index: u64) -> u64 { - let len = slice.len(); - assert!(len < 256); - let mut rv = 0u64; - for i in 0..8 { - let pos = (hash_index >> i) & 0xff; - rv |= u64::from(slice[pos as usize % len]) << i; - } - rv -} - -impl BloomHashIndex for Pubkey { - fn hash(&self, hash_index: u64) -> u64 { - slice_hash(self.as_ref(), hash_index) - } -} - -impl BloomHashIndex for Hash { - fn hash(&self, hash_index: u64) -> u64 { - slice_hash(self.as_ref(), hash_index) - } -} diff --git a/book/crds_value.rs b/book/crds_value.rs deleted file mode 100644 index 512d490a4105dd..00000000000000 --- a/book/crds_value.rs +++ /dev/null @@ -1,147 +0,0 @@ -use contact_info::ContactInfo; -use solana_sdk::pubkey::Pubkey; -use std::fmt; -use transaction::Transaction; - -/// CrdsValue that is replicated across the cluster -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub enum CrdsValue { - /// * Merge Strategy - Latest wallclock is picked - ContactInfo(ContactInfo), - /// TODO, Votes need a height potentially in the userdata - /// * Merge Strategy - Latest height is picked - Vote(Vote), - /// * Merge Strategy - Latest wallclock is picked - LeaderId(LeaderId), -} - -#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)] -pub struct LeaderId { - pub id: Pubkey, - pub leader_id: Pubkey, - pub wallclock: u64, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct Vote { - pub transaction: Transaction, - pub height: u64, - pub wallclock: u64, -} - -/// Type of the replicated value -/// These are labels for values in a record that is assosciated with `Pubkey` -#[derive(PartialEq, Hash, Eq, Clone, Debug)] -pub enum CrdsValueLabel { - ContactInfo(Pubkey), - Vote(Pubkey), - LeaderId(Pubkey), -} - -impl fmt::Display for CrdsValueLabel { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - CrdsValueLabel::ContactInfo(_) => write!(f, "ContactInfo({})", self.pubkey()), - CrdsValueLabel::Vote(_) => write!(f, "Vote({})", self.pubkey()), - CrdsValueLabel::LeaderId(_) => write!(f, "LeaderId({})", self.pubkey()), - } - } -} - -impl CrdsValueLabel { - pub fn pubkey(&self) -> Pubkey { - match self { - CrdsValueLabel::ContactInfo(p) => *p, - CrdsValueLabel::Vote(p) => *p, - CrdsValueLabel::LeaderId(p) => *p, - } - } -} - -impl CrdsValue { - /// Totally unsecure unverfiable wallclock of the node that generatd this message - /// Latest wallclock is always picked. - /// This is used to time out push messages. - pub fn wallclock(&self) -> u64 { - match self { - CrdsValue::ContactInfo(contact_info) => contact_info.wallclock, - CrdsValue::Vote(vote) => vote.wallclock, - CrdsValue::LeaderId(leader_id) => leader_id.wallclock, - } - } - pub fn label(&self) -> CrdsValueLabel { - match self { - CrdsValue::ContactInfo(contact_info) => CrdsValueLabel::ContactInfo(contact_info.id), - CrdsValue::Vote(vote) => CrdsValueLabel::Vote(vote.transaction.account_keys[0]), - CrdsValue::LeaderId(leader_id) => CrdsValueLabel::LeaderId(leader_id.id), - } - } - pub fn contact_info(&self) -> Option<&ContactInfo> { - match self { - CrdsValue::ContactInfo(contact_info) => Some(contact_info), - _ => None, - } - } - pub fn leader_id(&self) -> Option<&LeaderId> { - match self { - CrdsValue::LeaderId(leader_id) => Some(leader_id), - _ => None, - } - } - pub fn vote(&self) -> Option<&Vote> { - match self { - CrdsValue::Vote(vote) => Some(vote), - _ => None, - } - } - /// Return all the possible labels for a record identified by Pubkey. - pub fn record_labels(key: Pubkey) -> [CrdsValueLabel; 3] { - [ - CrdsValueLabel::ContactInfo(key), - CrdsValueLabel::Vote(key), - CrdsValueLabel::LeaderId(key), - ] - } -} -#[cfg(test)] -mod test { - use super::*; - use contact_info::ContactInfo; - use system_transaction::test_tx; - - #[test] - fn test_labels() { - let mut hits = [false; 3]; - // this method should cover all the possible labels - for v in &CrdsValue::record_labels(Pubkey::default()) { - match v { - CrdsValueLabel::ContactInfo(_) => hits[0] = true, - CrdsValueLabel::Vote(_) => hits[1] = true, - CrdsValueLabel::LeaderId(_) => hits[2] = true, - } - } - assert!(hits.iter().all(|x| *x)); - } - #[test] - fn test_keys_and_values() { - let v = CrdsValue::LeaderId(LeaderId::default()); - let key = v.clone().leader_id().unwrap().id; - assert_eq!(v.wallclock(), 0); - assert_eq!(v.label(), CrdsValueLabel::LeaderId(key)); - - let v = CrdsValue::ContactInfo(ContactInfo::default()); - assert_eq!(v.wallclock(), 0); - let key = v.clone().contact_info().unwrap().id; - assert_eq!(v.label(), CrdsValueLabel::ContactInfo(key)); - - let v = CrdsValue::Vote(Vote { - transaction: test_tx(), - height: 1, - wallclock: 0, - }); - assert_eq!(v.wallclock(), 0); - let key = v.clone().vote().unwrap().transaction.account_keys[0]; - assert_eq!(v.label(), CrdsValueLabel::Vote(key)); - } - -} diff --git a/book/css/chrome.css b/book/css/chrome.css deleted file mode 100644 index 82883e6b9a73a2..00000000000000 --- a/book/css/chrome.css +++ /dev/null @@ -1,417 +0,0 @@ -/* CSS for UI elements (a.k.a. chrome) */ - -@import 'variables.css'; - -::-webkit-scrollbar { - background: var(--bg); -} -::-webkit-scrollbar-thumb { - background: var(--scrollbar); -} - -#searchresults a, -.content a:link, -a:visited, -a > .hljs { - color: var(--links); -} - -/* Menu Bar */ - -#menu-bar { - position: -webkit-sticky; - position: sticky; - top: 0; - z-index: 101; - margin: auto calc(0px - var(--page-padding)); -} -#menu-bar > #menu-bar-sticky-container { - display: flex; - flex-wrap: wrap; - background-color: var(--bg); - border-bottom-color: var(--bg); - border-bottom-width: 1px; - border-bottom-style: solid; -} -.js #menu-bar > #menu-bar-sticky-container { - transition: transform 0.3s; -} -#menu-bar.bordered > #menu-bar-sticky-container { - border-bottom-color: var(--table-border-color); -} -#menu-bar i, #menu-bar .icon-button { - position: relative; - padding: 0 8px; - z-index: 10; - line-height: 50px; - cursor: pointer; - transition: color 0.5s; -} -@media only screen and (max-width: 420px) { - #menu-bar i, #menu-bar .icon-button { - padding: 0 5px; - } -} - -.icon-button { - border: none; - background: none; - padding: 0; - color: inherit; -} -.icon-button i { - margin: 0; -} - -#print-button { - margin: 0 15px; -} - -html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-container { - transform: translateY(-60px); -} - -.left-buttons { - display: flex; - margin: 0 5px; -} -.no-js .left-buttons { - display: none; -} - -.menu-title { - display: inline-block; - font-weight: 200; - font-size: 20px; - line-height: 50px; - text-align: center; - margin: 0; - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.js .menu-title { - cursor: pointer; -} - -.menu-bar, -.menu-bar:visited, -.nav-chapters, -.nav-chapters:visited, -.mobile-nav-chapters, -.mobile-nav-chapters:visited, -.menu-bar .icon-button, -.menu-bar a i { - color: var(--icons); -} - -.menu-bar i:hover, -.menu-bar .icon-button:hover, -.nav-chapters:hover, -.mobile-nav-chapters i:hover { - color: var(--icons-hover); -} - -/* Nav Icons */ - -.nav-chapters { - font-size: 2.5em; - text-align: center; - text-decoration: none; - - position: fixed; - top: 50px; /* Height of menu-bar */ - bottom: 0; - margin: 0; - max-width: 150px; - min-width: 90px; - - display: flex; - justify-content: center; - align-content: center; - flex-direction: column; - - transition: color 0.5s; -} - -.nav-chapters:hover { text-decoration: none; } - -.nav-wrapper { - margin-top: 50px; - display: none; -} - -.mobile-nav-chapters { - font-size: 2.5em; - text-align: center; - text-decoration: none; - width: 90px; - border-radius: 5px; - background-color: var(--sidebar-bg); -} - -.previous { - float: left; -} - -.next { - float: right; - right: var(--page-padding); -} - -@media only screen and (max-width: 1080px) { - .nav-wide-wrapper { display: none; } - .nav-wrapper { display: block; } -} - -@media only screen and (max-width: 1380px) { - .sidebar-visible .nav-wide-wrapper { display: none; } - .sidebar-visible .nav-wrapper { display: block; } -} - -/* Inline code */ - -:not(pre) > .hljs { - display: inline-block; - vertical-align: middle; - padding: 0.1em 0.3em; - border-radius: 3px; - color: var(--inline-code-color); -} - -a:hover > .hljs { - text-decoration: underline; -} - -pre { - position: relative; -} -pre > .buttons { - position: absolute; - z-index: 100; - right: 5px; - top: 5px; - - color: var(--sidebar-fg); - cursor: pointer; -} -pre > .buttons :hover { - color: var(--sidebar-active); -} -pre > .buttons i { - margin-left: 8px; -} -pre > .buttons button { - color: inherit; - background: transparent; - border: none; - cursor: inherit; -} -pre > .result { - margin-top: 10px; -} - -/* Search */ - -#searchresults a { - text-decoration: none; -} - -mark { - border-radius: 2px; - padding: 0 3px 1px 3px; - margin: 0 -3px -1px -3px; - background-color: var(--search-mark-bg); - transition: background-color 300ms linear; - cursor: pointer; -} - -mark.fade-out { - background-color: rgba(0,0,0,0) !important; - cursor: auto; -} - -.searchbar-outer { - margin-left: auto; - margin-right: auto; - max-width: var(--content-max-width); -} - -#searchbar { - width: 100%; - margin: 5px auto 0px auto; - padding: 10px 16px; - transition: box-shadow 300ms ease-in-out; - border: 1px solid var(--searchbar-border-color); - border-radius: 3px; - background-color: var(--searchbar-bg); - color: var(--searchbar-fg); -} -#searchbar:focus, -#searchbar.active { - box-shadow: 0 0 3px var(--searchbar-shadow-color); -} - -.searchresults-header { - font-weight: bold; - font-size: 1em; - padding: 18px 0 0 5px; - color: var(--searchresults-header-fg); -} - -.searchresults-outer { - margin-left: auto; - margin-right: auto; - max-width: var(--content-max-width); - border-bottom: 1px dashed var(--searchresults-border-color); -} - -ul#searchresults { - list-style: none; - padding-left: 20px; -} -ul#searchresults li { - margin: 10px 0px; - padding: 2px; - border-radius: 2px; -} -ul#searchresults li.focus { - background-color: var(--searchresults-li-bg); -} -ul#searchresults span.teaser { - display: block; - clear: both; - margin: 5px 0 0 20px; - font-size: 0.8em; -} -ul#searchresults span.teaser em { - font-weight: bold; - font-style: normal; -} - -/* Sidebar */ - -.sidebar { - position: fixed; - left: 0; - top: 0; - bottom: 0; - width: var(--sidebar-width); - overflow-y: auto; - padding: 10px 10px; - font-size: 0.875em; - box-sizing: border-box; - -webkit-overflow-scrolling: touch; - overscroll-behavior-y: contain; - background-color: var(--sidebar-bg); - color: var(--sidebar-fg); -} -.js .sidebar { - transition: transform 0.3s; /* Animation: slide away */ -} -.sidebar code { - line-height: 2em; -} -.sidebar-hidden .sidebar { - transform: translateX(calc(0px - var(--sidebar-width))); -} -.sidebar::-webkit-scrollbar { - background: var(--sidebar-bg); -} -.sidebar::-webkit-scrollbar-thumb { - background: var(--scrollbar); -} - -.sidebar-visible .page-wrapper { - transform: translateX(var(--sidebar-width)); -} -@media only screen and (min-width: 620px) { - .sidebar-visible .page-wrapper { - transform: none; - margin-left: var(--sidebar-width); - } -} - -.chapter { - list-style: none outside none; - padding-left: 0; - line-height: 2.2em; -} -.chapter li { - color: var(--sidebar-non-existant); -} -.chapter li a { - color: var(--sidebar-fg); - display: block; - padding: 0; - text-decoration: none; -} -.chapter li a:hover { text-decoration: none } -.chapter li .active, -a:hover { - /* Animate color change */ - color: var(--sidebar-active); -} - -.spacer { - width: 100%; - height: 3px; - margin: 5px 0px; -} -.chapter .spacer { - background-color: var(--sidebar-spacer); -} - -@media (-moz-touch-enabled: 1), (pointer: coarse) { - .chapter li a { padding: 5px 0; } - .spacer { margin: 10px 0; } -} - -.section { - list-style: none outside none; - padding-left: 20px; - line-height: 1.9em; -} - -/* Theme Menu Popup */ - -.theme-popup { - position: absolute; - left: 10px; - top: 50px; - z-index: 1000; - border-radius: 4px; - font-size: 0.7em; - color: var(--fg); - background: var(--theme-popup-bg); - border: 1px solid var(--theme-popup-border); - margin: 0; - padding: 0; - list-style: none; - display: none; -} -.theme-popup .default { - color: var(--icons); -} -.theme-popup .theme { - width: 100%; - border: 0; - margin: 0; - padding: 2px 10px; - line-height: 25px; - white-space: nowrap; - text-align: left; - cursor: pointer; - color: inherit; - background: inherit; - font-size: inherit; -} -.theme-popup .theme:hover { - background-color: var(--theme-hover); -} -.theme-popup .theme:hover:first-child, -.theme-popup .theme:hover:last-child { - border-top-left-radius: inherit; - border-top-right-radius: inherit; -} diff --git a/book/css/general.css b/book/css/general.css deleted file mode 100644 index aedfb332b7458b..00000000000000 --- a/book/css/general.css +++ /dev/null @@ -1,144 +0,0 @@ -/* Base styles and content styles */ - -@import 'variables.css'; - -html { - font-family: "Open Sans", sans-serif; - color: var(--fg); - background-color: var(--bg); - text-size-adjust: none; -} - -body { - margin: 0; - font-size: 1rem; - overflow-x: hidden; -} - -code { - font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace; - font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */ -} - -.left { float: left; } -.right { float: right; } -.hidden { display: none; } -.play-button.hidden { display: none; } - -h2, h3 { margin-top: 2.5em; } -h4, h5 { margin-top: 2em; } - -.header + .header h3, -.header + .header h4, -.header + .header h5 { - margin-top: 1em; -} - -a.header:target h1:before, -a.header:target h2:before, -a.header:target h3:before, -a.header:target h4:before { - display: inline-block; - content: "»"; - margin-left: -30px; - width: 30px; -} - -.page { - outline: 0; - padding: 0 var(--page-padding); -} -.page-wrapper { - box-sizing: border-box; -} -.js .page-wrapper { - transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ -} - -.content { - overflow-y: auto; - padding: 0 15px; - padding-bottom: 50px; -} -.content main { - margin-left: auto; - margin-right: auto; - max-width: var(--content-max-width); -} -.content a { text-decoration: none; } -.content a:hover { text-decoration: underline; } -.content img { max-width: 100%; } -.content .header:link, -.content .header:visited { - color: var(--fg); -} -.content .header:link, -.content .header:visited:hover { - text-decoration: none; -} - -table { - margin: 0 auto; - border-collapse: collapse; -} -table td { - padding: 3px 20px; - border: 1px var(--table-border-color) solid; -} -table thead { - background: var(--table-header-bg); -} -table thead td { - font-weight: 700; - border: none; -} -table thead tr { - border: 1px var(--table-header-bg) solid; -} -/* Alternate background colors for rows */ -table tbody tr:nth-child(2n) { - background: var(--table-alternate-bg); -} - - -blockquote { - margin: 20px 0; - padding: 0 20px; - color: var(--fg); - background-color: var(--quote-bg); - border-top: .1em solid var(--quote-border); - border-bottom: .1em solid var(--quote-border); -} - - -:not(.footnote-definition) + .footnote-definition, -.footnote-definition + :not(.footnote-definition) { - margin-top: 2em; -} -.footnote-definition { - font-size: 0.9em; - margin: 0.5em 0; -} -.footnote-definition p { - display: inline; -} - -.tooltiptext { - position: absolute; - visibility: hidden; - color: #fff; - background-color: #333; - transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ - left: -8px; /* Half of the width of the icon */ - top: -35px; - font-size: 0.8em; - text-align: center; - border-radius: 6px; - padding: 5px 8px; - margin: 5px; - z-index: 1000; -} -.tooltipped .tooltiptext { - visibility: visible; -} - \ No newline at end of file diff --git a/book/css/print.css b/book/css/print.css deleted file mode 100644 index 5e690f7559943d..00000000000000 --- a/book/css/print.css +++ /dev/null @@ -1,54 +0,0 @@ - -#sidebar, -#menu-bar, -.nav-chapters, -.mobile-nav-chapters { - display: none; -} - -#page-wrapper.page-wrapper { - transform: none; - margin-left: 0px; - overflow-y: initial; -} - -#content { - max-width: none; - margin: 0; - padding: 0; -} - -.page { - overflow-y: initial; -} - -code { - background-color: #666666; - border-radius: 5px; - - /* Force background to be printed in Chrome */ - -webkit-print-color-adjust: exact; -} - -pre > .buttons { - z-index: 2; -} - -a, a:visited, a:active, a:hover { - color: #4183c4; - text-decoration: none; -} - -h1, h2, h3, h4, h5, h6 { - page-break-inside: avoid; - page-break-after: avoid; -} - -pre, code { - page-break-inside: avoid; - white-space: pre-wrap; -} - -.fa { - display: none !important; -} diff --git a/book/css/variables.css b/book/css/variables.css deleted file mode 100644 index 29daa07293793e..00000000000000 --- a/book/css/variables.css +++ /dev/null @@ -1,210 +0,0 @@ - -/* Globals */ - -:root { - --sidebar-width: 300px; - --page-padding: 15px; - --content-max-width: 750px; -} - -/* Themes */ - -.ayu { - --bg: hsl(210, 25%, 8%); - --fg: #c5c5c5; - - --sidebar-bg: #14191f; - --sidebar-fg: #c8c9db; - --sidebar-non-existant: #5c6773; - --sidebar-active: #ffb454; - --sidebar-spacer: #2d334f; - - --scrollbar: var(--sidebar-fg); - - --icons: #737480; - --icons-hover: #b7b9cc; - - --links: #0096cf; - - --inline-code-color: #ffb454; - - --theme-popup-bg: #14191f; - --theme-popup-border: #5c6773; - --theme-hover: #191f26; - - --quote-bg: hsl(226, 15%, 17%); - --quote-border: hsl(226, 15%, 22%); - - --table-border-color: hsl(210, 25%, 13%); - --table-header-bg: hsl(210, 25%, 28%); - --table-alternate-bg: hsl(210, 25%, 11%); - - --searchbar-border-color: #848484; - --searchbar-bg: #424242; - --searchbar-fg: #fff; - --searchbar-shadow-color: #d4c89f; - --searchresults-header-fg: #666; - --searchresults-border-color: #888; - --searchresults-li-bg: #252932; - --search-mark-bg: #e3b171; -} - -.coal { - --bg: hsl(200, 7%, 8%); - --fg: #98a3ad; - - --sidebar-bg: #292c2f; - --sidebar-fg: #a1adb8; - --sidebar-non-existant: #505254; - --sidebar-active: #3473ad; - --sidebar-spacer: #393939; - - --scrollbar: var(--sidebar-fg); - - --icons: #43484d; - --icons-hover: #b3c0cc; - - --links: #2b79a2; - - --inline-code-color: #c5c8c6;; - - --theme-popup-bg: #141617; - --theme-popup-border: #43484d; - --theme-hover: #1f2124; - - --quote-bg: hsl(234, 21%, 18%); - --quote-border: hsl(234, 21%, 23%); - - --table-border-color: hsl(200, 7%, 13%); - --table-header-bg: hsl(200, 7%, 28%); - --table-alternate-bg: hsl(200, 7%, 11%); - - --searchbar-border-color: #aaa; - --searchbar-bg: #b7b7b7; - --searchbar-fg: #000; - --searchbar-shadow-color: #aaa; - --searchresults-header-fg: #666; - --searchresults-border-color: #98a3ad; - --searchresults-li-bg: #2b2b2f; - --search-mark-bg: #355c7d; -} - -.light { - --bg: hsl(0, 0%, 100%); - --fg: #333333; - - --sidebar-bg: #fafafa; - --sidebar-fg: #364149; - --sidebar-non-existant: #aaaaaa; - --sidebar-active: #008cff; - --sidebar-spacer: #f4f4f4; - - --scrollbar: #cccccc; - - --icons: #cccccc; - --icons-hover: #333333; - - --links: #4183c4; - - --inline-code-color: #6e6b5e; - - --theme-popup-bg: #fafafa; - --theme-popup-border: #cccccc; - --theme-hover: #e6e6e6; - - --quote-bg: hsl(197, 37%, 96%); - --quote-border: hsl(197, 37%, 91%); - - --table-border-color: hsl(0, 0%, 95%); - --table-header-bg: hsl(0, 0%, 80%); - --table-alternate-bg: hsl(0, 0%, 97%); - - --searchbar-border-color: #aaa; - --searchbar-bg: #fafafa; - --searchbar-fg: #000; - --searchbar-shadow-color: #aaa; - --searchresults-header-fg: #666; - --searchresults-border-color: #888; - --searchresults-li-bg: #e4f2fe; - --search-mark-bg: #a2cff5; -} - -.navy { - --bg: hsl(226, 23%, 11%); - --fg: #bcbdd0; - - --sidebar-bg: #282d3f; - --sidebar-fg: #c8c9db; - --sidebar-non-existant: #505274; - --sidebar-active: #2b79a2; - --sidebar-spacer: #2d334f; - - --scrollbar: var(--sidebar-fg); - - --icons: #737480; - --icons-hover: #b7b9cc; - - --links: #2b79a2; - - --inline-code-color: #c5c8c6;; - - --theme-popup-bg: #161923; - --theme-popup-border: #737480; - --theme-hover: #282e40; - - --quote-bg: hsl(226, 15%, 17%); - --quote-border: hsl(226, 15%, 22%); - - --table-border-color: hsl(226, 23%, 16%); - --table-header-bg: hsl(226, 23%, 31%); - --table-alternate-bg: hsl(226, 23%, 14%); - - --searchbar-border-color: #aaa; - --searchbar-bg: #aeaec6; - --searchbar-fg: #000; - --searchbar-shadow-color: #aaa; - --searchresults-header-fg: #5f5f71; - --searchresults-border-color: #5c5c68; - --searchresults-li-bg: #242430; - --search-mark-bg: #a2cff5; -} - -.rust { - --bg: hsl(60, 9%, 87%); - --fg: #262625; - - --sidebar-bg: #3b2e2a; - --sidebar-fg: #c8c9db; - --sidebar-non-existant: #505254; - --sidebar-active: #e69f67; - --sidebar-spacer: #45373a; - - --scrollbar: var(--sidebar-fg); - - --icons: #737480; - --icons-hover: #262625; - - --links: #2b79a2; - - --inline-code-color: #6e6b5e; - - --theme-popup-bg: #e1e1db; - --theme-popup-border: #b38f6b; - --theme-hover: #99908a; - - --quote-bg: hsl(60, 5%, 75%); - --quote-border: hsl(60, 5%, 70%); - - --table-border-color: hsl(60, 9%, 82%); - --table-header-bg: #b3a497; - --table-alternate-bg: hsl(60, 9%, 84%); - - --searchbar-border-color: #aaa; - --searchbar-bg: #fafafa; - --searchbar-fg: #000; - --searchbar-shadow-color: #aaa; - --searchresults-header-fg: #666; - --searchresults-border-color: #888; - --searchresults-li-bg: #dec2a2; - --search-mark-bg: #e69f67; -} diff --git a/book/db_ledger.rs b/book/db_ledger.rs deleted file mode 100644 index 15d8183d45a7b9..00000000000000 --- a/book/db_ledger.rs +++ /dev/null @@ -1,634 +0,0 @@ -//! The `ledger` module provides functions for parallel verification of the -//! Proof of History ledger as well as iterative read, append write, and random -//! access read to a persistent file-based ledger. - -use bincode::{deserialize, serialize}; -use byteorder::{ByteOrder, LittleEndian, ReadBytesExt}; -use entry::Entry; -use ledger::Block; -use packet::{Blob, SharedBlob, BLOB_HEADER_SIZE}; -use result::{Error, Result}; -use rocksdb::{ColumnFamily, Options, WriteBatch, DB}; -use serde::de::DeserializeOwned; -use serde::Serialize; -use std::io; - -pub const DB_LEDGER_DIRECTORY: &str = "db_ledger"; - -#[derive(Debug, PartialEq, Eq)] -pub enum DbLedgerError { - BlobForIndexExists, - InvalidBlobData, -} - -pub trait LedgerColumnFamily { - type ValueType: DeserializeOwned + Serialize; - - fn get(&self, db: &DB, key: &[u8]) -> Result> { - let data_bytes = db.get_cf(self.handle(db), key)?; - - if let Some(raw) = data_bytes { - let result: Self::ValueType = deserialize(&raw)?; - Ok(Some(result)) - } else { - Ok(None) - } - } - - fn get_bytes(&self, db: &DB, key: &[u8]) -> Result>> { - let data_bytes = db.get_cf(self.handle(db), key)?; - Ok(data_bytes.map(|x| x.to_vec())) - } - - fn put_bytes(&self, db: &DB, key: &[u8], serialized_value: &[u8]) -> Result<()> { - db.put_cf(self.handle(db), &key, &serialized_value)?; - Ok(()) - } - - fn put(&self, db: &DB, key: &[u8], value: &Self::ValueType) -> Result<()> { - let serialized = serialize(value)?; - db.put_cf(self.handle(db), &key, &serialized)?; - Ok(()) - } - - fn delete(&self, db: &DB, key: &[u8]) -> Result<()> { - db.delete_cf(self.handle(db), &key)?; - Ok(()) - } - - fn handle(&self, db: &DB) -> ColumnFamily; -} - -pub trait LedgerColumnFamilyRaw { - fn get(&self, db: &DB, key: &[u8]) -> Result>> { - let data_bytes = db.get_cf(self.handle(db), key)?; - Ok(data_bytes.map(|x| x.to_vec())) - } - - fn put(&self, db: &DB, key: &[u8], serialized_value: &[u8]) -> Result<()> { - db.put_cf(self.handle(db), &key, &serialized_value)?; - Ok(()) - } - - fn delete(&self, db: &DB, key: &[u8]) -> Result<()> { - db.delete_cf(self.handle(db), &key)?; - Ok(()) - } - - fn handle(&self, db: &DB) -> ColumnFamily; -} - -#[derive(Debug, Default, Deserialize, Serialize, Eq, PartialEq)] -// The Meta column family -pub struct SlotMeta { - // The total number of consecutive blob starting from index 0 - // we have received for this slot. - pub consumed: u64, - // The entry height of the highest blob received for this slot. - pub received: u64, -} - -impl SlotMeta { - fn new() -> Self { - SlotMeta { - consumed: 0, - received: 0, - } - } -} - -#[derive(Default)] -pub struct MetaCf {} - -impl MetaCf { - pub fn key(slot_height: u64) -> Vec { - let mut key = vec![0u8; 8]; - LittleEndian::write_u64(&mut key[0..8], slot_height); - key - } -} - -impl LedgerColumnFamily for MetaCf { - type ValueType = SlotMeta; - - fn handle(&self, db: &DB) -> ColumnFamily { - db.cf_handle(META_CF).unwrap() - } -} - -// The data column family -#[derive(Default)] -pub struct DataCf {} - -impl DataCf { - pub fn get_by_slot_index( - &self, - db: &DB, - slot_height: u64, - index: u64, - ) -> Result>> { - let key = Self::key(slot_height, index); - self.get(db, &key) - } - - pub fn put_by_slot_index( - &self, - db: &DB, - slot_height: u64, - index: u64, - serialized_value: &[u8], - ) -> Result<()> { - let key = Self::key(slot_height, index); - self.put(db, &key, serialized_value) - } - - pub fn key(slot_height: u64, index: u64) -> Vec { - let mut key = vec![0u8; 16]; - LittleEndian::write_u64(&mut key[0..8], slot_height); - LittleEndian::write_u64(&mut key[8..16], index); - key - } - - pub fn slot_height_from_key(key: &[u8]) -> Result { - let mut rdr = io::Cursor::new(&key[0..8]); - let height = rdr.read_u64::()?; - Ok(height) - } - - pub fn index_from_key(key: &[u8]) -> Result { - let mut rdr = io::Cursor::new(&key[8..16]); - let index = rdr.read_u64::()?; - Ok(index) - } -} - -impl LedgerColumnFamilyRaw for DataCf { - fn handle(&self, db: &DB) -> ColumnFamily { - db.cf_handle(DATA_CF).unwrap() - } -} - -// The erasure column family -#[derive(Default)] -pub struct ErasureCf {} - -impl ErasureCf { - pub fn get_by_slot_index( - &self, - db: &DB, - slot_height: u64, - index: u64, - ) -> Result>> { - let key = Self::key(slot_height, index); - self.get(db, &key) - } - - pub fn put_by_slot_index( - &self, - db: &DB, - slot_height: u64, - index: u64, - serialized_value: &[u8], - ) -> Result<()> { - let key = Self::key(slot_height, index); - self.put(db, &key, serialized_value) - } - - pub fn key(slot_height: u64, index: u64) -> Vec { - DataCf::key(slot_height, index) - } - - pub fn index_from_key(key: &[u8]) -> Result { - DataCf::index_from_key(key) - } -} - -impl LedgerColumnFamilyRaw for ErasureCf { - fn handle(&self, db: &DB) -> ColumnFamily { - db.cf_handle(ERASURE_CF).unwrap() - } -} - -// ledger window -pub struct DbLedger { - // Underlying database is automatically closed in the Drop implementation of DB - pub db: DB, - pub meta_cf: MetaCf, - pub data_cf: DataCf, - pub erasure_cf: ErasureCf, -} - -// TODO: Once we support a window that knows about different leader -// slots, change functions where this is used to take slot height -// as a variable argument -pub const DEFAULT_SLOT_HEIGHT: u64 = 0; -// Column family for metadata about a leader slot -pub const META_CF: &str = "meta"; -// Column family for the data in a leader slot -pub const DATA_CF: &str = "data"; -// Column family for erasure data -pub const ERASURE_CF: &str = "erasure"; - -impl DbLedger { - // Opens a Ledger in directory, provides "infinite" window of blobs - pub fn open(ledger_path: &str) -> Result { - // Use default database options - let mut options = Options::default(); - options.create_if_missing(true); - options.create_missing_column_families(true); - - // Column family names - let cfs = vec![META_CF, DATA_CF, ERASURE_CF]; - - // Open the database - let db = DB::open_cf(&options, ledger_path, &cfs)?; - - // Create the metadata column family - let meta_cf = MetaCf::default(); - - // Create the data column family - let data_cf = DataCf::default(); - - // Create the erasure column family - let erasure_cf = ErasureCf::default(); - - Ok(DbLedger { - db, - meta_cf, - data_cf, - erasure_cf, - }) - } - - pub fn write_shared_blobs(&mut self, slot: u64, shared_blobs: &[SharedBlob]) -> Result<()> { - let blob_locks: Vec<_> = shared_blobs.iter().map(|b| b.read().unwrap()).collect(); - let blobs: Vec<&Blob> = blob_locks.iter().map(|b| &**b).collect(); - self.write_blobs(slot, &blobs) - } - - pub fn write_blobs<'a, I>(&mut self, slot: u64, blobs: I) -> Result<()> - where - I: IntoIterator, - { - for blob in blobs.into_iter() { - let index = blob.index()?; - let key = DataCf::key(slot, index); - self.insert_data_blob(&key, blob)?; - } - Ok(()) - } - - pub fn write_entries(&mut self, slot: u64, entries: &[Entry]) -> Result<()> { - let shared_blobs = entries.to_blobs(); - let blob_locks: Vec<_> = shared_blobs.iter().map(|b| b.read().unwrap()).collect(); - let blobs: Vec<&Blob> = blob_locks.iter().map(|b| &**b).collect(); - self.write_blobs(slot, &blobs)?; - Ok(()) - } - - pub fn insert_data_blob(&self, key: &[u8], new_blob: &Blob) -> Result> { - let slot_height = DataCf::slot_height_from_key(key)?; - let meta_key = MetaCf::key(slot_height); - - let mut should_write_meta = false; - - let mut meta = { - if let Some(meta) = self.db.get_cf(self.meta_cf.handle(&self.db), &meta_key)? { - deserialize(&meta)? - } else { - should_write_meta = true; - SlotMeta::new() - } - }; - - let mut index = DataCf::index_from_key(key)?; - - // TODO: Handle if leader sends different blob for same index when the index > consumed - // The old window implementation would just replace that index. - if index < meta.consumed { - return Err(Error::DbLedgerError(DbLedgerError::BlobForIndexExists)); - } - - // Index is zero-indexed, while the "received" height starts from 1, - // so received = index + 1 for the same blob. - if index >= meta.received { - meta.received = index + 1; - should_write_meta = true; - } - - let mut consumed_queue = vec![]; - - if meta.consumed == index { - // Add the new blob to the consumed queue - let serialized_entry_data = - &new_blob.data[BLOB_HEADER_SIZE..BLOB_HEADER_SIZE + new_blob.size()?]; - // Verify entries can actually be reconstructed - let entry: Entry = deserialize(serialized_entry_data) - .expect("Blob made it past validation, so must be deserializable at this point"); - should_write_meta = true; - meta.consumed += 1; - consumed_queue.push(entry); - // Find the next consecutive block of blobs. - // TODO: account for consecutive blocks that - // span multiple slots - loop { - index += 1; - let key = DataCf::key(slot_height, index); - if let Some(blob_data) = self.data_cf.get(&self.db, &key)? { - let serialized_entry_data = &blob_data[BLOB_HEADER_SIZE..]; - let entry: Entry = deserialize(serialized_entry_data) - .expect("Ledger should only contain well formed data"); - consumed_queue.push(entry); - meta.consumed += 1; - } else { - break; - } - } - } - - // Commit Step: Atomic write both the metadata and the data - let mut batch = WriteBatch::default(); - if should_write_meta { - batch.put_cf(self.meta_cf.handle(&self.db), &meta_key, &serialize(&meta)?)?; - } - - let serialized_blob_data = &new_blob.data[..BLOB_HEADER_SIZE + new_blob.size()?]; - batch.put_cf(self.data_cf.handle(&self.db), key, serialized_blob_data)?; - self.db.write(batch)?; - Ok(consumed_queue) - } - - // Fill 'buf' with num_blobs or most number of consecutive - // whole blobs that fit into buf.len() - // - // Return tuple of (number of blob read, total size of blobs read) - pub fn get_blob_bytes( - &mut self, - start_index: u64, - num_blobs: u64, - buf: &mut [u8], - ) -> Result<(u64, u64)> { - let start_key = DataCf::key(DEFAULT_SLOT_HEIGHT, start_index); - let mut db_iterator = self.db.raw_iterator_cf(self.data_cf.handle(&self.db))?; - db_iterator.seek(&start_key); - let mut total_blobs = 0; - let mut total_current_size = 0; - for expected_index in start_index..start_index + num_blobs { - if !db_iterator.valid() { - if expected_index == start_index { - return Err(Error::IO(io::Error::new( - io::ErrorKind::NotFound, - "Blob at start_index not found", - ))); - } else { - break; - } - } - - // Check key is the next sequential key based on - // blob index - let key = &db_iterator.key().expect("Expected valid key"); - let index = DataCf::index_from_key(key)?; - if index != expected_index { - break; - } - - // Get the blob data - let value = &db_iterator.value(); - - if value.is_none() { - break; - } - - let value = value.as_ref().unwrap(); - let blob_data_len = value.len(); - - if total_current_size + blob_data_len > buf.len() { - break; - } - - buf[total_current_size..total_current_size + value.len()].copy_from_slice(value); - total_current_size += blob_data_len; - total_blobs += 1; - - // TODO: Change this logic to support looking for data - // that spans multiple leader slots, once we support - // a window that knows about different leader slots - db_iterator.next(); - } - - Ok((total_blobs, total_current_size as u64)) - } -} - -pub fn write_entries_to_ledger(ledger_paths: &[String], entries: &[Entry]) { - for ledger_path in ledger_paths { - let mut db_ledger = - DbLedger::open(ledger_path).expect("Expected to be able to open database ledger"); - db_ledger - .write_entries(DEFAULT_SLOT_HEIGHT, &entries) - .expect("Expected successful write of genesis entries"); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use ledger::{get_tmp_ledger_path, make_tiny_test_entries, Block}; - use rocksdb::{Options, DB}; - - #[test] - fn test_put_get_simple() { - let ledger_path = get_tmp_ledger_path("test_put_get_simple"); - let ledger = DbLedger::open(&ledger_path).unwrap(); - - // Test meta column family - let meta = SlotMeta::new(); - let meta_key = MetaCf::key(0); - ledger.meta_cf.put(&ledger.db, &meta_key, &meta).unwrap(); - let result = ledger - .meta_cf - .get(&ledger.db, &meta_key) - .unwrap() - .expect("Expected meta object to exist"); - - assert_eq!(result, meta); - - // Test erasure column family - let erasure = vec![1u8; 16]; - let erasure_key = ErasureCf::key(DEFAULT_SLOT_HEIGHT, 0); - ledger - .erasure_cf - .put(&ledger.db, &erasure_key, &erasure) - .unwrap(); - - let result = ledger - .erasure_cf - .get(&ledger.db, &erasure_key) - .unwrap() - .expect("Expected erasure object to exist"); - - assert_eq!(result, erasure); - - // Test data column family - let data = vec![2u8; 16]; - let data_key = DataCf::key(DEFAULT_SLOT_HEIGHT, 0); - ledger.data_cf.put(&ledger.db, &data_key, &data).unwrap(); - - let result = ledger - .data_cf - .get(&ledger.db, &data_key) - .unwrap() - .expect("Expected data object to exist"); - - assert_eq!(result, data); - - // Destroying database without closing it first is undefined behavior - drop(ledger); - DB::destroy(&Options::default(), &ledger_path) - .expect("Expected successful database destruction"); - } - - #[test] - fn test_get_blobs_bytes() { - let shared_blobs = make_tiny_test_entries(10).to_blobs(); - let blob_locks: Vec<_> = shared_blobs.iter().map(|b| b.read().unwrap()).collect(); - let blobs: Vec<&Blob> = blob_locks.iter().map(|b| &**b).collect(); - - let ledger_path = get_tmp_ledger_path("test_get_blobs_bytes"); - let mut ledger = DbLedger::open(&ledger_path).unwrap(); - ledger.write_blobs(DEFAULT_SLOT_HEIGHT, &blobs).unwrap(); - - let mut buf = [0; 1024]; - let (num_blobs, bytes) = ledger.get_blob_bytes(0, 1, &mut buf).unwrap(); - let bytes = bytes as usize; - assert_eq!(num_blobs, 1); - { - let blob_data = &buf[..bytes]; - assert_eq!(blob_data, &blobs[0].data[..bytes]); - } - - let (num_blobs, bytes2) = ledger.get_blob_bytes(0, 2, &mut buf).unwrap(); - let bytes2 = bytes2 as usize; - assert_eq!(num_blobs, 2); - assert!(bytes2 > bytes); - { - let blob_data_1 = &buf[..bytes]; - assert_eq!(blob_data_1, &blobs[0].data[..bytes]); - - let blob_data_2 = &buf[bytes..bytes2]; - assert_eq!(blob_data_2, &blobs[1].data[..bytes2 - bytes]); - } - - // buf size part-way into blob[1], should just return blob[0] - let mut buf = vec![0; bytes + 1]; - let (num_blobs, bytes3) = ledger.get_blob_bytes(0, 2, &mut buf).unwrap(); - assert_eq!(num_blobs, 1); - let bytes3 = bytes3 as usize; - assert_eq!(bytes3, bytes); - - let mut buf = vec![0; bytes2 - 1]; - let (num_blobs, bytes4) = ledger.get_blob_bytes(0, 2, &mut buf).unwrap(); - assert_eq!(num_blobs, 1); - let bytes4 = bytes4 as usize; - assert_eq!(bytes4, bytes); - - let mut buf = vec![0; bytes * 2]; - let (num_blobs, bytes6) = ledger.get_blob_bytes(9, 1, &mut buf).unwrap(); - assert_eq!(num_blobs, 1); - let bytes6 = bytes6 as usize; - - { - let blob_data = &buf[..bytes6]; - assert_eq!(blob_data, &blobs[9].data[..bytes6]); - } - - // Read out of range - assert!(ledger.get_blob_bytes(20, 2, &mut buf).is_err()); - - // Destroying database without closing it first is undefined behavior - drop(ledger); - DB::destroy(&Options::default(), &ledger_path) - .expect("Expected successful database destruction"); - } - - #[test] - fn test_insert_data_blobs_basic() { - let entries = make_tiny_test_entries(2); - let shared_blobs = entries.to_blobs(); - let blob_locks: Vec<_> = shared_blobs.iter().map(|b| b.read().unwrap()).collect(); - let blobs: Vec<&Blob> = blob_locks.iter().map(|b| &**b).collect(); - - let ledger_path = get_tmp_ledger_path("test_insert_data_blobs_basic"); - let ledger = DbLedger::open(&ledger_path).unwrap(); - - // Insert second blob, we're missing the first blob, so should return nothing - let result = ledger - .insert_data_blob(&DataCf::key(DEFAULT_SLOT_HEIGHT, 1), blobs[1]) - .unwrap(); - - assert!(result.len() == 0); - let meta = ledger - .meta_cf - .get(&ledger.db, &MetaCf::key(DEFAULT_SLOT_HEIGHT)) - .unwrap() - .expect("Expected new metadata object to be created"); - assert!(meta.consumed == 0 && meta.received == 2); - - // Insert first blob, check for consecutive returned entries - let result = ledger - .insert_data_blob(&DataCf::key(DEFAULT_SLOT_HEIGHT, 0), blobs[0]) - .unwrap(); - - assert_eq!(result, entries); - - let meta = ledger - .meta_cf - .get(&ledger.db, &MetaCf::key(DEFAULT_SLOT_HEIGHT)) - .unwrap() - .expect("Expected new metadata object to exist"); - assert!(meta.consumed == 2 && meta.received == 2); - - // Destroying database without closing it first is undefined behavior - drop(ledger); - DB::destroy(&Options::default(), &ledger_path) - .expect("Expected successful database destruction"); - } - - #[test] - fn test_insert_data_blobs_multiple() { - let num_blobs = 10; - let entries = make_tiny_test_entries(num_blobs); - let shared_blobs = entries.to_blobs(); - let blob_locks: Vec<_> = shared_blobs.iter().map(|b| b.read().unwrap()).collect(); - let blobs: Vec<&Blob> = blob_locks.iter().map(|b| &**b).collect(); - - let ledger_path = get_tmp_ledger_path("test_insert_data_blobs_multiple"); - let ledger = DbLedger::open(&ledger_path).unwrap(); - - // Insert first blob, check for consecutive returned blobs - for i in (0..num_blobs).rev() { - let result = ledger - .insert_data_blob(&DataCf::key(DEFAULT_SLOT_HEIGHT, i as u64), blobs[i]) - .unwrap(); - - let meta = ledger - .meta_cf - .get(&ledger.db, &MetaCf::key(DEFAULT_SLOT_HEIGHT)) - .unwrap() - .expect("Expected metadata object to exist"); - if i != 0 { - assert_eq!(result.len(), 0); - assert!(meta.consumed == 0 && meta.received == num_blobs as u64); - } else { - assert_eq!(result, entries); - assert!(meta.consumed == num_blobs as u64 && meta.received == num_blobs as u64); - } - } - - // Destroying database without closing it first is undefined behavior - drop(ledger); - DB::destroy(&Options::default(), &ledger_path) - .expect("Expected successful database destruction"); - } -} diff --git a/book/db_window.rs b/book/db_window.rs deleted file mode 100644 index caec3cc06aa812..00000000000000 --- a/book/db_window.rs +++ /dev/null @@ -1,578 +0,0 @@ -//! Set of functions for emulating windowing functions from a database ledger implementation -use cluster_info::ClusterInfo; -use counter::Counter; -use db_ledger::*; -use entry::Entry; -use leader_scheduler::LeaderScheduler; -use log::Level; -use packet::{SharedBlob, BLOB_HEADER_SIZE}; -use result::Result; -use rocksdb::DBRawIterator; -use solana_metrics::{influxdb, submit}; -use solana_sdk::pubkey::Pubkey; -use std::cmp; -use std::net::SocketAddr; -use std::sync::atomic::AtomicUsize; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, RwLock}; -use streamer::BlobSender; - -pub fn repair( - slot: u64, - db_ledger: &DbLedger, - cluster_info: &Arc>, - id: &Pubkey, - times: usize, - tick_height: u64, - max_entry_height: u64, - leader_scheduler_option: &Arc>, -) -> Result)>> { - let rcluster_info = cluster_info.read().unwrap(); - let mut is_next_leader = false; - let meta = db_ledger.meta_cf.get(&db_ledger.db, &MetaCf::key(slot))?; - if meta.is_none() { - return Ok(vec![]); - } - let meta = meta.unwrap(); - - let consumed = meta.consumed; - let received = meta.received; - - // Repair should only be called when received > consumed, enforced in window_service - assert!(received > consumed); - { - let ls_lock = leader_scheduler_option.read().unwrap(); - if !ls_lock.use_only_bootstrap_leader { - // Calculate the next leader rotation height and check if we are the leader - if let Some(next_leader_rotation_height) = ls_lock.max_height_for_leader(tick_height) { - match ls_lock.get_scheduled_leader(next_leader_rotation_height) { - Some((leader_id, _)) if leader_id == *id => is_next_leader = true, - // In the case that we are not in the current scope of the leader schedule - // window then either: - // - // 1) The replicate stage hasn't caught up to the "consumed" entries we sent, - // in which case it will eventually catch up - // - // 2) We are on the border between seed_rotation_intervals, so the - // schedule won't be known until the entry on that cusp is received - // by the replicate stage (which comes after this stage). Hence, the next - // leader at the beginning of that next epoch will not know they are the - // leader until they receive that last "cusp" entry. The leader also won't ask for repairs - // for that entry because "is_next_leader" won't be set here. In this case, - // everybody will be blocking waiting for that "cusp" entry instead of repairing, - // until the leader hits "times" >= the max times in calculate_max_repair_entry_height(). - // The impact of this, along with the similar problem from broadcast for the transitioning - // leader, can be observed in the multinode test, test_full_leader_validator_network(), - None => (), - _ => (), - } - } - } - } - - let num_peers = rcluster_info.tvu_peers().len() as u64; - - // Check if there's a max_entry_height limitation - let max_repair_entry_height = if max_entry_height == 0 { - calculate_max_repair_entry_height(num_peers, consumed, received, times, is_next_leader) - } else { - max_entry_height + 2 - }; - - let idxs = find_missing_data_indexes(slot, db_ledger, consumed, max_repair_entry_height - 1); - - let reqs: Vec<_> = idxs - .into_iter() - .filter_map(|pix| rcluster_info.window_index_request(pix).ok()) - .collect(); - - drop(rcluster_info); - - inc_new_counter_info!("streamer-repair_window-repair", reqs.len()); - - if log_enabled!(Level::Trace) { - trace!( - "{}: repair_window counter times: {} consumed: {} received: {} max_repair_entry_height: {} missing: {}", - id, - times, - consumed, - received, - max_repair_entry_height, - reqs.len() - ); - for (to, _) in &reqs { - trace!("{}: repair_window request to {}", id, to); - } - } - - Ok(reqs) -} - -// Given a start and end entry index, find all the missing -// indexes in the ledger in the range [start_index, end_index) -pub fn find_missing_indexes( - db_iterator: &mut DBRawIterator, - slot: u64, - start_index: u64, - end_index: u64, - key: &Fn(u64, u64) -> Vec, - index_from_key: &Fn(&[u8]) -> Result, -) -> Vec { - if start_index >= end_index { - return vec![]; - } - - let mut missing_indexes = vec![]; - - // Seek to the first blob with index >= start_index - db_iterator.seek(&key(slot, start_index)); - - // The index of the first missing blob in the slot - let mut prev_index = start_index; - loop { - if !db_iterator.valid() { - break; - } - let current_key = db_iterator.key().expect("Expect a valid key"); - let current_index = - index_from_key(¤t_key).expect("Expect to be able to parse index from valid key"); - let upper_index = cmp::min(current_index, end_index); - for i in prev_index..upper_index { - missing_indexes.push(i); - } - if current_index >= end_index { - break; - } - - prev_index = current_index + 1; - db_iterator.next(); - } - - missing_indexes -} - -pub fn find_missing_data_indexes( - slot: u64, - db_ledger: &DbLedger, - start_index: u64, - end_index: u64, -) -> Vec { - let mut db_iterator = db_ledger - .db - .raw_iterator_cf(db_ledger.data_cf.handle(&db_ledger.db)) - .expect("Expected to be able to open database iterator"); - - find_missing_indexes( - &mut db_iterator, - slot, - start_index, - end_index, - &DataCf::key, - &DataCf::index_from_key, - ) -} - -pub fn find_missing_coding_indexes( - slot: u64, - db_ledger: &DbLedger, - start_index: u64, - end_index: u64, -) -> Vec { - let mut db_iterator = db_ledger - .db - .raw_iterator_cf(db_ledger.erasure_cf.handle(&db_ledger.db)) - .expect("Expected to be able to open database iterator"); - - find_missing_indexes( - &mut db_iterator, - slot, - start_index, - end_index, - &ErasureCf::key, - &ErasureCf::index_from_key, - ) -} - -pub fn retransmit_all_leader_blocks( - dq: &[SharedBlob], - leader_scheduler: &LeaderScheduler, - retransmit: &BlobSender, -) -> Result<()> { - let mut retransmit_queue: Vec = Vec::new(); - for b in dq { - // Check if the blob is from the scheduled leader for its slot. If so, - // add to the retransmit_queue - let slot = b.read().unwrap().slot()?; - if let Some(leader_id) = leader_scheduler.get_leader_for_slot(slot) { - add_blob_to_retransmit_queue(b, leader_id, &mut retransmit_queue); - } - } - - submit( - influxdb::Point::new("retransmit-queue") - .add_field( - "count", - influxdb::Value::Integer(retransmit_queue.len() as i64), - ).to_owned(), - ); - - if !retransmit_queue.is_empty() { - inc_new_counter_info!("streamer-recv_window-retransmit", retransmit_queue.len()); - retransmit.send(retransmit_queue)?; - } - Ok(()) -} - -pub fn add_blob_to_retransmit_queue( - b: &SharedBlob, - leader_id: Pubkey, - retransmit_queue: &mut Vec, -) { - let p = b.read().unwrap(); - if p.id().expect("get_id in fn add_block_to_retransmit_queue") == leader_id { - let nv = SharedBlob::default(); - { - let mut mnv = nv.write().unwrap(); - let sz = p.meta.size; - mnv.meta.size = sz; - mnv.data[..sz].copy_from_slice(&p.data[..sz]); - } - retransmit_queue.push(nv); - } -} - -/// Process a blob: Add blob to the ledger window. If a continuous set of blobs -/// starting from consumed is thereby formed, add that continuous -/// range of blobs to a queue to be sent on to the next stage. -pub fn process_blob( - leader_scheduler: &LeaderScheduler, - db_ledger: &mut DbLedger, - blob: &SharedBlob, - max_ix: u64, - pix: u64, - consume_queue: &mut Vec, - tick_height: &mut u64, - done: &Arc, -) -> Result<()> { - let is_coding = blob.read().unwrap().is_coding(); - - // Check if the blob is in the range of our known leaders. If not, we return. - let slot = blob.read().unwrap().slot()?; - let leader = leader_scheduler.get_leader_for_slot(slot); - - if leader.is_none() { - return Ok(()); - } - - // Insert the new blob into the window - let mut consumed_entries = if is_coding { - let erasure_key = ErasureCf::key(slot, pix); - let rblob = &blob.read().unwrap(); - let size = rblob.size()?; - db_ledger.erasure_cf.put( - &db_ledger.db, - &erasure_key, - &rblob.data[..BLOB_HEADER_SIZE + size], - )?; - vec![] - } else { - let data_key = ErasureCf::key(slot, pix); - db_ledger.insert_data_blob(&data_key, &blob.read().unwrap())? - }; - - // TODO: Once erasure is fixed, readd that logic here - - for entry in &consumed_entries { - *tick_height += entry.is_tick() as u64; - } - - // For downloading storage blobs, - // we only want up to a certain index - // then stop - if max_ix != 0 && !consumed_entries.is_empty() { - let meta = db_ledger - .meta_cf - .get(&db_ledger.db, &MetaCf::key(slot))? - .expect("Expect metadata to exist if consumed entries is nonzero"); - - let consumed = meta.consumed; - - // Check if we ran over the last wanted entry - if consumed > max_ix { - let extra_unwanted_entries_len = consumed - (max_ix + 1); - let consumed_entries_len = consumed_entries.len(); - consumed_entries.truncate(consumed_entries_len - extra_unwanted_entries_len as usize); - done.store(true, Ordering::Relaxed); - } - } - - consume_queue.extend(consumed_entries); - Ok(()) -} - -pub fn calculate_max_repair_entry_height( - num_peers: u64, - consumed: u64, - received: u64, - times: usize, - is_next_leader: bool, -) -> u64 { - // Calculate the highest blob index that this node should have already received - // via avalanche. The avalanche splits data stream into nodes and each node retransmits - // the data to their peer nodes. So there's a possibility that a blob (with index lower - // than current received index) is being retransmitted by a peer node. - if times >= 8 || is_next_leader { - // if repair backoff is getting high, or if we are the next leader, - // don't wait for avalanche. received - 1 is the index of the highest blob. - received - } else { - cmp::max(consumed, received.saturating_sub(num_peers)) - } -} - -#[cfg(test)] -mod test { - use super::*; - use ledger::{get_tmp_ledger_path, make_tiny_test_entries, Block}; - use packet::{Blob, Packet, Packets, SharedBlob, PACKET_DATA_SIZE}; - use rocksdb::{Options, DB}; - use signature::{Keypair, KeypairUtil}; - use std::io; - use std::io::Write; - use std::net::UdpSocket; - use std::sync::atomic::{AtomicBool, Ordering}; - use std::sync::mpsc::channel; - use std::sync::Arc; - use std::time::Duration; - use streamer::{receiver, responder, PacketReceiver}; - - fn get_msgs(r: PacketReceiver, num: &mut usize) { - for _t in 0..5 { - let timer = Duration::new(1, 0); - match r.recv_timeout(timer) { - Ok(m) => *num += m.read().unwrap().packets.len(), - e => info!("error {:?}", e), - } - if *num == 10 { - break; - } - } - } - #[test] - pub fn streamer_debug() { - write!(io::sink(), "{:?}", Packet::default()).unwrap(); - write!(io::sink(), "{:?}", Packets::default()).unwrap(); - write!(io::sink(), "{:?}", Blob::default()).unwrap(); - } - - #[test] - pub fn streamer_send_test() { - let read = UdpSocket::bind("127.0.0.1:0").expect("bind"); - read.set_read_timeout(Some(Duration::new(1, 0))).unwrap(); - - let addr = read.local_addr().unwrap(); - let send = UdpSocket::bind("127.0.0.1:0").expect("bind"); - let exit = Arc::new(AtomicBool::new(false)); - let (s_reader, r_reader) = channel(); - let t_receiver = receiver( - Arc::new(read), - exit.clone(), - s_reader, - "window-streamer-test", - ); - let t_responder = { - let (s_responder, r_responder) = channel(); - let t_responder = responder("streamer_send_test", Arc::new(send), r_responder); - let mut msgs = Vec::new(); - for i in 0..10 { - let mut b = SharedBlob::default(); - { - let mut w = b.write().unwrap(); - w.data[0] = i as u8; - w.meta.size = PACKET_DATA_SIZE; - w.meta.set_addr(&addr); - } - msgs.push(b); - } - s_responder.send(msgs).expect("send"); - t_responder - }; - - let mut num = 0; - get_msgs(r_reader, &mut num); - assert_eq!(num, 10); - exit.store(true, Ordering::Relaxed); - t_receiver.join().expect("join"); - t_responder.join().expect("join"); - } - - #[test] - pub fn test_calculate_max_repair_entry_height() { - assert_eq!(calculate_max_repair_entry_height(20, 4, 11, 0, false), 4); - assert_eq!(calculate_max_repair_entry_height(0, 10, 90, 0, false), 90); - assert_eq!(calculate_max_repair_entry_height(15, 10, 90, 32, false), 90); - assert_eq!(calculate_max_repair_entry_height(15, 10, 90, 0, false), 75); - assert_eq!(calculate_max_repair_entry_height(90, 10, 90, 0, false), 10); - assert_eq!(calculate_max_repair_entry_height(90, 10, 50, 0, false), 10); - assert_eq!(calculate_max_repair_entry_height(90, 10, 99, 0, false), 10); - assert_eq!(calculate_max_repair_entry_height(90, 10, 101, 0, false), 11); - assert_eq!(calculate_max_repair_entry_height(90, 10, 101, 0, true), 101); - assert_eq!( - calculate_max_repair_entry_height(90, 10, 101, 30, true), - 101 - ); - } - - #[test] - pub fn test_retransmit() { - let leader = Keypair::new().pubkey(); - let nonleader = Keypair::new().pubkey(); - let leader_scheduler = LeaderScheduler::from_bootstrap_leader(leader); - let blob = SharedBlob::default(); - - let (blob_sender, blob_receiver) = channel(); - - // Expect blob from leader to be retransmitted - blob.write().unwrap().set_id(&leader).unwrap(); - retransmit_all_leader_blocks(&vec![blob.clone()], &leader_scheduler, &blob_sender) - .expect("Expect successful retransmit"); - let output_blob = blob_receiver - .try_recv() - .expect("Expect input blob to be retransmitted"); - - // Retransmitted blob should be missing the leader id - assert_ne!(*output_blob[0].read().unwrap(), *blob.read().unwrap()); - // Set the leader in the retransmitted blob, should now match the original - output_blob[0].write().unwrap().set_id(&leader).unwrap(); - assert_eq!(*output_blob[0].read().unwrap(), *blob.read().unwrap()); - - // Expect blob from nonleader to not be retransmitted - blob.write().unwrap().set_id(&nonleader).unwrap(); - retransmit_all_leader_blocks(&vec![blob], &leader_scheduler, &blob_sender) - .expect("Expect successful retransmit"); - assert!(blob_receiver.try_recv().is_err()); - } - - #[test] - pub fn test_find_missing_data_indexes_sanity() { - let slot = 0; - - // Create RocksDb ledger - let db_ledger_path = get_tmp_ledger_path("test_find_missing_data_indexes_sanity"); - let mut db_ledger = DbLedger::open(&db_ledger_path).unwrap(); - - // Early exit conditions - let empty: Vec = vec![]; - assert_eq!(find_missing_data_indexes(slot, &db_ledger, 0, 0), empty); - assert_eq!(find_missing_data_indexes(slot, &db_ledger, 5, 5), empty); - assert_eq!(find_missing_data_indexes(slot, &db_ledger, 4, 3), empty); - - let shared_blob = &make_tiny_test_entries(1).to_blobs()[0]; - let first_index = 10; - { - let mut bl = shared_blob.write().unwrap(); - bl.set_index(10).unwrap(); - } - - // Insert one blob at index = first_index - db_ledger - .write_blobs(slot, &vec![&*shared_blob.read().unwrap()]) - .unwrap(); - - // The first blob has index = first_index. Thus, for i < first_index, - // given the input range of [i, first_index], the missing indexes should be - // [i, first_index - 1] - for i in 0..first_index { - let result = find_missing_data_indexes(slot, &db_ledger, i, first_index); - let expected: Vec = (i..first_index).collect(); - - assert_eq!(result, expected); - } - - drop(db_ledger); - DB::destroy(&Options::default(), &db_ledger_path) - .expect("Expected successful database destruction"); - } - - #[test] - pub fn test_find_missing_data_indexes() { - let slot = 0; - // Create RocksDb ledger - let db_ledger_path = get_tmp_ledger_path("test_find_missing_data_indexes"); - let mut db_ledger = DbLedger::open(&db_ledger_path).unwrap(); - - // Write entries - let gap = 10; - let num_entries = 10; - let shared_blobs = make_tiny_test_entries(num_entries).to_blobs(); - for (b, i) in shared_blobs.iter().zip(0..shared_blobs.len() as u64) { - b.write().unwrap().set_index(i * gap).unwrap(); - } - let blob_locks: Vec<_> = shared_blobs.iter().map(|b| b.read().unwrap()).collect(); - let blobs: Vec<&Blob> = blob_locks.iter().map(|b| &**b).collect(); - db_ledger.write_blobs(slot, &blobs).unwrap(); - - // Index of the first blob is 0 - // Index of the second blob is "gap" - // Thus, the missing indexes should then be [1, gap - 1] for the input index - // range of [0, gap) - let expected: Vec = (1..gap).collect(); - assert_eq!( - find_missing_data_indexes(slot, &db_ledger, 0, gap), - expected - ); - assert_eq!( - find_missing_data_indexes(slot, &db_ledger, 1, gap), - expected, - ); - assert_eq!( - find_missing_data_indexes(slot, &db_ledger, 0, gap - 1), - &expected[..expected.len() - 1], - ); - - for i in 0..num_entries as u64 { - for j in 0..i { - let expected: Vec = (j..i) - .flat_map(|k| { - let begin = k * gap + 1; - let end = (k + 1) * gap; - (begin..end) - }).collect(); - assert_eq!( - find_missing_data_indexes(slot, &db_ledger, j * gap, i * gap), - expected, - ); - } - } - - drop(db_ledger); - DB::destroy(&Options::default(), &db_ledger_path) - .expect("Expected successful database destruction"); - } - - #[test] - pub fn test_no_missing_blob_indexes() { - let slot = 0; - // Create RocksDb ledger - let db_ledger_path = get_tmp_ledger_path("test_find_missing_data_indexes"); - let mut db_ledger = DbLedger::open(&db_ledger_path).unwrap(); - - // Write entries - let num_entries = 10; - let shared_blobs = make_tiny_test_entries(num_entries).to_blobs(); - let blob_locks: Vec<_> = shared_blobs.iter().map(|b| b.read().unwrap()).collect(); - let blobs: Vec<&Blob> = blob_locks.iter().map(|b| &**b).collect(); - db_ledger.write_blobs(slot, &blobs).unwrap(); - - let empty: Vec = vec![]; - for i in 0..num_entries as u64 { - for j in 0..i { - assert_eq!(find_missing_data_indexes(slot, &db_ledger, j, i), empty); - } - } - - drop(db_ledger); - DB::destroy(&Options::default(), &db_ledger_path) - .expect("Expected successful database destruction"); - } -} diff --git a/book/elasticlunr.min.js b/book/elasticlunr.min.js deleted file mode 100644 index 94b20dd2ef4609..00000000000000 --- a/book/elasticlunr.min.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * elasticlunr - http://weixsong.github.io - * Lightweight full-text search engine in Javascript for browser search and offline search. - 0.9.5 - * - * Copyright (C) 2017 Oliver Nightingale - * Copyright (C) 2017 Wei Song - * MIT Licensed - * @license - */ -!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o>; -pub type EntryReceiver = Receiver>; - -/// Each Entry contains three pieces of data. The `num_hashes` field is the number -/// of hashes performed since the previous entry. The `id` field is the result -/// of hashing `id` from the previous entry `num_hashes` times. The `transactions` -/// field points to Transactions that took place shortly before `id` was generated. -/// -/// If you divide `num_hashes` by the amount of time it takes to generate a new hash, you -/// get a duration estimate since the last Entry. Since processing power increases -/// over time, one should expect the duration `num_hashes` represents to decrease proportionally. -/// An upper bound on Duration can be estimated by assuming each hash was generated by the -/// world's fastest processor at the time the entry was recorded. Or said another way, it -/// is physically not possible for a shorter duration to have occurred if one assumes the -/// hash was computed by the world's fastest processor at that time. The hash chain is both -/// a Verifiable Delay Function (VDF) and a Proof of Work (not to be confused with Proof of -/// Work consensus!) - -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub struct Entry { - /// The the previous Entry ID. - pub prev_id: Hash, - - /// The number of hashes since the previous Entry ID. - pub num_hashes: u64, - - /// The SHA-256 hash `num_hashes` after the previous Entry ID. - pub id: Hash, - - /// An unordered list of transactions that were observed before the Entry ID was - /// generated. They may have been observed before a previous Entry ID but were - /// pushed back into this list to ensure deterministic interpretation of the ledger. - pub transactions: Vec, -} - -impl Entry { - /// Creates the next Entry `num_hashes` after `start_hash`. - pub fn new(prev_id: &Hash, num_hashes: u64, transactions: Vec) -> Self { - let entry = { - if num_hashes == 0 && transactions.is_empty() { - Entry { - prev_id: *prev_id, - num_hashes: 0, - id: *prev_id, - transactions, - } - } else if num_hashes == 0 { - // If you passed in transactions, but passed in num_hashes == 0, then - // next_hash will generate the next hash and set num_hashes == 1 - let id = next_hash(prev_id, 1, &transactions); - Entry { - prev_id: *prev_id, - num_hashes: 1, - id, - transactions, - } - } else { - // Otherwise, the next Entry `num_hashes` after `start_hash`. - // If you wanted a tick for instance, then pass in num_hashes = 1 - // and transactions = empty - let id = next_hash(prev_id, num_hashes, &transactions); - Entry { - prev_id: *prev_id, - num_hashes, - id, - transactions, - } - } - }; - - let size = serialized_size(&entry).unwrap(); - if size > BLOB_DATA_SIZE as u64 { - panic!( - "Serialized entry size too large: {} ({} transactions):", - size, - entry.transactions.len() - ); - } - entry - } - - pub fn to_blob( - &self, - idx: Option, - id: Option, - addr: Option<&SocketAddr>, - ) -> SharedBlob { - let blob = SharedBlob::default(); - { - let mut blob_w = blob.write().unwrap(); - let pos = { - let mut out = Cursor::new(blob_w.data_mut()); - serialize_into(&mut out, &self).expect("failed to serialize output"); - out.position() as usize - }; - blob_w.set_size(pos); - - if let Some(idx) = idx { - blob_w.set_index(idx).expect("set_index()"); - } - if let Some(id) = id { - blob_w.set_id(&id).expect("set_id()"); - } - if let Some(addr) = addr { - blob_w.meta.set_addr(addr); - } - blob_w.set_flags(0).unwrap(); - } - blob - } - - /// Estimate serialized_size of Entry without creating an Entry. - pub fn serialized_size(transactions: &[Transaction]) -> u64 { - let txs_size = serialized_size(transactions).unwrap(); - - // num_hashes + id + prev_id + txs - - (size_of::() + 2 * size_of::()) as u64 + txs_size - } - - pub fn num_will_fit(transactions: &[Transaction]) -> usize { - if transactions.is_empty() { - return 0; - } - let mut num = transactions.len(); - let mut upper = transactions.len(); - let mut lower = 1; // if one won't fit, we have a lot of TODOs - let mut next = transactions.len(); // optimistic - loop { - debug!( - "num {}, upper {} lower {} next {} transactions.len() {}", - num, - upper, - lower, - next, - transactions.len() - ); - if Self::serialized_size(&transactions[..num]) <= BLOB_DATA_SIZE as u64 { - next = (upper + num) / 2; - lower = num; - debug!("num {} fits, maybe too well? trying {}", num, next); - } else { - next = (lower + num) / 2; - upper = num; - debug!("num {} doesn't fit! trying {}", num, next); - } - // same as last time - if next == num { - debug!("converged on num {}", num); - break; - } - num = next; - } - num - } - - /// Creates the next Tick Entry `num_hashes` after `start_hash`. - pub fn new_mut( - start_hash: &mut Hash, - num_hashes: &mut u64, - transactions: Vec, - ) -> Self { - let entry = Self::new(start_hash, *num_hashes, transactions); - *start_hash = entry.id; - *num_hashes = 0; - assert!(serialized_size(&entry).unwrap() <= BLOB_DATA_SIZE as u64); - entry - } - - /// Creates a Entry from the number of hashes `num_hashes` since the previous transaction - /// and that resulting `id`. - pub fn new_tick(prev_id: &Hash, num_hashes: u64, id: &Hash) -> Self { - Entry { - prev_id: *prev_id, - num_hashes, - id: *id, - transactions: vec![], - } - } - - pub fn verify_self(&self) -> bool { - self.id == next_hash(&self.prev_id, self.num_hashes, &self.transactions) - } - - /// Verifies self.id is the result of hashing a `start_hash` `self.num_hashes` times. - /// If the transaction is not a Tick, then hash that as well. - pub fn verify(&self, start_hash: &Hash) -> bool { - let ref_hash = next_hash(start_hash, self.num_hashes, &self.transactions); - if self.id != ref_hash { - warn!( - "next_hash is invalid expected: {:?} actual: {:?}", - self.id, ref_hash - ); - return false; - } - true - } - - pub fn is_tick(&self) -> bool { - self.transactions.is_empty() - } -} - -/// Creates the hash `num_hashes` after `start_hash`. If the transaction contains -/// a signature, the final hash will be a hash of both the previous ID and -/// the signature. If num_hashes is zero and there's no transaction data, -/// start_hash is returned. -fn next_hash(start_hash: &Hash, num_hashes: u64, transactions: &[Transaction]) -> Hash { - if num_hashes == 0 && transactions.is_empty() { - return *start_hash; - } - - let mut poh = Poh::new(*start_hash, 0); - - for _ in 1..num_hashes { - poh.hash(); - } - - if transactions.is_empty() { - poh.tick().id - } else { - poh.record(Transaction::hash(transactions)).id - } -} - -/// Creates the next Tick or Transaction Entry `num_hashes` after `start_hash`. -pub fn next_entry(prev_id: &Hash, num_hashes: u64, transactions: Vec) -> Entry { - assert!(num_hashes > 0 || transactions.is_empty()); - Entry { - prev_id: *prev_id, - num_hashes, - id: next_hash(prev_id, num_hashes, &transactions), - transactions, - } -} - -pub fn reconstruct_entries_from_blobs(blobs: Vec) -> Result<(Vec, u64)> { - let mut entries: Vec = Vec::with_capacity(blobs.len()); - let mut num_ticks = 0; - - for blob in blobs { - let entry: Entry = { - let msg = blob.read().unwrap(); - let msg_size = msg.size()?; - deserialize(&msg.data()[..msg_size]).expect("Error reconstructing entry") - }; - - if entry.is_tick() { - num_ticks += 1 - } - entries.push(entry) - } - Ok((entries, num_ticks)) -} - -#[cfg(test)] -mod tests { - use super::*; - use budget_transaction::BudgetTransaction; - use chrono::prelude::*; - use entry::Entry; - use signature::{Keypair, KeypairUtil}; - use solana_sdk::hash::hash; - use system_transaction::SystemTransaction; - use transaction::Transaction; - - #[test] - fn test_entry_verify() { - let zero = Hash::default(); - let one = hash(&zero.as_ref()); - assert!(Entry::new_tick(&zero, 0, &zero).verify(&zero)); // base case, never used - assert!(!Entry::new_tick(&zero, 0, &zero).verify(&one)); // base case, bad - assert!(next_entry(&zero, 1, vec![]).verify(&zero)); // inductive step - assert!(next_entry(&zero, 1, vec![]).verify_self()); // also inductive step - assert!(!next_entry(&zero, 1, vec![]).verify(&one)); // inductive step, bad - } - - #[test] - fn test_transaction_reorder_attack() { - let zero = Hash::default(); - - // First, verify entries - let keypair = Keypair::new(); - let tx0 = Transaction::system_new(&keypair, keypair.pubkey(), 0, zero); - let tx1 = Transaction::system_new(&keypair, keypair.pubkey(), 1, zero); - let mut e0 = Entry::new(&zero, 0, vec![tx0.clone(), tx1.clone()]); - assert!(e0.verify(&zero)); - - // Next, swap two transactions and ensure verification fails. - e0.transactions[0] = tx1; // <-- attack - e0.transactions[1] = tx0; - assert!(!e0.verify(&zero)); - } - - #[test] - fn test_witness_reorder_attack() { - let zero = Hash::default(); - - // First, verify entries - let keypair = Keypair::new(); - let tx0 = Transaction::budget_new_timestamp( - &keypair, - keypair.pubkey(), - keypair.pubkey(), - Utc::now(), - zero, - ); - let tx1 = - Transaction::budget_new_signature(&keypair, keypair.pubkey(), keypair.pubkey(), zero); - let mut e0 = Entry::new(&zero, 0, vec![tx0.clone(), tx1.clone()]); - assert!(e0.verify(&zero)); - - // Next, swap two witness transactions and ensure verification fails. - e0.transactions[0] = tx1; // <-- attack - e0.transactions[1] = tx0; - assert!(!e0.verify(&zero)); - } - - #[test] - fn test_next_entry() { - let zero = Hash::default(); - let tick = next_entry(&zero, 1, vec![]); - assert_eq!(tick.num_hashes, 1); - assert_ne!(tick.id, zero); - - let tick = next_entry(&zero, 0, vec![]); - assert_eq!(tick.num_hashes, 0); - assert_eq!(tick.id, zero); - - let keypair = Keypair::new(); - let tx0 = Transaction::budget_new_timestamp( - &keypair, - keypair.pubkey(), - keypair.pubkey(), - Utc::now(), - zero, - ); - let entry0 = next_entry(&zero, 1, vec![tx0.clone()]); - assert_eq!(entry0.num_hashes, 1); - assert_eq!(entry0.id, next_hash(&zero, 1, &vec![tx0])); - } - - #[test] - #[should_panic] - fn test_next_entry_panic() { - let zero = Hash::default(); - let keypair = Keypair::new(); - let tx = Transaction::system_new(&keypair, keypair.pubkey(), 0, zero); - next_entry(&zero, 0, vec![tx]); - } - - #[test] - fn test_serialized_size() { - let zero = Hash::default(); - let keypair = Keypair::new(); - let tx = Transaction::system_new(&keypair, keypair.pubkey(), 0, zero); - let entry = next_entry(&zero, 1, vec![tx.clone()]); - assert_eq!( - Entry::serialized_size(&[tx]), - serialized_size(&entry).unwrap() - ); - } -} diff --git a/book/erasure.rs b/book/erasure.rs deleted file mode 100644 index bdd7f6381ef689..00000000000000 --- a/book/erasure.rs +++ /dev/null @@ -1,943 +0,0 @@ -// Support erasure coding -use packet::{SharedBlob, BLOB_DATA_SIZE, BLOB_HEADER_SIZE}; -use solana_sdk::pubkey::Pubkey; -use std::cmp; -use std::mem; -use std::result; -use window::WindowSlot; - -//TODO(sakridge) pick these values -pub const NUM_DATA: usize = 16; // number of data blobs -pub const NUM_CODING: usize = 4; // number of coding blobs, also the maximum number that can go missing -pub const ERASURE_SET_SIZE: usize = NUM_DATA + NUM_CODING; // total number of blobs in an erasure set, includes data and coding blobs - -pub const JERASURE_ALIGN: usize = 4; // data size has to be a multiple of 4 bytes - -macro_rules! align { - ($x:expr, $align:expr) => { - $x + ($align - 1) & !($align - 1) - }; -} - -#[derive(Debug, PartialEq, Eq)] -pub enum ErasureError { - NotEnoughBlocksToDecode, - DecodeError, - EncodeError, - InvalidBlockSize, -} - -pub type Result = result::Result; - -// k = number of data devices -// m = number of coding devices -// w = word size - -extern "C" { - fn jerasure_matrix_encode( - k: i32, - m: i32, - w: i32, - matrix: *const i32, - data_ptrs: *const *const u8, - coding_ptrs: *const *mut u8, - size: i32, - ); - fn jerasure_matrix_decode( - k: i32, - m: i32, - w: i32, - matrix: *const i32, - row_k_ones: i32, - erasures: *const i32, - data_ptrs: *const *mut u8, - coding_ptrs: *const *mut u8, - size: i32, - ) -> i32; - fn galois_single_divide(a: i32, b: i32, w: i32) -> i32; -} - -fn get_matrix(m: i32, k: i32, w: i32) -> Vec { - let mut matrix = vec![0; (m * k) as usize]; - for i in 0..m { - for j in 0..k { - unsafe { - matrix[(i * k + j) as usize] = galois_single_divide(1, i ^ (m + j), w); - } - } - } - matrix -} - -pub const ERASURE_W: i32 = 32; - -// Generate coding blocks into coding -// There are some alignment restrictions, blocks should be aligned by 16 bytes -// which means their size should be >= 16 bytes -pub fn generate_coding_blocks(coding: &mut [&mut [u8]], data: &[&[u8]]) -> Result<()> { - if data.is_empty() { - return Ok(()); - } - let k = data.len() as i32; - let m = coding.len() as i32; - let block_len = data[0].len() as i32; - let matrix: Vec = get_matrix(m, k, ERASURE_W); - let mut data_arg = Vec::with_capacity(data.len()); - for block in data { - if block_len != block.len() as i32 { - error!( - "data block size incorrect {} expected {}", - block.len(), - block_len - ); - return Err(ErasureError::InvalidBlockSize); - } - data_arg.push(block.as_ptr()); - } - let mut coding_arg = Vec::with_capacity(coding.len()); - for mut block in coding { - if block_len != block.len() as i32 { - error!( - "coding block size incorrect {} expected {}", - block.len(), - block_len - ); - return Err(ErasureError::InvalidBlockSize); - } - coding_arg.push(block.as_mut_ptr()); - } - - unsafe { - jerasure_matrix_encode( - k, - m, - ERASURE_W, - matrix.as_ptr(), - data_arg.as_ptr(), - coding_arg.as_ptr(), - block_len, - ); - } - Ok(()) -} - -// Recover data + coding blocks into data blocks -// data: array of blocks to recover into -// coding: arry of coding blocks -// erasures: list of indices in data where blocks should be recovered -pub fn decode_blocks( - data: &mut [&mut [u8]], - coding: &mut [&mut [u8]], - erasures: &[i32], -) -> Result<()> { - if data.is_empty() { - return Ok(()); - } - let block_len = data[0].len(); - let matrix: Vec = get_matrix(coding.len() as i32, data.len() as i32, ERASURE_W); - - // generate coding pointers, blocks should be the same size - let mut coding_arg: Vec<*mut u8> = Vec::new(); - for x in coding.iter_mut() { - if x.len() != block_len { - return Err(ErasureError::InvalidBlockSize); - } - coding_arg.push(x.as_mut_ptr()); - } - - // generate data pointers, blocks should be the same size - let mut data_arg: Vec<*mut u8> = Vec::new(); - for x in data.iter_mut() { - if x.len() != block_len { - return Err(ErasureError::InvalidBlockSize); - } - data_arg.push(x.as_mut_ptr()); - } - let ret = unsafe { - jerasure_matrix_decode( - data.len() as i32, - coding.len() as i32, - ERASURE_W, - matrix.as_ptr(), - 0, - erasures.as_ptr(), - data_arg.as_ptr(), - coding_arg.as_ptr(), - data[0].len() as i32, - ) - }; - trace!("jerasure_matrix_decode ret: {}", ret); - for x in data[erasures[0] as usize][0..8].iter() { - trace!("{} ", x) - } - trace!(""); - if ret < 0 { - return Err(ErasureError::DecodeError); - } - Ok(()) -} - -// Generate coding blocks in window starting from start_idx, -// for num_blobs.. For each block place the coding blobs -// at the end of the block like so: -// -// block-size part of a Window, with each element a WindowSlot.. -// |<======================= NUM_DATA ==============================>| -// |<==== NUM_CODING ===>| -// +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ -// | D | | D | | D | | D | | D | | D | | D | | D | | D | | D | -// +---+ +---+ +---+ +---+ +---+ . . . +---+ +---+ +---+ +---+ +---+ -// | | | | | | | | | | | | | C | | C | | C | | C | -// +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ -// -// blob structure for coding, recover -// -// + ------- meta is set and used by transport, meta.size is actual length -// | of data in the byte array blob.data -// | -// | + -- data is stuff shipped over the wire, and has an included -// | | header -// V V -// +----------+------------------------------------------------------------+ -// | meta | data | -// |+---+-- |+---+---+---+---+------------------------------------------+| -// || s | . || i | | f | s | || -// || i | . || n | i | l | i | || -// || z | . || d | d | a | z | blob.data(), or blob.data_mut() || -// || e | || e | | g | e | || -// |+---+-- || x | | s | | || -// | |+---+---+---+---+------------------------------------------+| -// +----------+------------------------------------------------------------+ -// | |<=== coding blob part for "coding" =======>| -// | | -// |<============== data blob part for "coding" ==============>| -// -// -// -pub fn generate_coding( - id: &Pubkey, - window: &mut [WindowSlot], - receive_index: u64, - num_blobs: usize, - transmit_index_coding: &mut u64, -) -> Result<()> { - // beginning of the coding blobs of the block that receive_index points into - let coding_index_start = - receive_index - (receive_index % NUM_DATA as u64) + (NUM_DATA - NUM_CODING) as u64; - - let start_idx = receive_index as usize % window.len(); - let mut block_start = start_idx - (start_idx % NUM_DATA); - - loop { - let block_end = block_start + NUM_DATA; - if block_end > (start_idx + num_blobs) { - break; - } - info!( - "generate_coding {} start: {} end: {} start_idx: {} num_blobs: {}", - id, block_start, block_end, start_idx, num_blobs - ); - - let mut max_data_size = 0; - - // find max_data_size, maybe bail if not all the data is here - for i in block_start..block_end { - let n = i % window.len(); - trace!("{} window[{}] = {:?}", id, n, window[n].data); - - if let Some(b) = &window[n].data { - max_data_size = cmp::max(b.read().unwrap().meta.size, max_data_size); - } else { - trace!("{} data block is null @ {}", id, n); - return Ok(()); - } - } - - // round up to the nearest jerasure alignment - max_data_size = align!(max_data_size, JERASURE_ALIGN); - - trace!("{} max_data_size: {}", id, max_data_size); - - let mut data_blobs = Vec::with_capacity(NUM_DATA); - for i in block_start..block_end { - let n = i % window.len(); - - if let Some(b) = &window[n].data { - // make sure extra bytes in each blob are zero-d out for generation of - // coding blobs - let mut b_wl = b.write().unwrap(); - for i in b_wl.meta.size..max_data_size { - b_wl.data[i] = 0; - } - data_blobs.push(b); - } - } - - // getting ready to do erasure coding, means that we're potentially - // going back in time, tell our caller we've inserted coding blocks - // starting at coding_index_start - *transmit_index_coding = cmp::min(*transmit_index_coding, coding_index_start); - - let mut coding_blobs = Vec::with_capacity(NUM_CODING); - let coding_start = block_end - NUM_CODING; - for i in coding_start..block_end { - let n = i % window.len(); - assert!(window[n].coding.is_none()); - - window[n].coding = Some(SharedBlob::default()); - - let coding = window[n].coding.clone().unwrap(); - let mut coding_wl = coding.write().unwrap(); - for i in 0..max_data_size { - coding_wl.data[i] = 0; - } - // copy index and id from the data blob - if let Some(data) = &window[n].data { - let data_rl = data.read().unwrap(); - - let index = data_rl.index().unwrap(); - let slot = data_rl.slot().unwrap(); - let id = data_rl.id().unwrap(); - - trace!( - "{} copying index {} id {:?} from data to coding", - id, - index, - id - ); - coding_wl.set_index(index).unwrap(); - coding_wl.set_slot(slot).unwrap(); - coding_wl.set_id(&id).unwrap(); - } - coding_wl.set_size(max_data_size); - if coding_wl.set_coding().is_err() { - return Err(ErasureError::EncodeError); - } - - coding_blobs.push(coding.clone()); - } - - let data_locks: Vec<_> = data_blobs.iter().map(|b| b.read().unwrap()).collect(); - - let data_ptrs: Vec<_> = data_locks - .iter() - .enumerate() - .map(|(i, l)| { - trace!("{} i: {} data: {}", id, i, l.data[0]); - &l.data[..max_data_size] - }).collect(); - - let mut coding_locks: Vec<_> = coding_blobs.iter().map(|b| b.write().unwrap()).collect(); - - let mut coding_ptrs: Vec<_> = coding_locks - .iter_mut() - .enumerate() - .map(|(i, l)| { - trace!("{} i: {} coding: {}", id, i, l.data[0],); - &mut l.data_mut()[..max_data_size] - }).collect(); - - generate_coding_blocks(coding_ptrs.as_mut_slice(), &data_ptrs)?; - debug!( - "{} start_idx: {} data: {}:{} coding: {}:{}", - id, start_idx, block_start, block_end, coding_start, block_end - ); - block_start = block_end; - } - Ok(()) -} - -// examine the window slot at idx returns -// true if slot is empty -// true if slot is stale (i.e. has the wrong index), old blob is flushed -// false if slot has a blob with the right index -fn is_missing(id: &Pubkey, idx: u64, window_slot: &mut Option, c_or_d: &str) -> bool { - if let Some(blob) = window_slot.take() { - let blob_idx = blob.read().unwrap().index().unwrap(); - if blob_idx == idx { - trace!("recover {}: idx: {} good {}", id, idx, c_or_d); - // put it back - mem::replace(window_slot, Some(blob)); - false - } else { - trace!( - "recover {}: idx: {} old {} {}, recycling", - id, - idx, - c_or_d, - blob_idx, - ); - true - } - } else { - trace!("recover {}: idx: {} None {}", id, idx, c_or_d); - // nothing there - true - } -} - -// examine the window beginning at block_start for missing or -// stale (based on block_start_idx) blobs -// if a blob is stale, remove it from the window slot -// side effect: block will be cleaned of old blobs -fn find_missing( - id: &Pubkey, - block_start_idx: u64, - block_start: usize, - window: &mut [WindowSlot], -) -> (usize, usize) { - let mut data_missing = 0; - let mut coding_missing = 0; - let block_end = block_start + NUM_DATA; - let coding_start = block_start + NUM_DATA - NUM_CODING; - - // count missing blobs in the block - for i in block_start..block_end { - let idx = (i - block_start) as u64 + block_start_idx; - let n = i % window.len(); - - if is_missing(id, idx, &mut window[n].data, "data") { - data_missing += 1; - } - - if i >= coding_start && is_missing(id, idx, &mut window[n].coding, "coding") { - coding_missing += 1; - } - } - (data_missing, coding_missing) -} - -// Recover a missing block into window -// missing blocks should be None or old... -// If not enough coding or data blocks are present to restore -// any of the blocks, the block is skipped. -// Side effect: old blobs in a block are None'd -pub fn recover(id: &Pubkey, window: &mut [WindowSlot], start_idx: u64, start: usize) -> Result<()> { - let block_start = start - (start % NUM_DATA); - let block_start_idx = start_idx - (start_idx % NUM_DATA as u64); - - debug!("start: {} block_start: {}", start, block_start); - - let coding_start = block_start + NUM_DATA - NUM_CODING; - let block_end = block_start + NUM_DATA; - trace!( - "recover {}: block_start_idx: {} block_start: {} coding_start: {} block_end: {}", - id, - block_start_idx, - block_start, - coding_start, - block_end - ); - - let (data_missing, coding_missing) = find_missing(id, block_start_idx, block_start, window); - - // if we're not missing data, or if we have too much missin but have enough coding - if data_missing == 0 { - // nothing to do... - return Ok(()); - } - - if (data_missing + coding_missing) > NUM_CODING { - trace!( - "recover {}: start: {} skipping recovery data: {} coding: {}", - id, - block_start, - data_missing, - coding_missing - ); - // nothing to do... - return Err(ErasureError::NotEnoughBlocksToDecode); - } - - trace!( - "recover {}: recovering: data: {} coding: {}", - id, - data_missing, - coding_missing - ); - let mut blobs: Vec = Vec::with_capacity(NUM_DATA + NUM_CODING); - let mut locks = Vec::with_capacity(NUM_DATA + NUM_CODING); - let mut erasures: Vec = Vec::with_capacity(NUM_CODING); - let mut meta = None; - let mut size = None; - - // add the data blobs we have into recovery blob vector - for i in block_start..block_end { - let j = i % window.len(); - - if let Some(b) = window[j].data.clone() { - if meta.is_none() { - meta = Some(b.read().unwrap().meta.clone()); - trace!("recover {} meta at {} {:?}", id, j, meta); - } - blobs.push(b); - } else { - let n = SharedBlob::default(); - window[j].data = Some(n.clone()); - // mark the missing memory - blobs.push(n); - erasures.push((i - block_start) as i32); - } - } - for i in coding_start..block_end { - let j = i % window.len(); - if let Some(b) = window[j].coding.clone() { - if size.is_none() { - size = Some(b.read().unwrap().meta.size - BLOB_HEADER_SIZE); - trace!( - "{} recover size {} from {}", - id, - size.unwrap(), - i as u64 + block_start_idx - ); - } - blobs.push(b); - } else { - let n = SharedBlob::default(); - window[j].coding = Some(n.clone()); - //mark the missing memory - blobs.push(n); - erasures.push(((i - coding_start) + NUM_DATA) as i32); - } - } - - // now that we have size (from coding), zero out data blob tails - let size = size.unwrap(); - for i in block_start..block_end { - let j = i % window.len(); - - if let Some(b) = &window[j].data { - let mut b_wl = b.write().unwrap(); - for i in b_wl.meta.size..size { - b_wl.data[i] = 0; - } - } - } - - // marks end of erasures - erasures.push(-1); - trace!("erasures[]: {} {:?} data_size: {}", id, erasures, size,); - //lock everything for write - for b in &blobs { - locks.push(b.write().unwrap()); - } - - { - let mut coding_ptrs: Vec<&mut [u8]> = Vec::with_capacity(NUM_CODING); - let mut data_ptrs: Vec<&mut [u8]> = Vec::with_capacity(NUM_DATA); - for (i, l) in locks.iter_mut().enumerate() { - if i < NUM_DATA { - trace!("{} pushing data: {}", id, i); - data_ptrs.push(&mut l.data[..size]); - } else { - trace!("{} pushing coding: {}", id, i); - coding_ptrs.push(&mut l.data_mut()[..size]); - } - } - trace!( - "{} coding_ptrs.len: {} data_ptrs.len {}", - id, - coding_ptrs.len(), - data_ptrs.len() - ); - decode_blocks( - data_ptrs.as_mut_slice(), - coding_ptrs.as_mut_slice(), - &erasures, - )?; - } - - let meta = meta.unwrap(); - let mut corrupt = false; - // repopulate header data size from recovered blob contents - for i in &erasures[..erasures.len() - 1] { - let n = *i as usize; - let mut idx = n as u64 + block_start_idx; - - let mut data_size; - if n < NUM_DATA { - data_size = locks[n].data_size().unwrap() as usize; - data_size -= BLOB_HEADER_SIZE; - if data_size > BLOB_DATA_SIZE { - error!("{} corrupt data blob[{}] data_size: {}", id, idx, data_size); - corrupt = true; - } - } else { - data_size = size; - idx -= NUM_CODING as u64; - locks[n].set_index(idx).unwrap(); - - if data_size - BLOB_HEADER_SIZE > BLOB_DATA_SIZE { - error!( - "{} corrupt coding blob[{}] data_size: {}", - id, idx, data_size - ); - corrupt = true; - } - } - - locks[n].meta = meta.clone(); - locks[n].set_size(data_size); - trace!( - "{} erasures[{}] ({}) size: {} data[0]: {}", - id, - *i, - idx, - data_size, - locks[n].data()[0] - ); - } - assert!(!corrupt, " {} ", id); - - Ok(()) -} - -#[cfg(test)] -mod test { - use erasure; - use logger; - use packet::{index_blobs, SharedBlob, BLOB_DATA_SIZE, BLOB_HEADER_SIZE, BLOB_SIZE}; - use rand::{thread_rng, Rng}; - use signature::{Keypair, KeypairUtil}; - use solana_sdk::pubkey::Pubkey; - // use std::sync::{Arc, RwLock}; - use window::WindowSlot; - - #[test] - pub fn test_coding() { - let zero_vec = vec![0; 16]; - let mut vs: Vec> = (0..4).map(|i| (i..(16 + i)).collect()).collect(); - let v_orig: Vec = vs[0].clone(); - - let m = 2; - let mut coding_blocks: Vec<_> = (0..m).map(|_| vec![0u8; 16]).collect(); - - { - let mut coding_blocks_slices: Vec<_> = - coding_blocks.iter_mut().map(|x| x.as_mut_slice()).collect(); - let v_slices: Vec<_> = vs.iter().map(|x| x.as_slice()).collect(); - - assert!( - erasure::generate_coding_blocks( - coding_blocks_slices.as_mut_slice(), - v_slices.as_slice(), - ).is_ok() - ); - } - trace!("coding blocks:"); - for b in &coding_blocks { - trace!("{:?}", b); - } - let erasure: i32 = 1; - let erasures = vec![erasure, -1]; - // clear an entry - vs[erasure as usize].copy_from_slice(zero_vec.as_slice()); - - { - let mut coding_blocks_slices: Vec<_> = - coding_blocks.iter_mut().map(|x| x.as_mut_slice()).collect(); - let mut v_slices: Vec<_> = vs.iter_mut().map(|x| x.as_mut_slice()).collect(); - - assert!( - erasure::decode_blocks( - v_slices.as_mut_slice(), - coding_blocks_slices.as_mut_slice(), - erasures.as_slice(), - ).is_ok() - ); - } - - trace!("vs:"); - for v in &vs { - trace!("{:?}", v); - } - assert_eq!(v_orig, vs[0]); - } - - fn print_window(window: &[WindowSlot]) { - for (i, w) in window.iter().enumerate() { - print!("window({:>w$}): ", i, w = 2); - if w.data.is_some() { - let window_l1 = w.data.clone().unwrap(); - let window_l2 = window_l1.read().unwrap(); - print!( - "data index: {:?} meta.size: {} data: ", - window_l2.index(), - window_l2.meta.size - ); - for i in 0..64 { - print!("{:>w$} ", window_l2.data()[i], w = 3); - } - } else { - print!("data null "); - } - println!(); - print!("window({:>w$}): ", i, w = 2); - if w.coding.is_some() { - let window_l1 = w.coding.clone().unwrap(); - let window_l2 = window_l1.read().unwrap(); - print!( - "coding index: {:?} meta.size: {} data: ", - window_l2.index(), - window_l2.meta.size - ); - for i in 0..8 { - print!("{:>w$} ", window_l2.data()[i], w = 3); - } - } else { - print!("coding null"); - } - println!(); - } - } - - const WINDOW_SIZE: usize = 64; - fn generate_window(offset: usize, num_blobs: usize) -> Vec { - let mut window = vec![ - WindowSlot { - data: None, - coding: None, - leader_unknown: false, - }; - WINDOW_SIZE - ]; - let mut blobs = Vec::with_capacity(num_blobs); - for i in 0..num_blobs { - let b = SharedBlob::default(); - let b_ = b.clone(); - let mut w = b.write().unwrap(); - // generate a random length, multiple of 4 between 8 and 32 - let data_len = if i == 3 { - BLOB_DATA_SIZE - } else { - (thread_rng().gen_range(2, 8) * 4) + 1 - }; - - eprintln!("data_len of {} is {}", i, data_len); - w.set_size(data_len); - - for k in 0..data_len { - w.data_mut()[k] = (k + i) as u8; - } - - // overfill, simulates re-used blobs - for i in BLOB_HEADER_SIZE + data_len..BLOB_SIZE { - w.data[i] = thread_rng().gen(); - } - - blobs.push(b_); - } - - index_blobs(&blobs, &Keypair::new().pubkey(), offset as u64, 13); - for b in blobs { - let idx = b.read().unwrap().index().unwrap() as usize % WINDOW_SIZE; - - window[idx].data = Some(b); - } - window - } - - fn scramble_window_tails(window: &mut [WindowSlot], num_blobs: usize) { - for i in 0..num_blobs { - if let Some(b) = &window[i].data { - let size = { - let b_l = b.read().unwrap(); - b_l.meta.size - } as usize; - - let mut b_l = b.write().unwrap(); - for i in size..BLOB_SIZE { - b_l.data[i] = thread_rng().gen(); - } - } - } - } - - #[test] - pub fn test_window_recover_basic() { - logger::setup(); - // Generate a window - let offset = 0; - let num_blobs = erasure::NUM_DATA + 2; - let mut window = generate_window(WINDOW_SIZE, num_blobs); - - for slot in &window { - if let Some(blob) = &slot.data { - let blob_r = blob.read().unwrap(); - assert!(!blob_r.is_coding()); - } - } - - println!("** after-gen-window:"); - print_window(&window); - - // Generate the coding blocks - let mut index = (erasure::NUM_DATA + 2) as u64; - let id = Pubkey::default(); - assert!( - erasure::generate_coding(&id, &mut window, offset as u64, num_blobs, &mut index) - .is_ok() - ); - assert_eq!(index, (erasure::NUM_DATA - erasure::NUM_CODING) as u64); - - println!("** after-gen-coding:"); - print_window(&window); - - println!("** whack data block:"); - // test erasing a data block - let erase_offset = offset; - // Create a hole in the window - let refwindow = window[erase_offset].data.clone(); - window[erase_offset].data = None; - print_window(&window); - - // put junk in the tails, simulates re-used blobs - scramble_window_tails(&mut window, num_blobs); - - // Recover it from coding - assert!(erasure::recover(&id, &mut window, (offset + WINDOW_SIZE) as u64, offset,).is_ok()); - println!("** after-recover:"); - print_window(&window); - - { - // Check the result, block is here to drop locks - - let window_l = window[erase_offset].data.clone().unwrap(); - let window_l2 = window_l.read().unwrap(); - let ref_l = refwindow.clone().unwrap(); - let ref_l2 = ref_l.read().unwrap(); - - assert_eq!(window_l2.meta.size, ref_l2.meta.size); - assert_eq!( - window_l2.data[..window_l2.meta.size], - ref_l2.data[..window_l2.meta.size] - ); - assert_eq!(window_l2.meta.addr, ref_l2.meta.addr); - assert_eq!(window_l2.meta.port, ref_l2.meta.port); - assert_eq!(window_l2.meta.v6, ref_l2.meta.v6); - assert_eq!( - window_l2.index().unwrap(), - (erase_offset + WINDOW_SIZE) as u64 - ); - } - - println!("** whack coding block and data block"); - // tests erasing a coding block and a data block - let erase_offset = offset + erasure::NUM_DATA - erasure::NUM_CODING; - // Create a hole in the window - let refwindow = window[erase_offset].data.clone(); - window[erase_offset].data = None; - window[erase_offset].coding = None; - - print_window(&window); - - // Recover it from coding - assert!(erasure::recover(&id, &mut window, (offset + WINDOW_SIZE) as u64, offset,).is_ok()); - println!("** after-recover:"); - print_window(&window); - - { - // Check the result, block is here to drop locks - let window_l = window[erase_offset].data.clone().unwrap(); - let window_l2 = window_l.read().unwrap(); - let ref_l = refwindow.clone().unwrap(); - let ref_l2 = ref_l.read().unwrap(); - assert_eq!(window_l2.meta.size, ref_l2.meta.size); - assert_eq!( - window_l2.data[..window_l2.meta.size], - ref_l2.data[..window_l2.meta.size] - ); - assert_eq!(window_l2.meta.addr, ref_l2.meta.addr); - assert_eq!(window_l2.meta.port, ref_l2.meta.port); - assert_eq!(window_l2.meta.v6, ref_l2.meta.v6); - assert_eq!( - window_l2.index().unwrap(), - (erase_offset + WINDOW_SIZE) as u64 - ); - } - - println!("** make stale data block index"); - // tests erasing a coding block - let erase_offset = offset; - // Create a hole in the window by making the blob's index stale - let refwindow = window[offset].data.clone(); - if let Some(blob) = &window[erase_offset].data { - blob.write() - .unwrap() - .set_index(erase_offset as u64) - .unwrap(); // this also writes to refwindow... - } - print_window(&window); - - // Recover it from coding - assert!(erasure::recover(&id, &mut window, (offset + WINDOW_SIZE) as u64, offset,).is_ok()); - println!("** after-recover:"); - print_window(&window); - - // fix refwindow, we wrote to it above... - if let Some(blob) = &refwindow { - blob.write() - .unwrap() - .set_index((erase_offset + WINDOW_SIZE) as u64) - .unwrap(); // this also writes to refwindow... - } - - { - // Check the result, block is here to drop locks - let window_l = window[erase_offset].data.clone().unwrap(); - let window_l2 = window_l.read().unwrap(); - let ref_l = refwindow.clone().unwrap(); - let ref_l2 = ref_l.read().unwrap(); - assert_eq!(window_l2.meta.size, ref_l2.meta.size); - assert_eq!( - window_l2.data[..window_l2.meta.size], - ref_l2.data[..window_l2.meta.size] - ); - assert_eq!(window_l2.index().unwrap(), ref_l2.index().unwrap()); - assert_eq!(window_l2.slot().unwrap(), ref_l2.slot().unwrap()); - assert_eq!(window_l2.meta.addr, ref_l2.meta.addr); - assert_eq!(window_l2.meta.port, ref_l2.meta.port); - assert_eq!(window_l2.meta.v6, ref_l2.meta.v6); - assert_eq!( - window_l2.index().unwrap(), - (erase_offset + WINDOW_SIZE) as u64 - ); - } - } - - // //TODO This needs to be reworked - // #[test] - // #[ignore] - // pub fn test_window_recover() { - // logger::setup(); - // let offset = 4; - // let data_len = 16; - // let num_blobs = erasure::NUM_DATA + 2; - // let (mut window, blobs_len) = generate_window(data_len, offset, num_blobs); - // println!("** after-gen:"); - // print_window(&window); - // assert!(erasure::generate_coding(&mut window, offset, blobs_len).is_ok()); - // println!("** after-coding:"); - // print_window(&window); - // let refwindow = window[offset + 1].clone(); - // window[offset + 1] = None; - // window[offset + 2] = None; - // window[offset + erasure::SET_SIZE + 3] = None; - // window[offset + (2 * erasure::SET_SIZE) + 0] = None; - // window[offset + (2 * erasure::SET_SIZE) + 1] = None; - // window[offset + (2 * erasure::SET_SIZE) + 2] = None; - // let window_l0 = &(window[offset + (3 * erasure::SET_SIZE)]).clone().unwrap(); - // window_l0.write().unwrap().data[0] = 55; - // println!("** after-nulling:"); - // print_window(&window); - // assert!(erasure::recover(&mut window, offset, offset + blobs_len).is_ok()); - // println!("** after-restore:"); - // print_window(&window); - // let window_l = window[offset + 1].clone().unwrap(); - // let ref_l = refwindow.clone().unwrap(); - // assert_eq!( - // window_l.read().unwrap().data()[..data_len], - // ref_l.read().unwrap().data()[..data_len] - // ); - // } -} diff --git a/book/favicon.png b/book/favicon.png deleted file mode 100644 index a5b1aa16c4dcb6c872cb5af799bfc9b5552c7b9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5679 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 A;{X5v diff --git a/book/fetch_stage.rs b/book/fetch_stage.rs deleted file mode 100644 index 34d6dc3f073434..00000000000000 --- a/book/fetch_stage.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! The `fetch_stage` batches input from a UDP socket and sends it to a channel. - -use service::Service; -use std::net::UdpSocket; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc::channel; -use std::sync::Arc; -use std::thread::{self, JoinHandle}; -use streamer::{self, PacketReceiver}; - -pub struct FetchStage { - exit: Arc, - thread_hdls: Vec>, -} - -impl FetchStage { - pub fn new(sockets: Vec, exit: Arc) -> (Self, PacketReceiver) { - let tx_sockets = sockets.into_iter().map(Arc::new).collect(); - Self::new_multi_socket(tx_sockets, exit) - } - pub fn new_multi_socket( - sockets: Vec>, - exit: Arc, - ) -> (Self, PacketReceiver) { - let (sender, receiver) = channel(); - let thread_hdls: Vec<_> = sockets - .into_iter() - .map(|socket| streamer::receiver(socket, exit.clone(), sender.clone(), "fetch-stage")) - .collect(); - - (FetchStage { exit, thread_hdls }, receiver) - } - - pub fn close(&self) { - self.exit.store(true, Ordering::Relaxed); - } -} - -impl Service for FetchStage { - type JoinReturnType = (); - - fn join(self) -> thread::Result<()> { - for thread_hdl in self.thread_hdls { - thread_hdl.join()?; - } - Ok(()) - } -} diff --git a/book/fullnode.html b/book/fullnode.html deleted file mode 100644 index 950d6f1aa4a6f4..00000000000000 --- a/book/fullnode.html +++ /dev/null @@ -1,223 +0,0 @@ - - - - - - Fullnode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

Fullnode

-

Fullnode block diagrams

-

Pipelining

-

The fullnodes make extensive use of an optimization common in CPU design, -called pipelining. Pipelining is the right tool for the job when there's a -stream of input data that needs to be processed by a sequence of steps, and -there's different hardware responsible for each. The quintessential example is -using a washer and dryer to wash/dry/fold several loads of laundry. Washing -must occur before drying and drying before folding, but each of the three -operations is performed by a separate unit. To maximize efficiency, one creates -a pipeline of stages. We'll call the washer one stage, the dryer another, and -the folding process a third. To run the pipeline, one adds a second load of -laundry to the washer just after the first load is added to the dryer. -Likewise, the third load is added to the washer after the second is in the -dryer and the first is being folded. In this way, one can make progress on -three loads of laundry simultaneously. Given infinite loads, the pipeline will -consistently complete a load at the rate of the slowest stage in the pipeline.

-

Pipelining in the fullnode

-

The fullnode contains two pipelined processes, one used in leader mode called -the Tpu and one used in validator mode called the Tvu. In both cases, the -hardware being pipelined is the same, the network input, the GPU cards, the CPU -cores, writes to disk, and the network output. What it does with that hardware -is different. The Tpu exists to create ledger entries whereas the Tvu exists -to validate them.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/fullnode.rs b/book/fullnode.rs deleted file mode 100644 index 4feddd3b2ddd8f..00000000000000 --- a/book/fullnode.rs +++ /dev/null @@ -1,1040 +0,0 @@ -//! The `fullnode` module hosts all the fullnode microservices. - -use bank::Bank; -use broadcast_stage::BroadcastStage; -use cluster_info::{ClusterInfo, Node, NodeInfo}; -use leader_scheduler::LeaderScheduler; -use ledger::read_ledger; -use ncp::Ncp; -use rpc::JsonRpcService; -use rpc_pubsub::PubSubService; -use service::Service; -use signature::{Keypair, KeypairUtil}; -use solana_sdk::hash::Hash; -use solana_sdk::timing::timestamp; -use std::net::UdpSocket; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, RwLock}; -use std::thread::Result; -use tpu::{Tpu, TpuReturnType}; -use tpu_forwarder::TpuForwarder; -use tvu::{Tvu, TvuReturnType}; -use untrusted::Input; -use window::{new_window, SharedWindow}; - -pub enum NodeRole { - Leader(LeaderServices), - Validator(ValidatorServices), -} - -pub struct LeaderServices { - tpu: Tpu, - broadcast_stage: BroadcastStage, -} - -impl LeaderServices { - fn new(tpu: Tpu, broadcast_stage: BroadcastStage) -> Self { - LeaderServices { - tpu, - broadcast_stage, - } - } - - pub fn join(self) -> Result> { - self.broadcast_stage.join()?; - self.tpu.join() - } - - pub fn is_exited(&self) -> bool { - self.tpu.is_exited() - } - - pub fn exit(&self) -> () { - self.tpu.exit(); - } -} - -pub struct ValidatorServices { - tvu: Tvu, - tpu_forwarder: TpuForwarder, -} - -impl ValidatorServices { - fn new(tvu: Tvu, tpu_forwarder: TpuForwarder) -> Self { - ValidatorServices { tvu, tpu_forwarder } - } - - pub fn join(self) -> Result> { - let ret = self.tvu.join(); // TVU calls the shots, we wait for it to shut down - self.tpu_forwarder.join()?; - ret - } - - pub fn is_exited(&self) -> bool { - self.tvu.is_exited() - } - - pub fn exit(&self) -> () { - self.tvu.exit() - } -} - -#[derive(Debug)] -pub enum FullnodeReturnType { - LeaderToValidatorRotation, - ValidatorToLeaderRotation, -} - -pub struct Fullnode { - pub node_role: Option, - keypair: Arc, - vote_account_keypair: Arc, - exit: Arc, - rpc_service: Option, - rpc_pubsub_service: Option, - ncp: Ncp, - bank: Arc, - cluster_info: Arc>, - ledger_path: String, - sigverify_disabled: bool, - shared_window: SharedWindow, - replicate_socket: Vec, - repair_socket: UdpSocket, - retransmit_socket: UdpSocket, - transaction_sockets: Vec, - broadcast_socket: UdpSocket, - rpc_addr: SocketAddr, - rpc_pubsub_addr: SocketAddr, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "PascalCase")] -/// Fullnode configuration to be stored in file -pub struct Config { - pub node_info: NodeInfo, - pkcs8: Vec, - vote_account_pkcs8: Vec, -} - -impl Config { - pub fn new(bind_addr: &SocketAddr, pkcs8: Vec, vote_account_pkcs8: Vec) -> Self { - let keypair = - Keypair::from_pkcs8(Input::from(&pkcs8)).expect("from_pkcs8 in fullnode::Config new"); - let pubkey = keypair.pubkey(); - let node_info = NodeInfo::new_with_pubkey_socketaddr(pubkey, bind_addr); - Config { - node_info, - pkcs8, - vote_account_pkcs8, - } - } - pub fn keypair(&self) -> Keypair { - Keypair::from_pkcs8(Input::from(&self.pkcs8)) - .expect("from_pkcs8 in fullnode::Config keypair") - } - pub fn vote_account_keypair(&self) -> Keypair { - Keypair::from_pkcs8(Input::from(&self.vote_account_pkcs8)) - .expect("from_pkcs8 in fullnode::Config vote_account_keypair") - } -} - -impl Fullnode { - pub fn new( - node: Node, - ledger_path: &str, - keypair: Arc, - vote_account_keypair: Arc, - leader_addr: Option, - sigverify_disabled: bool, - leader_scheduler: LeaderScheduler, - rpc_port: Option, - ) -> Self { - let leader_scheduler = Arc::new(RwLock::new(leader_scheduler)); - - info!("creating bank..."); - - let (bank, entry_height, last_entry_id) = - Self::new_bank_from_ledger(ledger_path, leader_scheduler); - - info!("creating networking stack..."); - let local_gossip_addr = node.sockets.gossip.local_addr().unwrap(); - - info!( - "starting... local gossip address: {} (advertising {})", - local_gossip_addr, node.info.ncp - ); - let mut rpc_addr = node.info.rpc; - if let Some(port) = rpc_port { - rpc_addr.set_port(port); - } - - let leader_info = leader_addr.map(|i| NodeInfo::new_entry_point(&i)); - let server = Self::new_with_bank( - keypair, - vote_account_keypair, - bank, - entry_height, - &last_entry_id, - node, - leader_info.as_ref(), - ledger_path, - sigverify_disabled, - rpc_port, - ); - - match leader_addr { - Some(leader_addr) => { - info!( - "validator ready... rpc address: {}, connected to: {}", - rpc_addr, leader_addr - ); - } - None => { - info!("leader ready... rpc address: {}", rpc_addr); - } - } - - server - } - - /// Create a fullnode instance acting as a leader or validator. - #[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] - pub fn new_with_bank( - keypair: Arc, - vote_account_keypair: Arc, - bank: Bank, - entry_height: u64, - last_entry_id: &Hash, - mut node: Node, - bootstrap_leader_info_option: Option<&NodeInfo>, - ledger_path: &str, - sigverify_disabled: bool, - rpc_port: Option, - ) -> Self { - let mut rpc_addr = node.info.rpc; - let mut rpc_pubsub_addr = node.info.rpc_pubsub; - // Use custom RPC port, if provided (`Some(port)`) - // RPC port may be any valid open port on the node - // If rpc_port == `None`, node will listen on the ports set in NodeInfo - if let Some(port) = rpc_port { - rpc_addr.set_port(port); - node.info.rpc = rpc_addr; - rpc_pubsub_addr.set_port(port + 1); - node.info.rpc_pubsub = rpc_pubsub_addr; - } - - let exit = Arc::new(AtomicBool::new(false)); - let bank = Arc::new(bank); - - let window = new_window(32 * 1024); - let shared_window = Arc::new(RwLock::new(window)); - node.info.wallclock = timestamp(); - let cluster_info = Arc::new(RwLock::new(ClusterInfo::new(node.info))); - - let (rpc_service, rpc_pubsub_service) = - Self::startup_rpc_services(rpc_addr, rpc_pubsub_addr, &bank, &cluster_info); - - let ncp = Ncp::new( - &cluster_info, - shared_window.clone(), - Some(ledger_path), - node.sockets.gossip, - exit.clone(), - ); - - // Insert the bootstrap leader info, should only be None if this node - // is the bootstrap leader - if let Some(bootstrap_leader_info) = bootstrap_leader_info_option { - cluster_info - .write() - .unwrap() - .insert_info(bootstrap_leader_info.clone()); - } - - // Get the scheduled leader - let (scheduled_leader, leader_slot) = bank - .get_current_leader() - .expect("Leader not known after processing bank"); - - cluster_info.write().unwrap().set_leader(scheduled_leader); - let node_role = if scheduled_leader != keypair.pubkey() { - // Start in validator mode. - let tvu = Tvu::new( - keypair.clone(), - vote_account_keypair.clone(), - &bank, - entry_height, - *last_entry_id, - cluster_info.clone(), - shared_window.clone(), - node.sockets - .replicate - .iter() - .map(|s| s.try_clone().expect("Failed to clone replicate sockets")) - .collect(), - node.sockets - .repair - .try_clone() - .expect("Failed to clone repair socket"), - node.sockets - .retransmit - .try_clone() - .expect("Failed to clone retransmit socket"), - Some(ledger_path), - ); - let tpu_forwarder = TpuForwarder::new( - node.sockets - .transaction - .iter() - .map(|s| s.try_clone().expect("Failed to clone transaction sockets")) - .collect(), - cluster_info.clone(), - ); - - let validator_state = ValidatorServices::new(tvu, tpu_forwarder); - Some(NodeRole::Validator(validator_state)) - } else { - let max_tick_height = { - let ls_lock = bank.leader_scheduler.read().unwrap(); - ls_lock.max_height_for_leader(bank.tick_height()) - }; - // Start in leader mode. - let (tpu, entry_receiver, tpu_exit) = Tpu::new( - &bank, - Default::default(), - node.sockets - .transaction - .iter() - .map(|s| s.try_clone().expect("Failed to clone transaction sockets")) - .collect(), - ledger_path, - sigverify_disabled, - max_tick_height, - last_entry_id, - ); - - let broadcast_stage = BroadcastStage::new( - node.sockets - .broadcast - .try_clone() - .expect("Failed to clone broadcast socket"), - cluster_info.clone(), - shared_window.clone(), - entry_height, - leader_slot, - entry_receiver, - max_tick_height, - bank.tick_height(), - tpu_exit, - ); - let leader_state = LeaderServices::new(tpu, broadcast_stage); - Some(NodeRole::Leader(leader_state)) - }; - - Fullnode { - keypair, - vote_account_keypair, - cluster_info, - shared_window, - bank, - sigverify_disabled, - ncp, - rpc_service: Some(rpc_service), - rpc_pubsub_service: Some(rpc_pubsub_service), - node_role, - ledger_path: ledger_path.to_owned(), - exit, - replicate_socket: node.sockets.replicate, - repair_socket: node.sockets.repair, - retransmit_socket: node.sockets.retransmit, - transaction_sockets: node.sockets.transaction, - broadcast_socket: node.sockets.broadcast, - rpc_addr, - rpc_pubsub_addr, - } - } - - fn leader_to_validator(&mut self) -> Result<()> { - // Close down any services that could have a reference to the bank - if self.rpc_service.is_some() { - let old_rpc_service = self.rpc_service.take().unwrap(); - old_rpc_service.close()?; - } - - if self.rpc_pubsub_service.is_some() { - let old_rpc_pubsub_service = self.rpc_pubsub_service.take().unwrap(); - old_rpc_pubsub_service.close()?; - } - - // Correctness check: Ensure that references to the bank and leader scheduler are no - // longer held by any running thread - let mut new_leader_scheduler = self.bank.leader_scheduler.read().unwrap().clone(); - - // Clear the leader scheduler - new_leader_scheduler.reset(); - - let (new_bank, scheduled_leader, entry_height, last_entry_id) = { - // TODO: We can avoid building the bank again once RecordStage is - // integrated with BankingStage - let (new_bank, entry_height, last_id) = Self::new_bank_from_ledger( - &self.ledger_path, - Arc::new(RwLock::new(new_leader_scheduler)), - ); - - let new_bank = Arc::new(new_bank); - let (scheduled_leader, _) = new_bank - .get_current_leader() - .expect("Scheduled leader should exist after rebuilding bank"); - - (new_bank, scheduled_leader, entry_height, last_id) - }; - - self.cluster_info - .write() - .unwrap() - .set_leader(scheduled_leader); - - // Spin up new versions of all the services that relied on the bank, passing in the - // new bank - let (rpc_service, rpc_pubsub_service) = Self::startup_rpc_services( - self.rpc_addr, - self.rpc_pubsub_addr, - &new_bank, - &self.cluster_info, - ); - self.rpc_service = Some(rpc_service); - self.rpc_pubsub_service = Some(rpc_pubsub_service); - self.bank = new_bank; - - // In the rare case that the leader exited on a multiple of seed_rotation_interval - // when the new leader schedule was being generated, and there are no other validators - // in the active set, then the leader scheduler will pick the same leader again, so - // check for that - if scheduled_leader == self.keypair.pubkey() { - let tick_height = self.bank.tick_height(); - self.validator_to_leader(tick_height, entry_height, last_entry_id); - Ok(()) - } else { - let tvu = Tvu::new( - self.keypair.clone(), - self.vote_account_keypair.clone(), - &self.bank, - entry_height, - last_entry_id, - self.cluster_info.clone(), - self.shared_window.clone(), - self.replicate_socket - .iter() - .map(|s| s.try_clone().expect("Failed to clone replicate sockets")) - .collect(), - self.repair_socket - .try_clone() - .expect("Failed to clone repair socket"), - self.retransmit_socket - .try_clone() - .expect("Failed to clone retransmit socket"), - Some(&self.ledger_path), - ); - let tpu_forwarder = TpuForwarder::new( - self.transaction_sockets - .iter() - .map(|s| s.try_clone().expect("Failed to clone transaction sockets")) - .collect(), - self.cluster_info.clone(), - ); - - let validator_state = ValidatorServices::new(tvu, tpu_forwarder); - self.node_role = Some(NodeRole::Validator(validator_state)); - Ok(()) - } - } - - fn validator_to_leader(&mut self, tick_height: u64, entry_height: u64, last_id: Hash) { - self.cluster_info - .write() - .unwrap() - .set_leader(self.keypair.pubkey()); - - let max_tick_height = { - let ls_lock = self.bank.leader_scheduler.read().unwrap(); - ls_lock.max_height_for_leader(tick_height) - }; - - let (tpu, blob_receiver, tpu_exit) = Tpu::new( - &self.bank, - Default::default(), - self.transaction_sockets - .iter() - .map(|s| s.try_clone().expect("Failed to clone transaction sockets")) - .collect(), - &self.ledger_path, - self.sigverify_disabled, - max_tick_height, - // We pass the last_entry_id from the replicate stage because we can't trust that - // the window didn't overwrite the slot at for the last entry that the replicate stage - // processed. We also want to avoid reading processing the ledger for the last id. - &last_id, - ); - - let broadcast_stage = BroadcastStage::new( - self.broadcast_socket - .try_clone() - .expect("Failed to clone broadcast socket"), - self.cluster_info.clone(), - self.shared_window.clone(), - entry_height, - 0, // TODO: get real leader slot from leader_scheduler - blob_receiver, - max_tick_height, - tick_height, - tpu_exit, - ); - let leader_state = LeaderServices::new(tpu, broadcast_stage); - self.node_role = Some(NodeRole::Leader(leader_state)); - } - - pub fn check_role_exited(&self) -> bool { - match self.node_role { - Some(NodeRole::Leader(ref leader_services)) => leader_services.is_exited(), - Some(NodeRole::Validator(ref validator_services)) => validator_services.is_exited(), - None => false, - } - } - - pub fn handle_role_transition(&mut self) -> Result> { - let node_role = self.node_role.take(); - match node_role { - Some(NodeRole::Leader(leader_services)) => match leader_services.join()? { - Some(TpuReturnType::LeaderRotation) => { - self.leader_to_validator()?; - Ok(Some(FullnodeReturnType::LeaderToValidatorRotation)) - } - _ => Ok(None), - }, - Some(NodeRole::Validator(validator_services)) => match validator_services.join()? { - Some(TvuReturnType::LeaderRotation(tick_height, entry_height, last_entry_id)) => { - //TODO: Fix this to return actual poh height. - self.validator_to_leader(tick_height, entry_height, last_entry_id); - Ok(Some(FullnodeReturnType::ValidatorToLeaderRotation)) - } - _ => Ok(None), - }, - None => Ok(None), - } - } - - //used for notifying many nodes in parallel to exit - pub fn exit(&self) { - self.exit.store(true, Ordering::Relaxed); - if let Some(ref rpc_service) = self.rpc_service { - rpc_service.exit(); - } - if let Some(ref rpc_pubsub_service) = self.rpc_pubsub_service { - rpc_pubsub_service.exit(); - } - match self.node_role { - Some(NodeRole::Leader(ref leader_services)) => leader_services.exit(), - Some(NodeRole::Validator(ref validator_services)) => validator_services.exit(), - _ => (), - } - } - - pub fn close(self) -> Result<(Option)> { - self.exit(); - self.join() - } - - pub fn new_bank_from_ledger( - ledger_path: &str, - leader_scheduler: Arc>, - ) -> (Bank, u64, Hash) { - let mut bank = Bank::new_with_builtin_programs(); - bank.leader_scheduler = leader_scheduler; - let entries = read_ledger(ledger_path, true).expect("opening ledger"); - let entries = entries - .map(|e| e.unwrap_or_else(|err| panic!("failed to parse entry. error: {}", err))); - info!("processing ledger..."); - - let (entry_height, last_entry_id) = bank.process_ledger(entries).expect("process_ledger"); - // entry_height is the network-wide agreed height of the ledger. - // initialize it from the input ledger - info!("processed {} ledger...", entry_height); - (bank, entry_height, last_entry_id) - } - - pub fn get_leader_scheduler(&self) -> &Arc> { - &self.bank.leader_scheduler - } - - fn startup_rpc_services( - rpc_addr: SocketAddr, - rpc_pubsub_addr: SocketAddr, - bank: &Arc, - cluster_info: &Arc>, - ) -> (JsonRpcService, PubSubService) { - let rpc_port = rpc_addr.port(); - let rpc_pubsub_port = rpc_pubsub_addr.port(); - // TODO: The RPC service assumes that there is a drone running on the leader - // Drone location/id will need to be handled a different way as soon as leader rotation begins - ( - JsonRpcService::new( - bank, - cluster_info, - SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), rpc_port), - ), - PubSubService::new( - bank, - SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), rpc_pubsub_port), - ), - ) - } -} - -impl Service for Fullnode { - type JoinReturnType = Option; - - fn join(self) -> Result> { - if let Some(rpc_service) = self.rpc_service { - rpc_service.join()?; - } - if let Some(rpc_pubsub_service) = self.rpc_pubsub_service { - rpc_pubsub_service.join()?; - } - - self.ncp.join()?; - - match self.node_role { - Some(NodeRole::Validator(validator_service)) => { - if let Some(TvuReturnType::LeaderRotation(_, _, _)) = validator_service.join()? { - return Ok(Some(FullnodeReturnType::ValidatorToLeaderRotation)); - } - } - Some(NodeRole::Leader(leader_service)) => { - if let Some(TpuReturnType::LeaderRotation) = leader_service.join()? { - return Ok(Some(FullnodeReturnType::LeaderToValidatorRotation)); - } - } - _ => (), - } - - Ok(None) - } -} - -#[cfg(test)] -mod tests { - use bank::Bank; - use cluster_info::Node; - use fullnode::{Fullnode, FullnodeReturnType, NodeRole, TvuReturnType}; - use leader_scheduler::{make_active_set_entries, LeaderScheduler, LeaderSchedulerConfig}; - use ledger::{create_tmp_genesis, create_tmp_sample_ledger, LedgerWriter}; - use packet::make_consecutive_blobs; - use service::Service; - use signature::{Keypair, KeypairUtil}; - use std::cmp; - use std::fs::remove_dir_all; - use std::net::UdpSocket; - use std::sync::mpsc::channel; - use std::sync::{Arc, RwLock}; - use streamer::responder; - - #[test] - fn validator_exit() { - let keypair = Keypair::new(); - let tn = Node::new_localhost_with_pubkey(keypair.pubkey()); - let (mint, validator_ledger_path) = - create_tmp_genesis("validator_exit", 10_000, keypair.pubkey(), 1000); - let mut bank = Bank::new(&mint); - let entry = tn.info.clone(); - let genesis_entries = &mint.create_entries(); - let entry_height = genesis_entries.len() as u64; - - let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( - entry.id, - ))); - bank.leader_scheduler = leader_scheduler; - - let last_id = bank.last_id(); - let v = Fullnode::new_with_bank( - Arc::new(keypair), - Arc::new(Keypair::new()), - bank, - entry_height, - &last_id, - tn, - Some(&entry), - &validator_ledger_path, - false, - None, - ); - v.close().unwrap(); - remove_dir_all(validator_ledger_path).unwrap(); - } - - #[test] - fn validator_parallel_exit() { - let mut ledger_paths = vec![]; - let vals: Vec = (0..2) - .map(|i| { - let keypair = Keypair::new(); - let tn = Node::new_localhost_with_pubkey(keypair.pubkey()); - let (mint, validator_ledger_path) = create_tmp_genesis( - &format!("validator_parallel_exit_{}", i), - 10_000, - keypair.pubkey(), - 1000, - ); - ledger_paths.push(validator_ledger_path.clone()); - let mut bank = Bank::new(&mint); - let entry = tn.info.clone(); - - let leader_scheduler = Arc::new(RwLock::new( - LeaderScheduler::from_bootstrap_leader(entry.id), - )); - bank.leader_scheduler = leader_scheduler; - - let entry_height = mint.create_entries().len() as u64; - let last_id = bank.last_id(); - Fullnode::new_with_bank( - Arc::new(keypair), - Arc::new(Keypair::new()), - bank, - entry_height, - &last_id, - tn, - Some(&entry), - &validator_ledger_path, - false, - None, - ) - }).collect(); - - //each validator can exit in parallel to speed many sequential calls to `join` - vals.iter().for_each(|v| v.exit()); - //while join is called sequentially, the above exit call notified all the - //validators to exit from all their threads - vals.into_iter().for_each(|v| { - v.join().unwrap(); - }); - - for path in ledger_paths { - remove_dir_all(path).unwrap(); - } - } - - #[test] - fn test_leader_to_leader_transition() { - // Create the leader node information - let bootstrap_leader_keypair = Keypair::new(); - let bootstrap_leader_node = - Node::new_localhost_with_pubkey(bootstrap_leader_keypair.pubkey()); - let bootstrap_leader_info = bootstrap_leader_node.info.clone(); - - // Make a mint and a genesis entries for leader ledger - let num_ending_ticks = 1; - let (_, bootstrap_leader_ledger_path, genesis_entries) = create_tmp_sample_ledger( - "test_leader_to_leader_transition", - 10_000, - num_ending_ticks, - bootstrap_leader_keypair.pubkey(), - 500, - ); - - let initial_tick_height = genesis_entries - .iter() - .skip(2) - .fold(0, |tick_count, entry| tick_count + entry.is_tick() as u64); - - // Create the common leader scheduling configuration - let num_slots_per_epoch = 3; - let leader_rotation_interval = 5; - let seed_rotation_interval = num_slots_per_epoch * leader_rotation_interval; - let active_window_length = 5; - - // Set the bootstrap height to be bigger than the initial tick height. - // Once the leader hits the bootstrap height ticks, because there are no other - // choices in the active set, this leader will remain the leader in the next - // epoch. In the next epoch, check that the same leader knows to shut down and - // restart as a leader again. - let bootstrap_height = initial_tick_height + 1; - let leader_scheduler_config = LeaderSchedulerConfig::new( - Some(bootstrap_height as u64), - Some(leader_rotation_interval), - Some(seed_rotation_interval), - Some(active_window_length), - ); - - // Start up the leader - let mut bootstrap_leader = Fullnode::new( - bootstrap_leader_node, - &bootstrap_leader_ledger_path, - Arc::new(bootstrap_leader_keypair), - Arc::new(Keypair::new()), - Some(bootstrap_leader_info.ncp), - false, - LeaderScheduler::new(&leader_scheduler_config), - None, - ); - - // Wait for the leader to transition, ticks should cause the leader to - // reach the height for leader rotation - match bootstrap_leader.handle_role_transition().unwrap() { - Some(FullnodeReturnType::LeaderToValidatorRotation) => (), - _ => { - panic!("Expected a leader transition"); - } - } - - match bootstrap_leader.node_role { - Some(NodeRole::Leader(_)) => (), - _ => { - panic!("Expected bootstrap leader to be a leader"); - } - } - } - - #[test] - fn test_wrong_role_transition() { - // Create the leader node information - let bootstrap_leader_keypair = Arc::new(Keypair::new()); - let bootstrap_leader_node = - Node::new_localhost_with_pubkey(bootstrap_leader_keypair.pubkey()); - let bootstrap_leader_info = bootstrap_leader_node.info.clone(); - - // Create the validator node information - let validator_keypair = Keypair::new(); - let validator_node = Node::new_localhost_with_pubkey(validator_keypair.pubkey()); - - // Make a common mint and a genesis entry for both leader + validator's ledgers - let num_ending_ticks = 1; - let (mint, bootstrap_leader_ledger_path, genesis_entries) = create_tmp_sample_ledger( - "test_wrong_role_transition", - 10_000, - num_ending_ticks, - bootstrap_leader_keypair.pubkey(), - 500, - ); - - let last_id = genesis_entries - .last() - .expect("expected at least one genesis entry") - .id; - - // Write the entries to the ledger that will cause leader rotation - // after the bootstrap height - let mut ledger_writer = LedgerWriter::open(&bootstrap_leader_ledger_path, false).unwrap(); - let (active_set_entries, validator_vote_account_keypair) = make_active_set_entries( - &validator_keypair, - &mint.keypair(), - &last_id, - &last_id, - num_ending_ticks, - ); - - let genesis_tick_height = genesis_entries - .iter() - .skip(2) - .fold(0, |tick_count, entry| tick_count + entry.is_tick() as u64) - + num_ending_ticks as u64; - ledger_writer.write_entries(&active_set_entries).unwrap(); - - // Create the common leader scheduling configuration - let num_slots_per_epoch = 3; - let leader_rotation_interval = 5; - let seed_rotation_interval = num_slots_per_epoch * leader_rotation_interval; - - // Set the bootstrap height exactly the current tick height, so that we can - // test if the bootstrap leader knows to immediately transition to a validator - // after parsing the ledger during startup - let bootstrap_height = genesis_tick_height; - let leader_scheduler_config = LeaderSchedulerConfig::new( - Some(bootstrap_height), - Some(leader_rotation_interval), - Some(seed_rotation_interval), - Some(genesis_tick_height), - ); - - // Test that a node knows to transition to a validator based on parsing the ledger - let leader_vote_account_keypair = Arc::new(Keypair::new()); - let bootstrap_leader = Fullnode::new( - bootstrap_leader_node, - &bootstrap_leader_ledger_path, - bootstrap_leader_keypair, - leader_vote_account_keypair, - Some(bootstrap_leader_info.ncp), - false, - LeaderScheduler::new(&leader_scheduler_config), - None, - ); - - match bootstrap_leader.node_role { - Some(NodeRole::Validator(_)) => (), - _ => { - panic!("Expected bootstrap leader to be a validator"); - } - } - - // Test that a node knows to transition to a leader based on parsing the ledger - let validator = Fullnode::new( - validator_node, - &bootstrap_leader_ledger_path, - Arc::new(validator_keypair), - Arc::new(validator_vote_account_keypair), - Some(bootstrap_leader_info.ncp), - false, - LeaderScheduler::new(&leader_scheduler_config), - None, - ); - - match validator.node_role { - Some(NodeRole::Leader(_)) => (), - _ => { - panic!("Expected node to be the leader"); - } - } - let _ignored = remove_dir_all(&bootstrap_leader_ledger_path); - } - - #[test] - fn test_validator_to_leader_transition() { - // Make a leader identity - let leader_keypair = Keypair::new(); - let leader_node = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - let leader_id = leader_node.info.id; - let leader_ncp = leader_node.info.ncp; - - // Create validator identity - let num_ending_ticks = 1; - let (mint, validator_ledger_path, genesis_entries) = create_tmp_sample_ledger( - "test_validator_to_leader_transition", - 10_000, - num_ending_ticks, - leader_id, - 500, - ); - - let validator_keypair = Keypair::new(); - let validator_node = Node::new_localhost_with_pubkey(validator_keypair.pubkey()); - let validator_info = validator_node.info.clone(); - - let mut last_id = genesis_entries - .last() - .expect("expected at least one genesis entry") - .id; - - // Write two entries so that the validator is in the active set: - // - // 1) Give the validator a nonzero number of tokens - // Write the bootstrap entries to the ledger that will cause leader rotation - // after the bootstrap height - // - // 2) A vote from the validator - let mut ledger_writer = LedgerWriter::open(&validator_ledger_path, false).unwrap(); - let (active_set_entries, validator_vote_account_keypair) = - make_active_set_entries(&validator_keypair, &mint.keypair(), &last_id, &last_id, 0); - let initial_tick_height = genesis_entries - .iter() - .skip(2) - .fold(0, |tick_count, entry| tick_count + entry.is_tick() as u64); - let initial_non_tick_height = genesis_entries.len() as u64 - initial_tick_height; - let active_set_entries_len = active_set_entries.len() as u64; - last_id = active_set_entries.last().unwrap().id; - ledger_writer.write_entries(&active_set_entries).unwrap(); - let ledger_initial_len = genesis_entries.len() as u64 + active_set_entries_len; - - // Set the leader scheduler for the validator - let leader_rotation_interval = 10; - let num_bootstrap_slots = 2; - let bootstrap_height = num_bootstrap_slots * leader_rotation_interval; - - let leader_scheduler_config = LeaderSchedulerConfig::new( - Some(bootstrap_height), - Some(leader_rotation_interval), - Some(leader_rotation_interval * 2), - Some(bootstrap_height), - ); - - // Start the validator - let mut validator = Fullnode::new( - validator_node, - &validator_ledger_path, - Arc::new(validator_keypair), - Arc::new(validator_vote_account_keypair), - Some(leader_ncp), - false, - LeaderScheduler::new(&leader_scheduler_config), - None, - ); - - // Send blobs to the validator from our mock leader - let t_responder = { - let (s_responder, r_responder) = channel(); - let blob_sockets: Vec> = leader_node - .sockets - .replicate - .into_iter() - .map(Arc::new) - .collect(); - - let t_responder = responder( - "test_validator_to_leader_transition", - blob_sockets[0].clone(), - r_responder, - ); - - // Send the blobs out of order, in reverse. Also send an extra - // "extra_blobs" number of blobs to make sure the window stops in the right place. - let extra_blobs = cmp::max(leader_rotation_interval / 3, 1); - let total_blobs_to_send = bootstrap_height + extra_blobs; - let tvu_address = &validator_info.tvu; - let msgs = make_consecutive_blobs( - leader_id, - total_blobs_to_send, - ledger_initial_len, - last_id, - &tvu_address, - ).into_iter() - .rev() - .collect(); - s_responder.send(msgs).expect("send"); - t_responder - }; - - // Wait for validator to shut down tvu - let node_role = validator.node_role.take(); - match node_role { - Some(NodeRole::Validator(validator_services)) => { - let join_result = validator_services - .join() - .expect("Expected successful validator join"); - if let Some(TvuReturnType::LeaderRotation(tick_height, _, _)) = join_result { - assert_eq!(tick_height, bootstrap_height); - } else { - panic!("Expected validator to have exited due to leader rotation"); - } - } - _ => panic!("Role should not be leader"), - } - - // Check the validator ledger for the correct entry + tick heights, we should've - // transitioned after tick_height = bootstrap_height. - let (bank, entry_height, _) = Fullnode::new_bank_from_ledger( - &validator_ledger_path, - Arc::new(RwLock::new(LeaderScheduler::new(&leader_scheduler_config))), - ); - - assert_eq!(bank.tick_height(), bootstrap_height); - assert_eq!( - entry_height, - // Only the first genesis entry has num_hashes = 0, every other entry - // had num_hashes = 1 - bootstrap_height + active_set_entries_len + initial_non_tick_height, - ); - - // Shut down - t_responder.join().expect("responder thread join"); - validator.close().unwrap(); - remove_dir_all(&validator_ledger_path).unwrap(); - } -} diff --git a/book/highlight.css b/book/highlight.css deleted file mode 100644 index c667e3a04449a3..00000000000000 --- a/book/highlight.css +++ /dev/null @@ -1,69 +0,0 @@ -/* Base16 Atelier Dune Light - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Dune Comment */ -.hljs-comment, -.hljs-quote { - color: #AAA; -} - -/* Atelier-Dune Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #d73737; -} - -/* Atelier-Dune Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #b65611; -} - -/* Atelier-Dune Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #60ac39; -} - -/* Atelier-Dune Blue */ -.hljs-title, -.hljs-section { - color: #6684e1; -} - -/* Atelier-Dune Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #b854d4; -} - -.hljs { - display: block; - overflow-x: auto; - background: #f1f1f1; - color: #6e6b5e; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/book/highlight.js b/book/highlight.js deleted file mode 100644 index bb48d118ddd921..00000000000000 --- a/book/highlight.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */ -!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){s+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"
":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("diff",function(e){return{aliases:["patch"],c:[{cN:"meta",r:10,v:[{b:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"comment",v:[{b:/Index: /,e:/$/},{b:/={3,}/,e:/$/},{b:/^\-{3}/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+{3}/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"addition",b:"^\\!",e:"$"}]}});hljs.registerLanguage("nginx",function(e){var r={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+e.UIR}]},b={eW:!0,l:"[a-z/_]+",k:{literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[e.HCM,{cN:"string",c:[e.BE,r],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[r]},{cN:"regexp",c:[e.BE,r],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},r]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s+{",rB:!0,e:"{",c:[{cN:"section",b:e.UIR}],r:0},{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"attribute",b:e.UIR,starts:b}],r:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("objectivec",function(e){var t={cN:"built_in",b:"\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+"},_={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},i=/[a-zA-Z@][a-zA-Z0-9_]*/,n="@interface @class @protocol @implementation";return{aliases:["mm","objc","obj-c"],k:_,l:i,i:""}]}]},{cN:"class",b:"("+n.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:n,l:i,c:[e.UTM]},{b:"\\."+e.UIR,r:0}]}});hljs.registerLanguage("xml",function(s){var e="[A-Za-z0-9\\._:-]+",t={eW:!0,i:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0}]},{cN:"tag",b:"|$)",e:">",k:{name:"style"},c:[t],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"|$)",e:">",k:{name:"script"},c:[t],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}});hljs.registerLanguage("handlebars",function(e){var a={"builtin-name":"each in with if else unless bindattr action collection debugger log outlet template unbound view yield"};return{aliases:["hbs","html.hbs","html.handlebars"],cI:!0,sL:"xml",c:[e.C("{{!(--)?","(--)?}}"),{cN:"template-tag",b:/\{\{[#\/]/,e:/\}\}/,c:[{cN:"name",b:/[a-zA-Z\.-]+/,k:a,starts:{eW:!0,r:0,c:[e.QSM]}}]},{cN:"template-variable",b:/\{\{/,e:/\}\}/,k:a}]}});hljs.registerLanguage("ini",function(e){var b={cN:"string",c:[e.BE],v:[{b:"'''",e:"'''",r:10},{b:'"""',e:'"""',r:10},{b:'"',e:'"'},{b:"'",e:"'"}]};return{aliases:["toml"],cI:!0,i:/\S/,c:[e.C(";","$"),e.HCM,{cN:"section",b:/^\s*\[+/,e:/\]+/},{b:/^[a-z0-9\[\]_-]+\s*=\s*/,e:"$",rB:!0,c:[{cN:"attr",b:/[a-z0-9\[\]_-]+/},{b:/=/,eW:!0,r:0,c:[{cN:"literal",b:/\bon|off|true|false|yes|no\b/},{cN:"variable",v:[{b:/\$[\w\d"][\w\d_]*/},{b:/\$\{(.*?)}/}]},b,{cN:"number",b:/([\+\-]+)?[\d]+_[\d_]+/},e.NM]}]}]}});hljs.registerLanguage("javascript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},c={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,c,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:t,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,c,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:r+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:r,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+r+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:r},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:s}]}]},{b://,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:r}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("python",function(e){var r={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},b={cN:"meta",b:/^(>>>|\.\.\.) /},c={cN:"subst",b:/\{/,e:/\}/,k:r,i:/#/},a={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[b],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[b],r:10},{b:/(fr|rf|f)'''/,e:/'''/,c:[b,c]},{b:/(fr|rf|f)"""/,e:/"""/,c:[b,c]},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},{b:/(fr|rf|f)'/,e:/'/,c:[c]},{b:/(fr|rf|f)"/,e:/"/,c:[c]},e.ASM,e.QSM]},s={cN:"number",r:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},i={cN:"params",b:/\(/,e:/\)/,c:["self",b,s,a]};return c.c=[a,s,b],{aliases:["py","gyp"],k:r,i:/(<\/|->|\?)|=>/,c:[b,s,a,e.HCM,{v:[{cN:"function",bK:"def"},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,i,{b:/->/,eW:!0,k:"None"}]},{cN:"meta",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}});hljs.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"section",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"quote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"^```w*s*$",e:"^```s*$"},{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"string",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"symbol",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:/^\[[^\n]+\]:/,rB:!0,c:[{cN:"symbol",b:/\[/,e:/\]/,eB:!0,eE:!0},{cN:"link",b:/:\s*/,e:/$/,eB:!0}]}]}});hljs.registerLanguage("php",function(e){var c={b:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},i={cN:"meta",b:/<\?(php)?|\?>/},t={cN:"string",c:[e.BE,i],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},a={v:[e.BNM,e.CNM]};return{aliases:["php3","php4","php5","php6"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[e.HCM,e.C("//","$",{c:[i]}),e.C("/\\*","\\*/",{c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:/<<<['"]?\w+['"]?$/,e:/^\w+;?$/,c:[e.BE,{cN:"subst",v:[{b:/\$\w+/},{b:/\{\$/,e:/\}/}]}]},i,{cN:"keyword",b:/\$this\b/},c,{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",c,e.CBCM,t,a]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},t,a]}});hljs.registerLanguage("d",function(e){var t={keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},r="(0|[1-9][\\d_]*)",a="(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)",i="0[bB][01_]+",n="([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)",_="0[xX]"+n,c="([eE][+-]?"+a+")",d="("+a+"(\\.\\d*|"+c+")|\\d+\\."+a+a+"|\\."+r+c+"?)",o="(0[xX]("+n+"\\."+n+"|\\.?"+n+")[pP][+-]?"+a+")",s="("+r+"|"+i+"|"+_+")",l="("+o+"|"+d+")",u="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",b={cN:"number",b:"\\b"+s+"(L|u|U|Lu|LU|uL|UL)?",r:0},f={cN:"number",b:"\\b("+l+"([fF]|L|i|[fF]i|Li)?|"+s+"(i|[fF]i|Li))",r:0},g={cN:"string",b:"'("+u+"|.)",e:"'",i:"."},h={b:u,r:0},p={cN:"string",b:'"',c:[h],e:'"[cwd]?'},m={cN:"string",b:'[rq]"',e:'"[cwd]?',r:5},w={cN:"string",b:"`",e:"`[cwd]?"},N={cN:"string",b:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',r:10},A={cN:"string",b:'q"\\{',e:'\\}"'},F={cN:"meta",b:"^#!",e:"$",r:5},y={cN:"meta",b:"#(line)",e:"$",r:5},L={cN:"keyword",b:"@[a-zA-Z_][a-zA-Z_\\d]*"},v=e.C("\\/\\+","\\+\\/",{c:["self"],r:10});return{l:e.UIR,k:t,c:[e.CLCM,e.CBCM,v,N,p,m,w,A,f,b,g,F,y,L]}});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:n,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})],i:"\\S"},c={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return n.splice(n.length,0,t,c),{c:n,k:i,i:"\\S"}});hljs.registerLanguage("go",function(e){var t={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{aliases:["golang"],k:t,i:"{",e:"}"},n={v:[{b:/\$\d/},{b:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{b:/[\$%@][^\s\w{]/,r:0}]},i=[e.BE,r,n],o=[n,e.HCM,e.C("^\\=\\w","\\=cut",{eW:!0}),s,{cN:"string",c:i,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",r:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",r:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",r:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",r:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",r:5},{b:"qw\\s+q",e:"q",r:5},{b:"'",e:"'",c:[e.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[e.BE]},{b:"{\\w+}",c:[],r:0},{b:"-?\\w+\\s*\\=\\>",c:[],r:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\/\\/|"+e.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",r:0,c:[e.HCM,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",r:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[e.BE],r:0}]},{cN:"function",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",eE:!0,r:5,c:[e.TM]},{b:"-\\w\\b",r:0},{b:"^__DATA__$",e:"^__END__$",sL:"mojolicious",c:[{b:"^@@.*",e:"$",cN:"comment"}]}];return r.c=o,s.c=o,{aliases:["pl","pm"],l:/[\w\.]+/,k:t,c:o}});hljs.registerLanguage("rust",function(e){var t="([ui](8|16|32|64|128|size)|f(32|64))?",r="alignof as be box break const continue crate do else enum extern false fn for if impl in let loop match mod mut offsetof once priv proc pub pure ref return self Self sizeof static struct super trait true type typeof unsafe unsized use virtual while where yield move default",n="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{aliases:["rs"],k:{keyword:r,literal:"true false Some None Ok Err",built_in:n},l:e.IR+"!?",i:""}]}});hljs.registerLanguage("ruby",function(e){var b="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},c={cN:"doctag",b:"@[A-Za-z]+"},a={b:"#<",e:">"},s=[e.C("#","$",{c:[c]}),e.C("^\\=begin","^\\=end",{c:[c],r:10}),e.C("^__END__","\\n$")],n={cN:"subst",b:"#\\{",e:"}",k:r},t={cN:"string",c:[e.BE,n],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{b:/<<(-?)\w+$/,e:/^\s*\w+$/}]},i={cN:"params",b:"\\(",e:"\\)",endsParent:!0,k:r},d=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{b:"<\\s*",c:[{b:"("+e.IR+"::)?"+e.IR}]}].concat(s)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:b}),i].concat(s)},{b:e.IR+"::"},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":(?!\\s)",c:[t,{b:b}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{cN:"params",b:/\|/,e:/\|/,k:r},{b:"("+e.RSR+"|unless)\\s*",k:"unless",c:[a,{cN:"regexp",c:[e.BE,n],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(s),r:0}].concat(s);n.c=d,i.c=d;var l="[>?]>",o="[\\w#]+\\(\\w+\\):\\d+:\\d+>",u="(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>",w=[{b:/^\s*=>/,starts:{e:"$",c:d}},{cN:"meta",b:"^("+l+"|"+o+"|"+u+")",starts:{e:"$",c:d}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,i:/\/\*/,c:s.concat(w).concat(d)}});hljs.registerLanguage("makefile",function(e){var i={cN:"variable",v:[{b:"\\$\\("+e.UIR+"\\)",c:[e.BE]},{b:/\$[@%] *$",rE:!0,c:l.c,e:t.v[0].b},{b:"<%[%=-]?",e:"[%-]?%>",sL:"ruby",eB:!0,eE:!0,r:0},{cN:"type",b:"!!"+e.UIR},{cN:"meta",b:"&"+e.UIR+"$"},{cN:"meta",b:"\\*"+e.UIR+"$"},{cN:"bullet",b:"^ *-",r:0},e.HCM,{bK:b,k:{literal:b}},e.CNM,l]}});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",t={b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/},{b:/\(/,e:/\)/,c:[e.ASM,e.QSM]}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{b:"@",e:"[{;]",i:/:/,c:[{cN:"keyword",b:/\w+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:c,r:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,t]}]}});hljs.registerLanguage("java",function(e){var a="[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",t=a+"(<"+a+"(\\s*,\\s*"+a+")*>)?",r="false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",s="\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",c={cN:"number",b:s,r:0};return{aliases:["jsp"],k:r,i:/<\/|#/,c:[e.C("/\\*\\*","\\*/",{r:0,c:[{b:/\w+@/,r:0},{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return else",r:0},{cN:"function",b:"("+t+"\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:r,c:[{b:e.UIR+"\\s*\\(",rB:!0,r:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:r,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},c,{cN:"meta",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("armasm",function(s){return{cI:!0,aliases:["arm"],l:"\\.?"+s.IR,k:{meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .arm .thumb .code16 .code32 .force_thumb .thumb_func .ltorg ALIAS ALIGN ARM AREA ASSERT ATTR CN CODE CODE16 CODE32 COMMON CP DATA DCB DCD DCDU DCDO DCFD DCFDU DCI DCQ DCQU DCW DCWU DN ELIF ELSE END ENDFUNC ENDIF ENDP ENTRY EQU EXPORT EXPORTAS EXTERN FIELD FILL FUNCTION GBLA GBLL GBLS GET GLOBAL IF IMPORT INCBIN INCLUDE INFO KEEP LCLA LCLL LCLS LTORG MACRO MAP MEND MEXIT NOFP OPT PRESERVE8 PROC QN READONLY RELOC REQUIRE REQUIRE8 RLIST FN ROUT SETA SETL SETS SN SPACE SUBT THUMB THUMBX TTL WHILE WEND ",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 pc lr sp ip sl sb fp a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 f0 f1 f2 f3 f4 f5 f6 f7 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 q0 q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 cpsr_c cpsr_x cpsr_s cpsr_f cpsr_cx cpsr_cxs cpsr_xs cpsr_xsf cpsr_sf cpsr_cxsf spsr_c spsr_x spsr_s spsr_f spsr_cx spsr_cxs spsr_xs spsr_xsf spsr_sf spsr_cxsf s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s30 s31 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 d16 d17 d18 d19 d20 d21 d22 d23 d24 d25 d26 d27 d28 d29 d30 d31 {PC} {VAR} {TRUE} {FALSE} {OPT} {CONFIG} {ENDIAN} {CODESIZE} {CPU} {FPU} {ARCHITECTURE} {PCSTOREOFFSET} {ARMASM_VERSION} {INTER} {ROPI} {RWPI} {SWST} {NOSWST} . @"},c:[{cN:"keyword",b:"\\b(adc|(qd?|sh?|u[qh]?)?add(8|16)?|usada?8|(q|sh?|u[qh]?)?(as|sa)x|and|adrl?|sbc|rs[bc]|asr|b[lx]?|blx|bxj|cbn?z|tb[bh]|bic|bfc|bfi|[su]bfx|bkpt|cdp2?|clz|clrex|cmp|cmn|cpsi[ed]|cps|setend|dbg|dmb|dsb|eor|isb|it[te]{0,3}|lsl|lsr|ror|rrx|ldm(([id][ab])|f[ds])?|ldr((s|ex)?[bhd])?|movt?|mvn|mra|mar|mul|[us]mull|smul[bwt][bt]|smu[as]d|smmul|smmla|mla|umlaal|smlal?([wbt][bt]|d)|mls|smlsl?[ds]|smc|svc|sev|mia([bt]{2}|ph)?|mrr?c2?|mcrr2?|mrs|msr|orr|orn|pkh(tb|bt)|rbit|rev(16|sh)?|sel|[su]sat(16)?|nop|pop|push|rfe([id][ab])?|stm([id][ab])?|str(ex)?[bhd]?|(qd?)?sub|(sh?|q|u[qh]?)?sub(8|16)|[su]xt(a?h|a?b(16)?)|srs([id][ab])?|swpb?|swi|smi|tst|teq|wfe|wfi|yield)(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo)?[sptrx]?",e:"\\s"},s.C("[;@]","$",{r:0}),s.CBCM,s.QSM,{cN:"string",b:"'",e:"[^\\\\]'",r:0},{cN:"title",b:"\\|",e:"\\|",i:"\\n",r:0},{cN:"number",v:[{b:"[#$=]?0x[0-9a-f]+"},{b:"[#$=]?0b[01]+"},{b:"[#$=]\\d+"},{b:"\\b\\d+"}],r:0},{cN:"symbol",v:[{b:"^[a-z_\\.\\$][a-z0-9_\\.\\$]+"},{b:"^\\s*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{b:"[=#]\\w+"}],r:0}]}});hljs.registerLanguage("swift",function(e){var i={keyword:"__COLUMN__ __FILE__ __FUNCTION__ __LINE__ as as! as? associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},t={cN:"type",b:"\\b[A-Z][\\wÀ-ʸ']*",r:0},n=e.C("/\\*","\\*/",{c:["self"]}),r={cN:"subst",b:/\\\(/,e:"\\)",k:i,c:[]},a={cN:"number",b:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",r:0},o=e.inherit(e.QSM,{c:[r,e.BE]});return r.c=[a],{k:i,c:[o,e.CLCM,n,t,a,{cN:"function",bK:"func",e:"{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{b://},{cN:"params",b:/\(/,e:/\)/,endsParent:!0,k:i,c:["self",a,o,e.CBCM,{b:":"}],i:/["']/}],i:/\[|%/},{cN:"class",bK:"struct protocol class extension enum",k:i,e:"\\{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{cN:"meta",b:"(@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain)"},{bK:"import",e:/$/,c:[e.CLCM,n]}]}});hljs.registerLanguage("cpp",function(t){var e={cN:"keyword",b:"\\b[a-z\\d_]*_t\\b"},r={cN:"string",v:[{b:'(u8?|U)?L?"',e:'"',i:"\\n",c:[t.BE]},{b:'(u8?|U)?R"',e:'"',c:[t.BE]},{b:"'\\\\?.",e:"'",i:"."}]},s={cN:"number",v:[{b:"\\b(0b[01']+)"},{b:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{b:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],r:0},i={cN:"meta",b:/#\s*[a-z]+\b/,e:/$/,k:{"meta-keyword":"if else elif endif define undef warning error line pragma ifdef ifndef include"},c:[{b:/\\\n/,r:0},t.inherit(r,{cN:"meta-string"}),{cN:"meta-string",b:/<[^\n>]*>/,e:/$/,i:"\\n"},t.CLCM,t.CBCM]},a=t.IR+"\\s*\\(",c={keyword:"int float while private char catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignof constexpr decltype noexcept static_assert thread_local restrict _Bool complex _Complex _Imaginary atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and or not",built_in:"std string cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr",literal:"true false nullptr NULL"},n=[e,t.CLCM,t.CBCM,s,r];return{aliases:["c","cc","h","c++","h++","hpp"],k:c,i:"",k:c,c:["self",e]},{b:t.IR+"::",k:c},{v:[{b:/=/,e:/;/},{b:/\(/,e:/\)/},{bK:"new throw return else",e:/;/}],k:c,c:n.concat([{b:/\(/,e:/\)/,k:c,c:n.concat(["self"]),r:0}]),r:0},{cN:"function",b:"("+t.IR+"[\\*&\\s]+)+"+a,rB:!0,e:/[{;=]/,eE:!0,k:c,i:/[^\w\s\*&]/,c:[{b:a,rB:!0,c:[t.TM],r:0},{cN:"params",b:/\(/,e:/\)/,k:c,r:0,c:[t.CLCM,t.CBCM,r,s,e]},t.CLCM,t.CBCM,i]},{cN:"class",bK:"class struct",e:/[{;:]/,c:[{b://,c:["self"]},t.TM]}]),exports:{preprocessor:i,strings:r,k:c}}});hljs.registerLanguage("x86asm",function(s){return{cI:!0,l:"[.%]?"+s.IR,k:{keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",built_in:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr",meta:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},c:[s.C(";","$",{r:0}),{cN:"number",v:[{b:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",r:0},{b:"\\$[0-9][0-9A-Fa-f]*",r:0},{b:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{b:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"}]},s.QSM,{cN:"string",v:[{b:"'",e:"[^\\\\]'"},{b:"`",e:"[^\\\\]`"}],r:0},{cN:"symbol",v:[{b:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{b:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],r:0},{cN:"subst",b:"%[0-9]+",r:0},{cN:"subst",b:"%!S+",r:0},{cN:"meta",b:/^\s*\.[\w_-]+/}]}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},s={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/\b-?[a-z\._]+\b/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,s,a,t]}});hljs.registerLanguage("shell",function(s){return{aliases:["console"],c:[{cN:"meta",b:"^\\s{0,3}[\\w\\d\\[\\]()@-]*[>%$#]",starts:{e:"$",sL:"bash"}}]}});hljs.registerLanguage("http",function(e){var t="HTTP/[0-9\\.]+";return{aliases:["https"],i:"\\S",c:[{b:"^"+t,e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{b:"^[A-Z]+ (.*?) "+t+"$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0},{b:t},{cN:"keyword",b:"[A-Z]+"}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{e:"$",r:0}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}});hljs.registerLanguage("cs",function(e){var i={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long nameof object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let on orderby partial remove select set value var where yield",literal:"null false true"},t={cN:"string",b:'@"',e:'"',c:[{b:'""'}]},r=e.inherit(t,{i:/\n/}),a={cN:"subst",b:"{",e:"}",k:i},c=e.inherit(a,{i:/\n/}),n={cN:"string",b:/\$"/,e:'"',i:/\n/,c:[{b:"{{"},{b:"}}"},e.BE,c]},s={cN:"string",b:/\$@"/,e:'"',c:[{b:"{{"},{b:"}}"},{b:'""'},a]},o=e.inherit(s,{i:/\n/,c:[{b:"{{"},{b:"}}"},{b:'""'},c]});a.c=[s,n,t,e.ASM,e.QSM,e.CNM,e.CBCM],c.c=[o,n,r,e.ASM,e.QSM,e.CNM,e.inherit(e.CBCM,{i:/\n/})];var l={v:[s,n,t,e.ASM,e.QSM]},b=e.IR+"(<"+e.IR+"(\\s*,\\s*"+e.IR+")*>)?(\\[\\])?";return{aliases:["csharp"],k:i,i:/::/,c:[e.C("///","$",{rB:!0,c:[{cN:"doctag",v:[{b:"///",r:0},{b:""},{b:""}]}]}),e.CLCM,e.CBCM,{cN:"meta",b:"#",e:"$",k:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},l,e.CNM,{bK:"class interface",e:/[{;=]/,i:/[^\s:]/,c:[e.TM,e.CLCM,e.CBCM]},{bK:"namespace",e:/[{;=]/,i:/[^\s:]/,c:[e.inherit(e.TM,{b:"[a-zA-Z](\\.?\\w)*"}),e.CLCM,e.CBCM]},{cN:"meta",b:"^\\s*\\[",eB:!0,e:"\\]",eE:!0,c:[{cN:"meta-string",b:/"/,e:/"/}]},{bK:"new return throw await else",r:0},{cN:"function",b:"("+b+"\\s+)+"+e.IR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:i,c:[{b:e.IR+"\\s*\\(",rB:!0,c:[e.TM],r:0},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:i,r:0,c:[l,e.CNM,e.CBCM]},e.CLCM,e.CBCM]}]}});hljs.registerLanguage("coffeescript",function(e){var c={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super yield import export from as default await then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",built_in:"npm require console print module global window document"},n="[A-Za-z$_][0-9A-Za-z$_]*",r={cN:"subst",b:/#\{/,e:/}/,k:c},i=[e.BNM,e.inherit(e.CNM,{starts:{e:"(\\s*/)?",r:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/"""/,e:/"""/,c:[e.BE,r]},{b:/"/,e:/"/,c:[e.BE,r]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[r,e.HCM]},{b:"//[gim]*",r:0},{b:/\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/}]},{b:"@"+n},{sL:"javascript",eB:!0,eE:!0,v:[{b:"```",e:"```"},{b:"`",e:"`"}]}];r.c=i;var s=e.inherit(e.TM,{b:n}),t="(\\(.*\\))?\\s*\\B[-=]>",o={cN:"params",b:"\\([^\\(]",rB:!0,c:[{b:/\(/,e:/\)/,k:c,c:["self"].concat(i)}]};return{aliases:["coffee","cson","iced"],k:c,i:/\/\*/,c:i.concat([e.C("###","###"),e.HCM,{cN:"function",b:"^\\s*"+n+"\\s*=\\s*"+t,e:"[-=]>",rB:!0,c:[s,o]},{b:/[:\(,=]\s*/,r:0,c:[{cN:"function",b:t,e:"[-=]>",rB:!0,c:[o]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[s]},s]},{b:n+":",e:":",rB:!0,rE:!0,r:0}])}});hljs.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>{}*#]/,c:[{bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment",e:/;/,eW:!0,l:/[\w\.]+/,k:{keyword:"abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second section securefile security seed segment select self sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null",built_in:"array bigint binary bit blob boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text varchar varying void"},c:[{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[e.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[e.BE]},e.CNM,e.CBCM,t]},e.CBCM,t]}});hljs.registerLanguage("apache",function(e){var r={cN:"number",b:"[\\$%]\\d+"};return{aliases:["apacheconf"],cI:!0,c:[e.HCM,{cN:"section",b:""},{cN:"attribute",b:/\w+/,r:0,k:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,r:0,k:{literal:"on off all"},c:[{cN:"meta",b:"\\s\\[",e:"\\]$"},{cN:"variable",b:"[\\$%]\\{",e:"\\}",c:["self",r]},r,e.QSM]}}],i:/\S/}});hljs.registerLanguage("haskell",function(e){var i={v:[e.C("--","$"),e.C("{-","-}",{c:["self"]})]},a={cN:"meta",b:"{-#",e:"#-}"},l={cN:"meta",b:"^#",e:"$"},c={cN:"type",b:"\\b[A-Z][\\w']*",r:0},n={b:"\\(",e:"\\)",i:'"',c:[a,l,{cN:"type",b:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TM,{b:"[_a-z][\\w']*"}),i]},s={b:"{",e:"}",c:n.c};return{aliases:["hs"],k:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",c:[{bK:"module",e:"where",k:"module where",c:[n,i],i:"\\W\\.|;"},{b:"\\bimport\\b",e:"$",k:"import qualified as hiding",c:[n,i],i:"\\W\\.|;"},{cN:"class",b:"^(\\s*)?(class|instance)\\b",e:"where",k:"class family instance where",c:[c,n,i]},{cN:"class",b:"\\b(data|(new)?type)\\b",e:"$",k:"data family type newtype deriving",c:[a,c,n,s,i]},{bK:"default",e:"$",c:[c,n,i]},{bK:"infix infixl infixr",e:"$",c:[e.CNM,i]},{b:"\\bforeign\\b",e:"$",k:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",c:[c,e.QSM,i]},{cN:"meta",b:"#!\\/usr\\/bin\\/env runhaskell",e:"$"},a,l,e.QSM,e.CNM,c,e.inherit(e.TM,{b:"^[_a-z][\\w']*"}),i,{b:"->|<-"}]}});hljs.registerLanguage("scala",function(e){var t={cN:"meta",b:"@[A-Za-z]+"},a={cN:"subst",v:[{b:"\\$[A-Za-z0-9_]+"},{b:"\\${",e:"}"}]},r={cN:"string",v:[{b:'"',e:'"',i:"\\n",c:[e.BE]},{b:'"""',e:'"""',r:10},{b:'[a-z]+"',e:'"',i:"\\n",c:[e.BE,a]},{cN:"string",b:'[a-z]+"""',e:'"""',c:[a],r:10}]},c={cN:"symbol",b:"'\\w[\\w\\d_]*(?!')"},i={cN:"type",b:"\\b[A-Z][A-Za-z0-9_]*",r:0},s={cN:"title",b:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,r:0},n={cN:"class",bK:"class object trait type",e:/[:={\[\n;]/,eE:!0,c:[{bK:"extends with",r:10},{b:/\[/,e:/\]/,eB:!0,eE:!0,r:0,c:[i]},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,r:0,c:[i]},s]},l={cN:"function",bK:"def",e:/[:={\[(\n;]/,eE:!0,c:[s]};return{k:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},c:[e.CLCM,e.CBCM,r,c,i,l,n,e.CNM,t]}}); \ No newline at end of file diff --git a/book/img/fullnode.svg b/book/img/fullnode.svg deleted file mode 100644 index bed26722138085..00000000000000 --- a/book/img/fullnode.svg +++ /dev/null @@ -1,278 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Client - - - - -Fullnode - - - - -JsonRpcService - - - - -Bank - - - - -Tpu - - - - -Ncp - - - - -Tvu - - - - -Validators - - - diff --git a/book/img/leader-scheduler.svg b/book/img/leader-scheduler.svg deleted file mode 100644 index 3d13d7549b728a..00000000000000 --- a/book/img/leader-scheduler.svg +++ /dev/null @@ -1,330 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -time - - - - -L1 - - - - -L2 - - - - -L3 - - - - -L4 - - - - -L5 - - - - -x - - - - -xx - - - - -E3 - - - - -xx - - - - -E2 - - - - -E4 - - - - -xx - - - - -x - - - - -E5 - - - - -E1 - - - - -xx - - - - -E3' - - - - -xx - - - - -x - - - - -xx - - - - -xx - - - - -validator - - - - -vote(E1) - - - - -vote(E2) - - - - -slash(E3) - - - - -vote(E4) - - - - -hang - - - - -on - - - - -to - - - - -action - - - - -E4 - - - - -and - - - - -E5 - - - - -for - - - - -more... - - - diff --git a/book/img/runtime.svg b/book/img/runtime.svg deleted file mode 100644 index 3e823946d93895..00000000000000 --- a/book/img/runtime.svg +++ /dev/null @@ -1,211 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -sigverify - - - - -load - - - - -data - - - - -lock - - - - -memory - - - - -execute - - - - -validate - - - - -commit - - - - -data - - - - -fee - - - - -unlock - - - - -allocate - - - - -memory - - - - -accounts - - - diff --git a/book/img/sdk-tools.svg b/book/img/sdk-tools.svg deleted file mode 100644 index 629a3feaa35012..00000000000000 --- a/book/img/sdk-tools.svg +++ /dev/null @@ -1,237 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Client - - - - -Verifier - - - - -Loader - - - - -Solana - - - - -LoadAccounts - - - - -Runtime - - - - -Interpreter - - - - -Accounts - - - diff --git a/book/img/tpu.svg b/book/img/tpu.svg deleted file mode 100644 index 794b8c9cd29351..00000000000000 --- a/book/img/tpu.svg +++ /dev/null @@ -1,323 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Clients - - - - -Tpu - - - - -Fetch - - - - -Stage - - - - -SigVerify - - - - -Stage - - - - -PohService - - - - -Banking - - - - -Stage - - - - -Bank - - - - -Write - - - - -Stage - - - - -Ledger - - - - -Validators - - - diff --git a/book/img/tvu.svg b/book/img/tvu.svg deleted file mode 100644 index 6080e033bcaf4e..00000000000000 --- a/book/img/tvu.svg +++ /dev/null @@ -1,323 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Leader - - - - -Tvu - - - - -Blob - - - - -Fetch - - - - -Stage - - - - -Validators - - - - -Retransmit - - - - -Stage - - - - -Replicate - - - - -Stage - - - - -Bank - - - - -Ledger - - - - -Write - - - - -Stage - - - - -Storage - - - - -Stage - - - diff --git a/book/index.html b/book/index.html deleted file mode 100644 index cfe0b3f80c3e3d..00000000000000 --- a/book/index.html +++ /dev/null @@ -1,215 +0,0 @@ - - - - - - Introduction - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

Disclaimer

-

All claims, content, designs, algorithms, estimates, roadmaps, specifications, -and performance measurements described in this project are done with the -author's best effort. It is up to the reader to check and validate their -accuracy and truthfulness. Furthermore nothing in this project constitutes a -solicitation for investment.

-

Introduction

-

This document defines the architecture of Solana, a blockchain built from the -ground up for scale. The goal of the architecture is to demonstrate there -exists a set of software algorithms that in combination, removes software as a -performance bottleneck, allowing transaction throughput to scale proportionally -with network bandwidth. The architecture goes on to satisfy all three desirable -properties of a proper blockchain, that it not only be scalable, but that it is -also secure and decentralized.

-

With this architecture, we calculate a theoretical upper bound of 710 thousand -transactions per second (tps) on a standard gigabit network and 28.4 million -tps on 40 gigabit. In practice, our focus has been on gigabit. We soak-tested -a 150 node permissioned testnet and it is able to maintain a mean transaction -throughput of approximately 200 thousand tps with peaks over 400 thousand.

-

Furthermore, we have found high throughput extends beyond simple payments, and -that this architecture is also able to perform safe, concurrent execution of -programs authored in a general purpose programming language, such as C. We feel -the extension warrants industry focus on an additional performance metric -already common in the CPU industry, millions of instructions per second or -mips. By measuring mips, we see that batching instructions within a transaction -amortizes the cost of signature verification, lifting the maximum theoretical -instruction throughput up to almost exactly that of centralized databases.

-

Lastly, we discuss the relationships between high throughput, security and -transaction fees. Solana's efficient use hardware drives transaction fees into -the ballpark of 1/1000th of a cent. The drop in fees in turn makes certain -denial of service attacks cheaper. We discuss what these attacks look like and -Solana's techniques to defend against them.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/introduction.html b/book/introduction.html deleted file mode 100644 index 215ec423b6c36b..00000000000000 --- a/book/introduction.html +++ /dev/null @@ -1,223 +0,0 @@ - - - - - - Introduction - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

Disclaimer

-

All claims, content, designs, algorithms, estimates, roadmaps, specifications, -and performance measurements described in this project are done with the -author's best effort. It is up to the reader to check and validate their -accuracy and truthfulness. Furthermore nothing in this project constitutes a -solicitation for investment.

-

Introduction

-

This document defines the architecture of Solana, a blockchain built from the -ground up for scale. The goal of the architecture is to demonstrate there -exists a set of software algorithms that in combination, removes software as a -performance bottleneck, allowing transaction throughput to scale proportionally -with network bandwidth. The architecture goes on to satisfy all three desirable -properties of a proper blockchain, that it not only be scalable, but that it is -also secure and decentralized.

-

With this architecture, we calculate a theoretical upper bound of 710 thousand -transactions per second (tps) on a standard gigabit network and 28.4 million -tps on 40 gigabit. In practice, our focus has been on gigabit. We soak-tested -a 150 node permissioned testnet and it is able to maintain a mean transaction -throughput of approximately 200 thousand tps with peaks over 400 thousand.

-

Furthermore, we have found high throughput extends beyond simple payments, and -that this architecture is also able to perform safe, concurrent execution of -programs authored in a general purpose programming language, such as C. We feel -the extension warrants industry focus on an additional performance metric -already common in the CPU industry, millions of instructions per second or -mips. By measuring mips, we see that batching instructions within a transaction -amortizes the cost of signature verification, lifting the maximum theoretical -instruction throughput up to almost exactly that of centralized databases.

-

Lastly, we discuss the relationships between high throughput, security and -transaction fees. Solana's efficient use hardware drives transaction fees into -the ballpark of 1/1000th of a cent. The drop in fees in turn makes certain -denial of service attacks cheaper. We discuss what these attacks look like and -Solana's techniques to defend against them.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/jsonrpc-api.html b/book/jsonrpc-api.html deleted file mode 100644 index fe8d97b100d1dc..00000000000000 --- a/book/jsonrpc-api.html +++ /dev/null @@ -1,514 +0,0 @@ - - - - - - JSON RPC API - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

JSON RPC API

-

Solana nodes accept HTTP requests using the JSON-RPC 2.0 specification.

-

To interact with a Solana node inside a JavaScript application, use the solana-web3.js library, which gives a convenient interface for the RPC methods.

-

RPC HTTP Endpoint

-

Default port: 8899 -eg. http://localhost:8899, http://192.168.1.88:8899

-

RPC PubSub WebSocket Endpoint

-

Default port: 8900 -eg. ws://localhost:8900, http://192.168.1.88:8900

-

Methods

- -

Request Formatting

-

To make a JSON-RPC request, send an HTTP POST request with a Content-Type: application/json header. The JSON request data should contain 4 fields:

-
    -
  • jsonrpc, set to "2.0"
  • -
  • id, a unique client-generated identifying integer
  • -
  • method, a string containing the method to be invoked
  • -
  • params, a JSON array of ordered parameter values
  • -
-

Example using curl:

-
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' 192.168.1.88:8899
-
-

The response output will be a JSON object with the following fields:

-
    -
  • jsonrpc, matching the request specification
  • -
  • id, matching the request identifier
  • -
  • result, requested data or success confirmation
  • -
-

Requests can be sent in batches by sending an array of JSON-RPC request objects as the data for a single POST.

-

Definitions

-
    -
  • Hash: A SHA-256 hash of a chunk of data.
  • -
  • Pubkey: The public key of a Ed25519 key-pair.
  • -
  • Signature: An Ed25519 signature of a chunk of data.
  • -
  • Transaction: A Solana instruction signed by a client key-pair.
  • -
-

JSON RPC API Reference

-

confirmTransaction

-

Returns a transaction receipt

-
Parameters:
-
    -
  • string - Signature of Transaction to confirm, as base-58 encoded string
  • -
-
Results:
-
    -
  • boolean - Transaction status, true if Transaction is confirmed
  • -
-
Example:
-
// Request
-curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"confirmTransaction", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
-
-// Result
-{"jsonrpc":"2.0","result":true,"id":1}
-
-
-

getBalance

-

Returns the balance of the account of provided Pubkey

-
Parameters:
-
    -
  • string - Pubkey of account to query, as base-58 encoded string
  • -
-
Results:
-
    -
  • integer - quantity, as a signed 64-bit integer
  • -
-
Example:
-
// Request
-curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' http://localhost:8899
-
-// Result
-{"jsonrpc":"2.0","result":0,"id":1}
-
-
-

getAccountInfo

-

Returns all information associated with the account of provided Pubkey

-
Parameters:
-
    -
  • string - Pubkey of account to query, as base-58 encoded string
  • -
-
Results:
-

The result field will be a JSON object with the following sub fields:

-
    -
  • tokens, number of tokens assigned to this account, as a signed 64-bit integer
  • -
  • owner, array of 32 bytes representing the program this account has been assigned to
  • -
  • userdata, array of bytes representing any userdata associated with the account
  • -
  • executable, boolean indicating if the account contains a program (and is strictly read-only)
  • -
  • loader, array of 32 bytes representing the loader for this program (if executable), otherwise all
  • -
-
Example:
-
// Request
-curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"]}' http://localhost:8899
-
-// Result
-{"jsonrpc":"2.0","result":{"executable":false,"loader":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"tokens":1,"userdata":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"id":1}
-
-
-

getLastId

-

Returns the last entry ID from the ledger

-
Parameters:
-

None

-
Results:
-
    -
  • string - the ID of last entry, a Hash as base-58 encoded string
  • -
-
Example:
-
// Request
-curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getLastId"}' http://localhost:8899
-
-// Result
-{"jsonrpc":"2.0","result":"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC","id":1}
-
-
-

getSignatureStatus

-

Returns the status of a given signature. This method is similar to -confirmTransaction but provides more resolution for error -events.

-
Parameters:
-
    -
  • string - Signature of Transaction to confirm, as base-58 encoded string
  • -
-
Results:
-
    -
  • string - Transaction status: -
      -
    • Confirmed - Transaction was successful
    • -
    • SignatureNotFound - Unknown transaction
    • -
    • ProgramRuntimeError - An error occurred in the program that processed this Transaction
    • -
    • AccountInUse - Another Transaction had a write lock one of the Accounts specified in this Transaction. The Transaction may succeed if retried
    • -
    • GenericFailure - Some other error occurred. Note: In the future new Transaction statuses may be added to this list. It's safe to assume that all new statuses will be more specific error conditions that previously presented as GenericFailure
    • -
    -
  • -
-
Example:
-
// Request
-curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatus", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
-
-// Result
-{"jsonrpc":"2.0","result":"SignatureNotFound","id":1}
-
-
-

getTransactionCount

-

Returns the current Transaction count from the ledger

-
Parameters:
-

None

-
Results:
-
    -
  • integer - count, as unsigned 64-bit integer
  • -
-
Example:
-
// Request
-curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://localhost:8899
-
-// Result
-{"jsonrpc":"2.0","result":268,"id":1}
-
-
-

requestAirdrop

-

Requests an airdrop of tokens to a Pubkey

-
Parameters:
-
    -
  • string - Pubkey of account to receive tokens, as base-58 encoded string
  • -
  • integer - token quantity, as a signed 64-bit integer
  • -
-
Results:
-
    -
  • string - Transaction Signature of airdrop, as base-58 encoded string
  • -
-
Example:
-
// Request
-curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"requestAirdrop", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri", 50]}' http://localhost:8899
-
-// Result
-{"jsonrpc":"2.0","result":"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW","id":1}
-
-
-

sendTransaction

-

Creates new transaction

-
Parameters:
-
    -
  • array - array of octets containing a fully-signed Transaction
  • -
-
Results:
-
    -
  • string - Transaction Signature, as base-58 encoded string
  • -
-
Example:
-
// Request
-curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"sendTransaction", "params":[[61, 98, 55, 49, 15, 187, 41, 215, 176, 49, 234, 229, 228, 77, 129, 221, 239, 88, 145, 227, 81, 158, 223, 123, 14, 229, 235, 247, 191, 115, 199, 71, 121, 17, 32, 67, 63, 209, 239, 160, 161, 2, 94, 105, 48, 159, 235, 235, 93, 98, 172, 97, 63, 197, 160, 164, 192, 20, 92, 111, 57, 145, 251, 6, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 13, 39, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 11, 12, 106, 49, 74, 226, 201, 16, 161, 192, 28, 84, 124, 97, 190, 201, 171, 186, 6, 18, 70, 142, 89, 185, 176, 154, 115, 61, 26, 163, 77, 1, 88, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]}' http://localhost:8899
-
-// Result
-{"jsonrpc":"2.0","result":"2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b","id":1}
-
-
-

Subscription Websocket

-

After connect to the RPC PubSub websocket at ws://<ADDRESS>/:

-
    -
  • Submit subscription requests to the websocket using the methods below
  • -
  • Multiple subscriptions may be active at once
  • -
-
-

accountSubscribe

-

Subscribe to an account to receive notifications when the userdata for a given account public key changes

-
Parameters:
-
    -
  • string - account Pubkey, as base-58 encoded string
  • -
-
Results:
-
    -
  • integer - Subscription id (needed to unsubscribe)
  • -
-
Example:
-
// Request
-{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"]}
-
-// Result
-{"jsonrpc": "2.0","result": 0,"id": 1}
-
-
Notification Format:
-
{"jsonrpc": "2.0","method": "accountNotification", "params": {"result": {"executable":false,"loader":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"tokens":1,"userdata":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"subscription":0}}
-
-
-

accountUnsubscribe

-

Unsubscribe from account userdata change notifications

-
Parameters:
-
    -
  • integer - id of account Subscription to cancel
  • -
-
Results:
-
    -
  • bool - unsubscribe success message
  • -
-
Example:
-
// Request
-{"jsonrpc":"2.0", "id":1, "method":"accountUnsubscribe", "params":[0]}
-
-// Result
-{"jsonrpc": "2.0","result": true,"id": 1}
-
-
-

signatureSubscribe

-

Subscribe to a transaction signature to receive notification when the transaction is confirmed -On signatureNotification, the subscription is automatically cancelled

-
Parameters:
-
    -
  • string - Transaction Signature, as base-58 encoded string
  • -
-
Results:
-
    -
  • integer - subscription id (needed to unsubscribe)
  • -
-
Example:
-
// Request
-{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b"]}
-
-// Result
-{"jsonrpc": "2.0","result": 0,"id": 1}
-
-
Notification Format:
-
{"jsonrpc": "2.0","method": "signatureNotification", "params": {"result": "Confirmed","subscription":0}}
-
-
-

signatureUnsubscribe

-

Unsubscribe from account userdata change notifications

-
Parameters:
-
    -
  • integer - id of account subscription to cancel
  • -
-
Results:
-
    -
  • bool - unsubscribe success message
  • -
-
Example:
-
// Request
-{"jsonrpc":"2.0", "id":1, "method":"signatureUnsubscribe", "params":[0]}
-
-// Result
-{"jsonrpc": "2.0","result": true,"id": 1}
-
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/jsonrpc-service.html b/book/jsonrpc-service.html deleted file mode 100644 index 640aa8680ac051..00000000000000 --- a/book/jsonrpc-service.html +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - JsonRpcService - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

JsonRpcService

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/leader-scheduler.html b/book/leader-scheduler.html deleted file mode 100644 index 0a5c7540b88edf..00000000000000 --- a/book/leader-scheduler.html +++ /dev/null @@ -1,286 +0,0 @@ - - - - - - Leader Rotation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

Leader Rotation

-

A property of any permissionless blockchain is that the entity choosing the next block is randomly selected. In proof of stake systems, -that entity is typically called the "leader" or "block producer." In Solana, we call it the leader. Under the hood, a leader is -simply a mode of the fullnode. A fullnode runs as either a leader or validator. In this chapter, we describe how a fullnode determines -what node is the leader, how that mechanism may choose different leaders at the same time, and if so, how the system converges in response.

-

Leader Seed Generation

-

Leader selection is decided via a random seed. The process is as follows:

-
    -
  1. Periodically, at a specific PoH tick count, select the signatures of the votes that made up the last supermajority
  2. -
  3. Concatenate the signatures
  4. -
  5. Hash the resulting string for N counts
  6. -
  7. The resulting hash is the random seed for M counts, M leader slots, where M > N
  8. -
-

Leader Rotation

-
    -
  1. The leader is chosen via a random seed generated from stake weights and votes (the leader schedule)
  2. -
  3. The leader is rotated every T PoH ticks (leader slot), according to the leader schedule
  4. -
  5. The schedule is applicable for M voting rounds
  6. -
-

Leader's transmit for a count of T PoH ticks. When T is reached all the validators should switch to the next scheduled leader. To schedule leaders, the supermajority + M nodes are shuffled using the above calculated random seed.

-

All T ticks must be observed from the current leader for that part of PoH to be accepted by the network. If T ticks (and any intervening transactions) are not observed, the network optimistically fills in the T ticks, and continues with PoH from the next leader.

-

Partitions, Forks

-

Forks can arise at PoH tick counts that correspond to leader rotations, because leader nodes may or may not have observed the previous leader's data. These empty ticks are generated by all nodes in the network at a network-specified rate for hashes-per-tick Z.

-

There are only two possible versions of the PoH during a voting round: PoH with T ticks and entries generated by the current leader, or PoH with just ticks. The "just ticks" version of the PoH can be thought of as a virtual ledger, one that all nodes in the network can derive from the last tick in the previous slot.

-

Validators can ignore forks at other points (e.g. from the wrong leader), or slash the leader responsible for the fork.

-

Validators vote on the longest chain that contains their previous vote, or a longer chain if the lockout on their previous vote has expired.

-

Validator's View

-
Time Progression
-

The diagram below represents a validator's view of the PoH stream with possible forks over time. L1, L2, etc. are leader slots, and Es represent entries from that leader during that leader's slot. The xs represent ticks only, and time flows downwards in the diagram.

-

Leader scheduler

-

Note that an E appearing on 2 branches at the same slot is a slashable condition, so a validator observing L3 and L3' can slash L3 and safely choose x for that slot. Once a validator observes a supermajority vote on any branch, other branches can be discarded below that tick count. For any slot, validators need only consider a single "has entries" chain or a "ticks only" chain.

-
Time Division
-

It's useful to consider leader rotation over PoH tick count as time division of the job of encoding state for the network. The following table presents the above tree of forks as a time-divided ledger.

- - - -
leader slot L1 L2 L3 L4 L5
data E1 E2 E3 E4 E5
ticks since prev x xx
-

Note that only data from leader L3 will be accepted during leader slot -L3. Data from L3 may include "catchup" ticks back to a slot other than -L2 if L3 did not observe L2's data. L4 and L5's transmissions -include the "ticks since prev" PoH entries.

-

This arrangement of the network data streams permits nodes to save exactly this -to the ledger for replay, restart, and checkpoints.

-

Leader's View

-

When a new leader begins a slot, it must first transmit any PoH (ticks) -required to link the new slot with the most recently observed and voted -slot.

-

Examples

-

Small Partition

-
    -
  1. Network partition M occurs for 10% of the nodes
  2. -
  3. The larger partition K, with 90% of the stake weight continues to operate as -normal
  4. -
  5. M cycles through the ranks until one of them is leader, generating ticks for -slots where the leader is in K.
  6. -
  7. M validators observe 10% of the vote pool, finality is not reached.
  8. -
  9. M and K reconnect.
  10. -
  11. M validators cancel their votes on M, which has not reached finality, and -re-cast on K (after their vote lockout on M).
  12. -
-

Leader Timeout

-
    -
  1. Next rank leader node V observes a timeout from current leader A, fills in -A's slot with virtual ticks and starts sending out entries.
  2. -
  3. Nodes observing both streams keep track of the forks, waiting for: -
      -
    • their vote on leader A to expire in order to be able to vote on B
    • -
    • a supermajority on A's slot
    • -
    -
  4. -
  5. If the first case occurs, leader B's slot is filled with ticks. if the -second case occurs, A's slot is filled with ticks
  6. -
  7. Partition is resolved just like in the Small Partition -above
  8. -
-

Network Variables

-

A - name of a node

-

B - name of a node

-

K - number of nodes in the supermajority to whom leaders broadcast their -PoH hash for validation

-

M - number of nodes outside the supermajority to whom leaders broadcast their -PoH hash for validation

-

N - number of voting rounds for which a leader schedule is considered before -a new leader schedule is used

-

T - number of PoH ticks per leader slot (also voting round)

-

V - name of a node that will create virtual ticks

-

Z - number of hashes per PoH tick

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/leader_scheduler.rs b/book/leader_scheduler.rs deleted file mode 100644 index fb78ff1dee30a7..00000000000000 --- a/book/leader_scheduler.rs +++ /dev/null @@ -1,1407 +0,0 @@ -//! The `leader_scheduler` module implements a structure and functions for tracking and -//! managing the schedule for leader rotation - -use bank::Bank; - -use bincode::serialize; -use byteorder::{LittleEndian, ReadBytesExt}; -use entry::Entry; -use ledger::create_ticks; -use signature::{Keypair, KeypairUtil}; -use solana_sdk::hash::{hash, Hash}; -use solana_sdk::pubkey::Pubkey; -use std::collections::HashSet; -use std::io::Cursor; -use system_transaction::SystemTransaction; -use transaction::Transaction; -use vote_program::{Vote, VoteProgram}; -use vote_transaction::VoteTransaction; - -pub const DEFAULT_BOOTSTRAP_HEIGHT: u64 = 1000; -pub const DEFAULT_LEADER_ROTATION_INTERVAL: u64 = 100; -pub const DEFAULT_SEED_ROTATION_INTERVAL: u64 = 1000; -pub const DEFAULT_ACTIVE_WINDOW_LENGTH: u64 = 1000; - -pub struct LeaderSchedulerConfig { - // The interval at which to rotate the leader, should be much less than - // seed_rotation_interval - pub leader_rotation_interval_option: Option, - - // The interval at which to generate the seed used for ranking the validators - pub seed_rotation_interval_option: Option, - - // The last height at which the bootstrap_leader will be in power before - // the leader rotation process begins to pick future leaders - pub bootstrap_height_option: Option, - - // The length of the acceptable window for determining live validators - pub active_window_length_option: Option, -} - -// Used to toggle leader rotation in fullnode so that tests that don't -// need leader rotation don't break -impl LeaderSchedulerConfig { - pub fn new( - bootstrap_height_option: Option, - leader_rotation_interval_option: Option, - seed_rotation_interval_option: Option, - active_window_length_option: Option, - ) -> Self { - LeaderSchedulerConfig { - bootstrap_height_option, - leader_rotation_interval_option, - seed_rotation_interval_option, - active_window_length_option, - } - } -} - -#[derive(Clone, Debug)] -pub struct LeaderScheduler { - // Set to true if we want the default implementation of the LeaderScheduler, - // where ony the bootstrap leader is used - pub use_only_bootstrap_leader: bool, - - // The interval at which to rotate the leader, should be much less than - // seed_rotation_interval - pub leader_rotation_interval: u64, - - // The interval at which to generate the seed used for ranking the validators - pub seed_rotation_interval: u64, - - // The first leader who will bootstrap the network - pub bootstrap_leader: Pubkey, - - // The last height at which the bootstrap_leader will be in power before - // the leader rotation process begins to pick future leaders - pub bootstrap_height: u64, - - // The last height at which the seed + schedule was generated - pub last_seed_height: Option, - - // The length of time in ticks for which a vote qualifies a candidate for leader - // selection - pub active_window_length: u64, - - // Round-robin ordering for the validators - leader_schedule: Vec, - - // The seed used to determine the round robin order of leaders - seed: u64, -} - -// The LeaderScheduler implements a schedule for leaders as follows: -// -// 1) During the bootstrapping period of bootstrap_height PoH counts, the -// leader is hard-coded to the bootstrap_leader that is read from the genesis block. -// -// 2) After the first seed is generated, this signals the beginning of actual leader rotation. -// From this point on, every seed_rotation_interval PoH counts we generate the seed based -// on the PoH height, and use it to do a weighted sample from the set -// of validators based on current stake weight. This gets you the bootstrap leader A for -// the next leader_rotation_interval PoH counts. On the same PoH count we generate the seed, -// we also order the validators based on their current stake weight, and starting -// from leader A, we then pick the next leader sequentially every leader_rotation_interval -// PoH counts based on this fixed ordering, so the next -// seed_rotation_interval / leader_rotation_interval leaders are determined. -// -// 3) When we we hit the next seed rotation PoH height, step 2) is executed again to -// calculate the leader schedule for the upcoming seed_rotation_interval PoH counts. -impl LeaderScheduler { - pub fn from_bootstrap_leader(bootstrap_leader: Pubkey) -> Self { - let config = LeaderSchedulerConfig::new(None, None, None, None); - let mut leader_scheduler = LeaderScheduler::new(&config); - leader_scheduler.use_only_bootstrap_leader = true; - leader_scheduler.bootstrap_leader = bootstrap_leader; - leader_scheduler - } - - pub fn new(config: &LeaderSchedulerConfig) -> Self { - let mut bootstrap_height = DEFAULT_BOOTSTRAP_HEIGHT; - if let Some(input) = config.bootstrap_height_option { - bootstrap_height = input; - } - - let mut leader_rotation_interval = DEFAULT_LEADER_ROTATION_INTERVAL; - if let Some(input) = config.leader_rotation_interval_option { - leader_rotation_interval = input; - } - - let mut seed_rotation_interval = DEFAULT_SEED_ROTATION_INTERVAL; - if let Some(input) = config.seed_rotation_interval_option { - seed_rotation_interval = input; - } - - let mut active_window_length = DEFAULT_ACTIVE_WINDOW_LENGTH; - if let Some(input) = config.active_window_length_option { - active_window_length = input; - } - - // Enforced invariants - assert!(seed_rotation_interval >= leader_rotation_interval); - assert!(bootstrap_height > 0); - assert!(seed_rotation_interval % leader_rotation_interval == 0); - - LeaderScheduler { - use_only_bootstrap_leader: false, - leader_rotation_interval, - seed_rotation_interval, - leader_schedule: Vec::new(), - last_seed_height: None, - bootstrap_leader: Pubkey::default(), - bootstrap_height, - active_window_length, - seed: 0, - } - } - - pub fn is_leader_rotation_height(&self, height: u64) -> bool { - if self.use_only_bootstrap_leader { - return false; - } - - if height < self.bootstrap_height { - return false; - } - - (height - self.bootstrap_height) % self.leader_rotation_interval == 0 - } - - pub fn count_until_next_leader_rotation(&self, height: u64) -> Option { - if self.use_only_bootstrap_leader { - return None; - } - - if height < self.bootstrap_height { - Some(self.bootstrap_height - height) - } else { - Some( - self.leader_rotation_interval - - ((height - self.bootstrap_height) % self.leader_rotation_interval), - ) - } - } - - // Let Leader X be the leader at the input tick height. This function returns the - // the PoH height at which Leader X's slot ends. - pub fn max_height_for_leader(&self, height: u64) -> Option { - if self.use_only_bootstrap_leader || self.get_scheduled_leader(height).is_none() { - return None; - } - - let result = { - if height < self.bootstrap_height || self.leader_schedule.len() > 1 { - // Two cases to consider: - // - // 1) If height is less than the bootstrap height, then the current leader's - // slot ends when PoH height = bootstrap_height - // - // 2) Otherwise, if height >= bootstrap height, then we have generated a schedule. - // If this leader is not the only one in the schedule, then they will - // only be leader until the end of this slot (someone else is then guaranteed - // to take over) - // - // Both above cases are calculated by the function: - // count_until_next_leader_rotation() + height - self.count_until_next_leader_rotation(height).expect( - "Should return some value when not using default implementation - of LeaderScheduler", - ) + height - } else { - // If the height is greater than bootstrap_height and this leader is - // the only leader in the schedule, then that leader will be in power - // for every slot until the next epoch, which is seed_rotation_interval - // PoH counts from the beginning of the last epoch. - self.last_seed_height.expect( - "If height >= bootstrap height, then we expect - a seed has been generated", - ) + self.seed_rotation_interval - } - }; - - Some(result) - } - - pub fn reset(&mut self) { - self.last_seed_height = None; - } - - pub fn update_height(&mut self, height: u64, bank: &Bank) { - if self.use_only_bootstrap_leader { - return; - } - - if height < self.bootstrap_height { - return; - } - - if let Some(last_seed_height) = self.last_seed_height { - if height <= last_seed_height { - return; - } - } - - if (height - self.bootstrap_height) % self.seed_rotation_interval == 0 { - self.generate_schedule(height, bank); - } - } - - // Uses the schedule generated by the last call to generate_schedule() to return the - // leader for a given PoH height in round-robin fashion - pub fn get_scheduled_leader(&self, height: u64) -> Option<(Pubkey, u64)> { - if self.use_only_bootstrap_leader { - return Some((self.bootstrap_leader, 0)); - } - - // This covers cases where the schedule isn't yet generated. - if self.last_seed_height == None { - if height < self.bootstrap_height { - return Some((self.bootstrap_leader, 0)); - } else { - // If there's been no schedule generated yet before we reach the end of the - // bootstrapping period, then the leader is unknown - return None; - } - } - - // If we have a schedule, then just check that we are within the bounds of that - // schedule [last_seed_height, last_seed_height + seed_rotation_interval). - // Leaders outside of this bound are undefined. - let last_seed_height = self.last_seed_height.unwrap(); - if height >= last_seed_height + self.seed_rotation_interval || height < last_seed_height { - return None; - } - - // Find index into the leader_schedule that this PoH height maps to - let leader_slot = (height - self.bootstrap_height) / self.leader_rotation_interval + 1; - let index = (height - last_seed_height) / self.leader_rotation_interval; - let validator_index = index as usize % self.leader_schedule.len(); - Some((self.leader_schedule[validator_index], leader_slot)) - } - - pub fn get_leader_for_slot(&self, slot_height: u64) -> Option { - let tick_height = self.slot_height_to_first_tick_height(slot_height); - self.get_scheduled_leader(tick_height).map(|(id, _)| id) - } - - // Maps the nth slot (where n == slot_height) to the tick height of - // the first tick for that slot - fn slot_height_to_first_tick_height(&self, slot_height: u64) -> u64 { - if slot_height == 0 { - 0 - } else { - (slot_height - 1) * self.leader_rotation_interval + self.bootstrap_height - } - } - - // TODO: We use a HashSet for now because a single validator could potentially register - // multiple vote account. Once that is no longer possible (see the TODO in vote_program.rs, - // process_transaction(), case VoteInstruction::RegisterAccount), we can use a vector. - fn get_active_set(&mut self, height: u64, bank: &Bank) -> HashSet { - let upper_bound = height; - let lower_bound = height.saturating_sub(self.active_window_length); - - { - let accounts = bank.accounts.read().unwrap(); - - // TODO: iterate through checkpoints, too - accounts - .accounts - .values() - .filter_map(|account| { - if VoteProgram::check_id(&account.owner) { - if let Ok(vote_state) = VoteProgram::deserialize(&account.userdata) { - return vote_state - .votes - .back() - .filter(|vote| { - vote.tick_height > lower_bound - && vote.tick_height <= upper_bound - }).map(|_| vote_state.node_id); - } - } - - None - }).collect() - } - } - - // Called every seed_rotation_interval entries, generates the leader schedule - // for the range of entries: [height, height + seed_rotation_interval) - fn generate_schedule(&mut self, height: u64, bank: &Bank) { - assert!(height >= self.bootstrap_height); - assert!((height - self.bootstrap_height) % self.seed_rotation_interval == 0); - let seed = Self::calculate_seed(height); - self.seed = seed; - let active_set = self.get_active_set(height, &bank); - let ranked_active_set = Self::rank_active_set(bank, active_set.iter()); - - // Handle case where there are no active validators with - // non-zero stake. In this case, use the bootstrap leader for - // the upcoming rounds - if ranked_active_set.is_empty() { - self.last_seed_height = Some(height); - self.leader_schedule = vec![self.bootstrap_leader]; - self.last_seed_height = Some(height); - return; - } - - let (mut validator_rankings, total_stake) = ranked_active_set.iter().fold( - (Vec::with_capacity(ranked_active_set.len()), 0), - |(mut ids, total_stake), (pk, stake)| { - ids.push(**pk); - (ids, total_stake + stake) - }, - ); - - // Choose the validator that will be the first to be the leader in this new - // schedule - let ordered_account_stake = ranked_active_set.into_iter().map(|(_, stake)| stake); - let start_index = Self::choose_account(ordered_account_stake, self.seed, total_stake); - validator_rankings.rotate_left(start_index); - - // There are only seed_rotation_interval / self.leader_rotation_interval slots, so - // we only need to keep at most that many validators in the schedule - let slots_per_epoch = self.seed_rotation_interval / self.leader_rotation_interval; - - // If possible, try to avoid having the same leader twice in a row, but - // if there's only one leader to choose from, then we have no other choice - if validator_rankings.len() > 1 { - let (old_epoch_last_leader, _) = self - .get_scheduled_leader(height - 1) - .expect("Previous leader schedule should still exist"); - let new_epoch_start_leader = validator_rankings[0]; - - if old_epoch_last_leader == new_epoch_start_leader { - if slots_per_epoch == 1 { - // If there is only one slot per epoch, and the same leader as the last slot - // of the previous epoch was chosen, then pick the next leader in the - // rankings instead - validator_rankings[0] = validator_rankings[1]; - } else { - // If there is more than one leader in the schedule, truncate and set the most - // recent leader to the back of the line. This way that node will still remain - // in the rotation, just at a later slot. - validator_rankings.truncate(slots_per_epoch as usize); - validator_rankings.rotate_left(1); - } - } - } - - self.leader_schedule = validator_rankings; - self.last_seed_height = Some(height); - } - - fn rank_active_set<'a, I>(bank: &Bank, active: I) -> Vec<(&'a Pubkey, u64)> - where - I: Iterator, - { - let mut active_accounts: Vec<(&'a Pubkey, u64)> = active - .filter_map(|pk| { - let stake = bank.get_stake(pk); - if stake > 0 { - Some((pk, stake as u64)) - } else { - None - } - }).collect(); - - active_accounts.sort_by( - |(pk1, t1), (pk2, t2)| { - if t1 == t2 { - pk1.cmp(&pk2) - } else { - t1.cmp(&t2) - } - }, - ); - active_accounts - } - - fn calculate_seed(height: u64) -> u64 { - let hash = hash(&serialize(&height).unwrap()); - let bytes = hash.as_ref(); - let mut rdr = Cursor::new(bytes); - rdr.read_u64::().unwrap() - } - - fn choose_account(stakes: I, seed: u64, total_stake: u64) -> usize - where - I: IntoIterator, - { - let mut total = 0; - let mut chosen_account = 0; - let seed = seed % total_stake; - for (i, s) in stakes.into_iter().enumerate() { - // We should have filtered out all accounts with zero stake in - // rank_active_set() - assert!(s != 0); - total += s; - if total > seed { - chosen_account = i; - break; - } - } - - chosen_account - } -} - -impl Default for LeaderScheduler { - // Create a dummy leader scheduler - fn default() -> Self { - let id = Pubkey::default(); - Self::from_bootstrap_leader(id) - } -} - -// Create two entries so that the node with keypair == active_keypair -// is in the active set for leader selection: -// 1) Give the node a nonzero number of tokens, -// 2) A vote from the validator -pub fn make_active_set_entries( - active_keypair: &Keypair, - token_source: &Keypair, - last_entry_id: &Hash, - last_tick_id: &Hash, - num_ending_ticks: usize, -) -> (Vec, Keypair) { - // 1) Create transfer token entry - let transfer_tx = - Transaction::system_new(&token_source, active_keypair.pubkey(), 2, *last_tick_id); - let transfer_entry = Entry::new(last_entry_id, 1, vec![transfer_tx]); - let mut last_entry_id = transfer_entry.id; - - // 2) Create the vote account - let vote_account = Keypair::new(); - let create_vote_account_tx = - Transaction::vote_account_new(active_keypair, vote_account.pubkey(), *last_tick_id, 1); - - let create_vote_account_entry = Entry::new(&last_entry_id, 1, vec![create_vote_account_tx]); - last_entry_id = create_vote_account_entry.id; - - // 3) Register the vote account - let register_vote_account_tx = - Transaction::vote_account_register(active_keypair, vote_account.pubkey(), *last_tick_id, 0); - - let register_vote_account_entry = Entry::new(&last_entry_id, 1, vec![register_vote_account_tx]); - last_entry_id = register_vote_account_entry.id; - - // 4) Create vote entry - let vote = Vote { tick_height: 1 }; - let vote_tx = Transaction::vote_new(&vote_account, vote, *last_tick_id, 0); - let vote_entry = Entry::new(&last_entry_id, 1, vec![vote_tx]); - last_entry_id = vote_entry.id; - - // 5) Create the ending empty ticks - let mut txs = vec![ - transfer_entry, - create_vote_account_entry, - register_vote_account_entry, - vote_entry, - ]; - let empty_ticks = create_ticks(num_ending_ticks, last_entry_id); - txs.extend(empty_ticks); - (txs, vote_account) -} - -#[cfg(test)] -mod tests { - use bank::Bank; - use leader_scheduler::{ - LeaderScheduler, LeaderSchedulerConfig, DEFAULT_BOOTSTRAP_HEIGHT, - DEFAULT_LEADER_ROTATION_INTERVAL, DEFAULT_SEED_ROTATION_INTERVAL, - }; - use mint::Mint; - use signature::{Keypair, KeypairUtil}; - use solana_sdk::hash::Hash; - use solana_sdk::pubkey::Pubkey; - use std::collections::HashSet; - use std::hash::Hash as StdHash; - use std::iter::FromIterator; - use transaction::Transaction; - use vote_program::Vote; - use vote_transaction::{create_vote_account, VoteTransaction}; - - fn to_hashset_owned(slice: &[T]) -> HashSet - where - T: Eq + StdHash + Clone, - { - HashSet::from_iter(slice.iter().cloned()) - } - - fn push_vote(vote_account: &Keypair, bank: &Bank, height: u64, last_id: Hash) { - let vote = Vote { - tick_height: height, - }; - - let new_vote_tx = Transaction::vote_new(vote_account, vote, last_id, 0); - - bank.process_transaction(&new_vote_tx).unwrap(); - } - - fn run_scheduler_test( - num_validators: usize, - bootstrap_height: u64, - leader_rotation_interval: u64, - seed_rotation_interval: u64, - ) { - // Allow the validators to be in the active window for the entire test - let active_window_length = seed_rotation_interval + bootstrap_height; - - // Set up the LeaderScheduler struct - let bootstrap_leader_id = Keypair::new().pubkey(); - let leader_scheduler_config = LeaderSchedulerConfig::new( - Some(bootstrap_height), - Some(leader_rotation_interval), - Some(seed_rotation_interval), - Some(active_window_length), - ); - - let mut leader_scheduler = LeaderScheduler::new(&leader_scheduler_config); - leader_scheduler.bootstrap_leader = bootstrap_leader_id; - - // Create the bank and validators, which are inserted in order of account balance - let num_vote_account_tokens = 1; - let mint = Mint::new( - (((num_validators + 1) / 2) * (num_validators + 1) - + num_vote_account_tokens * num_validators) as u64, - ); - let bank = Bank::new(&mint); - let mut validators = vec![]; - let last_id = mint - .create_entries() - .last() - .expect("Mint should not create empty genesis entries") - .id; - for i in 0..num_validators { - let new_validator = Keypair::new(); - let new_pubkey = new_validator.pubkey(); - validators.push(new_pubkey); - // Give the validator some tokens - bank.transfer( - (i + 1 + num_vote_account_tokens) as u64, - &mint.keypair(), - new_pubkey, - last_id, - ).unwrap(); - - // Create a vote account - let new_vote_account = create_vote_account( - &new_validator, - &bank, - num_vote_account_tokens as u64, - mint.last_id(), - ).unwrap(); - // Vote to make the validator part of the active set for the entire test - // (we made the active_window_length large enough at the beginning of the test) - push_vote(&new_vote_account, &bank, 1, mint.last_id()); - } - - // The scheduled leader during the bootstrapping period (assuming a seed + schedule - // haven't been generated, otherwise that schedule takes precendent) should always - // be the bootstrap leader - assert_eq!( - leader_scheduler.get_scheduled_leader(0), - Some((bootstrap_leader_id, 0)) - ); - assert_eq!( - leader_scheduler.get_scheduled_leader(bootstrap_height - 1), - Some((bootstrap_leader_id, 0)) - ); - assert_eq!( - leader_scheduler.get_scheduled_leader(bootstrap_height), - None - ); - - // Generate the schedule at the end of the bootstrapping period, should be the - // same leader for the next leader_rotation_interval entries - leader_scheduler.generate_schedule(bootstrap_height, &bank); - - // The leader outside of the newly generated schedule window: - // [bootstrap_height, bootstrap_height + seed_rotation_interval) - // should be undefined - assert_eq!( - leader_scheduler.get_scheduled_leader(bootstrap_height - 1), - None, - ); - - assert_eq!( - leader_scheduler.get_scheduled_leader(bootstrap_height + seed_rotation_interval), - None, - ); - - // For the next seed_rotation_interval entries, call get_scheduled_leader every - // leader_rotation_interval entries, and the next leader should be the next validator - // in order of stake - - // Note: seed_rotation_interval must be divisible by leader_rotation_interval, enforced - // by the LeaderScheduler constructor - let num_rounds = seed_rotation_interval / leader_rotation_interval; - let mut start_leader_index = None; - for i in 0..num_rounds { - let begin_height = bootstrap_height + i * leader_rotation_interval; - let (current_leader, slot) = leader_scheduler - .get_scheduled_leader(begin_height) - .expect("Expected a leader from scheduler"); - - // Note: The "validators" vector is already sorted by stake, so the expected order - // for the leader schedule can be derived by just iterating through the vector - // in order. The only excpetion is for the bootstrap leader in the schedule, we need to - // find the index into the "validators" vector where the schedule begins. - if None == start_leader_index { - start_leader_index = Some( - validators - .iter() - .position(|v| *v == current_leader) - .unwrap(), - ); - } - - let expected_leader = - validators[(start_leader_index.unwrap() + i as usize) % num_validators]; - assert_eq!(current_leader, expected_leader); - assert_eq!(slot, i + 1); - // Check that the same leader is in power for the next leader_rotation_interval entries - assert_eq!( - leader_scheduler.get_scheduled_leader(begin_height + leader_rotation_interval - 1), - Some((current_leader, slot)) - ); - } - } - - #[test] - fn test_active_set() { - let leader_id = Keypair::new().pubkey(); - let active_window_length = 1000; - let mint = Mint::new_with_leader(10000, leader_id, 500); - let bank = Bank::new(&mint); - - let leader_scheduler_config = - LeaderSchedulerConfig::new(Some(100), Some(100), Some(100), Some(active_window_length)); - - let mut leader_scheduler = LeaderScheduler::new(&leader_scheduler_config); - leader_scheduler.bootstrap_leader = leader_id; - - // Insert a bunch of votes at height "start_height" - let start_height = 3; - let num_old_ids = 20; - let mut old_ids = HashSet::new(); - for _ in 0..num_old_ids { - let new_keypair = Keypair::new(); - let pk = new_keypair.pubkey(); - old_ids.insert(pk.clone()); - - // Give the account some stake - bank.transfer(5, &mint.keypair(), pk, mint.last_id()) - .unwrap(); - - // Create a vote account - let new_vote_account = - create_vote_account(&new_keypair, &bank, 1, mint.last_id()).unwrap(); - - // Push a vote for the account - push_vote(&new_vote_account, &bank, start_height, mint.last_id()); - } - - // Insert a bunch of votes at height "start_height + active_window_length" - let num_new_ids = 10; - let mut new_ids = HashSet::new(); - for _ in 0..num_new_ids { - let new_keypair = Keypair::new(); - let pk = new_keypair.pubkey(); - new_ids.insert(pk); - // Give the account some stake - bank.transfer(5, &mint.keypair(), pk, mint.last_id()) - .unwrap(); - - // Create a vote account - let new_vote_account = - create_vote_account(&new_keypair, &bank, 1, mint.last_id()).unwrap(); - - push_vote( - &new_vote_account, - &bank, - start_height + active_window_length, - mint.last_id(), - ); - } - - // Queries for the active set - let result = - leader_scheduler.get_active_set(active_window_length + start_height - 1, &bank); - assert_eq!(result, old_ids); - - let result = leader_scheduler.get_active_set(active_window_length + start_height, &bank); - assert_eq!(result, new_ids); - - let result = - leader_scheduler.get_active_set(2 * active_window_length + start_height - 1, &bank); - assert_eq!(result, new_ids); - - let result = - leader_scheduler.get_active_set(2 * active_window_length + start_height, &bank); - assert!(result.is_empty()); - } - - #[test] - fn test_seed() { - // Check that num_seeds different seeds are generated - let num_seeds = 1000; - let mut old_seeds = HashSet::new(); - for i in 0..num_seeds { - let seed = LeaderScheduler::calculate_seed(i); - assert!(!old_seeds.contains(&seed)); - old_seeds.insert(seed); - } - } - - #[test] - fn test_rank_active_set() { - let num_validators: usize = 101; - // Give mint sum(1..num_validators) tokens - let mint = Mint::new((((num_validators + 1) / 2) * (num_validators + 1)) as u64); - let bank = Bank::new(&mint); - let mut validators = vec![]; - let last_id = mint - .create_entries() - .last() - .expect("Mint should not create empty genesis entries") - .id; - for i in 0..num_validators { - let new_validator = Keypair::new(); - let new_pubkey = new_validator.pubkey(); - validators.push(new_validator); - bank.transfer( - (num_validators - i) as u64, - &mint.keypair(), - new_pubkey, - last_id, - ).unwrap(); - } - - let validators_pk: Vec = validators.iter().map(Keypair::pubkey).collect(); - let result = LeaderScheduler::rank_active_set(&bank, validators_pk.iter()); - - assert_eq!(result.len(), validators.len()); - - // Expect the result to be the reverse of the list we passed into the rank_active_set() - for (i, (pk, stake)) in result.into_iter().enumerate() { - assert_eq!(stake, i as u64 + 1); - assert_eq!(*pk, validators[num_validators - i - 1].pubkey()); - } - - // Transfer all the tokens to a new set of validators, old validators should now - // have balance of zero and get filtered out of the rankings - let mut new_validators = vec![]; - for i in 0..num_validators { - let new_validator = Keypair::new(); - let new_pubkey = new_validator.pubkey(); - new_validators.push(new_validator); - bank.transfer( - (num_validators - i) as u64, - &validators[i], - new_pubkey, - last_id, - ).unwrap(); - } - - let all_validators: Vec = validators - .iter() - .chain(new_validators.iter()) - .map(Keypair::pubkey) - .collect(); - let result = LeaderScheduler::rank_active_set(&bank, all_validators.iter()); - assert_eq!(result.len(), new_validators.len()); - - for (i, (pk, balance)) in result.into_iter().enumerate() { - assert_eq!(balance, i as u64 + 1); - assert_eq!(*pk, new_validators[num_validators - i - 1].pubkey()); - } - - // Break ties between validators with the same balances using public key - let mint = Mint::new(num_validators as u64); - let bank = Bank::new(&mint); - let mut tied_validators_pk = vec![]; - let last_id = mint - .create_entries() - .last() - .expect("Mint should not create empty genesis entries") - .id; - - for _ in 0..num_validators { - let new_validator = Keypair::new(); - let new_pubkey = new_validator.pubkey(); - tied_validators_pk.push(new_pubkey); - bank.transfer(1, &mint.keypair(), new_pubkey, last_id) - .unwrap(); - } - - let result = LeaderScheduler::rank_active_set(&bank, tied_validators_pk.iter()); - let mut sorted: Vec<&Pubkey> = tied_validators_pk.iter().map(|x| x).collect(); - sorted.sort_by(|pk1, pk2| pk1.cmp(pk2)); - assert_eq!(result.len(), tied_validators_pk.len()); - for (i, (pk, s)) in result.into_iter().enumerate() { - assert_eq!(s, 1); - assert_eq!(*pk, *sorted[i]); - } - } - - #[test] - fn test_choose_account() { - let tokens = vec![10, 30, 50, 5, 1]; - let total_tokens = tokens.iter().sum(); - let mut seed = tokens[0]; - assert_eq!( - LeaderScheduler::choose_account(tokens.clone(), seed, total_tokens), - 1 - ); - - seed = tokens[0] - 1; - assert_eq!( - LeaderScheduler::choose_account(tokens.clone(), seed, total_tokens), - 0 - ); - - seed = 0; - assert_eq!( - LeaderScheduler::choose_account(tokens.clone(), seed, total_tokens), - 0 - ); - - seed = total_tokens; - assert_eq!( - LeaderScheduler::choose_account(tokens.clone(), seed, total_tokens), - 0 - ); - - seed = total_tokens - 1; - assert_eq!( - LeaderScheduler::choose_account(tokens.clone(), seed, total_tokens), - tokens.len() - 1 - ); - - seed = tokens[0..3].iter().sum(); - assert_eq!( - LeaderScheduler::choose_account(tokens.clone(), seed, total_tokens), - 3 - ); - } - - #[test] - fn test_scheduler() { - // Test when the number of validators equals - // seed_rotation_interval / leader_rotation_interval, so each validator - // is selected once - let mut num_validators = 100; - let mut bootstrap_height = 500; - let mut leader_rotation_interval = 100; - let mut seed_rotation_interval = leader_rotation_interval * num_validators; - run_scheduler_test( - num_validators, - bootstrap_height, - leader_rotation_interval as u64, - seed_rotation_interval as u64, - ); - - // Test when there are fewer validators than - // seed_rotation_interval / leader_rotation_interval, so each validator - // is selected multiple times - num_validators = 3; - bootstrap_height = 500; - leader_rotation_interval = 100; - seed_rotation_interval = 1000; - run_scheduler_test( - num_validators, - bootstrap_height, - leader_rotation_interval as u64, - seed_rotation_interval as u64, - ); - - // Test when there are fewer number of validators than - // seed_rotation_interval / leader_rotation_interval, so each validator - // may not be selected - num_validators = 10; - bootstrap_height = 500; - leader_rotation_interval = 100; - seed_rotation_interval = 200; - run_scheduler_test( - num_validators, - bootstrap_height, - leader_rotation_interval as u64, - seed_rotation_interval as u64, - ); - - // Test when seed_rotation_interval == leader_rotation_interval, - // only one validator should be selected - num_validators = 10; - bootstrap_height = 1; - leader_rotation_interval = 1; - seed_rotation_interval = 1; - run_scheduler_test( - num_validators, - bootstrap_height, - leader_rotation_interval as u64, - seed_rotation_interval as u64, - ); - } - - #[test] - fn test_scheduler_active_window() { - let num_validators = 10; - let num_vote_account_tokens = 1; - // Set up the LeaderScheduler struct - let bootstrap_leader_id = Keypair::new().pubkey(); - let bootstrap_height = 500; - let leader_rotation_interval = 100; - // Make sure seed_rotation_interval is big enough so we select all the - // validators as part of the schedule each time (we need to check the active window - // is the cause of validators being truncated later) - let seed_rotation_interval = leader_rotation_interval * num_validators; - let active_window_length = seed_rotation_interval; - - let leader_scheduler_config = LeaderSchedulerConfig::new( - Some(bootstrap_height), - Some(leader_rotation_interval), - Some(seed_rotation_interval), - Some(active_window_length), - ); - - let mut leader_scheduler = LeaderScheduler::new(&leader_scheduler_config); - leader_scheduler.bootstrap_leader = bootstrap_leader_id; - - // Create the bank and validators - let mint = Mint::new( - ((((num_validators + 1) / 2) * (num_validators + 1)) - + (num_vote_account_tokens * num_validators)) as u64, - ); - let bank = Bank::new(&mint); - let mut validators = vec![]; - let last_id = mint - .create_entries() - .last() - .expect("Mint should not create empty genesis entries") - .id; - for i in 0..num_validators { - let new_validator = Keypair::new(); - let new_pubkey = new_validator.pubkey(); - validators.push(new_pubkey); - // Give the validator some tokens - bank.transfer( - (i + 1 + num_vote_account_tokens) as u64, - &mint.keypair(), - new_pubkey, - last_id, - ).unwrap(); - - // Create a vote account - let new_vote_account = create_vote_account( - &new_validator, - &bank, - num_vote_account_tokens as u64, - mint.last_id(), - ).unwrap(); - - // Vote at height i * active_window_length for validator i - push_vote( - &new_vote_account, - &bank, - i * active_window_length + bootstrap_height, - mint.last_id(), - ); - } - - // Generate schedule every active_window_length entries and check that - // validators are falling out of the rotation as they fall out of the - // active set - for i in 0..=num_validators { - leader_scheduler.generate_schedule(i * active_window_length + bootstrap_height, &bank); - let result = &leader_scheduler.leader_schedule; - let expected = if i == num_validators { - bootstrap_leader_id - } else { - validators[i as usize] - }; - - assert_eq!(vec![expected], *result); - } - } - - #[test] - fn test_multiple_vote() { - let leader_keypair = Keypair::new(); - let leader_id = leader_keypair.pubkey(); - let active_window_length = 1000; - let mint = Mint::new_with_leader(10000, leader_id, 500); - let bank = Bank::new(&mint); - - let leader_scheduler_config = - LeaderSchedulerConfig::new(Some(100), Some(100), Some(100), Some(active_window_length)); - - let mut leader_scheduler = LeaderScheduler::new(&leader_scheduler_config); - leader_scheduler.bootstrap_leader = leader_id; - - // Check that a node that votes twice in a row will get included in the active - // window - let initial_vote_height = 1; - - // Create a vote account - let new_vote_account = - create_vote_account(&leader_keypair, &bank, 1, mint.last_id()).unwrap(); - - // Vote twice - push_vote( - &new_vote_account, - &bank, - initial_vote_height, - mint.last_id(), - ); - push_vote( - &new_vote_account, - &bank, - initial_vote_height + 1, - mint.last_id(), - ); - - let result = - leader_scheduler.get_active_set(initial_vote_height + active_window_length, &bank); - assert_eq!(result, to_hashset_owned(&vec![leader_id])); - let result = - leader_scheduler.get_active_set(initial_vote_height + active_window_length + 1, &bank); - assert!(result.is_empty()); - } - - #[test] - fn test_update_height() { - let bootstrap_leader_id = Keypair::new().pubkey(); - let bootstrap_height = 500; - let leader_rotation_interval = 100; - // Make sure seed_rotation_interval is big enough so we select all the - // validators as part of the schedule each time (we need to check the active window - // is the cause of validators being truncated later) - let seed_rotation_interval = leader_rotation_interval; - let active_window_length = 1; - - let leader_scheduler_config = LeaderSchedulerConfig::new( - Some(bootstrap_height), - Some(leader_rotation_interval), - Some(seed_rotation_interval), - Some(active_window_length), - ); - - let mut leader_scheduler = LeaderScheduler::new(&leader_scheduler_config); - leader_scheduler.bootstrap_leader = bootstrap_leader_id; - - // Check that the generate_schedule() function is being called by the - // update_height() function at the correct entry heights. - let bank = Bank::default(); - leader_scheduler.update_height(bootstrap_height - 1, &bank); - assert_eq!(leader_scheduler.last_seed_height, None); - leader_scheduler.update_height(bootstrap_height, &bank); - assert_eq!(leader_scheduler.last_seed_height, Some(bootstrap_height)); - leader_scheduler.update_height(bootstrap_height + seed_rotation_interval - 1, &bank); - assert_eq!(leader_scheduler.last_seed_height, Some(bootstrap_height)); - leader_scheduler.update_height(bootstrap_height + seed_rotation_interval, &bank); - assert_eq!( - leader_scheduler.last_seed_height, - Some(bootstrap_height + seed_rotation_interval) - ); - } - - #[test] - fn test_constructors() { - let bootstrap_leader_id = Keypair::new().pubkey(); - - // Check defaults for LeaderScheduler - let leader_scheduler_config = LeaderSchedulerConfig::new(None, None, None, None); - - let leader_scheduler = LeaderScheduler::new(&leader_scheduler_config); - - assert_eq!(leader_scheduler.bootstrap_leader, Pubkey::default()); - - assert_eq!(leader_scheduler.bootstrap_height, DEFAULT_BOOTSTRAP_HEIGHT); - - assert_eq!( - leader_scheduler.leader_rotation_interval, - DEFAULT_LEADER_ROTATION_INTERVAL - ); - assert_eq!( - leader_scheduler.seed_rotation_interval, - DEFAULT_SEED_ROTATION_INTERVAL - ); - - // Check actual arguments for LeaderScheduler - let bootstrap_height = 500; - let leader_rotation_interval = 100; - let seed_rotation_interval = 200; - let active_window_length = 1; - - let leader_scheduler_config = LeaderSchedulerConfig::new( - Some(bootstrap_height), - Some(leader_rotation_interval), - Some(seed_rotation_interval), - Some(active_window_length), - ); - - let mut leader_scheduler = LeaderScheduler::new(&leader_scheduler_config); - leader_scheduler.bootstrap_leader = bootstrap_leader_id; - - assert_eq!(leader_scheduler.bootstrap_height, bootstrap_height); - - assert_eq!( - leader_scheduler.leader_rotation_interval, - leader_rotation_interval - ); - assert_eq!( - leader_scheduler.seed_rotation_interval, - seed_rotation_interval - ); - } - - fn run_consecutive_leader_test(num_slots_per_epoch: u64, add_validator: bool) { - let bootstrap_leader_keypair = Keypair::new(); - let bootstrap_leader_id = bootstrap_leader_keypair.pubkey(); - let bootstrap_height = 500; - let leader_rotation_interval = 100; - let seed_rotation_interval = num_slots_per_epoch * leader_rotation_interval; - let active_window_length = bootstrap_height + seed_rotation_interval; - - let leader_scheduler_config = LeaderSchedulerConfig::new( - Some(bootstrap_height), - Some(leader_rotation_interval), - Some(seed_rotation_interval), - Some(active_window_length), - ); - - let mut leader_scheduler = LeaderScheduler::new(&leader_scheduler_config); - leader_scheduler.bootstrap_leader = bootstrap_leader_id; - - // Create mint and bank - let mint = Mint::new_with_leader(10000, bootstrap_leader_id, 0); - let bank = Bank::new(&mint); - let last_id = mint - .create_entries() - .last() - .expect("Mint should not create empty genesis entries") - .id; - let initial_vote_height = 1; - - // Create and add validator to the active set - let validator_keypair = Keypair::new(); - let validator_id = validator_keypair.pubkey(); - if add_validator { - bank.transfer(5, &mint.keypair(), validator_id, last_id) - .unwrap(); - // Create a vote account - let new_vote_account = - create_vote_account(&validator_keypair, &bank, 1, mint.last_id()).unwrap(); - push_vote( - &new_vote_account, - &bank, - initial_vote_height, - mint.last_id(), - ); - } - - // Make sure the bootstrap leader, not the validator, is picked again on next slot - // Depending on the seed, we make the leader stake either 2, or 3. Because the - // validator stake is always 1, then the rankings will always be - // [(validator, 1), (leader, leader_stake)]. Thus we just need to make sure that - // seed % (leader_stake + 1) > 0 to make sure that the leader is picked again. - let seed = LeaderScheduler::calculate_seed(bootstrap_height); - let leader_stake = { - if seed % 3 == 0 { - 3 - } else { - 2 - } - }; - - let vote_account_tokens = 1; - bank.transfer( - leader_stake + vote_account_tokens, - &mint.keypair(), - bootstrap_leader_id, - last_id, - ).unwrap(); - - // Create a vote account - let new_vote_account = create_vote_account( - &bootstrap_leader_keypair, - &bank, - vote_account_tokens, - mint.last_id(), - ).unwrap(); - - // Add leader to the active set - push_vote( - &new_vote_account, - &bank, - initial_vote_height, - mint.last_id(), - ); - - leader_scheduler.generate_schedule(bootstrap_height, &bank); - - // Make sure the validator, not the leader is selected on the first slot of the - // next epoch - if add_validator { - assert!(leader_scheduler.leader_schedule[0] == validator_id); - } else { - assert!(leader_scheduler.leader_schedule[0] == bootstrap_leader_id); - } - } - - #[test] - fn test_avoid_consecutive_leaders() { - // Test when there is both a leader + validator in the active set - run_consecutive_leader_test(1, true); - run_consecutive_leader_test(2, true); - run_consecutive_leader_test(10, true); - - // Test when there is only one node in the active set - run_consecutive_leader_test(1, false); - run_consecutive_leader_test(2, false); - run_consecutive_leader_test(10, false); - } - - #[test] - fn test_max_height_for_leader() { - let bootstrap_leader_keypair = Keypair::new(); - let bootstrap_leader_id = bootstrap_leader_keypair.pubkey(); - let bootstrap_height = 500; - let leader_rotation_interval = 100; - let seed_rotation_interval = 2 * leader_rotation_interval; - let active_window_length = bootstrap_height + seed_rotation_interval; - - let leader_scheduler_config = LeaderSchedulerConfig::new( - Some(bootstrap_height), - Some(leader_rotation_interval), - Some(seed_rotation_interval), - Some(active_window_length), - ); - - let mut leader_scheduler = LeaderScheduler::new(&leader_scheduler_config); - leader_scheduler.bootstrap_leader = bootstrap_leader_id; - - // Create mint and bank - let mint = Mint::new_with_leader(10000, bootstrap_leader_id, 500); - let bank = Bank::new(&mint); - let last_id = mint - .create_entries() - .last() - .expect("Mint should not create empty genesis entries") - .id; - let initial_vote_height = 1; - - // No schedule generated yet, so for all heights < bootstrap height, the - // max height will be bootstrap leader - assert_eq!( - leader_scheduler.max_height_for_leader(0), - Some(bootstrap_height) - ); - assert_eq!( - leader_scheduler.max_height_for_leader(bootstrap_height - 1), - Some(bootstrap_height) - ); - assert_eq!( - leader_scheduler.max_height_for_leader(bootstrap_height), - None - ); - - // Test when the active set == 1 node - - // Generate schedule where the bootstrap leader will be the only - // choice because the active set is empty. Thus if the schedule - // was generated on PoH height bootstrap_height + n * seed_rotation_interval, - // then the same leader will be in power until PoH height - // bootstrap_height + (n + 1) * seed_rotation_interval - leader_scheduler.generate_schedule(bootstrap_height, &bank); - assert_eq!( - leader_scheduler.max_height_for_leader(bootstrap_height), - Some(bootstrap_height + seed_rotation_interval) - ); - assert_eq!( - leader_scheduler.max_height_for_leader(bootstrap_height - 1), - None - ); - leader_scheduler.generate_schedule(bootstrap_height + seed_rotation_interval, &bank); - assert_eq!( - leader_scheduler.max_height_for_leader(bootstrap_height + seed_rotation_interval), - Some(bootstrap_height + 2 * seed_rotation_interval) - ); - assert_eq!( - leader_scheduler.max_height_for_leader(bootstrap_height + seed_rotation_interval - 1), - None - ); - - leader_scheduler.reset(); - - // Now test when the active set > 1 node - - // Create and add validator to the active set - let validator_keypair = Keypair::new(); - let validator_id = validator_keypair.pubkey(); - - // Create a vote account for the validator - bank.transfer(5, &mint.keypair(), validator_id, last_id) - .unwrap(); - let new_validator_vote_account = - create_vote_account(&validator_keypair, &bank, 1, mint.last_id()).unwrap(); - push_vote( - &new_validator_vote_account, - &bank, - initial_vote_height, - mint.last_id(), - ); - - // Create a vote account for the leader - bank.transfer(5, &mint.keypair(), bootstrap_leader_id, last_id) - .unwrap(); - let new_leader_vote_account = - create_vote_account(&bootstrap_leader_keypair, &bank, 1, mint.last_id()).unwrap(); - - // Add leader to the active set - push_vote( - &new_leader_vote_account, - &bank, - initial_vote_height, - mint.last_id(), - ); - - // Generate the schedule - leader_scheduler.generate_schedule(bootstrap_height, &bank); - - assert_eq!( - leader_scheduler.max_height_for_leader(bootstrap_height), - Some(bootstrap_height + leader_rotation_interval) - ); - assert_eq!( - leader_scheduler.max_height_for_leader(bootstrap_height - 1), - None - ); - assert_eq!( - leader_scheduler.max_height_for_leader(bootstrap_height + leader_rotation_interval), - Some(bootstrap_height + 2 * leader_rotation_interval) - ); - assert_eq!( - leader_scheduler.max_height_for_leader(bootstrap_height + seed_rotation_interval), - None, - ); - - leader_scheduler.generate_schedule(bootstrap_height + seed_rotation_interval, &bank); - - assert_eq!( - leader_scheduler.max_height_for_leader(bootstrap_height + seed_rotation_interval), - Some(bootstrap_height + seed_rotation_interval + leader_rotation_interval) - ); - assert_eq!( - leader_scheduler.max_height_for_leader(bootstrap_height + seed_rotation_interval - 1), - None - ); - assert_eq!( - leader_scheduler.max_height_for_leader(bootstrap_height + 2 * seed_rotation_interval), - None - ); - } -} diff --git a/book/ledger.html b/book/ledger.html deleted file mode 100644 index 507c63ca61c5a1..00000000000000 --- a/book/ledger.html +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - Ledger format - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

Ledger format

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/ledger.rs b/book/ledger.rs deleted file mode 100644 index e37314dc2abed8..00000000000000 --- a/book/ledger.rs +++ /dev/null @@ -1,1007 +0,0 @@ -//! The `ledger` module provides functions for parallel verification of the -//! Proof of History ledger as well as iterative read, append write, and random -//! access read to a persistent file-based ledger. - -use bincode::{self, deserialize_from, serialize_into, serialized_size}; -#[cfg(test)] -use budget_transaction::BudgetTransaction; -#[cfg(test)] -use chrono::prelude::Utc; -use entry::Entry; -use log::Level::Trace; -use mint::Mint; -use packet::{SharedBlob, BLOB_DATA_SIZE}; -use rayon::prelude::*; -use signature::{Keypair, KeypairUtil}; -#[cfg(test)] -use solana_sdk::hash::hash; -use solana_sdk::hash::Hash; -use solana_sdk::pubkey::Pubkey; -use std::fs::{create_dir_all, remove_dir_all, File, OpenOptions}; -use std::io::prelude::*; -use std::io::{self, BufReader, BufWriter, Seek, SeekFrom}; -use std::mem::size_of; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; -use std::path::Path; -use transaction::Transaction; -use vote_program::Vote; -use vote_transaction::VoteTransaction; - -// -// A persistent ledger is 2 files: -// ledger_path/ --+ -// +-- index <== an array of u64 offsets into data, -// | each offset points to the first bytes -// | of a u64 that contains the length of -// | the entry. To make the code smaller, -// | index[0] is set to 0, TODO: this field -// | could later be used for other stuff... -// +-- data <== concatenated instances of -// u64 length -// entry data -// -// When opening a ledger, we have the ability to "audit" it, which means we need -// to pick which file to use as "truth", and correct the other file as -// necessary, if possible. -// -// The protocol for writing the ledger is to append to the data file first, the -// index file 2nd. If the writing node is interupted while appending to the -// ledger, there are some possibilities we need to cover: -// -// 1. a partial write of data, which might be a partial write of length -// or a partial write entry data -// 2. a partial or missing write to index for that entry -// -// There is also the possibility of "unsynchronized" reading of the ledger -// during transfer across nodes via rsync (or whatever). In this case, if the -// transfer of the data file is done before the transfer of the index file, -// it's likely that the index file will be far ahead of the data file in time. -// -// The quickest and most reliable strategy for recovery is therefore to treat -// the data file as nearest to the "truth". -// -// The logic for "recovery/audit" is to open index and read backwards from the -// last u64-aligned entry to get to where index and data agree (i.e. where a -// successful deserialization of an entry can be performed), then truncate -// both files to this syncrhonization point. -// - -// ledger window -#[derive(Debug)] -pub struct LedgerWindow { - index: BufReader, - data: BufReader, -} - -pub const LEDGER_DATA_FILE: &str = "data"; -const LEDGER_INDEX_FILE: &str = "index"; - -// use a CONST because there's a cast, and we don't want "sizeof:: as u64"... -const SIZEOF_U64: u64 = size_of::() as u64; - -#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] -fn err_bincode_to_io(e: Box) -> io::Error { - io::Error::new(io::ErrorKind::Other, e.to_string()) -} - -fn read_entry(file: &mut A, len: u64) -> io::Result { - deserialize_from(file.take(len)).map_err(err_bincode_to_io) -} - -fn entry_at(file: &mut A, at: u64) -> io::Result { - let len = u64_at(file, at)?; - - read_entry(file, len) -} - -fn next_entry(file: &mut A) -> io::Result { - let len = deserialize_from(file.take(SIZEOF_U64)).map_err(err_bincode_to_io)?; - read_entry(file, len) -} - -fn u64_at(file: &mut A, at: u64) -> io::Result { - file.seek(SeekFrom::Start(at))?; - deserialize_from(file.take(SIZEOF_U64)).map_err(err_bincode_to_io) -} - -impl LedgerWindow { - // opens a Ledger in directory, provides "infinite" window - // - pub fn open(ledger_path: &str) -> io::Result { - let ledger_path = Path::new(&ledger_path); - - let index = File::open(ledger_path.join(LEDGER_INDEX_FILE))?; - let index = BufReader::new(index); - let data = File::open(ledger_path.join(LEDGER_DATA_FILE))?; - let data = BufReader::with_capacity(BLOB_DATA_SIZE, data); - - Ok(LedgerWindow { index, data }) - } - - pub fn get_entry(&mut self, index: u64) -> io::Result { - let offset = self.get_entry_offset(index)?; - entry_at(&mut self.data, offset) - } - - // Fill 'buf' with num_entries or most number of whole entries that fit into buf.len() - // - // Return tuple of (number of entries read, total size of entries read) - pub fn get_entries_bytes( - &mut self, - start_index: u64, - num_entries: u64, - buf: &mut [u8], - ) -> io::Result<(u64, u64)> { - let start_offset = self.get_entry_offset(start_index)?; - let mut total_entries = 0; - let mut end_offset = 0; - for i in 0..num_entries { - let offset = self.get_entry_offset(start_index + i)?; - let len = u64_at(&mut self.data, offset)?; - let cur_end_offset = offset + len + SIZEOF_U64; - if (cur_end_offset - start_offset) > buf.len() as u64 { - break; - } - end_offset = cur_end_offset; - total_entries += 1; - } - - if total_entries == 0 { - return Ok((0, 0)); - } - - let read_len = end_offset - start_offset; - self.data.seek(SeekFrom::Start(start_offset))?; - let fread_len = self.data.read(&mut buf[..read_len as usize])? as u64; - if fread_len != read_len { - return Err(io::Error::new( - io::ErrorKind::Other, - format!( - "entry read_len({}) doesn't match expected ({})", - fread_len, read_len - ), - )); - } - Ok((total_entries, read_len)) - } - - fn get_entry_offset(&mut self, index: u64) -> io::Result { - u64_at(&mut self.index, index * SIZEOF_U64) - } -} - -pub fn verify_ledger(ledger_path: &str) -> io::Result<()> { - let ledger_path = Path::new(&ledger_path); - - let index = File::open(ledger_path.join(LEDGER_INDEX_FILE))?; - - let index_len = index.metadata()?.len(); - - if index_len % SIZEOF_U64 != 0 { - Err(io::Error::new( - io::ErrorKind::Other, - format!("index is not a multiple of {} bytes long", SIZEOF_U64), - ))?; - } - let mut index = BufReader::new(index); - - let data = File::open(ledger_path.join(LEDGER_DATA_FILE))?; - let mut data = BufReader::with_capacity(BLOB_DATA_SIZE, data); - - let mut last_data_offset = 0; - let mut index_offset = 0; - let mut data_read = 0; - let mut last_len = 0; - let mut i = 0; - - while index_offset < index_len { - let data_offset = u64_at(&mut index, index_offset)?; - - if last_data_offset + last_len != data_offset { - Err(io::Error::new( - io::ErrorKind::Other, - format!( - "at entry[{}], a gap or an overlap last_offset {} offset {} last_len {}", - i, last_data_offset, data_offset, last_len - ), - ))?; - } - - match entry_at(&mut data, data_offset) { - Err(e) => Err(io::Error::new( - io::ErrorKind::Other, - format!( - "entry[{}] deserialize() failed at offset {}, err: {}", - index_offset / SIZEOF_U64, - data_offset, - e.to_string(), - ), - ))?, - Ok(entry) => { - last_len = serialized_size(&entry).map_err(err_bincode_to_io)? + SIZEOF_U64 - } - } - - last_data_offset = data_offset; - data_read += last_len; - index_offset += SIZEOF_U64; - i += 1; - } - let data = data.into_inner(); - if data_read != data.metadata()?.len() { - Err(io::Error::new( - io::ErrorKind::Other, - "garbage on end of data file", - ))?; - } - Ok(()) -} - -fn recover_ledger(ledger_path: &str) -> io::Result<()> { - let ledger_path = Path::new(ledger_path); - let mut index = OpenOptions::new() - .write(true) - .read(true) - .open(ledger_path.join(LEDGER_INDEX_FILE))?; - - let mut data = OpenOptions::new() - .write(true) - .read(true) - .open(ledger_path.join(LEDGER_DATA_FILE))?; - - // first, truncate to a multiple of SIZEOF_U64 - let len = index.metadata()?.len(); - - if len % SIZEOF_U64 != 0 { - trace!("recover: trimming index len to {}", len - len % SIZEOF_U64); - index.set_len(len - (len % SIZEOF_U64))?; - } - - // next, pull index offsets off one at a time until the last one points - // to a valid entry deserialization offset... - loop { - let len = index.metadata()?.len(); - trace!("recover: index len:{}", len); - - // should never happen - if len < SIZEOF_U64 { - trace!("recover: error index len {} too small", len); - - Err(io::Error::new(io::ErrorKind::Other, "empty ledger index"))?; - } - - let offset = u64_at(&mut index, len - SIZEOF_U64)?; - trace!("recover: offset[{}]: {}", (len / SIZEOF_U64) - 1, offset); - - match entry_at(&mut data, offset) { - Ok(entry) => { - trace!("recover: entry[{}]: {:?}", (len / SIZEOF_U64) - 1, entry); - - let entry_len = serialized_size(&entry).map_err(err_bincode_to_io)?; - - trace!("recover: entry_len: {}", entry_len); - - // now trim data file to size... - data.set_len(offset + SIZEOF_U64 + entry_len)?; - - trace!( - "recover: trimmed data file to {}", - offset + SIZEOF_U64 + entry_len - ); - - break; // all good - } - Err(_err) => { - trace!( - "recover: no entry recovered at {} {}", - offset, - _err.to_string() - ); - index.set_len(len - SIZEOF_U64)?; - } - } - } - if log_enabled!(Trace) { - let num_entries = index.metadata()?.len() / SIZEOF_U64; - trace!("recover: done. {} entries", num_entries); - } - - // flush everything to disk... - index.sync_all()?; - data.sync_all() -} - -// TODO?? ... we could open the files on demand to support [], but today -// LedgerWindow needs "&mut self" -// -//impl Index for LedgerWindow { -// type Output = io::Result; -// -// fn index(&mut self, index: u64) -> &io::Result { -// match u64_at(&mut self.index, index * SIZEOF_U64) { -// Ok(offset) => &entry_at(&mut self.data, offset), -// Err(e) => &Err(e), -// } -// } -//} - -#[derive(Debug)] -pub struct LedgerWriter { - index: BufWriter, - data: BufWriter, -} - -impl LedgerWriter { - // recover and open the ledger for writing - pub fn recover(ledger_path: &str) -> io::Result { - recover_ledger(ledger_path)?; - LedgerWriter::open(ledger_path, false) - } - - // opens or creates a LedgerWriter in ledger_path directory - pub fn open(ledger_path: &str, create: bool) -> io::Result { - let ledger_path = Path::new(&ledger_path); - - if create { - let _ignored = remove_dir_all(ledger_path); - create_dir_all(ledger_path)?; - } - - let index = OpenOptions::new() - .create(create) - .append(true) - .open(ledger_path.join(LEDGER_INDEX_FILE))?; - - if log_enabled!(Trace) { - let len = index.metadata()?.len(); - trace!("LedgerWriter::new: index fp:{}", len); - } - let index = BufWriter::new(index); - - let data = OpenOptions::new() - .create(create) - .append(true) - .open(ledger_path.join(LEDGER_DATA_FILE))?; - - if log_enabled!(Trace) { - let len = data.metadata()?.len(); - trace!("LedgerWriter::new: data fp:{}", len); - } - let data = BufWriter::new(data); - - Ok(LedgerWriter { index, data }) - } - - fn write_entry_noflush(&mut self, entry: &Entry) -> io::Result<()> { - let len = serialized_size(&entry).map_err(err_bincode_to_io)?; - - serialize_into(&mut self.data, &len).map_err(err_bincode_to_io)?; - if log_enabled!(Trace) { - let offset = self.data.seek(SeekFrom::Current(0))?; - trace!("write_entry: after len data fp:{}", offset); - } - - serialize_into(&mut self.data, &entry).map_err(err_bincode_to_io)?; - if log_enabled!(Trace) { - let offset = self.data.seek(SeekFrom::Current(0))?; - trace!("write_entry: after entry data fp:{}", offset); - } - - let offset = self.data.seek(SeekFrom::Current(0))? - len - SIZEOF_U64; - trace!("write_entry: offset:{} len:{}", offset, len); - - serialize_into(&mut self.index, &offset).map_err(err_bincode_to_io)?; - - if log_enabled!(Trace) { - let offset = self.index.seek(SeekFrom::Current(0))?; - trace!("write_entry: end index fp:{}", offset); - } - Ok(()) - } - - pub fn write_entry(&mut self, entry: &Entry) -> io::Result<()> { - self.write_entry_noflush(&entry)?; - self.index.flush()?; - self.data.flush()?; - Ok(()) - } - - pub fn write_entries<'a, I>(&mut self, entries: I) -> io::Result<()> - where - I: IntoIterator, - { - for entry in entries { - self.write_entry_noflush(&entry)?; - } - self.index.flush()?; - self.data.flush()?; - Ok(()) - } -} - -#[derive(Debug)] -pub struct LedgerReader { - data: BufReader, -} - -impl Iterator for LedgerReader { - type Item = io::Result; - - fn next(&mut self) -> Option> { - match next_entry(&mut self.data) { - Ok(entry) => Some(Ok(entry)), - Err(_) => None, - } - } -} - -/// Return an iterator for all the entries in the given file. -pub fn read_ledger( - ledger_path: &str, - recover: bool, -) -> io::Result>> { - if recover { - recover_ledger(ledger_path)?; - } - - let ledger_path = Path::new(&ledger_path); - let data = File::open(ledger_path.join(LEDGER_DATA_FILE))?; - let data = BufReader::new(data); - - Ok(LedgerReader { data }) -} - -// a Block is a slice of Entries -pub trait Block { - /// Verifies the hashes and counts of a slice of transactions are all consistent. - fn verify(&self, start_hash: &Hash) -> bool; - fn to_blobs(&self) -> Vec; - fn to_blobs_with_id(&self, id: Pubkey, start_id: u64, addr: &SocketAddr) -> Vec; - fn votes(&self) -> Vec<(Pubkey, Vote, Hash)>; -} - -impl Block for [Entry] { - fn verify(&self, start_hash: &Hash) -> bool { - let genesis = [Entry::new_tick(start_hash, 0, start_hash)]; - let entry_pairs = genesis.par_iter().chain(self).zip(self); - entry_pairs.all(|(x0, x1)| { - let r = x1.verify(&x0.id); - if !r { - warn!( - "entry invalid!: x0: {:?}, x1: {:?} num txs: {}", - x0.id, - x1.id, - x1.transactions.len() - ); - } - r - }) - } - - fn to_blobs_with_id(&self, id: Pubkey, start_idx: u64, addr: &SocketAddr) -> Vec { - self.iter() - .enumerate() - .map(|(i, entry)| entry.to_blob(Some(start_idx + i as u64), Some(id), Some(&addr))) - .collect() - } - - fn to_blobs(&self) -> Vec { - let default_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0); - self.to_blobs_with_id(Pubkey::default(), 0, &default_addr) - } - - fn votes(&self) -> Vec<(Pubkey, Vote, Hash)> { - self.iter() - .flat_map(|entry| { - entry - .transactions - .iter() - .flat_map(VoteTransaction::get_votes) - }).collect() - } -} - -/// Creates the next entries for given transactions, outputs -/// updates start_hash to id of last Entry, sets num_hashes to 0 -pub fn next_entries_mut( - start_hash: &mut Hash, - num_hashes: &mut u64, - transactions: Vec, -) -> Vec { - // TODO: ?? find a number that works better than |? - // V - if transactions.is_empty() || transactions.len() == 1 { - vec![Entry::new_mut(start_hash, num_hashes, transactions)] - } else { - let mut chunk_start = 0; - let mut entries = Vec::new(); - - while chunk_start < transactions.len() { - let mut chunk_end = transactions.len(); - let mut upper = chunk_end; - let mut lower = chunk_start; - let mut next = chunk_end; // be optimistic that all will fit - - // binary search for how many transactions will fit in an Entry (i.e. a BLOB) - loop { - debug!( - "chunk_end {}, upper {} lower {} next {} transactions.len() {}", - chunk_end, - upper, - lower, - next, - transactions.len() - ); - if Entry::serialized_size(&transactions[chunk_start..chunk_end]) - <= BLOB_DATA_SIZE as u64 - { - next = (upper + chunk_end) / 2; - lower = chunk_end; - debug!( - "chunk_end {} fits, maybe too well? trying {}", - chunk_end, next - ); - } else { - next = (lower + chunk_end) / 2; - upper = chunk_end; - debug!("chunk_end {} doesn't fit! trying {}", chunk_end, next); - } - // same as last time - if next == chunk_end { - debug!("converged on chunk_end {}", chunk_end); - break; - } - chunk_end = next; - } - entries.push(Entry::new_mut( - start_hash, - num_hashes, - transactions[chunk_start..chunk_end].to_vec(), - )); - chunk_start = chunk_end; - } - - entries - } -} - -/// Creates the next Entries for given transactions -pub fn next_entries( - start_hash: &Hash, - num_hashes: u64, - transactions: Vec, -) -> Vec { - let mut id = *start_hash; - let mut num_hashes = num_hashes; - next_entries_mut(&mut id, &mut num_hashes, transactions) -} - -pub fn get_tmp_ledger_path(name: &str) -> String { - use std::env; - let out_dir = env::var("OUT_DIR").unwrap_or_else(|_| "target".to_string()); - let keypair = Keypair::new(); - - let path = format!("{}/tmp/ledger-{}-{}", out_dir, name, keypair.pubkey()); - - // whack any possible collision - let _ignored = remove_dir_all(&path); - - path -} - -pub fn create_tmp_ledger_with_mint(name: &str, mint: &Mint) -> String { - let path = get_tmp_ledger_path(name); - - let mut writer = LedgerWriter::open(&path, true).unwrap(); - writer.write_entries(&mint.create_entries()).unwrap(); - - path -} - -pub fn create_tmp_genesis( - name: &str, - num: u64, - bootstrap_leader_id: Pubkey, - bootstrap_leader_tokens: u64, -) -> (Mint, String) { - let mint = Mint::new_with_leader(num, bootstrap_leader_id, bootstrap_leader_tokens); - let path = create_tmp_ledger_with_mint(name, &mint); - - (mint, path) -} - -pub fn create_ticks(num_ticks: usize, mut hash: Hash) -> Vec { - let mut ticks = Vec::with_capacity(num_ticks as usize); - for _ in 0..num_ticks { - let new_tick = Entry::new(&hash, 1, vec![]); - hash = new_tick.id; - ticks.push(new_tick); - } - - ticks -} - -pub fn create_tmp_sample_ledger( - name: &str, - num_tokens: u64, - num_ending_ticks: usize, - bootstrap_leader_id: Pubkey, - bootstrap_leader_tokens: u64, -) -> (Mint, String, Vec) { - let mint = Mint::new_with_leader(num_tokens, bootstrap_leader_id, bootstrap_leader_tokens); - let path = get_tmp_ledger_path(name); - - // Create the entries - let mut genesis = mint.create_entries(); - let ticks = create_ticks(num_ending_ticks, mint.last_id()); - genesis.extend(ticks); - - let mut writer = LedgerWriter::open(&path, true).unwrap(); - writer.write_entries(&genesis.clone()).unwrap(); - - (mint, path, genesis) -} - -#[cfg(test)] -pub fn make_tiny_test_entries(num: usize) -> Vec { - let zero = Hash::default(); - let one = hash(&zero.as_ref()); - let keypair = Keypair::new(); - - let mut id = one; - let mut num_hashes = 0; - (0..num) - .map(|_| { - Entry::new_mut( - &mut id, - &mut num_hashes, - vec![Transaction::budget_new_timestamp( - &keypair, - keypair.pubkey(), - keypair.pubkey(), - Utc::now(), - one, - )], - ) - }).collect() -} - -#[cfg(test)] -mod tests { - use super::*; - use bincode::{deserialize, serialized_size}; - use budget_transaction::BudgetTransaction; - use entry::{next_entry, reconstruct_entries_from_blobs, Entry}; - use packet::{to_blobs, BLOB_DATA_SIZE, PACKET_DATA_SIZE}; - use signature::{Keypair, KeypairUtil}; - use solana_sdk::hash::hash; - use std; - use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - use transaction::Transaction; - use vote_program::Vote; - - #[test] - fn test_verify_slice() { - use logger; - logger::setup(); - let zero = Hash::default(); - let one = hash(&zero.as_ref()); - assert!(vec![][..].verify(&zero)); // base case - assert!(vec![Entry::new_tick(&zero, 0, &zero)][..].verify(&zero)); // singleton case 1 - assert!(!vec![Entry::new_tick(&zero, 0, &zero)][..].verify(&one)); // singleton case 2, bad - assert!(vec![next_entry(&zero, 0, vec![]); 2][..].verify(&zero)); // inductive step - - let mut bad_ticks = vec![next_entry(&zero, 0, vec![]); 2]; - bad_ticks[1].id = one; - assert!(!bad_ticks.verify(&zero)); // inductive step, bad - } - - fn make_test_entries() -> Vec { - let zero = Hash::default(); - let one = hash(&zero.as_ref()); - let keypair = Keypair::new(); - let vote_account = Keypair::new(); - let tx0 = Transaction::vote_new(&vote_account, Vote { tick_height: 1 }, one, 1); - let tx1 = Transaction::budget_new_timestamp( - &keypair, - keypair.pubkey(), - keypair.pubkey(), - Utc::now(), - one, - ); - // - // TODO: this magic number and the mix of transaction types - // is designed to fill up a Blob more or less exactly, - // to get near enough the the threshold that - // deserialization falls over if it uses the wrong size() - // parameter to index into blob.data() - // - // magic numbers -----------------+ - // | - // V - let mut transactions = vec![tx0; 362]; - transactions.extend(vec![tx1; 100]); - next_entries(&zero, 0, transactions) - } - - #[test] - fn test_entries_to_blobs() { - use logger; - logger::setup(); - let entries = make_test_entries(); - - let blob_q = entries.to_blobs(); - - assert_eq!(reconstruct_entries_from_blobs(blob_q).unwrap().0, entries); - } - - #[test] - fn test_bad_blobs_attack() { - use logger; - logger::setup(); - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8000); - let blobs_q = to_blobs(vec![(0, addr)]).unwrap(); // <-- attack! - assert!(reconstruct_entries_from_blobs(blobs_q).is_err()); - } - - #[test] - fn test_next_entries() { - use logger; - logger::setup(); - let id = Hash::default(); - let next_id = hash(&id.as_ref()); - let keypair = Keypair::new(); - let vote_account = Keypair::new(); - let tx_small = Transaction::vote_new(&vote_account, Vote { tick_height: 1 }, next_id, 2); - let tx_large = Transaction::budget_new(&keypair, keypair.pubkey(), 1, next_id); - - let tx_small_size = serialized_size(&tx_small).unwrap() as usize; - let tx_large_size = serialized_size(&tx_large).unwrap() as usize; - let entry_size = serialized_size(&Entry { - prev_id: Hash::default(), - num_hashes: 0, - id: Hash::default(), - transactions: vec![], - }).unwrap() as usize; - assert!(tx_small_size < tx_large_size); - assert!(tx_large_size < PACKET_DATA_SIZE); - - let threshold = (BLOB_DATA_SIZE - entry_size) / tx_small_size; - - // verify no split - let transactions = vec![tx_small.clone(); threshold]; - let entries0 = next_entries(&id, 0, transactions.clone()); - assert_eq!(entries0.len(), 1); - assert!(entries0.verify(&id)); - - // verify the split with uniform transactions - let transactions = vec![tx_small.clone(); threshold * 2]; - let entries0 = next_entries(&id, 0, transactions.clone()); - assert_eq!(entries0.len(), 2); - assert!(entries0.verify(&id)); - - // verify the split with small transactions followed by large - // transactions - let mut transactions = vec![tx_small.clone(); BLOB_DATA_SIZE / tx_small_size]; - let large_transactions = vec![tx_large.clone(); BLOB_DATA_SIZE / tx_large_size]; - - transactions.extend(large_transactions); - - let entries0 = next_entries(&id, 0, transactions.clone()); - assert!(entries0.len() >= 2); - assert!(entries0.verify(&id)); - } - - #[test] - fn test_ledger_reader_writer() { - use logger; - logger::setup(); - let ledger_path = get_tmp_ledger_path("test_ledger_reader_writer"); - let entries = make_tiny_test_entries(10); - - { - let mut writer = LedgerWriter::open(&ledger_path, true).unwrap(); - writer.write_entries(&entries.clone()).unwrap(); - // drops writer, flushes buffers - } - verify_ledger(&ledger_path).unwrap(); - - let mut read_entries = vec![]; - for x in read_ledger(&ledger_path, true).unwrap() { - let entry = x.unwrap(); - trace!("entry... {:?}", entry); - read_entries.push(entry); - } - assert_eq!(read_entries, entries); - - let mut window = LedgerWindow::open(&ledger_path).unwrap(); - - for (i, entry) in entries.iter().enumerate() { - let read_entry = window.get_entry(i as u64).unwrap(); - assert_eq!(*entry, read_entry); - } - assert!(window.get_entry(100).is_err()); - - std::fs::remove_file(Path::new(&ledger_path).join(LEDGER_DATA_FILE)).unwrap(); - // empty data file should fall over - assert!(LedgerWindow::open(&ledger_path).is_err()); - assert!(read_ledger(&ledger_path, false).is_err()); - - std::fs::remove_dir_all(ledger_path).unwrap(); - } - - fn truncated_last_entry(ledger_path: &str, entries: Vec) { - let len = { - let mut writer = LedgerWriter::open(&ledger_path, true).unwrap(); - writer.write_entries(&entries).unwrap(); - writer.data.seek(SeekFrom::Current(0)).unwrap() - }; - verify_ledger(&ledger_path).unwrap(); - - let data = OpenOptions::new() - .write(true) - .open(Path::new(&ledger_path).join(LEDGER_DATA_FILE)) - .unwrap(); - data.set_len(len - 4).unwrap(); - } - - fn garbage_on_data(ledger_path: &str, entries: Vec) { - let mut writer = LedgerWriter::open(&ledger_path, true).unwrap(); - writer.write_entries(&entries).unwrap(); - writer.data.write_all(b"hi there!").unwrap(); - } - - fn read_ledger_check(ledger_path: &str, entries: Vec, len: usize) { - let read_entries = read_ledger(&ledger_path, true).unwrap(); - let mut i = 0; - - for entry in read_entries { - assert_eq!(entry.unwrap(), entries[i]); - i += 1; - } - assert_eq!(i, len); - } - - fn ledger_window_check(ledger_path: &str, entries: Vec, len: usize) { - let mut window = LedgerWindow::open(&ledger_path).unwrap(); - for i in 0..len { - let entry = window.get_entry(i as u64); - assert_eq!(entry.unwrap(), entries[i]); - } - } - - #[test] - fn test_recover_ledger() { - use logger; - logger::setup(); - - let entries = make_tiny_test_entries(10); - let ledger_path = get_tmp_ledger_path("test_recover_ledger"); - - // truncate data file, tests recover inside read_ledger_check() - truncated_last_entry(&ledger_path, entries.clone()); - read_ledger_check(&ledger_path, entries.clone(), entries.len() - 1); - - // truncate data file, tests recover inside LedgerWindow::new() - truncated_last_entry(&ledger_path, entries.clone()); - ledger_window_check(&ledger_path, entries.clone(), entries.len() - 1); - - // restore last entry, tests recover_ledger() inside LedgerWriter::new() - truncated_last_entry(&ledger_path, entries.clone()); - // verify should fail at first - assert!(verify_ledger(&ledger_path).is_err()); - { - let mut writer = LedgerWriter::recover(&ledger_path).unwrap(); - writer.write_entry(&entries[entries.len() - 1]).unwrap(); - } - // and be fine after recover() - verify_ledger(&ledger_path).unwrap(); - - read_ledger_check(&ledger_path, entries.clone(), entries.len()); - ledger_window_check(&ledger_path, entries.clone(), entries.len()); - - // make it look like data is newer in time, check reader... - garbage_on_data(&ledger_path, entries.clone()); - read_ledger_check(&ledger_path, entries.clone(), entries.len()); - - // make it look like data is newer in time, check window... - garbage_on_data(&ledger_path, entries.clone()); - ledger_window_check(&ledger_path, entries.clone(), entries.len()); - - // make it look like data is newer in time, check writer... - garbage_on_data(&ledger_path, entries[..entries.len() - 1].to_vec()); - assert!(verify_ledger(&ledger_path).is_err()); - { - let mut writer = LedgerWriter::recover(&ledger_path).unwrap(); - writer.write_entry(&entries[entries.len() - 1]).unwrap(); - } - verify_ledger(&ledger_path).unwrap(); - read_ledger_check(&ledger_path, entries.clone(), entries.len()); - ledger_window_check(&ledger_path, entries.clone(), entries.len()); - let _ignored = remove_dir_all(&ledger_path); - } - - #[test] - fn test_verify_ledger() { - use logger; - logger::setup(); - - let entries = make_tiny_test_entries(10); - let ledger_path = get_tmp_ledger_path("test_verify_ledger"); - { - let mut writer = LedgerWriter::open(&ledger_path, true).unwrap(); - writer.write_entries(&entries).unwrap(); - } - // TODO more cases that make ledger_verify() fail - // assert!(verify_ledger(&ledger_path).is_err()); - - assert!(verify_ledger(&ledger_path).is_ok()); - let _ignored = remove_dir_all(&ledger_path); - } - - #[test] - fn test_get_entries_bytes() { - use logger; - logger::setup(); - let entries = make_tiny_test_entries(10); - let ledger_path = get_tmp_ledger_path("test_raw_entries"); - { - let mut writer = LedgerWriter::open(&ledger_path, true).unwrap(); - writer.write_entries(&entries).unwrap(); - } - - let mut window = LedgerWindow::open(&ledger_path).unwrap(); - let mut buf = [0; 1024]; - let (num_entries, bytes) = window.get_entries_bytes(0, 1, &mut buf).unwrap(); - let bytes = bytes as usize; - assert_eq!(num_entries, 1); - let entry: Entry = deserialize(&buf[size_of::()..bytes]).unwrap(); - assert_eq!(entry, entries[0]); - - let (num_entries, bytes2) = window.get_entries_bytes(0, 2, &mut buf).unwrap(); - let bytes2 = bytes2 as usize; - assert_eq!(num_entries, 2); - assert!(bytes2 > bytes); - for (i, ref entry) in entries.iter().enumerate() { - info!("entry[{}] = {:?}", i, entry.id); - } - - let entry: Entry = deserialize(&buf[size_of::()..bytes]).unwrap(); - assert_eq!(entry, entries[0]); - - let entry: Entry = deserialize(&buf[bytes + size_of::()..bytes2]).unwrap(); - assert_eq!(entry, entries[1]); - - // buf size part-way into entry[1], should just return entry[0] - let mut buf = vec![0; bytes + size_of::() + 1]; - let (num_entries, bytes3) = window.get_entries_bytes(0, 2, &mut buf).unwrap(); - assert_eq!(num_entries, 1); - let bytes3 = bytes3 as usize; - assert_eq!(bytes3, bytes); - - let mut buf = vec![0; bytes2 - 1]; - let (num_entries, bytes4) = window.get_entries_bytes(0, 2, &mut buf).unwrap(); - assert_eq!(num_entries, 1); - let bytes4 = bytes4 as usize; - assert_eq!(bytes4, bytes); - - let mut buf = vec![0; bytes + size_of::() - 1]; - let (num_entries, bytes5) = window.get_entries_bytes(0, 2, &mut buf).unwrap(); - assert_eq!(num_entries, 1); - let bytes5 = bytes5 as usize; - assert_eq!(bytes5, bytes); - - let mut buf = vec![0; bytes * 2]; - let (num_entries, bytes6) = window.get_entries_bytes(9, 1, &mut buf).unwrap(); - assert_eq!(num_entries, 1); - let bytes6 = bytes6 as usize; - - let entry: Entry = deserialize(&buf[size_of::()..bytes6]).unwrap(); - assert_eq!(entry, entries[9]); - - // Read out of range - assert!(window.get_entries_bytes(20, 2, &mut buf).is_err()); - - let _ignored = remove_dir_all(&ledger_path); - } -} diff --git a/book/ledger_write_stage.rs b/book/ledger_write_stage.rs deleted file mode 100644 index e41f75f8a66b80..00000000000000 --- a/book/ledger_write_stage.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! The `ledger_write_stage` module implements the ledger write stage. It -//! writes entries to the given writer, which is typically a file - -use counter::Counter; -use entry::{EntryReceiver, EntrySender}; -use ledger::LedgerWriter; -use log::Level; -use result::{Error, Result}; -use service::Service; -use solana_sdk::timing::duration_as_ms; -use std::sync::atomic::AtomicUsize; -use std::sync::mpsc::{channel, RecvTimeoutError}; -use std::thread::{self, Builder, JoinHandle}; -use std::time::{Duration, Instant}; - -pub struct LedgerWriteStage { - write_thread: JoinHandle<()>, -} - -impl LedgerWriteStage { - pub fn write( - ledger_writer: Option<&mut LedgerWriter>, - entry_receiver: &EntryReceiver, - entry_sender: &EntrySender, - ) -> Result<()> { - let mut ventries = Vec::new(); - let mut received_entries = entry_receiver.recv_timeout(Duration::new(1, 0))?; - let mut num_new_entries = 0; - let now = Instant::now(); - - loop { - num_new_entries += received_entries.len(); - ventries.push(received_entries); - - if let Ok(n) = entry_receiver.try_recv() { - received_entries = n; - } else { - break; - } - } - - if let Some(ledger_writer) = ledger_writer { - ledger_writer.write_entries(ventries.iter().flatten())?; - } - - inc_new_counter_info!("ledger_writer_stage-entries_received", num_new_entries); - for entries in ventries { - entry_sender.send(entries)?; - } - inc_new_counter_info!( - "ledger_writer_stage-time_ms", - duration_as_ms(&now.elapsed()) as usize - ); - Ok(()) - } - - pub fn new(ledger_path: Option<&str>, entry_receiver: EntryReceiver) -> (Self, EntryReceiver) { - let mut ledger_writer = ledger_path.map(|p| LedgerWriter::open(p, false).unwrap()); - - let (entry_sender, entry_forwarder) = channel(); - let write_thread = Builder::new() - .name("solana-ledger-writer".to_string()) - .spawn(move || loop { - if let Err(e) = Self::write(ledger_writer.as_mut(), &entry_receiver, &entry_sender) - { - match e { - Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => { - break; - } - Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), - _ => { - inc_new_counter_info!( - "ledger_writer_stage-write_and_send_entries-error", - 1 - ); - error!("{:?}", e); - } - } - }; - }).unwrap(); - - (LedgerWriteStage { write_thread }, entry_forwarder) - } -} - -impl Service for LedgerWriteStage { - type JoinReturnType = (); - - fn join(self) -> thread::Result<()> { - self.write_thread.join() - } -} diff --git a/book/lib.rs b/book/lib.rs deleted file mode 100644 index 60ecef26a67aa7..00000000000000 --- a/book/lib.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! The `solana` library implements the Solana high-performance blockchain architecture. -//! It includes a full Rust implementation of the architecture (see -//! [Fullnode](server/struct.Fullnode.html)) as well as hooks to GPU implementations of its most -//! paralellizable components (i.e. [SigVerify](sigverify/index.html)). It also includes -//! command-line tools to spin up fullnodes and a Rust library -//! (see [ThinClient](thin_client/struct.ThinClient.html)) to interact with them. -//! - -#![cfg_attr(feature = "unstable", feature(test))] -#[macro_use] -pub mod counter; -pub mod bank; -pub mod banking_stage; -pub mod blob_fetch_stage; -pub mod bloom; -pub mod bpf_loader; -pub mod broadcast_stage; -pub mod budget_expr; -pub mod budget_instruction; -pub mod budget_transaction; -#[cfg(feature = "chacha")] -pub mod chacha; -#[cfg(all(feature = "chacha", feature = "cuda"))] -pub mod chacha_cuda; -pub mod client; -pub mod crds; -pub mod crds_gossip; -pub mod crds_gossip_error; -pub mod crds_gossip_pull; -pub mod crds_gossip_push; -pub mod crds_traits_impls; -pub mod crds_value; -#[macro_use] -pub mod contact_info; -pub mod budget_program; -pub mod cluster_info; -pub mod compute_leader_finality_service; -pub mod db_ledger; -pub mod db_window; -pub mod entry; -#[cfg(feature = "erasure")] -pub mod erasure; -pub mod fetch_stage; -pub mod fullnode; -pub mod leader_scheduler; -pub mod ledger; -pub mod ledger_write_stage; -pub mod loader_transaction; -pub mod logger; -pub mod mint; -pub mod native_loader; -pub mod ncp; -pub mod netutil; -pub mod packet; -pub mod payment_plan; -pub mod poh; -pub mod poh_recorder; -pub mod poh_service; -pub mod recvmmsg; -pub mod replicate_stage; -pub mod replicator; -pub mod result; -pub mod retransmit_stage; -pub mod rpc; -pub mod rpc_pubsub; -pub mod rpc_request; -pub mod service; -pub mod signature; -pub mod sigverify; -pub mod sigverify_stage; -pub mod storage_program; -pub mod storage_stage; -pub mod storage_transaction; -pub mod store_ledger_stage; -pub mod streamer; -pub mod system_program; -pub mod system_transaction; -pub mod thin_client; -pub mod token_program; -pub mod tpu; -pub mod tpu_forwarder; -pub mod transaction; -pub mod tvu; -pub mod vote_program; -pub mod vote_stage; -pub mod vote_transaction; -pub mod wallet; -pub mod window; -pub mod window_service; -extern crate bincode; -extern crate bs58; -extern crate bv; -extern crate byteorder; -extern crate bytes; -extern crate chrono; -extern crate clap; -extern crate dirs; -extern crate elf; -extern crate generic_array; -#[cfg(test)] -#[cfg(any(feature = "chacha", feature = "cuda"))] -#[macro_use] -extern crate hex_literal; -extern crate indexmap; -extern crate ipnetwork; -extern crate itertools; -extern crate libc; -extern crate libloading; -#[macro_use] -extern crate log; -extern crate nix; -extern crate pnet_datalink; -extern crate rayon; -extern crate reqwest; -extern crate ring; -extern crate rocksdb; -extern crate serde; -#[macro_use] -extern crate serde_derive; -#[macro_use] -extern crate serde_json; -extern crate serde_cbor; -extern crate sha2; -extern crate socket2; -extern crate solana_drone; -extern crate solana_jsonrpc_core as jsonrpc_core; -extern crate solana_jsonrpc_http_server as jsonrpc_http_server; -#[macro_use] -extern crate solana_jsonrpc_macros as jsonrpc_macros; -extern crate solana_jsonrpc_pubsub as jsonrpc_pubsub; -extern crate solana_jsonrpc_ws_server as jsonrpc_ws_server; -extern crate solana_metrics; -extern crate solana_sdk; -extern crate sys_info; -extern crate tokio; -extern crate tokio_codec; -extern crate untrusted; - -#[cfg(test)] -#[macro_use] -extern crate matches; - -extern crate rand; diff --git a/book/loader_transaction.rs b/book/loader_transaction.rs deleted file mode 100644 index 10bc3eecb9e4b6..00000000000000 --- a/book/loader_transaction.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! The `dynamic_transaction` module provides functionality for loading and calling a program - -use signature::{Keypair, KeypairUtil}; -use solana_sdk::hash::Hash; -use solana_sdk::loader_instruction::LoaderInstruction; -use solana_sdk::pubkey::Pubkey; -use transaction::Transaction; - -pub trait LoaderTransaction { - fn loader_write( - from_keypair: &Keypair, - loader: Pubkey, - offset: u32, - bytes: Vec, - last_id: Hash, - fee: u64, - ) -> Self; - - fn loader_finalize(from_keypair: &Keypair, loader: Pubkey, last_id: Hash, fee: u64) -> Self; -} - -impl LoaderTransaction for Transaction { - fn loader_write( - from_keypair: &Keypair, - loader: Pubkey, - offset: u32, - bytes: Vec, - last_id: Hash, - fee: u64, - ) -> Self { - trace!( - "LoaderTransaction::Write() program {:?} offset {} length {}", - from_keypair.pubkey(), - offset, - bytes.len() - ); - let instruction = LoaderInstruction::Write { offset, bytes }; - Transaction::new(from_keypair, &[], loader, &instruction, last_id, fee) - } - - fn loader_finalize(from_keypair: &Keypair, loader: Pubkey, last_id: Hash, fee: u64) -> Self { - trace!( - "LoaderTransaction::Finalize() program {:?}", - from_keypair.pubkey(), - ); - let instruction = LoaderInstruction::Finalize; - Transaction::new(from_keypair, &[], loader, &instruction, last_id, fee) - } -} diff --git a/book/logger.rs b/book/logger.rs deleted file mode 100644 index 08375adf57e32a..00000000000000 --- a/book/logger.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! The `logger` module provides a setup function for `env_logger`. Its only function, -//! `setup()` may be called multiple times. - -use std::sync::{Once, ONCE_INIT}; -extern crate env_logger; - -static INIT: Once = ONCE_INIT; - -/// Setup function that is only run once, even if called multiple times. -pub fn setup() { - INIT.call_once(|| { - env_logger::Builder::from_default_env() - .default_format_timestamp_nanos(true) - .init(); - }); -} diff --git a/book/mark.min.js b/book/mark.min.js deleted file mode 100644 index 16362318834726..00000000000000 --- a/book/mark.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*!*************************************************** -* mark.js v8.11.1 -* https://markjs.io/ -* Copyright (c) 2014–2018, Julian Kühnel -* Released under the MIT license https://git.io/vwTVl -*****************************************************/ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Mark=t()}(this,function(){"use strict";var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},n=function(){function e(e,t){for(var n=0;n1&&void 0!==arguments[1])||arguments[1],i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:5e3;t(this,e),this.ctx=n,this.iframes=r,this.exclude=i,this.iframesTimeout=o}return n(e,[{key:"getContexts",value:function(){var e=[];return(void 0!==this.ctx&&this.ctx?NodeList.prototype.isPrototypeOf(this.ctx)?Array.prototype.slice.call(this.ctx):Array.isArray(this.ctx)?this.ctx:"string"==typeof this.ctx?Array.prototype.slice.call(document.querySelectorAll(this.ctx)):[this.ctx]:[]).forEach(function(t){var n=e.filter(function(e){return e.contains(t)}).length>0;-1!==e.indexOf(t)||n||e.push(t)}),e}},{key:"getIframeContents",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){},r=void 0;try{var i=e.contentWindow;if(r=i.document,!i||!r)throw new Error("iframe inaccessible")}catch(e){n()}r&&t(r)}},{key:"isIframeBlank",value:function(e){var t="about:blank",n=e.getAttribute("src").trim();return e.contentWindow.location.href===t&&n!==t&&n}},{key:"observeIframeLoad",value:function(e,t,n){var r=this,i=!1,o=null,a=function a(){if(!i){i=!0,clearTimeout(o);try{r.isIframeBlank(e)||(e.removeEventListener("load",a),r.getIframeContents(e,t,n))}catch(e){n()}}};e.addEventListener("load",a),o=setTimeout(a,this.iframesTimeout)}},{key:"onIframeReady",value:function(e,t,n){try{"complete"===e.contentWindow.document.readyState?this.isIframeBlank(e)?this.observeIframeLoad(e,t,n):this.getIframeContents(e,t,n):this.observeIframeLoad(e,t,n)}catch(e){n()}}},{key:"waitForIframes",value:function(e,t){var n=this,r=0;this.forEachIframe(e,function(){return!0},function(e){r++,n.waitForIframes(e.querySelector("html"),function(){--r||t()})},function(e){e||t()})}},{key:"forEachIframe",value:function(t,n,r){var i=this,o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},a=t.querySelectorAll("iframe"),s=a.length,c=0;a=Array.prototype.slice.call(a);var u=function(){--s<=0&&o(c)};s||u(),a.forEach(function(t){e.matches(t,i.exclude)?u():i.onIframeReady(t,function(e){n(t)&&(c++,r(e)),u()},u)})}},{key:"createIterator",value:function(e,t,n){return document.createNodeIterator(e,t,n,!1)}},{key:"createInstanceOnIframe",value:function(t){return new e(t.querySelector("html"),this.iframes)}},{key:"compareNodeIframe",value:function(e,t,n){if(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_PRECEDING){if(null===t)return!0;if(t.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)return!0}return!1}},{key:"getIteratorNode",value:function(e){var t=e.previousNode();return{prevNode:t,node:null===t?e.nextNode():e.nextNode()&&e.nextNode()}}},{key:"checkIframeFilter",value:function(e,t,n,r){var i=!1,o=!1;return r.forEach(function(e,t){e.val===n&&(i=t,o=e.handled)}),this.compareNodeIframe(e,t,n)?(!1!==i||o?!1===i||o||(r[i].handled=!0):r.push({val:n,handled:!0}),!0):(!1===i&&r.push({val:n,handled:!1}),!1)}},{key:"handleOpenIframes",value:function(e,t,n,r){var i=this;e.forEach(function(e){e.handled||i.getIframeContents(e.val,function(e){i.createInstanceOnIframe(e).forEachNode(t,n,r)})})}},{key:"iterateThroughNodes",value:function(e,t,n,r,i){for(var o,a=this,s=this.createIterator(t,e,r),c=[],u=[],l=void 0,h=void 0;void 0,o=a.getIteratorNode(s),h=o.prevNode,l=o.node;)this.iframes&&this.forEachIframe(t,function(e){return a.checkIframeFilter(l,h,e,c)},function(t){a.createInstanceOnIframe(t).forEachNode(e,function(e){return u.push(e)},r)}),u.push(l);u.forEach(function(e){n(e)}),this.iframes&&this.handleOpenIframes(c,e,n,r),i()}},{key:"forEachNode",value:function(e,t,n){var r=this,i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},o=this.getContexts(),a=o.length;a||i(),o.forEach(function(o){var s=function(){r.iterateThroughNodes(e,o,t,n,function(){--a<=0&&i()})};r.iframes?r.waitForIframes(o,s):s()})}}],[{key:"matches",value:function(e,t){var n="string"==typeof t?[t]:t,r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector;if(r){var i=!1;return n.every(function(t){return!r.call(e,t)||(i=!0,!1)}),i}return!1}}]),e}(),o=function(){function e(n){t(this,e),this.opt=r({},{diacritics:!0,synonyms:{},accuracy:"partially",caseSensitive:!1,ignoreJoiners:!1,ignorePunctuation:[],wildcards:"disabled"},n)}return n(e,[{key:"create",value:function(e){return"disabled"!==this.opt.wildcards&&(e=this.setupWildcardsRegExp(e)),e=this.escapeStr(e),Object.keys(this.opt.synonyms).length&&(e=this.createSynonymsRegExp(e)),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),this.opt.diacritics&&(e=this.createDiacriticsRegExp(e)),e=this.createMergedBlanksRegExp(e),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.createJoinersRegExp(e)),"disabled"!==this.opt.wildcards&&(e=this.createWildcardsRegExp(e)),e=this.createAccuracyRegExp(e),new RegExp(e,"gm"+(this.opt.caseSensitive?"":"i"))}},{key:"escapeStr",value:function(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}},{key:"createSynonymsRegExp",value:function(e){var t=this.opt.synonyms,n=this.opt.caseSensitive?"":"i",r=this.opt.ignoreJoiners||this.opt.ignorePunctuation.length?"\0":"";for(var i in t)if(t.hasOwnProperty(i)){var o=t[i],a="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(i):this.escapeStr(i),s="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(o):this.escapeStr(o);""!==a&&""!==s&&(e=e.replace(new RegExp("("+this.escapeStr(a)+"|"+this.escapeStr(s)+")","gm"+n),r+"("+this.processSynonyms(a)+"|"+this.processSynonyms(s)+")"+r))}return e}},{key:"processSynonyms",value:function(e){return(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),e}},{key:"setupWildcardsRegExp",value:function(e){return(e=e.replace(/(?:\\)*\?/g,function(e){return"\\"===e.charAt(0)?"?":""})).replace(/(?:\\)*\*/g,function(e){return"\\"===e.charAt(0)?"*":""})}},{key:"createWildcardsRegExp",value:function(e){var t="withSpaces"===this.opt.wildcards;return e.replace(/\u0001/g,t?"[\\S\\s]?":"\\S?").replace(/\u0002/g,t?"[\\S\\s]*?":"\\S*")}},{key:"setupIgnoreJoinersRegExp",value:function(e){return e.replace(/[^(|)\\]/g,function(e,t,n){var r=n.charAt(t+1);return/[(|)\\]/.test(r)||""===r?e:e+"\0"})}},{key:"createJoinersRegExp",value:function(e){var t=[],n=this.opt.ignorePunctuation;return Array.isArray(n)&&n.length&&t.push(this.escapeStr(n.join(""))),this.opt.ignoreJoiners&&t.push("\\u00ad\\u200b\\u200c\\u200d"),t.length?e.split(/\u0000+/).join("["+t.join("")+"]*"):e}},{key:"createDiacriticsRegExp",value:function(e){var t=this.opt.caseSensitive?"":"i",n=this.opt.caseSensitive?["aàáảãạăằắẳẵặâầấẩẫậäåāą","AÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćč","CÇĆČ","dđď","DĐĎ","eèéẻẽẹêềếểễệëěēę","EÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïī","IÌÍỈĨỊÎÏĪ","lł","LŁ","nñňń","NÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøō","OÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rř","RŘ","sšśșş","SŠŚȘŞ","tťțţ","TŤȚŢ","uùúủũụưừứửữựûüůū","UÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿ","YÝỲỶỸỴŸ","zžżź","ZŽŻŹ"]:["aàáảãạăằắẳẵặâầấẩẫậäåāąAÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćčCÇĆČ","dđďDĐĎ","eèéẻẽẹêềếểễệëěēęEÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïīIÌÍỈĨỊÎÏĪ","lłLŁ","nñňńNÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøōOÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rřRŘ","sšśșşSŠŚȘŞ","tťțţTŤȚŢ","uùúủũụưừứửữựûüůūUÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿYÝỲỶỸỴŸ","zžżźZŽŻŹ"],r=[];return e.split("").forEach(function(i){n.every(function(n){if(-1!==n.indexOf(i)){if(r.indexOf(n)>-1)return!1;e=e.replace(new RegExp("["+n+"]","gm"+t),"["+n+"]"),r.push(n)}return!0})}),e}},{key:"createMergedBlanksRegExp",value:function(e){return e.replace(/[\s]+/gim,"[\\s]+")}},{key:"createAccuracyRegExp",value:function(e){var t=this,n=this.opt.accuracy,r="string"==typeof n?n:n.value,i="";switch(("string"==typeof n?[]:n.limiters).forEach(function(e){i+="|"+t.escapeStr(e)}),r){case"partially":default:return"()("+e+")";case"complementary":return"()([^"+(i="\\s"+(i||this.escapeStr("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~¡¿")))+"]*"+e+"[^"+i+"]*)";case"exactly":return"(^|\\s"+i+")("+e+")(?=$|\\s"+i+")"}}}]),e}(),a=function(){function a(e){t(this,a),this.ctx=e,this.ie=!1;var n=window.navigator.userAgent;(n.indexOf("MSIE")>-1||n.indexOf("Trident")>-1)&&(this.ie=!0)}return n(a,[{key:"log",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"debug",r=this.opt.log;this.opt.debug&&"object"===(void 0===r?"undefined":e(r))&&"function"==typeof r[n]&&r[n]("mark.js: "+t)}},{key:"getSeparatedKeywords",value:function(e){var t=this,n=[];return e.forEach(function(e){t.opt.separateWordSearch?e.split(" ").forEach(function(e){e.trim()&&-1===n.indexOf(e)&&n.push(e)}):e.trim()&&-1===n.indexOf(e)&&n.push(e)}),{keywords:n.sort(function(e,t){return t.length-e.length}),length:n.length}}},{key:"isNumeric",value:function(e){return Number(parseFloat(e))==e}},{key:"checkRanges",value:function(e){var t=this;if(!Array.isArray(e)||"[object Object]"!==Object.prototype.toString.call(e[0]))return this.log("markRanges() will only accept an array of objects"),this.opt.noMatch(e),[];var n=[],r=0;return e.sort(function(e,t){return e.start-t.start}).forEach(function(e){var i=t.callNoMatchOnInvalidRanges(e,r),o=i.start,a=i.end;i.valid&&(e.start=o,e.length=a-o,n.push(e),r=a)}),n}},{key:"callNoMatchOnInvalidRanges",value:function(e,t){var n=void 0,r=void 0,i=!1;return e&&void 0!==e.start?(r=(n=parseInt(e.start,10))+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&r-t>0&&r-n>0?i=!0:(this.log("Ignoring invalid or overlapping range: "+JSON.stringify(e)),this.opt.noMatch(e))):(this.log("Ignoring invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:n,end:r,valid:i}}},{key:"checkWhitespaceRanges",value:function(e,t,n){var r=void 0,i=!0,o=n.length,a=t-o,s=parseInt(e.start,10)-a;return(r=(s=s>o?o:s)+parseInt(e.length,10))>o&&(r=o,this.log("End range automatically set to the max value of "+o)),s<0||r-s<0||s>o||r>o?(i=!1,this.log("Invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)):""===n.substring(s,r).replace(/\s+/g,"")&&(i=!1,this.log("Skipping whitespace only range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:s,end:r,valid:i}}},{key:"getTextNodes",value:function(e){var t=this,n="",r=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,function(e){r.push({start:n.length,end:(n+=e.textContent).length,node:e})},function(e){return t.matchesExclude(e.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT},function(){e({value:n,nodes:r})})}},{key:"matchesExclude",value:function(e){return i.matches(e,this.opt.exclude.concat(["script","style","title","head","html"]))}},{key:"wrapRangeInTextNode",value:function(e,t,n){var r=this.opt.element?this.opt.element:"mark",i=e.splitText(t),o=i.splitText(n-t),a=document.createElement(r);return a.setAttribute("data-markjs","true"),this.opt.className&&a.setAttribute("class",this.opt.className),a.textContent=i.textContent,i.parentNode.replaceChild(a,i),o}},{key:"wrapRangeInMappedTextNode",value:function(e,t,n,r,i){var o=this;e.nodes.every(function(a,s){var c=e.nodes[s+1];if(void 0===c||c.start>t){if(!r(a.node))return!1;var u=t-a.start,l=(n>a.end?a.end:n)-a.start,h=e.value.substr(0,a.start),f=e.value.substr(l+a.start);if(a.node=o.wrapRangeInTextNode(a.node,u,l),e.value=h+f,e.nodes.forEach(function(t,n){n>=s&&(e.nodes[n].start>0&&n!==s&&(e.nodes[n].start-=l),e.nodes[n].end-=l)}),n-=l,i(a.node.previousSibling,a.start),!(n>a.end))return!1;t=a.end}return!0})}},{key:"wrapGroups",value:function(e,t,n,r){return r((e=this.wrapRangeInTextNode(e,t,t+n)).previousSibling),e}},{key:"separateGroups",value:function(e,t,n,r,i){for(var o=t.length,a=1;a-1&&r(t[a],e)&&(e=this.wrapGroups(e,s,t[a].length,i))}return e}},{key:"wrapMatches",value:function(e,t,n,r,i){var o=this,a=0===t?0:t+1;this.getTextNodes(function(t){t.nodes.forEach(function(t){t=t.node;for(var i=void 0;null!==(i=e.exec(t.textContent))&&""!==i[a];){if(o.opt.separateGroups)t=o.separateGroups(t,i,a,n,r);else{if(!n(i[a],t))continue;var s=i.index;if(0!==a)for(var c=1;c, - pubkey: Pubkey, - pub tokens: u64, - pub bootstrap_leader_id: Pubkey, - pub bootstrap_leader_tokens: u64, -} - -impl Mint { - pub fn new_with_pkcs8( - tokens: u64, - pkcs8: Vec, - bootstrap_leader_id: Pubkey, - bootstrap_leader_tokens: u64, - ) -> Self { - let keypair = - Keypair::from_pkcs8(Input::from(&pkcs8)).expect("from_pkcs8 in mint pub fn new"); - let pubkey = keypair.pubkey(); - Mint { - pkcs8, - pubkey, - tokens, - bootstrap_leader_id, - bootstrap_leader_tokens, - } - } - - pub fn new_with_leader( - tokens: u64, - bootstrap_leader: Pubkey, - bootstrap_leader_tokens: u64, - ) -> Self { - let rnd = SystemRandom::new(); - let pkcs8 = Keypair::generate_pkcs8(&rnd) - .expect("generate_pkcs8 in mint pub fn new") - .to_vec(); - Self::new_with_pkcs8(tokens, pkcs8, bootstrap_leader, bootstrap_leader_tokens) - } - - pub fn new(tokens: u64) -> Self { - let rnd = SystemRandom::new(); - let pkcs8 = Keypair::generate_pkcs8(&rnd) - .expect("generate_pkcs8 in mint pub fn new") - .to_vec(); - Self::new_with_pkcs8(tokens, pkcs8, Pubkey::default(), 0) - } - - pub fn seed(&self) -> Hash { - hash(&self.pkcs8) - } - - pub fn last_id(&self) -> Hash { - self.create_entries().last().unwrap().id - } - - pub fn keypair(&self) -> Keypair { - Keypair::from_pkcs8(Input::from(&self.pkcs8)).expect("from_pkcs8 in mint pub fn keypair") - } - - pub fn pubkey(&self) -> Pubkey { - self.pubkey - } - - pub fn create_transaction(&self) -> Vec { - let keypair = self.keypair(); - // Check if creating the leader genesis entries is necessary - if self.bootstrap_leader_id == Pubkey::default() { - let tx = Transaction::system_move(&keypair, self.pubkey(), self.tokens, self.seed(), 0); - vec![tx] - } else { - // Create moves from mint to itself (deposit), and then a move from the mint - // to the bootstrap leader - let moves = vec![ - (self.pubkey(), self.tokens), - (self.bootstrap_leader_id, self.bootstrap_leader_tokens), - ]; - vec![Transaction::system_move_many( - &keypair, - &moves, - self.seed(), - 0, - )] - } - } - - pub fn create_entries(&self) -> Vec { - let e0 = Entry::new(&self.seed(), 0, vec![]); - let e1 = Entry::new(&e0.id, 1, self.create_transaction()); - let e2 = Entry::new(&e1.id, 1, vec![]); // include a tick - vec![e0, e1, e2] - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bincode::deserialize; - use ledger::Block; - use solana_sdk::system_instruction::SystemInstruction; - use system_program::SystemProgram; - - #[test] - fn test_create_transactions() { - let mut transactions = Mint::new(100).create_transaction().into_iter(); - let tx = transactions.next().unwrap(); - assert_eq!(tx.instructions.len(), 1); - assert!(SystemProgram::check_id(tx.program_id(0))); - let instruction: SystemInstruction = deserialize(tx.userdata(0)).unwrap(); - if let SystemInstruction::Move { tokens } = instruction { - assert_eq!(tokens, 100); - } - assert_eq!(transactions.next(), None); - } - - #[test] - fn test_create_leader_transactions() { - let dummy_leader_id = Keypair::new().pubkey(); - let dummy_leader_tokens = 1; - let mut transactions = Mint::new_with_leader(100, dummy_leader_id, dummy_leader_tokens) - .create_transaction() - .into_iter(); - let tx = transactions.next().unwrap(); - assert_eq!(tx.instructions.len(), 2); - assert!(SystemProgram::check_id(tx.program_id(0))); - assert!(SystemProgram::check_id(tx.program_id(1))); - let instruction: SystemInstruction = deserialize(tx.userdata(0)).unwrap(); - if let SystemInstruction::Move { tokens } = instruction { - assert_eq!(tokens, 100); - } - let instruction: SystemInstruction = deserialize(tx.userdata(1)).unwrap(); - if let SystemInstruction::Move { tokens } = instruction { - assert_eq!(tokens, 1); - } - assert_eq!(transactions.next(), None); - } - - #[test] - fn test_verify_entries() { - let entries = Mint::new(100).create_entries(); - assert!(entries[..].verify(&entries[0].id)); - } - - #[test] - fn test_verify_leader_entries() { - let dummy_leader_id = Keypair::new().pubkey(); - let dummy_leader_tokens = 1; - let entries = - Mint::new_with_leader(100, dummy_leader_id, dummy_leader_tokens).create_entries(); - assert!(entries[..].verify(&entries[0].id)); - } -} diff --git a/book/native_loader.rs b/book/native_loader.rs deleted file mode 100644 index 27885990e8e26a..00000000000000 --- a/book/native_loader.rs +++ /dev/null @@ -1,127 +0,0 @@ -//! Native loader -use bincode::deserialize; -use libc; -#[cfg(unix)] -use libloading::os::unix::*; -#[cfg(windows)] -use libloading::os::windows::*; -use solana_sdk::account::KeyedAccount; -use solana_sdk::loader_instruction::LoaderInstruction; -use solana_sdk::native_program; -use solana_sdk::pubkey::Pubkey; -use std::env; -use std::path::PathBuf; -use std::str; - -/// Dynamic link library prefixs -#[cfg(unix)] -const PLATFORM_FILE_PREFIX_NATIVE: &str = "lib"; -#[cfg(windows)] -const PLATFORM_FILE_PREFIX_NATIVE: &str = ""; - -/// Dynamic link library file extension specific to the platform -#[cfg(any(target_os = "macos", target_os = "ios"))] -const PLATFORM_FILE_EXTENSION_NATIVE: &str = "dylib"; -/// Dynamic link library file extension specific to the platform -#[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))] -const PLATFORM_FILE_EXTENSION_NATIVE: &str = "so"; -/// Dynamic link library file extension specific to the platform -#[cfg(windows)] -const PLATFORM_FILE_EXTENSION_NATIVE: &str = "dll"; - -fn create_path(name: &str) -> PathBuf { - let pathbuf = { - let current_exe = env::current_exe().unwrap(); - PathBuf::from(current_exe.parent().unwrap()) - }; - - pathbuf.join( - PathBuf::from(PLATFORM_FILE_PREFIX_NATIVE.to_string() + name) - .with_extension(PLATFORM_FILE_EXTENSION_NATIVE), - ) -} - -const NATIVE_LOADER_PROGRAM_ID: [u8; 32] = [ - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -]; - -pub fn check_id(program_id: &Pubkey) -> bool { - program_id.as_ref() == NATIVE_LOADER_PROGRAM_ID -} - -pub fn id() -> Pubkey { - Pubkey::new(&NATIVE_LOADER_PROGRAM_ID) -} - -pub fn process_instruction( - program_id: &Pubkey, - keyed_accounts: &mut [KeyedAccount], - ix_userdata: &[u8], - tick_height: u64, -) -> bool { - if keyed_accounts[0].account.executable { - // dispatch it - let name = keyed_accounts[0].account.userdata.clone(); - let name = match str::from_utf8(&name) { - Ok(v) => v, - Err(e) => { - warn!("Invalid UTF-8 sequence: {}", e); - return false; - } - }; - trace!("Call native {:?}", name); - let path = create_path(&name); - // TODO linux tls bug can cause crash on dlclose(), workaround by never unloading - match Library::open(Some(&path), libc::RTLD_NODELETE | libc::RTLD_NOW) { - Ok(library) => unsafe { - let entrypoint: Symbol = - match library.get(native_program::ENTRYPOINT.as_bytes()) { - Ok(s) => s, - Err(e) => { - warn!( - "{:?}: Unable to find {:?} in program", - e, - native_program::ENTRYPOINT - ); - return false; - } - }; - return entrypoint( - program_id, - &mut keyed_accounts[1..], - ix_userdata, - tick_height, - ); - }, - Err(e) => { - warn!("Unable to load: {:?}", e); - return false; - } - } - } else if let Ok(instruction) = deserialize(ix_userdata) { - match instruction { - LoaderInstruction::Write { offset, bytes } => { - trace!("NativeLoader::Write offset {} bytes {:?}", offset, bytes); - let offset = offset as usize; - if keyed_accounts[0].account.userdata.len() < offset + bytes.len() { - warn!( - "Error: Overflow, {} < {}", - keyed_accounts[0].account.userdata.len(), - offset + bytes.len() - ); - return false; - } - // native loader takes a name and we assume it all comes in at once - keyed_accounts[0].account.userdata = bytes; - } - - LoaderInstruction::Finalize => { - keyed_accounts[0].account.executable = true; - trace!("NativeLoader::Finalize prog: {:?}", keyed_accounts[0].key); - } - } - } else { - warn!("Invalid userdata in instruction: {:?}", ix_userdata); - } - true -} diff --git a/book/ncp.html b/book/ncp.html deleted file mode 100644 index 8743b66e17dd3e..00000000000000 --- a/book/ncp.html +++ /dev/null @@ -1,201 +0,0 @@ - - - - - - Ncp - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

Ncp

-

The Network Control Plane implements a gossip network between all nodes on in the cluster.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/ncp.rs b/book/ncp.rs deleted file mode 100644 index 738534d25112f6..00000000000000 --- a/book/ncp.rs +++ /dev/null @@ -1,86 +0,0 @@ -//! The `ncp` module implements the network control plane. - -use cluster_info::ClusterInfo; -use service::Service; -use std::net::UdpSocket; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc::channel; -use std::sync::{Arc, RwLock}; -use std::thread::{self, JoinHandle}; -use streamer; -use window::SharedWindow; - -pub struct Ncp { - exit: Arc, - thread_hdls: Vec>, -} - -impl Ncp { - pub fn new( - cluster_info: &Arc>, - window: SharedWindow, - ledger_path: Option<&str>, - gossip_socket: UdpSocket, - exit: Arc, - ) -> Self { - let (request_sender, request_receiver) = channel(); - let gossip_socket = Arc::new(gossip_socket); - trace!( - "Ncp: id: {}, listening on: {:?}", - &cluster_info.read().unwrap().my_data().id, - gossip_socket.local_addr().unwrap() - ); - let t_receiver = - streamer::blob_receiver(gossip_socket.clone(), exit.clone(), request_sender); - let (response_sender, response_receiver) = channel(); - let t_responder = streamer::responder("ncp", gossip_socket, response_receiver); - let t_listen = ClusterInfo::listen( - cluster_info.clone(), - window, - ledger_path, - request_receiver, - response_sender.clone(), - exit.clone(), - ); - let t_gossip = ClusterInfo::gossip(cluster_info.clone(), response_sender, exit.clone()); - let thread_hdls = vec![t_receiver, t_responder, t_listen, t_gossip]; - Ncp { exit, thread_hdls } - } - - pub fn close(self) -> thread::Result<()> { - self.exit.store(true, Ordering::Relaxed); - self.join() - } -} - -impl Service for Ncp { - type JoinReturnType = (); - - fn join(self) -> thread::Result<()> { - for thread_hdl in self.thread_hdls { - thread_hdl.join()?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use cluster_info::{ClusterInfo, Node}; - use ncp::Ncp; - use std::sync::atomic::AtomicBool; - use std::sync::{Arc, RwLock}; - - #[test] - #[ignore] - // test that stage will exit when flag is set - fn test_exit() { - let exit = Arc::new(AtomicBool::new(false)); - let tn = Node::new_localhost(); - let cluster_info = ClusterInfo::new(tn.info.clone()); - let c = Arc::new(RwLock::new(cluster_info)); - let w = Arc::new(RwLock::new(vec![])); - let d = Ncp::new(&c, w, None, tn.sockets.gossip, exit.clone()); - d.close().expect("thread join"); - } -} diff --git a/book/netutil.rs b/book/netutil.rs deleted file mode 100644 index 133482bc54f806..00000000000000 --- a/book/netutil.rs +++ /dev/null @@ -1,304 +0,0 @@ -//! The `netutil` module assists with networking - -use nix::sys::socket::setsockopt; -use nix::sys::socket::sockopt::{ReuseAddr, ReusePort}; -use pnet_datalink as datalink; -use rand::{thread_rng, Rng}; -use reqwest; -use socket2::{Domain, SockAddr, Socket, Type}; -use std::io; -use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, UdpSocket}; -use std::os::unix::io::AsRawFd; - -/// A data type representing a public Udp socket -pub struct UdpSocketPair { - pub addr: SocketAddr, // Public address of the socket - pub receiver: UdpSocket, // Locally bound socket that can receive from the public address - pub sender: UdpSocket, // Locally bound socket to send via public address -} - -/// Tries to determine the public IP address of this machine -pub fn get_public_ip_addr() -> Result { - let body = reqwest::get("http://ifconfig.co/ip") - .map_err(|err| err.to_string())? - .text() - .map_err(|err| err.to_string())?; - - match body.lines().next() { - Some(ip) => Result::Ok(ip.parse().unwrap()), - None => Result::Err("Empty response body".to_string()), - } -} - -pub fn parse_port_or_addr(optstr: Option<&str>, default_port: u16) -> SocketAddr { - let daddr = SocketAddr::from(([0, 0, 0, 0], default_port)); - - if let Some(addrstr) = optstr { - if let Ok(port) = addrstr.parse() { - let mut addr = daddr; - addr.set_port(port); - addr - } else if let Ok(addr) = addrstr.parse() { - addr - } else { - daddr - } - } else { - daddr - } -} - -fn find_eth0ish_ip_addr(ifaces: &mut Vec) -> Option { - // put eth0 and wifi0, etc. up front of our list of candidates - ifaces.sort_by(|a, b| { - a.name - .chars() - .last() - .unwrap() - .cmp(&b.name.chars().last().unwrap()) - }); - - for iface in ifaces.clone() { - trace!("get_ip_addr considering iface {}", iface.name); - for p in iface.ips { - trace!(" ip {}", p); - if p.ip().is_loopback() { - trace!(" loopback"); - continue; - } - if p.ip().is_multicast() { - trace!(" multicast"); - continue; - } - match p.ip() { - IpAddr::V4(addr) => { - if addr.is_link_local() { - trace!(" link local"); - continue; - } - trace!(" picked {}", p.ip()); - return Some(p.ip()); - } - IpAddr::V6(_addr) => { - // Select an ipv6 address if the config is selected - #[cfg(feature = "ipv6")] - { - return Some(p.ip()); - } - } - } - } - } - None -} - -pub fn get_ip_addr() -> Option { - let mut ifaces = datalink::interfaces(); - - find_eth0ish_ip_addr(&mut ifaces) -} - -fn udp_socket(reuseaddr: bool) -> io::Result { - let sock = Socket::new(Domain::ipv4(), Type::dgram(), None)?; - let sock_fd = sock.as_raw_fd(); - - if reuseaddr { - // best effort, i.e. ignore errors here, we'll get the failure in caller - setsockopt(sock_fd, ReusePort, &true).ok(); - setsockopt(sock_fd, ReuseAddr, &true).ok(); - } - - Ok(sock) -} - -pub fn bind_in_range(range: (u16, u16)) -> io::Result<(u16, UdpSocket)> { - let sock = udp_socket(false)?; - - let (start, end) = range; - let mut tries_left = end - start; - let mut rand_port = thread_rng().gen_range(start, end); - loop { - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), rand_port); - - match sock.bind(&SockAddr::from(addr)) { - Ok(_) => { - let sock = sock.into_udp_socket(); - break Result::Ok((sock.local_addr().unwrap().port(), sock)); - } - Err(err) => { - if tries_left == 0 { - return Err(err); - } - } - } - rand_port += 1; - if rand_port == end { - rand_port = start; - } - tries_left -= 1; - } -} - -// binds many sockets to the same port in a range -pub fn multi_bind_in_range(range: (u16, u16), num: usize) -> io::Result<(u16, Vec)> { - let mut sockets = Vec::with_capacity(num); - - let port = { - let (port, _) = bind_in_range(range)?; - port - }; // drop the probe, port should be available... briefly. - - for _ in 0..num { - sockets.push(bind_to(port, true)?); - } - Ok((port, sockets)) -} - -pub fn bind_to(port: u16, reuseaddr: bool) -> io::Result { - let sock = udp_socket(reuseaddr)?; - - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), port); - - match sock.bind(&SockAddr::from(addr)) { - Ok(_) => Result::Ok(sock.into_udp_socket()), - Err(err) => Err(err), - } -} - -pub fn find_available_port_in_range(range: (u16, u16)) -> io::Result { - let (start, end) = range; - let mut tries_left = end - start; - let mut rand_port = thread_rng().gen_range(start, end); - loop { - match TcpListener::bind(SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), - rand_port, - )) { - Ok(_) => { - break Ok(rand_port); - } - Err(err) => { - if tries_left == 0 { - return Err(err); - } - } - } - rand_port += 1; - if rand_port == end { - rand_port = start; - } - tries_left -= 1; - } -} - -#[cfg(test)] -mod tests { - use ipnetwork::IpNetwork; - use logger; - use netutil::*; - use pnet_datalink as datalink; - - #[test] - fn test_find_eth0ish_ip_addr() { - logger::setup(); - - macro_rules! mock_interface { - ($name:ident, $ip_mask:expr) => { - datalink::NetworkInterface { - name: stringify!($name).to_string(), - index: 0, - mac: None, - ips: vec![IpNetwork::V4($ip_mask.parse().unwrap())], - flags: 0, - } - }; - } - - // loopback bad - assert_eq!( - find_eth0ish_ip_addr(&mut vec![mock_interface!(lo, "127.0.0.1/24")]), - None - ); - // multicast bad - assert_eq!( - find_eth0ish_ip_addr(&mut vec![mock_interface!(eth0, "224.0.1.5/24")]), - None - ); - - // finds "wifi0" - assert_eq!( - find_eth0ish_ip_addr(&mut vec![ - mock_interface!(eth0, "224.0.1.5/24"), - mock_interface!(eth2, "192.168.137.1/8"), - mock_interface!(eth3, "10.0.75.1/8"), - mock_interface!(eth4, "172.22.140.113/4"), - mock_interface!(lo, "127.0.0.1/24"), - mock_interface!(wifi0, "192.168.1.184/8"), - ]), - Some(mock_interface!(wifi0, "192.168.1.184/8").ips[0].ip()) - ); - // finds "wifi0" in the middle - assert_eq!( - find_eth0ish_ip_addr(&mut vec![ - mock_interface!(eth0, "224.0.1.5/24"), - mock_interface!(eth2, "192.168.137.1/8"), - mock_interface!(eth3, "10.0.75.1/8"), - mock_interface!(wifi0, "192.168.1.184/8"), - mock_interface!(eth4, "172.22.140.113/4"), - mock_interface!(lo, "127.0.0.1/24"), - ]), - Some(mock_interface!(wifi0, "192.168.1.184/8").ips[0].ip()) - ); - // picks "eth2", is lowest valid "eth" - assert_eq!( - find_eth0ish_ip_addr(&mut vec![ - mock_interface!(eth0, "224.0.1.5/24"), - mock_interface!(eth2, "192.168.137.1/8"), - mock_interface!(eth3, "10.0.75.1/8"), - mock_interface!(eth4, "172.22.140.113/4"), - mock_interface!(lo, "127.0.0.1/24"), - ]), - Some(mock_interface!(eth2, "192.168.137.1/8").ips[0].ip()) - ); - } - - #[test] - fn test_parse_port_or_addr() { - let p1 = parse_port_or_addr(Some("9000"), 1); - assert_eq!(p1.port(), 9000); - let p2 = parse_port_or_addr(Some("127.0.0.1:7000"), 1); - assert_eq!(p2.port(), 7000); - let p2 = parse_port_or_addr(Some("hi there"), 1); - assert_eq!(p2.port(), 1); - let p3 = parse_port_or_addr(None, 1); - assert_eq!(p3.port(), 1); - } - - #[test] - fn test_bind() { - assert_eq!(bind_in_range((2000, 2001)).unwrap().0, 2000); - let x = bind_to(2002, true).unwrap(); - let y = bind_to(2002, true).unwrap(); - assert_eq!( - x.local_addr().unwrap().port(), - y.local_addr().unwrap().port() - ); - let (port, v) = multi_bind_in_range((2010, 2110), 10).unwrap(); - for sock in &v { - assert_eq!(port, sock.local_addr().unwrap().port()); - } - } - - #[test] - #[should_panic] - fn test_bind_in_range_nil() { - let _ = bind_in_range((2000, 2000)); - } - - #[test] - fn test_find_available_port_in_range() { - assert_eq!(find_available_port_in_range((3000, 3001)).unwrap(), 3000); - let port = find_available_port_in_range((3000, 3050)).unwrap(); - assert!(3000 <= port && port < 3050); - } -} diff --git a/book/packet.rs b/book/packet.rs deleted file mode 100644 index e134308698300d..00000000000000 --- a/book/packet.rs +++ /dev/null @@ -1,569 +0,0 @@ -//! The `packet` module defines data structures and methods to pull data from the network. -use bincode::{deserialize, serialize}; -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; -use counter::Counter; -#[cfg(test)] -use entry::Entry; -#[cfg(test)] -use ledger::Block; -use log::Level; -use recvmmsg::{recv_mmsg, NUM_RCVMMSGS}; -use result::{Error, Result}; -use serde::Serialize; -#[cfg(test)] -use solana_sdk::hash::Hash; -pub use solana_sdk::packet::PACKET_DATA_SIZE; -use solana_sdk::pubkey::Pubkey; -use std::fmt; -use std::io; -use std::mem::size_of; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket}; -use std::sync::atomic::AtomicUsize; -use std::sync::{Arc, RwLock}; - -pub type SharedPackets = Arc>; -pub type SharedBlob = Arc>; -pub type SharedBlobs = Vec; - -pub const NUM_PACKETS: usize = 1024 * 8; -pub const BLOB_SIZE: usize = (64 * 1024 - 128); // wikipedia says there should be 20b for ipv4 headers -pub const BLOB_DATA_SIZE: usize = BLOB_SIZE - (BLOB_HEADER_SIZE * 2); -pub const NUM_BLOBS: usize = (NUM_PACKETS * PACKET_DATA_SIZE) / BLOB_SIZE; - -#[derive(Clone, Default, Debug, PartialEq)] -#[repr(C)] -pub struct Meta { - pub size: usize, - pub num_retransmits: u64, - pub addr: [u16; 8], - pub port: u16, - pub v6: bool, -} - -#[derive(Clone)] -#[repr(C)] -pub struct Packet { - pub data: [u8; PACKET_DATA_SIZE], - pub meta: Meta, -} - -impl fmt::Debug for Packet { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "Packet {{ size: {:?}, addr: {:?} }}", - self.meta.size, - self.meta.addr() - ) - } -} - -impl Default for Packet { - fn default() -> Packet { - Packet { - data: [0u8; PACKET_DATA_SIZE], - meta: Meta::default(), - } - } -} - -impl Meta { - pub fn addr(&self) -> SocketAddr { - if !self.v6 { - let addr = [ - self.addr[0] as u8, - self.addr[1] as u8, - self.addr[2] as u8, - self.addr[3] as u8, - ]; - let ipv4: Ipv4Addr = From::<[u8; 4]>::from(addr); - SocketAddr::new(IpAddr::V4(ipv4), self.port) - } else { - let ipv6: Ipv6Addr = From::<[u16; 8]>::from(self.addr); - SocketAddr::new(IpAddr::V6(ipv6), self.port) - } - } - - pub fn set_addr(&mut self, a: &SocketAddr) { - match *a { - SocketAddr::V4(v4) => { - let ip = v4.ip().octets(); - self.addr[0] = u16::from(ip[0]); - self.addr[1] = u16::from(ip[1]); - self.addr[2] = u16::from(ip[2]); - self.addr[3] = u16::from(ip[3]); - self.addr[4] = 0; - self.addr[5] = 0; - self.addr[6] = 0; - self.addr[7] = 0; - self.v6 = false; - } - SocketAddr::V6(v6) => { - self.addr = v6.ip().segments(); - self.v6 = true; - } - } - self.port = a.port(); - } -} - -#[derive(Debug)] -pub struct Packets { - pub packets: Vec, -} - -//auto derive doesn't support large arrays -impl Default for Packets { - fn default() -> Packets { - Packets { - packets: vec![Packet::default(); NUM_PACKETS], - } - } -} - -#[derive(Clone)] -pub struct Blob { - pub data: [u8; BLOB_SIZE], - pub meta: Meta, -} - -impl PartialEq for Blob { - fn eq(&self, other: &Blob) -> bool { - self.data.iter().zip(other.data.iter()).all(|(a, b)| a == b) && self.meta == other.meta - } -} - -impl fmt::Debug for Blob { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "Blob {{ size: {:?}, addr: {:?} }}", - self.meta.size, - self.meta.addr() - ) - } -} - -//auto derive doesn't support large arrays -impl Default for Blob { - fn default() -> Blob { - Blob { - data: [0u8; BLOB_SIZE], - meta: Meta::default(), - } - } -} - -#[derive(Debug)] -pub enum BlobError { - /// the Blob's meta and data are not self-consistent - BadState, - /// Blob verification failed - VerificationFailed, -} - -impl Packets { - fn run_read_from(&mut self, socket: &UdpSocket) -> Result { - self.packets.resize(NUM_PACKETS, Packet::default()); - let mut i = 0; - //DOCUMENTED SIDE-EFFECT - //Performance out of the IO without poll - // * block on the socket until it's readable - // * set the socket to non blocking - // * read until it fails - // * set it back to blocking before returning - socket.set_nonblocking(false)?; - trace!("receiving on {}", socket.local_addr().unwrap()); - loop { - match recv_mmsg(socket, &mut self.packets[i..]) { - Err(_) if i > 0 => { - inc_new_counter_info!("packets-recv_count", i); - debug!("got {:?} messages on {}", i, socket.local_addr().unwrap()); - socket.set_nonblocking(true)?; - return Ok(i); - } - Err(e) => { - trace!("recv_from err {:?}", e); - return Err(Error::IO(e)); - } - Ok(npkts) => { - trace!("got {} packets", npkts); - i += npkts; - if npkts != NUM_RCVMMSGS { - socket.set_nonblocking(true)?; - inc_new_counter_info!("packets-recv_count", i); - return Ok(i); - } - } - } - } - } - pub fn recv_from(&mut self, socket: &UdpSocket) -> Result<()> { - let sz = self.run_read_from(socket)?; - self.packets.resize(sz, Packet::default()); - debug!("recv_from: {}", sz); - Ok(()) - } - pub fn send_to(&self, socket: &UdpSocket) -> Result<()> { - for p in &self.packets { - let a = p.meta.addr(); - socket.send_to(&p.data[..p.meta.size], &a)?; - } - Ok(()) - } -} - -pub fn to_packets_chunked(xs: &[T], chunks: usize) -> Vec { - let mut out = vec![]; - for x in xs.chunks(chunks) { - let mut p = SharedPackets::default(); - p.write() - .unwrap() - .packets - .resize(x.len(), Default::default()); - for (i, o) in x.iter().zip(p.write().unwrap().packets.iter_mut()) { - let v = serialize(&i).expect("serialize request"); - let len = v.len(); - o.data[..len].copy_from_slice(&v); - o.meta.size = len; - } - out.push(p); - } - out -} - -pub fn to_packets(xs: &[T]) -> Vec { - to_packets_chunked(xs, NUM_PACKETS) -} - -pub fn to_blob(resp: T, rsp_addr: SocketAddr) -> Result { - let blob = SharedBlob::default(); - { - let mut b = blob.write().unwrap(); - let v = serialize(&resp)?; - let len = v.len(); - assert!(len <= BLOB_SIZE); - b.data[..len].copy_from_slice(&v); - b.meta.size = len; - b.meta.set_addr(&rsp_addr); - } - Ok(blob) -} - -pub fn to_blobs(rsps: Vec<(T, SocketAddr)>) -> Result { - let mut blobs = Vec::new(); - for (resp, rsp_addr) in rsps { - blobs.push(to_blob(resp, rsp_addr)?); - } - Ok(blobs) -} - -const BLOB_SLOT_END: usize = size_of::(); -const BLOB_INDEX_END: usize = BLOB_SLOT_END + size_of::(); -const BLOB_ID_END: usize = BLOB_INDEX_END + size_of::(); -const BLOB_FLAGS_END: usize = BLOB_ID_END + size_of::(); -const BLOB_SIZE_END: usize = BLOB_FLAGS_END + size_of::(); - -macro_rules! align { - ($x:expr, $align:expr) => { - $x + ($align - 1) & !($align - 1) - }; -} - -pub const BLOB_FLAG_IS_CODING: u32 = 0x1; -pub const BLOB_HEADER_SIZE: usize = align!(BLOB_SIZE_END, 64); - -impl Blob { - pub fn slot(&self) -> Result { - let mut rdr = io::Cursor::new(&self.data[0..BLOB_SLOT_END]); - let r = rdr.read_u64::()?; - Ok(r) - } - pub fn set_slot(&mut self, ix: u64) -> Result<()> { - let mut wtr = vec![]; - wtr.write_u64::(ix)?; - self.data[..BLOB_SLOT_END].clone_from_slice(&wtr); - Ok(()) - } - pub fn index(&self) -> Result { - let mut rdr = io::Cursor::new(&self.data[BLOB_SLOT_END..BLOB_INDEX_END]); - let r = rdr.read_u64::()?; - Ok(r) - } - pub fn set_index(&mut self, ix: u64) -> Result<()> { - let mut wtr = vec![]; - wtr.write_u64::(ix)?; - self.data[BLOB_SLOT_END..BLOB_INDEX_END].clone_from_slice(&wtr); - Ok(()) - } - /// sender id, we use this for identifying if its a blob from the leader that we should - /// retransmit. eventually blobs should have a signature that we can use ffor spam filtering - pub fn id(&self) -> Result { - let e = deserialize(&self.data[BLOB_INDEX_END..BLOB_ID_END])?; - Ok(e) - } - - pub fn set_id(&mut self, id: &Pubkey) -> Result<()> { - let wtr = serialize(id)?; - self.data[BLOB_INDEX_END..BLOB_ID_END].clone_from_slice(&wtr); - Ok(()) - } - - pub fn flags(&self) -> Result { - let mut rdr = io::Cursor::new(&self.data[BLOB_ID_END..BLOB_FLAGS_END]); - let r = rdr.read_u32::()?; - Ok(r) - } - - pub fn set_flags(&mut self, ix: u32) -> Result<()> { - let mut wtr = vec![]; - wtr.write_u32::(ix)?; - self.data[BLOB_ID_END..BLOB_FLAGS_END].clone_from_slice(&wtr); - Ok(()) - } - - pub fn is_coding(&self) -> bool { - (self.flags().unwrap() & BLOB_FLAG_IS_CODING) != 0 - } - - pub fn set_coding(&mut self) -> Result<()> { - let flags = self.flags().unwrap(); - self.set_flags(flags | BLOB_FLAG_IS_CODING) - } - - pub fn data_size(&self) -> Result { - let mut rdr = io::Cursor::new(&self.data[BLOB_FLAGS_END..BLOB_SIZE_END]); - let r = rdr.read_u64::()?; - Ok(r) - } - - pub fn set_data_size(&mut self, ix: u64) -> Result<()> { - let mut wtr = vec![]; - wtr.write_u64::(ix)?; - self.data[BLOB_FLAGS_END..BLOB_SIZE_END].clone_from_slice(&wtr); - Ok(()) - } - - pub fn data(&self) -> &[u8] { - &self.data[BLOB_HEADER_SIZE..] - } - pub fn data_mut(&mut self) -> &mut [u8] { - &mut self.data[BLOB_HEADER_SIZE..] - } - pub fn size(&self) -> Result { - let size = self.data_size()? as usize; - if self.meta.size == size { - Ok(size - BLOB_HEADER_SIZE) - } else { - Err(Error::BlobError(BlobError::BadState)) - } - } - pub fn set_size(&mut self, size: usize) { - let new_size = size + BLOB_HEADER_SIZE; - self.meta.size = new_size; - self.set_data_size(new_size as u64).unwrap(); - } - - pub fn recv_blob(socket: &UdpSocket, r: &SharedBlob) -> io::Result<()> { - let mut p = r.write().unwrap(); - trace!("receiving on {}", socket.local_addr().unwrap()); - - let (nrecv, from) = socket.recv_from(&mut p.data)?; - p.meta.size = nrecv; - p.meta.set_addr(&from); - trace!("got {} bytes from {}", nrecv, from); - Ok(()) - } - - pub fn recv_from(socket: &UdpSocket) -> Result { - let mut v = Vec::new(); - //DOCUMENTED SIDE-EFFECT - //Performance out of the IO without poll - // * block on the socket until it's readable - // * set the socket to non blocking - // * read until it fails - // * set it back to blocking before returning - socket.set_nonblocking(false)?; - for i in 0..NUM_BLOBS { - let r = SharedBlob::default(); - - match Blob::recv_blob(socket, &r) { - Err(_) if i > 0 => { - trace!("got {:?} messages on {}", i, socket.local_addr().unwrap()); - break; - } - Err(e) => { - if e.kind() != io::ErrorKind::WouldBlock { - info!("recv_from err {:?}", e); - } - return Err(Error::IO(e)); - } - Ok(()) => { - if i == 0 { - socket.set_nonblocking(true)?; - } - } - } - v.push(r); - } - Ok(v) - } - pub fn send_to(socket: &UdpSocket, v: SharedBlobs) -> Result<()> { - for r in v { - { - let p = r.read().unwrap(); - let a = p.meta.addr(); - if let Err(e) = socket.send_to(&p.data[..p.meta.size], &a) { - warn!( - "error sending {} byte packet to {:?}: {:?}", - p.meta.size, a, e - ); - Err(e)?; - } - } - } - Ok(()) - } -} - -pub fn index_blobs(blobs: &[SharedBlob], id: &Pubkey, mut index: u64, slot: u64) { - // enumerate all the blobs, those are the indices - for b in blobs { - let mut blob = b.write().unwrap(); - - blob.set_index(index).expect("set_index"); - blob.set_slot(slot).expect("set_slot"); - blob.set_id(id).expect("set_id"); - blob.set_flags(0).unwrap(); - - index += 1; - } -} - -#[cfg(test)] -pub fn make_consecutive_blobs( - me_id: Pubkey, - num_blobs_to_make: u64, - start_height: u64, - start_hash: Hash, - addr: &SocketAddr, -) -> SharedBlobs { - let mut last_hash = start_hash; - let num_hashes = 1; - let mut all_entries = Vec::with_capacity(num_blobs_to_make as usize); - for _ in 0..num_blobs_to_make { - let entry = Entry::new(&last_hash, num_hashes, vec![]); - last_hash = entry.id; - all_entries.push(entry); - } - let mut new_blobs = all_entries.to_blobs_with_id(me_id, start_height, addr); - new_blobs.truncate(num_blobs_to_make as usize); - new_blobs -} - -#[cfg(test)] -mod tests { - use packet::{ - to_packets, Blob, Meta, Packet, Packets, SharedBlob, SharedPackets, NUM_PACKETS, - PACKET_DATA_SIZE, - }; - use signature::{Keypair, KeypairUtil}; - use solana_sdk::hash::Hash; - use std::io; - use std::io::Write; - use std::net::UdpSocket; - use system_transaction::SystemTransaction; - use transaction::Transaction; - - #[test] - pub fn packet_send_recv() { - let reader = UdpSocket::bind("127.0.0.1:0").expect("bind"); - let addr = reader.local_addr().unwrap(); - let sender = UdpSocket::bind("127.0.0.1:0").expect("bind"); - let saddr = sender.local_addr().unwrap(); - let p = SharedPackets::default(); - p.write().unwrap().packets.resize(10, Packet::default()); - for m in p.write().unwrap().packets.iter_mut() { - m.meta.set_addr(&addr); - m.meta.size = PACKET_DATA_SIZE; - } - p.read().unwrap().send_to(&sender).unwrap(); - p.write().unwrap().recv_from(&reader).unwrap(); - for m in p.write().unwrap().packets.iter_mut() { - assert_eq!(m.meta.size, PACKET_DATA_SIZE); - assert_eq!(m.meta.addr(), saddr); - } - } - - #[test] - fn test_to_packets() { - let keypair = Keypair::new(); - let hash = Hash::new(&[1; 32]); - let tx = Transaction::system_new(&keypair, keypair.pubkey(), 1, hash); - let rv = to_packets(&vec![tx.clone(); 1]); - assert_eq!(rv.len(), 1); - assert_eq!(rv[0].read().unwrap().packets.len(), 1); - - let rv = to_packets(&vec![tx.clone(); NUM_PACKETS]); - assert_eq!(rv.len(), 1); - assert_eq!(rv[0].read().unwrap().packets.len(), NUM_PACKETS); - - let rv = to_packets(&vec![tx.clone(); NUM_PACKETS + 1]); - assert_eq!(rv.len(), 2); - assert_eq!(rv[0].read().unwrap().packets.len(), NUM_PACKETS); - assert_eq!(rv[1].read().unwrap().packets.len(), 1); - } - - #[test] - pub fn blob_send_recv() { - trace!("start"); - let reader = UdpSocket::bind("127.0.0.1:0").expect("bind"); - let addr = reader.local_addr().unwrap(); - let sender = UdpSocket::bind("127.0.0.1:0").expect("bind"); - let p = SharedBlob::default(); - p.write().unwrap().meta.set_addr(&addr); - p.write().unwrap().meta.size = 1024; - let v = vec![p]; - Blob::send_to(&sender, v).unwrap(); - trace!("send_to"); - let rv = Blob::recv_from(&reader).unwrap(); - trace!("recv_from"); - assert_eq!(rv.len(), 1); - assert_eq!(rv[0].read().unwrap().meta.size, 1024); - } - - #[cfg(all(feature = "ipv6", test))] - #[test] - pub fn blob_ipv6_send_recv() { - let reader = UdpSocket::bind("[::1]:0").expect("bind"); - let addr = reader.local_addr().unwrap(); - let sender = UdpSocket::bind("[::1]:0").expect("bind"); - let p = SharedBlob::default(); - p.as_mut().unwrap().meta.set_addr(&addr); - p.as_mut().unwrap().meta.size = 1024; - let mut v = VecDeque::default(); - v.push_back(p); - Blob::send_to(&r, &sender, &mut v).unwrap(); - let mut rv = Blob::recv_from(&reader).unwrap(); - let rp = rv.pop_front().unwrap(); - assert_eq!(rp.as_mut().meta.size, 1024); - } - - #[test] - pub fn debug_trait() { - write!(io::sink(), "{:?}", Packet::default()).unwrap(); - write!(io::sink(), "{:?}", Packets::default()).unwrap(); - write!(io::sink(), "{:?}", Blob::default()).unwrap(); - } - #[test] - pub fn blob_test() { - let mut b = Blob::default(); - b.set_index(::max_value()).unwrap(); - assert_eq!(b.index().unwrap(), ::max_value()); - b.data_mut()[0] = 1; - assert_eq!(b.data()[0], 1); - assert_eq!(b.index().unwrap(), ::max_value()); - assert_eq!(b.meta, Meta::default()); - } - -} diff --git a/book/payment_plan.rs b/book/payment_plan.rs deleted file mode 100644 index c0f5f1e651ab63..00000000000000 --- a/book/payment_plan.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! The `plan` module provides a domain-specific language for payment plans. Users create BudgetExpr objects that -//! are given to an interpreter. The interpreter listens for `Witness` transactions, -//! which it uses to reduce the payment plan. When the plan is reduced to a -//! `Payment`, the payment is executed. - -use chrono::prelude::*; -use solana_sdk::pubkey::Pubkey; - -/// The types of events a payment plan can process. -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub enum Witness { - /// The current time. - Timestamp(DateTime), - - /// A signature from Pubkey. - Signature, -} - -/// Some amount of tokens that should be sent to the `to` `Pubkey`. -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub struct Payment { - /// Amount to be paid. - pub tokens: u64, - - /// The `Pubkey` that `tokens` should be paid to. - pub to: Pubkey, -} diff --git a/book/poh.html b/book/poh.html deleted file mode 100644 index 60948bb29f1ef2..00000000000000 --- a/book/poh.html +++ /dev/null @@ -1,235 +0,0 @@ - - - - - - Proof of History - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

Proof of History

-

Proof of History overview

-

Relationship to consensus mechanisms

-

Most confusingly, a Proof of History (PoH) is more similar to a Verifiable -Delay Function (VDF) than a Proof of Work or Proof of Stake consensus -mechanism. The name unfortunately requires some historical context to -understand. Proof of History was developed by Anatoly Yakovenko in November of -2017, roughly 6 months before we saw a paper using the term -VDF. At that time, it was commonplace to -publish new proofs of some desirable property used to build most any blockchain -component. Some time shortly after, the crypto community began charting out all -the different consensus mechanisms and because most of them started with "Proof -of", the prefix became synonymous with a "consensus" suffix. Proof of History -is not a consensus mechanism, but it is used to improve the performance of -Solana's Proof of Stake consensus. It is also used to improve the performance -of the replication and storage protocols. To minimize confusion, Solana may -rebrand PoH to some flavor of the term VDF.

-

Relationship to VDFs

-

A desirable property of a VDF is that verification time is very fast. Solana's -approach to verifying its delay function is proportional to the time it took to -create it. Split over a 4000 core GPU, it is sufficiently fast for Solana's -needs, but if you asked the authors the paper cited above, they might tell you -(and have) that Solana's approach is algorithmically slow it shouldn't be -called a VDF. We argue the term VDF should represent the category of verifiable -delay functions and not just the subset with certain performance -characteristics. Until that's resolved, Solana will likely continue using the -term PoH for its application-specific VDF.

-

Another difference between PoH and VDFs used only for tracking duration, is -that PoH's hash chain includes hashes of any data the application observed. -That data is a double-edged sword. On one side, the data "proves history" - -that the data most certainly existed before hashes after it. On the side, it -means the application can manipulate the hash chain by changing when the data -is hashed. The PoH chain therefore does not serve as a good source of -randomness whereas a VDF without that data could. Solana's leader selection -algorithm (TODO: add link), for example, is derived only from the VDF height -and not its hash at that height.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/poh.rs b/book/poh.rs deleted file mode 100644 index 1b1a908b98a39a..00000000000000 --- a/book/poh.rs +++ /dev/null @@ -1,116 +0,0 @@ -//! The `Poh` module provides an object for generating a Proof of History. -//! It records Hashes items on behalf of its users. -use solana_sdk::hash::{hash, hashv, Hash}; - -pub struct Poh { - prev_id: Hash, - id: Hash, - num_hashes: u64, - pub tick_height: u64, -} - -#[derive(Debug)] -pub struct PohEntry { - pub prev_id: Hash, - pub num_hashes: u64, - pub id: Hash, - pub mixin: Option, -} - -impl Poh { - pub fn new(prev_id: Hash, tick_height: u64) -> Self { - Poh { - prev_id, - num_hashes: 0, - id: prev_id, - tick_height, - } - } - - pub fn hash(&mut self) { - self.id = hash(&self.id.as_ref()); - self.num_hashes += 1; - } - - pub fn record(&mut self, mixin: Hash) -> PohEntry { - self.id = hashv(&[&self.id.as_ref(), &mixin.as_ref()]); - - let prev_id = self.prev_id; - self.prev_id = self.id; - - let num_hashes = self.num_hashes + 1; - self.num_hashes = 0; - - PohEntry { - prev_id, - num_hashes, - id: self.id, - mixin: Some(mixin), - } - } - - // emissions of Ticks (i.e. PohEntries without a mixin) allows - // validators to parallelize the work of catching up - pub fn tick(&mut self) -> PohEntry { - self.hash(); - - let num_hashes = self.num_hashes; - self.num_hashes = 0; - - let prev_id = self.prev_id; - self.prev_id = self.id; - - self.tick_height += 1; - - PohEntry { - prev_id, - num_hashes, - id: self.id, - mixin: None, - } - } -} - -#[cfg(test)] -pub fn verify(initial: Hash, entries: &[PohEntry]) -> bool { - let mut prev_id = initial; - - for entry in entries { - assert!(entry.num_hashes != 0); - assert!(prev_id == entry.prev_id); - - for _ in 1..entry.num_hashes { - prev_id = hash(&prev_id.as_ref()); - } - prev_id = match entry.mixin { - Some(mixin) => hashv(&[&prev_id.as_ref(), &mixin.as_ref()]), - None => hash(&prev_id.as_ref()), - }; - if prev_id != entry.id { - return false; - } - } - - true -} - -#[cfg(test)] -mod tests { - use poh::{self, PohEntry}; - use solana_sdk::hash::Hash; - - #[test] - #[should_panic] - fn test_poh_verify_assert() { - poh::verify( - Hash::default(), - &[PohEntry { - prev_id: Hash::default(), - num_hashes: 0, - id: Hash::default(), - mixin: None, - }], - ); - } - -} diff --git a/book/poh_recorder.rs b/book/poh_recorder.rs deleted file mode 100644 index 0ac484c8c0c9f6..00000000000000 --- a/book/poh_recorder.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! The `poh_recorder` module provides an object for synchronizing with Proof of History. -//! It synchronizes PoH, bank's register_tick and the ledger -//! -use bank::Bank; -use entry::Entry; -use poh::Poh; -use result::{Error, Result}; -use solana_sdk::hash::Hash; -use std::sync::mpsc::Sender; -use std::sync::{Arc, Mutex}; -use transaction::Transaction; - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum PohRecorderError { - InvalidCallingObject, - MaxHeightReached, -} - -#[derive(Clone)] -pub struct PohRecorder { - poh: Arc>, - bank: Arc, - sender: Sender>, - max_tick_height: Option, -} - -impl PohRecorder { - pub fn hash(&self) -> Result<()> { - // TODO: amortize the cost of this lock by doing the loop in here for - // some min amount of hashes - let mut poh = self.poh.lock().unwrap(); - if self.is_max_tick_height_reached(&poh) { - Err(Error::PohRecorderError(PohRecorderError::MaxHeightReached)) - } else { - poh.hash(); - Ok(()) - } - } - - pub fn tick(&mut self) -> Result<()> { - // Register and send the entry out while holding the lock if the max PoH height - // hasn't been reached. - // This guarantees PoH order and Entry production and banks LastId queue is the same - let mut poh = self.poh.lock().unwrap(); - if self.is_max_tick_height_reached(&poh) { - Err(Error::PohRecorderError(PohRecorderError::MaxHeightReached)) - } else { - self.register_and_send_tick(&mut *poh)?; - Ok(()) - } - } - - pub fn record(&self, mixin: Hash, txs: Vec) -> Result<()> { - // Register and send the entry out while holding the lock. - // This guarantees PoH order and Entry production and banks LastId queue is the same. - let mut poh = self.poh.lock().unwrap(); - if self.is_max_tick_height_reached(&poh) { - Err(Error::PohRecorderError(PohRecorderError::MaxHeightReached)) - } else { - self.record_and_send_txs(&mut *poh, mixin, txs)?; - Ok(()) - } - } - - /// A recorder to synchronize PoH with the following data structures - /// * bank - the LastId's queue is updated on `tick` and `record` events - /// * sender - the Entry channel that outputs to the ledger - pub fn new( - bank: Arc, - sender: Sender>, - last_entry_id: Hash, - max_tick_height: Option, - ) -> Self { - let poh = Arc::new(Mutex::new(Poh::new(last_entry_id, bank.tick_height()))); - PohRecorder { - poh, - bank, - sender, - max_tick_height, - } - } - - fn is_max_tick_height_reached(&self, poh: &Poh) -> bool { - if let Some(max_tick_height) = self.max_tick_height { - poh.tick_height >= max_tick_height - } else { - false - } - } - - fn record_and_send_txs(&self, poh: &mut Poh, mixin: Hash, txs: Vec) -> Result<()> { - let entry = poh.record(mixin); - assert!(!txs.is_empty(), "Entries without transactions are used to track real-time passing in the ledger and can only be generated with PohRecorder::tick function"); - let entry = Entry { - prev_id: entry.prev_id, - num_hashes: entry.num_hashes, - id: entry.id, - transactions: txs, - }; - self.sender.send(vec![entry])?; - Ok(()) - } - - fn register_and_send_tick(&self, poh: &mut Poh) -> Result<()> { - let tick = poh.tick(); - let tick = Entry { - prev_id: tick.prev_id, - num_hashes: tick.num_hashes, - id: tick.id, - transactions: vec![], - }; - self.bank.register_tick(&tick.id); - self.sender.send(vec![tick])?; - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use mint::Mint; - use solana_sdk::hash::hash; - use std::sync::mpsc::channel; - use std::sync::Arc; - use system_transaction::test_tx; - - #[test] - fn test_poh() { - let mint = Mint::new(1); - let bank = Arc::new(Bank::new(&mint)); - let prev_id = bank.last_id(); - let (entry_sender, entry_receiver) = channel(); - let mut poh_recorder = PohRecorder::new(bank, entry_sender, prev_id, None); - - //send some data - let h1 = hash(b"hello world!"); - let tx = test_tx(); - assert!(poh_recorder.record(h1, vec![tx]).is_ok()); - assert!(poh_recorder.tick().is_ok()); - - //get some events - let _ = entry_receiver.recv().unwrap(); - let _ = entry_receiver.recv().unwrap(); - - //make sure it handles channel close correctly - drop(entry_receiver); - assert!(poh_recorder.tick().is_err()); - } -} diff --git a/book/poh_service.rs b/book/poh_service.rs deleted file mode 100644 index a9d53a464d54f3..00000000000000 --- a/book/poh_service.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! The `poh_service` module implements a service that records the passing of -//! "ticks", a measure of time in the PoH stream - -use poh_recorder::PohRecorder; -use result::Result; -use service::Service; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use std::thread::sleep; -use std::thread::{self, Builder, JoinHandle}; -use std::time::Duration; -pub const NUM_TICKS_PER_SECOND: usize = 10; - -#[derive(Copy, Clone)] -pub enum Config { - /// * `Tick` - Run full PoH thread. Tick is a rough estimate of how many hashes to roll before transmitting a new entry. - Tick(usize), - /// * `Sleep`- Low power mode. Sleep is a rough estimate of how long to sleep before rolling 1 poh once and producing 1 - /// tick. - Sleep(Duration), -} - -impl Default for Config { - fn default() -> Config { - // TODO: Change this to Tick to enable PoH - Config::Sleep(Duration::from_millis(1000 / NUM_TICKS_PER_SECOND as u64)) - } -} - -pub struct PohService { - tick_producer: JoinHandle>, - pub poh_exit: Arc, -} - -impl PohService { - pub fn exit(&self) -> () { - self.poh_exit.store(true, Ordering::Relaxed); - } - - pub fn close(self) -> thread::Result> { - self.exit(); - self.join() - } - - pub fn new(poh_recorder: PohRecorder, config: Config) -> Self { - // PohService is a headless producer, so when it exits it should notify the banking stage. - // Since channel are not used to talk between these threads an AtomicBool is used as a - // signal. - let poh_exit = Arc::new(AtomicBool::new(false)); - let poh_exit_ = poh_exit.clone(); - // Single thread to generate ticks - let tick_producer = Builder::new() - .name("solana-poh-service-tick_producer".to_string()) - .spawn(move || { - let mut poh_recorder_ = poh_recorder; - let return_value = Self::tick_producer(&mut poh_recorder_, config, &poh_exit_); - poh_exit_.store(true, Ordering::Relaxed); - return_value - }).unwrap(); - - PohService { - tick_producer, - poh_exit, - } - } - - fn tick_producer(poh: &mut PohRecorder, config: Config, poh_exit: &AtomicBool) -> Result<()> { - loop { - match config { - Config::Tick(num) => { - for _ in 0..num { - poh.hash()?; - } - } - Config::Sleep(duration) => { - sleep(duration); - } - } - poh.tick()?; - if poh_exit.load(Ordering::Relaxed) { - debug!("tick service exited"); - return Ok(()); - } - } - } -} - -impl Service for PohService { - type JoinReturnType = Result<()>; - - fn join(self) -> thread::Result> { - self.tick_producer.join() - } -} diff --git a/book/print.html b/book/print.html deleted file mode 100644 index d1e13035b1294a..00000000000000 --- a/book/print.html +++ /dev/null @@ -1,1282 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

Disclaimer

-

All claims, content, designs, algorithms, estimates, roadmaps, specifications, -and performance measurements described in this project are done with the -author's best effort. It is up to the reader to check and validate their -accuracy and truthfulness. Furthermore nothing in this project constitutes a -solicitation for investment.

-

Introduction

-

This document defines the architecture of Solana, a blockchain built from the -ground up for scale. The goal of the architecture is to demonstrate there -exists a set of software algorithms that in combination, removes software as a -performance bottleneck, allowing transaction throughput to scale proportionally -with network bandwidth. The architecture goes on to satisfy all three desirable -properties of a proper blockchain, that it not only be scalable, but that it is -also secure and decentralized.

-

With this architecture, we calculate a theoretical upper bound of 710 thousand -transactions per second (tps) on a standard gigabit network and 28.4 million -tps on 40 gigabit. In practice, our focus has been on gigabit. We soak-tested -a 150 node permissioned testnet and it is able to maintain a mean transaction -throughput of approximately 200 thousand tps with peaks over 400 thousand.

-

Furthermore, we have found high throughput extends beyond simple payments, and -that this architecture is also able to perform safe, concurrent execution of -programs authored in a general purpose programming language, such as C. We feel -the extension warrants industry focus on an additional performance metric -already common in the CPU industry, millions of instructions per second or -mips. By measuring mips, we see that batching instructions within a transaction -amortizes the cost of signature verification, lifting the maximum theoretical -instruction throughput up to almost exactly that of centralized databases.

-

Lastly, we discuss the relationships between high throughput, security and -transaction fees. Solana's efficient use hardware drives transaction fees into -the ballpark of 1/1000th of a cent. The drop in fees in turn makes certain -denial of service attacks cheaper. We discuss what these attacks look like and -Solana's techniques to defend against them.

-

Terminology

-

Teminology Currently in Use

-

The following list contains words commonly used throughout the Solana architecture.

-
    -
  • account - a persistent file addressed by pubkey and with tokens tracking its lifetime
  • -
  • cluster - a set of fullnodes maintaining a single ledger
  • -
  • finality - the wallclock duration between a leader creating a tick entry and recoginizing -a supermajority of validator votes with a ledger interpretation that matches the leader's
  • -
  • fullnode - a full participant in the cluster - either a leader or validator node
  • -
  • entry - an entry on the ledger - either a tick or a transactions entry
  • -
  • instruction - the smallest unit of a program that a client can include in a transaction
  • -
  • keypair - a public and secret key
  • -
  • node count - the number of fullnodes participating in a cluster
  • -
  • program - the code that interprets instructions
  • -
  • pubkey - the public key of a keypair
  • -
  • tick - a ledger entry that estimates wallclock duration
  • -
  • tick height - the Nth tick in the ledger
  • -
  • tps - transactions per second
  • -
  • transaction - one or more instructions signed by the client and executed atomically
  • -
  • transactions entry - a set of transactions that may be executed in parallel
  • -
-

Terminology Reserved for Future Use

-

The following keywords do not have any functionality but are reserved by Solana -for potential future use.

-
    -
  • epoch - the time in which a leader schedule is valid
  • -
  • mips - millions of instructions per second
  • -
  • public key - We currently use pubkey
  • -
  • slot - the time in which a single leader may produce entries
  • -
  • secret key - Users currently only use keypair
  • -
-

Synchronization

-

It's possible for a centralized database to process 710,000 transactions per -second on a standard gigabit network if the transactions are, on average, no -more than 176 bytes. A centralized database can also replicate itself and -maintain high availability without significantly compromising that transaction -rate using the distributed system technique known as Optimistic Concurrency -Control [H.T.Kung, J.T.Robinson -(1981)]. At -Solana, we're demonstrating that these same theoretical limits apply just as -well to blockchain on an adversarial network. The key ingredient? Finding a way -to share time when nodes can't trust one-another. Once nodes can trust time, -suddenly ~40 years of distributed systems research becomes applicable to -blockchain!

-
-

Perhaps the most striking difference between algorithms obtained by our -method and ones based upon timeout is that using timeout produces a -traditional distributed algorithm in which the processes operate -asynchronously, while our method produces a globally synchronous one in which -every process does the same thing at (approximately) the same time. Our -method seems to contradict the whole purpose of distributed processing, which -is to permit different processes to operate independently and perform -different functions. However, if a distributed system is really a single -system, then the processes must be synchronized in some way. Conceptually, -the easiest way to synchronize processes is to get them all to do the same -thing at the same time. Therefore, our method is used to implement a kernel -that performs the necessary synchronization--for example, making sure that -two different processes do not try to modify a file at the same time. -Processes might spend only a small fraction of their time executing the -synchronizing kernel; the rest of the time, they can operate -independently--e.g., accessing different files. This is an approach we have -advocated even when fault-tolerance is not required. The method's basic -simplicity makes it easier to understand the precise properties of a system, -which is crucial if one is to know just how fault-tolerant the system is. -[L.Lamport -(1984)]

-
-

Introduction to VDFs

-

A Verifiable Delay Function is conceptually a water clock where its water marks -can be recorded and later verified that the water most certainly passed -through. Anatoly describes the water clock analogy in detail here:

-

water clock analogy

-

The same technique has been used in Bitcoin since day one. The Bitcoin feature -is called nLocktime and it can be used to postdate transactions using block -height instead of a timestamp. As a Bitcoin client, you'd use block height -instead of a timestamp if you don't trust the network. Block height turns out -to be an instance of what's being called a Verifiable Delay Function in -cryptography circles. It's a cryptographically secure way to say time has -passed. In Solana, we use a far more granular verifiable delay function, a SHA -256 hash chain, to checkpoint the ledger and coordinate consensus. With it, we -implement Optimistic Concurrency Control and are now well en route towards that -theoretical limit of 710,000 transactions per second.

-

Proof of History

-

Proof of History overview

-

Relationship to consensus mechanisms

-

Most confusingly, a Proof of History (PoH) is more similar to a Verifiable -Delay Function (VDF) than a Proof of Work or Proof of Stake consensus -mechanism. The name unfortunately requires some historical context to -understand. Proof of History was developed by Anatoly Yakovenko in November of -2017, roughly 6 months before we saw a paper using the term -VDF. At that time, it was commonplace to -publish new proofs of some desirable property used to build most any blockchain -component. Some time shortly after, the crypto community began charting out all -the different consensus mechanisms and because most of them started with "Proof -of", the prefix became synonymous with a "consensus" suffix. Proof of History -is not a consensus mechanism, but it is used to improve the performance of -Solana's Proof of Stake consensus. It is also used to improve the performance -of the replication and storage protocols. To minimize confusion, Solana may -rebrand PoH to some flavor of the term VDF.

-

Relationship to VDFs

-

A desirable property of a VDF is that verification time is very fast. Solana's -approach to verifying its delay function is proportional to the time it took to -create it. Split over a 4000 core GPU, it is sufficiently fast for Solana's -needs, but if you asked the authors the paper cited above, they might tell you -(and have) that Solana's approach is algorithmically slow it shouldn't be -called a VDF. We argue the term VDF should represent the category of verifiable -delay functions and not just the subset with certain performance -characteristics. Until that's resolved, Solana will likely continue using the -term PoH for its application-specific VDF.

-

Another difference between PoH and VDFs used only for tracking duration, is -that PoH's hash chain includes hashes of any data the application observed. -That data is a double-edged sword. On one side, the data "proves history" - -that the data most certainly existed before hashes after it. On the side, it -means the application can manipulate the hash chain by changing when the data -is hashed. The PoH chain therefore does not serve as a good source of -randomness whereas a VDF without that data could. Solana's leader selection -algorithm (TODO: add link), for example, is derived only from the VDF height -and not its hash at that height.

-

Leader Rotation

-

A property of any permissionless blockchain is that the entity choosing the next block is randomly selected. In proof of stake systems, -that entity is typically called the "leader" or "block producer." In Solana, we call it the leader. Under the hood, a leader is -simply a mode of the fullnode. A fullnode runs as either a leader or validator. In this chapter, we describe how a fullnode determines -what node is the leader, how that mechanism may choose different leaders at the same time, and if so, how the system converges in response.

-

Leader Seed Generation

-

Leader selection is decided via a random seed. The process is as follows:

-
    -
  1. Periodically, at a specific PoH tick count, select the signatures of the votes that made up the last supermajority
  2. -
  3. Concatenate the signatures
  4. -
  5. Hash the resulting string for N counts
  6. -
  7. The resulting hash is the random seed for M counts, M leader slots, where M > N
  8. -
-

Leader Rotation

-
    -
  1. The leader is chosen via a random seed generated from stake weights and votes (the leader schedule)
  2. -
  3. The leader is rotated every T PoH ticks (leader slot), according to the leader schedule
  4. -
  5. The schedule is applicable for M voting rounds
  6. -
-

Leader's transmit for a count of T PoH ticks. When T is reached all the validators should switch to the next scheduled leader. To schedule leaders, the supermajority + M nodes are shuffled using the above calculated random seed.

-

All T ticks must be observed from the current leader for that part of PoH to be accepted by the network. If T ticks (and any intervening transactions) are not observed, the network optimistically fills in the T ticks, and continues with PoH from the next leader.

-

Partitions, Forks

-

Forks can arise at PoH tick counts that correspond to leader rotations, because leader nodes may or may not have observed the previous leader's data. These empty ticks are generated by all nodes in the network at a network-specified rate for hashes-per-tick Z.

-

There are only two possible versions of the PoH during a voting round: PoH with T ticks and entries generated by the current leader, or PoH with just ticks. The "just ticks" version of the PoH can be thought of as a virtual ledger, one that all nodes in the network can derive from the last tick in the previous slot.

-

Validators can ignore forks at other points (e.g. from the wrong leader), or slash the leader responsible for the fork.

-

Validators vote on the longest chain that contains their previous vote, or a longer chain if the lockout on their previous vote has expired.

-

Validator's View

-
Time Progression
-

The diagram below represents a validator's view of the PoH stream with possible forks over time. L1, L2, etc. are leader slots, and Es represent entries from that leader during that leader's slot. The xs represent ticks only, and time flows downwards in the diagram.

-

Leader scheduler

-

Note that an E appearing on 2 branches at the same slot is a slashable condition, so a validator observing L3 and L3' can slash L3 and safely choose x for that slot. Once a validator observes a supermajority vote on any branch, other branches can be discarded below that tick count. For any slot, validators need only consider a single "has entries" chain or a "ticks only" chain.

-
Time Division
-

It's useful to consider leader rotation over PoH tick count as time division of the job of encoding state for the network. The following table presents the above tree of forks as a time-divided ledger.

- - - -
leader slot L1 L2 L3 L4 L5
data E1 E2 E3 E4 E5
ticks since prev x xx
-

Note that only data from leader L3 will be accepted during leader slot -L3. Data from L3 may include "catchup" ticks back to a slot other than -L2 if L3 did not observe L2's data. L4 and L5's transmissions -include the "ticks since prev" PoH entries.

-

This arrangement of the network data streams permits nodes to save exactly this -to the ledger for replay, restart, and checkpoints.

-

Leader's View

-

When a new leader begins a slot, it must first transmit any PoH (ticks) -required to link the new slot with the most recently observed and voted -slot.

-

Examples

-

Small Partition

-
    -
  1. Network partition M occurs for 10% of the nodes
  2. -
  3. The larger partition K, with 90% of the stake weight continues to operate as -normal
  4. -
  5. M cycles through the ranks until one of them is leader, generating ticks for -slots where the leader is in K.
  6. -
  7. M validators observe 10% of the vote pool, finality is not reached.
  8. -
  9. M and K reconnect.
  10. -
  11. M validators cancel their votes on M, which has not reached finality, and -re-cast on K (after their vote lockout on M).
  12. -
-

Leader Timeout

-
    -
  1. Next rank leader node V observes a timeout from current leader A, fills in -A's slot with virtual ticks and starts sending out entries.
  2. -
  3. Nodes observing both streams keep track of the forks, waiting for: -
      -
    • their vote on leader A to expire in order to be able to vote on B
    • -
    • a supermajority on A's slot
    • -
    -
  4. -
  5. If the first case occurs, leader B's slot is filled with ticks. if the -second case occurs, A's slot is filled with ticks
  6. -
  7. Partition is resolved just like in the Small Partition -above
  8. -
-

Network Variables

-

A - name of a node

-

B - name of a node

-

K - number of nodes in the supermajority to whom leaders broadcast their -PoH hash for validation

-

M - number of nodes outside the supermajority to whom leaders broadcast their -PoH hash for validation

-

N - number of voting rounds for which a leader schedule is considered before -a new leader schedule is used

-

T - number of PoH ticks per leader slot (also voting round)

-

V - name of a node that will create virtual ticks

-

Z - number of hashes per PoH tick

-

Fullnode

-

Fullnode block diagrams

-

Pipelining

-

The fullnodes make extensive use of an optimization common in CPU design, -called pipelining. Pipelining is the right tool for the job when there's a -stream of input data that needs to be processed by a sequence of steps, and -there's different hardware responsible for each. The quintessential example is -using a washer and dryer to wash/dry/fold several loads of laundry. Washing -must occur before drying and drying before folding, but each of the three -operations is performed by a separate unit. To maximize efficiency, one creates -a pipeline of stages. We'll call the washer one stage, the dryer another, and -the folding process a third. To run the pipeline, one adds a second load of -laundry to the washer just after the first load is added to the dryer. -Likewise, the third load is added to the washer after the second is in the -dryer and the first is being folded. In this way, one can make progress on -three loads of laundry simultaneously. Given infinite loads, the pipeline will -consistently complete a load at the rate of the slowest stage in the pipeline.

-

Pipelining in the fullnode

-

The fullnode contains two pipelined processes, one used in leader mode called -the Tpu and one used in validator mode called the Tvu. In both cases, the -hardware being pipelined is the same, the network input, the GPU cards, the CPU -cores, writes to disk, and the network output. What it does with that hardware -is different. The Tpu exists to create ledger entries whereas the Tvu exists -to validate them.

-

The Transaction Processing Unit

-

Tpu block diagram

-

The Transaction Validation Unit

-

Tvu block diagram

-

Ncp

-

The Network Control Plane implements a gossip network between all nodes on in the cluster.

-

JsonRpcService

-

Avalanche replication

-

The Avalance explainer video is -a conceptual overview of how a Solana leader can continuously process a gigabit -of transaction data per second and then get that same data, after being -recorded on the ledger, out to multiple validators on a single gigabit -backplane.

-

In practice, we found that just one level of the Avalanche validator tree is -sufficient for at least 150 validators. We anticipate adding the second level -to solve one of two problems:

-
    -
  1. To transmit ledger segments to slower "replicator" nodes.
  2. -
  3. To scale up the number of validators nodes.
  4. -
-

Both problems justify the additional level, but you won't find it implemented -in the reference design just yet, because Solana's gossip implementation is -currently the bottleneck on the number of nodes per Solana cluster. That work -is being actively developed here:

-

Scalable Gossip

-

Storage

-

Background

-

At full capacity on a 1gbps network Solana would generate 4 petabytes of data -per year. If each fullnode was required to store the full ledger, the cost of -storage would discourage fullnode participation, thus centralizing the network -around those that could afford it. Solana aims to keep the cost of a fullnode -below $5,000 USD to maximize participation. To achieve that, the network needs -to minimize redundant storage while at the same time ensuring the validity and -availability of each copy.

-

To trust storage of ledger segments, Solana has replicators periodically -submit proofs to the network that the data was replicated. Each proof is called -a Proof of Replication. The basic idea of it is to encrypt a dataset with a -public symmetric key and then hash the encrypted dataset. Solana uses CBC -encryption. -To prevent a malicious replicator from deleting the data as soon as it's -hashed, a replicator is required hash random segments of the dataset. -Alternatively, Solana could require hashing the reverse of the encrypted data, -but random sampling is sufficient and much faster. Either solution ensures -that all the data is present during the generation of the proof and also -requires the validator to have the entirety of the encrypted data present for -verification of every proof of every identity. The space required to validate -is:

-

number_of_proofs * data_size

-

Optimization with PoH

-

Solana is not the only distribute systems project using Proof of Replication, -but it might be the most efficient implementation because of its ability to -synchronize nodes with its Proof of History. With PoH, Solana is able to record -a hash of the PoRep samples in the ledger. Thus the blocks stay in the exact -same order for every PoRep and verification can stream the data and verify all -the proofs in a single batch. This way Solana can verify multiple proofs -concurrently, each one on its own GPU core. With the current generation of -graphics cards our network can support up to 14,000 replication identities or -symmetric keys. The total space required for verification is:

-

2 CBC_blocks * number_of_identities

-

with core count of equal to (Number of Identities). A CBC block is expected to -be 1MB in size.

-

Network

-

Validators for PoRep are the same validators that are verifying transactions. -They have some stake that they have put up as collateral that ensures that -their work is honest. If you can prove that a validator verified a fake PoRep, -then the validator's stake is slashed.

-

Replicators are specialized light clients. They download a part of the ledger -and store it and provide proofs of storing the ledger. For each verified proof, -replicators are rewarded tokens from the mining pool.

-

Constraints

-

Solana's PoRep protocol instroduces the following constraints:

-
    -
  • At most 14,000 replication identities can be used, because that is how many GPU -cores are currently available to a computer costing under $5,000 USD.
  • -
  • Verification requires generating the CBC blocks. That requires space of 2 -blocks per identity, and 1 GPU core per identity for the same dataset. As -many identities at once are batched with as many proofs for those identities -verified concurrently for the same dataset.
  • -
-

Validation and Replication Protocol

-
    -
  1. The network sets a replication target number, let's say 1k. 1k PoRep -identities are created from signatures of a PoH hash. They are tied to a -specific PoH hash. It doesn't matter who creates them, or it could simply be -the last 1k validation signatures we saw for the ledger at that count. This is -maybe just the initial batch of identities, because we want to stagger identity -rotation.
  2. -
  3. Any client can use any of these identities to create PoRep proofs. -Replicator identities are the CBC encryption keys.
  4. -
  5. Periodically at a specific PoH count, a replicator that wants to create -PoRep proofs signs the PoH hash at that count. That signature is the seed -used to pick the block and identity to replicate. A block is 1TB of ledger.
  6. -
  7. Periodically at a specific PoH count, a replicator submits PoRep proofs for -their selected block. A signature of the PoH hash at that count is the seed -used to sample the 1TB encrypted block, and hash it. This is done faster than -it takes to encrypt the 1TB block with the original identity.
  8. -
  9. Replicators must submit some number of fake proofs, which they can prove to -be fake by providing the seed for the hash result.
  10. -
  11. Periodically at a specific PoH count, validators sign the hash and use the -signature to select the 1TB block that they need to validate. They batch all -the identities and proofs and submit approval for all the verified ones.
  12. -
  13. After #6, replicator client submit the proofs of fake proofs.
  14. -
-

For any random seed, Solana requires everyone to use a signature that is -derived from a PoH hash. Every node uses the same count so that the same PoH -hash is signed by every participant. The signatures are then each -cryptographically tied to the keypair, which prevents a leader from grinding on -the resulting value for more than 1 identity.

-

Key rotation is staggered. Once going, the next identity is generated by -hashing itself with a PoH hash.

-

Since there are many more client identities then encryption identities, the -reward is split amont multiple clients to prevent Sybil attacks from generating -many clients to acquire the same block of data. To remain BFT, the network -needs to avoid a single human entity from storing all the replications of a -single chunk of the ledger.

-

Solana's solution to this is to require clients to continue using the same -identity. If the first round is used to acquire the same block for many client -identities, the second round for the same client identities will require a -redistribution of the signatures, and therefore PoRep identities and blocks. -Thus to get a reward for storage, clients are not rewarded for storage of the -first block. The network rewards long-lived client identities more than new -ones.

-

The Solana SDK

-

Introduction

-

With the Solana runtime, we can execute on-chain programs concurrently, and -written in the client’s choice of programming language.

-

Client interactions with Solana

-

SDK tools

-

As shown in the diagram above an untrusted client, creates a program in the -language of their choice, (i.e. C/C++/Rust/Lua), and compiles it with LLVM to a -position independent shared object ELF, targeting BPF bytecode, and sends it to -the Solana cluster. Next, the client sends messages to the Solana cluster, -which target that program. The Solana runtime loads the previously submitted -ELF and passes it the client's message for interpretation.

-

Persistent Storage

-

Solana supports several kinds of persistent storage, called accounts:

-
    -
  1. Executable
  2. -
  3. Writable by a client
  4. -
  5. Writable by a program
  6. -
  7. Read-only
  8. -
-

All accounts are identified by public keys and may hold arbirary data. -When the client sends messages to programs, it requests access to storage -using those keys. The runtime loads the account data and passes it to the -program. The runtime also ensures accounts aren't written to if not owned -by the client or program. Any writes to read-only accounts are discarded -unless the write was to credit tokens. Any user may credit other accounts -tokens, regardless of account permission.

-

Runtime

-

The goal with the runtime is to have a general purpose execution environment -that is highly parallelizable. To achieve this goal the runtime forces each -Instruction to specify all of its memory dependencies up front, and therefore a -single Instruction cannot cause a dynamic memory allocation. An explicit -Instruction for memory allocation from the SystemProgram::CreateAccount is -the only way to allocate new memory in the engine. A Transaction may compose -multiple Instruction, including SystemProgram::CreateAccount, into a single -atomic sequence which allows for memory allocation to achieve a result that is -similar to dynamic allocation.

-

State

-

State is addressed by an Account which is at the moment simply the Pubkey. Our -goal is to eliminate memory allocation from within the program itself. Thus -the client of the program provides all the state that is necessary for the -program to execute in the transaction itself. The runtime interacts with the -program through an entry point with a well defined interface. The userdata -stored in an Account is an opaque type to the runtime, a Vec<u8>, the -contents of which the program code has full control over.

-

The Transaction structure specifies a list of Pubkey's and signatures for those -keys and a sequential list of instructions that will operate over the state's -associated with the account_keys. For the transaction to be committed all -the instructions must execute successfully, if any abort the whole transaction -fails to commit.

-

Account structure Accounts maintain token state as well as program specific

-

memory.

-

Transaction Engine

-

At its core, the engine looks up all the Pubkeys maps them to accounts and -routs them to the program_id entry point.

-

Execution

-

Transactions are batched and processed in a pipeline

-

Runtime pipeline

-

At the execute stage, the loaded pages have no data dependencies, so all the -programs can be executed in parallel.

-

The runtime enforces the following rules:

-
    -
  1. The program_id code is the only code that will modify the contents of -Account::userdata of Account's that have been assigned to it. This means -that upon assignment userdata vector is guaranteed to be 0.
  2. -
  3. Total balances on all the accounts is equal before and after execution of a -Transaction.
  4. -
  5. Balances of each of the accounts not assigned to program_id must be equal -to or greater after the Transaction than before the transaction.
  6. -
  7. All Instructions in the Transaction executed without a failure.
  8. -
-

Entry Point Execution of the program involves mapping the Program's public

-

key to an entry point which takes a pointer to the transaction, and an array of -loaded pages.

-

System Interface

-

The interface is best described by the Instruction::userdata that the -user encodes.

-
    -
  • CreateAccount - This allows the user to create and assign an Account to a -Program.
  • -
  • Assign - allows the user to assign an existing account to a Program.
  • -
  • Move - moves tokens between Accounts that are associated with -SystemProgram. This cannot be used to move tokens of other Accounts. -Programs need to implement their own version of Move.
  • -
-

Notes

-
    -
  1. There is no dynamic memory allocation. Client's need to call the -SystemProgram to create memory before passing it to another program. This -Instruction can be composed into a single Transaction with the call to the -program itself.
  2. -
  3. Runtime guarantees that when memory is assigned to the Program it is zero -initialized.
  4. -
  5. Runtime guarantees that Program's code is the only thing that can modify -memory that its assigned to
  6. -
  7. Runtime guarantees that the Program can only spend tokens that are in -Accounts that are assigned to it
  8. -
  9. Runtime guarantees the balances belonging to Accounts are balanced before -and after the transaction
  10. -
  11. Runtime guarantees that multiple instructions all executed successfully when -a transaction is committed.
  12. -
-

Future Work

- -

Ledger format

-

Appendix

-

The following sections contain reference material you may find useful in your -Solana journey.

-

JSON RPC API

-

Solana nodes accept HTTP requests using the JSON-RPC 2.0 specification.

-

To interact with a Solana node inside a JavaScript application, use the solana-web3.js library, which gives a convenient interface for the RPC methods.

-

RPC HTTP Endpoint

-

Default port: 8899 -eg. http://localhost:8899, http://192.168.1.88:8899

-

RPC PubSub WebSocket Endpoint

-

Default port: 8900 -eg. ws://localhost:8900, http://192.168.1.88:8900

-

Methods

- -

Request Formatting

-

To make a JSON-RPC request, send an HTTP POST request with a Content-Type: application/json header. The JSON request data should contain 4 fields:

-
    -
  • jsonrpc, set to "2.0"
  • -
  • id, a unique client-generated identifying integer
  • -
  • method, a string containing the method to be invoked
  • -
  • params, a JSON array of ordered parameter values
  • -
-

Example using curl:

-
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' 192.168.1.88:8899
-
-

The response output will be a JSON object with the following fields:

-
    -
  • jsonrpc, matching the request specification
  • -
  • id, matching the request identifier
  • -
  • result, requested data or success confirmation
  • -
-

Requests can be sent in batches by sending an array of JSON-RPC request objects as the data for a single POST.

-

Definitions

-
    -
  • Hash: A SHA-256 hash of a chunk of data.
  • -
  • Pubkey: The public key of a Ed25519 key-pair.
  • -
  • Signature: An Ed25519 signature of a chunk of data.
  • -
  • Transaction: A Solana instruction signed by a client key-pair.
  • -
-

JSON RPC API Reference

-

confirmTransaction

-

Returns a transaction receipt

-
Parameters:
-
    -
  • string - Signature of Transaction to confirm, as base-58 encoded string
  • -
-
Results:
-
    -
  • boolean - Transaction status, true if Transaction is confirmed
  • -
-
Example:
-
// Request
-curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"confirmTransaction", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
-
-// Result
-{"jsonrpc":"2.0","result":true,"id":1}
-
-
-

getBalance

-

Returns the balance of the account of provided Pubkey

-
Parameters:
-
    -
  • string - Pubkey of account to query, as base-58 encoded string
  • -
-
Results:
-
    -
  • integer - quantity, as a signed 64-bit integer
  • -
-
Example:
-
// Request
-curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' http://localhost:8899
-
-// Result
-{"jsonrpc":"2.0","result":0,"id":1}
-
-
-

getAccountInfo

-

Returns all information associated with the account of provided Pubkey

-
Parameters:
-
    -
  • string - Pubkey of account to query, as base-58 encoded string
  • -
-
Results:
-

The result field will be a JSON object with the following sub fields:

-
    -
  • tokens, number of tokens assigned to this account, as a signed 64-bit integer
  • -
  • owner, array of 32 bytes representing the program this account has been assigned to
  • -
  • userdata, array of bytes representing any userdata associated with the account
  • -
  • executable, boolean indicating if the account contains a program (and is strictly read-only)
  • -
  • loader, array of 32 bytes representing the loader for this program (if executable), otherwise all
  • -
-
Example:
-
// Request
-curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"]}' http://localhost:8899
-
-// Result
-{"jsonrpc":"2.0","result":{"executable":false,"loader":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"tokens":1,"userdata":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"id":1}
-
-
-

getLastId

-

Returns the last entry ID from the ledger

-
Parameters:
-

None

-
Results:
-
    -
  • string - the ID of last entry, a Hash as base-58 encoded string
  • -
-
Example:
-
// Request
-curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getLastId"}' http://localhost:8899
-
-// Result
-{"jsonrpc":"2.0","result":"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC","id":1}
-
-
-

getSignatureStatus

-

Returns the status of a given signature. This method is similar to -confirmTransaction but provides more resolution for error -events.

-
Parameters:
-
    -
  • string - Signature of Transaction to confirm, as base-58 encoded string
  • -
-
Results:
-
    -
  • string - Transaction status: -
      -
    • Confirmed - Transaction was successful
    • -
    • SignatureNotFound - Unknown transaction
    • -
    • ProgramRuntimeError - An error occurred in the program that processed this Transaction
    • -
    • AccountInUse - Another Transaction had a write lock one of the Accounts specified in this Transaction. The Transaction may succeed if retried
    • -
    • GenericFailure - Some other error occurred. Note: In the future new Transaction statuses may be added to this list. It's safe to assume that all new statuses will be more specific error conditions that previously presented as GenericFailure
    • -
    -
  • -
-
Example:
-
// Request
-curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatus", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
-
-// Result
-{"jsonrpc":"2.0","result":"SignatureNotFound","id":1}
-
-
-

getTransactionCount

-

Returns the current Transaction count from the ledger

-
Parameters:
-

None

-
Results:
-
    -
  • integer - count, as unsigned 64-bit integer
  • -
-
Example:
-
// Request
-curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://localhost:8899
-
-// Result
-{"jsonrpc":"2.0","result":268,"id":1}
-
-
-

requestAirdrop

-

Requests an airdrop of tokens to a Pubkey

-
Parameters:
-
    -
  • string - Pubkey of account to receive tokens, as base-58 encoded string
  • -
  • integer - token quantity, as a signed 64-bit integer
  • -
-
Results:
-
    -
  • string - Transaction Signature of airdrop, as base-58 encoded string
  • -
-
Example:
-
// Request
-curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"requestAirdrop", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri", 50]}' http://localhost:8899
-
-// Result
-{"jsonrpc":"2.0","result":"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW","id":1}
-
-
-

sendTransaction

-

Creates new transaction

-
Parameters:
-
    -
  • array - array of octets containing a fully-signed Transaction
  • -
-
Results:
-
    -
  • string - Transaction Signature, as base-58 encoded string
  • -
-
Example:
-
// Request
-curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"sendTransaction", "params":[[61, 98, 55, 49, 15, 187, 41, 215, 176, 49, 234, 229, 228, 77, 129, 221, 239, 88, 145, 227, 81, 158, 223, 123, 14, 229, 235, 247, 191, 115, 199, 71, 121, 17, 32, 67, 63, 209, 239, 160, 161, 2, 94, 105, 48, 159, 235, 235, 93, 98, 172, 97, 63, 197, 160, 164, 192, 20, 92, 111, 57, 145, 251, 6, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 13, 39, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 11, 12, 106, 49, 74, 226, 201, 16, 161, 192, 28, 84, 124, 97, 190, 201, 171, 186, 6, 18, 70, 142, 89, 185, 176, 154, 115, 61, 26, 163, 77, 1, 88, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]}' http://localhost:8899
-
-// Result
-{"jsonrpc":"2.0","result":"2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b","id":1}
-
-
-

Subscription Websocket

-

After connect to the RPC PubSub websocket at ws://<ADDRESS>/:

-
    -
  • Submit subscription requests to the websocket using the methods below
  • -
  • Multiple subscriptions may be active at once
  • -
-
-

accountSubscribe

-

Subscribe to an account to receive notifications when the userdata for a given account public key changes

-
Parameters:
-
    -
  • string - account Pubkey, as base-58 encoded string
  • -
-
Results:
-
    -
  • integer - Subscription id (needed to unsubscribe)
  • -
-
Example:
-
// Request
-{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"]}
-
-// Result
-{"jsonrpc": "2.0","result": 0,"id": 1}
-
-
Notification Format:
-
{"jsonrpc": "2.0","method": "accountNotification", "params": {"result": {"executable":false,"loader":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"tokens":1,"userdata":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"subscription":0}}
-
-
-

accountUnsubscribe

-

Unsubscribe from account userdata change notifications

-
Parameters:
-
    -
  • integer - id of account Subscription to cancel
  • -
-
Results:
-
    -
  • bool - unsubscribe success message
  • -
-
Example:
-
// Request
-{"jsonrpc":"2.0", "id":1, "method":"accountUnsubscribe", "params":[0]}
-
-// Result
-{"jsonrpc": "2.0","result": true,"id": 1}
-
-
-

signatureSubscribe

-

Subscribe to a transaction signature to receive notification when the transaction is confirmed -On signatureNotification, the subscription is automatically cancelled

-
Parameters:
-
    -
  • string - Transaction Signature, as base-58 encoded string
  • -
-
Results:
-
    -
  • integer - subscription id (needed to unsubscribe)
  • -
-
Example:
-
// Request
-{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b"]}
-
-// Result
-{"jsonrpc": "2.0","result": 0,"id": 1}
-
-
Notification Format:
-
{"jsonrpc": "2.0","method": "signatureNotification", "params": {"result": "Confirmed","subscription":0}}
-
-
-

signatureUnsubscribe

-

Unsubscribe from account userdata change notifications

-
Parameters:
-
    -
  • integer - id of account subscription to cancel
  • -
-
Results:
-
    -
  • bool - unsubscribe success message
  • -
-
Example:
-
// Request
-{"jsonrpc":"2.0", "id":1, "method":"signatureUnsubscribe", "params":[0]}
-
-// Result
-{"jsonrpc": "2.0","result": true,"id": 1}
-
-

solana-wallet CLI

-

The solana crate is distributed with a command-line interface tool

-

Examples

-

Get Pubkey

-
// Command
-$ solana-wallet address
-
-// Return
-<PUBKEY>
-
-

Airdrop Tokens

-
// Command
-$ solana-wallet airdrop 123
-
-// Return
-"Your balance is: 123"
-
-

Get Balance

-
// Command
-$ solana-wallet balance
-
-// Return
-"Your balance is: 123"
-
-

Confirm Transaction

-
// Command
-$ solana-wallet confirm <TX_SIGNATURE>
-
-// Return
-"Confirmed" / "Not found"
-
-

Deploy program

-
// Command
-$ solana-wallet deploy <PATH>
-
-// Return
-<PROGRAM_ID>
-
-

Unconditional Immediate Transfer

-
// Command
-$ solana-wallet pay <PUBKEY> 123
-
-// Return
-<TX_SIGNATURE>
-
-

Post-Dated Transfer

-
// Command
-$ solana-wallet pay <PUBKEY> 123 \
-    --after 2018-12-24T23:59:00 --require-timestamp-from <PUBKEY>
-
-// Return
-{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
-
-

require-timestamp-from is optional. If not provided, the transaction will expect a timestamp signed by this wallet's secret key

-

Authorized Transfer

-

A third party must send a signature to unlock the tokens.

-
// Command
-$ solana-wallet pay <PUBKEY> 123 \
-    --require-signature-from <PUBKEY>
-
-// Return
-{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
-
-

Post-Dated and Authorized Transfer

-
// Command
-$ solana-wallet pay <PUBKEY> 123 \
-    --after 2018-12-24T23:59 --require-timestamp-from <PUBKEY> \
-    --require-signature-from <PUBKEY>
-
-// Return
-{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
-
-

Multiple Witnesses

-
// Command
-$ solana-wallet pay <PUBKEY> 123 \
-    --require-signature-from <PUBKEY> \
-    --require-signature-from <PUBKEY>
-
-// Return
-{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
-
-

Cancelable Transfer

-
// Command
-$ solana-wallet pay <PUBKEY> 123 \
-    --require-signature-from <PUBKEY> \
-    --cancelable
-
-// Return
-{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
-
-

Cancel Transfer

-
// Command
-$ solana-wallet cancel <PROCESS_ID>
-
-// Return
-<TX_SIGNATURE>
-
-

Send Signature

-
// Command
-$ solana-wallet send-signature <PUBKEY> <PROCESS_ID>
-
-// Return
-<TX_SIGNATURE>
-
-

Indicate Elapsed Time

-

Use the current system time:

-
// Command
-$ solana-wallet send-timestamp <PUBKEY> <PROCESS_ID>
-
-// Return
-<TX_SIGNATURE>
-
-

Or specify some other arbitrary timestamp:

-
// Command
-$ solana-wallet send-timestamp <PUBKEY> <PROCESS_ID> --date 2018-12-24T23:59:00
-
-// Return
-<TX_SIGNATURE>
-
-

Usage

-
solana-wallet 0.11.0
-
-USAGE:
-    solana-wallet [OPTIONS] [SUBCOMMAND]
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-OPTIONS:
-    -k, --keypair <PATH>         /path/to/id.json
-    -n, --network <HOST:PORT>    Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001
-        --proxy <URL>            Address of TLS proxy
-        --port <NUM>             Optional rpc-port configuration to connect to non-default nodes
-        --timeout <SECS>         Max seconds to wait to get necessary gossip from the network
-
-SUBCOMMANDS:
-    address                  Get your public key
-    airdrop                  Request a batch of tokens
-    balance                  Get your balance
-    cancel                   Cancel a transfer
-    confirm                  Confirm transaction by signature
-    deploy                   Deploy a program
-    get-transaction-count    Get current transaction count
-    help                     Prints this message or the help of the given subcommand(s)
-    pay                      Send a payment
-    send-signature           Send a signature to authorize a transfer
-    send-timestamp           Send a timestamp to unlock a transfer
-
-
solana-wallet-address 
-Get your public key
-
-USAGE:
-    solana-wallet address
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-
solana-wallet-airdrop 
-Request a batch of tokens
-
-USAGE:
-    solana-wallet airdrop <NUM>
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-ARGS:
-    <NUM>    The number of tokens to request
-
-
solana-wallet-balance 
-Get your balance
-
-USAGE:
-    solana-wallet balance
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-
solana-wallet-cancel 
-Cancel a transfer
-
-USAGE:
-    solana-wallet cancel <PROCESS_ID>
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-ARGS:
-    <PROCESS_ID>    The process id of the transfer to cancel
-
-
solana-wallet-confirm 
-Confirm transaction by signature
-
-USAGE:
-    solana-wallet confirm <SIGNATURE>
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-ARGS:
-    <SIGNATURE>    The transaction signature to confirm
-
-
solana-wallet-deploy 
-Deploy a program
-
-USAGE:
-    solana-wallet deploy <PATH>
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-ARGS:
-    <PATH>    /path/to/program.o
-
-
solana-wallet-get-transaction-count 
-Get current transaction count
-
-USAGE:
-    solana-wallet get-transaction-count
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-
solana-wallet-pay 
-Send a payment
-
-USAGE:
-    solana-wallet pay [FLAGS] [OPTIONS] <PUBKEY> <NUM>
-
-FLAGS:
-        --cancelable    
-    -h, --help          Prints help information
-    -V, --version       Prints version information
-
-OPTIONS:
-        --after <DATETIME>                      A timestamp after which transaction will execute
-        --require-timestamp-from <PUBKEY>       Require timestamp from this third party
-        --require-signature-from <PUBKEY>...    Any third party signatures required to unlock the tokens
-
-ARGS:
-    <PUBKEY>    The pubkey of recipient
-    <NUM>       The number of tokens to send
-
-
solana-wallet-send-signature 
-Send a signature to authorize a transfer
-
-USAGE:
-    solana-wallet send-signature <PUBKEY> <PROCESS_ID>
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-ARGS:
-    <PUBKEY>        The pubkey of recipient
-    <PROCESS_ID>    The process id of the transfer to authorize
-
-
solana-wallet-send-timestamp 
-Send a timestamp to unlock a transfer
-
-USAGE:
-    solana-wallet send-timestamp [OPTIONS] <PUBKEY> <PROCESS_ID>
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-OPTIONS:
-        --date <DATETIME>    Optional arbitrary timestamp to apply
-
-ARGS:
-    <PUBKEY>        The pubkey of recipient
-    <PROCESS_ID>    The process id of the transfer to unlock
-
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/programs.html b/book/programs.html deleted file mode 100644 index 7beec11f5e749a..00000000000000 --- a/book/programs.html +++ /dev/null @@ -1,226 +0,0 @@ - - - - - - On-chain programs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

The Solana SDK

-

Introduction

-

With the Solana runtime, we can execute on-chain programs concurrently, and -written in the client’s choice of programming language.

-

Client interactions with Solana

-

SDK tools

-

As shown in the diagram above an untrusted client, creates a program in the -language of their choice, (i.e. C/C++/Rust/Lua), and compiles it with LLVM to a -position independent shared object ELF, targeting BPF bytecode, and sends it to -the Solana cluster. Next, the client sends messages to the Solana cluster, -which target that program. The Solana runtime loads the previously submitted -ELF and passes it the client's message for interpretation.

-

Persistent Storage

-

Solana supports several kinds of persistent storage, called accounts:

-
    -
  1. Executable
  2. -
  3. Writable by a client
  4. -
  5. Writable by a program
  6. -
  7. Read-only
  8. -
-

All accounts are identified by public keys and may hold arbirary data. -When the client sends messages to programs, it requests access to storage -using those keys. The runtime loads the account data and passes it to the -program. The runtime also ensures accounts aren't written to if not owned -by the client or program. Any writes to read-only accounts are discarded -unless the write was to credit tokens. Any user may credit other accounts -tokens, regardless of account permission.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/recvmmsg.rs b/book/recvmmsg.rs deleted file mode 100644 index ce6f89a3790e5d..00000000000000 --- a/book/recvmmsg.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! The `recvmmsg` module provides recvmmsg() API implementation - -use packet::Packet; -use std::cmp; -use std::io; -use std::net::UdpSocket; - -pub const NUM_RCVMMSGS: usize = 16; - -#[cfg(not(target_os = "linux"))] -pub fn recv_mmsg(socket: &UdpSocket, packets: &mut [Packet]) -> io::Result { - let mut i = 0; - socket.set_nonblocking(false)?; - let count = cmp::min(NUM_RCVMMSGS, packets.len()); - for p in packets.iter_mut().take(count) { - p.meta.size = 0; - match socket.recv_from(&mut p.data) { - Err(_) if i > 0 => { - break; - } - Err(e) => { - return Err(e); - } - Ok((nrecv, from)) => { - p.meta.size = nrecv; - p.meta.set_addr(&from); - if i == 0 { - socket.set_nonblocking(true)?; - } - } - } - i += 1; - } - Ok(i) -} - -#[cfg(target_os = "linux")] -pub fn recv_mmsg(sock: &UdpSocket, packets: &mut [Packet]) -> io::Result { - use libc::{ - c_void, iovec, mmsghdr, recvmmsg, sockaddr_in, socklen_t, time_t, timespec, MSG_WAITFORONE, - }; - use nix::sys::socket::InetAddr; - use std::mem; - use std::os::unix::io::AsRawFd; - - let mut hdrs: [mmsghdr; NUM_RCVMMSGS] = unsafe { mem::zeroed() }; - let mut iovs: [iovec; NUM_RCVMMSGS] = unsafe { mem::zeroed() }; - let mut addr: [sockaddr_in; NUM_RCVMMSGS] = unsafe { mem::zeroed() }; - let addrlen = mem::size_of_val(&addr) as socklen_t; - - let sock_fd = sock.as_raw_fd(); - - let count = cmp::min(iovs.len(), packets.len()); - - for i in 0..count { - iovs[i].iov_base = packets[i].data.as_mut_ptr() as *mut c_void; - iovs[i].iov_len = packets[i].data.len(); - - hdrs[i].msg_hdr.msg_name = &mut addr[i] as *mut _ as *mut _; - hdrs[i].msg_hdr.msg_namelen = addrlen; - hdrs[i].msg_hdr.msg_iov = &mut iovs[i]; - hdrs[i].msg_hdr.msg_iovlen = 1; - } - let mut ts = timespec { - tv_sec: 1 as time_t, - tv_nsec: 0, - }; - - let npkts = - match unsafe { recvmmsg(sock_fd, &mut hdrs[0], count as u32, MSG_WAITFORONE, &mut ts) } { - -1 => return Err(io::Error::last_os_error()), - n => { - for i in 0..n as usize { - let mut p = &mut packets[i]; - p.meta.size = hdrs[i].msg_len as usize; - let inet_addr = InetAddr::V4(addr[i]); - p.meta.set_addr(&inet_addr.to_std()); - } - n as usize - } - }; - - Ok(npkts) -} - -#[cfg(test)] -mod tests { - use packet::PACKET_DATA_SIZE; - use recvmmsg::*; - - #[test] - pub fn test_recv_mmsg_one_iter() { - let reader = UdpSocket::bind("127.0.0.1:0").expect("bind"); - let addr = reader.local_addr().unwrap(); - let sender = UdpSocket::bind("127.0.0.1:0").expect("bind"); - let saddr = sender.local_addr().unwrap(); - let sent = NUM_RCVMMSGS - 1; - for _ in 0..sent { - let data = [0; PACKET_DATA_SIZE]; - sender.send_to(&data[..], &addr).unwrap(); - } - - let mut packets = vec![Packet::default(); NUM_RCVMMSGS]; - let recv = recv_mmsg(&reader, &mut packets[..]).unwrap(); - assert_eq!(sent, recv); - for i in 0..recv { - assert_eq!(packets[i].meta.size, PACKET_DATA_SIZE); - assert_eq!(packets[i].meta.addr(), saddr); - } - } - - #[test] - pub fn test_recv_mmsg_multi_iter() { - let reader = UdpSocket::bind("127.0.0.1:0").expect("bind"); - let addr = reader.local_addr().unwrap(); - let sender = UdpSocket::bind("127.0.0.1:0").expect("bind"); - let saddr = sender.local_addr().unwrap(); - let sent = NUM_RCVMMSGS + 10; - for _ in 0..sent { - let data = [0; PACKET_DATA_SIZE]; - sender.send_to(&data[..], &addr).unwrap(); - } - - let mut packets = vec![Packet::default(); NUM_RCVMMSGS * 2]; - let recv = recv_mmsg(&reader, &mut packets[..]).unwrap(); - assert_eq!(NUM_RCVMMSGS, recv); - for i in 0..recv { - assert_eq!(packets[i].meta.size, PACKET_DATA_SIZE); - assert_eq!(packets[i].meta.addr(), saddr); - } - - let recv = recv_mmsg(&reader, &mut packets[..]).unwrap(); - assert_eq!(sent - NUM_RCVMMSGS, recv); - for i in 0..recv { - assert_eq!(packets[i].meta.size, PACKET_DATA_SIZE); - assert_eq!(packets[i].meta.addr(), saddr); - } - } - - #[test] - pub fn test_recv_mmsg_multi_addrs() { - let reader = UdpSocket::bind("127.0.0.1:0").expect("bind"); - let addr = reader.local_addr().unwrap(); - - let sender1 = UdpSocket::bind("127.0.0.1:0").expect("bind"); - let saddr1 = sender1.local_addr().unwrap(); - let sent1 = NUM_RCVMMSGS - 1; - - let sender2 = UdpSocket::bind("127.0.0.1:0").expect("bind"); - let saddr2 = sender2.local_addr().unwrap(); - let sent2 = NUM_RCVMMSGS + 1; - - for _ in 0..sent1 { - let data = [0; PACKET_DATA_SIZE]; - sender1.send_to(&data[..], &addr).unwrap(); - } - - for _ in 0..sent2 { - let data = [0; PACKET_DATA_SIZE]; - sender2.send_to(&data[..], &addr).unwrap(); - } - - let mut packets = vec![Packet::default(); NUM_RCVMMSGS * 2]; - - let recv = recv_mmsg(&reader, &mut packets[..]).unwrap(); - assert_eq!(NUM_RCVMMSGS, recv); - for i in 0..sent1 { - assert_eq!(packets[i].meta.size, PACKET_DATA_SIZE); - assert_eq!(packets[i].meta.addr(), saddr1); - } - - for i in sent1..recv { - assert_eq!(packets[i].meta.size, PACKET_DATA_SIZE); - assert_eq!(packets[i].meta.addr(), saddr2); - } - - let recv = recv_mmsg(&reader, &mut packets[..]).unwrap(); - assert_eq!(sent1 + sent2 - NUM_RCVMMSGS, recv); - for i in 0..recv { - assert_eq!(packets[i].meta.size, PACKET_DATA_SIZE); - assert_eq!(packets[i].meta.addr(), saddr2); - } - } -} diff --git a/book/replicate_stage.rs b/book/replicate_stage.rs deleted file mode 100644 index bccf44eef46c82..00000000000000 --- a/book/replicate_stage.rs +++ /dev/null @@ -1,683 +0,0 @@ -//! The `replicate_stage` replicates transactions broadcast by the leader. - -use bank::Bank; -use cluster_info::ClusterInfo; -use counter::Counter; -use entry::{EntryReceiver, EntrySender}; -use solana_sdk::hash::Hash; - -use ledger::Block; -use log::Level; -use packet::BlobError; -use result::{Error, Result}; -use service::Service; -use signature::{Keypair, KeypairUtil}; -use solana_metrics::{influxdb, submit}; -use solana_sdk::timing::duration_as_ms; -use std::net::UdpSocket; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::sync::mpsc::channel; -use std::sync::mpsc::RecvTimeoutError; -use std::sync::{Arc, RwLock}; -use std::thread::{self, Builder, JoinHandle}; -use std::time::Duration; -use std::time::Instant; -use streamer::{responder, BlobSender}; -use vote_stage::send_validator_vote; - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum ReplicateStageReturnType { - LeaderRotation(u64, u64, Hash), -} - -// Implement a destructor for the ReplicateStage thread to signal it exited -// even on panics -struct Finalizer { - exit_sender: Arc, -} - -impl Finalizer { - fn new(exit_sender: Arc) -> Self { - Finalizer { exit_sender } - } -} -// Implement a destructor for Finalizer. -impl Drop for Finalizer { - fn drop(&mut self) { - self.exit_sender.clone().store(true, Ordering::Relaxed); - } -} - -pub struct ReplicateStage { - t_responder: JoinHandle<()>, - t_replicate: JoinHandle>, -} - -impl ReplicateStage { - /// Process entry blobs, already in order - fn replicate_requests( - bank: &Arc, - cluster_info: &Arc>, - window_receiver: &EntryReceiver, - keypair: &Arc, - vote_account_keypair: &Arc, - vote_blob_sender: Option<&BlobSender>, - ledger_entry_sender: &EntrySender, - entry_height: &mut u64, - last_entry_id: &mut Hash, - ) -> Result<()> { - let timer = Duration::new(1, 0); - //coalesce all the available entries into a single vote - let mut entries = window_receiver.recv_timeout(timer)?; - while let Ok(mut more) = window_receiver.try_recv() { - entries.append(&mut more); - } - - submit( - influxdb::Point::new("replicate-stage") - .add_field("count", influxdb::Value::Integer(entries.len() as i64)) - .to_owned(), - ); - - let mut res = Ok(()); - let mut num_entries_to_write = entries.len(); - let now = Instant::now(); - if !entries.as_slice().verify(last_entry_id) { - inc_new_counter_info!("replicate_stage-verify-fail", entries.len()); - return Err(Error::BlobError(BlobError::VerificationFailed)); - } - inc_new_counter_info!( - "replicate_stage-verify-duration", - duration_as_ms(&now.elapsed()) as usize - ); - let (current_leader, _) = bank - .get_current_leader() - .expect("Scheduled leader id should never be unknown while processing entries"); - for (i, entry) in entries.iter().enumerate() { - res = bank.process_entry(&entry); - let my_id = keypair.pubkey(); - let (scheduled_leader, _) = bank - .get_current_leader() - .expect("Scheduled leader id should never be unknown while processing entries"); - - // TODO: Remove this soon once we boot the leader from ClusterInfo - if scheduled_leader != current_leader { - cluster_info.write().unwrap().set_leader(scheduled_leader); - } - if my_id == scheduled_leader { - num_entries_to_write = i + 1; - break; - } - - if res.is_err() { - // TODO: This will return early from the first entry that has an erroneous - // transaction, instead of processing the rest of the entries in the vector - // of received entries. This is in line with previous behavior when - // bank.process_entries() was used to process the entries, but doesn't solve the - // issue that the bank state was still changed, leading to inconsistencies with the - // leader as the leader currently should not be publishing erroneous transactions - break; - } - } - - // If leader rotation happened, only write the entries up to leader rotation. - entries.truncate(num_entries_to_write); - *last_entry_id = entries - .last() - .expect("Entries cannot be empty at this point") - .id; - - inc_new_counter_info!( - "replicate-transactions", - entries.iter().map(|x| x.transactions.len()).sum() - ); - - let entries_len = entries.len() as u64; - // TODO: move this to another stage? - // TODO: In line with previous behavior, this will write all the entries even if - // an error occurred processing one of the entries (causing the rest of the entries to - // not be processed). - if entries_len != 0 { - ledger_entry_sender.send(entries)?; - } - - *entry_height += entries_len; - res?; - if let Some(sender) = vote_blob_sender { - send_validator_vote(bank, vote_account_keypair, &cluster_info, sender)?; - } - - Ok(()) - } - - pub fn new( - keypair: Arc, - vote_account_keypair: Arc, - bank: Arc, - cluster_info: Arc>, - window_receiver: EntryReceiver, - exit: Arc, - entry_height: u64, - last_entry_id: Hash, - ) -> (Self, EntryReceiver) { - let (vote_blob_sender, vote_blob_receiver) = channel(); - let (ledger_entry_sender, ledger_entry_receiver) = channel(); - let send = UdpSocket::bind("0.0.0.0:0").expect("bind"); - let t_responder = responder("replicate_stage", Arc::new(send), vote_blob_receiver); - - let keypair = Arc::new(keypair); - - let t_replicate = Builder::new() - .name("solana-replicate-stage".to_string()) - .spawn(move || { - let _exit = Finalizer::new(exit); - let now = Instant::now(); - let mut next_vote_secs = 1; - let mut entry_height_ = entry_height; - let mut last_entry_id = last_entry_id; - loop { - let (leader_id, _) = bank - .get_current_leader() - .expect("Scheduled leader id should never be unknown at this point"); - - if leader_id == keypair.pubkey() { - return Some(ReplicateStageReturnType::LeaderRotation( - bank.tick_height(), - entry_height_, - // We should never start the TPU / this stage on an exact entry that causes leader - // rotation (Fullnode should automatically transition on startup if it detects - // are no longer a validator. Hence we can assume that some entry must have - // triggered leader rotation - last_entry_id, - )); - } - - // Only vote once a second. - let vote_sender = if now.elapsed().as_secs() > next_vote_secs { - next_vote_secs += 1; - Some(&vote_blob_sender) - } else { - None - }; - - match Self::replicate_requests( - &bank, - &cluster_info, - &window_receiver, - &keypair, - &vote_account_keypair, - vote_sender, - &ledger_entry_sender, - &mut entry_height_, - &mut last_entry_id, - ) { - Err(Error::RecvTimeoutError(RecvTimeoutError::Disconnected)) => break, - Err(Error::RecvTimeoutError(RecvTimeoutError::Timeout)) => (), - Err(e) => error!("{:?}", e), - Ok(()) => (), - } - } - - None - }).unwrap(); - - ( - ReplicateStage { - t_responder, - t_replicate, - }, - ledger_entry_receiver, - ) - } -} - -impl Service for ReplicateStage { - type JoinReturnType = Option; - - fn join(self) -> thread::Result> { - self.t_responder.join()?; - self.t_replicate.join() - } -} - -#[cfg(test)] -mod test { - use bank::Bank; - use cluster_info::{ClusterInfo, Node}; - use entry::Entry; - use fullnode::Fullnode; - use leader_scheduler::{make_active_set_entries, LeaderScheduler, LeaderSchedulerConfig}; - use ledger::{create_ticks, create_tmp_sample_ledger, LedgerWriter}; - use logger; - use packet::BlobError; - use replicate_stage::{ReplicateStage, ReplicateStageReturnType}; - use result::Error; - use service::Service; - use signature::{Keypair, KeypairUtil}; - use solana_sdk::hash::Hash; - use std::fs::remove_dir_all; - use std::sync::atomic::{AtomicBool, Ordering}; - use std::sync::mpsc::channel; - use std::sync::{Arc, RwLock}; - use vote_stage::{send_validator_vote, VoteError}; - - #[test] - pub fn test_replicate_stage_leader_rotation_exit() { - logger::setup(); - - // Set up dummy node to host a ReplicateStage - let my_keypair = Keypair::new(); - let my_id = my_keypair.pubkey(); - let my_node = Node::new_localhost_with_pubkey(my_id); - let cluster_info_me = ClusterInfo::new(my_node.info.clone()); - - // Create keypair for the old leader - let old_leader_id = Keypair::new().pubkey(); - - // Create a ledger - let num_ending_ticks = 1; - let (mint, my_ledger_path, genesis_entries) = create_tmp_sample_ledger( - "test_replicate_stage_leader_rotation_exit", - 10_000, - num_ending_ticks, - old_leader_id, - 500, - ); - let mut last_id = genesis_entries - .last() - .expect("expected at least one genesis entry") - .id; - - // Write two entries to the ledger so that the validator is in the active set: - // 1) Give the validator a nonzero number of tokens 2) A vote from the validator . - // This will cause leader rotation after the bootstrap height - let mut ledger_writer = LedgerWriter::open(&my_ledger_path, false).unwrap(); - let (active_set_entries, vote_account_keypair) = - make_active_set_entries(&my_keypair, &mint.keypair(), &last_id, &last_id, 0); - last_id = active_set_entries.last().unwrap().id; - let initial_tick_height = genesis_entries - .iter() - .skip(2) - .fold(0, |tick_count, entry| tick_count + entry.is_tick() as u64); - let active_set_entries_len = active_set_entries.len() as u64; - let initial_non_tick_height = genesis_entries.len() as u64 - initial_tick_height; - let initial_entry_len = genesis_entries.len() as u64 + active_set_entries_len; - ledger_writer.write_entries(&active_set_entries).unwrap(); - - // Set up the LeaderScheduler so that this this node becomes the leader at - // bootstrap_height = num_bootstrap_slots * leader_rotation_interval - let leader_rotation_interval = 10; - let num_bootstrap_slots = 2; - let bootstrap_height = num_bootstrap_slots * leader_rotation_interval; - let leader_scheduler_config = LeaderSchedulerConfig::new( - Some(bootstrap_height), - Some(leader_rotation_interval), - Some(leader_rotation_interval * 2), - Some(bootstrap_height), - ); - - let leader_scheduler = - Arc::new(RwLock::new(LeaderScheduler::new(&leader_scheduler_config))); - - // Set up the bank - let (bank, _, last_entry_id) = - Fullnode::new_bank_from_ledger(&my_ledger_path, leader_scheduler); - - // Set up the replicate stage - let (entry_sender, entry_receiver) = channel(); - let exit = Arc::new(AtomicBool::new(false)); - let (replicate_stage, ledger_writer_recv) = ReplicateStage::new( - Arc::new(my_keypair), - Arc::new(vote_account_keypair), - Arc::new(bank), - Arc::new(RwLock::new(cluster_info_me)), - entry_receiver, - exit.clone(), - initial_entry_len, - last_entry_id, - ); - - // Send enough ticks to trigger leader rotation - let extra_entries = leader_rotation_interval; - let total_entries_to_send = (bootstrap_height + extra_entries) as usize; - let num_hashes = 1; - let mut entries_to_send = vec![]; - - while entries_to_send.len() < total_entries_to_send { - let entry = Entry::new(&mut last_id, num_hashes, vec![]); - last_id = entry.id; - entries_to_send.push(entry); - } - - assert!((num_ending_ticks as u64) < bootstrap_height); - - // Add on the only entries that weren't ticks to the bootstrap height to get the - // total expected entry length - let leader_rotation_index = (bootstrap_height - initial_tick_height - 1) as usize; - let expected_entry_height = - bootstrap_height + initial_non_tick_height + active_set_entries_len; - let expected_last_id = entries_to_send[leader_rotation_index].id; - entry_sender.send(entries_to_send.clone()).unwrap(); - - // Wait for replicate_stage to exit and check return value is correct - assert_eq!( - Some(ReplicateStageReturnType::LeaderRotation( - bootstrap_height, - expected_entry_height, - expected_last_id, - )), - replicate_stage.join().expect("replicate stage join") - ); - - // Check that the entries on the ledger writer channel are correct - let received_ticks = ledger_writer_recv - .recv() - .expect("Expected to recieve an entry on the ledger writer receiver"); - - assert_eq!( - &received_ticks[..], - &entries_to_send[..leader_rotation_index + 1] - ); - - assert_eq!(exit.load(Ordering::Relaxed), true); - - let _ignored = remove_dir_all(&my_ledger_path); - } - - #[test] - fn test_vote_error_replicate_stage_correctness() { - // Set up dummy node to host a ReplicateStage - let my_keypair = Keypair::new(); - let my_id = my_keypair.pubkey(); - let my_node = Node::new_localhost_with_pubkey(my_id); - - // Create keypair for the leader - let leader_id = Keypair::new().pubkey(); - let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::default())); - - let num_ending_ticks = 0; - let (_, my_ledger_path, genesis_entries) = create_tmp_sample_ledger( - "test_vote_error_replicate_stage_correctness", - 10_000, - num_ending_ticks, - leader_id, - 500, - ); - - let initial_entry_len = genesis_entries.len(); - - // Set up the bank - let (bank, _, last_entry_id) = - Fullnode::new_bank_from_ledger(&my_ledger_path, leader_scheduler); - - // Set up the cluster info - let cluster_info_me = Arc::new(RwLock::new(ClusterInfo::new(my_node.info.clone()))); - - // Set up the replicate stage - let vote_account_keypair = Arc::new(Keypair::new()); - let bank = Arc::new(bank); - let (entry_sender, entry_receiver) = channel(); - let exit = Arc::new(AtomicBool::new(false)); - let (replicate_stage, ledger_writer_recv) = ReplicateStage::new( - Arc::new(my_keypair), - vote_account_keypair.clone(), - bank.clone(), - cluster_info_me.clone(), - entry_receiver, - exit.clone(), - initial_entry_len as u64, - last_entry_id, - ); - - // Vote sender should error because no leader contact info is found in the - // ClusterInfo - let (mock_sender, _mock_receiver) = channel(); - let vote_err = - send_validator_vote(&bank, &vote_account_keypair, &cluster_info_me, &mock_sender); - if let Err(Error::VoteError(vote_error)) = vote_err { - assert_eq!(vote_error, VoteError::LeaderInfoNotFound); - } else { - panic!("Expected validator vote to fail with LeaderInfoNotFound"); - } - - // Send ReplicateStage an entry, should see it on the ledger writer receiver - let next_tick = create_ticks( - 1, - genesis_entries - .last() - .expect("Expected nonzero number of entries in genesis") - .id, - ); - entry_sender - .send(next_tick.clone()) - .expect("Error sending entry to ReplicateStage"); - let received_tick = ledger_writer_recv - .recv() - .expect("Expected to recieve an entry on the ledger writer receiver"); - - assert_eq!(next_tick, received_tick); - drop(entry_sender); - replicate_stage - .join() - .expect("Expect successful ReplicateStage exit"); - let _ignored = remove_dir_all(&my_ledger_path); - } - - #[test] - fn test_vote_error_replicate_stage_leader_rotation() { - // Set up dummy node to host a ReplicateStage - let my_keypair = Keypair::new(); - let my_id = my_keypair.pubkey(); - let my_node = Node::new_localhost_with_pubkey(my_id); - - // Create keypair for the leader - let leader_id = Keypair::new().pubkey(); - - // Create the ledger - let (mint, my_ledger_path, genesis_entries) = create_tmp_sample_ledger( - "test_vote_error_replicate_stage_leader_rotation", - 10_000, - 0, - leader_id, - 500, - ); - - let mut last_id = genesis_entries - .last() - .expect("expected at least one genesis entry") - .id; - - // Write two entries to the ledger so that the validator is in the active set: - // 1) Give the validator a nonzero number of tokens 2) A vote from the validator. - // This will cause leader rotation after the bootstrap height - let mut ledger_writer = LedgerWriter::open(&my_ledger_path, false).unwrap(); - let (active_set_entries, vote_account_keypair) = - make_active_set_entries(&my_keypair, &mint.keypair(), &last_id, &last_id, 0); - last_id = active_set_entries.last().unwrap().id; - let initial_tick_height = genesis_entries - .iter() - .skip(2) - .fold(0, |tick_count, entry| tick_count + entry.is_tick() as u64); - let active_set_entries_len = active_set_entries.len() as u64; - let initial_non_tick_height = genesis_entries.len() as u64 - initial_tick_height; - let initial_entry_len = genesis_entries.len() as u64 + active_set_entries_len; - ledger_writer.write_entries(&active_set_entries).unwrap(); - - // Set up the LeaderScheduler so that this this node becomes the leader at - // bootstrap_height = num_bootstrap_slots * leader_rotation_interval - let leader_rotation_interval = 10; - let num_bootstrap_slots = 2; - let bootstrap_height = num_bootstrap_slots * leader_rotation_interval; - let leader_scheduler_config = LeaderSchedulerConfig::new( - Some(bootstrap_height), - Some(leader_rotation_interval), - Some(leader_rotation_interval * 2), - Some(bootstrap_height), - ); - - let leader_scheduler = - Arc::new(RwLock::new(LeaderScheduler::new(&leader_scheduler_config))); - - // Set up the bank - let (bank, _, last_entry_id) = - Fullnode::new_bank_from_ledger(&my_ledger_path, leader_scheduler); - - // Set up the cluster info - let cluster_info_me = Arc::new(RwLock::new(ClusterInfo::new(my_node.info.clone()))); - - // Set up the replicate stage - let vote_account_keypair = Arc::new(vote_account_keypair); - let bank = Arc::new(bank); - let (entry_sender, entry_receiver) = channel(); - let exit = Arc::new(AtomicBool::new(false)); - let (replicate_stage, ledger_writer_recv) = ReplicateStage::new( - Arc::new(my_keypair), - vote_account_keypair.clone(), - bank.clone(), - cluster_info_me.clone(), - entry_receiver, - exit.clone(), - initial_entry_len as u64, - last_entry_id, - ); - - // Vote sender should error because no leader contact info is found in the - // ClusterInfo - let (mock_sender, _mock_receiver) = channel(); - let vote_err = - send_validator_vote(&bank, &vote_account_keypair, &cluster_info_me, &mock_sender); - if let Err(Error::VoteError(vote_error)) = vote_err { - assert_eq!(vote_error, VoteError::LeaderInfoNotFound); - } else { - panic!("Expected validator vote to fail with LeaderInfoNotFound"); - } - - // Send enough ticks to trigger leader rotation - let total_entries_to_send = (bootstrap_height - initial_tick_height) as usize; - let num_hashes = 1; - - // Add on the only entries that weren't ticks to the bootstrap height to get the - // total expected entry length - let expected_entry_height = - bootstrap_height + initial_non_tick_height + active_set_entries_len; - let leader_rotation_index = (bootstrap_height - initial_tick_height - 1) as usize; - let mut expected_last_id = Hash::default(); - for i in 0..total_entries_to_send { - let entry = Entry::new(&mut last_id, num_hashes, vec![]); - last_id = entry.id; - entry_sender - .send(vec![entry.clone()]) - .expect("Expected to be able to send entry to ReplicateStage"); - // Check that the entries on the ledger writer channel are correct - let received_entry = ledger_writer_recv - .recv() - .expect("Expected to recieve an entry on the ledger writer receiver"); - assert_eq!(received_entry[0], entry); - - if i == leader_rotation_index { - expected_last_id = entry.id; - } - } - - assert_ne!(expected_last_id, Hash::default()); - - // Wait for replicate_stage to exit and check return value is correct - assert_eq!( - Some(ReplicateStageReturnType::LeaderRotation( - bootstrap_height, - expected_entry_height, - expected_last_id, - )), - replicate_stage.join().expect("replicate stage join") - ); - - assert_eq!(exit.load(Ordering::Relaxed), true); - let _ignored = remove_dir_all(&my_ledger_path); - } - - #[test] - fn test_replicate_stage_poh_error_entry_receiver() { - // Set up dummy node to host a ReplicateStage - let my_keypair = Keypair::new(); - let my_id = my_keypair.pubkey(); - let vote_keypair = Keypair::new(); - let my_node = Node::new_localhost_with_pubkey(my_id); - // Set up the cluster info - let cluster_info_me = Arc::new(RwLock::new(ClusterInfo::new(my_node.info.clone()))); - let (entry_sender, entry_receiver) = channel(); - let (ledger_entry_sender, _ledger_entry_receiver) = channel(); - let mut last_entry_id = Hash::default(); - // Create keypair for the old leader - let old_leader_id = Keypair::new().pubkey(); - - let (_, my_ledger_path, _) = create_tmp_sample_ledger( - "test_replicate_stage_leader_rotation_exit", - 10_000, - 0, - old_leader_id, - 500, - ); - - let mut entry_height = 0; - let mut last_id = Hash::default(); - let mut entries = Vec::new(); - for _ in 0..5 { - let entry = Entry::new(&mut last_id, 1, vec![]); //just ticks - last_id = entry.id; - entries.push(entry); - } - entry_sender - .send(entries.clone()) - .expect("Expected to err out"); - - let res = ReplicateStage::replicate_requests( - &Arc::new(Bank::default()), - &cluster_info_me, - &entry_receiver, - &Arc::new(my_keypair), - &Arc::new(vote_keypair), - None, - &ledger_entry_sender, - &mut entry_height, - &mut last_entry_id, - ); - - match res { - Ok(_) => (), - Err(e) => assert!(false, "Entries were not sent correctly {:?}", e), - } - - entries.clear(); - for _ in 0..5 { - let entry = Entry::new(&mut Hash::default(), 0, vec![]); //just broken entries - entries.push(entry); - } - entry_sender - .send(entries.clone()) - .expect("Expected to err out"); - - let res = ReplicateStage::replicate_requests( - &Arc::new(Bank::default()), - &cluster_info_me, - &entry_receiver, - &Arc::new(Keypair::new()), - &Arc::new(Keypair::new()), - None, - &ledger_entry_sender, - &mut entry_height, - &mut last_entry_id, - ); - - match res { - Ok(_) => assert!(false, "Should have failed because entries are broken"), - Err(Error::BlobError(BlobError::VerificationFailed)) => (), - Err(e) => assert!( - false, - "Should have failed because with blob error, instead, got {:?}", - e - ), - } - - let _ignored = remove_dir_all(&my_ledger_path); - } -} diff --git a/book/replicator.rs b/book/replicator.rs deleted file mode 100644 index c6a8563fb5ea49..00000000000000 --- a/book/replicator.rs +++ /dev/null @@ -1,337 +0,0 @@ -use blob_fetch_stage::BlobFetchStage; -use cluster_info::{ClusterInfo, Node, NodeInfo}; -use leader_scheduler::LeaderScheduler; -use ncp::Ncp; -use service::Service; -use solana_sdk::hash::{Hash, Hasher}; -use std::fs::File; -use std::io; -use std::io::BufReader; -use std::io::Read; -use std::io::Seek; -use std::io::SeekFrom; -use std::io::{Error, ErrorKind}; -use std::mem::size_of; -use std::net::SocketAddr; -use std::net::UdpSocket; -use std::path::Path; -use std::sync::atomic::AtomicBool; -use std::sync::mpsc::channel; -use std::sync::{Arc, RwLock}; -use std::thread::JoinHandle; -use std::time::Duration; -use store_ledger_stage::StoreLedgerStage; -use streamer::BlobReceiver; -use thin_client::poll_gossip_for_leader; -use window; -use window_service::window_service; - -pub struct Replicator { - ncp: Ncp, - fetch_stage: BlobFetchStage, - store_ledger_stage: StoreLedgerStage, - t_window: JoinHandle<()>, - pub retransmit_receiver: BlobReceiver, -} - -pub fn sample_file(in_path: &Path, sample_offsets: &[u64]) -> io::Result { - let in_file = File::open(in_path)?; - let metadata = in_file.metadata()?; - let mut buffer_file = BufReader::new(in_file); - - let mut hasher = Hasher::default(); - let sample_size = size_of::(); - let sample_size64 = sample_size as u64; - let mut buf = vec![0; sample_size]; - - let file_len = metadata.len(); - if file_len < sample_size64 { - return Err(Error::new(ErrorKind::Other, "file too short!")); - } - for offset in sample_offsets { - if *offset > (file_len - sample_size64) / sample_size64 { - return Err(Error::new(ErrorKind::Other, "offset too large")); - } - buffer_file.seek(SeekFrom::Start(*offset * sample_size64))?; - trace!("sampling @ {} ", *offset); - match buffer_file.read(&mut buf) { - Ok(size) => { - assert_eq!(size, buf.len()); - hasher.hash(&buf); - } - Err(e) => { - warn!("Error sampling file"); - return Err(e); - } - } - } - - Ok(hasher.result()) -} - -impl Replicator { - pub fn new( - entry_height: u64, - max_entry_height: u64, - exit: &Arc, - ledger_path: Option<&str>, - node: Node, - network_addr: Option, - done: Arc, - ) -> (Replicator, NodeInfo) { - const REPLICATOR_WINDOW_SIZE: usize = 32 * 1024; - let window = window::new_window(REPLICATOR_WINDOW_SIZE); - let shared_window = Arc::new(RwLock::new(window)); - - let cluster_info = Arc::new(RwLock::new(ClusterInfo::new(node.info))); - - let leader_info = network_addr.map(|i| NodeInfo::new_entry_point(&i)); - let leader_pubkey; - if let Some(leader_info) = leader_info { - leader_pubkey = leader_info.id; - cluster_info.write().unwrap().insert_info(leader_info); - } else { - panic!("No leader info!"); - } - - let repair_socket = Arc::new(node.sockets.repair); - let mut blob_sockets: Vec> = - node.sockets.replicate.into_iter().map(Arc::new).collect(); - blob_sockets.push(repair_socket.clone()); - let (fetch_stage, blob_fetch_receiver) = - BlobFetchStage::new_multi_socket(blob_sockets, exit.clone()); - - let (entry_window_sender, entry_window_receiver) = channel(); - // todo: pull blobs off the retransmit_receiver and recycle them? - let (retransmit_sender, retransmit_receiver) = channel(); - let t_window = window_service( - cluster_info.clone(), - shared_window.clone(), - 0, - entry_height, - max_entry_height, - blob_fetch_receiver, - entry_window_sender, - retransmit_sender, - repair_socket, - Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( - leader_pubkey, - ))), - done, - ); - - let store_ledger_stage = StoreLedgerStage::new(entry_window_receiver, ledger_path); - - let ncp = Ncp::new( - &cluster_info, - shared_window.clone(), - ledger_path, - node.sockets.gossip, - exit.clone(), - ); - - let leader = - poll_gossip_for_leader(network_addr.unwrap(), Some(10)).expect("couldn't reach leader"); - - ( - Replicator { - ncp, - fetch_stage, - store_ledger_stage, - t_window, - retransmit_receiver, - }, - leader, - ) - } - - pub fn join(self) { - self.ncp.join().unwrap(); - self.fetch_stage.join().unwrap(); - self.t_window.join().unwrap(); - self.store_ledger_stage.join().unwrap(); - - // Drain the queue here to prevent self.retransmit_receiver from being dropped - // before the window_service thread is joined - let mut retransmit_queue_count = 0; - while let Ok(_blob) = self.retransmit_receiver.recv_timeout(Duration::new(1, 0)) { - retransmit_queue_count += 1; - } - debug!("retransmit channel count: {}", retransmit_queue_count); - } -} - -#[cfg(test)] -mod tests { - use client::mk_client; - use cluster_info::Node; - use fullnode::Fullnode; - use leader_scheduler::LeaderScheduler; - use ledger::{create_tmp_genesis, get_tmp_ledger_path, read_ledger}; - use logger; - use replicator::sample_file; - use replicator::Replicator; - use signature::{Keypair, KeypairUtil}; - use solana_sdk::hash::Hash; - use std::fs::File; - use std::fs::{create_dir_all, remove_dir_all, remove_file}; - use std::io::Write; - use std::mem::size_of; - use std::path::PathBuf; - use std::sync::atomic::{AtomicBool, Ordering}; - use std::sync::Arc; - use std::thread::sleep; - use std::time::Duration; - - #[test] - fn test_replicator_startup() { - logger::setup(); - info!("starting replicator test"); - let entry_height = 0; - let replicator_ledger_path = &get_tmp_ledger_path("replicator_test_replicator_ledger"); - - let exit = Arc::new(AtomicBool::new(false)); - let done = Arc::new(AtomicBool::new(false)); - - info!("starting leader node"); - let leader_keypair = Arc::new(Keypair::new()); - let leader_node = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - let network_addr = leader_node.sockets.gossip.local_addr().unwrap(); - let leader_info = leader_node.info.clone(); - let vote_account_keypair = Arc::new(Keypair::new()); - - let leader_ledger_path = "replicator_test_leader_ledger"; - let (mint, leader_ledger_path) = - create_tmp_genesis(leader_ledger_path, 100, leader_info.id, 1); - - let leader = Fullnode::new( - leader_node, - &leader_ledger_path, - leader_keypair, - vote_account_keypair, - None, - false, - LeaderScheduler::from_bootstrap_leader(leader_info.id), - None, - ); - - let mut leader_client = mk_client(&leader_info); - - let bob = Keypair::new(); - - let last_id = leader_client.get_last_id(); - leader_client - .transfer(1, &mint.keypair(), bob.pubkey(), &last_id) - .unwrap(); - - let replicator_keypair = Keypair::new(); - - info!("starting replicator node"); - let replicator_node = Node::new_localhost_with_pubkey(replicator_keypair.pubkey()); - let (replicator, _leader_info) = Replicator::new( - entry_height, - 1, - &exit, - Some(replicator_ledger_path), - replicator_node, - Some(network_addr), - done.clone(), - ); - - let mut num_entries = 0; - for _ in 0..60 { - match read_ledger(replicator_ledger_path, true) { - Ok(entries) => { - for _ in entries { - num_entries += 1; - } - info!("{} entries", num_entries); - if num_entries > 0 { - break; - } - } - Err(e) => { - info!("error reading ledger: {:?}", e); - } - } - sleep(Duration::from_millis(300)); - let last_id = leader_client.get_last_id(); - leader_client - .transfer(1, &mint.keypair(), bob.pubkey(), &last_id) - .unwrap(); - } - assert_eq!(done.load(Ordering::Relaxed), true); - assert!(num_entries > 0); - exit.store(true, Ordering::Relaxed); - replicator.join(); - leader.exit(); - let _ignored = remove_dir_all(&leader_ledger_path); - let _ignored = remove_dir_all(&replicator_ledger_path); - } - - fn tmp_file_path(name: &str) -> PathBuf { - use std::env; - let out_dir = env::var("OUT_DIR").unwrap_or_else(|_| "target".to_string()); - let keypair = Keypair::new(); - - let mut path = PathBuf::new(); - path.push(out_dir); - path.push("tmp"); - create_dir_all(&path).unwrap(); - - path.push(format!("{}-{}", name, keypair.pubkey())); - path - } - - #[test] - fn test_sample_file() { - logger::setup(); - let in_path = tmp_file_path("test_sample_file_input.txt"); - let num_strings = 4096; - let string = "12foobar"; - { - let mut in_file = File::create(&in_path).unwrap(); - for _ in 0..num_strings { - in_file.write(string.as_bytes()).unwrap(); - } - } - let num_samples = (string.len() * num_strings / size_of::()) as u64; - let samples: Vec<_> = (0..num_samples).collect(); - let res = sample_file(&in_path, samples.as_slice()); - assert!(res.is_ok()); - let ref_hash: Hash = Hash::new(&[ - 173, 251, 182, 165, 10, 54, 33, 150, 133, 226, 106, 150, 99, 192, 179, 1, 230, 144, - 151, 126, 18, 191, 54, 67, 249, 140, 230, 160, 56, 30, 170, 52, - ]); - let res = res.unwrap(); - assert_eq!(res, ref_hash); - - // Sample just past the end - assert!(sample_file(&in_path, &[num_samples]).is_err()); - remove_file(&in_path).unwrap(); - } - - #[test] - fn test_sample_file_invalid_offset() { - let in_path = tmp_file_path("test_sample_file_invalid_offset_input.txt"); - { - let mut in_file = File::create(&in_path).unwrap(); - for _ in 0..4096 { - in_file.write("123456foobar".as_bytes()).unwrap(); - } - } - let samples = [0, 200000]; - let res = sample_file(&in_path, &samples); - assert!(res.is_err()); - remove_file(in_path).unwrap(); - } - - #[test] - fn test_sample_file_missing_file() { - let in_path = tmp_file_path("test_sample_file_that_doesnt_exist.txt"); - let samples = [0, 5]; - let res = sample_file(&in_path, &samples); - assert!(res.is_err()); - } - -} diff --git a/book/result.rs b/book/result.rs deleted file mode 100644 index 7d17f114e519af..00000000000000 --- a/book/result.rs +++ /dev/null @@ -1,187 +0,0 @@ -//! The `result` module exposes a Result type that propagates one of many different Error types. - -use bank; -use bincode; -use cluster_info; -use db_ledger; -#[cfg(feature = "erasure")] -use erasure; -use packet; -use poh_recorder; -use rocksdb; -use serde_json; -use std; -use std::any::Any; -use vote_stage; - -#[derive(Debug)] -pub enum Error { - IO(std::io::Error), - JSON(serde_json::Error), - AddrParse(std::net::AddrParseError), - JoinError(Box), - RecvError(std::sync::mpsc::RecvError), - RecvTimeoutError(std::sync::mpsc::RecvTimeoutError), - Serialize(std::boxed::Box), - BankError(bank::BankError), - ClusterInfoError(cluster_info::ClusterInfoError), - BlobError(packet::BlobError), - #[cfg(feature = "erasure")] - ErasureError(erasure::ErasureError), - SendError, - PohRecorderError(poh_recorder::PohRecorderError), - VoteError(vote_stage::VoteError), - RocksDb(rocksdb::Error), - DbLedgerError(db_ledger::DbLedgerError), -} - -pub type Result = std::result::Result; - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "solana error") - } -} - -impl std::error::Error for Error {} - -impl std::convert::From for Error { - fn from(e: std::sync::mpsc::RecvError) -> Error { - Error::RecvError(e) - } -} -impl std::convert::From for Error { - fn from(e: std::sync::mpsc::RecvTimeoutError) -> Error { - Error::RecvTimeoutError(e) - } -} -impl std::convert::From for Error { - fn from(e: bank::BankError) -> Error { - Error::BankError(e) - } -} -impl std::convert::From for Error { - fn from(e: cluster_info::ClusterInfoError) -> Error { - Error::ClusterInfoError(e) - } -} -#[cfg(feature = "erasure")] -impl std::convert::From for Error { - fn from(e: erasure::ErasureError) -> Error { - Error::ErasureError(e) - } -} -impl std::convert::From> for Error { - fn from(_e: std::sync::mpsc::SendError) -> Error { - Error::SendError - } -} -impl std::convert::From> for Error { - fn from(e: Box) -> Error { - Error::JoinError(e) - } -} -impl std::convert::From for Error { - fn from(e: std::io::Error) -> Error { - Error::IO(e) - } -} -impl std::convert::From for Error { - fn from(e: serde_json::Error) -> Error { - Error::JSON(e) - } -} -impl std::convert::From for Error { - fn from(e: std::net::AddrParseError) -> Error { - Error::AddrParse(e) - } -} -impl std::convert::From> for Error { - fn from(e: std::boxed::Box) -> Error { - Error::Serialize(e) - } -} -impl std::convert::From for Error { - fn from(e: poh_recorder::PohRecorderError) -> Error { - Error::PohRecorderError(e) - } -} -impl std::convert::From for Error { - fn from(e: vote_stage::VoteError) -> Error { - Error::VoteError(e) - } -} -impl std::convert::From for Error { - fn from(e: rocksdb::Error) -> Error { - Error::RocksDb(e) - } -} -impl std::convert::From for Error { - fn from(e: db_ledger::DbLedgerError) -> Error { - Error::DbLedgerError(e) - } -} - -#[cfg(test)] -mod tests { - use result::Error; - use result::Result; - use serde_json; - use std::io; - use std::io::Write; - use std::net::SocketAddr; - use std::panic; - use std::sync::mpsc::channel; - use std::sync::mpsc::RecvError; - use std::sync::mpsc::RecvTimeoutError; - use std::thread; - - fn addr_parse_error() -> Result { - let r = "12fdfasfsafsadfs".parse()?; - Ok(r) - } - - fn join_error() -> Result<()> { - panic::set_hook(Box::new(|_info| {})); - let r = thread::spawn(|| panic!("hi")).join()?; - Ok(r) - } - fn json_error() -> Result<()> { - let r = serde_json::from_slice("=342{;;;;:}".as_bytes())?; - Ok(r) - } - fn send_error() -> Result<()> { - let (s, r) = channel(); - drop(r); - s.send(())?; - Ok(()) - } - - #[test] - fn from_test() { - assert_matches!(addr_parse_error(), Err(Error::AddrParse(_))); - assert_matches!(Error::from(RecvError {}), Error::RecvError(_)); - assert_matches!( - Error::from(RecvTimeoutError::Timeout), - Error::RecvTimeoutError(_) - ); - assert_matches!(send_error(), Err(Error::SendError)); - assert_matches!(join_error(), Err(Error::JoinError(_))); - let ioe = io::Error::new(io::ErrorKind::NotFound, "hi"); - assert_matches!(Error::from(ioe), Error::IO(_)); - } - #[test] - fn fmt_test() { - write!(io::sink(), "{:?}", addr_parse_error()).unwrap(); - write!(io::sink(), "{:?}", Error::from(RecvError {})).unwrap(); - write!(io::sink(), "{:?}", Error::from(RecvTimeoutError::Timeout)).unwrap(); - write!(io::sink(), "{:?}", send_error()).unwrap(); - write!(io::sink(), "{:?}", join_error()).unwrap(); - write!(io::sink(), "{:?}", json_error()).unwrap(); - write!( - io::sink(), - "{:?}", - Error::from(io::Error::new(io::ErrorKind::NotFound, "hi")) - ).unwrap(); - } -} diff --git a/book/retransmit_stage.rs b/book/retransmit_stage.rs deleted file mode 100644 index 9f462336e303fd..00000000000000 --- a/book/retransmit_stage.rs +++ /dev/null @@ -1,127 +0,0 @@ -//! The `retransmit_stage` retransmits blobs between validators - -use cluster_info::ClusterInfo; -use counter::Counter; -use entry::Entry; - -use leader_scheduler::LeaderScheduler; -use log::Level; -use result::{Error, Result}; -use service::Service; -use solana_metrics::{influxdb, submit}; -use std::net::UdpSocket; -use std::sync::atomic::{AtomicBool, AtomicUsize}; -use std::sync::mpsc::RecvTimeoutError; -use std::sync::mpsc::{channel, Receiver}; -use std::sync::{Arc, RwLock}; -use std::thread::{self, Builder, JoinHandle}; -use std::time::Duration; -use streamer::BlobReceiver; -use window::SharedWindow; -use window_service::window_service; - -fn retransmit( - cluster_info: &Arc>, - r: &BlobReceiver, - sock: &UdpSocket, -) -> Result<()> { - let timer = Duration::new(1, 0); - let mut dq = r.recv_timeout(timer)?; - while let Ok(mut nq) = r.try_recv() { - dq.append(&mut nq); - } - - submit( - influxdb::Point::new("retransmit-stage") - .add_field("count", influxdb::Value::Integer(dq.len() as i64)) - .to_owned(), - ); - - for b in &mut dq { - ClusterInfo::retransmit(&cluster_info, b, sock)?; - } - Ok(()) -} - -/// Service to retransmit messages from the leader to layer 1 nodes. -/// See `cluster_info` for network layer definitions. -/// # Arguments -/// * `sock` - Socket to read from. Read timeout is set to 1. -/// * `exit` - Boolean to signal system exit. -/// * `cluster_info` - This structure needs to be updated and populated by the bank and via gossip. -/// * `recycler` - Blob recycler. -/// * `r` - Receive channel for blobs to be retransmitted to all the layer 1 nodes. -fn retransmitter( - sock: Arc, - cluster_info: Arc>, - r: BlobReceiver, -) -> JoinHandle<()> { - Builder::new() - .name("solana-retransmitter".to_string()) - .spawn(move || { - trace!("retransmitter started"); - loop { - if let Err(e) = retransmit(&cluster_info, &r, &sock) { - match e { - Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => break, - Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), - _ => { - inc_new_counter_info!("streamer-retransmit-error", 1, 1); - } - } - } - } - trace!("exiting retransmitter"); - }).unwrap() -} - -pub struct RetransmitStage { - thread_hdls: Vec>, -} - -impl RetransmitStage { - pub fn new( - cluster_info: &Arc>, - window: SharedWindow, - tick_height: u64, - entry_height: u64, - retransmit_socket: Arc, - repair_socket: Arc, - fetch_stage_receiver: BlobReceiver, - leader_scheduler: Arc>, - ) -> (Self, Receiver>) { - let (retransmit_sender, retransmit_receiver) = channel(); - - let t_retransmit = - retransmitter(retransmit_socket, cluster_info.clone(), retransmit_receiver); - let (entry_sender, entry_receiver) = channel(); - let done = Arc::new(AtomicBool::new(false)); - let t_window = window_service( - cluster_info.clone(), - window, - tick_height, - entry_height, - 0, - fetch_stage_receiver, - entry_sender, - retransmit_sender, - repair_socket, - leader_scheduler, - done, - ); - - let thread_hdls = vec![t_retransmit, t_window]; - (RetransmitStage { thread_hdls }, entry_receiver) - } -} - -impl Service for RetransmitStage { - type JoinReturnType = (); - - fn join(self) -> thread::Result<()> { - for thread_hdl in self.thread_hdls { - thread_hdl.join()?; - } - Ok(()) - } -} diff --git a/book/rpc.rs b/book/rpc.rs deleted file mode 100644 index b861a3d492f409..00000000000000 --- a/book/rpc.rs +++ /dev/null @@ -1,771 +0,0 @@ -//! The `rpc` module implements the Solana RPC interface. - -use bank::{Bank, BankError}; -use bincode::{deserialize, serialize}; -use bs58; -use cluster_info::ClusterInfo; -use jsonrpc_core::*; -use jsonrpc_http_server::*; -use packet::PACKET_DATA_SIZE; -use service::Service; -use signature::Signature; -use solana_drone::drone::{request_airdrop_transaction, DRONE_PORT}; -use solana_sdk::account::Account; -use solana_sdk::pubkey::Pubkey; -use std::mem; -use std::net::{SocketAddr, UdpSocket}; -use std::result; -use std::str::FromStr; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, RwLock}; -use std::thread::{self, sleep, Builder, JoinHandle}; -use std::time::Duration; -use std::time::Instant; -use transaction::Transaction; - -pub const RPC_PORT: u16 = 8899; - -pub struct JsonRpcService { - thread_hdl: JoinHandle<()>, - exit: Arc, -} - -impl JsonRpcService { - pub fn new( - bank: &Arc, - cluster_info: &Arc>, - rpc_addr: SocketAddr, - ) -> Self { - let exit = Arc::new(AtomicBool::new(false)); - let request_processor = JsonRpcRequestProcessor::new(bank.clone()); - let info = cluster_info.clone(); - let exit_pubsub = exit.clone(); - let exit_ = exit.clone(); - let thread_hdl = Builder::new() - .name("solana-jsonrpc".to_string()) - .spawn(move || { - let mut io = MetaIoHandler::default(); - let rpc = RpcSolImpl; - io.extend_with(rpc.to_delegate()); - - let server = - ServerBuilder::with_meta_extractor(io, move |_req: &hyper::Request| Meta { - request_processor: request_processor.clone(), - cluster_info: info.clone(), - rpc_addr, - exit: exit_pubsub.clone(), - }).threads(4) - .cors(DomainsValidation::AllowOnly(vec![ - AccessControlAllowOrigin::Any, - ])) - .start_http(&rpc_addr); - if server.is_err() { - warn!("JSON RPC service unavailable: unable to bind to RPC port {}. \nMake sure this port is not already in use by another application", rpc_addr.port()); - return; - } - while !exit_.load(Ordering::Relaxed) { - sleep(Duration::from_millis(100)); - } - server.unwrap().close(); - () - }) - .unwrap(); - JsonRpcService { thread_hdl, exit } - } - - pub fn exit(&self) { - self.exit.store(true, Ordering::Relaxed); - } - - pub fn close(self) -> thread::Result<()> { - self.exit(); - self.join() - } -} - -impl Service for JsonRpcService { - type JoinReturnType = (); - - fn join(self) -> thread::Result<()> { - self.thread_hdl.join() - } -} - -#[derive(Clone)] -pub struct Meta { - pub request_processor: JsonRpcRequestProcessor, - pub cluster_info: Arc>, - pub rpc_addr: SocketAddr, - pub exit: Arc, -} -impl Metadata for Meta {} - -#[derive(Copy, Clone, PartialEq, Serialize, Debug)] -pub enum RpcSignatureStatus { - AccountInUse, - Confirmed, - GenericFailure, - ProgramRuntimeError, - SignatureNotFound, -} -impl FromStr for RpcSignatureStatus { - type Err = Error; - - fn from_str(s: &str) -> Result { - match s { - "AccountInUse" => Ok(RpcSignatureStatus::AccountInUse), - "Confirmed" => Ok(RpcSignatureStatus::Confirmed), - "GenericFailure" => Ok(RpcSignatureStatus::GenericFailure), - "ProgramRuntimeError" => Ok(RpcSignatureStatus::ProgramRuntimeError), - "SignatureNotFound" => Ok(RpcSignatureStatus::SignatureNotFound), - _ => Err(Error::parse_error()), - } - } -} - -build_rpc_trait! { - pub trait RpcSol { - type Metadata; - - #[rpc(meta, name = "confirmTransaction")] - fn confirm_transaction(&self, Self::Metadata, String) -> Result; - - #[rpc(meta, name = "getAccountInfo")] - fn get_account_info(&self, Self::Metadata, String) -> Result; - - #[rpc(meta, name = "getBalance")] - fn get_balance(&self, Self::Metadata, String) -> Result; - - #[rpc(meta, name = "getFinality")] - fn get_finality(&self, Self::Metadata) -> Result; - - #[rpc(meta, name = "getLastId")] - fn get_last_id(&self, Self::Metadata) -> Result; - - #[rpc(meta, name = "getSignatureStatus")] - fn get_signature_status(&self, Self::Metadata, String) -> Result; - - #[rpc(meta, name = "getTransactionCount")] - fn get_transaction_count(&self, Self::Metadata) -> Result; - - #[rpc(meta, name= "requestAirdrop")] - fn request_airdrop(&self, Self::Metadata, String, u64) -> Result; - - #[rpc(meta, name = "sendTransaction")] - fn send_transaction(&self, Self::Metadata, Vec) -> Result; - } -} - -pub struct RpcSolImpl; -impl RpcSol for RpcSolImpl { - type Metadata = Meta; - - fn confirm_transaction(&self, meta: Self::Metadata, id: String) -> Result { - info!("confirm_transaction rpc request received: {:?}", id); - self.get_signature_status(meta, id) - .map(|status| status == RpcSignatureStatus::Confirmed) - } - - fn get_account_info(&self, meta: Self::Metadata, id: String) -> Result { - info!("get_account_info rpc request received: {:?}", id); - let pubkey = verify_pubkey(id)?; - meta.request_processor.get_account_info(pubkey) - } - fn get_balance(&self, meta: Self::Metadata, id: String) -> Result { - info!("get_balance rpc request received: {:?}", id); - let pubkey = verify_pubkey(id)?; - meta.request_processor.get_balance(pubkey) - } - fn get_finality(&self, meta: Self::Metadata) -> Result { - info!("get_finality rpc request received"); - meta.request_processor.get_finality() - } - fn get_last_id(&self, meta: Self::Metadata) -> Result { - info!("get_last_id rpc request received"); - meta.request_processor.get_last_id() - } - fn get_signature_status(&self, meta: Self::Metadata, id: String) -> Result { - info!("get_signature_status rpc request received: {:?}", id); - let signature = verify_signature(&id)?; - Ok( - match meta.request_processor.get_signature_status(signature) { - Ok(_) => RpcSignatureStatus::Confirmed, - Err(BankError::AccountInUse) => RpcSignatureStatus::AccountInUse, - Err(BankError::ProgramRuntimeError(_)) => RpcSignatureStatus::ProgramRuntimeError, - // Report SignatureReserved as SignatureNotFound as SignatureReserved is - // transitory while the bank processes the associated transaction. - Err(BankError::SignatureReserved) => RpcSignatureStatus::SignatureNotFound, - Err(BankError::SignatureNotFound) => RpcSignatureStatus::SignatureNotFound, - Err(err) => { - trace!("mapping {:?} to GenericFailure", err); - RpcSignatureStatus::GenericFailure - } - }, - ) - } - fn get_transaction_count(&self, meta: Self::Metadata) -> Result { - info!("get_transaction_count rpc request received"); - meta.request_processor.get_transaction_count() - } - fn request_airdrop(&self, meta: Self::Metadata, id: String, tokens: u64) -> Result { - trace!("request_airdrop id={} tokens={}", id, tokens); - let pubkey = verify_pubkey(id)?; - - let mut drone_addr = get_leader_addr(&meta.cluster_info)?; - drone_addr.set_port(DRONE_PORT); - let last_id = meta.request_processor.bank.last_id(); - let transaction = request_airdrop_transaction(&drone_addr, &pubkey, tokens, last_id) - .map_err(|err| { - info!("request_airdrop_transaction failed: {:?}", err); - Error::internal_error() - })?;; - - let data = serialize(&transaction).map_err(|err| { - info!("request_airdrop: serialize error: {:?}", err); - Error::internal_error() - })?; - - let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); - let transactions_addr = get_leader_addr(&meta.cluster_info)?; - transactions_socket - .send_to(&data, transactions_addr) - .map_err(|err| { - info!("request_airdrop: send_to error: {:?}", err); - Error::internal_error() - })?; - - let signature = transaction.signatures[0]; - let now = Instant::now(); - let mut signature_status; - loop { - signature_status = meta.request_processor.get_signature_status(signature); - - if signature_status.is_ok() { - info!("airdrop signature ok"); - return Ok(bs58::encode(signature).into_string()); - } else if now.elapsed().as_secs() > 5 { - info!("airdrop signature timeout"); - return Err(Error::internal_error()); - } - sleep(Duration::from_millis(100)); - } - } - fn send_transaction(&self, meta: Self::Metadata, data: Vec) -> Result { - let tx: Transaction = deserialize(&data).map_err(|err| { - info!("send_transaction: deserialize error: {:?}", err); - Error::invalid_request() - })?; - if data.len() >= PACKET_DATA_SIZE { - info!( - "send_transaction: transaction too large: {} bytes (max: {} bytes)", - data.len(), - PACKET_DATA_SIZE - ); - return Err(Error::invalid_request()); - } - let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); - let transactions_addr = get_leader_addr(&meta.cluster_info)?; - transactions_socket - .send_to(&data, transactions_addr) - .map_err(|err| { - info!("send_transaction: send_to error: {:?}", err); - Error::internal_error() - })?; - let signature = bs58::encode(tx.signatures[0]).into_string(); - trace!( - "send_transaction: sent {} bytes, signature={}", - data.len(), - signature - ); - Ok(signature) - } -} -#[derive(Clone)] -pub struct JsonRpcRequestProcessor { - bank: Arc, -} -impl JsonRpcRequestProcessor { - /// Create a new request processor that wraps the given Bank. - pub fn new(bank: Arc) -> Self { - JsonRpcRequestProcessor { bank } - } - - /// Process JSON-RPC request items sent via JSON-RPC. - pub fn get_account_info(&self, pubkey: Pubkey) -> Result { - self.bank - .get_account(&pubkey) - .ok_or_else(Error::invalid_request) - } - fn get_balance(&self, pubkey: Pubkey) -> Result { - let val = self.bank.get_balance(&pubkey); - Ok(val) - } - fn get_finality(&self) -> Result { - Ok(self.bank.finality()) - } - fn get_last_id(&self) -> Result { - let id = self.bank.last_id(); - Ok(bs58::encode(id).into_string()) - } - pub fn get_signature_status(&self, signature: Signature) -> result::Result<(), BankError> { - self.bank.get_signature_status(&signature) - } - fn get_transaction_count(&self) -> Result { - Ok(self.bank.transaction_count() as u64) - } -} - -fn get_leader_addr(cluster_info: &Arc>) -> Result { - if let Some(leader_data) = cluster_info.read().unwrap().leader_data() { - Ok(leader_data.tpu) - } else { - Err(Error { - code: ErrorCode::InternalError, - message: "No leader detected".into(), - data: None, - }) - } -} - -fn verify_pubkey(input: String) -> Result { - let pubkey_vec = bs58::decode(input).into_vec().map_err(|err| { - info!("verify_pubkey: invalid input: {:?}", err); - Error::invalid_request() - })?; - if pubkey_vec.len() != mem::size_of::() { - info!( - "verify_pubkey: invalid pubkey_vec length: {}", - pubkey_vec.len() - ); - Err(Error::invalid_request()) - } else { - Ok(Pubkey::new(&pubkey_vec)) - } -} - -fn verify_signature(input: &str) -> Result { - let signature_vec = bs58::decode(input).into_vec().map_err(|err| { - info!("verify_signature: invalid input: {}: {:?}", input, err); - Error::invalid_request() - })?; - if signature_vec.len() != mem::size_of::() { - info!( - "verify_signature: invalid signature_vec length: {}", - signature_vec.len() - ); - Err(Error::invalid_request()) - } else { - Ok(Signature::new(&signature_vec)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bank::Bank; - use bincode::serialize; - use cluster_info::{Node, NodeInfo}; - use fullnode::Fullnode; - use jsonrpc_core::Response; - use leader_scheduler::LeaderScheduler; - use ledger::create_tmp_ledger_with_mint; - use mint::Mint; - use reqwest; - use reqwest::header::CONTENT_TYPE; - use signature::{Keypair, KeypairUtil}; - use solana_sdk::hash::{hash, Hash}; - use std::fs::remove_dir_all; - use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - use system_transaction::SystemTransaction; - use transaction::Transaction; - - fn start_rpc_handler_with_tx(pubkey: Pubkey) -> (MetaIoHandler, Meta, Hash, Keypair) { - let alice = Mint::new(10_000); - let bank = Bank::new(&alice); - - let last_id = bank.last_id(); - let tx = Transaction::system_move(&alice.keypair(), pubkey, 20, last_id, 0); - bank.process_transaction(&tx).expect("process transaction"); - - let request_processor = JsonRpcRequestProcessor::new(Arc::new(bank)); - let cluster_info = Arc::new(RwLock::new(ClusterInfo::new(NodeInfo::default()))); - let leader = NodeInfo::new_with_socketaddr(&socketaddr!("127.0.0.1:1234")); - cluster_info.write().unwrap().insert_info(leader.clone()); - cluster_info.write().unwrap().set_leader(leader.id); - let rpc_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0); - let exit = Arc::new(AtomicBool::new(false)); - - let mut io = MetaIoHandler::default(); - let rpc = RpcSolImpl; - io.extend_with(rpc.to_delegate()); - let meta = Meta { - request_processor, - cluster_info, - rpc_addr, - exit, - }; - (io, meta, last_id, alice.keypair()) - } - - #[test] - #[ignore] - fn test_rpc_new() { - let alice = Mint::new(10_000); - let bank = Bank::new(&alice); - let cluster_info = Arc::new(RwLock::new(ClusterInfo::new(NodeInfo::default()))); - let rpc_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 24680); - let rpc_service = JsonRpcService::new(&Arc::new(bank), &cluster_info, rpc_addr); - let thread = rpc_service.thread_hdl.thread(); - assert_eq!(thread.name().unwrap(), "solana-jsonrpc"); - - let rpc_string = format!("http://{}", rpc_addr.to_string()); - let client = reqwest::Client::new(); - let request = json!({ - "jsonrpc": "2.0", - "id": 1, - "method": "getBalance", - "params": [alice.pubkey().to_string()], - }); - let mut response = client - .post(&rpc_string) - .header(CONTENT_TYPE, "application/json") - .body(request.to_string()) - .send() - .unwrap(); - let json: Value = serde_json::from_str(&response.text().unwrap()).unwrap(); - - assert_eq!(10_000, json["result"].as_u64().unwrap()); - } - - #[test] - fn test_rpc_request_processor_new() { - let alice = Mint::new(10_000); - let bob_pubkey = Keypair::new().pubkey(); - let bank = Bank::new(&alice); - let arc_bank = Arc::new(bank); - let request_processor = JsonRpcRequestProcessor::new(arc_bank.clone()); - thread::spawn(move || { - let last_id = arc_bank.last_id(); - let tx = Transaction::system_move(&alice.keypair(), bob_pubkey, 20, last_id, 0); - arc_bank - .process_transaction(&tx) - .expect("process transaction"); - }).join() - .unwrap(); - assert_eq!(request_processor.get_transaction_count().unwrap(), 1); - } - - #[test] - fn test_rpc_get_balance() { - let bob_pubkey = Keypair::new().pubkey(); - let (io, meta, _last_id, _alice_keypair) = start_rpc_handler_with_tx(bob_pubkey); - - let req = format!( - r#"{{"jsonrpc":"2.0","id":1,"method":"getBalance","params":["{}"]}}"#, - bob_pubkey - ); - let res = io.handle_request_sync(&req, meta); - let expected = format!(r#"{{"jsonrpc":"2.0","result":20,"id":1}}"#); - let expected: Response = - serde_json::from_str(&expected).expect("expected response deserialization"); - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - } - - #[test] - fn test_rpc_get_tx_count() { - let bob_pubkey = Keypair::new().pubkey(); - let (io, meta, _last_id, _alice_keypair) = start_rpc_handler_with_tx(bob_pubkey); - - let req = format!(r#"{{"jsonrpc":"2.0","id":1,"method":"getTransactionCount"}}"#); - let res = io.handle_request_sync(&req, meta); - let expected = format!(r#"{{"jsonrpc":"2.0","result":1,"id":1}}"#); - let expected: Response = - serde_json::from_str(&expected).expect("expected response deserialization"); - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - } - - #[test] - fn test_rpc_get_account_info() { - let bob_pubkey = Keypair::new().pubkey(); - let (io, meta, _last_id, _alice_keypair) = start_rpc_handler_with_tx(bob_pubkey); - - let req = format!( - r#"{{"jsonrpc":"2.0","id":1,"method":"getAccountInfo","params":["{}"]}}"#, - bob_pubkey - ); - let res = io.handle_request_sync(&req, meta); - let expected = r#"{ - "jsonrpc":"2.0", - "result":{ - "owner": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], - "tokens": 20, - "userdata": [], - "executable": false, - "loader": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] - }, - "id":1} - "#; - let expected: Response = - serde_json::from_str(&expected).expect("expected response deserialization"); - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - } - - #[test] - fn test_rpc_confirm_tx() { - let bob_pubkey = Keypair::new().pubkey(); - let (io, meta, last_id, alice_keypair) = start_rpc_handler_with_tx(bob_pubkey); - let tx = Transaction::system_move(&alice_keypair, bob_pubkey, 20, last_id, 0); - - let req = format!( - r#"{{"jsonrpc":"2.0","id":1,"method":"confirmTransaction","params":["{}"]}}"#, - tx.signatures[0] - ); - let res = io.handle_request_sync(&req, meta); - let expected = format!(r#"{{"jsonrpc":"2.0","result":true,"id":1}}"#); - let expected: Response = - serde_json::from_str(&expected).expect("expected response deserialization"); - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - } - - #[test] - fn test_rpc_get_signature_status() { - let bob_pubkey = Keypair::new().pubkey(); - let (io, meta, last_id, alice_keypair) = start_rpc_handler_with_tx(bob_pubkey); - let tx = Transaction::system_move(&alice_keypair, bob_pubkey, 20, last_id, 0); - - let req = format!( - r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":["{}"]}}"#, - tx.signatures[0] - ); - let res = io.handle_request_sync(&req, meta.clone()); - let expected = format!(r#"{{"jsonrpc":"2.0","result":"Confirmed","id":1}}"#); - let expected: Response = - serde_json::from_str(&expected).expect("expected response deserialization"); - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - - // Test getSignatureStatus request on unprocessed tx - let tx = Transaction::system_move(&alice_keypair, bob_pubkey, 10, last_id, 0); - let req = format!( - r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":["{}"]}}"#, - tx.signatures[0] - ); - let res = io.handle_request_sync(&req, meta); - let expected = format!(r#"{{"jsonrpc":"2.0","result":"SignatureNotFound","id":1}}"#); - let expected: Response = - serde_json::from_str(&expected).expect("expected response deserialization"); - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - } - - #[test] - fn test_rpc_get_finality() { - let bob_pubkey = Keypair::new().pubkey(); - let (io, meta, _last_id, _alice_keypair) = start_rpc_handler_with_tx(bob_pubkey); - - let req = format!(r#"{{"jsonrpc":"2.0","id":1,"method":"getFinality"}}"#); - let res = io.handle_request_sync(&req, meta); - let expected = format!(r#"{{"jsonrpc":"2.0","result":18446744073709551615,"id":1}}"#); - let expected: Response = - serde_json::from_str(&expected).expect("expected response deserialization"); - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - } - - #[test] - fn test_rpc_get_last_id() { - let bob_pubkey = Keypair::new().pubkey(); - let (io, meta, last_id, _alice_keypair) = start_rpc_handler_with_tx(bob_pubkey); - - let req = format!(r#"{{"jsonrpc":"2.0","id":1,"method":"getLastId"}}"#); - let res = io.handle_request_sync(&req, meta); - let expected = format!(r#"{{"jsonrpc":"2.0","result":"{}","id":1}}"#, last_id); - let expected: Response = - serde_json::from_str(&expected).expect("expected response deserialization"); - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - } - - #[test] - fn test_rpc_fail_request_airdrop() { - let bob_pubkey = Keypair::new().pubkey(); - let (io, meta, _last_id, _alice_keypair) = start_rpc_handler_with_tx(bob_pubkey); - - // Expect internal error because no leader is running - let req = format!( - r#"{{"jsonrpc":"2.0","id":1,"method":"requestAirdrop","params":["{}", 50]}}"#, - bob_pubkey - ); - let res = io.handle_request_sync(&req, meta); - let expected = - r#"{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error"},"id":1}"#; - let expected: Response = - serde_json::from_str(expected).expect("expected response deserialization"); - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - } - - #[test] - fn test_rpc_send_tx() { - let leader_keypair = Arc::new(Keypair::new()); - let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - - let alice = Mint::new(10_000_000); - let mut bank = Bank::new(&alice); - let bob_pubkey = Keypair::new().pubkey(); - let leader_data = leader.info.clone(); - let ledger_path = create_tmp_ledger_with_mint("rpc_send_tx", &alice); - - let last_id = bank.last_id(); - let tx = Transaction::system_move(&alice.keypair(), bob_pubkey, 20, last_id, 0); - let serial_tx = serialize(&tx).unwrap(); - - let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( - leader_data.id, - ))); - bank.leader_scheduler = leader_scheduler; - - let vote_account_keypair = Arc::new(Keypair::new()); - let entry_height = alice.create_entries().len() as u64; - let server = Fullnode::new_with_bank( - leader_keypair, - vote_account_keypair, - bank, - entry_height, - &last_id, - leader, - None, - &ledger_path, - false, - None, - ); - sleep(Duration::from_millis(900)); - - let client = reqwest::Client::new(); - let request = json!({ - "jsonrpc": "2.0", - "id": 1, - "method": "sendTransaction", - "params": json!([serial_tx]) - }); - let rpc_addr = leader_data.rpc; - let rpc_string = format!("http://{}", rpc_addr.to_string()); - let mut response = client - .post(&rpc_string) - .header(CONTENT_TYPE, "application/json") - .body(request.to_string()) - .send() - .unwrap(); - let json: Value = serde_json::from_str(&response.text().unwrap()).unwrap(); - let signature = &json["result"]; - - sleep(Duration::from_millis(500)); - - let client = reqwest::Client::new(); - let request = json!({ - "jsonrpc": "2.0", - "id": 1, - "method": "confirmTransaction", - "params": [signature], - }); - let mut response = client - .post(&rpc_string) - .header(CONTENT_TYPE, "application/json") - .body(request.to_string()) - .send() - .unwrap(); - let response_json_text = response.text().unwrap(); - let json: Value = serde_json::from_str(&response_json_text).unwrap(); - - assert_eq!(true, json["result"]); - - server.close().unwrap(); - remove_dir_all(ledger_path).unwrap(); - } - - #[test] - fn test_rpc_send_bad_tx() { - let alice = Mint::new(10_000); - let bank = Bank::new(&alice); - - let mut io = MetaIoHandler::default(); - let rpc = RpcSolImpl; - io.extend_with(rpc.to_delegate()); - let meta = Meta { - request_processor: JsonRpcRequestProcessor::new(Arc::new(bank)), - cluster_info: Arc::new(RwLock::new(ClusterInfo::new(NodeInfo::default()))), - rpc_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0), - exit: Arc::new(AtomicBool::new(false)), - }; - - let req = - r#"{"jsonrpc":"2.0","id":1,"method":"sendTransaction","params":[[0,0,0,0,0,0,0,0]]}"#; - let res = io.handle_request_sync(req, meta.clone()); - let expected = - r#"{"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid request"},"id":1}"#; - let expected: Response = - serde_json::from_str(expected).expect("expected response deserialization"); - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - } - - #[test] - fn test_rpc_get_leader_addr() { - let cluster_info = Arc::new(RwLock::new(ClusterInfo::new(NodeInfo::default()))); - assert_eq!( - get_leader_addr(&cluster_info), - Err(Error { - code: ErrorCode::InternalError, - message: "No leader detected".into(), - data: None, - }) - ); - let leader = NodeInfo::new_with_socketaddr(&socketaddr!("127.0.0.1:1234")); - cluster_info.write().unwrap().insert_info(leader.clone()); - cluster_info.write().unwrap().set_leader(leader.id); - assert_eq!( - get_leader_addr(&cluster_info), - Ok(socketaddr!("127.0.0.1:1234")) - ); - } - - #[test] - fn test_rpc_verify_pubkey() { - let pubkey = Keypair::new().pubkey(); - assert_eq!(verify_pubkey(pubkey.to_string()).unwrap(), pubkey); - let bad_pubkey = "a1b2c3d4"; - assert_eq!( - verify_pubkey(bad_pubkey.to_string()), - Err(Error::invalid_request()) - ); - } - - #[test] - fn test_rpc_verify_signature() { - let tx = - Transaction::system_move(&Keypair::new(), Keypair::new().pubkey(), 20, hash(&[0]), 0); - assert_eq!( - verify_signature(&tx.signatures[0].to_string()).unwrap(), - tx.signatures[0] - ); - let bad_signature = "a1b2c3d4"; - assert_eq!( - verify_signature(&bad_signature.to_string()), - Err(Error::invalid_request()) - ); - } -} diff --git a/book/rpc_pubsub.rs b/book/rpc_pubsub.rs deleted file mode 100644 index 1bf4bfc86624ef..00000000000000 --- a/book/rpc_pubsub.rs +++ /dev/null @@ -1,632 +0,0 @@ -//! The `pubsub` module implements a threaded subscription service on client RPC request - -use bank::Bank; -use bs58; -use jsonrpc_core::futures::Future; -use jsonrpc_core::*; -use jsonrpc_macros::pubsub; -use jsonrpc_pubsub::{PubSubHandler, Session, SubscriptionId}; -use jsonrpc_ws_server::{RequestContext, Sender, ServerBuilder}; -use rpc::{JsonRpcRequestProcessor, RpcSignatureStatus}; -use service::Service; -use signature::{Keypair, KeypairUtil, Signature}; -use solana_sdk::account::Account; -use solana_sdk::pubkey::Pubkey; -use std::collections::HashMap; -use std::mem; -use std::net::SocketAddr; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{atomic, Arc, RwLock}; -use std::thread::{self, sleep, Builder, JoinHandle}; -use std::time::Duration; - -pub enum ClientState { - Uninitialized, - Init(Sender), -} - -pub struct PubSubService { - thread_hdl: JoinHandle<()>, - exit: Arc, -} - -impl Service for PubSubService { - type JoinReturnType = (); - - fn join(self) -> thread::Result<()> { - self.thread_hdl.join() - } -} - -impl PubSubService { - pub fn new(bank: &Arc, pubsub_addr: SocketAddr) -> Self { - let rpc = RpcSolPubSubImpl::new(JsonRpcRequestProcessor::new(bank.clone()), bank.clone()); - let exit = Arc::new(AtomicBool::new(false)); - let exit_ = exit.clone(); - let thread_hdl = Builder::new() - .name("solana-pubsub".to_string()) - .spawn(move || { - let mut io = PubSubHandler::default(); - io.extend_with(rpc.to_delegate()); - - let server = ServerBuilder::with_meta_extractor(io, |context: &RequestContext| { - info!("New pubsub connection"); - let session = Arc::new(Session::new(context.sender().clone())); - session.on_drop(Box::new(|| { - info!("Pubsub connection dropped"); - })); - session - }) - .start(&pubsub_addr); - - if server.is_err() { - warn!("Pubsub service unavailable: unable to bind to port {}. \nMake sure this port is not already in use by another application", pubsub_addr.port()); - return; - } - while !exit_.load(Ordering::Relaxed) { - sleep(Duration::from_millis(100)); - } - server.unwrap().close(); - () - }) - .unwrap(); - PubSubService { thread_hdl, exit } - } - - pub fn exit(&self) { - self.exit.store(true, Ordering::Relaxed); - } - - pub fn close(self) -> thread::Result<()> { - self.exit(); - self.join() - } -} - -build_rpc_trait! { - pub trait RpcSolPubSub { - type Metadata; - - #[pubsub(name = "accountNotification")] { - // Get notification every time account userdata is changed - // Accepts pubkey parameter as base-58 encoded string - #[rpc(name = "accountSubscribe")] - fn account_subscribe(&self, Self::Metadata, pubsub::Subscriber, String); - - // Unsubscribe from account notification subscription. - #[rpc(name = "accountUnsubscribe")] - fn account_unsubscribe(&self, SubscriptionId) -> Result; - } - #[pubsub(name = "signatureNotification")] { - // Get notification when signature is verified - // Accepts signature parameter as base-58 encoded string - #[rpc(name = "signatureSubscribe")] - fn signature_subscribe(&self, Self::Metadata, pubsub::Subscriber, String); - - // Unsubscribe from signature notification subscription. - #[rpc(name = "signatureUnsubscribe")] - fn signature_unsubscribe(&self, SubscriptionId) -> Result; - } - } -} - -struct RpcSolPubSubImpl { - uid: Arc, - request_processor: JsonRpcRequestProcessor, - bank: Arc, - account_subscriptions: Arc>>, - signature_subscriptions: Arc>>, -} - -impl RpcSolPubSubImpl { - fn new(request_processor: JsonRpcRequestProcessor, bank: Arc) -> Self { - RpcSolPubSubImpl { - uid: Default::default(), - request_processor, - bank, - account_subscriptions: Default::default(), - signature_subscriptions: Default::default(), - } - } -} - -impl RpcSolPubSub for RpcSolPubSubImpl { - type Metadata = Arc; - - fn account_subscribe( - &self, - _meta: Self::Metadata, - subscriber: pubsub::Subscriber, - pubkey_str: String, - ) { - let pubkey_vec = bs58::decode(pubkey_str).into_vec().unwrap(); - if pubkey_vec.len() != mem::size_of::() { - subscriber - .reject(Error { - code: ErrorCode::InvalidParams, - message: "Invalid Request: Invalid pubkey provided".into(), - data: None, - }).unwrap(); - return; - } - let pubkey = Pubkey::new(&pubkey_vec); - - let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst); - let sub_id = SubscriptionId::Number(id as u64); - info!("account_subscribe: account={:?} id={:?}", pubkey, sub_id); - let sink = subscriber.assign_id(sub_id.clone()).unwrap(); - let bank_sub_id = Keypair::new().pubkey(); - self.account_subscriptions - .write() - .unwrap() - .insert(sub_id.clone(), (bank_sub_id, pubkey)); - - self.bank - .add_account_subscription(bank_sub_id, pubkey, sink); - } - - fn account_unsubscribe(&self, id: SubscriptionId) -> Result { - info!("account_unsubscribe: id={:?}", id); - if let Some((bank_sub_id, pubkey)) = self.account_subscriptions.write().unwrap().remove(&id) - { - self.bank.remove_account_subscription(&bank_sub_id, &pubkey); - Ok(true) - } else { - Err(Error { - code: ErrorCode::InvalidParams, - message: "Invalid Request: Subscription id does not exist".into(), - data: None, - }) - } - } - - fn signature_subscribe( - &self, - _meta: Self::Metadata, - subscriber: pubsub::Subscriber, - signature_str: String, - ) { - info!("signature_subscribe"); - let signature_vec = bs58::decode(signature_str).into_vec().unwrap(); - if signature_vec.len() != mem::size_of::() { - subscriber - .reject(Error { - code: ErrorCode::InvalidParams, - message: "Invalid Request: Invalid signature provided".into(), - data: None, - }).unwrap(); - return; - } - let signature = Signature::new(&signature_vec); - - let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst); - let sub_id = SubscriptionId::Number(id as u64); - let sink = subscriber.assign_id(sub_id.clone()).unwrap(); - let bank_sub_id = Keypair::new().pubkey(); - self.signature_subscriptions - .write() - .unwrap() - .insert(sub_id.clone(), (bank_sub_id, signature)); - - match self.request_processor.get_signature_status(signature) { - Ok(_) => { - sink.notify(Ok(RpcSignatureStatus::Confirmed)) - .wait() - .unwrap(); - self.signature_subscriptions - .write() - .unwrap() - .remove(&sub_id); - } - Err(_) => { - self.bank - .add_signature_subscription(bank_sub_id, signature, sink); - } - } - } - - fn signature_unsubscribe(&self, id: SubscriptionId) -> Result { - info!("signature_unsubscribe"); - if let Some((bank_sub_id, signature)) = - self.signature_subscriptions.write().unwrap().remove(&id) - { - self.bank - .remove_signature_subscription(&bank_sub_id, &signature); - Ok(true) - } else { - Err(Error { - code: ErrorCode::InvalidParams, - message: "Invalid Request: Subscription id does not exist".into(), - data: None, - }) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use budget_program::BudgetState; - use budget_transaction::BudgetTransaction; - use jsonrpc_core::futures::sync::mpsc; - use mint::Mint; - use signature::{Keypair, KeypairUtil}; - use std::net::{IpAddr, Ipv4Addr}; - use system_transaction::SystemTransaction; - use tokio::prelude::{Async, Stream}; - use transaction::Transaction; - - #[test] - fn test_pubsub_new() { - let alice = Mint::new(10_000); - let bank = Bank::new(&alice); - let pubsub_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0); - let pubsub_service = PubSubService::new(&Arc::new(bank), pubsub_addr); - let thread = pubsub_service.thread_hdl.thread(); - assert_eq!(thread.name().unwrap(), "solana-pubsub"); - } - - #[test] - fn test_signature_subscribe() { - let alice = Mint::new(10_000); - let bob = Keypair::new(); - let bob_pubkey = bob.pubkey(); - let bank = Bank::new(&alice); - let arc_bank = Arc::new(bank); - let last_id = arc_bank.last_id(); - - let (sender, mut receiver) = mpsc::channel(1); - let session = Arc::new(Session::new(sender)); - - let mut io = PubSubHandler::default(); - let rpc = RpcSolPubSubImpl::new( - JsonRpcRequestProcessor::new(arc_bank.clone()), - arc_bank.clone(), - ); - io.extend_with(rpc.to_delegate()); - - // Test signature subscription - let tx = Transaction::system_move(&alice.keypair(), bob_pubkey, 20, last_id, 0); - - let req = format!( - r#"{{"jsonrpc":"2.0","id":1,"method":"signatureSubscribe","params":["{}"]}}"#, - tx.signatures[0].to_string() - ); - let res = io.handle_request_sync(&req, session.clone()); - let expected = format!(r#"{{"jsonrpc":"2.0","result":0,"id":1}}"#); - let expected: Response = - serde_json::from_str(&expected).expect("expected response deserialization"); - - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - - // Test bad parameter - let req = format!( - r#"{{"jsonrpc":"2.0","id":1,"method":"signatureSubscribe","params":["a1b2c3"]}}"# - ); - let res = io.handle_request_sync(&req, session.clone()); - let expected = format!(r#"{{"jsonrpc":"2.0","error":{{"code":-32602,"message":"Invalid Request: Invalid signature provided"}},"id":1}}"#); - let expected: Response = - serde_json::from_str(&expected).expect("expected response deserialization"); - - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - - arc_bank - .process_transaction(&tx) - .expect("process transaction"); - sleep(Duration::from_millis(200)); - - // Test signature confirmation notification - let string = receiver.poll(); - assert!(string.is_ok()); - if let Async::Ready(Some(response)) = string.unwrap() { - let expected = format!(r#"{{"jsonrpc":"2.0","method":"signatureNotification","params":{{"result":"Confirmed","subscription":0}}}}"#); - assert_eq!(expected, response); - } - - // Test subscription id increment - let tx = Transaction::system_move(&alice.keypair(), bob_pubkey, 10, last_id, 0); - let req = format!( - r#"{{"jsonrpc":"2.0","id":1,"method":"signatureSubscribe","params":["{}"]}}"#, - tx.signatures[0].to_string() - ); - let res = io.handle_request_sync(&req, session.clone()); - let expected = format!(r#"{{"jsonrpc":"2.0","result":1,"id":1}}"#); - let expected: Response = - serde_json::from_str(&expected).expect("expected response deserialization"); - - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - } - - #[test] - fn test_signature_unsubscribe() { - let alice = Mint::new(10_000); - let bob_pubkey = Keypair::new().pubkey(); - let bank = Bank::new(&alice); - let arc_bank = Arc::new(bank); - let last_id = arc_bank.last_id(); - - let (sender, _receiver) = mpsc::channel(1); - let session = Arc::new(Session::new(sender)); - - let mut io = PubSubHandler::default(); - let rpc = RpcSolPubSubImpl::new( - JsonRpcRequestProcessor::new(arc_bank.clone()), - arc_bank.clone(), - ); - io.extend_with(rpc.to_delegate()); - - let tx = Transaction::system_move(&alice.keypair(), bob_pubkey, 20, last_id, 0); - let req = format!( - r#"{{"jsonrpc":"2.0","id":1,"method":"signatureSubscribe","params":["{}"]}}"#, - tx.signatures[0].to_string() - ); - let _res = io.handle_request_sync(&req, session.clone()); - - let req = - format!(r#"{{"jsonrpc":"2.0","id":1,"method":"signatureUnsubscribe","params":[0]}}"#); - let res = io.handle_request_sync(&req, session.clone()); - - let expected = format!(r#"{{"jsonrpc":"2.0","result":true,"id":1}}"#); - let expected: Response = - serde_json::from_str(&expected).expect("expected response deserialization"); - - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - - // Test bad parameter - let req = - format!(r#"{{"jsonrpc":"2.0","id":1,"method":"signatureUnsubscribe","params":[1]}}"#); - let res = io.handle_request_sync(&req, session.clone()); - let expected = format!(r#"{{"jsonrpc":"2.0","error":{{"code":-32602,"message":"Invalid Request: Subscription id does not exist"}},"id":1}}"#); - let expected: Response = - serde_json::from_str(&expected).expect("expected response deserialization"); - - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - } - - #[test] - fn test_account_subscribe() { - let alice = Mint::new(10_000); - let bob_pubkey = Keypair::new().pubkey(); - let witness = Keypair::new(); - let contract_funds = Keypair::new(); - let contract_state = Keypair::new(); - let budget_program_id = BudgetState::id(); - let loader = Pubkey::default(); // TODO - let executable = false; // TODO - let bank = Bank::new(&alice); - let arc_bank = Arc::new(bank); - let last_id = arc_bank.last_id(); - - let (sender, mut receiver) = mpsc::channel(1); - let session = Arc::new(Session::new(sender)); - - let mut io = PubSubHandler::default(); - let rpc = RpcSolPubSubImpl::new( - JsonRpcRequestProcessor::new(arc_bank.clone()), - arc_bank.clone(), - ); - io.extend_with(rpc.to_delegate()); - - let req = format!( - r#"{{"jsonrpc":"2.0","id":1,"method":"accountSubscribe","params":["{}"]}}"#, - contract_state.pubkey().to_string() - ); - - let res = io.handle_request_sync(&req, session.clone()); - let expected = format!(r#"{{"jsonrpc":"2.0","result":0,"id":1}}"#); - let expected: Response = - serde_json::from_str(&expected).expect("expected response deserialization"); - - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - - // Test bad parameter - let req = format!( - r#"{{"jsonrpc":"2.0","id":1,"method":"accountSubscribe","params":["a1b2c3"]}}"# - ); - let res = io.handle_request_sync(&req, session.clone()); - let expected = format!(r#"{{"jsonrpc":"2.0","error":{{"code":-32602,"message":"Invalid Request: Invalid pubkey provided"}},"id":1}}"#); - let expected: Response = - serde_json::from_str(&expected).expect("expected response deserialization"); - - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - - let tx = Transaction::system_create( - &alice.keypair(), - contract_funds.pubkey(), - last_id, - 50, - 0, - budget_program_id, - 0, - ); - arc_bank - .process_transaction(&tx) - .expect("process transaction"); - - let tx = Transaction::system_create( - &alice.keypair(), - contract_state.pubkey(), - last_id, - 1, - 196, - budget_program_id, - 0, - ); - - arc_bank - .process_transaction(&tx) - .expect("process transaction"); - - // Test signature confirmation notification #1 - let string = receiver.poll(); - assert!(string.is_ok()); - - let expected_userdata = arc_bank - .get_account(&contract_state.pubkey()) - .unwrap() - .userdata; - - let expected = json!({ - "jsonrpc": "2.0", - "method": "accountNotification", - "params": { - "result": { - "owner": budget_program_id, - "tokens": 1, - "userdata": expected_userdata, - "executable": executable, - "loader": loader, - - }, - "subscription": 0, - } - }); - - if let Async::Ready(Some(response)) = string.unwrap() { - assert_eq!(serde_json::to_string(&expected).unwrap(), response); - } - - let tx = Transaction::budget_new_when_signed( - &contract_funds, - bob_pubkey, - contract_state.pubkey(), - witness.pubkey(), - None, - 50, - last_id, - ); - arc_bank - .process_transaction(&tx) - .expect("process transaction"); - sleep(Duration::from_millis(200)); - - // Test signature confirmation notification #2 - let string = receiver.poll(); - assert!(string.is_ok()); - let expected_userdata = arc_bank - .get_account(&contract_state.pubkey()) - .unwrap() - .userdata; - let expected = json!({ - "jsonrpc": "2.0", - "method": "accountNotification", - "params": { - "result": { - "owner": budget_program_id, - "tokens": 51, - "userdata": expected_userdata, - "executable": executable, - "loader": loader, - }, - "subscription": 0, - } - }); - - if let Async::Ready(Some(response)) = string.unwrap() { - assert_eq!(serde_json::to_string(&expected).unwrap(), response); - } - - let tx = Transaction::system_new(&alice.keypair(), witness.pubkey(), 1, last_id); - arc_bank - .process_transaction(&tx) - .expect("process transaction"); - sleep(Duration::from_millis(200)); - let tx = Transaction::budget_new_signature( - &witness, - contract_state.pubkey(), - bob_pubkey, - last_id, - ); - arc_bank - .process_transaction(&tx) - .expect("process transaction"); - sleep(Duration::from_millis(200)); - - let expected_userdata = arc_bank - .get_account(&contract_state.pubkey()) - .unwrap() - .userdata; - let expected = json!({ - "jsonrpc": "2.0", - "method": "accountNotification", - "params": { - "result": { - "owner": budget_program_id, - "tokens": 1, - "userdata": expected_userdata, - "executable": executable, - "loader": loader, - }, - "subscription": 0, - } - }); - let string = receiver.poll(); - assert!(string.is_ok()); - if let Async::Ready(Some(response)) = string.unwrap() { - assert_eq!(serde_json::to_string(&expected).unwrap(), response); - } - } - - #[test] - fn test_account_unsubscribe() { - let alice = Mint::new(10_000); - let bob_pubkey = Keypair::new().pubkey(); - let bank = Bank::new(&alice); - let arc_bank = Arc::new(bank); - - let (sender, _receiver) = mpsc::channel(1); - let session = Arc::new(Session::new(sender)); - - let mut io = PubSubHandler::default(); - let rpc = RpcSolPubSubImpl::new( - JsonRpcRequestProcessor::new(arc_bank.clone()), - arc_bank.clone(), - ); - - io.extend_with(rpc.to_delegate()); - - let req = format!( - r#"{{"jsonrpc":"2.0","id":1,"method":"accountSubscribe","params":["{}"]}}"#, - bob_pubkey.to_string() - ); - let _res = io.handle_request_sync(&req, session.clone()); - - let req = - format!(r#"{{"jsonrpc":"2.0","id":1,"method":"accountUnsubscribe","params":[0]}}"#); - let res = io.handle_request_sync(&req, session.clone()); - - let expected = format!(r#"{{"jsonrpc":"2.0","result":true,"id":1}}"#); - let expected: Response = - serde_json::from_str(&expected).expect("expected response deserialization"); - - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - - // Test bad parameter - let req = - format!(r#"{{"jsonrpc":"2.0","id":1,"method":"accountUnsubscribe","params":[1]}}"#); - let res = io.handle_request_sync(&req, session.clone()); - let expected = format!(r#"{{"jsonrpc":"2.0","error":{{"code":-32602,"message":"Invalid Request: Subscription id does not exist"}},"id":1}}"#); - let expected: Response = - serde_json::from_str(&expected).expect("expected response deserialization"); - - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(expected, result); - } -} diff --git a/book/rpc_request.rs b/book/rpc_request.rs deleted file mode 100644 index d1b58f9659b2bd..00000000000000 --- a/book/rpc_request.rs +++ /dev/null @@ -1,191 +0,0 @@ -use reqwest; -use reqwest::header::CONTENT_TYPE; -use serde_json::{self, Value}; -use std::{error, fmt}; - -pub enum RpcRequest { - ConfirmTransaction, - GetAccountInfo, - GetBalance, - GetFinality, - GetLastId, - GetSignatureStatus, - GetTransactionCount, - RequestAirdrop, - SendTransaction, -} -impl RpcRequest { - pub fn make_rpc_request( - &self, - rpc_addr: &str, - id: u64, - params: Option, - ) -> Result> { - let request = self.build_request_json(id, params); - let client = reqwest::Client::new(); - let mut response = client - .post(rpc_addr) - .header(CONTENT_TYPE, "application/json") - .body(request.to_string()) - .send()?; - let json: Value = serde_json::from_str(&response.text()?)?; - if json["error"].is_object() { - Err(RpcError::RpcRequestError(format!( - "RPC Error response: {}", - serde_json::to_string(&json["error"]).unwrap() - )))? - } - Ok(json["result"].clone()) - } - fn build_request_json(&self, id: u64, params: Option) -> Value { - let jsonrpc = "2.0"; - let method = match self { - RpcRequest::ConfirmTransaction => "confirmTransaction", - RpcRequest::GetAccountInfo => "getAccountInfo", - RpcRequest::GetBalance => "getBalance", - RpcRequest::GetFinality => "getFinality", - RpcRequest::GetLastId => "getLastId", - RpcRequest::GetSignatureStatus => "getSignatureStatus", - RpcRequest::GetTransactionCount => "getTransactionCount", - RpcRequest::RequestAirdrop => "requestAirdrop", - RpcRequest::SendTransaction => "sendTransaction", - }; - let mut request = json!({ - "jsonrpc": jsonrpc, - "id": id, - "method": method, - }); - if let Some(param_string) = params { - request["params"] = param_string; - } - request - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum RpcError { - RpcRequestError(String), -} - -impl fmt::Display for RpcError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "invalid") - } -} - -impl error::Error for RpcError { - fn description(&self) -> &str { - "invalid" - } - - fn cause(&self) -> Option<&error::Error> { - // Generic error, underlying cause isn't tracked. - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - use jsonrpc_core::*; - use jsonrpc_http_server::*; - use serde_json::Number; - use std::net::{Ipv4Addr, SocketAddr}; - use std::sync::mpsc::channel; - use std::thread; - - #[test] - fn test_build_request_json() { - let test_request = RpcRequest::GetAccountInfo; - let request = test_request.build_request_json( - 1, - Some(json!(["deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx"])), - ); - assert_eq!(request["method"], "getAccountInfo"); - assert_eq!( - request["params"], - json!(["deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx"]) - ); - - let test_request = RpcRequest::GetBalance; - let request = test_request.build_request_json( - 1, - Some(json!(["deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx"])), - ); - assert_eq!(request["method"], "getBalance"); - - let test_request = RpcRequest::GetFinality; - let request = test_request.build_request_json(1, None); - assert_eq!(request["method"], "getFinality"); - assert_eq!(request["params"], json!(null)); - - let test_request = RpcRequest::GetLastId; - let request = test_request.build_request_json(1, None); - assert_eq!(request["method"], "getLastId"); - - let test_request = RpcRequest::GetTransactionCount; - let request = test_request.build_request_json(1, None); - assert_eq!(request["method"], "getTransactionCount"); - - let test_request = RpcRequest::RequestAirdrop; - let request = test_request.build_request_json(1, None); - assert_eq!(request["method"], "requestAirdrop"); - - let test_request = RpcRequest::SendTransaction; - let request = test_request.build_request_json(1, None); - assert_eq!(request["method"], "sendTransaction"); - } - #[test] - fn test_make_rpc_request() { - let (sender, receiver) = channel(); - thread::spawn(move || { - let rpc_addr = socketaddr!(0, 0); - let mut io = IoHandler::default(); - // Successful request - io.add_method("getBalance", |_params: Params| { - Ok(Value::Number(Number::from(50))) - }); - // Failed request - io.add_method("getLastId", |params: Params| { - if params != Params::None { - Err(Error::invalid_request()) - } else { - Ok(Value::String( - "deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx".to_string(), - )) - } - }); - - let server = ServerBuilder::new(io) - .threads(1) - .cors(DomainsValidation::AllowOnly(vec![ - AccessControlAllowOrigin::Any, - ])).start_http(&rpc_addr) - .expect("Unable to start RPC server"); - sender.send(*server.address()).unwrap(); - server.wait(); - }); - - let rpc_addr = receiver.recv().unwrap(); - let rpc_addr = format!("http://{}", rpc_addr.to_string()); - - let balance = RpcRequest::GetBalance.make_rpc_request( - &rpc_addr, - 1, - Some(json!(["deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx"])), - ); - assert!(balance.is_ok()); - assert_eq!(balance.unwrap().as_u64().unwrap(), 50); - - let last_id = RpcRequest::GetLastId.make_rpc_request(&rpc_addr, 2, None); - assert!(last_id.is_ok()); - assert_eq!( - last_id.unwrap().as_str().unwrap(), - "deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx" - ); - - // Send erroneous parameter - let last_id = RpcRequest::GetLastId.make_rpc_request(&rpc_addr, 3, Some(json!("paramter"))); - assert_eq!(last_id.is_err(), true); - } -} diff --git a/book/runtime.html b/book/runtime.html deleted file mode 100644 index 243290510c054e..00000000000000 --- a/book/runtime.html +++ /dev/null @@ -1,279 +0,0 @@ - - - - - - The Runtime - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

Runtime

-

The goal with the runtime is to have a general purpose execution environment -that is highly parallelizable. To achieve this goal the runtime forces each -Instruction to specify all of its memory dependencies up front, and therefore a -single Instruction cannot cause a dynamic memory allocation. An explicit -Instruction for memory allocation from the SystemProgram::CreateAccount is -the only way to allocate new memory in the engine. A Transaction may compose -multiple Instruction, including SystemProgram::CreateAccount, into a single -atomic sequence which allows for memory allocation to achieve a result that is -similar to dynamic allocation.

-

State

-

State is addressed by an Account which is at the moment simply the Pubkey. Our -goal is to eliminate memory allocation from within the program itself. Thus -the client of the program provides all the state that is necessary for the -program to execute in the transaction itself. The runtime interacts with the -program through an entry point with a well defined interface. The userdata -stored in an Account is an opaque type to the runtime, a Vec<u8>, the -contents of which the program code has full control over.

-

The Transaction structure specifies a list of Pubkey's and signatures for those -keys and a sequential list of instructions that will operate over the state's -associated with the account_keys. For the transaction to be committed all -the instructions must execute successfully, if any abort the whole transaction -fails to commit.

-

Account structure Accounts maintain token state as well as program specific

-

memory.

-

Transaction Engine

-

At its core, the engine looks up all the Pubkeys maps them to accounts and -routs them to the program_id entry point.

-

Execution

-

Transactions are batched and processed in a pipeline

-

Runtime pipeline

-

At the execute stage, the loaded pages have no data dependencies, so all the -programs can be executed in parallel.

-

The runtime enforces the following rules:

-
    -
  1. The program_id code is the only code that will modify the contents of -Account::userdata of Account's that have been assigned to it. This means -that upon assignment userdata vector is guaranteed to be 0.
  2. -
  3. Total balances on all the accounts is equal before and after execution of a -Transaction.
  4. -
  5. Balances of each of the accounts not assigned to program_id must be equal -to or greater after the Transaction than before the transaction.
  6. -
  7. All Instructions in the Transaction executed without a failure.
  8. -
-

Entry Point Execution of the program involves mapping the Program's public

-

key to an entry point which takes a pointer to the transaction, and an array of -loaded pages.

-

System Interface

-

The interface is best described by the Instruction::userdata that the -user encodes.

-
    -
  • CreateAccount - This allows the user to create and assign an Account to a -Program.
  • -
  • Assign - allows the user to assign an existing account to a Program.
  • -
  • Move - moves tokens between Accounts that are associated with -SystemProgram. This cannot be used to move tokens of other Accounts. -Programs need to implement their own version of Move.
  • -
-

Notes

-
    -
  1. There is no dynamic memory allocation. Client's need to call the -SystemProgram to create memory before passing it to another program. This -Instruction can be composed into a single Transaction with the call to the -program itself.
  2. -
  3. Runtime guarantees that when memory is assigned to the Program it is zero -initialized.
  4. -
  5. Runtime guarantees that Program's code is the only thing that can modify -memory that its assigned to
  6. -
  7. Runtime guarantees that the Program can only spend tokens that are in -Accounts that are assigned to it
  8. -
  9. Runtime guarantees the balances belonging to Accounts are balanced before -and after the transaction
  10. -
  11. Runtime guarantees that multiple instructions all executed successfully when -a transaction is committed.
  12. -
-

Future Work

- - -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/searcher.js b/book/searcher.js deleted file mode 100644 index 7fd97d4879269f..00000000000000 --- a/book/searcher.js +++ /dev/null @@ -1,477 +0,0 @@ -"use strict"; -window.search = window.search || {}; -(function search(search) { - // Search functionality - // - // You can use !hasFocus() to prevent keyhandling in your key - // event handlers while the user is typing their search. - - if (!Mark || !elasticlunr) { - return; - } - - //IE 11 Compatibility from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith - if (!String.prototype.startsWith) { - String.prototype.startsWith = function(search, pos) { - return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; - }; - } - - var search_wrap = document.getElementById('search-wrapper'), - searchbar = document.getElementById('searchbar'), - searchbar_outer = document.getElementById('searchbar-outer'), - searchresults = document.getElementById('searchresults'), - searchresults_outer = document.getElementById('searchresults-outer'), - searchresults_header = document.getElementById('searchresults-header'), - searchicon = document.getElementById('search-toggle'), - content = document.getElementById('content'), - - searchindex = null, - doc_urls = [], - results_options = { - teaser_word_count: 30, - limit_results: 30, - }, - search_options = { - bool: "AND", - expand: true, - fields: { - title: {boost: 1}, - body: {boost: 1}, - breadcrumbs: {boost: 0} - } - }, - mark_exclude = [], - marker = new Mark(content), - current_searchterm = "", - URL_SEARCH_PARAM = 'search', - URL_MARK_PARAM = 'highlight', - teaser_count = 0, - - SEARCH_HOTKEY_KEYCODE = 83, - ESCAPE_KEYCODE = 27, - DOWN_KEYCODE = 40, - UP_KEYCODE = 38, - SELECT_KEYCODE = 13; - - function hasFocus() { - return searchbar === document.activeElement; - } - - function removeChildren(elem) { - while (elem.firstChild) { - elem.removeChild(elem.firstChild); - } - } - - // Helper to parse a url into its building blocks. - function parseURL(url) { - var a = document.createElement('a'); - a.href = url; - return { - source: url, - protocol: a.protocol.replace(':',''), - host: a.hostname, - port: a.port, - params: (function(){ - var ret = {}; - var seg = a.search.replace(/^\?/,'').split('&'); - var len = seg.length, i = 0, s; - for (;i': '>', - '"': '"', - "'": ''' - }; - var repl = function(c) { return MAP[c]; }; - return function(s) { - return s.replace(/[&<>'"]/g, repl); - }; - })(); - - function formatSearchMetric(count, searchterm) { - if (count == 1) { - return count + " search result for '" + searchterm + "':"; - } else if (count == 0) { - return "No search results for '" + searchterm + "'."; - } else { - return count + " search results for '" + searchterm + "':"; - } - } - - function formatSearchResult(result, searchterms) { - var teaser = makeTeaser(escapeHTML(result.doc.body), searchterms); - teaser_count++; - - // The ?URL_MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor - var url = doc_urls[result.ref].split("#"); - if (url.length == 1) { // no anchor found - url.push(""); - } - - return '' + result.doc.breadcrumbs + '' - + '' - + teaser + ''; - } - - function makeTeaser(body, searchterms) { - // The strategy is as follows: - // First, assign a value to each word in the document: - // Words that correspond to search terms (stemmer aware): 40 - // Normal words: 2 - // First word in a sentence: 8 - // Then use a sliding window with a constant number of words and count the - // sum of the values of the words within the window. Then use the window that got the - // maximum sum. If there are multiple maximas, then get the last one. - // Enclose the terms in . - var stemmed_searchterms = searchterms.map(function(w) { - return elasticlunr.stemmer(w.toLowerCase()); - }); - var searchterm_weight = 40; - var weighted = []; // contains elements of ["word", weight, index_in_document] - // split in sentences, then words - var sentences = body.toLowerCase().split('. '); - var index = 0; - var value = 0; - var searchterm_found = false; - for (var sentenceindex in sentences) { - var words = sentences[sentenceindex].split(' '); - value = 8; - for (var wordindex in words) { - var word = words[wordindex]; - if (word.length > 0) { - for (var searchtermindex in stemmed_searchterms) { - if (elasticlunr.stemmer(word).startsWith(stemmed_searchterms[searchtermindex])) { - value = searchterm_weight; - searchterm_found = true; - } - }; - weighted.push([word, value, index]); - value = 2; - } - index += word.length; - index += 1; // ' ' or '.' if last word in sentence - }; - index += 1; // because we split at a two-char boundary '. ' - }; - - if (weighted.length == 0) { - return body; - } - - var window_weight = []; - var window_size = Math.min(weighted.length, results_options.teaser_word_count); - - var cur_sum = 0; - for (var wordindex = 0; wordindex < window_size; wordindex++) { - cur_sum += weighted[wordindex][1]; - }; - window_weight.push(cur_sum); - for (var wordindex = 0; wordindex < weighted.length - window_size; wordindex++) { - cur_sum -= weighted[wordindex][1]; - cur_sum += weighted[wordindex + window_size][1]; - window_weight.push(cur_sum); - }; - - if (searchterm_found) { - var max_sum = 0; - var max_sum_window_index = 0; - // backwards - for (var i = window_weight.length - 1; i >= 0; i--) { - if (window_weight[i] > max_sum) { - max_sum = window_weight[i]; - max_sum_window_index = i; - } - }; - } else { - max_sum_window_index = 0; - } - - // add around searchterms - var teaser_split = []; - var index = weighted[max_sum_window_index][2]; - for (var i = max_sum_window_index; i < max_sum_window_index+window_size; i++) { - var word = weighted[i]; - if (index < word[2]) { - // missing text from index to start of `word` - teaser_split.push(body.substring(index, word[2])); - index = word[2]; - } - if (word[1] == searchterm_weight) { - teaser_split.push("") - } - index = word[2] + word[0].length; - teaser_split.push(body.substring(word[2], index)); - if (word[1] == searchterm_weight) { - teaser_split.push("") - } - }; - - return teaser_split.join(''); - } - - function init(config) { - results_options = config.results_options; - search_options = config.search_options; - searchbar_outer = config.searchbar_outer; - doc_urls = config.doc_urls; - searchindex = elasticlunr.Index.load(config.index); - - // Set up events - searchicon.addEventListener('click', function(e) { searchIconClickHandler(); }, false); - searchbar.addEventListener('keyup', function(e) { searchbarKeyUpHandler(); }, false); - document.addEventListener('keydown', function(e) { globalKeyHandler(e); }, false); - // If the user uses the browser buttons, do the same as if a reload happened - window.onpopstate = function(e) { doSearchOrMarkFromUrl(); }; - // Suppress "submit" events so the page doesn't reload when the user presses Enter - document.addEventListener('submit', function(e) { e.preventDefault(); }, false); - - // If reloaded, do the search or mark again, depending on the current url parameters - doSearchOrMarkFromUrl(); - } - - function unfocusSearchbar() { - // hacky, but just focusing a div only works once - var tmp = document.createElement('input'); - tmp.setAttribute('style', 'position: absolute; opacity: 0;'); - searchicon.appendChild(tmp); - tmp.focus(); - tmp.remove(); - } - - // On reload or browser history backwards/forwards events, parse the url and do search or mark - function doSearchOrMarkFromUrl() { - // Check current URL for search request - var url = parseURL(window.location.href); - if (url.params.hasOwnProperty(URL_SEARCH_PARAM) - && url.params[URL_SEARCH_PARAM] != "") { - showSearch(true); - searchbar.value = decodeURIComponent( - (url.params[URL_SEARCH_PARAM]+'').replace(/\+/g, '%20')); - searchbarKeyUpHandler(); // -> doSearch() - } else { - showSearch(false); - } - - if (url.params.hasOwnProperty(URL_MARK_PARAM)) { - var words = url.params[URL_MARK_PARAM].split(' '); - marker.mark(words, { - exclude: mark_exclude - }); - - var markers = document.querySelectorAll("mark"); - function hide() { - for (var i = 0; i < markers.length; i++) { - markers[i].classList.add("fade-out"); - window.setTimeout(function(e) { marker.unmark(); }, 300); - } - } - for (var i = 0; i < markers.length; i++) { - markers[i].addEventListener('click', hide); - } - } - } - - // Eventhandler for keyevents on `document` - function globalKeyHandler(e) { - if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea') { return; } - - if (e.keyCode === ESCAPE_KEYCODE) { - e.preventDefault(); - searchbar.classList.remove("active"); - setSearchUrlParameters("", - (searchbar.value.trim() !== "") ? "push" : "replace"); - if (hasFocus()) { - unfocusSearchbar(); - } - showSearch(false); - marker.unmark(); - } else if (!hasFocus() && e.keyCode === SEARCH_HOTKEY_KEYCODE) { - e.preventDefault(); - showSearch(true); - window.scrollTo(0, 0); - searchbar.select(); - } else if (hasFocus() && e.keyCode === DOWN_KEYCODE) { - e.preventDefault(); - unfocusSearchbar(); - searchresults.firstElementChild.classList.add("focus"); - } else if (!hasFocus() && (e.keyCode === DOWN_KEYCODE - || e.keyCode === UP_KEYCODE - || e.keyCode === SELECT_KEYCODE)) { - // not `:focus` because browser does annoying scrolling - var focused = searchresults.querySelector("li.focus"); - if (!focused) return; - e.preventDefault(); - if (e.keyCode === DOWN_KEYCODE) { - var next = focused.nextElementSibling; - if (next) { - focused.classList.remove("focus"); - next.classList.add("focus"); - } - } else if (e.keyCode === UP_KEYCODE) { - focused.classList.remove("focus"); - var prev = focused.previousElementSibling; - if (prev) { - prev.classList.add("focus"); - } else { - searchbar.select(); - } - } else { // SELECT_KEYCODE - window.location.assign(focused.querySelector('a')); - } - } - } - - function showSearch(yes) { - if (yes) { - search_wrap.classList.remove('hidden'); - searchicon.setAttribute('aria-expanded', 'true'); - } else { - search_wrap.classList.add('hidden'); - searchicon.setAttribute('aria-expanded', 'false'); - var results = searchresults.children; - for (var i = 0; i < results.length; i++) { - results[i].classList.remove("focus"); - } - } - } - - function showResults(yes) { - if (yes) { - searchresults_outer.classList.remove('hidden'); - } else { - searchresults_outer.classList.add('hidden'); - } - } - - // Eventhandler for search icon - function searchIconClickHandler() { - if (search_wrap.classList.contains('hidden')) { - showSearch(true); - window.scrollTo(0, 0); - searchbar.select(); - } else { - showSearch(false); - } - } - - // Eventhandler for keyevents while the searchbar is focused - function searchbarKeyUpHandler() { - var searchterm = searchbar.value.trim(); - if (searchterm != "") { - searchbar.classList.add("active"); - doSearch(searchterm); - } else { - searchbar.classList.remove("active"); - showResults(false); - removeChildren(searchresults); - } - - setSearchUrlParameters(searchterm, "push_if_new_search_else_replace"); - - // Remove marks - marker.unmark(); - } - - // Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and #heading-anchor . - // `action` can be one of "push", "replace", "push_if_new_search_else_replace" - // and replaces or pushes a new browser history item. - // "push_if_new_search_else_replace" pushes if there is no `?URL_SEARCH_PARAM=abc` yet. - function setSearchUrlParameters(searchterm, action) { - var url = parseURL(window.location.href); - var first_search = ! url.params.hasOwnProperty(URL_SEARCH_PARAM); - if (searchterm != "" || action == "push_if_new_search_else_replace") { - url.params[URL_SEARCH_PARAM] = searchterm; - delete url.params[URL_MARK_PARAM]; - url.hash = ""; - } else { - delete url.params[URL_SEARCH_PARAM]; - } - // A new search will also add a new history item, so the user can go back - // to the page prior to searching. A updated search term will only replace - // the url. - if (action == "push" || (action == "push_if_new_search_else_replace" && first_search) ) { - history.pushState({}, document.title, renderURL(url)); - } else if (action == "replace" || (action == "push_if_new_search_else_replace" && !first_search) ) { - history.replaceState({}, document.title, renderURL(url)); - } - } - - function doSearch(searchterm) { - - // Don't search the same twice - if (current_searchterm == searchterm) { return; } - else { current_searchterm = searchterm; } - - if (searchindex == null) { return; } - - // Do the actual search - var results = searchindex.search(searchterm, search_options); - var resultcount = Math.min(results.length, results_options.limit_results); - - // Display search metrics - searchresults_header.innerText = formatSearchMetric(resultcount, searchterm); - - // Clear and insert results - var searchterms = searchterm.split(' '); - removeChildren(searchresults); - for(var i = 0; i < resultcount ; i++){ - var resultElem = document.createElement('li'); - resultElem.innerHTML = formatSearchResult(results[i], searchterms); - searchresults.appendChild(resultElem); - } - - // Display results - showResults(true); - } - - fetch(path_to_root + 'searchindex.json') - .then(response => response.json()) - .then(json => init(json)) - .catch(error => { // Try to load searchindex.js if fetch failed - var script = document.createElement('script'); - script.src = path_to_root + 'searchindex.js'; - script.onload = () => init(window.search); - document.head.appendChild(script); - }); - - // Exported functions - search.hasFocus = hasFocus; -})(window.search); diff --git a/book/searchindex.js b/book/searchindex.js deleted file mode 100644 index 389fddd04d93b1..00000000000000 --- a/book/searchindex.js +++ /dev/null @@ -1 +0,0 @@ -window.search = {"doc_urls":["introduction.html#disclaimer","introduction.html#introduction","terminology.html#terminology","terminology.html#teminology-currently-in-use","terminology.html#terminology-reserved-for-future-use","synchronization.html#synchronization","vdf.html#introduction-to-vdfs","poh.html#proof-of-history","poh.html#relationship-to-consensus-mechanisms","poh.html#relationship-to-vdfs","leader-scheduler.html#leader-rotation","leader-scheduler.html#leader-seed-generation","leader-scheduler.html#leader-rotation","leader-scheduler.html#partitions-forks","leader-scheduler.html#examples","leader-scheduler.html#small-partition","leader-scheduler.html#leader-timeout","leader-scheduler.html#network-variables","fullnode.html#fullnode","fullnode.html#pipelining","fullnode.html#pipelining-in-the-fullnode","tpu.html#the-transaction-processing-unit","tvu.html#the-transaction-validation-unit","ncp.html#ncp","jsonrpc-service.html#jsonrpcservice","avalanche.html#avalanche-replication","storage.html#storage","storage.html#background","storage.html#optimization-with-poh","storage.html#network","storage.html#constraints","storage.html#validation-and-replication-protocol","programs.html#the-solana-sdk","programs.html#introduction","programs.html#client-interactions-with-solana","programs.html#persistent-storage","runtime.html#runtime","runtime.html#state","runtime.html#account-structure-accounts-maintain-token-state-as-well-as-program-specific","runtime.html#transaction-engine","runtime.html#execution","runtime.html#entry-point-execution-of-the-program-involves-mapping-the-programs-public","runtime.html#system-interface","runtime.html#notes","runtime.html#future-work","ledger.html#ledger-format","appendix.html#appendix","jsonrpc-api.html#json-rpc-api","jsonrpc-api.html#rpc-http-endpoint","jsonrpc-api.html#rpc-pubsub-websocket-endpoint","jsonrpc-api.html#methods","jsonrpc-api.html#request-formatting","jsonrpc-api.html#definitions","jsonrpc-api.html#json-rpc-api-reference","jsonrpc-api.html#confirmtransaction","jsonrpc-api.html#getbalance","jsonrpc-api.html#getaccountinfo","jsonrpc-api.html#getlastid","jsonrpc-api.html#getsignaturestatus","jsonrpc-api.html#gettransactioncount","jsonrpc-api.html#requestairdrop","jsonrpc-api.html#sendtransaction","jsonrpc-api.html#subscription-websocket","jsonrpc-api.html#accountsubscribe","jsonrpc-api.html#accountunsubscribe","jsonrpc-api.html#signaturesubscribe","jsonrpc-api.html#signatureunsubscribe","wallet.html#solana-wallet-cli","wallet.html#examples","wallet.html#usage"],"index":{"documentStore":{"docInfo":{"0":{"body":27,"breadcrumbs":1,"title":1},"1":{"body":172,"breadcrumbs":1,"title":1},"10":{"body":46,"breadcrumbs":3,"title":2},"11":{"body":38,"breadcrumbs":4,"title":3},"12":{"body":73,"breadcrumbs":3,"title":2},"13":{"body":259,"breadcrumbs":3,"title":2},"14":{"body":0,"breadcrumbs":2,"title":1},"15":{"body":51,"breadcrumbs":3,"title":2},"16":{"body":56,"breadcrumbs":3,"title":2},"17":{"body":57,"breadcrumbs":3,"title":2},"18":{"body":0,"breadcrumbs":1,"title":1},"19":{"body":106,"breadcrumbs":1,"title":1},"2":{"body":0,"breadcrumbs":1,"title":1},"20":{"body":44,"breadcrumbs":2,"title":2},"21":{"body":0,"breadcrumbs":4,"title":3},"22":{"body":0,"breadcrumbs":4,"title":3},"23":{"body":9,"breadcrumbs":2,"title":1},"24":{"body":0,"breadcrumbs":2,"title":1},"25":{"body":81,"breadcrumbs":2,"title":2},"26":{"body":0,"breadcrumbs":3,"title":1},"27":{"body":129,"breadcrumbs":3,"title":1},"28":{"body":74,"breadcrumbs":4,"title":2},"29":{"body":41,"breadcrumbs":3,"title":1},"3":{"body":106,"breadcrumbs":3,"title":3},"30":{"body":50,"breadcrumbs":3,"title":1},"31":{"body":262,"breadcrumbs":5,"title":3},"32":{"body":0,"breadcrumbs":2,"title":2},"33":{"body":11,"breadcrumbs":1,"title":1},"34":{"body":42,"breadcrumbs":3,"title":3},"35":{"body":60,"breadcrumbs":2,"title":2},"36":{"body":53,"breadcrumbs":3,"title":1},"37":{"body":71,"breadcrumbs":3,"title":1},"38":{"body":1,"breadcrumbs":11,"title":9},"39":{"body":11,"breadcrumbs":4,"title":2},"4":{"body":35,"breadcrumbs":4,"title":4},"40":{"body":54,"breadcrumbs":3,"title":1},"41":{"body":9,"breadcrumbs":10,"title":8},"42":{"body":38,"breadcrumbs":4,"title":2},"43":{"body":61,"breadcrumbs":3,"title":1},"44":{"body":5,"breadcrumbs":4,"title":2},"45":{"body":0,"breadcrumbs":4,"title":2},"46":{"body":9,"breadcrumbs":1,"title":1},"47":{"body":25,"breadcrumbs":4,"title":3},"48":{"body":6,"breadcrumbs":4,"title":3},"49":{"body":6,"breadcrumbs":5,"title":4},"5":{"body":196,"breadcrumbs":1,"title":1},"50":{"body":15,"breadcrumbs":2,"title":1},"51":{"body":85,"breadcrumbs":3,"title":2},"52":{"body":24,"breadcrumbs":2,"title":1},"53":{"body":0,"breadcrumbs":5,"title":4},"54":{"body":36,"breadcrumbs":2,"title":1},"55":{"body":38,"breadcrumbs":2,"title":1},"56":{"body":81,"breadcrumbs":2,"title":1},"57":{"body":32,"breadcrumbs":2,"title":1},"58":{"body":88,"breadcrumbs":2,"title":1},"59":{"body":29,"breadcrumbs":2,"title":1},"6":{"body":104,"breadcrumbs":3,"title":2},"60":{"body":47,"breadcrumbs":2,"title":1},"61":{"body":238,"breadcrumbs":2,"title":1},"62":{"body":16,"breadcrumbs":3,"title":2},"63":{"body":43,"breadcrumbs":2,"title":1},"64":{"body":27,"breadcrumbs":2,"title":1},"65":{"body":44,"breadcrumbs":2,"title":1},"66":{"body":27,"breadcrumbs":2,"title":1},"67":{"body":7,"breadcrumbs":4,"title":3},"68":{"body":220,"breadcrumbs":2,"title":1},"69":{"body":402,"breadcrumbs":2,"title":1},"7":{"body":3,"breadcrumbs":3,"title":2},"8":{"body":91,"breadcrumbs":4,"title":3},"9":{"body":124,"breadcrumbs":3,"title":2}},"docs":{"0":{"body":"All claims, content, designs, algorithms, estimates, roadmaps, specifications, and performance measurements described in this project are done with the author's best effort. It is up to the reader to check and validate their accuracy and truthfulness. Furthermore nothing in this project constitutes a solicitation for investment.","breadcrumbs":"Disclaimer","id":"0","title":"Disclaimer"},"1":{"body":"This document defines the architecture of Solana, a blockchain built from the ground up for scale. The goal of the architecture is to demonstrate there exists a set of software algorithms that in combination, removes software as a performance bottleneck, allowing transaction throughput to scale proportionally with network bandwidth. The architecture goes on to satisfy all three desirable properties of a proper blockchain, that it not only be scalable, but that it is also secure and decentralized. With this architecture, we calculate a theoretical upper bound of 710 thousand transactions per second (tps) on a standard gigabit network and 28.4 million tps on 40 gigabit. In practice, our focus has been on gigabit. We soak-tested a 150 node permissioned testnet and it is able to maintain a mean transaction throughput of approximately 200 thousand tps with peaks over 400 thousand. Furthermore, we have found high throughput extends beyond simple payments, and that this architecture is also able to perform safe, concurrent execution of programs authored in a general purpose programming language, such as C. We feel the extension warrants industry focus on an additional performance metric already common in the CPU industry, millions of instructions per second or mips. By measuring mips, we see that batching instructions within a transaction amortizes the cost of signature verification, lifting the maximum theoretical instruction throughput up to almost exactly that of centralized databases. Lastly, we discuss the relationships between high throughput, security and transaction fees. Solana's efficient use hardware drives transaction fees into the ballpark of 1/1000th of a cent. The drop in fees in turn makes certain denial of service attacks cheaper. We discuss what these attacks look like and Solana's techniques to defend against them.","breadcrumbs":"Introduction","id":"1","title":"Introduction"},"10":{"body":"A property of any permissionless blockchain is that the entity choosing the next block is randomly selected. In proof of stake systems, that entity is typically called the \"leader\" or \"block producer.\" In Solana, we call it the leader. Under the hood, a leader is simply a mode of the fullnode. A fullnode runs as either a leader or validator. In this chapter, we describe how a fullnode determines what node is the leader, how that mechanism may choose different leaders at the same time, and if so, how the system converges in response.","breadcrumbs":"Synchronization » Leader Rotation","id":"10","title":"Leader Rotation"},"11":{"body":"Leader selection is decided via a random seed. The process is as follows: Periodically, at a specific PoH tick count, select the signatures of the votes that made up the last supermajority Concatenate the signatures Hash the resulting string for N counts The resulting hash is the random seed for M counts, M leader slots, where M > N","breadcrumbs":"Synchronization » Leader Seed Generation","id":"11","title":"Leader Seed Generation"},"12":{"body":"The leader is chosen via a random seed generated from stake weights and votes (the leader schedule) The leader is rotated every T PoH ticks (leader slot), according to the leader schedule The schedule is applicable for M voting rounds Leader's transmit for a count of T PoH ticks. When T is reached all the validators should switch to the next scheduled leader. To schedule leaders, the supermajority + M nodes are shuffled using the above calculated random seed. All T ticks must be observed from the current leader for that part of PoH to be accepted by the network. If T ticks (and any intervening transactions) are not observed, the network optimistically fills in the T ticks, and continues with PoH from the next leader.","breadcrumbs":"Synchronization » Leader Rotation","id":"12","title":"Leader Rotation"},"13":{"body":"Forks can arise at PoH tick counts that correspond to leader rotations, because leader nodes may or may not have observed the previous leader's data. These empty ticks are generated by all nodes in the network at a network-specified rate for hashes-per-tick Z . There are only two possible versions of the PoH during a voting round: PoH with T ticks and entries generated by the current leader, or PoH with just ticks. The \"just ticks\" version of the PoH can be thought of as a virtual ledger, one that all nodes in the network can derive from the last tick in the previous slot. Validators can ignore forks at other points (e.g. from the wrong leader), or slash the leader responsible for the fork. Validators vote on the longest chain that contains their previous vote, or a longer chain if the lockout on their previous vote has expired. Validator's View Time Progression The diagram below represents a validator's view of the PoH stream with possible forks over time. L1, L2, etc. are leader slots, and E s represent entries from that leader during that leader's slot. The x s represent ticks only, and time flows downwards in the diagram. Note that an E appearing on 2 branches at the same slot is a slashable condition, so a validator observing L3 and L3' can slash L3 and safely choose x for that slot. Once a validator observes a supermajority vote on any branch, other branches can be discarded below that tick count. For any slot, validators need only consider a single \"has entries\" chain or a \"ticks only\" chain. Time Division It's useful to consider leader rotation over PoH tick count as time division of the job of encoding state for the network. The following table presents the above tree of forks as a time-divided ledger. leader slot L1 L2 L3 L4 L5 data E1 E2 E3 E4 E5 ticks since prev x xx Note that only data from leader L3 will be accepted during leader slot L3 . Data from L3 may include \"catchup\" ticks back to a slot other than L2 if L3 did not observe L2 's data. L4 and L5 's transmissions include the \"ticks since prev\" PoH entries. This arrangement of the network data streams permits nodes to save exactly this to the ledger for replay, restart, and checkpoints. Leader's View When a new leader begins a slot, it must first transmit any PoH (ticks) required to link the new slot with the most recently observed and voted slot.","breadcrumbs":"Synchronization » Partitions, Forks","id":"13","title":"Partitions, Forks"},"14":{"body":"","breadcrumbs":"Synchronization » Examples","id":"14","title":"Examples"},"15":{"body":"Network partition M occurs for 10% of the nodes The larger partition K , with 90% of the stake weight continues to operate as normal M cycles through the ranks until one of them is leader, generating ticks for slots where the leader is in K . M validators observe 10% of the vote pool, finality is not reached. M and K reconnect. M validators cancel their votes on M , which has not reached finality, and re-cast on K (after their vote lockout on M ).","breadcrumbs":"Synchronization » Small Partition","id":"15","title":"Small Partition"},"16":{"body":"Next rank leader node V observes a timeout from current leader A , fills in A 's slot with virtual ticks and starts sending out entries. Nodes observing both streams keep track of the forks, waiting for: their vote on leader A to expire in order to be able to vote on B a supermajority on A 's slot If the first case occurs, leader B 's slot is filled with ticks. if the second case occurs, A's slot is filled with ticks Partition is resolved just like in the Small Partition above","breadcrumbs":"Synchronization » Leader Timeout","id":"16","title":"Leader Timeout"},"17":{"body":"A - name of a node B - name of a node K - number of nodes in the supermajority to whom leaders broadcast their PoH hash for validation M - number of nodes outside the supermajority to whom leaders broadcast their PoH hash for validation N - number of voting rounds for which a leader schedule is considered before a new leader schedule is used T - number of PoH ticks per leader slot (also voting round) V - name of a node that will create virtual ticks Z - number of hashes per PoH tick","breadcrumbs":"Synchronization » Network Variables","id":"17","title":"Network Variables"},"18":{"body":"","breadcrumbs":"Fullnode","id":"18","title":"Fullnode"},"19":{"body":"The fullnodes make extensive use of an optimization common in CPU design, called pipelining . Pipelining is the right tool for the job when there's a stream of input data that needs to be processed by a sequence of steps, and there's different hardware responsible for each. The quintessential example is using a washer and dryer to wash/dry/fold several loads of laundry. Washing must occur before drying and drying before folding, but each of the three operations is performed by a separate unit. To maximize efficiency, one creates a pipeline of stages . We'll call the washer one stage, the dryer another, and the folding process a third. To run the pipeline, one adds a second load of laundry to the washer just after the first load is added to the dryer. Likewise, the third load is added to the washer after the second is in the dryer and the first is being folded. In this way, one can make progress on three loads of laundry simultaneously. Given infinite loads, the pipeline will consistently complete a load at the rate of the slowest stage in the pipeline.","breadcrumbs":"Pipelining","id":"19","title":"Pipelining"},"2":{"body":"","breadcrumbs":"Terminology","id":"2","title":"Terminology"},"20":{"body":"The fullnode contains two pipelined processes, one used in leader mode called the Tpu and one used in validator mode called the Tvu. In both cases, the hardware being pipelined is the same, the network input, the GPU cards, the CPU cores, writes to disk, and the network output. What it does with that hardware is different. The Tpu exists to create ledger entries whereas the Tvu exists to validate them.","breadcrumbs":"Pipelining in the fullnode","id":"20","title":"Pipelining in the fullnode"},"21":{"body":"","breadcrumbs":"Fullnode » The Transaction Processing Unit","id":"21","title":"The Transaction Processing Unit"},"22":{"body":"","breadcrumbs":"Fullnode » The Transaction Validation Unit","id":"22","title":"The Transaction Validation Unit"},"23":{"body":"The Network Control Plane implements a gossip network between all nodes on in the cluster.","breadcrumbs":"Fullnode » Ncp","id":"23","title":"Ncp"},"24":{"body":"","breadcrumbs":"Fullnode » JsonRpcService","id":"24","title":"JsonRpcService"},"25":{"body":"The Avalance explainer video is a conceptual overview of how a Solana leader can continuously process a gigabit of transaction data per second and then get that same data, after being recorded on the ledger, out to multiple validators on a single gigabit backplane. In practice, we found that just one level of the Avalanche validator tree is sufficient for at least 150 validators. We anticipate adding the second level to solve one of two problems: To transmit ledger segments to slower \"replicator\" nodes. To scale up the number of validators nodes. Both problems justify the additional level, but you won't find it implemented in the reference design just yet, because Solana's gossip implementation is currently the bottleneck on the number of nodes per Solana cluster. That work is being actively developed here: Scalable Gossip","breadcrumbs":"Avalanche replication","id":"25","title":"Avalanche replication"},"26":{"body":"","breadcrumbs":"Avalanche replication » Storage","id":"26","title":"Storage"},"27":{"body":"At full capacity on a 1gbps network Solana would generate 4 petabytes of data per year. If each fullnode was required to store the full ledger, the cost of storage would discourage fullnode participation, thus centralizing the network around those that could afford it. Solana aims to keep the cost of a fullnode below $5,000 USD to maximize participation. To achieve that, the network needs to minimize redundant storage while at the same time ensuring the validity and availability of each copy. To trust storage of ledger segments, Solana has replicators periodically submit proofs to the network that the data was replicated. Each proof is called a Proof of Replication. The basic idea of it is to encrypt a dataset with a public symmetric key and then hash the encrypted dataset. Solana uses CBC encryption . To prevent a malicious replicator from deleting the data as soon as it's hashed, a replicator is required hash random segments of the dataset. Alternatively, Solana could require hashing the reverse of the encrypted data, but random sampling is sufficient and much faster. Either solution ensures that all the data is present during the generation of the proof and also requires the validator to have the entirety of the encrypted data present for verification of every proof of every identity. The space required to validate is: number_of_proofs * data_size","breadcrumbs":"Avalanche replication » Background","id":"27","title":"Background"},"28":{"body":"Solana is not the only distribute systems project using Proof of Replication, but it might be the most efficient implementation because of its ability to synchronize nodes with its Proof of History. With PoH, Solana is able to record a hash of the PoRep samples in the ledger. Thus the blocks stay in the exact same order for every PoRep and verification can stream the data and verify all the proofs in a single batch. This way Solana can verify multiple proofs concurrently, each one on its own GPU core. With the current generation of graphics cards our network can support up to 14,000 replication identities or symmetric keys. The total space required for verification is: 2 CBC_blocks * number_of_identities with core count of equal to (Number of Identities). A CBC block is expected to be 1MB in size.","breadcrumbs":"Avalanche replication » Optimization with PoH","id":"28","title":"Optimization with PoH"},"29":{"body":"Validators for PoRep are the same validators that are verifying transactions. They have some stake that they have put up as collateral that ensures that their work is honest. If you can prove that a validator verified a fake PoRep, then the validator's stake is slashed. Replicators are specialized light clients. They download a part of the ledger and store it and provide proofs of storing the ledger. For each verified proof, replicators are rewarded tokens from the mining pool.","breadcrumbs":"Avalanche replication » Network","id":"29","title":"Network"},"3":{"body":"The following list contains words commonly used throughout the Solana architecture. account - a persistent file addressed by pubkey and with tokens tracking its lifetime cluster - a set of fullnodes maintaining a single ledger finality - the wallclock duration between a leader creating a tick entry and recoginizing a supermajority of validator votes with a ledger interpretation that matches the leader's fullnode - a full participant in the cluster - either a leader or validator node entry - an entry on the ledger - either a tick or a transactions entry instruction - the smallest unit of a program that a client can include in a transaction keypair - a public and secret key node count - the number of fullnodes participating in a cluster program - the code that interprets instructions pubkey - the public key of a keypair tick - a ledger entry that estimates wallclock duration tick height - the Nth tick in the ledger tps - transactions per second transaction - one or more instructions signed by the client and executed atomically transactions entry - a set of transactions that may be executed in parallel","breadcrumbs":"Teminology Currently in Use","id":"3","title":"Teminology Currently in Use"},"30":{"body":"Solana's PoRep protocol instroduces the following constraints: At most 14,000 replication identities can be used, because that is how many GPU cores are currently available to a computer costing under $5,000 USD. Verification requires generating the CBC blocks. That requires space of 2 blocks per identity, and 1 GPU core per identity for the same dataset. As many identities at once are batched with as many proofs for those identities verified concurrently for the same dataset.","breadcrumbs":"Avalanche replication » Constraints","id":"30","title":"Constraints"},"31":{"body":"The network sets a replication target number, let's say 1k. 1k PoRep identities are created from signatures of a PoH hash. They are tied to a specific PoH hash. It doesn't matter who creates them, or it could simply be the last 1k validation signatures we saw for the ledger at that count. This is maybe just the initial batch of identities, because we want to stagger identity rotation. Any client can use any of these identities to create PoRep proofs. Replicator identities are the CBC encryption keys. Periodically at a specific PoH count, a replicator that wants to create PoRep proofs signs the PoH hash at that count. That signature is the seed used to pick the block and identity to replicate. A block is 1TB of ledger. Periodically at a specific PoH count, a replicator submits PoRep proofs for their selected block. A signature of the PoH hash at that count is the seed used to sample the 1TB encrypted block, and hash it. This is done faster than it takes to encrypt the 1TB block with the original identity. Replicators must submit some number of fake proofs, which they can prove to be fake by providing the seed for the hash result. Periodically at a specific PoH count, validators sign the hash and use the signature to select the 1TB block that they need to validate. They batch all the identities and proofs and submit approval for all the verified ones. After #6, replicator client submit the proofs of fake proofs. For any random seed, Solana requires everyone to use a signature that is derived from a PoH hash. Every node uses the same count so that the same PoH hash is signed by every participant. The signatures are then each cryptographically tied to the keypair, which prevents a leader from grinding on the resulting value for more than 1 identity. Key rotation is staggered . Once going, the next identity is generated by hashing itself with a PoH hash. Since there are many more client identities then encryption identities, the reward is split amont multiple clients to prevent Sybil attacks from generating many clients to acquire the same block of data. To remain BFT, the network needs to avoid a single human entity from storing all the replications of a single chunk of the ledger. Solana's solution to this is to require clients to continue using the same identity. If the first round is used to acquire the same block for many client identities, the second round for the same client identities will require a redistribution of the signatures, and therefore PoRep identities and blocks. Thus to get a reward for storage, clients are not rewarded for storage of the first block. The network rewards long-lived client identities more than new ones.","breadcrumbs":"Avalanche replication » Validation and Replication Protocol","id":"31","title":"Validation and Replication Protocol"},"32":{"body":"","breadcrumbs":"The Solana SDK","id":"32","title":"The Solana SDK"},"33":{"body":"With the Solana runtime, we can execute on-chain programs concurrently, and written in the client’s choice of programming language.","breadcrumbs":"Introduction","id":"33","title":"Introduction"},"34":{"body":"As shown in the diagram above an untrusted client, creates a program in the language of their choice, (i.e. C/C++/Rust/Lua), and compiles it with LLVM to a position independent shared object ELF, targeting BPF bytecode, and sends it to the Solana cluster. Next, the client sends messages to the Solana cluster, which target that program. The Solana runtime loads the previously submitted ELF and passes it the client's message for interpretation.","breadcrumbs":"Client interactions with Solana","id":"34","title":"Client interactions with Solana"},"35":{"body":"Solana supports several kinds of persistent storage, called accounts : Executable Writable by a client Writable by a program Read-only All accounts are identified by public keys and may hold arbirary data. When the client sends messages to programs, it requests access to storage using those keys. The runtime loads the account data and passes it to the program. The runtime also ensures accounts aren't written to if not owned by the client or program. Any writes to read-only accounts are discarded unless the write was to credit tokens. Any user may credit other accounts tokens, regardless of account permission.","breadcrumbs":"Persistent Storage","id":"35","title":"Persistent Storage"},"36":{"body":"The goal with the runtime is to have a general purpose execution environment that is highly parallelizable. To achieve this goal the runtime forces each Instruction to specify all of its memory dependencies up front, and therefore a single Instruction cannot cause a dynamic memory allocation. An explicit Instruction for memory allocation from the SystemProgram::CreateAccount is the only way to allocate new memory in the engine. A Transaction may compose multiple Instruction, including SystemProgram::CreateAccount , into a single atomic sequence which allows for memory allocation to achieve a result that is similar to dynamic allocation.","breadcrumbs":"On-chain programs » Runtime","id":"36","title":"Runtime"},"37":{"body":"State is addressed by an Account which is at the moment simply the Pubkey. Our goal is to eliminate memory allocation from within the program itself. Thus the client of the program provides all the state that is necessary for the program to execute in the transaction itself. The runtime interacts with the program through an entry point with a well defined interface. The userdata stored in an Account is an opaque type to the runtime, a Vec , the contents of which the program code has full control over. The Transaction structure specifies a list of Pubkey's and signatures for those keys and a sequential list of instructions that will operate over the state's associated with the account_keys . For the transaction to be committed all the instructions must execute successfully, if any abort the whole transaction fails to commit.","breadcrumbs":"On-chain programs » State","id":"37","title":"State"},"38":{"body":"memory.","breadcrumbs":"On-chain programs » Account structure Accounts maintain token state as well as program specific","id":"38","title":"Account structure Accounts maintain token state as well as program specific"},"39":{"body":"At its core, the engine looks up all the Pubkeys maps them to accounts and routs them to the program_id entry point.","breadcrumbs":"On-chain programs » Transaction Engine","id":"39","title":"Transaction Engine"},"4":{"body":"The following keywords do not have any functionality but are reserved by Solana for potential future use. epoch - the time in which a leader schedule is valid mips - millions of instructions per second public key - We currently use pubkey slot - the time in which a single leader may produce entries secret key - Users currently only use keypair","breadcrumbs":"Terminology Reserved for Future Use","id":"4","title":"Terminology Reserved for Future Use"},"40":{"body":"Transactions are batched and processed in a pipeline At the execute stage, the loaded pages have no data dependencies, so all the programs can be executed in parallel. The runtime enforces the following rules: The program_id code is the only code that will modify the contents of Account::userdata of Account's that have been assigned to it. This means that upon assignment userdata vector is guaranteed to be 0 . Total balances on all the accounts is equal before and after execution of a Transaction. Balances of each of the accounts not assigned to program_id must be equal to or greater after the Transaction than before the transaction. All Instructions in the Transaction executed without a failure.","breadcrumbs":"On-chain programs » Execution","id":"40","title":"Execution"},"41":{"body":"key to an entry point which takes a pointer to the transaction, and an array of loaded pages.","breadcrumbs":"On-chain programs » Entry Point Execution of the program involves mapping the Program's public","id":"41","title":"Entry Point Execution of the program involves mapping the Program's public"},"42":{"body":"The interface is best described by the Instruction::userdata that the user encodes. CreateAccount - This allows the user to create and assign an Account to a Program. Assign - allows the user to assign an existing account to a Program . Move - moves tokens between Account s that are associated with SystemProgram . This cannot be used to move tokens of other Account s. Programs need to implement their own version of Move.","breadcrumbs":"On-chain programs » System Interface","id":"42","title":"System Interface"},"43":{"body":"There is no dynamic memory allocation. Client's need to call the SystemProgram to create memory before passing it to another program. This Instruction can be composed into a single Transaction with the call to the program itself. Runtime guarantees that when memory is assigned to the Program it is zero initialized. Runtime guarantees that Program 's code is the only thing that can modify memory that its assigned to Runtime guarantees that the Program can only spend tokens that are in Account s that are assigned to it Runtime guarantees the balances belonging to Account s are balanced before and after the transaction Runtime guarantees that multiple instructions all executed successfully when a transaction is committed.","breadcrumbs":"On-chain programs » Notes","id":"43","title":"Notes"},"44":{"body":"Continuations and Signals for long running Transactions","breadcrumbs":"On-chain programs » Future Work","id":"44","title":"Future Work"},"45":{"body":"","breadcrumbs":"On-chain programs » Ledger format","id":"45","title":"Ledger format"},"46":{"body":"The following sections contain reference material you may find useful in your Solana journey.","breadcrumbs":"Appendix","id":"46","title":"Appendix"},"47":{"body":"Solana nodes accept HTTP requests using the JSON-RPC 2.0 specification. To interact with a Solana node inside a JavaScript application, use the solana-web3.js library, which gives a convenient interface for the RPC methods.","breadcrumbs":"Appendix » JSON RPC API","id":"47","title":"JSON RPC API"},"48":{"body":"Default port: 8899 eg. http://localhost:8899, http://192.168.1.88:8899","breadcrumbs":"Appendix » RPC HTTP Endpoint","id":"48","title":"RPC HTTP Endpoint"},"49":{"body":"Default port: 8900 eg. ws://localhost:8900, http://192.168.1.88:8900","breadcrumbs":"Appendix » RPC PubSub WebSocket Endpoint","id":"49","title":"RPC PubSub WebSocket Endpoint"},"5":{"body":"It's possible for a centralized database to process 710,000 transactions per second on a standard gigabit network if the transactions are, on average, no more than 176 bytes. A centralized database can also replicate itself and maintain high availability without significantly compromising that transaction rate using the distributed system technique known as Optimistic Concurrency Control [H.T.Kung, J.T.Robinson (1981)] . At Solana, we're demonstrating that these same theoretical limits apply just as well to blockchain on an adversarial network. The key ingredient? Finding a way to share time when nodes can't trust one-another. Once nodes can trust time, suddenly ~40 years of distributed systems research becomes applicable to blockchain! Perhaps the most striking difference between algorithms obtained by our method and ones based upon timeout is that using timeout produces a traditional distributed algorithm in which the processes operate asynchronously, while our method produces a globally synchronous one in which every process does the same thing at (approximately) the same time. Our method seems to contradict the whole purpose of distributed processing, which is to permit different processes to operate independently and perform different functions. However, if a distributed system is really a single system, then the processes must be synchronized in some way. Conceptually, the easiest way to synchronize processes is to get them all to do the same thing at the same time. Therefore, our method is used to implement a kernel that performs the necessary synchronization--for example, making sure that two different processes do not try to modify a file at the same time. Processes might spend only a small fraction of their time executing the synchronizing kernel; the rest of the time, they can operate independently--e.g., accessing different files. This is an approach we have advocated even when fault-tolerance is not required. The method's basic simplicity makes it easier to understand the precise properties of a system, which is crucial if one is to know just how fault-tolerant the system is. [L.Lamport (1984)]","breadcrumbs":"Synchronization","id":"5","title":"Synchronization"},"50":{"body":"confirmTransaction getBalance getAccountInfo getLastId getSignatureStatus getTransactionCount requestAirdrop sendTransaction startSubscriptionChannel Subscription Websocket accountSubscribe accountUnsubscribe signatureSubscribe signatureUnsubscribe","breadcrumbs":"Appendix » Methods","id":"50","title":"Methods"},"51":{"body":"To make a JSON-RPC request, send an HTTP POST request with a Content-Type: application/json header. The JSON request data should contain 4 fields: jsonrpc , set to \"2.0\" id , a unique client-generated identifying integer method , a string containing the method to be invoked params , a JSON array of ordered parameter values Example using curl: curl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"getBalance\", \"params\":[\"83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri\"]}' 192.168.1.88:8899 The response output will be a JSON object with the following fields: jsonrpc , matching the request specification id , matching the request identifier result , requested data or success confirmation Requests can be sent in batches by sending an array of JSON-RPC request objects as the data for a single POST.","breadcrumbs":"Appendix » Request Formatting","id":"51","title":"Request Formatting"},"52":{"body":"Hash: A SHA-256 hash of a chunk of data. Pubkey: The public key of a Ed25519 key-pair. Signature: An Ed25519 signature of a chunk of data. Transaction: A Solana instruction signed by a client key-pair.","breadcrumbs":"Appendix » Definitions","id":"52","title":"Definitions"},"53":{"body":"","breadcrumbs":"Appendix » JSON RPC API Reference","id":"53","title":"JSON RPC API Reference"},"54":{"body":"Returns a transaction receipt Parameters: string - Signature of Transaction to confirm, as base-58 encoded string Results: boolean - Transaction status, true if Transaction is confirmed Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"confirmTransaction\", \"params\":[\"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW\"]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":true,\"id\":1}","breadcrumbs":"Appendix » confirmTransaction","id":"54","title":"confirmTransaction"},"55":{"body":"Returns the balance of the account of provided Pubkey Parameters: string - Pubkey of account to query, as base-58 encoded string Results: integer - quantity, as a signed 64-bit integer Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"getBalance\", \"params\":[\"83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri\"]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":0,\"id\":1}","breadcrumbs":"Appendix » getBalance","id":"55","title":"getBalance"},"56":{"body":"Returns all information associated with the account of provided Pubkey Parameters: string - Pubkey of account to query, as base-58 encoded string Results: The result field will be a JSON object with the following sub fields: tokens , number of tokens assigned to this account, as a signed 64-bit integer owner , array of 32 bytes representing the program this account has been assigned to userdata , array of bytes representing any userdata associated with the account executable , boolean indicating if the account contains a program (and is strictly read-only) loader , array of 32 bytes representing the loader for this program (if executable ), otherwise all Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"getAccountInfo\", \"params\":[\"2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST\"]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":{\"executable\":false,\"loader\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"owner\":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tokens\":1,\"userdata\":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},\"id\":1}","breadcrumbs":"Appendix » getAccountInfo","id":"56","title":"getAccountInfo"},"57":{"body":"Returns the last entry ID from the ledger Parameters: None Results: string - the ID of last entry, a Hash as base-58 encoded string Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getLastId\"}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":\"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC\",\"id\":1}","breadcrumbs":"Appendix » getLastId","id":"57","title":"getLastId"},"58":{"body":"Returns the status of a given signature. This method is similar to confirmTransaction but provides more resolution for error events. Parameters: string - Signature of Transaction to confirm, as base-58 encoded string Results: string - Transaction status: Confirmed - Transaction was successful SignatureNotFound - Unknown transaction ProgramRuntimeError - An error occurred in the program that processed this Transaction AccountInUse - Another Transaction had a write lock one of the Accounts specified in this Transaction. The Transaction may succeed if retried GenericFailure - Some other error occurred. Note : In the future new Transaction statuses may be added to this list. It's safe to assume that all new statuses will be more specific error conditions that previously presented as GenericFailure Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"getSignatureStatus\", \"params\":[\"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW\"]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":\"SignatureNotFound\",\"id\":1}","breadcrumbs":"Appendix » getSignatureStatus","id":"58","title":"getSignatureStatus"},"59":{"body":"Returns the current Transaction count from the ledger Parameters: None Results: integer - count, as unsigned 64-bit integer Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getTransactionCount\"}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":268,\"id\":1}","breadcrumbs":"Appendix » getTransactionCount","id":"59","title":"getTransactionCount"},"6":{"body":"A Verifiable Delay Function is conceptually a water clock where its water marks can be recorded and later verified that the water most certainly passed through. Anatoly describes the water clock analogy in detail here: water clock analogy The same technique has been used in Bitcoin since day one. The Bitcoin feature is called nLocktime and it can be used to postdate transactions using block height instead of a timestamp. As a Bitcoin client, you'd use block height instead of a timestamp if you don't trust the network. Block height turns out to be an instance of what's being called a Verifiable Delay Function in cryptography circles. It's a cryptographically secure way to say time has passed. In Solana, we use a far more granular verifiable delay function, a SHA 256 hash chain, to checkpoint the ledger and coordinate consensus. With it, we implement Optimistic Concurrency Control and are now well en route towards that theoretical limit of 710,000 transactions per second.","breadcrumbs":"Synchronization » Introduction to VDFs","id":"6","title":"Introduction to VDFs"},"60":{"body":"Requests an airdrop of tokens to a Pubkey Parameters: string - Pubkey of account to receive tokens, as base-58 encoded string integer - token quantity, as a signed 64-bit integer Results: string - Transaction Signature of airdrop, as base-58 encoded string Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"requestAirdrop\", \"params\":[\"83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri\", 50]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":\"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW\",\"id\":1}","breadcrumbs":"Appendix » requestAirdrop","id":"60","title":"requestAirdrop"},"61":{"body":"Creates new transaction Parameters: array - array of octets containing a fully-signed Transaction Results: string - Transaction Signature, as base-58 encoded string Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"sendTransaction\", \"params\":[[61, 98, 55, 49, 15, 187, 41, 215, 176, 49, 234, 229, 228, 77, 129, 221, 239, 88, 145, 227, 81, 158, 223, 123, 14, 229, 235, 247, 191, 115, 199, 71, 121, 17, 32, 67, 63, 209, 239, 160, 161, 2, 94, 105, 48, 159, 235, 235, 93, 98, 172, 97, 63, 197, 160, 164, 192, 20, 92, 111, 57, 145, 251, 6, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 13, 39, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 11, 12, 106, 49, 74, 226, 201, 16, 161, 192, 28, 84, 124, 97, 190, 201, 171, 186, 6, 18, 70, 142, 89, 185, 176, 154, 115, 61, 26, 163, 77, 1, 88, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":\"2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b\",\"id\":1}","breadcrumbs":"Appendix » sendTransaction","id":"61","title":"sendTransaction"},"62":{"body":"After connect to the RPC PubSub websocket at ws://
/ : Submit subscription requests to the websocket using the methods below Multiple subscriptions may be active at once","breadcrumbs":"Appendix » Subscription Websocket","id":"62","title":"Subscription Websocket"},"63":{"body":"Subscribe to an account to receive notifications when the userdata for a given account public key changes Parameters: string - account Pubkey, as base-58 encoded string Results: integer - Subscription id (needed to unsubscribe) Example: // Request\n{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"accountSubscribe\", \"params\":[\"CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12\"]} // Result\n{\"jsonrpc\": \"2.0\",\"result\": 0,\"id\": 1} Notification Format: {\"jsonrpc\": \"2.0\",\"method\": \"accountNotification\", \"params\": {\"result\": {\"executable\":false,\"loader\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"owner\":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tokens\":1,\"userdata\":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},\"subscription\":0}}","breadcrumbs":"Appendix » accountSubscribe","id":"63","title":"accountSubscribe"},"64":{"body":"Unsubscribe from account userdata change notifications Parameters: integer - id of account Subscription to cancel Results: bool - unsubscribe success message Example: // Request\n{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"accountUnsubscribe\", \"params\":[0]} // Result\n{\"jsonrpc\": \"2.0\",\"result\": true,\"id\": 1}","breadcrumbs":"Appendix » accountUnsubscribe","id":"64","title":"accountUnsubscribe"},"65":{"body":"Subscribe to a transaction signature to receive notification when the transaction is confirmed On signatureNotification , the subscription is automatically cancelled Parameters: string - Transaction Signature, as base-58 encoded string Results: integer - subscription id (needed to unsubscribe) Example: // Request\n{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"signatureSubscribe\", \"params\":[\"2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b\"]} // Result\n{\"jsonrpc\": \"2.0\",\"result\": 0,\"id\": 1} Notification Format: {\"jsonrpc\": \"2.0\",\"method\": \"signatureNotification\", \"params\": {\"result\": \"Confirmed\",\"subscription\":0}}","breadcrumbs":"Appendix » signatureSubscribe","id":"65","title":"signatureSubscribe"},"66":{"body":"Unsubscribe from account userdata change notifications Parameters: integer - id of account subscription to cancel Results: bool - unsubscribe success message Example: // Request\n{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"signatureUnsubscribe\", \"params\":[0]} // Result\n{\"jsonrpc\": \"2.0\",\"result\": true,\"id\": 1}","breadcrumbs":"Appendix » signatureUnsubscribe","id":"66","title":"signatureUnsubscribe"},"67":{"body":"The solana crate is distributed with a command-line interface tool","breadcrumbs":"Appendix » solana-wallet CLI","id":"67","title":"solana-wallet CLI"},"68":{"body":"Get Pubkey // Command\n$ solana-wallet address // Return\n Airdrop Tokens // Command\n$ solana-wallet airdrop 123 // Return\n\"Your balance is: 123\" Get Balance // Command\n$ solana-wallet balance // Return\n\"Your balance is: 123\" Confirm Transaction // Command\n$ solana-wallet confirm // Return\n\"Confirmed\" / \"Not found\" Deploy program // Command\n$ solana-wallet deploy // Return\n Unconditional Immediate Transfer // Command\n$ solana-wallet pay 123 // Return\n Post-Dated Transfer // Command\n$ solana-wallet pay 123 \\ --after 2018-12-24T23:59:00 --require-timestamp-from // Return\n{signature: , processId: } require-timestamp-from is optional. If not provided, the transaction will expect a timestamp signed by this wallet's secret key Authorized Transfer A third party must send a signature to unlock the tokens. // Command\n$ solana-wallet pay 123 \\ --require-signature-from // Return\n{signature: , processId: } Post-Dated and Authorized Transfer // Command\n$ solana-wallet pay 123 \\ --after 2018-12-24T23:59 --require-timestamp-from \\ --require-signature-from // Return\n{signature: , processId: } Multiple Witnesses // Command\n$ solana-wallet pay 123 \\ --require-signature-from \\ --require-signature-from // Return\n{signature: , processId: } Cancelable Transfer // Command\n$ solana-wallet pay 123 \\ --require-signature-from \\ --cancelable // Return\n{signature: , processId: } Cancel Transfer // Command\n$ solana-wallet cancel // Return\n Send Signature // Command\n$ solana-wallet send-signature // Return\n Indicate Elapsed Time Use the current system time: // Command\n$ solana-wallet send-timestamp // Return\n Or specify some other arbitrary timestamp: // Command\n$ solana-wallet send-timestamp --date 2018-12-24T23:59:00 // Return\n","breadcrumbs":"Appendix » Examples","id":"68","title":"Examples"},"69":{"body":"solana-wallet 0.11.0 USAGE: solana-wallet [OPTIONS] [SUBCOMMAND] FLAGS: -h, --help Prints help information -V, --version Prints version information OPTIONS: -k, --keypair /path/to/id.json -n, --network Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001 --proxy Address of TLS proxy --port Optional rpc-port configuration to connect to non-default nodes --timeout Max seconds to wait to get necessary gossip from the network SUBCOMMANDS: address Get your public key airdrop Request a batch of tokens balance Get your balance cancel Cancel a transfer confirm Confirm transaction by signature deploy Deploy a program get-transaction-count Get current transaction count help Prints this message or the help of the given subcommand(s) pay Send a payment send-signature Send a signature to authorize a transfer send-timestamp Send a timestamp to unlock a transfer solana-wallet-address Get your public key USAGE: solana-wallet address FLAGS: -h, --help Prints help information -V, --version Prints version information solana-wallet-airdrop Request a batch of tokens USAGE: solana-wallet airdrop FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: The number of tokens to request solana-wallet-balance Get your balance USAGE: solana-wallet balance FLAGS: -h, --help Prints help information -V, --version Prints version information solana-wallet-cancel Cancel a transfer USAGE: solana-wallet cancel FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: The process id of the transfer to cancel solana-wallet-confirm Confirm transaction by signature USAGE: solana-wallet confirm FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: The transaction signature to confirm solana-wallet-deploy Deploy a program USAGE: solana-wallet deploy FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: /path/to/program.o solana-wallet-get-transaction-count Get current transaction count USAGE: solana-wallet get-transaction-count FLAGS: -h, --help Prints help information -V, --version Prints version information solana-wallet-pay Send a payment USAGE: solana-wallet pay [FLAGS] [OPTIONS] FLAGS: --cancelable -h, --help Prints help information -V, --version Prints version information OPTIONS: --after A timestamp after which transaction will execute --require-timestamp-from Require timestamp from this third party --require-signature-from ... Any third party signatures required to unlock the tokens ARGS: The pubkey of recipient The number of tokens to send solana-wallet-send-signature Send a signature to authorize a transfer USAGE: solana-wallet send-signature FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: The pubkey of recipient The process id of the transfer to authorize solana-wallet-send-timestamp Send a timestamp to unlock a transfer USAGE: solana-wallet send-timestamp [OPTIONS] FLAGS: -h, --help Prints help information -V, --version Prints version information OPTIONS: --date Optional arbitrary timestamp to apply ARGS: The pubkey of recipient The process id of the transfer to unlock","breadcrumbs":"Appendix » Usage","id":"69","title":"Usage"},"7":{"body":"Proof of History overview","breadcrumbs":"Synchronization » Proof of History","id":"7","title":"Proof of History"},"8":{"body":"Most confusingly, a Proof of History (PoH) is more similar to a Verifiable Delay Function (VDF) than a Proof of Work or Proof of Stake consensus mechanism. The name unfortunately requires some historical context to understand. Proof of History was developed by Anatoly Yakovenko in November of 2017, roughly 6 months before we saw a paper using the term VDF . At that time, it was commonplace to publish new proofs of some desirable property used to build most any blockchain component. Some time shortly after, the crypto community began charting out all the different consensus mechanisms and because most of them started with \"Proof of\", the prefix became synonymous with a \"consensus\" suffix. Proof of History is not a consensus mechanism, but it is used to improve the performance of Solana's Proof of Stake consensus. It is also used to improve the performance of the replication and storage protocols. To minimize confusion, Solana may rebrand PoH to some flavor of the term VDF.","breadcrumbs":"Synchronization » Relationship to consensus mechanisms","id":"8","title":"Relationship to consensus mechanisms"},"9":{"body":"A desirable property of a VDF is that verification time is very fast. Solana's approach to verifying its delay function is proportional to the time it took to create it. Split over a 4000 core GPU, it is sufficiently fast for Solana's needs, but if you asked the authors the paper cited above, they might tell you (and have) that Solana's approach is algorithmically slow it shouldn't be called a VDF. We argue the term VDF should represent the category of verifiable delay functions and not just the subset with certain performance characteristics. Until that's resolved, Solana will likely continue using the term PoH for its application-specific VDF. Another difference between PoH and VDFs used only for tracking duration, is that PoH's hash chain includes hashes of any data the application observed. That data is a double-edged sword. On one side, the data \"proves history\" - that the data most certainly existed before hashes after it. On the side, it means the application can manipulate the hash chain by changing when the data is hashed. The PoH chain therefore does not serve as a good source of randomness whereas a VDF without that data could. Solana's leader selection algorithm (TODO: add link), for example, is derived only from the VDF height and not its hash at that height.","breadcrumbs":"Synchronization » Relationship to VDFs","id":"9","title":"Relationship to VDFs"}},"length":70,"save":true},"fields":["title","body","breadcrumbs"],"index":{"body":{"root":{"0":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},".":{"1":{"1":{".":{"0":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"40":{"tf":1.0},"61":{"tf":6.48074069840786}}},"1":{"/":{"1":{"0":{"0":{"0":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"15":{"tf":1.4142135623730951}}},"1":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"2":{"1":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":2,"docs":{"61":{"tf":1.0},"68":{"tf":3.0}}},"4":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"7":{".":{"0":{".":{"0":{".":{"1":{":":{"8":{"0":{"0":{"1":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":2,"docs":{"61":{"tf":1.0},"68":{"tf":1.7320508075688772}}},"3":{"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"4":{",":{"0":{"0":{"0":{"df":2,"docs":{"28":{"tf":1.0},"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"5":{"0":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"6":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"7":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":2,"docs":{"5":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"8":{"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"9":{"0":{"df":1,"docs":{"61":{"tf":1.0}}},"1":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"2":{".":{"1":{"6":{"8":{".":{"1":{".":{"8":{"8":{":":{"8":{"8":{"9":{"9":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"4":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"1":{"df":1,"docs":{"5":{"tf":1.0}}},"4":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":7,"docs":{"30":{"tf":1.0},"31":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}},"g":{"b":{"df":0,"docs":{},"p":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}},"k":{"df":1,"docs":{"31":{"tf":1.7320508075688772}}},"m":{"b":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}},"t":{"b":{"df":1,"docs":{"31":{"tf":2.0}}},"df":0,"docs":{}}},"2":{".":{"0":{"\"":{",":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"47":{"tf":1.0},"51":{"tf":1.0}}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"1":{"tf":1.0}}},"1":{"7":{"df":1,"docs":{"8":{"tf":1.0}}},"8":{"df":1,"docs":{"68":{"tf":1.7320508075688772}}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"1":{"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"2":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"3":{"4":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"5":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"4":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{},"t":{"2":{"3":{":":{"5":{"9":{":":{"0":{"0":{"df":1,"docs":{"68":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"68":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"5":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":2,"docs":{"52":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{".":{"4":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.0}}},"df":4,"docs":{"13":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"61":{"tf":1.0}}},"3":{"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"2":{"df":2,"docs":{"56":{"tf":1.4142135623730951},"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"4":{"0":{"0":{"0":{"df":1,"docs":{"9":{"tf":1.0}}},"df":1,"docs":{"1":{"tf":1.0}}},"df":3,"docs":{"1":{"tf":1.0},"5":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"df":2,"docs":{"27":{"tf":1.0},"51":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":2,"docs":{"27":{"tf":1.0},"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"df":2,"docs":{"60":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"8":{"df":9,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}},"6":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"4":{"df":4,"docs":{"55":{"tf":1.0},"56":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":3,"docs":{"31":{"tf":1.0},"61":{"tf":1.4142135623730951},"8":{"tf":1.0}}},"7":{"0":{"df":1,"docs":{"61":{"tf":1.0}}},"1":{"0":{",":{"0":{"0":{"0":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"8":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"9":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"9":{"0":{"df":1,"docs":{"15":{"tf":1.0}}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"a":{"'":{"df":1,"docs":{"16":{"tf":1.0}}},"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"28":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"v":{"df":5,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"34":{"tf":1.0},"9":{"tf":1.0}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"47":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"35":{"tf":1.0},"5":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"40":{"tf":1.0}}},":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":15,"docs":{"3":{"tf":1.0},"35":{"tf":2.6457513110645907},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"42":{"tf":2.0},"43":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":2.449489742783178},"58":{"tf":1.0},"60":{"tf":1.0},"63":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"66":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"63":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"63":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"64":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"u":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":2,"docs":{"27":{"tf":1.0},"36":{"tf":1.4142135623730951}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":2,"docs":{"25":{"tf":1.0},"62":{"tf":1.0}}}}}},"d":{"d":{"df":2,"docs":{"19":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"3":{"tf":1.0},"37":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":2.0}}}}}}},"df":3,"docs":{"19":{"tf":1.4142135623730951},"25":{"tf":1.0},"58":{"tf":1.0}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}}},"o":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"27":{"tf":1.0}}},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":3,"docs":{"60":{"tf":1.4142135623730951},"68":{"tf":1.4142135623730951},"69":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"df":4,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"5":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}}}}}}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":3,"docs":{"36":{"tf":2.23606797749979},"37":{"tf":1.0},"43":{"tf":1.0}}},"df":0,"docs":{},"w":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.0},"42":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"27":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"6":{"tf":1.0},"8":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":5,"docs":{"19":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"9":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"i":{"df":2,"docs":{"47":{"tf":1.0},"53":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"46":{"tf":1.0}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":9,"docs":{"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":4,"docs":{"12":{"tf":1.0},"47":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.7320508075688772}}},"df":2,"docs":{"5":{"tf":1.0},"69":{"tf":1.0}}}},"r":{"df":0,"docs":{},"o":{"a":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"5":{"tf":1.0},"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"v":{"df":1,"docs":{"31":{"tf":1.0}}},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}}}}}}}},"r":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":2.23606797749979},"3":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}}},"g":{"df":1,"docs":{"69":{"tf":2.6457513110645907}},"u":{"df":1,"docs":{"9":{"tf":1.0}}}},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}},"y":{"df":4,"docs":{"41":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.7320508075688772},"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"k":{"df":1,"docs":{"9":{"tf":1.0}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":4,"docs":{"40":{"tf":1.7320508075688772},"42":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772},"56":{"tf":1.4142135623730951}}}}},"o":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"37":{"tf":1.0},"42":{"tf":1.0},"56":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"58":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":2,"docs":{"3":{"tf":1.0},"36":{"tf":1.0}}}},"t":{"a":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"31":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"0":{"tf":1.0}}},"df":4,"docs":{"1":{"tf":1.0},"68":{"tf":1.4142135623730951},"69":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"65":{"tf":1.0}}}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"30":{"tf":1.0},"5":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"25":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}}}},"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"13":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":5,"docs":{"40":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"55":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":2.23606797749979}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"n":{"d":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"e":{"df":10,"docs":{"5":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"i":{"c":{"df":2,"docs":{"27":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"1":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"40":{"tf":1.0},"51":{"tf":1.0},"69":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":2,"docs":{"16":{"tf":1.4142135623730951},"17":{"tf":1.0}},"e":{"c":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":4,"docs":{"19":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.4142135623730951},"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":6,"docs":{"17":{"tf":1.0},"19":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"g":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"43":{"tf":1.0}}}},"w":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"27":{"tf":1.0},"62":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.0},"42":{"tf":1.0}}}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":6,"docs":{"1":{"tf":1.0},"23":{"tf":1.0},"3":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}}}},"df":4,"docs":{"55":{"tf":1.0},"56":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"1":{"tf":1.4142135623730951},"10":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":5,"docs":{"10":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"6":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"54":{"tf":1.0},"56":{"tf":1.0}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"16":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0}}},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"f":{"df":1,"docs":{"34":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"a":{"d":{"c":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"34":{"tf":1.0}}},"df":0,"docs":{}}},"df":2,"docs":{"5":{"tf":1.0},"56":{"tf":1.7320508075688772}}}}}},"c":{"/":{"c":{"+":{"+":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"a":{"df":1,"docs":{"34":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"12":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":8,"docs":{"10":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"27":{"tf":1.0},"35":{"tf":1.0},"43":{"tf":1.4142135623730951},"6":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":6,"docs":{"15":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":2.6457513110645907}}}}},"df":0,"docs":{}},"p":{"a":{"c":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"d":{"df":2,"docs":{"20":{"tf":1.0},"28":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"e":{"df":2,"docs":{"16":{"tf":1.4142135623730951},"20":{"tf":1.0}}},"t":{"df":1,"docs":{"15":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"36":{"tf":1.0}}}}},"b":{"c":{"_":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"28":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}},"r":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"1":{"tf":1.0},"27":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"6":{"tf":1.0},"9":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"13":{"tf":2.0},"33":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":1.7320508075688772}}}},"n":{"df":0,"docs":{},"g":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0},"9":{"tf":1.0}}}},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"10":{"tf":1.0}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"0":{"tf":1.0}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"6":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"33":{"tf":1.0},"34":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"s":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"13":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":2,"docs":{"31":{"tf":1.0},"52":{"tf":1.4142135623730951}}}}}},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"9":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":1,"docs":{"67":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":2,"docs":{"34":{"tf":1.0},"43":{"tf":1.0}}},"df":9,"docs":{"29":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"34":{"tf":1.7320508075688772},"35":{"tf":1.7320508075688772},"37":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.0}},"’":{"df":1,"docs":{"33":{"tf":1.0}}}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"23":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.7320508075688772},"34":{"tf":1.4142135623730951}}}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":4,"docs":{"3":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}},"m":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"67":{"tf":1.0},"68":{"tf":3.872983346207417}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"3":{"tf":1.0}}}},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"34":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}},"s":{"df":2,"docs":{"36":{"tf":1.0},"43":{"tf":1.0}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":3,"docs":{"25":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":6,"docs":{"1":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"33":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"58":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"69":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"65":{"tf":1.0},"68":{"tf":1.7320508075688772},"69":{"tf":2.449489742783178}},"e":{"d":{"\"":{",":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\"":{":":{"0":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"50":{"tf":1.0},"54":{"tf":1.0},"58":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"8":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"62":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":2,"docs":{"6":{"tf":1.0},"8":{"tf":2.449489742783178}}}}}},"i":{"d":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"17":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":7,"docs":{"13":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":1.0},"46":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.0},"61":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":12,"docs":{"0":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":6,"docs":{"12":{"tf":1.0},"15":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.0},"44":{"tf":1.0},"9":{"tf":1.0}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":4,"docs":{"23":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"47":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":5,"docs":{"20":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"39":{"tf":1.0},"9":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"27":{"tf":1.4142135623730951},"30":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":8,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.0},"13":{"tf":1.7320508075688772},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":2.6457513110645907},"59":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}}},"p":{"df":0,"docs":{},"u":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"67":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":10,"docs":{"17":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":2.0},"34":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"61":{"tf":1.0},"9":{"tf":1.0}},"e":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"42":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"8":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":2,"docs":{"31":{"tf":1.0},"6":{"tf":1.0}},"i":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":9,"docs":{"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":11,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.0},"4":{"tf":1.4142135623730951},"59":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}}}},"y":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"_":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.0}}}},"b":{"a":{"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":11,"docs":{"13":{"tf":2.449489742783178},"19":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":2.449489742783178},"28":{"tf":1.0},"31":{"tf":1.0},"35":{"tf":1.4142135623730951},"40":{"tf":1.0},"51":{"tf":1.7320508075688772},"52":{"tf":1.4142135623730951},"9":{"tf":2.449489742783178}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.7320508075688772},"30":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"68":{"tf":1.7320508075688772},"69":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}}}},"y":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"i":{"d":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"48":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"37":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"52":{"tf":1.0}}}}}}},"l":{"a":{"df":0,"docs":{},"y":{"df":3,"docs":{"6":{"tf":1.7320508075688772},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"27":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}}}}}}},"n":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"36":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":2,"docs":{"68":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":3,"docs":{"13":{"tf":1.0},"31":{"tf":1.0},"9":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":4,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"42":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":3,"docs":{"0":{"tf":1.0},"19":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":3,"docs":{"1":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"25":{"tf":1.0},"8":{"tf":1.0}}}}}}}},"i":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"34":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"10":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"5":{"tf":2.23606797749979},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"s":{"c":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"13":{"tf":1.0},"35":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"k":{"df":1,"docs":{"20":{"tf":1.0}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"28":{"tf":1.0},"5":{"tf":2.23606797749979},"67":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"0":{"tf":1.0},"31":{"tf":1.0}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"29":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.4142135623730951}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"1":{"tf":1.0}}}},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":2.0}}}}}},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"13":{"tf":1.7320508075688772},"27":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"36":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"g":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}},"1":{"df":1,"docs":{"13":{"tf":1.0}}},"2":{"df":1,"docs":{"13":{"tf":1.0}}},"3":{"df":1,"docs":{"13":{"tf":1.0}}},"4":{"df":1,"docs":{"13":{"tf":1.0}}},"5":{"df":1,"docs":{"13":{"tf":1.0}}},"a":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"19":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"28":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"d":{"2":{"5":{"5":{"1":{"9":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":1,"docs":{"13":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"28":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}}},"g":{"df":2,"docs":{"48":{"tf":1.0},"49":{"tf":1.0}}},"l":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":1,"docs":{"68":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":1,"docs":{"34":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"n":{"c":{"df":0,"docs":{},"o":{"d":{"df":11,"docs":{"13":{"tf":1.0},"42":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":2.23606797749979},"31":{"tf":2.0}}}}}}},"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"48":{"tf":1.0},"49":{"tf":1.0}}}}}}}},"df":1,"docs":{"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"36":{"tf":1.0},"39":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"29":{"tf":1.0},"35":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":10,"docs":{"13":{"tf":2.0},"16":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":2.449489742783178},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"p":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"4":{"tf":1.0}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"28":{"tf":1.0},"40":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"0":{"tf":1.0},"3":{"tf":1.0}}}}}},"t":{"c":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}},"x":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"1":{"tf":1.0},"13":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":18,"docs":{"14":{"tf":1.0},"19":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"68":{"tf":1.0},"9":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"\"":{":":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"\"":{":":{"1":{",":{"\"":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"\"":{":":{"[":{"3":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"5":{"0":{",":{"4":{"8":{",":{"5":{"3":{",":{"4":{"8":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"8":{"4":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"9":{"0":{",":{"2":{"5":{"2":{",":{"1":{"0":{",":{"7":{",":{"2":{"8":{",":{"2":{"4":{"6":{",":{"1":{"4":{"0":{",":{"8":{"8":{",":{"1":{"7":{"7":{",":{"9":{"8":{",":{"8":{"2":{",":{"1":{"0":{",":{"2":{"2":{"7":{",":{"8":{"9":{",":{"8":{"1":{",":{"1":{"8":{",":{"3":{"0":{",":{"1":{"9":{"4":{",":{"1":{"0":{"1":{",":{"1":{"9":{"9":{",":{"1":{"6":{",":{"1":{"1":{",":{"7":{"3":{",":{"1":{"3":{"3":{",":{"2":{"0":{",":{"2":{"4":{"6":{",":{"6":{"2":{",":{"1":{"1":{"4":{",":{"3":{"9":{",":{"2":{"0":{",":{"1":{"1":{"3":{",":{"1":{"8":{"9":{",":{"3":{"2":{",":{"5":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"4":{"7":{",":{"1":{"5":{",":{"3":{"6":{",":{"1":{"0":{"2":{",":{"1":{"6":{"7":{",":{"8":{"3":{",":{"2":{"2":{"5":{",":{"4":{"2":{",":{"1":{"3":{"3":{",":{"1":{"2":{"7":{",":{"8":{"2":{",":{"3":{"4":{",":{"3":{"6":{",":{"2":{"2":{"4":{",":{"2":{"0":{"7":{",":{"1":{"3":{"0":{",":{"1":{"0":{"9":{",":{"2":{"3":{"0":{",":{"2":{"2":{"4":{",":{"1":{"8":{"8":{",":{"1":{"6":{"3":{",":{"3":{"3":{",":{"2":{"1":{"3":{",":{"1":{"3":{",":{"5":{",":{"1":{"1":{"7":{",":{"2":{"1":{"1":{",":{"2":{"5":{"1":{",":{"6":{"5":{",":{"1":{"5":{"9":{",":{"1":{"9":{"7":{",":{"5":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{"df":0,"docs":{},"}":{",":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\"":{":":{"0":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":12,"docs":{"1":{"tf":1.0},"3":{"tf":1.4142135623730951},"33":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"40":{"tf":2.23606797749979},"41":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"56":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"1":{"tf":1.0},"20":{"tf":1.4142135623730951},"42":{"tf":1.0},"9":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"28":{"tf":1.0},"68":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"r":{"df":2,"docs":{"13":{"tf":1.0},"16":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.0}}}}}}}},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.7320508075688772}}}},"r":{"df":1,"docs":{"6":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.7320508075688772}},"l":{"df":1,"docs":{"1":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"51":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"3":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"l":{"df":2,"docs":{"12":{"tf":1.0},"16":{"tf":1.7320508075688772}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.4142135623730951},"3":{"tf":1.0}}}},"d":{"df":3,"docs":{"25":{"tf":1.0},"46":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"13":{"tf":1.0},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}}},"l":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":3.4641016151377544}}},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"13":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"19":{"tf":1.7320508075688772}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":9,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.0},"4":{"tf":1.0},"40":{"tf":1.0},"46":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0}}}}}},"r":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":2,"docs":{"13":{"tf":2.449489742783178},"16":{"tf":1.0}}},"m":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"45":{"tf":1.0},"51":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"1":{"tf":1.0},"25":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"3":{"tf":1.0},"37":{"tf":1.0}},"i":{"df":1,"docs":{"61":{"tf":1.0}}},"n":{"df":0,"docs":{},"o":{"d":{"df":6,"docs":{"10":{"tf":1.7320508075688772},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"3":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":5,"docs":{"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.7320508075688772},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"0":{"tf":1.0},"1":{"tf":1.0}}}}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"4":{"tf":1.4142135623730951},"44":{"tf":1.0},"58":{"tf":1.0}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"1":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.4142135623730951},"15":{"tf":1.0},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.0},"51":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":2,"docs":{"50":{"tf":1.0},"56":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"50":{"tf":1.0},"55":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"50":{"tf":1.0},"57":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":2,"docs":{"50":{"tf":1.0},"58":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"50":{"tf":1.0},"59":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"a":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"5":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"47":{"tf":1.0}},"n":{"df":4,"docs":{"19":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0}}}},"df":1,"docs":{"31":{"tf":1.0}},"e":{"df":1,"docs":{"1":{"tf":1.0}}},"o":{"d":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951}}}}}}},"p":{"df":0,"docs":{},"u":{"df":4,"docs":{"20":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"40":{"tf":1.0},"43":{"tf":2.23606797749979}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"h":{".":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"h":{"df":10,"docs":{"11":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.7320508075688772},"27":{"tf":2.0},"28":{"tf":1.0},"31":{"tf":3.3166247903554},"52":{"tf":1.4142135623730951},"57":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":2.449489742783178}}}}},"df":10,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"69":{"tf":3.3166247903554}},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"6":{"tf":1.7320508075688772},"9":{"tf":1.4142135623730951}}}}}},"l":{"df":0,"docs":{},"p":{"df":1,"docs":{"69":{"tf":4.898979485566356}}}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"25":{"tf":1.0},"6":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"5":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}},"i":{"df":4,"docs":{"28":{"tf":1.0},"7":{"tf":1.4142135623730951},"8":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"35":{"tf":1.0}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"o":{"d":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{":":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{":":{"/":{"/":{"1":{"9":{"2":{".":{"1":{"6":{"8":{".":{"1":{".":{"8":{"8":{":":{"8":{"8":{"9":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"8":{"8":{"9":{"9":{"df":9,"docs":{"48":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"47":{"tf":1.0},"48":{"tf":1.0},"51":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{".":{"df":1,"docs":{"34":{"tf":1.0}}},"d":{"\"":{":":{"1":{"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":7,"docs":{"51":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.7320508075688772}},"e":{"a":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":2.23606797749979},"31":{"tf":4.123105625617661}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":2,"docs":{"35":{"tf":1.0},"51":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"68":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":6,"docs":{"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"8":{"tf":1.4142135623730951}}}}}}},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"3":{"tf":1.0},"36":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"34":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"i":{"c":{"df":2,"docs":{"56":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"56":{"tf":1.0},"69":{"tf":4.69041575982343}}}}}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"31":{"tf":1.0},"43":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"19":{"tf":1.0},"20":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"47":{"tf":1.0}}},"df":0,"docs":{}},"t":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":1,"docs":{"30":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"1":{"tf":1.7320508075688772},"3":{"tf":1.7320508075688772},"36":{"tf":2.0},"37":{"tf":1.4142135623730951},"4":{"tf":1.0},"40":{"tf":1.0},"43":{"tf":1.4142135623730951},"52":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"42":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":9,"docs":{"51":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.0},"59":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.0},"37":{"tf":1.0},"47":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"a":{"c":{"df":4,"docs":{"37":{"tf":1.0},"42":{"tf":1.4142135623730951},"47":{"tf":1.0},"67":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.4142135623730951},"34":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"33":{"tf":1.0},"6":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"51":{"tf":1.0}}},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"41":{"tf":1.0}}}}}}},"t":{"'":{"df":5,"docs":{"13":{"tf":1.0},"27":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":4,"docs":{"31":{"tf":1.0},"37":{"tf":1.4142135623730951},"43":{"tf":1.0},"5":{"tf":1.0}}}}}}}},"j":{".":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"47":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"b":{"df":2,"docs":{"13":{"tf":1.0},"19":{"tf":1.0}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"46":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":4,"docs":{"47":{"tf":1.4142135623730951},"51":{"tf":2.23606797749979},"53":{"tf":1.0},"56":{"tf":1.0}},"r":{"df":0,"docs":{},"p":{"c":{"\"":{":":{"\"":{"2":{".":{"0":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":4,"docs":{"57":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"\"":{":":{"\"":{"2":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"6":{"c":{"b":{"8":{"df":0,"docs":{},"v":{"a":{"a":{"d":{"9":{"3":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"6":{"df":0,"docs":{},"v":{"d":{"8":{"df":0,"docs":{},"p":{"6":{"7":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"q":{"df":0,"docs":{},"z":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"x":{"4":{"7":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"g":{"9":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"1":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"3":{"a":{"2":{"df":0,"docs":{},"g":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"7":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"j":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"y":{"9":{"b":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"5":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"v":{"df":0,"docs":{},"z":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"x":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"9":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"j":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"j":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"8":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"q":{"df":0,"docs":{},"p":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"4":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"3":{"df":0,"docs":{},"z":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"z":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"v":{"6":{"df":0,"docs":{},"u":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"k":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"w":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"7":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"3":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"7":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"9":{"df":0,"docs":{},"j":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"2":{"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"w":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"3":{"df":0,"docs":{},"z":{"6":{"9":{"df":0,"docs":{},"x":{"df":0,"docs":{},"m":{"1":{"df":0,"docs":{},"z":{"df":0,"docs":{},"e":{"3":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"6":{"df":0,"docs":{},"j":{"c":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"0":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"55":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"6":{"8":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"54":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"{":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"\"":{":":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"\"":{":":{"1":{",":{"\"":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"\"":{":":{"[":{"3":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"5":{"0":{",":{"4":{"8":{",":{"5":{"3":{",":{"4":{"8":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"8":{"4":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"9":{"0":{",":{"2":{"5":{"2":{",":{"1":{"0":{",":{"7":{",":{"2":{"8":{",":{"2":{"4":{"6":{",":{"1":{"4":{"0":{",":{"8":{"8":{",":{"1":{"7":{"7":{",":{"9":{"8":{",":{"8":{"2":{",":{"1":{"0":{",":{"2":{"2":{"7":{",":{"8":{"9":{",":{"8":{"1":{",":{"1":{"8":{",":{"3":{"0":{",":{"1":{"9":{"4":{",":{"1":{"0":{"1":{",":{"1":{"9":{"9":{",":{"1":{"6":{",":{"1":{"1":{",":{"7":{"3":{",":{"1":{"3":{"3":{",":{"2":{"0":{",":{"2":{"4":{"6":{",":{"6":{"2":{",":{"1":{"1":{"4":{",":{"3":{"9":{",":{"2":{"0":{",":{"1":{"1":{"3":{",":{"1":{"8":{"9":{",":{"3":{"2":{",":{"5":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"4":{"7":{",":{"1":{"5":{",":{"3":{"6":{",":{"1":{"0":{"2":{",":{"1":{"6":{"7":{",":{"8":{"3":{",":{"2":{"2":{"5":{",":{"4":{"2":{",":{"1":{"3":{"3":{",":{"1":{"2":{"7":{",":{"8":{"2":{",":{"3":{"4":{",":{"3":{"6":{",":{"2":{"2":{"4":{",":{"2":{"0":{"7":{",":{"1":{"3":{"0":{",":{"1":{"0":{"9":{",":{"2":{"3":{"0":{",":{"2":{"2":{"4":{",":{"1":{"8":{"8":{",":{"1":{"6":{"3":{",":{"3":{"3":{",":{"2":{"1":{"3":{",":{"1":{"3":{",":{"5":{",":{"1":{"1":{"7":{",":{"2":{"1":{"1":{",":{"2":{"5":{"1":{",":{"6":{"5":{",":{"1":{"5":{"9":{",":{"1":{"9":{"7":{",":{"5":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{"df":0,"docs":{},"}":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"56":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":5,"docs":{"51":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"25":{"tf":1.0}}}}}}}}},"k":{"df":3,"docs":{"15":{"tf":2.0},"17":{"tf":1.0},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":2,"docs":{"16":{"tf":1.0},"27":{"tf":1.0}}}},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}}},"y":{"df":13,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"35":{"tf":1.4142135623730951},"37":{"tf":1.0},"4":{"tf":1.4142135623730951},"41":{"tf":1.0},"5":{"tf":1.0},"52":{"tf":1.7320508075688772},"63":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"p":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":4,"docs":{"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"4":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"4":{"tf":1.0}}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"35":{"tf":1.0}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"5":{"tf":1.0}},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"l":{".":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"1":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"2":{"df":1,"docs":{"13":{"tf":2.0}}},"3":{"df":1,"docs":{"13":{"tf":2.8284271247461903}}},"4":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"g":{"df":3,"docs":{"1":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"15":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"31":{"tf":1.0},"57":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.7320508075688772},"3":{"tf":1.0}}},"df":13,"docs":{"10":{"tf":2.6457513110645907},"11":{"tf":1.7320508075688772},"12":{"tf":3.1622776601683795},"13":{"tf":3.4641016151377544},"15":{"tf":1.4142135623730951},"16":{"tf":2.23606797749979},"17":{"tf":2.23606797749979},"20":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"4":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":12,"docs":{"13":{"tf":1.7320508075688772},"20":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"3":{"tf":2.23606797749979},"31":{"tf":1.7320508075688772},"45":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0}}}}}},"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"25":{"tf":1.7320508075688772}}}}}},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"47":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"3":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"1":{"tf":1.0}}}},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"19":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"67":{"tf":1.0}}},"k":{"df":2,"docs":{"13":{"tf":1.0},"9":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"37":{"tf":1.4142135623730951},"58":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"31":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"df":1,"docs":{"34":{"tf":1.0}}}}},"o":{"a":{"d":{"df":5,"docs":{"19":{"tf":2.6457513110645907},"34":{"tf":1.0},"35":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"56":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"58":{"tf":1.0}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"15":{"tf":1.0}}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"31":{"tf":1.0},"44":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.0},"39":{"tf":1.0}}}}}},"m":{"a":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"1":{"tf":1.0},"3":{"tf":1.0},"38":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{}}}},"k":{"df":0,"docs":{},"e":{"df":4,"docs":{"1":{"tf":1.0},"19":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"51":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"i":{"df":2,"docs":{"30":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}}}}},"p":{"df":2,"docs":{"39":{"tf":1.0},"41":{"tf":1.0}}},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"3":{"tf":1.0},"51":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"46":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.0}}}}}},"x":{"df":1,"docs":{"69":{"tf":1.0}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"19":{"tf":1.0},"27":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"y":{"b":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}},"df":4,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"15":{"tf":2.6457513110645907},"17":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"n":{"df":3,"docs":{"1":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"0":{"tf":1.0},"1":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"10":{"tf":1.0},"8":{"tf":2.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"36":{"tf":2.23606797749979},"37":{"tf":1.0},"38":{"tf":1.0},"43":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"\"":{":":{"\"":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"51":{"tf":1.0},"55":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"66":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"'":{"df":1,"docs":{"5":{"tf":1.0}}},"df":6,"docs":{"47":{"tf":1.0},"5":{"tf":2.0},"50":{"tf":1.0},"51":{"tf":1.4142135623730951},"58":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"4":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"29":{"tf":1.0}}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"27":{"tf":1.0},"8":{"tf":1.0}}}}},"p":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"4":{"tf":1.0}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":2,"docs":{"10":{"tf":1.0},"20":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":3,"docs":{"40":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"8":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":6,"docs":{"3":{"tf":1.0},"31":{"tf":1.7320508075688772},"5":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0},"8":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"42":{"tf":2.0}}}}},"u":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":7,"docs":{"25":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"43":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.0}}}}}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":2,"docs":{"17":{"tf":1.7320508075688772},"8":{"tf":1.0}}}}},"c":{"df":0,"docs":{},"p":{"df":1,"docs":{"23":{"tf":1.0}}}},"df":3,"docs":{"11":{"tf":1.4142135623730951},"17":{"tf":1.0},"69":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"37":{"tf":1.0},"5":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"d":{"df":9,"docs":{"13":{"tf":1.0},"19":{"tf":1.0},"27":{"tf":1.0},"31":{"tf":1.4142135623730951},"42":{"tf":1.0},"43":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":14,"docs":{"1":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"13":{"tf":2.23606797749979},"15":{"tf":1.0},"17":{"tf":1.0},"20":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"27":{"tf":2.0},"28":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951},"6":{"tf":1.0},"69":{"tf":1.7320508075688772}}}}}}},"w":{"df":7,"docs":{"13":{"tf":1.4142135623730951},"17":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"58":{"tf":1.4142135623730951},"61":{"tf":1.0},"8":{"tf":1.0}}},"x":{"df":0,"docs":{},"t":{"df":5,"docs":{"10":{"tf":1.0},"12":{"tf":1.4142135623730951},"16":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"o":{"d":{"df":0,"docs":{},"e":{"df":15,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":2.0},"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"17":{"tf":2.23606797749979},"23":{"tf":1.0},"25":{"tf":1.7320508075688772},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"47":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"69":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}},"e":{"df":2,"docs":{"57":{"tf":1.0},"59":{"tf":1.0}}}},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"43":{"tf":1.0},"58":{"tf":1.0}}},"h":{"df":1,"docs":{"0":{"tf":1.0}}},"i":{"df":0,"docs":{},"f":{"df":4,"docs":{"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}}},"w":{"df":1,"docs":{"6":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"3":{"tf":1.0}}}},"u":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":1,"docs":{"27":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"df":7,"docs":{"17":{"tf":2.23606797749979},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"56":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}},"df":1,"docs":{"69":{"tf":2.23606797749979}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":5,"docs":{"12":{"tf":1.4142135623730951},"13":{"tf":2.23606797749979},"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}},"c":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":4,"docs":{"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"19":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"c":{"df":5,"docs":{"13":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"5":{"tf":1.0},"62":{"tf":1.0}}},"df":12,"docs":{"13":{"tf":1.0},"15":{"tf":1.0},"19":{"tf":2.0},"20":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"5":{"tf":2.0},"58":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":1.0}}},"p":{"a":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"15":{"tf":1.0},"19":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.7320508075688772}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"19":{"tf":1.0},"28":{"tf":1.0}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"12":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":2.8284271247461903}}}}}}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"16":{"tf":1.0},"28":{"tf":1.0},"51":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"16":{"tf":1.0},"25":{"tf":1.0},"6":{"tf":1.0},"8":{"tf":1.0}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"20":{"tf":1.0},"51":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"9":{"tf":1.0}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":2,"docs":{"25":{"tf":1.0},"7":{"tf":1.0}}}}}}}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"35":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"56":{"tf":1.0}}}}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":2,"docs":{"40":{"tf":1.0},"41":{"tf":1.0}}}},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"8":{"tf":1.0},"9":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":2,"docs":{"3":{"tf":1.0},"40":{"tf":1.0}},"i":{"df":0,"docs":{},"z":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"m":{"df":3,"docs":{"51":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":13,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}}},"s":{"\"":{":":{"[":{"\"":{"2":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"6":{"c":{"b":{"8":{"df":0,"docs":{},"v":{"a":{"a":{"d":{"9":{"3":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"6":{"df":0,"docs":{},"v":{"d":{"8":{"df":0,"docs":{},"p":{"6":{"7":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"q":{"df":0,"docs":{},"z":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"x":{"4":{"7":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"g":{"9":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"1":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"3":{"a":{"2":{"df":0,"docs":{},"g":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"7":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"j":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"y":{"9":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"g":{"df":0,"docs":{},"v":{"df":0,"docs":{},"k":{"df":0,"docs":{},"y":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"h":{"b":{"2":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"n":{"3":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"f":{"df":0,"docs":{},"k":{"df":0,"docs":{},"x":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"h":{"df":0,"docs":{},"p":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"56":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}}},"5":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"v":{"df":0,"docs":{},"z":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"x":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"9":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"j":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"j":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"8":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"q":{"df":0,"docs":{},"p":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"4":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"3":{"df":0,"docs":{},"z":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"z":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"v":{"6":{"df":0,"docs":{},"u":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"k":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"w":{"df":2,"docs":{"54":{"tf":1.0},"58":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"8":{"3":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"t":{"2":{"df":0,"docs":{},"h":{"5":{"df":0,"docs":{},"u":{"1":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"d":{"df":0,"docs":{},"q":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"6":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"y":{"3":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"51":{"tf":1.0},"55":{"tf":1.0},"60":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"m":{"7":{"8":{"c":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"8":{"df":0,"docs":{},"o":{"3":{"df":0,"docs":{},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"h":{"df":0,"docs":{},"x":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"z":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"4":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"df":0,"docs":{},"k":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"x":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"k":{"df":0,"docs":{},"n":{"df":0,"docs":{},"h":{"1":{"2":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"0":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}}},"[":{"6":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"29":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}},"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"t":{"df":3,"docs":{"13":{"tf":1.0},"15":{"tf":1.7320508075688772},"16":{"tf":1.4142135623730951}}}}}},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"34":{"tf":1.0},"35":{"tf":1.0},"43":{"tf":1.0},"6":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"i":{"d":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{".":{"df":0,"docs":{},"o":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.7320508075688772}}}},"y":{"df":2,"docs":{"68":{"tf":2.449489742783178},"69":{"tf":1.7320508075688772}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":10,"docs":{"1":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"27":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.4142135623730951},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"0":{"tf":1.0},"1":{"tf":1.7320508075688772},"19":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"11":{"tf":1.0},"27":{"tf":1.0},"31":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"35":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}}}}},"t":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.0},"35":{"tf":1.4142135623730951}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":1,"docs":{"27":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"19":{"tf":2.6457513110645907},"20":{"tf":1.7320508075688772},"40":{"tf":1.0}}}}}}}},"l":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"23":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"h":{"'":{"df":1,"docs":{"9":{"tf":1.0}}},"df":8,"docs":{"11":{"tf":1.0},"12":{"tf":2.0},"13":{"tf":3.0},"17":{"tf":2.0},"28":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"8":{"tf":1.4142135623730951},"9":{"tf":1.7320508075688772}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"13":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"41":{"tf":1.4142135623730951},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"41":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.0},"29":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":4,"docs":{"28":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.23606797749979}}}},"t":{"df":3,"docs":{"48":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"5":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"d":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"df":10,"docs":{"51":{"tf":1.7320508075688772},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"4":{"tf":1.0}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"8":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":3,"docs":{"13":{"tf":1.0},"27":{"tf":1.4142135623730951},"58":{"tf":1.0}}}}}},"v":{"df":1,"docs":{"13":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":1,"docs":{"13":{"tf":2.0}},"s":{"df":2,"docs":{"34":{"tf":1.0},"58":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":4.795831523312719}}}}},"o":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"25":{"tf":1.4142135623730951}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"68":{"tf":3.0},"69":{"tf":2.449489742783178}}},"df":0,"docs":{}}},"df":9,"docs":{"11":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.0},"21":{"tf":1.0},"25":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":3.0},"58":{"tf":1.0},"69":{"tf":1.7320508075688772}},"i":{"d":{"df":1,"docs":{"68":{"tf":2.23606797749979}}},"df":0,"docs":{}}}}}},"d":{"df":0,"docs":{},"u":{"c":{"df":3,"docs":{"10":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"'":{"df":1,"docs":{"41":{"tf":1.0}}},"_":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"68":{"tf":1.0}}},"df":0,"docs":{}}},"df":15,"docs":{"1":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951},"35":{"tf":2.0},"37":{"tf":2.23606797749979},"38":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.7320508075688772},"43":{"tf":2.23606797749979},"56":{"tf":1.7320508075688772},"58":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.0}}}}}}}}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"13":{"tf":1.0},"19":{"tf":1.0}}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.4142135623730951},"28":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"f":{"df":8,"docs":{"10":{"tf":1.0},"27":{"tf":2.23606797749979},"28":{"tf":2.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.6457513110645907},"7":{"tf":1.4142135623730951},"8":{"tf":2.8284271247461903}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":5,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"5":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}}},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"30":{"tf":1.0},"31":{"tf":1.0},"8":{"tf":1.0}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"e":{"df":3,"docs":{"29":{"tf":1.0},"31":{"tf":1.0},"9":{"tf":1.0}}},"i":{"d":{"df":7,"docs":{"29":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}},"x":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}}},"u":{"b":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":11,"docs":{"3":{"tf":1.4142135623730951},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"52":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"63":{"tf":1.0},"68":{"tf":4.242640687119285},"69":{"tf":3.3166247903554}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":8,"docs":{"27":{"tf":1.0},"3":{"tf":1.4142135623730951},"35":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.0},"52":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.4142135623730951}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":2,"docs":{"49":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"55":{"tf":1.0},"60":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"55":{"tf":1.0},"56":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}}}}}}}},"r":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":5,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"31":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"df":0,"docs":{},"k":{"df":2,"docs":{"15":{"tf":1.0},"16":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"13":{"tf":1.0},"19":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"12":{"tf":1.0},"15":{"tf":1.4142135623730951}}}},"d":{"df":2,"docs":{"35":{"tf":1.4142135623730951},"56":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}},"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"v":{"df":3,"docs":{"60":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.7320508075688772}}}}},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"d":{"df":3,"docs":{"25":{"tf":1.0},"28":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}},"df":1,"docs":{"15":{"tf":1.0}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"25":{"tf":1.0},"46":{"tf":1.0},"53":{"tf":1.0}}}}},"g":{"a":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"1":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"1":{"tf":1.0}}}}},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"z":{"df":0,"docs":{},"v":{"df":1,"docs":{"69":{"tf":1.0}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"i":{"c":{"df":8,"docs":{"25":{"tf":1.4142135623730951},"27":{"tf":2.23606797749979},"28":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":3.0},"5":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":3,"docs":{"13":{"tf":1.7320508075688772},"56":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"50":{"tf":1.0},"60":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":17,"docs":{"35":{"tf":1.0},"47":{"tf":1.0},"51":{"tf":3.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"r":{"df":9,"docs":{"13":{"tf":1.0},"27":{"tf":2.23606797749979},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.7320508075688772},"5":{"tf":1.0},"68":{"tf":2.8284271247461903},"69":{"tf":2.0},"8":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"4":{"tf":1.4142135623730951}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"v":{"df":2,"docs":{"16":{"tf":1.0},"9":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":4,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0},"51":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":1,"docs":{"5":{"tf":1.0}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":16,"docs":{"11":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"36":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.7320508075688772},"57":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"59":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951},"63":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"65":{"tf":1.7320508075688772},"66":{"tf":1.4142135623730951}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"58":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":7,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"68":{"tf":3.872983346207417}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":2.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"o":{"a":{"d":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.0},"12":{"tf":1.4142135623730951},"13":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"n":{"d":{"df":4,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"t":{"df":2,"docs":{"39":{"tf":1.0},"6":{"tf":1.0}}}}},"p":{"c":{"df":7,"docs":{"47":{"tf":1.7320508075688772},"48":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.4142135623730951},"53":{"tf":1.0},"62":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"40":{"tf":1.0}}}},"n":{"df":3,"docs":{"10":{"tf":1.0},"19":{"tf":1.0},"44":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":7,"docs":{"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"43":{"tf":2.23606797749979}}}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":3,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"58":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":11,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":2.449489742783178},"5":{"tf":2.449489742783178},"6":{"tf":1.0}}},"p":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"13":{"tf":1.0}}}},"w":{"df":2,"docs":{"31":{"tf":1.0},"8":{"tf":1.0}}}},"c":{"a":{"df":0,"docs":{},"l":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"25":{"tf":1.0}}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":3,"docs":{"12":{"tf":2.23606797749979},"17":{"tf":1.4142135623730951},"4":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":4,"docs":{"13":{"tf":2.0},"16":{"tf":1.7320508075688772},"42":{"tf":1.4142135623730951},"43":{"tf":1.7320508075688772}},"e":{"c":{"df":1,"docs":{"69":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"d":{"df":10,"docs":{"1":{"tf":1.4142135623730951},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"3":{"tf":1.0},"31":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"4":{"tf":1.0},"68":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"46":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"6":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"d":{"df":3,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"31":{"tf":2.0}}},"df":1,"docs":{"1":{"tf":1.0}},"m":{"df":1,"docs":{"5":{"tf":1.0}}}},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"25":{"tf":1.0},"27":{"tf":1.4142135623730951}}}}}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.0},"11":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"d":{"df":6,"docs":{"16":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"51":{"tf":1.4142135623730951},"68":{"tf":2.23606797749979},"69":{"tf":3.605551275463989}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"50":{"tf":1.0},"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":2,"docs":{"19":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"9":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":4,"docs":{"1":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"51":{"tf":1.0}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"19":{"tf":1.0},"35":{"tf":1.0}}}}}},"h":{"a":{"df":2,"docs":{"52":{"tf":1.0},"6":{"tf":1.0}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"34":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"34":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"44":{"tf":1.0}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":12,"docs":{"1":{"tf":1.0},"11":{"tf":1.4142135623730951},"31":{"tf":2.8284271247461903},"37":{"tf":1.0},"52":{"tf":1.4142135623730951},"54":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"65":{"tf":1.4142135623730951},"68":{"tf":3.605551275463989},"69":{"tf":3.4641016151377544}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"65":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":8,"docs":{"3":{"tf":1.0},"31":{"tf":1.7320508075688772},"52":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.0}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"36":{"tf":1.0},"58":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":3,"docs":{"10":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":10,"docs":{"13":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"4":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0}}}}},"z":{"df":0,"docs":{},"e":{"df":1,"docs":{"28":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}},"df":2,"docs":{"13":{"tf":1.4142135623730951},"29":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":3.4641016151377544},"15":{"tf":1.0},"16":{"tf":2.0},"17":{"tf":1.0},"4":{"tf":1.0}}},"w":{"df":1,"docs":{"9":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"25":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"15":{"tf":1.0},"16":{"tf":1.0},"5":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"o":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"l":{"a":{"df":0,"docs":{},"n":{"a":{"'":{"df":6,"docs":{"1":{"tf":1.4142135623730951},"25":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":2.0}}},"df":22,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":2.23606797749979},"28":{"tf":1.7320508075688772},"3":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":2.0},"35":{"tf":1.0},"4":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.7320508075688772},"5":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.0},"67":{"tf":1.4142135623730951},"68":{"tf":3.872983346207417},"69":{"tf":4.69041575982343},"8":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.0}}}},"v":{"df":1,"docs":{"25":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"27":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"a":{"c":{"df":0,"docs":{},"e":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":8,"docs":{"0":{"tf":1.0},"11":{"tf":1.0},"31":{"tf":2.0},"38":{"tf":1.0},"47":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":5,"docs":{"13":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.0},"68":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"43":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"9":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":2,"docs":{"19":{"tf":1.7320508075688772},"40":{"tf":1.0}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}},"k":{"df":0,"docs":{},"e":{"df":5,"docs":{"10":{"tf":1.0},"12":{"tf":1.0},"15":{"tf":1.0},"29":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951}}}},"n":{"d":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.0},"8":{"tf":1.0}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"50":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":3,"docs":{"13":{"tf":1.0},"37":{"tf":1.7320508075688772},"38":{"tf":1.0}}},"u":{"df":2,"docs":{"54":{"tf":1.0},"58":{"tf":1.4142135623730951}},"s":{"df":1,"docs":{"58":{"tf":1.4142135623730951}}}}},"y":{"df":1,"docs":{"28":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"19":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"26":{"tf":1.0},"27":{"tf":1.7320508075688772},"31":{"tf":1.4142135623730951},"35":{"tf":1.7320508075688772},"8":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":4,"docs":{"27":{"tf":1.0},"29":{"tf":1.4142135623730951},"31":{"tf":1.0},"37":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"16":{"tf":1.0},"19":{"tf":1.0},"28":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"56":{"tf":1.0}}}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"5":{"tf":1.0}}}},"n":{"df":0,"docs":{},"g":{"df":11,"docs":{"11":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"58":{"tf":1.7320508075688772},"60":{"tf":2.0},"61":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"65":{"tf":1.4142135623730951}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"u":{"b":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"(":{"df":1,"docs":{"69":{"tf":1.0}}},"df":1,"docs":{"69":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":1,"docs":{"56":{"tf":1.0}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"27":{"tf":1.0},"31":{"tf":2.0},"34":{"tf":1.0},"62":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":6,"docs":{"50":{"tf":1.0},"62":{"tf":1.7320508075688772},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":1,"docs":{"58":{"tf":1.0}},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"51":{"tf":1.0},"58":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"37":{"tf":1.0},"43":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}},"d":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"25":{"tf":1.0},"27":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{},"x":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":6,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"28":{"tf":1.0},"35":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"5":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"12":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}}},"y":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"28":{"tf":1.0}}}}}}},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"28":{"tf":1.0},"5":{"tf":2.449489742783178}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":1,"docs":{"8":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":5,"docs":{"10":{"tf":1.4142135623730951},"28":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":2.449489742783178},"68":{"tf":1.0}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{":":{":":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"42":{"tf":1.0},"43":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"31":{"tf":1.0},"41":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"34":{"tf":1.4142135623730951}}}}}}},"df":3,"docs":{"12":{"tf":2.449489742783178},"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":3,"docs":{"1":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}}},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":2,"docs":{"2":{"tf":1.0},"4":{"tf":1.0}}}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.4142135623730951},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"19":{"tf":1.4142135623730951}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":4,"docs":{"31":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"43":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"r":{"d":{"df":3,"docs":{"19":{"tf":1.4142135623730951},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":4,"docs":{"27":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"37":{"tf":1.0}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"s":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":3,"docs":{"15":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":1.0}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":2.23606797749979}}}}}}}}}},"u":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":2.23606797749979},"13":{"tf":3.872983346207417},"15":{"tf":1.0},"16":{"tf":1.7320508075688772},"17":{"tf":1.7320508075688772},"3":{"tf":2.23606797749979}}}},"df":1,"docs":{"31":{"tf":1.4142135623730951}},"m":{"df":0,"docs":{},"e":{"df":9,"docs":{"10":{"tf":1.0},"13":{"tf":2.449489742783178},"27":{"tf":1.0},"4":{"tf":1.4142135623730951},"5":{"tf":2.6457513110645907},"6":{"tf":1.0},"68":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"16":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":3,"docs":{"6":{"tf":1.4142135623730951},"68":{"tf":2.6457513110645907},"69":{"tf":3.0}}}}},"df":0,"docs":{}}}}}},"l":{"df":1,"docs":{"69":{"tf":1.0}}},"o":{"d":{"df":0,"docs":{},"o":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":10,"docs":{"29":{"tf":1.0},"3":{"tf":1.0},"35":{"tf":1.4142135623730951},"38":{"tf":1.0},"42":{"tf":1.4142135623730951},"43":{"tf":1.0},"56":{"tf":1.4142135623730951},"60":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"9":{"tf":1.0}}},"l":{"df":2,"docs":{"19":{"tf":1.0},"67":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"28":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"p":{"df":2,"docs":{"1":{"tf":1.7320508075688772},"3":{"tf":1.0}},"u":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}}},"r":{"a":{"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"16":{"tf":1.0},"3":{"tf":1.0},"9":{"tf":1.0}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":25,"docs":{"1":{"tf":2.449489742783178},"12":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.0},"3":{"tf":2.449489742783178},"36":{"tf":1.0},"37":{"tf":2.0},"39":{"tf":1.0},"40":{"tf":2.23606797749979},"41":{"tf":1.0},"43":{"tf":1.7320508075688772},"44":{"tf":1.0},"5":{"tf":1.7320508075688772},"52":{"tf":1.0},"54":{"tf":2.0},"58":{"tf":3.0},"59":{"tf":1.0},"6":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.7320508075688772},"65":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"69":{"tf":3.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"68":{"tf":2.449489742783178},"69":{"tf":3.0}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"t":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"25":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":2,"docs":{"13":{"tf":1.0},"25":{"tf":1.0}}}},"i":{"df":1,"docs":{"5":{"tf":1.0}}},"u":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":1,"docs":{"54":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"27":{"tf":1.0},"5":{"tf":1.4142135623730951},"6":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"6":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"u":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}}},"w":{"df":0,"docs":{},"o":{"df":4,"docs":{"13":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0},"5":{"tf":1.0}}}},"x":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"68":{"tf":3.3166247903554}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":10,"docs":{"37":{"tf":1.0},"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"i":{"c":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"10":{"tf":1.0},"30":{"tf":1.0}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"5":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"51":{"tf":1.0}}}},"t":{"df":4,"docs":{"19":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"3":{"tf":1.0}}}},"k":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"58":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":2.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":1,"docs":{"59":{"tf":1.0}}}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"65":{"tf":1.0},"66":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.0},"9":{"tf":1.0}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}},"p":{"df":8,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"11":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"40":{"tf":1.0},"5":{"tf":1.0}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}},"s":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":3.4641016151377544}}}},"d":{"df":2,"docs":{"27":{"tf":1.0},"30":{"tf":1.0}}},"df":23,"docs":{"1":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.8284271247461903},"35":{"tf":1.0},"4":{"tf":2.0},"42":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.4142135623730951},"5":{"tf":1.7320508075688772},"51":{"tf":1.0},"6":{"tf":2.23606797749979},"62":{"tf":1.0},"68":{"tf":1.0},"8":{"tf":2.0},"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":6,"docs":{"37":{"tf":1.0},"40":{"tf":1.0},"56":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":3,"docs":{"35":{"tf":1.0},"4":{"tf":1.0},"42":{"tf":1.7320508075688772}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"'":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"29":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":14,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":2.23606797749979},"15":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"22":{"tf":1.0},"25":{"tf":2.0},"27":{"tf":1.7320508075688772},"29":{"tf":1.7320508075688772},"3":{"tf":1.4142135623730951},"31":{"tf":2.0},"4":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":2,"docs":{"31":{"tf":1.0},"51":{"tf":1.0}}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"f":{"df":3,"docs":{"6":{"tf":1.0},"8":{"tf":1.7320508075688772},"9":{"tf":2.8284271247461903}}}},"df":3,"docs":{"16":{"tf":1.0},"17":{"tf":1.0},"69":{"tf":3.3166247903554}},"e":{"c":{"<":{"df":0,"docs":{},"u":{"8":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}},"f":{"df":5,"docs":{"1":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":7,"docs":{"28":{"tf":1.4142135623730951},"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":1.0},"6":{"tf":2.0},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"42":{"tf":1.0},"69":{"tf":4.69041575982343}}}}}}}},"i":{"a":{"df":2,"docs":{"11":{"tf":1.0},"12":{"tf":1.0}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"13":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0}}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":1.4142135623730951},"13":{"tf":2.449489742783178},"15":{"tf":1.7320508075688772},"16":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.0},"69":{"tf":1.0}}}},"l":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"68":{"tf":1.0}}},"df":3,"docs":{"67":{"tf":1.0},"68":{"tf":3.872983346207417},"69":{"tf":4.69041575982343}}}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"r":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"h":{"/":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"19":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":2.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":2.23606797749979}}}}},"y":{"df":5,"docs":{"19":{"tf":1.0},"28":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.7320508075688772},"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"'":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.0}}}},"r":{"df":1,"docs":{"5":{"tf":1.0}}}},"b":{"3":{".":{"df":0,"docs":{},"j":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"49":{"tf":1.0},"50":{"tf":1.0},"62":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"15":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"l":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":2,"docs":{"20":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"37":{"tf":1.0},"5":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"37":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"40":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"d":{"df":1,"docs":{"3":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":4,"docs":{"25":{"tf":1.0},"29":{"tf":1.0},"44":{"tf":1.0},"8":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":3,"docs":{"20":{"tf":1.0},"35":{"tf":1.4142135623730951},"58":{"tf":1.0}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"33":{"tf":1.0},"35":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"s":{":":{"/":{"/":{"<":{"a":{"d":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"8":{"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"df":10,"docs":{"13":{"tf":1.7320508075688772},"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}},"x":{"df":1,"docs":{"13":{"tf":1.0}}}},"y":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"'":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"z":{"df":2,"docs":{"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":1,"docs":{"43":{"tf":1.0}}}}}}}},"breadcrumbs":{"root":{"0":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},".":{"1":{"1":{".":{"0":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"40":{"tf":1.0},"61":{"tf":6.48074069840786}}},"1":{"/":{"1":{"0":{"0":{"0":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"15":{"tf":1.4142135623730951}}},"1":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"2":{"1":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":2,"docs":{"61":{"tf":1.0},"68":{"tf":3.0}}},"4":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"7":{".":{"0":{".":{"0":{".":{"1":{":":{"8":{"0":{"0":{"1":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":2,"docs":{"61":{"tf":1.0},"68":{"tf":1.7320508075688772}}},"3":{"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"4":{",":{"0":{"0":{"0":{"df":2,"docs":{"28":{"tf":1.0},"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"5":{"0":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"6":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"7":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":2,"docs":{"5":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"8":{"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"9":{"0":{"df":1,"docs":{"61":{"tf":1.0}}},"1":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"2":{".":{"1":{"6":{"8":{".":{"1":{".":{"8":{"8":{":":{"8":{"8":{"9":{"9":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"4":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"1":{"df":1,"docs":{"5":{"tf":1.0}}},"4":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":7,"docs":{"30":{"tf":1.0},"31":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}},"g":{"b":{"df":0,"docs":{},"p":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}},"k":{"df":1,"docs":{"31":{"tf":1.7320508075688772}}},"m":{"b":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}},"t":{"b":{"df":1,"docs":{"31":{"tf":2.0}}},"df":0,"docs":{}}},"2":{".":{"0":{"\"":{",":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"47":{"tf":1.0},"51":{"tf":1.0}}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"1":{"tf":1.0}}},"1":{"7":{"df":1,"docs":{"8":{"tf":1.0}}},"8":{"df":1,"docs":{"68":{"tf":1.7320508075688772}}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"1":{"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"2":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"3":{"4":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"5":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"4":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{},"t":{"2":{"3":{":":{"5":{"9":{":":{"0":{"0":{"df":1,"docs":{"68":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"68":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"5":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":2,"docs":{"52":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{".":{"4":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.0}}},"df":4,"docs":{"13":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"61":{"tf":1.0}}},"3":{"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"2":{"df":2,"docs":{"56":{"tf":1.4142135623730951},"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"4":{"0":{"0":{"0":{"df":1,"docs":{"9":{"tf":1.0}}},"df":1,"docs":{"1":{"tf":1.0}}},"df":3,"docs":{"1":{"tf":1.0},"5":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"df":2,"docs":{"27":{"tf":1.0},"51":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":2,"docs":{"27":{"tf":1.0},"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"df":2,"docs":{"60":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"8":{"df":9,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}},"6":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"4":{"df":4,"docs":{"55":{"tf":1.0},"56":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":3,"docs":{"31":{"tf":1.0},"61":{"tf":1.4142135623730951},"8":{"tf":1.0}}},"7":{"0":{"df":1,"docs":{"61":{"tf":1.0}}},"1":{"0":{",":{"0":{"0":{"0":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"8":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"9":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"9":{"0":{"df":1,"docs":{"15":{"tf":1.0}}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"a":{"'":{"df":1,"docs":{"16":{"tf":1.0}}},"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"28":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"v":{"df":5,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"34":{"tf":1.0},"9":{"tf":1.0}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"47":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"35":{"tf":1.0},"5":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"40":{"tf":1.0}}},":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":15,"docs":{"3":{"tf":1.0},"35":{"tf":2.6457513110645907},"37":{"tf":1.4142135623730951},"38":{"tf":2.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"42":{"tf":2.0},"43":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":2.449489742783178},"58":{"tf":1.0},"60":{"tf":1.0},"63":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"66":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"63":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"63":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"64":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"u":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":2,"docs":{"27":{"tf":1.0},"36":{"tf":1.4142135623730951}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":2,"docs":{"25":{"tf":1.0},"62":{"tf":1.0}}}}}},"d":{"d":{"df":2,"docs":{"19":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"3":{"tf":1.0},"37":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":2.0}}}}}}},"df":3,"docs":{"19":{"tf":1.4142135623730951},"25":{"tf":1.0},"58":{"tf":1.0}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}}},"o":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"27":{"tf":1.0}}},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":3,"docs":{"60":{"tf":1.4142135623730951},"68":{"tf":1.4142135623730951},"69":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"df":4,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"5":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}}}}}}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":3,"docs":{"36":{"tf":2.23606797749979},"37":{"tf":1.0},"43":{"tf":1.0}}},"df":0,"docs":{},"w":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.0},"42":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"27":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"6":{"tf":1.0},"8":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":5,"docs":{"19":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"9":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"i":{"df":2,"docs":{"47":{"tf":1.4142135623730951},"53":{"tf":1.4142135623730951}}},"p":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":24,"docs":{"46":{"tf":1.4142135623730951},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":9,"docs":{"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":4,"docs":{"12":{"tf":1.0},"47":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.7320508075688772}}},"df":2,"docs":{"5":{"tf":1.0},"69":{"tf":1.0}}}},"r":{"df":0,"docs":{},"o":{"a":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"5":{"tf":1.0},"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"v":{"df":1,"docs":{"31":{"tf":1.0}}},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}}}}}}}},"r":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":2.23606797749979},"3":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}}},"g":{"df":1,"docs":{"69":{"tf":2.6457513110645907}},"u":{"df":1,"docs":{"9":{"tf":1.0}}}},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}},"y":{"df":4,"docs":{"41":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.7320508075688772},"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"k":{"df":1,"docs":{"9":{"tf":1.0}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":4,"docs":{"40":{"tf":1.7320508075688772},"42":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772},"56":{"tf":1.4142135623730951}}}}},"o":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"37":{"tf":1.0},"42":{"tf":1.0},"56":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"58":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":2,"docs":{"3":{"tf":1.0},"36":{"tf":1.0}}}},"t":{"a":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"31":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"0":{"tf":1.0}}},"df":4,"docs":{"1":{"tf":1.0},"68":{"tf":1.4142135623730951},"69":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"65":{"tf":1.0}}}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"30":{"tf":1.0},"5":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"25":{"tf":1.7320508075688772},"26":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0}}}},"df":0,"docs":{}}},"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}}}},"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"13":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":5,"docs":{"40":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"55":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":2.23606797749979}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"n":{"d":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"e":{"df":10,"docs":{"5":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"i":{"c":{"df":2,"docs":{"27":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"1":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"40":{"tf":1.0},"51":{"tf":1.0},"69":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":2,"docs":{"16":{"tf":1.4142135623730951},"17":{"tf":1.0}},"e":{"c":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":4,"docs":{"19":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.4142135623730951},"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":6,"docs":{"17":{"tf":1.0},"19":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"g":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"43":{"tf":1.0}}}},"w":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"27":{"tf":1.0},"62":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.0},"42":{"tf":1.0}}}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":6,"docs":{"1":{"tf":1.0},"23":{"tf":1.0},"3":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}}}},"df":4,"docs":{"55":{"tf":1.0},"56":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"1":{"tf":1.4142135623730951},"10":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":5,"docs":{"10":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"6":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"54":{"tf":1.0},"56":{"tf":1.0}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"16":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0}}},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"f":{"df":1,"docs":{"34":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"a":{"d":{"c":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"34":{"tf":1.0}}},"df":0,"docs":{}}},"df":2,"docs":{"5":{"tf":1.0},"56":{"tf":1.7320508075688772}}}}}},"c":{"/":{"c":{"+":{"+":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"a":{"df":1,"docs":{"34":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"12":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":8,"docs":{"10":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"27":{"tf":1.0},"35":{"tf":1.0},"43":{"tf":1.4142135623730951},"6":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":6,"docs":{"15":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":2.6457513110645907}}}}},"df":0,"docs":{}},"p":{"a":{"c":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"d":{"df":2,"docs":{"20":{"tf":1.0},"28":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"e":{"df":2,"docs":{"16":{"tf":1.4142135623730951},"20":{"tf":1.0}}},"t":{"df":1,"docs":{"15":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"36":{"tf":1.0}}}}},"b":{"c":{"_":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"28":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}},"r":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"1":{"tf":1.0},"27":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"6":{"tf":1.0},"9":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":14,"docs":{"13":{"tf":2.0},"33":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":1.7320508075688772}}}},"n":{"df":0,"docs":{},"g":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0},"9":{"tf":1.0}}}},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"10":{"tf":1.0}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"0":{"tf":1.0}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"6":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"33":{"tf":1.0},"34":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"s":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"13":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":2,"docs":{"31":{"tf":1.0},"52":{"tf":1.4142135623730951}}}}}},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"9":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":1,"docs":{"67":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":2,"docs":{"34":{"tf":1.0},"43":{"tf":1.0}}},"df":9,"docs":{"29":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"34":{"tf":2.0},"35":{"tf":1.7320508075688772},"37":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.0}},"’":{"df":1,"docs":{"33":{"tf":1.0}}}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"23":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.7320508075688772},"34":{"tf":1.4142135623730951}}}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":4,"docs":{"3":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}},"m":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"67":{"tf":1.0},"68":{"tf":3.872983346207417}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"3":{"tf":1.0}}}},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"34":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}},"s":{"df":2,"docs":{"36":{"tf":1.0},"43":{"tf":1.0}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":3,"docs":{"25":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":6,"docs":{"1":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"33":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"58":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"69":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"65":{"tf":1.0},"68":{"tf":1.7320508075688772},"69":{"tf":2.449489742783178}},"e":{"d":{"\"":{",":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\"":{":":{"0":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"50":{"tf":1.0},"54":{"tf":1.4142135623730951},"58":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"8":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"62":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":2,"docs":{"6":{"tf":1.0},"8":{"tf":2.6457513110645907}}}}}},"i":{"d":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"17":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":7,"docs":{"13":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":1.0},"46":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.0},"61":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":12,"docs":{"0":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":6,"docs":{"12":{"tf":1.0},"15":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.0},"44":{"tf":1.0},"9":{"tf":1.0}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":4,"docs":{"23":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"47":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":5,"docs":{"20":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"39":{"tf":1.0},"9":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"27":{"tf":1.4142135623730951},"30":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":8,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.0},"13":{"tf":1.7320508075688772},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":2.6457513110645907},"59":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}}},"p":{"df":0,"docs":{},"u":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"67":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":10,"docs":{"17":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":2.0},"34":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"61":{"tf":1.0},"9":{"tf":1.0}},"e":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"42":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"8":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":2,"docs":{"31":{"tf":1.0},"6":{"tf":1.0}},"i":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":9,"docs":{"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":11,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"30":{"tf":1.0},"4":{"tf":1.4142135623730951},"59":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}}}},"y":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"_":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.0}}}},"b":{"a":{"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":11,"docs":{"13":{"tf":2.449489742783178},"19":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":2.449489742783178},"28":{"tf":1.0},"31":{"tf":1.0},"35":{"tf":1.4142135623730951},"40":{"tf":1.0},"51":{"tf":1.7320508075688772},"52":{"tf":1.4142135623730951},"9":{"tf":2.449489742783178}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.7320508075688772},"30":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"68":{"tf":1.7320508075688772},"69":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}}}},"y":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"i":{"d":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"48":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"37":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}}}}}},"l":{"a":{"df":0,"docs":{},"y":{"df":3,"docs":{"6":{"tf":1.7320508075688772},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"27":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}}}}}}},"n":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"36":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":2,"docs":{"68":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":3,"docs":{"13":{"tf":1.0},"31":{"tf":1.0},"9":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":4,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"42":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":3,"docs":{"0":{"tf":1.0},"19":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":3,"docs":{"1":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"25":{"tf":1.0},"8":{"tf":1.0}}}}}}}},"i":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"34":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"10":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"5":{"tf":2.23606797749979},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"s":{"c":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"13":{"tf":1.0},"35":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"k":{"df":1,"docs":{"20":{"tf":1.0}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"28":{"tf":1.0},"5":{"tf":2.23606797749979},"67":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"0":{"tf":1.0},"31":{"tf":1.0}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"29":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.4142135623730951}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"1":{"tf":1.0}}}},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":2.0}}}}}},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"13":{"tf":1.7320508075688772},"27":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"36":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"g":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}},"1":{"df":1,"docs":{"13":{"tf":1.0}}},"2":{"df":1,"docs":{"13":{"tf":1.0}}},"3":{"df":1,"docs":{"13":{"tf":1.0}}},"4":{"df":1,"docs":{"13":{"tf":1.0}}},"5":{"df":1,"docs":{"13":{"tf":1.0}}},"a":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"19":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"28":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"d":{"2":{"5":{"5":{"1":{"9":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":1,"docs":{"13":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"28":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}}},"g":{"df":2,"docs":{"48":{"tf":1.0},"49":{"tf":1.0}}},"l":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":1,"docs":{"68":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":1,"docs":{"34":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"n":{"c":{"df":0,"docs":{},"o":{"d":{"df":11,"docs":{"13":{"tf":1.0},"42":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":2.23606797749979},"31":{"tf":2.0}}}}}}},"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"48":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951}}}}}}}},"df":1,"docs":{"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"36":{"tf":1.0},"39":{"tf":1.7320508075688772}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"29":{"tf":1.0},"35":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":10,"docs":{"13":{"tf":2.0},"16":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":2.449489742783178},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.7320508075688772},"57":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"p":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"4":{"tf":1.0}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"28":{"tf":1.0},"40":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"0":{"tf":1.0},"3":{"tf":1.0}}}}}},"t":{"c":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}},"x":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"1":{"tf":1.0},"13":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":18,"docs":{"14":{"tf":1.4142135623730951},"19":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"68":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"\"":{":":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"\"":{":":{"1":{",":{"\"":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"\"":{":":{"[":{"3":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"5":{"0":{",":{"4":{"8":{",":{"5":{"3":{",":{"4":{"8":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"8":{"4":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"9":{"0":{",":{"2":{"5":{"2":{",":{"1":{"0":{",":{"7":{",":{"2":{"8":{",":{"2":{"4":{"6":{",":{"1":{"4":{"0":{",":{"8":{"8":{",":{"1":{"7":{"7":{",":{"9":{"8":{",":{"8":{"2":{",":{"1":{"0":{",":{"2":{"2":{"7":{",":{"8":{"9":{",":{"8":{"1":{",":{"1":{"8":{",":{"3":{"0":{",":{"1":{"9":{"4":{",":{"1":{"0":{"1":{",":{"1":{"9":{"9":{",":{"1":{"6":{",":{"1":{"1":{",":{"7":{"3":{",":{"1":{"3":{"3":{",":{"2":{"0":{",":{"2":{"4":{"6":{",":{"6":{"2":{",":{"1":{"1":{"4":{",":{"3":{"9":{",":{"2":{"0":{",":{"1":{"1":{"3":{",":{"1":{"8":{"9":{",":{"3":{"2":{",":{"5":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"4":{"7":{",":{"1":{"5":{",":{"3":{"6":{",":{"1":{"0":{"2":{",":{"1":{"6":{"7":{",":{"8":{"3":{",":{"2":{"2":{"5":{",":{"4":{"2":{",":{"1":{"3":{"3":{",":{"1":{"2":{"7":{",":{"8":{"2":{",":{"3":{"4":{",":{"3":{"6":{",":{"2":{"2":{"4":{",":{"2":{"0":{"7":{",":{"1":{"3":{"0":{",":{"1":{"0":{"9":{",":{"2":{"3":{"0":{",":{"2":{"2":{"4":{",":{"1":{"8":{"8":{",":{"1":{"6":{"3":{",":{"3":{"3":{",":{"2":{"1":{"3":{",":{"1":{"3":{",":{"5":{",":{"1":{"1":{"7":{",":{"2":{"1":{"1":{",":{"2":{"5":{"1":{",":{"6":{"5":{",":{"1":{"5":{"9":{",":{"1":{"9":{"7":{",":{"5":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{"df":0,"docs":{},"}":{",":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\"":{":":{"0":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":12,"docs":{"1":{"tf":1.0},"3":{"tf":1.4142135623730951},"33":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"40":{"tf":2.449489742783178},"41":{"tf":1.4142135623730951},"43":{"tf":1.0},"5":{"tf":1.0},"56":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"1":{"tf":1.0},"20":{"tf":1.4142135623730951},"42":{"tf":1.0},"9":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"28":{"tf":1.0},"68":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"r":{"df":2,"docs":{"13":{"tf":1.0},"16":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.0}}}}}}}},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.7320508075688772}}}},"r":{"df":1,"docs":{"6":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.7320508075688772}},"l":{"df":1,"docs":{"1":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"51":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"3":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"l":{"df":2,"docs":{"12":{"tf":1.0},"16":{"tf":1.7320508075688772}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.4142135623730951},"3":{"tf":1.0}}}},"d":{"df":3,"docs":{"25":{"tf":1.0},"46":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"13":{"tf":1.0},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}}},"l":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":3.4641016151377544}}},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"13":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"19":{"tf":1.7320508075688772}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":9,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.0},"4":{"tf":1.0},"40":{"tf":1.0},"46":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0}}}}}},"r":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":2,"docs":{"13":{"tf":2.6457513110645907},"16":{"tf":1.0}}},"m":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"45":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"63":{"tf":1.0},"65":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"1":{"tf":1.0},"25":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"3":{"tf":1.0},"37":{"tf":1.0}},"i":{"df":1,"docs":{"61":{"tf":1.0}}},"n":{"df":0,"docs":{},"o":{"d":{"df":10,"docs":{"10":{"tf":1.7320508075688772},"18":{"tf":1.4142135623730951},"19":{"tf":1.0},"20":{"tf":1.7320508075688772},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"27":{"tf":1.7320508075688772},"3":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":5,"docs":{"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.7320508075688772},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"0":{"tf":1.0},"1":{"tf":1.0}}}}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"4":{"tf":1.7320508075688772},"44":{"tf":1.4142135623730951},"58":{"tf":1.0}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"1":{"tf":1.0},"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"13":{"tf":1.4142135623730951},"15":{"tf":1.0},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.0},"51":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":2,"docs":{"50":{"tf":1.0},"56":{"tf":1.4142135623730951}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"50":{"tf":1.0},"55":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"50":{"tf":1.0},"57":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":2,"docs":{"50":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"50":{"tf":1.0},"59":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"a":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"5":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"47":{"tf":1.0}},"n":{"df":4,"docs":{"19":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0}}}},"df":1,"docs":{"31":{"tf":1.0}},"e":{"df":1,"docs":{"1":{"tf":1.0}}},"o":{"d":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951}}}}}}},"p":{"df":0,"docs":{},"u":{"df":4,"docs":{"20":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"40":{"tf":1.0},"43":{"tf":2.23606797749979}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"h":{".":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"h":{"df":10,"docs":{"11":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.7320508075688772},"27":{"tf":2.0},"28":{"tf":1.0},"31":{"tf":3.3166247903554},"52":{"tf":1.4142135623730951},"57":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":2.449489742783178}}}}},"df":10,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"69":{"tf":3.3166247903554}},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"6":{"tf":1.7320508075688772},"9":{"tf":1.4142135623730951}}}}}},"l":{"df":0,"docs":{},"p":{"df":1,"docs":{"69":{"tf":4.898979485566356}}}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"25":{"tf":1.0},"6":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"5":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}},"i":{"df":4,"docs":{"28":{"tf":1.0},"7":{"tf":1.7320508075688772},"8":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"35":{"tf":1.0}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"o":{"d":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{":":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{":":{"/":{"/":{"1":{"9":{"2":{".":{"1":{"6":{"8":{".":{"1":{".":{"8":{"8":{":":{"8":{"8":{"9":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"8":{"8":{"9":{"9":{"df":9,"docs":{"48":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"47":{"tf":1.0},"48":{"tf":1.4142135623730951},"51":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{".":{"df":1,"docs":{"34":{"tf":1.0}}},"d":{"\"":{":":{"1":{"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":7,"docs":{"51":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.7320508075688772}},"e":{"a":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":2.23606797749979},"31":{"tf":4.123105625617661}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":2,"docs":{"35":{"tf":1.0},"51":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"68":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":6,"docs":{"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"8":{"tf":1.4142135623730951}}}}}}},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"3":{"tf":1.0},"36":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"34":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"i":{"c":{"df":2,"docs":{"56":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"56":{"tf":1.0},"69":{"tf":4.69041575982343}}}}}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"31":{"tf":1.0},"43":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"19":{"tf":1.0},"20":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"47":{"tf":1.0}}},"df":0,"docs":{}},"t":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":1,"docs":{"30":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"1":{"tf":1.7320508075688772},"3":{"tf":1.7320508075688772},"36":{"tf":2.0},"37":{"tf":1.4142135623730951},"4":{"tf":1.0},"40":{"tf":1.0},"43":{"tf":1.4142135623730951},"52":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"42":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":9,"docs":{"51":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.0},"59":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.4142135623730951},"37":{"tf":1.0},"47":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"a":{"c":{"df":4,"docs":{"37":{"tf":1.0},"42":{"tf":1.7320508075688772},"47":{"tf":1.0},"67":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.4142135623730951},"34":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"6":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"51":{"tf":1.0}}},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"41":{"tf":1.4142135623730951}}}}}}},"t":{"'":{"df":5,"docs":{"13":{"tf":1.0},"27":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":4,"docs":{"31":{"tf":1.0},"37":{"tf":1.4142135623730951},"43":{"tf":1.0},"5":{"tf":1.0}}}}}}}},"j":{".":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"47":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"b":{"df":2,"docs":{"13":{"tf":1.0},"19":{"tf":1.0}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"46":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":4,"docs":{"47":{"tf":1.7320508075688772},"51":{"tf":2.23606797749979},"53":{"tf":1.4142135623730951},"56":{"tf":1.0}},"r":{"df":0,"docs":{},"p":{"c":{"\"":{":":{"\"":{"2":{".":{"0":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":4,"docs":{"57":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"\"":{":":{"\"":{"2":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"6":{"c":{"b":{"8":{"df":0,"docs":{},"v":{"a":{"a":{"d":{"9":{"3":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"6":{"df":0,"docs":{},"v":{"d":{"8":{"df":0,"docs":{},"p":{"6":{"7":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"q":{"df":0,"docs":{},"z":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"x":{"4":{"7":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"g":{"9":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"1":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"3":{"a":{"2":{"df":0,"docs":{},"g":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"7":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"j":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"y":{"9":{"b":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"5":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"v":{"df":0,"docs":{},"z":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"x":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"9":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"j":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"j":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"8":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"q":{"df":0,"docs":{},"p":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"4":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"3":{"df":0,"docs":{},"z":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"z":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"v":{"6":{"df":0,"docs":{},"u":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"k":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"w":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"7":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"3":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"7":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"9":{"df":0,"docs":{},"j":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"2":{"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"w":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"3":{"df":0,"docs":{},"z":{"6":{"9":{"df":0,"docs":{},"x":{"df":0,"docs":{},"m":{"1":{"df":0,"docs":{},"z":{"df":0,"docs":{},"e":{"3":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"6":{"df":0,"docs":{},"j":{"c":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"0":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"55":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"6":{"8":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"54":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"{":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"\"":{":":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"\"":{":":{"1":{",":{"\"":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"\"":{":":{"[":{"3":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"5":{"0":{",":{"4":{"8":{",":{"5":{"3":{",":{"4":{"8":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"8":{"4":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"9":{"0":{",":{"2":{"5":{"2":{",":{"1":{"0":{",":{"7":{",":{"2":{"8":{",":{"2":{"4":{"6":{",":{"1":{"4":{"0":{",":{"8":{"8":{",":{"1":{"7":{"7":{",":{"9":{"8":{",":{"8":{"2":{",":{"1":{"0":{",":{"2":{"2":{"7":{",":{"8":{"9":{",":{"8":{"1":{",":{"1":{"8":{",":{"3":{"0":{",":{"1":{"9":{"4":{",":{"1":{"0":{"1":{",":{"1":{"9":{"9":{",":{"1":{"6":{",":{"1":{"1":{",":{"7":{"3":{",":{"1":{"3":{"3":{",":{"2":{"0":{",":{"2":{"4":{"6":{",":{"6":{"2":{",":{"1":{"1":{"4":{",":{"3":{"9":{",":{"2":{"0":{",":{"1":{"1":{"3":{",":{"1":{"8":{"9":{",":{"3":{"2":{",":{"5":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"4":{"7":{",":{"1":{"5":{",":{"3":{"6":{",":{"1":{"0":{"2":{",":{"1":{"6":{"7":{",":{"8":{"3":{",":{"2":{"2":{"5":{",":{"4":{"2":{",":{"1":{"3":{"3":{",":{"1":{"2":{"7":{",":{"8":{"2":{",":{"3":{"4":{",":{"3":{"6":{",":{"2":{"2":{"4":{",":{"2":{"0":{"7":{",":{"1":{"3":{"0":{",":{"1":{"0":{"9":{",":{"2":{"3":{"0":{",":{"2":{"2":{"4":{",":{"1":{"8":{"8":{",":{"1":{"6":{"3":{",":{"3":{"3":{",":{"2":{"1":{"3":{",":{"1":{"3":{",":{"5":{",":{"1":{"1":{"7":{",":{"2":{"1":{"1":{",":{"2":{"5":{"1":{",":{"6":{"5":{",":{"1":{"5":{"9":{",":{"1":{"9":{"7":{",":{"5":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{"df":0,"docs":{},"}":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"56":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":5,"docs":{"51":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"24":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"25":{"tf":1.0}}}}}}}}},"k":{"df":3,"docs":{"15":{"tf":2.0},"17":{"tf":1.0},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":2,"docs":{"16":{"tf":1.0},"27":{"tf":1.0}}}},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}}},"y":{"df":13,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"35":{"tf":1.4142135623730951},"37":{"tf":1.0},"4":{"tf":1.4142135623730951},"41":{"tf":1.0},"5":{"tf":1.0},"52":{"tf":1.7320508075688772},"63":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"p":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":4,"docs":{"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"4":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"4":{"tf":1.0}}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"35":{"tf":1.0}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"5":{"tf":1.0}},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"l":{".":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"1":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"2":{"df":1,"docs":{"13":{"tf":2.0}}},"3":{"df":1,"docs":{"13":{"tf":2.8284271247461903}}},"4":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"g":{"df":3,"docs":{"1":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"15":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"31":{"tf":1.0},"57":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.7320508075688772},"3":{"tf":1.0}}},"df":13,"docs":{"10":{"tf":2.8284271247461903},"11":{"tf":2.0},"12":{"tf":3.3166247903554},"13":{"tf":3.4641016151377544},"15":{"tf":1.4142135623730951},"16":{"tf":2.449489742783178},"17":{"tf":2.23606797749979},"20":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"4":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":12,"docs":{"13":{"tf":1.7320508075688772},"20":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"3":{"tf":2.23606797749979},"31":{"tf":1.7320508075688772},"45":{"tf":1.4142135623730951},"57":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0}}}}}},"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"25":{"tf":1.7320508075688772}}}}}},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"47":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"3":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"1":{"tf":1.0}}}},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"19":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"67":{"tf":1.0}}},"k":{"df":2,"docs":{"13":{"tf":1.0},"9":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"37":{"tf":1.4142135623730951},"58":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"31":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"df":1,"docs":{"34":{"tf":1.0}}}}},"o":{"a":{"d":{"df":5,"docs":{"19":{"tf":2.6457513110645907},"34":{"tf":1.0},"35":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"56":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"58":{"tf":1.0}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"15":{"tf":1.0}}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"31":{"tf":1.0},"44":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.0},"39":{"tf":1.0}}}}}},"m":{"a":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"1":{"tf":1.0},"3":{"tf":1.0},"38":{"tf":1.4142135623730951},"5":{"tf":1.0}}}}},"df":0,"docs":{}}}},"k":{"df":0,"docs":{},"e":{"df":4,"docs":{"1":{"tf":1.0},"19":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"51":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"i":{"df":2,"docs":{"30":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}}}}},"p":{"df":2,"docs":{"39":{"tf":1.0},"41":{"tf":1.4142135623730951}}},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"3":{"tf":1.0},"51":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"46":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.0}}}}}},"x":{"df":1,"docs":{"69":{"tf":1.0}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"19":{"tf":1.0},"27":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"y":{"b":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}},"df":4,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"15":{"tf":2.6457513110645907},"17":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"n":{"df":3,"docs":{"1":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"0":{"tf":1.0},"1":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"10":{"tf":1.0},"8":{"tf":2.23606797749979}}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"36":{"tf":2.23606797749979},"37":{"tf":1.0},"38":{"tf":1.0},"43":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"\"":{":":{"\"":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"51":{"tf":1.0},"55":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"66":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"'":{"df":1,"docs":{"5":{"tf":1.0}}},"df":6,"docs":{"47":{"tf":1.0},"5":{"tf":2.0},"50":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"58":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"4":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"29":{"tf":1.0}}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"27":{"tf":1.0},"8":{"tf":1.0}}}}},"p":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"4":{"tf":1.0}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":2,"docs":{"10":{"tf":1.0},"20":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":3,"docs":{"40":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"8":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":6,"docs":{"3":{"tf":1.0},"31":{"tf":1.7320508075688772},"5":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0},"8":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"42":{"tf":2.0}}}}},"u":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":7,"docs":{"25":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"43":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.0}}}}}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":2,"docs":{"17":{"tf":1.7320508075688772},"8":{"tf":1.0}}}}},"c":{"df":0,"docs":{},"p":{"df":1,"docs":{"23":{"tf":1.4142135623730951}}}},"df":3,"docs":{"11":{"tf":1.4142135623730951},"17":{"tf":1.0},"69":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"37":{"tf":1.0},"5":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"d":{"df":9,"docs":{"13":{"tf":1.0},"19":{"tf":1.0},"27":{"tf":1.0},"31":{"tf":1.4142135623730951},"42":{"tf":1.0},"43":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":14,"docs":{"1":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"13":{"tf":2.23606797749979},"15":{"tf":1.0},"17":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"27":{"tf":2.0},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"31":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951},"6":{"tf":1.0},"69":{"tf":1.7320508075688772}}}}}}},"w":{"df":7,"docs":{"13":{"tf":1.4142135623730951},"17":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"58":{"tf":1.4142135623730951},"61":{"tf":1.0},"8":{"tf":1.0}}},"x":{"df":0,"docs":{},"t":{"df":5,"docs":{"10":{"tf":1.0},"12":{"tf":1.4142135623730951},"16":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"o":{"d":{"df":0,"docs":{},"e":{"df":15,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":2.0},"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"17":{"tf":2.23606797749979},"23":{"tf":1.0},"25":{"tf":1.7320508075688772},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"47":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"69":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}},"e":{"df":2,"docs":{"57":{"tf":1.0},"59":{"tf":1.0}}}},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"58":{"tf":1.0}}},"h":{"df":1,"docs":{"0":{"tf":1.0}}},"i":{"df":0,"docs":{},"f":{"df":4,"docs":{"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}}},"w":{"df":1,"docs":{"6":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"3":{"tf":1.0}}}},"u":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":1,"docs":{"27":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"df":7,"docs":{"17":{"tf":2.23606797749979},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"56":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}},"df":1,"docs":{"69":{"tf":2.23606797749979}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":5,"docs":{"12":{"tf":1.4142135623730951},"13":{"tf":2.23606797749979},"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}},"c":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":4,"docs":{"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"19":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"c":{"df":5,"docs":{"13":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"5":{"tf":1.0},"62":{"tf":1.0}}},"df":12,"docs":{"13":{"tf":1.0},"15":{"tf":1.0},"19":{"tf":2.0},"20":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"5":{"tf":2.0},"58":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":1.0}}},"p":{"a":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"15":{"tf":1.0},"19":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.7320508075688772}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"19":{"tf":1.0},"28":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"12":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":2.8284271247461903}}}}}}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"16":{"tf":1.0},"28":{"tf":1.0},"51":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"16":{"tf":1.0},"25":{"tf":1.0},"6":{"tf":1.0},"8":{"tf":1.0}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"20":{"tf":1.0},"51":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"9":{"tf":1.0}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":2,"docs":{"25":{"tf":1.0},"7":{"tf":1.0}}}}}}}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"35":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"56":{"tf":1.0}}}}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":2,"docs":{"40":{"tf":1.0},"41":{"tf":1.0}}}},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"8":{"tf":1.0},"9":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":2,"docs":{"3":{"tf":1.0},"40":{"tf":1.0}},"i":{"df":0,"docs":{},"z":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"m":{"df":3,"docs":{"51":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":13,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}}},"s":{"\"":{":":{"[":{"\"":{"2":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"6":{"c":{"b":{"8":{"df":0,"docs":{},"v":{"a":{"a":{"d":{"9":{"3":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"6":{"df":0,"docs":{},"v":{"d":{"8":{"df":0,"docs":{},"p":{"6":{"7":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"q":{"df":0,"docs":{},"z":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"x":{"4":{"7":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"g":{"9":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"1":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"3":{"a":{"2":{"df":0,"docs":{},"g":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"7":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"j":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"y":{"9":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"g":{"df":0,"docs":{},"v":{"df":0,"docs":{},"k":{"df":0,"docs":{},"y":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"h":{"b":{"2":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"n":{"3":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"f":{"df":0,"docs":{},"k":{"df":0,"docs":{},"x":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"h":{"df":0,"docs":{},"p":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"56":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}}},"5":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"v":{"df":0,"docs":{},"z":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"x":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"9":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"j":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"j":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"8":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"q":{"df":0,"docs":{},"p":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"4":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"3":{"df":0,"docs":{},"z":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"z":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"v":{"6":{"df":0,"docs":{},"u":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"k":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"w":{"df":2,"docs":{"54":{"tf":1.0},"58":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"8":{"3":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"t":{"2":{"df":0,"docs":{},"h":{"5":{"df":0,"docs":{},"u":{"1":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"d":{"df":0,"docs":{},"q":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"6":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"y":{"3":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"51":{"tf":1.0},"55":{"tf":1.0},"60":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"m":{"7":{"8":{"c":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"8":{"df":0,"docs":{},"o":{"3":{"df":0,"docs":{},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"h":{"df":0,"docs":{},"x":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"z":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"4":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"df":0,"docs":{},"k":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"x":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"k":{"df":0,"docs":{},"n":{"df":0,"docs":{},"h":{"1":{"2":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"0":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}}},"[":{"6":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"29":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}},"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"t":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"15":{"tf":2.0},"16":{"tf":1.4142135623730951}}}}}},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"34":{"tf":1.0},"35":{"tf":1.0},"43":{"tf":1.0},"6":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"i":{"d":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{".":{"df":0,"docs":{},"o":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.7320508075688772}}}},"y":{"df":2,"docs":{"68":{"tf":2.449489742783178},"69":{"tf":1.7320508075688772}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":10,"docs":{"1":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"27":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.4142135623730951},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"0":{"tf":1.0},"1":{"tf":1.7320508075688772},"19":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"11":{"tf":1.0},"27":{"tf":1.0},"31":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"35":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}}}}},"t":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.0},"35":{"tf":1.7320508075688772}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":1,"docs":{"27":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"19":{"tf":2.8284271247461903},"20":{"tf":2.0},"40":{"tf":1.0}}}}}}}},"l":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"23":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"h":{"'":{"df":1,"docs":{"9":{"tf":1.0}}},"df":8,"docs":{"11":{"tf":1.0},"12":{"tf":2.0},"13":{"tf":3.0},"17":{"tf":2.0},"28":{"tf":1.7320508075688772},"31":{"tf":3.1622776601683795},"8":{"tf":1.4142135623730951},"9":{"tf":1.7320508075688772}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"13":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"41":{"tf":1.7320508075688772},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"41":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.0},"29":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":4,"docs":{"28":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.23606797749979}}}},"t":{"df":3,"docs":{"48":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"5":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"d":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"df":10,"docs":{"51":{"tf":1.7320508075688772},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"4":{"tf":1.0}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"8":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":3,"docs":{"13":{"tf":1.0},"27":{"tf":1.4142135623730951},"58":{"tf":1.0}}}}}},"v":{"df":1,"docs":{"13":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":1,"docs":{"13":{"tf":2.0}},"s":{"df":2,"docs":{"34":{"tf":1.0},"58":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":4.795831523312719}}}}},"o":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"25":{"tf":1.4142135623730951}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"68":{"tf":3.0},"69":{"tf":2.449489742783178}}},"df":0,"docs":{}}},"df":9,"docs":{"11":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.0},"21":{"tf":1.4142135623730951},"25":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":3.0},"58":{"tf":1.0},"69":{"tf":1.7320508075688772}},"i":{"d":{"df":1,"docs":{"68":{"tf":2.23606797749979}}},"df":0,"docs":{}}}}}},"d":{"df":0,"docs":{},"u":{"c":{"df":3,"docs":{"10":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"'":{"df":1,"docs":{"41":{"tf":1.4142135623730951}}},"_":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"68":{"tf":1.0}}},"df":0,"docs":{}}},"df":19,"docs":{"1":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951},"35":{"tf":2.0},"36":{"tf":1.0},"37":{"tf":2.449489742783178},"38":{"tf":1.7320508075688772},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"41":{"tf":1.7320508075688772},"42":{"tf":2.0},"43":{"tf":2.449489742783178},"44":{"tf":1.0},"45":{"tf":1.0},"56":{"tf":1.7320508075688772},"58":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.0}}}}}}}}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"13":{"tf":1.0},"19":{"tf":1.0}}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.4142135623730951},"28":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"f":{"df":8,"docs":{"10":{"tf":1.0},"27":{"tf":2.23606797749979},"28":{"tf":2.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.6457513110645907},"7":{"tf":1.7320508075688772},"8":{"tf":2.8284271247461903}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":5,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"5":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}}},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"e":{"df":3,"docs":{"29":{"tf":1.0},"31":{"tf":1.0},"9":{"tf":1.0}}},"i":{"d":{"df":7,"docs":{"29":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}},"x":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}}},"u":{"b":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":11,"docs":{"3":{"tf":1.4142135623730951},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"52":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"63":{"tf":1.0},"68":{"tf":4.242640687119285},"69":{"tf":3.3166247903554}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":8,"docs":{"27":{"tf":1.0},"3":{"tf":1.4142135623730951},"35":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.4142135623730951},"52":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.4142135623730951}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":2,"docs":{"49":{"tf":1.4142135623730951},"62":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"55":{"tf":1.0},"60":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"55":{"tf":1.0},"56":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}}}}}}}},"r":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":5,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"31":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"df":0,"docs":{},"k":{"df":2,"docs":{"15":{"tf":1.0},"16":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"13":{"tf":1.0},"19":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"12":{"tf":1.0},"15":{"tf":1.4142135623730951}}}},"d":{"df":2,"docs":{"35":{"tf":1.4142135623730951},"56":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}},"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"v":{"df":3,"docs":{"60":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.7320508075688772}}}}},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"d":{"df":3,"docs":{"25":{"tf":1.0},"28":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}},"df":1,"docs":{"15":{"tf":1.0}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"25":{"tf":1.0},"46":{"tf":1.0},"53":{"tf":1.4142135623730951}}}}},"g":{"a":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"1":{"tf":1.0},"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}}}}}}}}}}},"df":0,"docs":{}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"1":{"tf":1.0}}}}},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"z":{"df":0,"docs":{},"v":{"df":1,"docs":{"69":{"tf":1.0}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"i":{"c":{"df":9,"docs":{"25":{"tf":1.7320508075688772},"26":{"tf":1.0},"27":{"tf":2.449489742783178},"28":{"tf":1.7320508075688772},"29":{"tf":1.7320508075688772},"30":{"tf":1.4142135623730951},"31":{"tf":3.3166247903554},"5":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":3,"docs":{"13":{"tf":1.7320508075688772},"56":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"50":{"tf":1.0},"60":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"df":17,"docs":{"35":{"tf":1.0},"47":{"tf":1.0},"51":{"tf":3.1622776601683795},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"r":{"df":9,"docs":{"13":{"tf":1.0},"27":{"tf":2.23606797749979},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.7320508075688772},"5":{"tf":1.0},"68":{"tf":2.8284271247461903},"69":{"tf":2.0},"8":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"4":{"tf":1.7320508075688772}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"v":{"df":2,"docs":{"16":{"tf":1.0},"9":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":4,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0},"51":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":1,"docs":{"5":{"tf":1.0}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":16,"docs":{"11":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"36":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.7320508075688772},"57":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"59":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951},"63":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"65":{"tf":1.7320508075688772},"66":{"tf":1.4142135623730951}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"58":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":7,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"68":{"tf":3.872983346207417}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":2.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"o":{"a":{"d":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.4142135623730951},"12":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"n":{"d":{"df":4,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"t":{"df":2,"docs":{"39":{"tf":1.0},"6":{"tf":1.0}}}}},"p":{"c":{"df":7,"docs":{"47":{"tf":2.0},"48":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"53":{"tf":1.4142135623730951},"62":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"40":{"tf":1.0}}}},"n":{"df":3,"docs":{"10":{"tf":1.0},"19":{"tf":1.0},"44":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":7,"docs":{"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":2.0},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"43":{"tf":2.23606797749979}}}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":3,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"58":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":11,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":2.449489742783178},"5":{"tf":2.449489742783178},"6":{"tf":1.0}}},"p":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"13":{"tf":1.0}}}},"w":{"df":2,"docs":{"31":{"tf":1.0},"8":{"tf":1.0}}}},"c":{"a":{"df":0,"docs":{},"l":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"25":{"tf":1.0}}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":3,"docs":{"12":{"tf":2.23606797749979},"17":{"tf":1.4142135623730951},"4":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}}},"df":4,"docs":{"13":{"tf":2.0},"16":{"tf":1.7320508075688772},"42":{"tf":1.4142135623730951},"43":{"tf":1.7320508075688772}},"e":{"c":{"df":1,"docs":{"69":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"d":{"df":10,"docs":{"1":{"tf":1.4142135623730951},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"3":{"tf":1.0},"31":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"4":{"tf":1.0},"68":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"46":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"6":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"d":{"df":3,"docs":{"11":{"tf":2.0},"12":{"tf":1.4142135623730951},"31":{"tf":2.0}}},"df":1,"docs":{"1":{"tf":1.0}},"m":{"df":1,"docs":{"5":{"tf":1.0}}}},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"25":{"tf":1.0},"27":{"tf":1.4142135623730951}}}}}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.0},"11":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"d":{"df":6,"docs":{"16":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"51":{"tf":1.4142135623730951},"68":{"tf":2.23606797749979},"69":{"tf":3.605551275463989}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"50":{"tf":1.0},"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":2,"docs":{"19":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"9":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":4,"docs":{"1":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"51":{"tf":1.0}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"19":{"tf":1.0},"35":{"tf":1.0}}}}}},"h":{"a":{"df":2,"docs":{"52":{"tf":1.0},"6":{"tf":1.0}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"34":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"34":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"44":{"tf":1.0}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":12,"docs":{"1":{"tf":1.0},"11":{"tf":1.4142135623730951},"31":{"tf":2.8284271247461903},"37":{"tf":1.0},"52":{"tf":1.4142135623730951},"54":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"65":{"tf":1.4142135623730951},"68":{"tf":3.605551275463989},"69":{"tf":3.4641016151377544}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"65":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"65":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"66":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":8,"docs":{"3":{"tf":1.0},"31":{"tf":1.7320508075688772},"52":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.0}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"36":{"tf":1.0},"58":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":3,"docs":{"10":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":10,"docs":{"13":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"4":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0}}}}},"z":{"df":0,"docs":{},"e":{"df":1,"docs":{"28":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}},"df":2,"docs":{"13":{"tf":1.4142135623730951},"29":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":3.4641016151377544},"15":{"tf":1.0},"16":{"tf":2.0},"17":{"tf":1.0},"4":{"tf":1.0}}},"w":{"df":1,"docs":{"9":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"25":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"15":{"tf":1.4142135623730951},"16":{"tf":1.0},"5":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"o":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"l":{"a":{"df":0,"docs":{},"n":{"a":{"'":{"df":6,"docs":{"1":{"tf":1.4142135623730951},"25":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":2.0}}},"df":22,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":2.23606797749979},"28":{"tf":1.7320508075688772},"3":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":2.23606797749979},"35":{"tf":1.0},"4":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.7320508075688772},"5":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.0},"67":{"tf":1.7320508075688772},"68":{"tf":3.872983346207417},"69":{"tf":4.69041575982343},"8":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.0}}}},"v":{"df":1,"docs":{"25":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"27":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"a":{"c":{"df":0,"docs":{},"e":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":8,"docs":{"0":{"tf":1.0},"11":{"tf":1.0},"31":{"tf":2.0},"38":{"tf":1.4142135623730951},"47":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":5,"docs":{"13":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.0},"68":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"43":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"9":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":2,"docs":{"19":{"tf":1.7320508075688772},"40":{"tf":1.0}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}},"k":{"df":0,"docs":{},"e":{"df":5,"docs":{"10":{"tf":1.0},"12":{"tf":1.0},"15":{"tf":1.0},"29":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951}}}},"n":{"d":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.0},"8":{"tf":1.0}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"50":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":3,"docs":{"13":{"tf":1.0},"37":{"tf":2.0},"38":{"tf":1.4142135623730951}}},"u":{"df":2,"docs":{"54":{"tf":1.0},"58":{"tf":1.4142135623730951}},"s":{"df":1,"docs":{"58":{"tf":1.4142135623730951}}}}},"y":{"df":1,"docs":{"28":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"19":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"26":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"31":{"tf":1.4142135623730951},"35":{"tf":2.0},"8":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":4,"docs":{"27":{"tf":1.0},"29":{"tf":1.4142135623730951},"31":{"tf":1.0},"37":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"16":{"tf":1.0},"19":{"tf":1.0},"28":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"56":{"tf":1.0}}}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"5":{"tf":1.0}}}},"n":{"df":0,"docs":{},"g":{"df":11,"docs":{"11":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"58":{"tf":1.7320508075688772},"60":{"tf":2.0},"61":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"65":{"tf":1.4142135623730951}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"u":{"b":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"(":{"df":1,"docs":{"69":{"tf":1.0}}},"df":1,"docs":{"69":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":1,"docs":{"56":{"tf":1.0}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"27":{"tf":1.0},"31":{"tf":2.0},"34":{"tf":1.0},"62":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":6,"docs":{"50":{"tf":1.0},"62":{"tf":2.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":1,"docs":{"58":{"tf":1.0}},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"51":{"tf":1.0},"58":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"37":{"tf":1.0},"43":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}},"d":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"25":{"tf":1.0},"27":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{},"x":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":6,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"28":{"tf":1.0},"35":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"5":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"12":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}}},"y":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"28":{"tf":1.0}}}}}}},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":14,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"15":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"28":{"tf":1.0},"5":{"tf":2.6457513110645907},"6":{"tf":1.0},"7":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":1,"docs":{"8":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":5,"docs":{"10":{"tf":1.4142135623730951},"28":{"tf":1.0},"42":{"tf":1.4142135623730951},"5":{"tf":2.449489742783178},"68":{"tf":1.0}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{":":{":":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"42":{"tf":1.0},"43":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"31":{"tf":1.0},"41":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"34":{"tf":1.4142135623730951}}}}}}},"df":3,"docs":{"12":{"tf":2.449489742783178},"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":3,"docs":{"1":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}}}}}}},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":2,"docs":{"2":{"tf":1.4142135623730951},"4":{"tf":1.4142135623730951}}}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.4142135623730951},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"19":{"tf":1.4142135623730951}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":4,"docs":{"31":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"43":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"r":{"d":{"df":3,"docs":{"19":{"tf":1.4142135623730951},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":4,"docs":{"27":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"37":{"tf":1.0}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"s":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":3,"docs":{"15":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":1.0}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":2.23606797749979}}}}}}}}}},"u":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":2.23606797749979},"13":{"tf":3.872983346207417},"15":{"tf":1.0},"16":{"tf":1.7320508075688772},"17":{"tf":1.7320508075688772},"3":{"tf":2.23606797749979}}}},"df":1,"docs":{"31":{"tf":1.4142135623730951}},"m":{"df":0,"docs":{},"e":{"df":9,"docs":{"10":{"tf":1.0},"13":{"tf":2.449489742783178},"27":{"tf":1.0},"4":{"tf":1.4142135623730951},"5":{"tf":2.6457513110645907},"6":{"tf":1.0},"68":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"16":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":3,"docs":{"6":{"tf":1.4142135623730951},"68":{"tf":2.6457513110645907},"69":{"tf":3.0}}}}},"df":0,"docs":{}}}}}},"l":{"df":1,"docs":{"69":{"tf":1.0}}},"o":{"d":{"df":0,"docs":{},"o":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":10,"docs":{"29":{"tf":1.0},"3":{"tf":1.0},"35":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"42":{"tf":1.4142135623730951},"43":{"tf":1.0},"56":{"tf":1.4142135623730951},"60":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"9":{"tf":1.0}}},"l":{"df":2,"docs":{"19":{"tf":1.0},"67":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"28":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"p":{"df":2,"docs":{"1":{"tf":1.7320508075688772},"3":{"tf":1.0}},"u":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}}},"r":{"a":{"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"16":{"tf":1.0},"3":{"tf":1.0},"9":{"tf":1.0}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":25,"docs":{"1":{"tf":2.449489742783178},"12":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"25":{"tf":1.0},"29":{"tf":1.0},"3":{"tf":2.449489742783178},"36":{"tf":1.0},"37":{"tf":2.0},"39":{"tf":1.4142135623730951},"40":{"tf":2.23606797749979},"41":{"tf":1.0},"43":{"tf":1.7320508075688772},"44":{"tf":1.0},"5":{"tf":1.7320508075688772},"52":{"tf":1.0},"54":{"tf":2.0},"58":{"tf":3.0},"59":{"tf":1.0},"6":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.7320508075688772},"65":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"69":{"tf":3.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"68":{"tf":2.449489742783178},"69":{"tf":3.0}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"t":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"25":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":2,"docs":{"13":{"tf":1.0},"25":{"tf":1.0}}}},"i":{"df":1,"docs":{"5":{"tf":1.0}}},"u":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":1,"docs":{"54":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"27":{"tf":1.0},"5":{"tf":1.4142135623730951},"6":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"6":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"u":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}}},"w":{"df":0,"docs":{},"o":{"df":4,"docs":{"13":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0},"5":{"tf":1.0}}}},"x":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"68":{"tf":3.3166247903554}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":10,"docs":{"37":{"tf":1.0},"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"i":{"c":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"10":{"tf":1.0},"30":{"tf":1.0}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"5":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"51":{"tf":1.0}}}},"t":{"df":4,"docs":{"19":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"3":{"tf":1.0}}}},"k":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"58":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":2.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":1,"docs":{"59":{"tf":1.0}}}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"65":{"tf":1.0},"66":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.0},"9":{"tf":1.0}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}},"p":{"df":8,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"11":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"40":{"tf":1.0},"5":{"tf":1.0}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}},"s":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":3.605551275463989}}}},"d":{"df":2,"docs":{"27":{"tf":1.0},"30":{"tf":1.0}}},"df":23,"docs":{"1":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":2.8284271247461903},"35":{"tf":1.0},"4":{"tf":2.23606797749979},"42":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.4142135623730951},"5":{"tf":1.7320508075688772},"51":{"tf":1.0},"6":{"tf":2.23606797749979},"62":{"tf":1.0},"68":{"tf":1.0},"8":{"tf":2.0},"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":6,"docs":{"37":{"tf":1.0},"40":{"tf":1.0},"56":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":3,"docs":{"35":{"tf":1.0},"4":{"tf":1.0},"42":{"tf":1.7320508075688772}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"'":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"29":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":14,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":2.23606797749979},"15":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"25":{"tf":2.0},"27":{"tf":1.7320508075688772},"29":{"tf":1.7320508075688772},"3":{"tf":1.4142135623730951},"31":{"tf":2.23606797749979},"4":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":2,"docs":{"31":{"tf":1.0},"51":{"tf":1.0}}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"f":{"df":3,"docs":{"6":{"tf":1.4142135623730951},"8":{"tf":1.7320508075688772},"9":{"tf":3.0}}}},"df":3,"docs":{"16":{"tf":1.0},"17":{"tf":1.0},"69":{"tf":3.3166247903554}},"e":{"c":{"<":{"df":0,"docs":{},"u":{"8":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}},"f":{"df":5,"docs":{"1":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":7,"docs":{"28":{"tf":1.4142135623730951},"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":1.0},"6":{"tf":2.0},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"42":{"tf":1.0},"69":{"tf":4.69041575982343}}}}}}}},"i":{"a":{"df":2,"docs":{"11":{"tf":1.0},"12":{"tf":1.0}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"13":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0}}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":1.4142135623730951},"13":{"tf":2.449489742783178},"15":{"tf":1.7320508075688772},"16":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.0},"69":{"tf":1.0}}}},"l":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"68":{"tf":1.0}}},"df":3,"docs":{"67":{"tf":1.4142135623730951},"68":{"tf":3.872983346207417},"69":{"tf":4.69041575982343}}}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"r":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"h":{"/":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"19":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":2.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":2.23606797749979}}}}},"y":{"df":5,"docs":{"19":{"tf":1.0},"28":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.7320508075688772},"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"'":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.0}}}},"r":{"df":1,"docs":{"5":{"tf":1.0}}}},"b":{"3":{".":{"df":0,"docs":{},"j":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"49":{"tf":1.4142135623730951},"50":{"tf":1.0},"62":{"tf":2.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"15":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"l":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"5":{"tf":1.0},"6":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":2,"docs":{"20":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"37":{"tf":1.0},"5":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"37":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"40":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"d":{"df":1,"docs":{"3":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":4,"docs":{"25":{"tf":1.0},"29":{"tf":1.0},"44":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":3,"docs":{"20":{"tf":1.0},"35":{"tf":1.4142135623730951},"58":{"tf":1.0}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"33":{"tf":1.0},"35":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"s":{":":{"/":{"/":{"<":{"a":{"d":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"8":{"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"df":10,"docs":{"13":{"tf":1.7320508075688772},"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}},"x":{"df":1,"docs":{"13":{"tf":1.0}}}},"y":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"'":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"z":{"df":2,"docs":{"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":1,"docs":{"43":{"tf":1.0}}}}}}}},"title":{"root":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"38":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":2,"docs":{"47":{"tf":1.0},"53":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"46":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"67":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"52":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"48":{"tf":1.0},"49":{"tf":1.0}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"39":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"41":{"tf":1.0}}}}}},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"14":{"tf":1.0},"68":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"40":{"tf":1.0},"41":{"tf":1.0}}}}},"df":0,"docs":{}}}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"13":{"tf":1.0}}},"m":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"45":{"tf":1.0},"51":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"d":{"df":2,"docs":{"18":{"tf":1.0},"20":{"tf":1.0}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"4":{"tf":1.0},"44":{"tf":1.0}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"11":{"tf":1.0}}}}},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"55":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"7":{"tf":1.0}}}}}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":1,"docs":{"48":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"a":{"c":{"df":1,"docs":{"42":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"33":{"tf":1.0},"6":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"41":{"tf":1.0}}}}}}}},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"47":{"tf":1.0},"53":{"tf":1.0}},"r":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"l":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"16":{"tf":1.0}}}}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"45":{"tf":1.0}}}}}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{}}}},"p":{"df":1,"docs":{"41":{"tf":1.0}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"50":{"tf":1.0}}},"df":0,"docs":{}}}}}},"n":{"c":{"df":0,"docs":{},"p":{"df":1,"docs":{"23":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":2,"docs":{"17":{"tf":1.0},"29":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"43":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":1.0}}}}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"15":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"19":{"tf":1.0},"20":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"h":{"df":1,"docs":{"28":{"tf":1.0}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"41":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"21":{"tf":1.0}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"'":{"df":1,"docs":{"41":{"tf":1.0}}},"df":2,"docs":{"38":{"tf":1.0},"41":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"f":{"df":1,"docs":{"7":{"tf":1.0}}}},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"41":{"tf":1.0}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"u":{"b":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"53":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":2,"docs":{"8":{"tf":1.0},"9":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"25":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":1,"docs":{"51":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"4":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.0},"12":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"c":{"df":4,"docs":{"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"53":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"s":{"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"d":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{}},"n":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"66":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"a":{"df":3,"docs":{"32":{"tf":1.0},"34":{"tf":1.0},"67":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{}}},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"26":{"tf":1.0},"35":{"tf":1.0}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"42":{"tf":1.0}}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}}},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":2,"docs":{"2":{"tf":1.0},"4":{"tf":1.0}}}}}}}}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"21":{"tf":1.0},"22":{"tf":1.0},"39":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"21":{"tf":1.0},"22":{"tf":1.0}}}}},"s":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":2,"docs":{"3":{"tf":1.0},"4":{"tf":1.0}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"22":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"f":{"df":2,"docs":{"6":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"67":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"49":{"tf":1.0},"62":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"38":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"44":{"tf":1.0}}}}}}}}},"pipeline":["trimmer","stopWordFilter","stemmer"],"ref":"id","version":"0.9.5"},"results_options":{"limit_results":30,"teaser_word_count":30},"search_options":{"bool":"OR","expand":true,"fields":{"body":{"boost":1},"breadcrumbs":{"boost":1},"title":{"boost":2}}}}; \ No newline at end of file diff --git a/book/searchindex.json b/book/searchindex.json deleted file mode 100644 index af0c3757557e1a..00000000000000 --- a/book/searchindex.json +++ /dev/null @@ -1 +0,0 @@ -{"doc_urls":["introduction.html#disclaimer","introduction.html#introduction","terminology.html#terminology","terminology.html#teminology-currently-in-use","terminology.html#terminology-reserved-for-future-use","synchronization.html#synchronization","vdf.html#introduction-to-vdfs","poh.html#proof-of-history","poh.html#relationship-to-consensus-mechanisms","poh.html#relationship-to-vdfs","leader-scheduler.html#leader-rotation","leader-scheduler.html#leader-seed-generation","leader-scheduler.html#leader-rotation","leader-scheduler.html#partitions-forks","leader-scheduler.html#examples","leader-scheduler.html#small-partition","leader-scheduler.html#leader-timeout","leader-scheduler.html#network-variables","fullnode.html#fullnode","fullnode.html#pipelining","fullnode.html#pipelining-in-the-fullnode","tpu.html#the-transaction-processing-unit","tvu.html#the-transaction-validation-unit","ncp.html#ncp","jsonrpc-service.html#jsonrpcservice","avalanche.html#avalanche-replication","storage.html#storage","storage.html#background","storage.html#optimization-with-poh","storage.html#network","storage.html#constraints","storage.html#validation-and-replication-protocol","programs.html#the-solana-sdk","programs.html#introduction","programs.html#client-interactions-with-solana","programs.html#persistent-storage","runtime.html#runtime","runtime.html#state","runtime.html#account-structure-accounts-maintain-token-state-as-well-as-program-specific","runtime.html#transaction-engine","runtime.html#execution","runtime.html#entry-point-execution-of-the-program-involves-mapping-the-programs-public","runtime.html#system-interface","runtime.html#notes","runtime.html#future-work","ledger.html#ledger-format","appendix.html#appendix","jsonrpc-api.html#json-rpc-api","jsonrpc-api.html#rpc-http-endpoint","jsonrpc-api.html#rpc-pubsub-websocket-endpoint","jsonrpc-api.html#methods","jsonrpc-api.html#request-formatting","jsonrpc-api.html#definitions","jsonrpc-api.html#json-rpc-api-reference","jsonrpc-api.html#confirmtransaction","jsonrpc-api.html#getbalance","jsonrpc-api.html#getaccountinfo","jsonrpc-api.html#getlastid","jsonrpc-api.html#getsignaturestatus","jsonrpc-api.html#gettransactioncount","jsonrpc-api.html#requestairdrop","jsonrpc-api.html#sendtransaction","jsonrpc-api.html#subscription-websocket","jsonrpc-api.html#accountsubscribe","jsonrpc-api.html#accountunsubscribe","jsonrpc-api.html#signaturesubscribe","jsonrpc-api.html#signatureunsubscribe","wallet.html#solana-wallet-cli","wallet.html#examples","wallet.html#usage"],"index":{"documentStore":{"docInfo":{"0":{"body":27,"breadcrumbs":1,"title":1},"1":{"body":172,"breadcrumbs":1,"title":1},"10":{"body":46,"breadcrumbs":3,"title":2},"11":{"body":38,"breadcrumbs":4,"title":3},"12":{"body":73,"breadcrumbs":3,"title":2},"13":{"body":259,"breadcrumbs":3,"title":2},"14":{"body":0,"breadcrumbs":2,"title":1},"15":{"body":51,"breadcrumbs":3,"title":2},"16":{"body":56,"breadcrumbs":3,"title":2},"17":{"body":57,"breadcrumbs":3,"title":2},"18":{"body":0,"breadcrumbs":1,"title":1},"19":{"body":106,"breadcrumbs":1,"title":1},"2":{"body":0,"breadcrumbs":1,"title":1},"20":{"body":44,"breadcrumbs":2,"title":2},"21":{"body":0,"breadcrumbs":4,"title":3},"22":{"body":0,"breadcrumbs":4,"title":3},"23":{"body":9,"breadcrumbs":2,"title":1},"24":{"body":0,"breadcrumbs":2,"title":1},"25":{"body":81,"breadcrumbs":2,"title":2},"26":{"body":0,"breadcrumbs":3,"title":1},"27":{"body":129,"breadcrumbs":3,"title":1},"28":{"body":74,"breadcrumbs":4,"title":2},"29":{"body":41,"breadcrumbs":3,"title":1},"3":{"body":106,"breadcrumbs":3,"title":3},"30":{"body":50,"breadcrumbs":3,"title":1},"31":{"body":262,"breadcrumbs":5,"title":3},"32":{"body":0,"breadcrumbs":2,"title":2},"33":{"body":11,"breadcrumbs":1,"title":1},"34":{"body":42,"breadcrumbs":3,"title":3},"35":{"body":60,"breadcrumbs":2,"title":2},"36":{"body":53,"breadcrumbs":3,"title":1},"37":{"body":71,"breadcrumbs":3,"title":1},"38":{"body":1,"breadcrumbs":11,"title":9},"39":{"body":11,"breadcrumbs":4,"title":2},"4":{"body":35,"breadcrumbs":4,"title":4},"40":{"body":54,"breadcrumbs":3,"title":1},"41":{"body":9,"breadcrumbs":10,"title":8},"42":{"body":38,"breadcrumbs":4,"title":2},"43":{"body":61,"breadcrumbs":3,"title":1},"44":{"body":5,"breadcrumbs":4,"title":2},"45":{"body":0,"breadcrumbs":4,"title":2},"46":{"body":9,"breadcrumbs":1,"title":1},"47":{"body":25,"breadcrumbs":4,"title":3},"48":{"body":6,"breadcrumbs":4,"title":3},"49":{"body":6,"breadcrumbs":5,"title":4},"5":{"body":196,"breadcrumbs":1,"title":1},"50":{"body":15,"breadcrumbs":2,"title":1},"51":{"body":85,"breadcrumbs":3,"title":2},"52":{"body":24,"breadcrumbs":2,"title":1},"53":{"body":0,"breadcrumbs":5,"title":4},"54":{"body":36,"breadcrumbs":2,"title":1},"55":{"body":38,"breadcrumbs":2,"title":1},"56":{"body":81,"breadcrumbs":2,"title":1},"57":{"body":32,"breadcrumbs":2,"title":1},"58":{"body":88,"breadcrumbs":2,"title":1},"59":{"body":29,"breadcrumbs":2,"title":1},"6":{"body":104,"breadcrumbs":3,"title":2},"60":{"body":47,"breadcrumbs":2,"title":1},"61":{"body":238,"breadcrumbs":2,"title":1},"62":{"body":16,"breadcrumbs":3,"title":2},"63":{"body":43,"breadcrumbs":2,"title":1},"64":{"body":27,"breadcrumbs":2,"title":1},"65":{"body":44,"breadcrumbs":2,"title":1},"66":{"body":27,"breadcrumbs":2,"title":1},"67":{"body":7,"breadcrumbs":4,"title":3},"68":{"body":220,"breadcrumbs":2,"title":1},"69":{"body":402,"breadcrumbs":2,"title":1},"7":{"body":3,"breadcrumbs":3,"title":2},"8":{"body":91,"breadcrumbs":4,"title":3},"9":{"body":124,"breadcrumbs":3,"title":2}},"docs":{"0":{"body":"All claims, content, designs, algorithms, estimates, roadmaps, specifications, and performance measurements described in this project are done with the author's best effort. It is up to the reader to check and validate their accuracy and truthfulness. Furthermore nothing in this project constitutes a solicitation for investment.","breadcrumbs":"Disclaimer","id":"0","title":"Disclaimer"},"1":{"body":"This document defines the architecture of Solana, a blockchain built from the ground up for scale. The goal of the architecture is to demonstrate there exists a set of software algorithms that in combination, removes software as a performance bottleneck, allowing transaction throughput to scale proportionally with network bandwidth. The architecture goes on to satisfy all three desirable properties of a proper blockchain, that it not only be scalable, but that it is also secure and decentralized. With this architecture, we calculate a theoretical upper bound of 710 thousand transactions per second (tps) on a standard gigabit network and 28.4 million tps on 40 gigabit. In practice, our focus has been on gigabit. We soak-tested a 150 node permissioned testnet and it is able to maintain a mean transaction throughput of approximately 200 thousand tps with peaks over 400 thousand. Furthermore, we have found high throughput extends beyond simple payments, and that this architecture is also able to perform safe, concurrent execution of programs authored in a general purpose programming language, such as C. We feel the extension warrants industry focus on an additional performance metric already common in the CPU industry, millions of instructions per second or mips. By measuring mips, we see that batching instructions within a transaction amortizes the cost of signature verification, lifting the maximum theoretical instruction throughput up to almost exactly that of centralized databases. Lastly, we discuss the relationships between high throughput, security and transaction fees. Solana's efficient use hardware drives transaction fees into the ballpark of 1/1000th of a cent. The drop in fees in turn makes certain denial of service attacks cheaper. We discuss what these attacks look like and Solana's techniques to defend against them.","breadcrumbs":"Introduction","id":"1","title":"Introduction"},"10":{"body":"A property of any permissionless blockchain is that the entity choosing the next block is randomly selected. In proof of stake systems, that entity is typically called the \"leader\" or \"block producer.\" In Solana, we call it the leader. Under the hood, a leader is simply a mode of the fullnode. A fullnode runs as either a leader or validator. In this chapter, we describe how a fullnode determines what node is the leader, how that mechanism may choose different leaders at the same time, and if so, how the system converges in response.","breadcrumbs":"Synchronization » Leader Rotation","id":"10","title":"Leader Rotation"},"11":{"body":"Leader selection is decided via a random seed. The process is as follows: Periodically, at a specific PoH tick count, select the signatures of the votes that made up the last supermajority Concatenate the signatures Hash the resulting string for N counts The resulting hash is the random seed for M counts, M leader slots, where M > N","breadcrumbs":"Synchronization » Leader Seed Generation","id":"11","title":"Leader Seed Generation"},"12":{"body":"The leader is chosen via a random seed generated from stake weights and votes (the leader schedule) The leader is rotated every T PoH ticks (leader slot), according to the leader schedule The schedule is applicable for M voting rounds Leader's transmit for a count of T PoH ticks. When T is reached all the validators should switch to the next scheduled leader. To schedule leaders, the supermajority + M nodes are shuffled using the above calculated random seed. All T ticks must be observed from the current leader for that part of PoH to be accepted by the network. If T ticks (and any intervening transactions) are not observed, the network optimistically fills in the T ticks, and continues with PoH from the next leader.","breadcrumbs":"Synchronization » Leader Rotation","id":"12","title":"Leader Rotation"},"13":{"body":"Forks can arise at PoH tick counts that correspond to leader rotations, because leader nodes may or may not have observed the previous leader's data. These empty ticks are generated by all nodes in the network at a network-specified rate for hashes-per-tick Z . There are only two possible versions of the PoH during a voting round: PoH with T ticks and entries generated by the current leader, or PoH with just ticks. The \"just ticks\" version of the PoH can be thought of as a virtual ledger, one that all nodes in the network can derive from the last tick in the previous slot. Validators can ignore forks at other points (e.g. from the wrong leader), or slash the leader responsible for the fork. Validators vote on the longest chain that contains their previous vote, or a longer chain if the lockout on their previous vote has expired. Validator's View Time Progression The diagram below represents a validator's view of the PoH stream with possible forks over time. L1, L2, etc. are leader slots, and E s represent entries from that leader during that leader's slot. The x s represent ticks only, and time flows downwards in the diagram. Note that an E appearing on 2 branches at the same slot is a slashable condition, so a validator observing L3 and L3' can slash L3 and safely choose x for that slot. Once a validator observes a supermajority vote on any branch, other branches can be discarded below that tick count. For any slot, validators need only consider a single \"has entries\" chain or a \"ticks only\" chain. Time Division It's useful to consider leader rotation over PoH tick count as time division of the job of encoding state for the network. The following table presents the above tree of forks as a time-divided ledger. leader slot L1 L2 L3 L4 L5 data E1 E2 E3 E4 E5 ticks since prev x xx Note that only data from leader L3 will be accepted during leader slot L3 . Data from L3 may include \"catchup\" ticks back to a slot other than L2 if L3 did not observe L2 's data. L4 and L5 's transmissions include the \"ticks since prev\" PoH entries. This arrangement of the network data streams permits nodes to save exactly this to the ledger for replay, restart, and checkpoints. Leader's View When a new leader begins a slot, it must first transmit any PoH (ticks) required to link the new slot with the most recently observed and voted slot.","breadcrumbs":"Synchronization » Partitions, Forks","id":"13","title":"Partitions, Forks"},"14":{"body":"","breadcrumbs":"Synchronization » Examples","id":"14","title":"Examples"},"15":{"body":"Network partition M occurs for 10% of the nodes The larger partition K , with 90% of the stake weight continues to operate as normal M cycles through the ranks until one of them is leader, generating ticks for slots where the leader is in K . M validators observe 10% of the vote pool, finality is not reached. M and K reconnect. M validators cancel their votes on M , which has not reached finality, and re-cast on K (after their vote lockout on M ).","breadcrumbs":"Synchronization » Small Partition","id":"15","title":"Small Partition"},"16":{"body":"Next rank leader node V observes a timeout from current leader A , fills in A 's slot with virtual ticks and starts sending out entries. Nodes observing both streams keep track of the forks, waiting for: their vote on leader A to expire in order to be able to vote on B a supermajority on A 's slot If the first case occurs, leader B 's slot is filled with ticks. if the second case occurs, A's slot is filled with ticks Partition is resolved just like in the Small Partition above","breadcrumbs":"Synchronization » Leader Timeout","id":"16","title":"Leader Timeout"},"17":{"body":"A - name of a node B - name of a node K - number of nodes in the supermajority to whom leaders broadcast their PoH hash for validation M - number of nodes outside the supermajority to whom leaders broadcast their PoH hash for validation N - number of voting rounds for which a leader schedule is considered before a new leader schedule is used T - number of PoH ticks per leader slot (also voting round) V - name of a node that will create virtual ticks Z - number of hashes per PoH tick","breadcrumbs":"Synchronization » Network Variables","id":"17","title":"Network Variables"},"18":{"body":"","breadcrumbs":"Fullnode","id":"18","title":"Fullnode"},"19":{"body":"The fullnodes make extensive use of an optimization common in CPU design, called pipelining . Pipelining is the right tool for the job when there's a stream of input data that needs to be processed by a sequence of steps, and there's different hardware responsible for each. The quintessential example is using a washer and dryer to wash/dry/fold several loads of laundry. Washing must occur before drying and drying before folding, but each of the three operations is performed by a separate unit. To maximize efficiency, one creates a pipeline of stages . We'll call the washer one stage, the dryer another, and the folding process a third. To run the pipeline, one adds a second load of laundry to the washer just after the first load is added to the dryer. Likewise, the third load is added to the washer after the second is in the dryer and the first is being folded. In this way, one can make progress on three loads of laundry simultaneously. Given infinite loads, the pipeline will consistently complete a load at the rate of the slowest stage in the pipeline.","breadcrumbs":"Pipelining","id":"19","title":"Pipelining"},"2":{"body":"","breadcrumbs":"Terminology","id":"2","title":"Terminology"},"20":{"body":"The fullnode contains two pipelined processes, one used in leader mode called the Tpu and one used in validator mode called the Tvu. In both cases, the hardware being pipelined is the same, the network input, the GPU cards, the CPU cores, writes to disk, and the network output. What it does with that hardware is different. The Tpu exists to create ledger entries whereas the Tvu exists to validate them.","breadcrumbs":"Pipelining in the fullnode","id":"20","title":"Pipelining in the fullnode"},"21":{"body":"","breadcrumbs":"Fullnode » The Transaction Processing Unit","id":"21","title":"The Transaction Processing Unit"},"22":{"body":"","breadcrumbs":"Fullnode » The Transaction Validation Unit","id":"22","title":"The Transaction Validation Unit"},"23":{"body":"The Network Control Plane implements a gossip network between all nodes on in the cluster.","breadcrumbs":"Fullnode » Ncp","id":"23","title":"Ncp"},"24":{"body":"","breadcrumbs":"Fullnode » JsonRpcService","id":"24","title":"JsonRpcService"},"25":{"body":"The Avalance explainer video is a conceptual overview of how a Solana leader can continuously process a gigabit of transaction data per second and then get that same data, after being recorded on the ledger, out to multiple validators on a single gigabit backplane. In practice, we found that just one level of the Avalanche validator tree is sufficient for at least 150 validators. We anticipate adding the second level to solve one of two problems: To transmit ledger segments to slower \"replicator\" nodes. To scale up the number of validators nodes. Both problems justify the additional level, but you won't find it implemented in the reference design just yet, because Solana's gossip implementation is currently the bottleneck on the number of nodes per Solana cluster. That work is being actively developed here: Scalable Gossip","breadcrumbs":"Avalanche replication","id":"25","title":"Avalanche replication"},"26":{"body":"","breadcrumbs":"Avalanche replication » Storage","id":"26","title":"Storage"},"27":{"body":"At full capacity on a 1gbps network Solana would generate 4 petabytes of data per year. If each fullnode was required to store the full ledger, the cost of storage would discourage fullnode participation, thus centralizing the network around those that could afford it. Solana aims to keep the cost of a fullnode below $5,000 USD to maximize participation. To achieve that, the network needs to minimize redundant storage while at the same time ensuring the validity and availability of each copy. To trust storage of ledger segments, Solana has replicators periodically submit proofs to the network that the data was replicated. Each proof is called a Proof of Replication. The basic idea of it is to encrypt a dataset with a public symmetric key and then hash the encrypted dataset. Solana uses CBC encryption . To prevent a malicious replicator from deleting the data as soon as it's hashed, a replicator is required hash random segments of the dataset. Alternatively, Solana could require hashing the reverse of the encrypted data, but random sampling is sufficient and much faster. Either solution ensures that all the data is present during the generation of the proof and also requires the validator to have the entirety of the encrypted data present for verification of every proof of every identity. The space required to validate is: number_of_proofs * data_size","breadcrumbs":"Avalanche replication » Background","id":"27","title":"Background"},"28":{"body":"Solana is not the only distribute systems project using Proof of Replication, but it might be the most efficient implementation because of its ability to synchronize nodes with its Proof of History. With PoH, Solana is able to record a hash of the PoRep samples in the ledger. Thus the blocks stay in the exact same order for every PoRep and verification can stream the data and verify all the proofs in a single batch. This way Solana can verify multiple proofs concurrently, each one on its own GPU core. With the current generation of graphics cards our network can support up to 14,000 replication identities or symmetric keys. The total space required for verification is: 2 CBC_blocks * number_of_identities with core count of equal to (Number of Identities). A CBC block is expected to be 1MB in size.","breadcrumbs":"Avalanche replication » Optimization with PoH","id":"28","title":"Optimization with PoH"},"29":{"body":"Validators for PoRep are the same validators that are verifying transactions. They have some stake that they have put up as collateral that ensures that their work is honest. If you can prove that a validator verified a fake PoRep, then the validator's stake is slashed. Replicators are specialized light clients. They download a part of the ledger and store it and provide proofs of storing the ledger. For each verified proof, replicators are rewarded tokens from the mining pool.","breadcrumbs":"Avalanche replication » Network","id":"29","title":"Network"},"3":{"body":"The following list contains words commonly used throughout the Solana architecture. account - a persistent file addressed by pubkey and with tokens tracking its lifetime cluster - a set of fullnodes maintaining a single ledger finality - the wallclock duration between a leader creating a tick entry and recoginizing a supermajority of validator votes with a ledger interpretation that matches the leader's fullnode - a full participant in the cluster - either a leader or validator node entry - an entry on the ledger - either a tick or a transactions entry instruction - the smallest unit of a program that a client can include in a transaction keypair - a public and secret key node count - the number of fullnodes participating in a cluster program - the code that interprets instructions pubkey - the public key of a keypair tick - a ledger entry that estimates wallclock duration tick height - the Nth tick in the ledger tps - transactions per second transaction - one or more instructions signed by the client and executed atomically transactions entry - a set of transactions that may be executed in parallel","breadcrumbs":"Teminology Currently in Use","id":"3","title":"Teminology Currently in Use"},"30":{"body":"Solana's PoRep protocol instroduces the following constraints: At most 14,000 replication identities can be used, because that is how many GPU cores are currently available to a computer costing under $5,000 USD. Verification requires generating the CBC blocks. That requires space of 2 blocks per identity, and 1 GPU core per identity for the same dataset. As many identities at once are batched with as many proofs for those identities verified concurrently for the same dataset.","breadcrumbs":"Avalanche replication » Constraints","id":"30","title":"Constraints"},"31":{"body":"The network sets a replication target number, let's say 1k. 1k PoRep identities are created from signatures of a PoH hash. They are tied to a specific PoH hash. It doesn't matter who creates them, or it could simply be the last 1k validation signatures we saw for the ledger at that count. This is maybe just the initial batch of identities, because we want to stagger identity rotation. Any client can use any of these identities to create PoRep proofs. Replicator identities are the CBC encryption keys. Periodically at a specific PoH count, a replicator that wants to create PoRep proofs signs the PoH hash at that count. That signature is the seed used to pick the block and identity to replicate. A block is 1TB of ledger. Periodically at a specific PoH count, a replicator submits PoRep proofs for their selected block. A signature of the PoH hash at that count is the seed used to sample the 1TB encrypted block, and hash it. This is done faster than it takes to encrypt the 1TB block with the original identity. Replicators must submit some number of fake proofs, which they can prove to be fake by providing the seed for the hash result. Periodically at a specific PoH count, validators sign the hash and use the signature to select the 1TB block that they need to validate. They batch all the identities and proofs and submit approval for all the verified ones. After #6, replicator client submit the proofs of fake proofs. For any random seed, Solana requires everyone to use a signature that is derived from a PoH hash. Every node uses the same count so that the same PoH hash is signed by every participant. The signatures are then each cryptographically tied to the keypair, which prevents a leader from grinding on the resulting value for more than 1 identity. Key rotation is staggered . Once going, the next identity is generated by hashing itself with a PoH hash. Since there are many more client identities then encryption identities, the reward is split amont multiple clients to prevent Sybil attacks from generating many clients to acquire the same block of data. To remain BFT, the network needs to avoid a single human entity from storing all the replications of a single chunk of the ledger. Solana's solution to this is to require clients to continue using the same identity. If the first round is used to acquire the same block for many client identities, the second round for the same client identities will require a redistribution of the signatures, and therefore PoRep identities and blocks. Thus to get a reward for storage, clients are not rewarded for storage of the first block. The network rewards long-lived client identities more than new ones.","breadcrumbs":"Avalanche replication » Validation and Replication Protocol","id":"31","title":"Validation and Replication Protocol"},"32":{"body":"","breadcrumbs":"The Solana SDK","id":"32","title":"The Solana SDK"},"33":{"body":"With the Solana runtime, we can execute on-chain programs concurrently, and written in the client’s choice of programming language.","breadcrumbs":"Introduction","id":"33","title":"Introduction"},"34":{"body":"As shown in the diagram above an untrusted client, creates a program in the language of their choice, (i.e. C/C++/Rust/Lua), and compiles it with LLVM to a position independent shared object ELF, targeting BPF bytecode, and sends it to the Solana cluster. Next, the client sends messages to the Solana cluster, which target that program. The Solana runtime loads the previously submitted ELF and passes it the client's message for interpretation.","breadcrumbs":"Client interactions with Solana","id":"34","title":"Client interactions with Solana"},"35":{"body":"Solana supports several kinds of persistent storage, called accounts : Executable Writable by a client Writable by a program Read-only All accounts are identified by public keys and may hold arbirary data. When the client sends messages to programs, it requests access to storage using those keys. The runtime loads the account data and passes it to the program. The runtime also ensures accounts aren't written to if not owned by the client or program. Any writes to read-only accounts are discarded unless the write was to credit tokens. Any user may credit other accounts tokens, regardless of account permission.","breadcrumbs":"Persistent Storage","id":"35","title":"Persistent Storage"},"36":{"body":"The goal with the runtime is to have a general purpose execution environment that is highly parallelizable. To achieve this goal the runtime forces each Instruction to specify all of its memory dependencies up front, and therefore a single Instruction cannot cause a dynamic memory allocation. An explicit Instruction for memory allocation from the SystemProgram::CreateAccount is the only way to allocate new memory in the engine. A Transaction may compose multiple Instruction, including SystemProgram::CreateAccount , into a single atomic sequence which allows for memory allocation to achieve a result that is similar to dynamic allocation.","breadcrumbs":"On-chain programs » Runtime","id":"36","title":"Runtime"},"37":{"body":"State is addressed by an Account which is at the moment simply the Pubkey. Our goal is to eliminate memory allocation from within the program itself. Thus the client of the program provides all the state that is necessary for the program to execute in the transaction itself. The runtime interacts with the program through an entry point with a well defined interface. The userdata stored in an Account is an opaque type to the runtime, a Vec , the contents of which the program code has full control over. The Transaction structure specifies a list of Pubkey's and signatures for those keys and a sequential list of instructions that will operate over the state's associated with the account_keys . For the transaction to be committed all the instructions must execute successfully, if any abort the whole transaction fails to commit.","breadcrumbs":"On-chain programs » State","id":"37","title":"State"},"38":{"body":"memory.","breadcrumbs":"On-chain programs » Account structure Accounts maintain token state as well as program specific","id":"38","title":"Account structure Accounts maintain token state as well as program specific"},"39":{"body":"At its core, the engine looks up all the Pubkeys maps them to accounts and routs them to the program_id entry point.","breadcrumbs":"On-chain programs » Transaction Engine","id":"39","title":"Transaction Engine"},"4":{"body":"The following keywords do not have any functionality but are reserved by Solana for potential future use. epoch - the time in which a leader schedule is valid mips - millions of instructions per second public key - We currently use pubkey slot - the time in which a single leader may produce entries secret key - Users currently only use keypair","breadcrumbs":"Terminology Reserved for Future Use","id":"4","title":"Terminology Reserved for Future Use"},"40":{"body":"Transactions are batched and processed in a pipeline At the execute stage, the loaded pages have no data dependencies, so all the programs can be executed in parallel. The runtime enforces the following rules: The program_id code is the only code that will modify the contents of Account::userdata of Account's that have been assigned to it. This means that upon assignment userdata vector is guaranteed to be 0 . Total balances on all the accounts is equal before and after execution of a Transaction. Balances of each of the accounts not assigned to program_id must be equal to or greater after the Transaction than before the transaction. All Instructions in the Transaction executed without a failure.","breadcrumbs":"On-chain programs » Execution","id":"40","title":"Execution"},"41":{"body":"key to an entry point which takes a pointer to the transaction, and an array of loaded pages.","breadcrumbs":"On-chain programs » Entry Point Execution of the program involves mapping the Program's public","id":"41","title":"Entry Point Execution of the program involves mapping the Program's public"},"42":{"body":"The interface is best described by the Instruction::userdata that the user encodes. CreateAccount - This allows the user to create and assign an Account to a Program. Assign - allows the user to assign an existing account to a Program . Move - moves tokens between Account s that are associated with SystemProgram . This cannot be used to move tokens of other Account s. Programs need to implement their own version of Move.","breadcrumbs":"On-chain programs » System Interface","id":"42","title":"System Interface"},"43":{"body":"There is no dynamic memory allocation. Client's need to call the SystemProgram to create memory before passing it to another program. This Instruction can be composed into a single Transaction with the call to the program itself. Runtime guarantees that when memory is assigned to the Program it is zero initialized. Runtime guarantees that Program 's code is the only thing that can modify memory that its assigned to Runtime guarantees that the Program can only spend tokens that are in Account s that are assigned to it Runtime guarantees the balances belonging to Account s are balanced before and after the transaction Runtime guarantees that multiple instructions all executed successfully when a transaction is committed.","breadcrumbs":"On-chain programs » Notes","id":"43","title":"Notes"},"44":{"body":"Continuations and Signals for long running Transactions","breadcrumbs":"On-chain programs » Future Work","id":"44","title":"Future Work"},"45":{"body":"","breadcrumbs":"On-chain programs » Ledger format","id":"45","title":"Ledger format"},"46":{"body":"The following sections contain reference material you may find useful in your Solana journey.","breadcrumbs":"Appendix","id":"46","title":"Appendix"},"47":{"body":"Solana nodes accept HTTP requests using the JSON-RPC 2.0 specification. To interact with a Solana node inside a JavaScript application, use the solana-web3.js library, which gives a convenient interface for the RPC methods.","breadcrumbs":"Appendix » JSON RPC API","id":"47","title":"JSON RPC API"},"48":{"body":"Default port: 8899 eg. http://localhost:8899, http://192.168.1.88:8899","breadcrumbs":"Appendix » RPC HTTP Endpoint","id":"48","title":"RPC HTTP Endpoint"},"49":{"body":"Default port: 8900 eg. ws://localhost:8900, http://192.168.1.88:8900","breadcrumbs":"Appendix » RPC PubSub WebSocket Endpoint","id":"49","title":"RPC PubSub WebSocket Endpoint"},"5":{"body":"It's possible for a centralized database to process 710,000 transactions per second on a standard gigabit network if the transactions are, on average, no more than 176 bytes. A centralized database can also replicate itself and maintain high availability without significantly compromising that transaction rate using the distributed system technique known as Optimistic Concurrency Control [H.T.Kung, J.T.Robinson (1981)] . At Solana, we're demonstrating that these same theoretical limits apply just as well to blockchain on an adversarial network. The key ingredient? Finding a way to share time when nodes can't trust one-another. Once nodes can trust time, suddenly ~40 years of distributed systems research becomes applicable to blockchain! Perhaps the most striking difference between algorithms obtained by our method and ones based upon timeout is that using timeout produces a traditional distributed algorithm in which the processes operate asynchronously, while our method produces a globally synchronous one in which every process does the same thing at (approximately) the same time. Our method seems to contradict the whole purpose of distributed processing, which is to permit different processes to operate independently and perform different functions. However, if a distributed system is really a single system, then the processes must be synchronized in some way. Conceptually, the easiest way to synchronize processes is to get them all to do the same thing at the same time. Therefore, our method is used to implement a kernel that performs the necessary synchronization--for example, making sure that two different processes do not try to modify a file at the same time. Processes might spend only a small fraction of their time executing the synchronizing kernel; the rest of the time, they can operate independently--e.g., accessing different files. This is an approach we have advocated even when fault-tolerance is not required. The method's basic simplicity makes it easier to understand the precise properties of a system, which is crucial if one is to know just how fault-tolerant the system is. [L.Lamport (1984)]","breadcrumbs":"Synchronization","id":"5","title":"Synchronization"},"50":{"body":"confirmTransaction getBalance getAccountInfo getLastId getSignatureStatus getTransactionCount requestAirdrop sendTransaction startSubscriptionChannel Subscription Websocket accountSubscribe accountUnsubscribe signatureSubscribe signatureUnsubscribe","breadcrumbs":"Appendix » Methods","id":"50","title":"Methods"},"51":{"body":"To make a JSON-RPC request, send an HTTP POST request with a Content-Type: application/json header. The JSON request data should contain 4 fields: jsonrpc , set to \"2.0\" id , a unique client-generated identifying integer method , a string containing the method to be invoked params , a JSON array of ordered parameter values Example using curl: curl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"getBalance\", \"params\":[\"83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri\"]}' 192.168.1.88:8899 The response output will be a JSON object with the following fields: jsonrpc , matching the request specification id , matching the request identifier result , requested data or success confirmation Requests can be sent in batches by sending an array of JSON-RPC request objects as the data for a single POST.","breadcrumbs":"Appendix » Request Formatting","id":"51","title":"Request Formatting"},"52":{"body":"Hash: A SHA-256 hash of a chunk of data. Pubkey: The public key of a Ed25519 key-pair. Signature: An Ed25519 signature of a chunk of data. Transaction: A Solana instruction signed by a client key-pair.","breadcrumbs":"Appendix » Definitions","id":"52","title":"Definitions"},"53":{"body":"","breadcrumbs":"Appendix » JSON RPC API Reference","id":"53","title":"JSON RPC API Reference"},"54":{"body":"Returns a transaction receipt Parameters: string - Signature of Transaction to confirm, as base-58 encoded string Results: boolean - Transaction status, true if Transaction is confirmed Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"confirmTransaction\", \"params\":[\"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW\"]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":true,\"id\":1}","breadcrumbs":"Appendix » confirmTransaction","id":"54","title":"confirmTransaction"},"55":{"body":"Returns the balance of the account of provided Pubkey Parameters: string - Pubkey of account to query, as base-58 encoded string Results: integer - quantity, as a signed 64-bit integer Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"getBalance\", \"params\":[\"83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri\"]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":0,\"id\":1}","breadcrumbs":"Appendix » getBalance","id":"55","title":"getBalance"},"56":{"body":"Returns all information associated with the account of provided Pubkey Parameters: string - Pubkey of account to query, as base-58 encoded string Results: The result field will be a JSON object with the following sub fields: tokens , number of tokens assigned to this account, as a signed 64-bit integer owner , array of 32 bytes representing the program this account has been assigned to userdata , array of bytes representing any userdata associated with the account executable , boolean indicating if the account contains a program (and is strictly read-only) loader , array of 32 bytes representing the loader for this program (if executable ), otherwise all Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"getAccountInfo\", \"params\":[\"2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST\"]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":{\"executable\":false,\"loader\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"owner\":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tokens\":1,\"userdata\":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},\"id\":1}","breadcrumbs":"Appendix » getAccountInfo","id":"56","title":"getAccountInfo"},"57":{"body":"Returns the last entry ID from the ledger Parameters: None Results: string - the ID of last entry, a Hash as base-58 encoded string Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getLastId\"}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":\"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC\",\"id\":1}","breadcrumbs":"Appendix » getLastId","id":"57","title":"getLastId"},"58":{"body":"Returns the status of a given signature. This method is similar to confirmTransaction but provides more resolution for error events. Parameters: string - Signature of Transaction to confirm, as base-58 encoded string Results: string - Transaction status: Confirmed - Transaction was successful SignatureNotFound - Unknown transaction ProgramRuntimeError - An error occurred in the program that processed this Transaction AccountInUse - Another Transaction had a write lock one of the Accounts specified in this Transaction. The Transaction may succeed if retried GenericFailure - Some other error occurred. Note : In the future new Transaction statuses may be added to this list. It's safe to assume that all new statuses will be more specific error conditions that previously presented as GenericFailure Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"getSignatureStatus\", \"params\":[\"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW\"]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":\"SignatureNotFound\",\"id\":1}","breadcrumbs":"Appendix » getSignatureStatus","id":"58","title":"getSignatureStatus"},"59":{"body":"Returns the current Transaction count from the ledger Parameters: None Results: integer - count, as unsigned 64-bit integer Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getTransactionCount\"}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":268,\"id\":1}","breadcrumbs":"Appendix » getTransactionCount","id":"59","title":"getTransactionCount"},"6":{"body":"A Verifiable Delay Function is conceptually a water clock where its water marks can be recorded and later verified that the water most certainly passed through. Anatoly describes the water clock analogy in detail here: water clock analogy The same technique has been used in Bitcoin since day one. The Bitcoin feature is called nLocktime and it can be used to postdate transactions using block height instead of a timestamp. As a Bitcoin client, you'd use block height instead of a timestamp if you don't trust the network. Block height turns out to be an instance of what's being called a Verifiable Delay Function in cryptography circles. It's a cryptographically secure way to say time has passed. In Solana, we use a far more granular verifiable delay function, a SHA 256 hash chain, to checkpoint the ledger and coordinate consensus. With it, we implement Optimistic Concurrency Control and are now well en route towards that theoretical limit of 710,000 transactions per second.","breadcrumbs":"Synchronization » Introduction to VDFs","id":"6","title":"Introduction to VDFs"},"60":{"body":"Requests an airdrop of tokens to a Pubkey Parameters: string - Pubkey of account to receive tokens, as base-58 encoded string integer - token quantity, as a signed 64-bit integer Results: string - Transaction Signature of airdrop, as base-58 encoded string Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"requestAirdrop\", \"params\":[\"83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri\", 50]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":\"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW\",\"id\":1}","breadcrumbs":"Appendix » requestAirdrop","id":"60","title":"requestAirdrop"},"61":{"body":"Creates new transaction Parameters: array - array of octets containing a fully-signed Transaction Results: string - Transaction Signature, as base-58 encoded string Example: // Request\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"sendTransaction\", \"params\":[[61, 98, 55, 49, 15, 187, 41, 215, 176, 49, 234, 229, 228, 77, 129, 221, 239, 88, 145, 227, 81, 158, 223, 123, 14, 229, 235, 247, 191, 115, 199, 71, 121, 17, 32, 67, 63, 209, 239, 160, 161, 2, 94, 105, 48, 159, 235, 235, 93, 98, 172, 97, 63, 197, 160, 164, 192, 20, 92, 111, 57, 145, 251, 6, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 13, 39, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 11, 12, 106, 49, 74, 226, 201, 16, 161, 192, 28, 84, 124, 97, 190, 201, 171, 186, 6, 18, 70, 142, 89, 185, 176, 154, 115, 61, 26, 163, 77, 1, 88, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]}' http://localhost:8899 // Result\n{\"jsonrpc\":\"2.0\",\"result\":\"2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b\",\"id\":1}","breadcrumbs":"Appendix » sendTransaction","id":"61","title":"sendTransaction"},"62":{"body":"After connect to the RPC PubSub websocket at ws://
/ : Submit subscription requests to the websocket using the methods below Multiple subscriptions may be active at once","breadcrumbs":"Appendix » Subscription Websocket","id":"62","title":"Subscription Websocket"},"63":{"body":"Subscribe to an account to receive notifications when the userdata for a given account public key changes Parameters: string - account Pubkey, as base-58 encoded string Results: integer - Subscription id (needed to unsubscribe) Example: // Request\n{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"accountSubscribe\", \"params\":[\"CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12\"]} // Result\n{\"jsonrpc\": \"2.0\",\"result\": 0,\"id\": 1} Notification Format: {\"jsonrpc\": \"2.0\",\"method\": \"accountNotification\", \"params\": {\"result\": {\"executable\":false,\"loader\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"owner\":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tokens\":1,\"userdata\":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},\"subscription\":0}}","breadcrumbs":"Appendix » accountSubscribe","id":"63","title":"accountSubscribe"},"64":{"body":"Unsubscribe from account userdata change notifications Parameters: integer - id of account Subscription to cancel Results: bool - unsubscribe success message Example: // Request\n{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"accountUnsubscribe\", \"params\":[0]} // Result\n{\"jsonrpc\": \"2.0\",\"result\": true,\"id\": 1}","breadcrumbs":"Appendix » accountUnsubscribe","id":"64","title":"accountUnsubscribe"},"65":{"body":"Subscribe to a transaction signature to receive notification when the transaction is confirmed On signatureNotification , the subscription is automatically cancelled Parameters: string - Transaction Signature, as base-58 encoded string Results: integer - subscription id (needed to unsubscribe) Example: // Request\n{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"signatureSubscribe\", \"params\":[\"2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b\"]} // Result\n{\"jsonrpc\": \"2.0\",\"result\": 0,\"id\": 1} Notification Format: {\"jsonrpc\": \"2.0\",\"method\": \"signatureNotification\", \"params\": {\"result\": \"Confirmed\",\"subscription\":0}}","breadcrumbs":"Appendix » signatureSubscribe","id":"65","title":"signatureSubscribe"},"66":{"body":"Unsubscribe from account userdata change notifications Parameters: integer - id of account subscription to cancel Results: bool - unsubscribe success message Example: // Request\n{\"jsonrpc\":\"2.0\", \"id\":1, \"method\":\"signatureUnsubscribe\", \"params\":[0]} // Result\n{\"jsonrpc\": \"2.0\",\"result\": true,\"id\": 1}","breadcrumbs":"Appendix » signatureUnsubscribe","id":"66","title":"signatureUnsubscribe"},"67":{"body":"The solana crate is distributed with a command-line interface tool","breadcrumbs":"Appendix » solana-wallet CLI","id":"67","title":"solana-wallet CLI"},"68":{"body":"Get Pubkey // Command\n$ solana-wallet address // Return\n Airdrop Tokens // Command\n$ solana-wallet airdrop 123 // Return\n\"Your balance is: 123\" Get Balance // Command\n$ solana-wallet balance // Return\n\"Your balance is: 123\" Confirm Transaction // Command\n$ solana-wallet confirm // Return\n\"Confirmed\" / \"Not found\" Deploy program // Command\n$ solana-wallet deploy // Return\n Unconditional Immediate Transfer // Command\n$ solana-wallet pay 123 // Return\n Post-Dated Transfer // Command\n$ solana-wallet pay 123 \\ --after 2018-12-24T23:59:00 --require-timestamp-from // Return\n{signature: , processId: } require-timestamp-from is optional. If not provided, the transaction will expect a timestamp signed by this wallet's secret key Authorized Transfer A third party must send a signature to unlock the tokens. // Command\n$ solana-wallet pay 123 \\ --require-signature-from // Return\n{signature: , processId: } Post-Dated and Authorized Transfer // Command\n$ solana-wallet pay 123 \\ --after 2018-12-24T23:59 --require-timestamp-from \\ --require-signature-from // Return\n{signature: , processId: } Multiple Witnesses // Command\n$ solana-wallet pay 123 \\ --require-signature-from \\ --require-signature-from // Return\n{signature: , processId: } Cancelable Transfer // Command\n$ solana-wallet pay 123 \\ --require-signature-from \\ --cancelable // Return\n{signature: , processId: } Cancel Transfer // Command\n$ solana-wallet cancel // Return\n Send Signature // Command\n$ solana-wallet send-signature // Return\n Indicate Elapsed Time Use the current system time: // Command\n$ solana-wallet send-timestamp // Return\n Or specify some other arbitrary timestamp: // Command\n$ solana-wallet send-timestamp --date 2018-12-24T23:59:00 // Return\n","breadcrumbs":"Appendix » Examples","id":"68","title":"Examples"},"69":{"body":"solana-wallet 0.11.0 USAGE: solana-wallet [OPTIONS] [SUBCOMMAND] FLAGS: -h, --help Prints help information -V, --version Prints version information OPTIONS: -k, --keypair /path/to/id.json -n, --network Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001 --proxy Address of TLS proxy --port Optional rpc-port configuration to connect to non-default nodes --timeout Max seconds to wait to get necessary gossip from the network SUBCOMMANDS: address Get your public key airdrop Request a batch of tokens balance Get your balance cancel Cancel a transfer confirm Confirm transaction by signature deploy Deploy a program get-transaction-count Get current transaction count help Prints this message or the help of the given subcommand(s) pay Send a payment send-signature Send a signature to authorize a transfer send-timestamp Send a timestamp to unlock a transfer solana-wallet-address Get your public key USAGE: solana-wallet address FLAGS: -h, --help Prints help information -V, --version Prints version information solana-wallet-airdrop Request a batch of tokens USAGE: solana-wallet airdrop FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: The number of tokens to request solana-wallet-balance Get your balance USAGE: solana-wallet balance FLAGS: -h, --help Prints help information -V, --version Prints version information solana-wallet-cancel Cancel a transfer USAGE: solana-wallet cancel FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: The process id of the transfer to cancel solana-wallet-confirm Confirm transaction by signature USAGE: solana-wallet confirm FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: The transaction signature to confirm solana-wallet-deploy Deploy a program USAGE: solana-wallet deploy FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: /path/to/program.o solana-wallet-get-transaction-count Get current transaction count USAGE: solana-wallet get-transaction-count FLAGS: -h, --help Prints help information -V, --version Prints version information solana-wallet-pay Send a payment USAGE: solana-wallet pay [FLAGS] [OPTIONS] FLAGS: --cancelable -h, --help Prints help information -V, --version Prints version information OPTIONS: --after A timestamp after which transaction will execute --require-timestamp-from Require timestamp from this third party --require-signature-from ... Any third party signatures required to unlock the tokens ARGS: The pubkey of recipient The number of tokens to send solana-wallet-send-signature Send a signature to authorize a transfer USAGE: solana-wallet send-signature FLAGS: -h, --help Prints help information -V, --version Prints version information ARGS: The pubkey of recipient The process id of the transfer to authorize solana-wallet-send-timestamp Send a timestamp to unlock a transfer USAGE: solana-wallet send-timestamp [OPTIONS] FLAGS: -h, --help Prints help information -V, --version Prints version information OPTIONS: --date Optional arbitrary timestamp to apply ARGS: The pubkey of recipient The process id of the transfer to unlock","breadcrumbs":"Appendix » Usage","id":"69","title":"Usage"},"7":{"body":"Proof of History overview","breadcrumbs":"Synchronization » Proof of History","id":"7","title":"Proof of History"},"8":{"body":"Most confusingly, a Proof of History (PoH) is more similar to a Verifiable Delay Function (VDF) than a Proof of Work or Proof of Stake consensus mechanism. The name unfortunately requires some historical context to understand. Proof of History was developed by Anatoly Yakovenko in November of 2017, roughly 6 months before we saw a paper using the term VDF . At that time, it was commonplace to publish new proofs of some desirable property used to build most any blockchain component. Some time shortly after, the crypto community began charting out all the different consensus mechanisms and because most of them started with \"Proof of\", the prefix became synonymous with a \"consensus\" suffix. Proof of History is not a consensus mechanism, but it is used to improve the performance of Solana's Proof of Stake consensus. It is also used to improve the performance of the replication and storage protocols. To minimize confusion, Solana may rebrand PoH to some flavor of the term VDF.","breadcrumbs":"Synchronization » Relationship to consensus mechanisms","id":"8","title":"Relationship to consensus mechanisms"},"9":{"body":"A desirable property of a VDF is that verification time is very fast. Solana's approach to verifying its delay function is proportional to the time it took to create it. Split over a 4000 core GPU, it is sufficiently fast for Solana's needs, but if you asked the authors the paper cited above, they might tell you (and have) that Solana's approach is algorithmically slow it shouldn't be called a VDF. We argue the term VDF should represent the category of verifiable delay functions and not just the subset with certain performance characteristics. Until that's resolved, Solana will likely continue using the term PoH for its application-specific VDF. Another difference between PoH and VDFs used only for tracking duration, is that PoH's hash chain includes hashes of any data the application observed. That data is a double-edged sword. On one side, the data \"proves history\" - that the data most certainly existed before hashes after it. On the side, it means the application can manipulate the hash chain by changing when the data is hashed. The PoH chain therefore does not serve as a good source of randomness whereas a VDF without that data could. Solana's leader selection algorithm (TODO: add link), for example, is derived only from the VDF height and not its hash at that height.","breadcrumbs":"Synchronization » Relationship to VDFs","id":"9","title":"Relationship to VDFs"}},"length":70,"save":true},"fields":["title","body","breadcrumbs"],"index":{"body":{"root":{"0":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},".":{"1":{"1":{".":{"0":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"40":{"tf":1.0},"61":{"tf":6.48074069840786}}},"1":{"/":{"1":{"0":{"0":{"0":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"15":{"tf":1.4142135623730951}}},"1":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"2":{"1":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":2,"docs":{"61":{"tf":1.0},"68":{"tf":3.0}}},"4":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"7":{".":{"0":{".":{"0":{".":{"1":{":":{"8":{"0":{"0":{"1":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":2,"docs":{"61":{"tf":1.0},"68":{"tf":1.7320508075688772}}},"3":{"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"4":{",":{"0":{"0":{"0":{"df":2,"docs":{"28":{"tf":1.0},"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"5":{"0":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"6":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"7":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":2,"docs":{"5":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"8":{"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"9":{"0":{"df":1,"docs":{"61":{"tf":1.0}}},"1":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"2":{".":{"1":{"6":{"8":{".":{"1":{".":{"8":{"8":{":":{"8":{"8":{"9":{"9":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"4":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"1":{"df":1,"docs":{"5":{"tf":1.0}}},"4":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":7,"docs":{"30":{"tf":1.0},"31":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}},"g":{"b":{"df":0,"docs":{},"p":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}},"k":{"df":1,"docs":{"31":{"tf":1.7320508075688772}}},"m":{"b":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}},"t":{"b":{"df":1,"docs":{"31":{"tf":2.0}}},"df":0,"docs":{}}},"2":{".":{"0":{"\"":{",":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"47":{"tf":1.0},"51":{"tf":1.0}}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"1":{"tf":1.0}}},"1":{"7":{"df":1,"docs":{"8":{"tf":1.0}}},"8":{"df":1,"docs":{"68":{"tf":1.7320508075688772}}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"1":{"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"2":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"3":{"4":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"5":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"4":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{},"t":{"2":{"3":{":":{"5":{"9":{":":{"0":{"0":{"df":1,"docs":{"68":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"68":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"5":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":2,"docs":{"52":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{".":{"4":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.0}}},"df":4,"docs":{"13":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"61":{"tf":1.0}}},"3":{"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"2":{"df":2,"docs":{"56":{"tf":1.4142135623730951},"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"4":{"0":{"0":{"0":{"df":1,"docs":{"9":{"tf":1.0}}},"df":1,"docs":{"1":{"tf":1.0}}},"df":3,"docs":{"1":{"tf":1.0},"5":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"df":2,"docs":{"27":{"tf":1.0},"51":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":2,"docs":{"27":{"tf":1.0},"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"df":2,"docs":{"60":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"8":{"df":9,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}},"6":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"4":{"df":4,"docs":{"55":{"tf":1.0},"56":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":3,"docs":{"31":{"tf":1.0},"61":{"tf":1.4142135623730951},"8":{"tf":1.0}}},"7":{"0":{"df":1,"docs":{"61":{"tf":1.0}}},"1":{"0":{",":{"0":{"0":{"0":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"8":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"9":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"9":{"0":{"df":1,"docs":{"15":{"tf":1.0}}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"a":{"'":{"df":1,"docs":{"16":{"tf":1.0}}},"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"28":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"v":{"df":5,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"34":{"tf":1.0},"9":{"tf":1.0}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"47":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"35":{"tf":1.0},"5":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"40":{"tf":1.0}}},":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":15,"docs":{"3":{"tf":1.0},"35":{"tf":2.6457513110645907},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"42":{"tf":2.0},"43":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":2.449489742783178},"58":{"tf":1.0},"60":{"tf":1.0},"63":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"66":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"63":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"63":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"64":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"u":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":2,"docs":{"27":{"tf":1.0},"36":{"tf":1.4142135623730951}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":2,"docs":{"25":{"tf":1.0},"62":{"tf":1.0}}}}}},"d":{"d":{"df":2,"docs":{"19":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"3":{"tf":1.0},"37":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":2.0}}}}}}},"df":3,"docs":{"19":{"tf":1.4142135623730951},"25":{"tf":1.0},"58":{"tf":1.0}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}}},"o":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"27":{"tf":1.0}}},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":3,"docs":{"60":{"tf":1.4142135623730951},"68":{"tf":1.4142135623730951},"69":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"df":4,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"5":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}}}}}}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":3,"docs":{"36":{"tf":2.23606797749979},"37":{"tf":1.0},"43":{"tf":1.0}}},"df":0,"docs":{},"w":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.0},"42":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"27":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"6":{"tf":1.0},"8":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":5,"docs":{"19":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"9":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"i":{"df":2,"docs":{"47":{"tf":1.0},"53":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"46":{"tf":1.0}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":9,"docs":{"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":4,"docs":{"12":{"tf":1.0},"47":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.7320508075688772}}},"df":2,"docs":{"5":{"tf":1.0},"69":{"tf":1.0}}}},"r":{"df":0,"docs":{},"o":{"a":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"5":{"tf":1.0},"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"v":{"df":1,"docs":{"31":{"tf":1.0}}},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}}}}}}}},"r":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":2.23606797749979},"3":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}}},"g":{"df":1,"docs":{"69":{"tf":2.6457513110645907}},"u":{"df":1,"docs":{"9":{"tf":1.0}}}},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}},"y":{"df":4,"docs":{"41":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.7320508075688772},"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"k":{"df":1,"docs":{"9":{"tf":1.0}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":4,"docs":{"40":{"tf":1.7320508075688772},"42":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772},"56":{"tf":1.4142135623730951}}}}},"o":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"37":{"tf":1.0},"42":{"tf":1.0},"56":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"58":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":2,"docs":{"3":{"tf":1.0},"36":{"tf":1.0}}}},"t":{"a":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"31":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"0":{"tf":1.0}}},"df":4,"docs":{"1":{"tf":1.0},"68":{"tf":1.4142135623730951},"69":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"65":{"tf":1.0}}}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"30":{"tf":1.0},"5":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"25":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}}}},"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"13":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":5,"docs":{"40":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"55":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":2.23606797749979}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"n":{"d":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"e":{"df":10,"docs":{"5":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"i":{"c":{"df":2,"docs":{"27":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"1":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"40":{"tf":1.0},"51":{"tf":1.0},"69":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":2,"docs":{"16":{"tf":1.4142135623730951},"17":{"tf":1.0}},"e":{"c":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":4,"docs":{"19":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.4142135623730951},"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":6,"docs":{"17":{"tf":1.0},"19":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"g":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"43":{"tf":1.0}}}},"w":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"27":{"tf":1.0},"62":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.0},"42":{"tf":1.0}}}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":6,"docs":{"1":{"tf":1.0},"23":{"tf":1.0},"3":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}}}},"df":4,"docs":{"55":{"tf":1.0},"56":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"1":{"tf":1.4142135623730951},"10":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":5,"docs":{"10":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"6":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"54":{"tf":1.0},"56":{"tf":1.0}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"16":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0}}},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"f":{"df":1,"docs":{"34":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"a":{"d":{"c":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"34":{"tf":1.0}}},"df":0,"docs":{}}},"df":2,"docs":{"5":{"tf":1.0},"56":{"tf":1.7320508075688772}}}}}},"c":{"/":{"c":{"+":{"+":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"a":{"df":1,"docs":{"34":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"12":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":8,"docs":{"10":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"27":{"tf":1.0},"35":{"tf":1.0},"43":{"tf":1.4142135623730951},"6":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":6,"docs":{"15":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":2.6457513110645907}}}}},"df":0,"docs":{}},"p":{"a":{"c":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"d":{"df":2,"docs":{"20":{"tf":1.0},"28":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"e":{"df":2,"docs":{"16":{"tf":1.4142135623730951},"20":{"tf":1.0}}},"t":{"df":1,"docs":{"15":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"36":{"tf":1.0}}}}},"b":{"c":{"_":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"28":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}},"r":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"1":{"tf":1.0},"27":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"6":{"tf":1.0},"9":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"13":{"tf":2.0},"33":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":1.7320508075688772}}}},"n":{"df":0,"docs":{},"g":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0},"9":{"tf":1.0}}}},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"10":{"tf":1.0}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"0":{"tf":1.0}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"6":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"33":{"tf":1.0},"34":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"s":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"13":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":2,"docs":{"31":{"tf":1.0},"52":{"tf":1.4142135623730951}}}}}},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"9":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":1,"docs":{"67":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":2,"docs":{"34":{"tf":1.0},"43":{"tf":1.0}}},"df":9,"docs":{"29":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"34":{"tf":1.7320508075688772},"35":{"tf":1.7320508075688772},"37":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.0}},"’":{"df":1,"docs":{"33":{"tf":1.0}}}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"23":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.7320508075688772},"34":{"tf":1.4142135623730951}}}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":4,"docs":{"3":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}},"m":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"67":{"tf":1.0},"68":{"tf":3.872983346207417}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"3":{"tf":1.0}}}},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"34":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}},"s":{"df":2,"docs":{"36":{"tf":1.0},"43":{"tf":1.0}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":3,"docs":{"25":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":6,"docs":{"1":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"33":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"58":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"69":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"65":{"tf":1.0},"68":{"tf":1.7320508075688772},"69":{"tf":2.449489742783178}},"e":{"d":{"\"":{",":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\"":{":":{"0":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"50":{"tf":1.0},"54":{"tf":1.0},"58":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"8":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"62":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":2,"docs":{"6":{"tf":1.0},"8":{"tf":2.449489742783178}}}}}},"i":{"d":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"17":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":7,"docs":{"13":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":1.0},"46":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.0},"61":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":12,"docs":{"0":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":6,"docs":{"12":{"tf":1.0},"15":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.0},"44":{"tf":1.0},"9":{"tf":1.0}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":4,"docs":{"23":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"47":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":5,"docs":{"20":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"39":{"tf":1.0},"9":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"27":{"tf":1.4142135623730951},"30":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":8,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.0},"13":{"tf":1.7320508075688772},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":2.6457513110645907},"59":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}}},"p":{"df":0,"docs":{},"u":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"67":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":10,"docs":{"17":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":2.0},"34":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"61":{"tf":1.0},"9":{"tf":1.0}},"e":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"42":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"8":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":2,"docs":{"31":{"tf":1.0},"6":{"tf":1.0}},"i":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":9,"docs":{"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":11,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.0},"4":{"tf":1.4142135623730951},"59":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}}}},"y":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"_":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.0}}}},"b":{"a":{"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":11,"docs":{"13":{"tf":2.449489742783178},"19":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":2.449489742783178},"28":{"tf":1.0},"31":{"tf":1.0},"35":{"tf":1.4142135623730951},"40":{"tf":1.0},"51":{"tf":1.7320508075688772},"52":{"tf":1.4142135623730951},"9":{"tf":2.449489742783178}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.7320508075688772},"30":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"68":{"tf":1.7320508075688772},"69":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}}}},"y":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"i":{"d":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"48":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"37":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"52":{"tf":1.0}}}}}}},"l":{"a":{"df":0,"docs":{},"y":{"df":3,"docs":{"6":{"tf":1.7320508075688772},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"27":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}}}}}}},"n":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"36":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":2,"docs":{"68":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":3,"docs":{"13":{"tf":1.0},"31":{"tf":1.0},"9":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":4,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"42":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":3,"docs":{"0":{"tf":1.0},"19":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":3,"docs":{"1":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"25":{"tf":1.0},"8":{"tf":1.0}}}}}}}},"i":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"34":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"10":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"5":{"tf":2.23606797749979},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"s":{"c":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"13":{"tf":1.0},"35":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"k":{"df":1,"docs":{"20":{"tf":1.0}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"28":{"tf":1.0},"5":{"tf":2.23606797749979},"67":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"0":{"tf":1.0},"31":{"tf":1.0}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"29":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.4142135623730951}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"1":{"tf":1.0}}}},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":2.0}}}}}},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"13":{"tf":1.7320508075688772},"27":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"36":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"g":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}},"1":{"df":1,"docs":{"13":{"tf":1.0}}},"2":{"df":1,"docs":{"13":{"tf":1.0}}},"3":{"df":1,"docs":{"13":{"tf":1.0}}},"4":{"df":1,"docs":{"13":{"tf":1.0}}},"5":{"df":1,"docs":{"13":{"tf":1.0}}},"a":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"19":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"28":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"d":{"2":{"5":{"5":{"1":{"9":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":1,"docs":{"13":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"28":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}}},"g":{"df":2,"docs":{"48":{"tf":1.0},"49":{"tf":1.0}}},"l":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":1,"docs":{"68":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":1,"docs":{"34":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"n":{"c":{"df":0,"docs":{},"o":{"d":{"df":11,"docs":{"13":{"tf":1.0},"42":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":2.23606797749979},"31":{"tf":2.0}}}}}}},"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"48":{"tf":1.0},"49":{"tf":1.0}}}}}}}},"df":1,"docs":{"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"36":{"tf":1.0},"39":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"29":{"tf":1.0},"35":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":10,"docs":{"13":{"tf":2.0},"16":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":2.449489742783178},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"p":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"4":{"tf":1.0}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"28":{"tf":1.0},"40":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"0":{"tf":1.0},"3":{"tf":1.0}}}}}},"t":{"c":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}},"x":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"1":{"tf":1.0},"13":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":18,"docs":{"14":{"tf":1.0},"19":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"68":{"tf":1.0},"9":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"\"":{":":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"\"":{":":{"1":{",":{"\"":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"\"":{":":{"[":{"3":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"5":{"0":{",":{"4":{"8":{",":{"5":{"3":{",":{"4":{"8":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"8":{"4":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"9":{"0":{",":{"2":{"5":{"2":{",":{"1":{"0":{",":{"7":{",":{"2":{"8":{",":{"2":{"4":{"6":{",":{"1":{"4":{"0":{",":{"8":{"8":{",":{"1":{"7":{"7":{",":{"9":{"8":{",":{"8":{"2":{",":{"1":{"0":{",":{"2":{"2":{"7":{",":{"8":{"9":{",":{"8":{"1":{",":{"1":{"8":{",":{"3":{"0":{",":{"1":{"9":{"4":{",":{"1":{"0":{"1":{",":{"1":{"9":{"9":{",":{"1":{"6":{",":{"1":{"1":{",":{"7":{"3":{",":{"1":{"3":{"3":{",":{"2":{"0":{",":{"2":{"4":{"6":{",":{"6":{"2":{",":{"1":{"1":{"4":{",":{"3":{"9":{",":{"2":{"0":{",":{"1":{"1":{"3":{",":{"1":{"8":{"9":{",":{"3":{"2":{",":{"5":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"4":{"7":{",":{"1":{"5":{",":{"3":{"6":{",":{"1":{"0":{"2":{",":{"1":{"6":{"7":{",":{"8":{"3":{",":{"2":{"2":{"5":{",":{"4":{"2":{",":{"1":{"3":{"3":{",":{"1":{"2":{"7":{",":{"8":{"2":{",":{"3":{"4":{",":{"3":{"6":{",":{"2":{"2":{"4":{",":{"2":{"0":{"7":{",":{"1":{"3":{"0":{",":{"1":{"0":{"9":{",":{"2":{"3":{"0":{",":{"2":{"2":{"4":{",":{"1":{"8":{"8":{",":{"1":{"6":{"3":{",":{"3":{"3":{",":{"2":{"1":{"3":{",":{"1":{"3":{",":{"5":{",":{"1":{"1":{"7":{",":{"2":{"1":{"1":{",":{"2":{"5":{"1":{",":{"6":{"5":{",":{"1":{"5":{"9":{",":{"1":{"9":{"7":{",":{"5":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{"df":0,"docs":{},"}":{",":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\"":{":":{"0":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":12,"docs":{"1":{"tf":1.0},"3":{"tf":1.4142135623730951},"33":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"40":{"tf":2.23606797749979},"41":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"56":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"1":{"tf":1.0},"20":{"tf":1.4142135623730951},"42":{"tf":1.0},"9":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"28":{"tf":1.0},"68":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"r":{"df":2,"docs":{"13":{"tf":1.0},"16":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.0}}}}}}}},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.7320508075688772}}}},"r":{"df":1,"docs":{"6":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.7320508075688772}},"l":{"df":1,"docs":{"1":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"51":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"3":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"l":{"df":2,"docs":{"12":{"tf":1.0},"16":{"tf":1.7320508075688772}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.4142135623730951},"3":{"tf":1.0}}}},"d":{"df":3,"docs":{"25":{"tf":1.0},"46":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"13":{"tf":1.0},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}}},"l":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":3.4641016151377544}}},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"13":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"19":{"tf":1.7320508075688772}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":9,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.0},"4":{"tf":1.0},"40":{"tf":1.0},"46":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0}}}}}},"r":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":2,"docs":{"13":{"tf":2.449489742783178},"16":{"tf":1.0}}},"m":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"45":{"tf":1.0},"51":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"1":{"tf":1.0},"25":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"3":{"tf":1.0},"37":{"tf":1.0}},"i":{"df":1,"docs":{"61":{"tf":1.0}}},"n":{"df":0,"docs":{},"o":{"d":{"df":6,"docs":{"10":{"tf":1.7320508075688772},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"3":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":5,"docs":{"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.7320508075688772},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"0":{"tf":1.0},"1":{"tf":1.0}}}}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"4":{"tf":1.4142135623730951},"44":{"tf":1.0},"58":{"tf":1.0}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"1":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.4142135623730951},"15":{"tf":1.0},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.0},"51":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":2,"docs":{"50":{"tf":1.0},"56":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"50":{"tf":1.0},"55":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"50":{"tf":1.0},"57":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":2,"docs":{"50":{"tf":1.0},"58":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"50":{"tf":1.0},"59":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"a":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"5":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"47":{"tf":1.0}},"n":{"df":4,"docs":{"19":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0}}}},"df":1,"docs":{"31":{"tf":1.0}},"e":{"df":1,"docs":{"1":{"tf":1.0}}},"o":{"d":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951}}}}}}},"p":{"df":0,"docs":{},"u":{"df":4,"docs":{"20":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"40":{"tf":1.0},"43":{"tf":2.23606797749979}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"h":{".":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"h":{"df":10,"docs":{"11":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.7320508075688772},"27":{"tf":2.0},"28":{"tf":1.0},"31":{"tf":3.3166247903554},"52":{"tf":1.4142135623730951},"57":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":2.449489742783178}}}}},"df":10,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"69":{"tf":3.3166247903554}},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"6":{"tf":1.7320508075688772},"9":{"tf":1.4142135623730951}}}}}},"l":{"df":0,"docs":{},"p":{"df":1,"docs":{"69":{"tf":4.898979485566356}}}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"25":{"tf":1.0},"6":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"5":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}},"i":{"df":4,"docs":{"28":{"tf":1.0},"7":{"tf":1.4142135623730951},"8":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"35":{"tf":1.0}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"o":{"d":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{":":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{":":{"/":{"/":{"1":{"9":{"2":{".":{"1":{"6":{"8":{".":{"1":{".":{"8":{"8":{":":{"8":{"8":{"9":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"8":{"8":{"9":{"9":{"df":9,"docs":{"48":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"47":{"tf":1.0},"48":{"tf":1.0},"51":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{".":{"df":1,"docs":{"34":{"tf":1.0}}},"d":{"\"":{":":{"1":{"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":7,"docs":{"51":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.7320508075688772}},"e":{"a":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":2.23606797749979},"31":{"tf":4.123105625617661}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":2,"docs":{"35":{"tf":1.0},"51":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"68":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":6,"docs":{"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"8":{"tf":1.4142135623730951}}}}}}},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"3":{"tf":1.0},"36":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"34":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"i":{"c":{"df":2,"docs":{"56":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"56":{"tf":1.0},"69":{"tf":4.69041575982343}}}}}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"31":{"tf":1.0},"43":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"19":{"tf":1.0},"20":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"47":{"tf":1.0}}},"df":0,"docs":{}},"t":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":1,"docs":{"30":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"1":{"tf":1.7320508075688772},"3":{"tf":1.7320508075688772},"36":{"tf":2.0},"37":{"tf":1.4142135623730951},"4":{"tf":1.0},"40":{"tf":1.0},"43":{"tf":1.4142135623730951},"52":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"42":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":9,"docs":{"51":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.0},"59":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.0},"37":{"tf":1.0},"47":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"a":{"c":{"df":4,"docs":{"37":{"tf":1.0},"42":{"tf":1.4142135623730951},"47":{"tf":1.0},"67":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.4142135623730951},"34":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"33":{"tf":1.0},"6":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"51":{"tf":1.0}}},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"41":{"tf":1.0}}}}}}},"t":{"'":{"df":5,"docs":{"13":{"tf":1.0},"27":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":4,"docs":{"31":{"tf":1.0},"37":{"tf":1.4142135623730951},"43":{"tf":1.0},"5":{"tf":1.0}}}}}}}},"j":{".":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"47":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"b":{"df":2,"docs":{"13":{"tf":1.0},"19":{"tf":1.0}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"46":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":4,"docs":{"47":{"tf":1.4142135623730951},"51":{"tf":2.23606797749979},"53":{"tf":1.0},"56":{"tf":1.0}},"r":{"df":0,"docs":{},"p":{"c":{"\"":{":":{"\"":{"2":{".":{"0":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":4,"docs":{"57":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"\"":{":":{"\"":{"2":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"6":{"c":{"b":{"8":{"df":0,"docs":{},"v":{"a":{"a":{"d":{"9":{"3":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"6":{"df":0,"docs":{},"v":{"d":{"8":{"df":0,"docs":{},"p":{"6":{"7":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"q":{"df":0,"docs":{},"z":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"x":{"4":{"7":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"g":{"9":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"1":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"3":{"a":{"2":{"df":0,"docs":{},"g":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"7":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"j":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"y":{"9":{"b":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"5":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"v":{"df":0,"docs":{},"z":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"x":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"9":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"j":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"j":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"8":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"q":{"df":0,"docs":{},"p":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"4":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"3":{"df":0,"docs":{},"z":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"z":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"v":{"6":{"df":0,"docs":{},"u":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"k":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"w":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"7":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"3":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"7":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"9":{"df":0,"docs":{},"j":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"2":{"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"w":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"3":{"df":0,"docs":{},"z":{"6":{"9":{"df":0,"docs":{},"x":{"df":0,"docs":{},"m":{"1":{"df":0,"docs":{},"z":{"df":0,"docs":{},"e":{"3":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"6":{"df":0,"docs":{},"j":{"c":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"0":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"55":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"6":{"8":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"54":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"{":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"\"":{":":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"\"":{":":{"1":{",":{"\"":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"\"":{":":{"[":{"3":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"5":{"0":{",":{"4":{"8":{",":{"5":{"3":{",":{"4":{"8":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"8":{"4":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"9":{"0":{",":{"2":{"5":{"2":{",":{"1":{"0":{",":{"7":{",":{"2":{"8":{",":{"2":{"4":{"6":{",":{"1":{"4":{"0":{",":{"8":{"8":{",":{"1":{"7":{"7":{",":{"9":{"8":{",":{"8":{"2":{",":{"1":{"0":{",":{"2":{"2":{"7":{",":{"8":{"9":{",":{"8":{"1":{",":{"1":{"8":{",":{"3":{"0":{",":{"1":{"9":{"4":{",":{"1":{"0":{"1":{",":{"1":{"9":{"9":{",":{"1":{"6":{",":{"1":{"1":{",":{"7":{"3":{",":{"1":{"3":{"3":{",":{"2":{"0":{",":{"2":{"4":{"6":{",":{"6":{"2":{",":{"1":{"1":{"4":{",":{"3":{"9":{",":{"2":{"0":{",":{"1":{"1":{"3":{",":{"1":{"8":{"9":{",":{"3":{"2":{",":{"5":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"4":{"7":{",":{"1":{"5":{",":{"3":{"6":{",":{"1":{"0":{"2":{",":{"1":{"6":{"7":{",":{"8":{"3":{",":{"2":{"2":{"5":{",":{"4":{"2":{",":{"1":{"3":{"3":{",":{"1":{"2":{"7":{",":{"8":{"2":{",":{"3":{"4":{",":{"3":{"6":{",":{"2":{"2":{"4":{",":{"2":{"0":{"7":{",":{"1":{"3":{"0":{",":{"1":{"0":{"9":{",":{"2":{"3":{"0":{",":{"2":{"2":{"4":{",":{"1":{"8":{"8":{",":{"1":{"6":{"3":{",":{"3":{"3":{",":{"2":{"1":{"3":{",":{"1":{"3":{",":{"5":{",":{"1":{"1":{"7":{",":{"2":{"1":{"1":{",":{"2":{"5":{"1":{",":{"6":{"5":{",":{"1":{"5":{"9":{",":{"1":{"9":{"7":{",":{"5":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{"df":0,"docs":{},"}":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"56":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":5,"docs":{"51":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"25":{"tf":1.0}}}}}}}}},"k":{"df":3,"docs":{"15":{"tf":2.0},"17":{"tf":1.0},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":2,"docs":{"16":{"tf":1.0},"27":{"tf":1.0}}}},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}}},"y":{"df":13,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"35":{"tf":1.4142135623730951},"37":{"tf":1.0},"4":{"tf":1.4142135623730951},"41":{"tf":1.0},"5":{"tf":1.0},"52":{"tf":1.7320508075688772},"63":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"p":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":4,"docs":{"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"4":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"4":{"tf":1.0}}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"35":{"tf":1.0}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"5":{"tf":1.0}},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"l":{".":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"1":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"2":{"df":1,"docs":{"13":{"tf":2.0}}},"3":{"df":1,"docs":{"13":{"tf":2.8284271247461903}}},"4":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"g":{"df":3,"docs":{"1":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"15":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"31":{"tf":1.0},"57":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.7320508075688772},"3":{"tf":1.0}}},"df":13,"docs":{"10":{"tf":2.6457513110645907},"11":{"tf":1.7320508075688772},"12":{"tf":3.1622776601683795},"13":{"tf":3.4641016151377544},"15":{"tf":1.4142135623730951},"16":{"tf":2.23606797749979},"17":{"tf":2.23606797749979},"20":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"4":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":12,"docs":{"13":{"tf":1.7320508075688772},"20":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"3":{"tf":2.23606797749979},"31":{"tf":1.7320508075688772},"45":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0}}}}}},"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"25":{"tf":1.7320508075688772}}}}}},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"47":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"3":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"1":{"tf":1.0}}}},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"19":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"67":{"tf":1.0}}},"k":{"df":2,"docs":{"13":{"tf":1.0},"9":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"37":{"tf":1.4142135623730951},"58":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"31":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"df":1,"docs":{"34":{"tf":1.0}}}}},"o":{"a":{"d":{"df":5,"docs":{"19":{"tf":2.6457513110645907},"34":{"tf":1.0},"35":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"56":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"58":{"tf":1.0}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"15":{"tf":1.0}}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"31":{"tf":1.0},"44":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.0},"39":{"tf":1.0}}}}}},"m":{"a":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"1":{"tf":1.0},"3":{"tf":1.0},"38":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{}}}},"k":{"df":0,"docs":{},"e":{"df":4,"docs":{"1":{"tf":1.0},"19":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"51":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"i":{"df":2,"docs":{"30":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}}}}},"p":{"df":2,"docs":{"39":{"tf":1.0},"41":{"tf":1.0}}},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"3":{"tf":1.0},"51":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"46":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.0}}}}}},"x":{"df":1,"docs":{"69":{"tf":1.0}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"19":{"tf":1.0},"27":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"y":{"b":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}},"df":4,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"15":{"tf":2.6457513110645907},"17":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"n":{"df":3,"docs":{"1":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"0":{"tf":1.0},"1":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"10":{"tf":1.0},"8":{"tf":2.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"36":{"tf":2.23606797749979},"37":{"tf":1.0},"38":{"tf":1.0},"43":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"\"":{":":{"\"":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"51":{"tf":1.0},"55":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"66":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"'":{"df":1,"docs":{"5":{"tf":1.0}}},"df":6,"docs":{"47":{"tf":1.0},"5":{"tf":2.0},"50":{"tf":1.0},"51":{"tf":1.4142135623730951},"58":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"4":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"29":{"tf":1.0}}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"27":{"tf":1.0},"8":{"tf":1.0}}}}},"p":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"4":{"tf":1.0}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":2,"docs":{"10":{"tf":1.0},"20":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":3,"docs":{"40":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"8":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":6,"docs":{"3":{"tf":1.0},"31":{"tf":1.7320508075688772},"5":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0},"8":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"42":{"tf":2.0}}}}},"u":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":7,"docs":{"25":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"43":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.0}}}}}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":2,"docs":{"17":{"tf":1.7320508075688772},"8":{"tf":1.0}}}}},"c":{"df":0,"docs":{},"p":{"df":1,"docs":{"23":{"tf":1.0}}}},"df":3,"docs":{"11":{"tf":1.4142135623730951},"17":{"tf":1.0},"69":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"37":{"tf":1.0},"5":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"d":{"df":9,"docs":{"13":{"tf":1.0},"19":{"tf":1.0},"27":{"tf":1.0},"31":{"tf":1.4142135623730951},"42":{"tf":1.0},"43":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":14,"docs":{"1":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"13":{"tf":2.23606797749979},"15":{"tf":1.0},"17":{"tf":1.0},"20":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"27":{"tf":2.0},"28":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951},"6":{"tf":1.0},"69":{"tf":1.7320508075688772}}}}}}},"w":{"df":7,"docs":{"13":{"tf":1.4142135623730951},"17":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"58":{"tf":1.4142135623730951},"61":{"tf":1.0},"8":{"tf":1.0}}},"x":{"df":0,"docs":{},"t":{"df":5,"docs":{"10":{"tf":1.0},"12":{"tf":1.4142135623730951},"16":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"o":{"d":{"df":0,"docs":{},"e":{"df":15,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":2.0},"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"17":{"tf":2.23606797749979},"23":{"tf":1.0},"25":{"tf":1.7320508075688772},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"47":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"69":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}},"e":{"df":2,"docs":{"57":{"tf":1.0},"59":{"tf":1.0}}}},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"43":{"tf":1.0},"58":{"tf":1.0}}},"h":{"df":1,"docs":{"0":{"tf":1.0}}},"i":{"df":0,"docs":{},"f":{"df":4,"docs":{"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}}},"w":{"df":1,"docs":{"6":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"3":{"tf":1.0}}}},"u":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":1,"docs":{"27":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"df":7,"docs":{"17":{"tf":2.23606797749979},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"56":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}},"df":1,"docs":{"69":{"tf":2.23606797749979}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":5,"docs":{"12":{"tf":1.4142135623730951},"13":{"tf":2.23606797749979},"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}},"c":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":4,"docs":{"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"19":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"c":{"df":5,"docs":{"13":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"5":{"tf":1.0},"62":{"tf":1.0}}},"df":12,"docs":{"13":{"tf":1.0},"15":{"tf":1.0},"19":{"tf":2.0},"20":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"5":{"tf":2.0},"58":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":1.0}}},"p":{"a":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"15":{"tf":1.0},"19":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.7320508075688772}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"19":{"tf":1.0},"28":{"tf":1.0}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"12":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":2.8284271247461903}}}}}}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"16":{"tf":1.0},"28":{"tf":1.0},"51":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"16":{"tf":1.0},"25":{"tf":1.0},"6":{"tf":1.0},"8":{"tf":1.0}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"20":{"tf":1.0},"51":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"9":{"tf":1.0}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":2,"docs":{"25":{"tf":1.0},"7":{"tf":1.0}}}}}}}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"35":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"56":{"tf":1.0}}}}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":2,"docs":{"40":{"tf":1.0},"41":{"tf":1.0}}}},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"8":{"tf":1.0},"9":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":2,"docs":{"3":{"tf":1.0},"40":{"tf":1.0}},"i":{"df":0,"docs":{},"z":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"m":{"df":3,"docs":{"51":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":13,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}}},"s":{"\"":{":":{"[":{"\"":{"2":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"6":{"c":{"b":{"8":{"df":0,"docs":{},"v":{"a":{"a":{"d":{"9":{"3":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"6":{"df":0,"docs":{},"v":{"d":{"8":{"df":0,"docs":{},"p":{"6":{"7":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"q":{"df":0,"docs":{},"z":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"x":{"4":{"7":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"g":{"9":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"1":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"3":{"a":{"2":{"df":0,"docs":{},"g":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"7":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"j":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"y":{"9":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"g":{"df":0,"docs":{},"v":{"df":0,"docs":{},"k":{"df":0,"docs":{},"y":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"h":{"b":{"2":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"n":{"3":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"f":{"df":0,"docs":{},"k":{"df":0,"docs":{},"x":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"h":{"df":0,"docs":{},"p":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"56":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}}},"5":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"v":{"df":0,"docs":{},"z":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"x":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"9":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"j":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"j":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"8":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"q":{"df":0,"docs":{},"p":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"4":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"3":{"df":0,"docs":{},"z":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"z":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"v":{"6":{"df":0,"docs":{},"u":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"k":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"w":{"df":2,"docs":{"54":{"tf":1.0},"58":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"8":{"3":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"t":{"2":{"df":0,"docs":{},"h":{"5":{"df":0,"docs":{},"u":{"1":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"d":{"df":0,"docs":{},"q":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"6":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"y":{"3":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"51":{"tf":1.0},"55":{"tf":1.0},"60":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"m":{"7":{"8":{"c":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"8":{"df":0,"docs":{},"o":{"3":{"df":0,"docs":{},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"h":{"df":0,"docs":{},"x":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"z":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"4":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"df":0,"docs":{},"k":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"x":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"k":{"df":0,"docs":{},"n":{"df":0,"docs":{},"h":{"1":{"2":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"0":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}}},"[":{"6":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"29":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}},"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"t":{"df":3,"docs":{"13":{"tf":1.0},"15":{"tf":1.7320508075688772},"16":{"tf":1.4142135623730951}}}}}},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"34":{"tf":1.0},"35":{"tf":1.0},"43":{"tf":1.0},"6":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"i":{"d":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{".":{"df":0,"docs":{},"o":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.7320508075688772}}}},"y":{"df":2,"docs":{"68":{"tf":2.449489742783178},"69":{"tf":1.7320508075688772}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":10,"docs":{"1":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"27":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.4142135623730951},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"0":{"tf":1.0},"1":{"tf":1.7320508075688772},"19":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"11":{"tf":1.0},"27":{"tf":1.0},"31":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"35":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}}}}},"t":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.0},"35":{"tf":1.4142135623730951}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":1,"docs":{"27":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"19":{"tf":2.6457513110645907},"20":{"tf":1.7320508075688772},"40":{"tf":1.0}}}}}}}},"l":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"23":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"h":{"'":{"df":1,"docs":{"9":{"tf":1.0}}},"df":8,"docs":{"11":{"tf":1.0},"12":{"tf":2.0},"13":{"tf":3.0},"17":{"tf":2.0},"28":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"8":{"tf":1.4142135623730951},"9":{"tf":1.7320508075688772}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"13":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"41":{"tf":1.4142135623730951},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"41":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.0},"29":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":4,"docs":{"28":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.23606797749979}}}},"t":{"df":3,"docs":{"48":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"5":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"d":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"df":10,"docs":{"51":{"tf":1.7320508075688772},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"4":{"tf":1.0}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"8":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":3,"docs":{"13":{"tf":1.0},"27":{"tf":1.4142135623730951},"58":{"tf":1.0}}}}}},"v":{"df":1,"docs":{"13":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":1,"docs":{"13":{"tf":2.0}},"s":{"df":2,"docs":{"34":{"tf":1.0},"58":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":4.795831523312719}}}}},"o":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"25":{"tf":1.4142135623730951}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"68":{"tf":3.0},"69":{"tf":2.449489742783178}}},"df":0,"docs":{}}},"df":9,"docs":{"11":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.0},"21":{"tf":1.0},"25":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":3.0},"58":{"tf":1.0},"69":{"tf":1.7320508075688772}},"i":{"d":{"df":1,"docs":{"68":{"tf":2.23606797749979}}},"df":0,"docs":{}}}}}},"d":{"df":0,"docs":{},"u":{"c":{"df":3,"docs":{"10":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"'":{"df":1,"docs":{"41":{"tf":1.0}}},"_":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"68":{"tf":1.0}}},"df":0,"docs":{}}},"df":15,"docs":{"1":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951},"35":{"tf":2.0},"37":{"tf":2.23606797749979},"38":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.7320508075688772},"43":{"tf":2.23606797749979},"56":{"tf":1.7320508075688772},"58":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.0}}}}}}}}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"13":{"tf":1.0},"19":{"tf":1.0}}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.4142135623730951},"28":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"f":{"df":8,"docs":{"10":{"tf":1.0},"27":{"tf":2.23606797749979},"28":{"tf":2.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.6457513110645907},"7":{"tf":1.4142135623730951},"8":{"tf":2.8284271247461903}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":5,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"5":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}}},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"30":{"tf":1.0},"31":{"tf":1.0},"8":{"tf":1.0}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"e":{"df":3,"docs":{"29":{"tf":1.0},"31":{"tf":1.0},"9":{"tf":1.0}}},"i":{"d":{"df":7,"docs":{"29":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}},"x":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}}},"u":{"b":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":11,"docs":{"3":{"tf":1.4142135623730951},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"52":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"63":{"tf":1.0},"68":{"tf":4.242640687119285},"69":{"tf":3.3166247903554}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":8,"docs":{"27":{"tf":1.0},"3":{"tf":1.4142135623730951},"35":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.0},"52":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.4142135623730951}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":2,"docs":{"49":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"55":{"tf":1.0},"60":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"55":{"tf":1.0},"56":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}}}}}}}},"r":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":5,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"31":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"df":0,"docs":{},"k":{"df":2,"docs":{"15":{"tf":1.0},"16":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"13":{"tf":1.0},"19":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"12":{"tf":1.0},"15":{"tf":1.4142135623730951}}}},"d":{"df":2,"docs":{"35":{"tf":1.4142135623730951},"56":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}},"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"v":{"df":3,"docs":{"60":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.7320508075688772}}}}},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"d":{"df":3,"docs":{"25":{"tf":1.0},"28":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}},"df":1,"docs":{"15":{"tf":1.0}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"25":{"tf":1.0},"46":{"tf":1.0},"53":{"tf":1.0}}}}},"g":{"a":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"1":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"1":{"tf":1.0}}}}},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"z":{"df":0,"docs":{},"v":{"df":1,"docs":{"69":{"tf":1.0}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"i":{"c":{"df":8,"docs":{"25":{"tf":1.4142135623730951},"27":{"tf":2.23606797749979},"28":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":3.0},"5":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":3,"docs":{"13":{"tf":1.7320508075688772},"56":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"50":{"tf":1.0},"60":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":17,"docs":{"35":{"tf":1.0},"47":{"tf":1.0},"51":{"tf":3.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"r":{"df":9,"docs":{"13":{"tf":1.0},"27":{"tf":2.23606797749979},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.7320508075688772},"5":{"tf":1.0},"68":{"tf":2.8284271247461903},"69":{"tf":2.0},"8":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"4":{"tf":1.4142135623730951}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"v":{"df":2,"docs":{"16":{"tf":1.0},"9":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":4,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0},"51":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":1,"docs":{"5":{"tf":1.0}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":16,"docs":{"11":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"36":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.7320508075688772},"57":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"59":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951},"63":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"65":{"tf":1.7320508075688772},"66":{"tf":1.4142135623730951}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"58":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":7,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"68":{"tf":3.872983346207417}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":2.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"o":{"a":{"d":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.0},"12":{"tf":1.4142135623730951},"13":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"n":{"d":{"df":4,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"t":{"df":2,"docs":{"39":{"tf":1.0},"6":{"tf":1.0}}}}},"p":{"c":{"df":7,"docs":{"47":{"tf":1.7320508075688772},"48":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.4142135623730951},"53":{"tf":1.0},"62":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"40":{"tf":1.0}}}},"n":{"df":3,"docs":{"10":{"tf":1.0},"19":{"tf":1.0},"44":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":7,"docs":{"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"43":{"tf":2.23606797749979}}}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":3,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"58":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":11,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":2.449489742783178},"5":{"tf":2.449489742783178},"6":{"tf":1.0}}},"p":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"13":{"tf":1.0}}}},"w":{"df":2,"docs":{"31":{"tf":1.0},"8":{"tf":1.0}}}},"c":{"a":{"df":0,"docs":{},"l":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"25":{"tf":1.0}}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":3,"docs":{"12":{"tf":2.23606797749979},"17":{"tf":1.4142135623730951},"4":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":4,"docs":{"13":{"tf":2.0},"16":{"tf":1.7320508075688772},"42":{"tf":1.4142135623730951},"43":{"tf":1.7320508075688772}},"e":{"c":{"df":1,"docs":{"69":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"d":{"df":10,"docs":{"1":{"tf":1.4142135623730951},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"3":{"tf":1.0},"31":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"4":{"tf":1.0},"68":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"46":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"6":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"d":{"df":3,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"31":{"tf":2.0}}},"df":1,"docs":{"1":{"tf":1.0}},"m":{"df":1,"docs":{"5":{"tf":1.0}}}},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"25":{"tf":1.0},"27":{"tf":1.4142135623730951}}}}}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.0},"11":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"d":{"df":6,"docs":{"16":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"51":{"tf":1.4142135623730951},"68":{"tf":2.23606797749979},"69":{"tf":3.605551275463989}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"50":{"tf":1.0},"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":2,"docs":{"19":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"9":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":4,"docs":{"1":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"51":{"tf":1.0}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"19":{"tf":1.0},"35":{"tf":1.0}}}}}},"h":{"a":{"df":2,"docs":{"52":{"tf":1.0},"6":{"tf":1.0}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"34":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"34":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"44":{"tf":1.0}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":12,"docs":{"1":{"tf":1.0},"11":{"tf":1.4142135623730951},"31":{"tf":2.8284271247461903},"37":{"tf":1.0},"52":{"tf":1.4142135623730951},"54":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"65":{"tf":1.4142135623730951},"68":{"tf":3.605551275463989},"69":{"tf":3.4641016151377544}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"65":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":8,"docs":{"3":{"tf":1.0},"31":{"tf":1.7320508075688772},"52":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.0}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"36":{"tf":1.0},"58":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":3,"docs":{"10":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":10,"docs":{"13":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"4":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0}}}}},"z":{"df":0,"docs":{},"e":{"df":1,"docs":{"28":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}},"df":2,"docs":{"13":{"tf":1.4142135623730951},"29":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":3.4641016151377544},"15":{"tf":1.0},"16":{"tf":2.0},"17":{"tf":1.0},"4":{"tf":1.0}}},"w":{"df":1,"docs":{"9":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"25":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"15":{"tf":1.0},"16":{"tf":1.0},"5":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"o":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"l":{"a":{"df":0,"docs":{},"n":{"a":{"'":{"df":6,"docs":{"1":{"tf":1.4142135623730951},"25":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":2.0}}},"df":22,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":2.23606797749979},"28":{"tf":1.7320508075688772},"3":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":2.0},"35":{"tf":1.0},"4":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.7320508075688772},"5":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.0},"67":{"tf":1.4142135623730951},"68":{"tf":3.872983346207417},"69":{"tf":4.69041575982343},"8":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.0}}}},"v":{"df":1,"docs":{"25":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"27":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"a":{"c":{"df":0,"docs":{},"e":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":8,"docs":{"0":{"tf":1.0},"11":{"tf":1.0},"31":{"tf":2.0},"38":{"tf":1.0},"47":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":5,"docs":{"13":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.0},"68":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"43":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"9":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":2,"docs":{"19":{"tf":1.7320508075688772},"40":{"tf":1.0}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}},"k":{"df":0,"docs":{},"e":{"df":5,"docs":{"10":{"tf":1.0},"12":{"tf":1.0},"15":{"tf":1.0},"29":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951}}}},"n":{"d":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.0},"8":{"tf":1.0}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"50":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":3,"docs":{"13":{"tf":1.0},"37":{"tf":1.7320508075688772},"38":{"tf":1.0}}},"u":{"df":2,"docs":{"54":{"tf":1.0},"58":{"tf":1.4142135623730951}},"s":{"df":1,"docs":{"58":{"tf":1.4142135623730951}}}}},"y":{"df":1,"docs":{"28":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"19":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"26":{"tf":1.0},"27":{"tf":1.7320508075688772},"31":{"tf":1.4142135623730951},"35":{"tf":1.7320508075688772},"8":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":4,"docs":{"27":{"tf":1.0},"29":{"tf":1.4142135623730951},"31":{"tf":1.0},"37":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"16":{"tf":1.0},"19":{"tf":1.0},"28":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"56":{"tf":1.0}}}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"5":{"tf":1.0}}}},"n":{"df":0,"docs":{},"g":{"df":11,"docs":{"11":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"58":{"tf":1.7320508075688772},"60":{"tf":2.0},"61":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"65":{"tf":1.4142135623730951}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"u":{"b":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"(":{"df":1,"docs":{"69":{"tf":1.0}}},"df":1,"docs":{"69":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":1,"docs":{"56":{"tf":1.0}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"27":{"tf":1.0},"31":{"tf":2.0},"34":{"tf":1.0},"62":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":6,"docs":{"50":{"tf":1.0},"62":{"tf":1.7320508075688772},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":1,"docs":{"58":{"tf":1.0}},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"51":{"tf":1.0},"58":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"37":{"tf":1.0},"43":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}},"d":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"25":{"tf":1.0},"27":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{},"x":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":6,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"28":{"tf":1.0},"35":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"5":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"12":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}}},"y":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"28":{"tf":1.0}}}}}}},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"28":{"tf":1.0},"5":{"tf":2.449489742783178}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":1,"docs":{"8":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":5,"docs":{"10":{"tf":1.4142135623730951},"28":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":2.449489742783178},"68":{"tf":1.0}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{":":{":":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"42":{"tf":1.0},"43":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"31":{"tf":1.0},"41":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"34":{"tf":1.4142135623730951}}}}}}},"df":3,"docs":{"12":{"tf":2.449489742783178},"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":3,"docs":{"1":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}}},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":2,"docs":{"2":{"tf":1.0},"4":{"tf":1.0}}}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.4142135623730951},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"19":{"tf":1.4142135623730951}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":4,"docs":{"31":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"43":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"r":{"d":{"df":3,"docs":{"19":{"tf":1.4142135623730951},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":4,"docs":{"27":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"37":{"tf":1.0}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"s":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":3,"docs":{"15":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":1.0}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":2.23606797749979}}}}}}}}}},"u":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":2.23606797749979},"13":{"tf":3.872983346207417},"15":{"tf":1.0},"16":{"tf":1.7320508075688772},"17":{"tf":1.7320508075688772},"3":{"tf":2.23606797749979}}}},"df":1,"docs":{"31":{"tf":1.4142135623730951}},"m":{"df":0,"docs":{},"e":{"df":9,"docs":{"10":{"tf":1.0},"13":{"tf":2.449489742783178},"27":{"tf":1.0},"4":{"tf":1.4142135623730951},"5":{"tf":2.6457513110645907},"6":{"tf":1.0},"68":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"16":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":3,"docs":{"6":{"tf":1.4142135623730951},"68":{"tf":2.6457513110645907},"69":{"tf":3.0}}}}},"df":0,"docs":{}}}}}},"l":{"df":1,"docs":{"69":{"tf":1.0}}},"o":{"d":{"df":0,"docs":{},"o":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":10,"docs":{"29":{"tf":1.0},"3":{"tf":1.0},"35":{"tf":1.4142135623730951},"38":{"tf":1.0},"42":{"tf":1.4142135623730951},"43":{"tf":1.0},"56":{"tf":1.4142135623730951},"60":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"9":{"tf":1.0}}},"l":{"df":2,"docs":{"19":{"tf":1.0},"67":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"28":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"p":{"df":2,"docs":{"1":{"tf":1.7320508075688772},"3":{"tf":1.0}},"u":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}}},"r":{"a":{"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"16":{"tf":1.0},"3":{"tf":1.0},"9":{"tf":1.0}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":25,"docs":{"1":{"tf":2.449489742783178},"12":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.0},"3":{"tf":2.449489742783178},"36":{"tf":1.0},"37":{"tf":2.0},"39":{"tf":1.0},"40":{"tf":2.23606797749979},"41":{"tf":1.0},"43":{"tf":1.7320508075688772},"44":{"tf":1.0},"5":{"tf":1.7320508075688772},"52":{"tf":1.0},"54":{"tf":2.0},"58":{"tf":3.0},"59":{"tf":1.0},"6":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.7320508075688772},"65":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"69":{"tf":3.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"68":{"tf":2.449489742783178},"69":{"tf":3.0}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"t":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"25":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":2,"docs":{"13":{"tf":1.0},"25":{"tf":1.0}}}},"i":{"df":1,"docs":{"5":{"tf":1.0}}},"u":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":1,"docs":{"54":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"27":{"tf":1.0},"5":{"tf":1.4142135623730951},"6":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"6":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"u":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}}},"w":{"df":0,"docs":{},"o":{"df":4,"docs":{"13":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0},"5":{"tf":1.0}}}},"x":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"68":{"tf":3.3166247903554}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":10,"docs":{"37":{"tf":1.0},"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"i":{"c":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"10":{"tf":1.0},"30":{"tf":1.0}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"5":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"51":{"tf":1.0}}}},"t":{"df":4,"docs":{"19":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"3":{"tf":1.0}}}},"k":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"58":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":2.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":1,"docs":{"59":{"tf":1.0}}}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"65":{"tf":1.0},"66":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.0},"9":{"tf":1.0}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}},"p":{"df":8,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"11":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"40":{"tf":1.0},"5":{"tf":1.0}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}},"s":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":3.4641016151377544}}}},"d":{"df":2,"docs":{"27":{"tf":1.0},"30":{"tf":1.0}}},"df":23,"docs":{"1":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.8284271247461903},"35":{"tf":1.0},"4":{"tf":2.0},"42":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.4142135623730951},"5":{"tf":1.7320508075688772},"51":{"tf":1.0},"6":{"tf":2.23606797749979},"62":{"tf":1.0},"68":{"tf":1.0},"8":{"tf":2.0},"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":6,"docs":{"37":{"tf":1.0},"40":{"tf":1.0},"56":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":3,"docs":{"35":{"tf":1.0},"4":{"tf":1.0},"42":{"tf":1.7320508075688772}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"'":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"29":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":14,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":2.23606797749979},"15":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"22":{"tf":1.0},"25":{"tf":2.0},"27":{"tf":1.7320508075688772},"29":{"tf":1.7320508075688772},"3":{"tf":1.4142135623730951},"31":{"tf":2.0},"4":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":2,"docs":{"31":{"tf":1.0},"51":{"tf":1.0}}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"f":{"df":3,"docs":{"6":{"tf":1.0},"8":{"tf":1.7320508075688772},"9":{"tf":2.8284271247461903}}}},"df":3,"docs":{"16":{"tf":1.0},"17":{"tf":1.0},"69":{"tf":3.3166247903554}},"e":{"c":{"<":{"df":0,"docs":{},"u":{"8":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}},"f":{"df":5,"docs":{"1":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":7,"docs":{"28":{"tf":1.4142135623730951},"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":1.0},"6":{"tf":2.0},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"42":{"tf":1.0},"69":{"tf":4.69041575982343}}}}}}}},"i":{"a":{"df":2,"docs":{"11":{"tf":1.0},"12":{"tf":1.0}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"13":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0}}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":1.4142135623730951},"13":{"tf":2.449489742783178},"15":{"tf":1.7320508075688772},"16":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.0},"69":{"tf":1.0}}}},"l":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"68":{"tf":1.0}}},"df":3,"docs":{"67":{"tf":1.0},"68":{"tf":3.872983346207417},"69":{"tf":4.69041575982343}}}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"r":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"h":{"/":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"19":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":2.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":2.23606797749979}}}}},"y":{"df":5,"docs":{"19":{"tf":1.0},"28":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.7320508075688772},"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"'":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.0}}}},"r":{"df":1,"docs":{"5":{"tf":1.0}}}},"b":{"3":{".":{"df":0,"docs":{},"j":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"49":{"tf":1.0},"50":{"tf":1.0},"62":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"15":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"l":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":2,"docs":{"20":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"37":{"tf":1.0},"5":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"37":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"40":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"d":{"df":1,"docs":{"3":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":4,"docs":{"25":{"tf":1.0},"29":{"tf":1.0},"44":{"tf":1.0},"8":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":3,"docs":{"20":{"tf":1.0},"35":{"tf":1.4142135623730951},"58":{"tf":1.0}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"33":{"tf":1.0},"35":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"s":{":":{"/":{"/":{"<":{"a":{"d":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"8":{"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"df":10,"docs":{"13":{"tf":1.7320508075688772},"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}},"x":{"df":1,"docs":{"13":{"tf":1.0}}}},"y":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"'":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"z":{"df":2,"docs":{"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":1,"docs":{"43":{"tf":1.0}}}}}}}},"breadcrumbs":{"root":{"0":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},".":{"1":{"1":{".":{"0":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"40":{"tf":1.0},"61":{"tf":6.48074069840786}}},"1":{"/":{"1":{"0":{"0":{"0":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"15":{"tf":1.4142135623730951}}},"1":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"2":{"1":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":2,"docs":{"61":{"tf":1.0},"68":{"tf":3.0}}},"4":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"7":{".":{"0":{".":{"0":{".":{"1":{":":{"8":{"0":{"0":{"1":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":2,"docs":{"61":{"tf":1.0},"68":{"tf":1.7320508075688772}}},"3":{"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"4":{",":{"0":{"0":{"0":{"df":2,"docs":{"28":{"tf":1.0},"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"5":{"0":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"6":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"7":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":2,"docs":{"5":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.0}}},"8":{"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"9":{"0":{"df":1,"docs":{"61":{"tf":1.0}}},"1":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"2":{".":{"1":{"6":{"8":{".":{"1":{".":{"8":{"8":{":":{"8":{"8":{"9":{"9":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"4":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"1":{"df":1,"docs":{"5":{"tf":1.0}}},"4":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":7,"docs":{"30":{"tf":1.0},"31":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}},"g":{"b":{"df":0,"docs":{},"p":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}},"k":{"df":1,"docs":{"31":{"tf":1.7320508075688772}}},"m":{"b":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}},"t":{"b":{"df":1,"docs":{"31":{"tf":2.0}}},"df":0,"docs":{}}},"2":{".":{"0":{"\"":{",":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"47":{"tf":1.0},"51":{"tf":1.0}}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"1":{"tf":1.0}}},"1":{"7":{"df":1,"docs":{"8":{"tf":1.0}}},"8":{"df":1,"docs":{"68":{"tf":1.7320508075688772}}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"1":{"2":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"2":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"3":{"4":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"5":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"9":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"4":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{},"t":{"2":{"3":{":":{"5":{"9":{":":{"0":{"0":{"df":1,"docs":{"68":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"68":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"5":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"6":{"df":2,"docs":{"52":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"6":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{".":{"4":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.0}}},"df":4,"docs":{"13":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"61":{"tf":1.0}}},"3":{"1":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"2":{"df":2,"docs":{"56":{"tf":1.4142135623730951},"61":{"tf":1.0}}},"5":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"4":{"0":{"0":{"0":{"df":1,"docs":{"9":{"tf":1.0}}},"df":1,"docs":{"1":{"tf":1.0}}},"df":3,"docs":{"1":{"tf":1.0},"5":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.0}}},"9":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"df":2,"docs":{"27":{"tf":1.0},"51":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":2,"docs":{"27":{"tf":1.0},"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"df":2,"docs":{"60":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"8":{"df":9,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}},"6":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"4":{"df":4,"docs":{"55":{"tf":1.0},"56":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":3,"docs":{"31":{"tf":1.0},"61":{"tf":1.4142135623730951},"8":{"tf":1.0}}},"7":{"0":{"df":1,"docs":{"61":{"tf":1.0}}},"1":{"0":{",":{"0":{"0":{"0":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.0}}},"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"8":{"0":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"1":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"8":{"9":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"9":{"0":{"df":1,"docs":{"15":{"tf":1.0}}},"2":{"df":1,"docs":{"61":{"tf":1.0}}},"3":{"df":1,"docs":{"61":{"tf":1.0}}},"4":{"df":1,"docs":{"61":{"tf":1.0}}},"7":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"61":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"a":{"'":{"df":1,"docs":{"16":{"tf":1.0}}},"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"28":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"v":{"df":5,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"34":{"tf":1.0},"9":{"tf":1.0}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"47":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"35":{"tf":1.0},"5":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"40":{"tf":1.0}}},":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":15,"docs":{"3":{"tf":1.0},"35":{"tf":2.6457513110645907},"37":{"tf":1.4142135623730951},"38":{"tf":2.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"42":{"tf":2.0},"43":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":2.449489742783178},"58":{"tf":1.0},"60":{"tf":1.0},"63":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"66":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"63":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"63":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"64":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"u":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":2,"docs":{"27":{"tf":1.0},"36":{"tf":1.4142135623730951}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":2,"docs":{"25":{"tf":1.0},"62":{"tf":1.0}}}}}},"d":{"d":{"df":2,"docs":{"19":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"3":{"tf":1.0},"37":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":2.0}}}}}}},"df":3,"docs":{"19":{"tf":1.4142135623730951},"25":{"tf":1.0},"58":{"tf":1.0}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}}},"o":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"27":{"tf":1.0}}},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":3,"docs":{"60":{"tf":1.4142135623730951},"68":{"tf":1.4142135623730951},"69":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"df":4,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"5":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}}}}}}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":3,"docs":{"36":{"tf":2.23606797749979},"37":{"tf":1.0},"43":{"tf":1.0}}},"df":0,"docs":{},"w":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.0},"42":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"27":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"6":{"tf":1.0},"8":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":5,"docs":{"19":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"9":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"i":{"df":2,"docs":{"47":{"tf":1.4142135623730951},"53":{"tf":1.4142135623730951}}},"p":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":24,"docs":{"46":{"tf":1.4142135623730951},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":9,"docs":{"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":4,"docs":{"12":{"tf":1.0},"47":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.7320508075688772}}},"df":2,"docs":{"5":{"tf":1.0},"69":{"tf":1.0}}}},"r":{"df":0,"docs":{},"o":{"a":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"5":{"tf":1.0},"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"v":{"df":1,"docs":{"31":{"tf":1.0}}},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}}}}}}}},"r":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":2.23606797749979},"3":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}}},"g":{"df":1,"docs":{"69":{"tf":2.6457513110645907}},"u":{"df":1,"docs":{"9":{"tf":1.0}}}},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}},"y":{"df":4,"docs":{"41":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.7320508075688772},"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"k":{"df":1,"docs":{"9":{"tf":1.0}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":4,"docs":{"40":{"tf":1.7320508075688772},"42":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772},"56":{"tf":1.4142135623730951}}}}},"o":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"37":{"tf":1.0},"42":{"tf":1.0},"56":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"58":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":2,"docs":{"3":{"tf":1.0},"36":{"tf":1.0}}}},"t":{"a":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"31":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"0":{"tf":1.0}}},"df":4,"docs":{"1":{"tf":1.0},"68":{"tf":1.4142135623730951},"69":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"65":{"tf":1.0}}}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"30":{"tf":1.0},"5":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"25":{"tf":1.7320508075688772},"26":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0}}}},"df":0,"docs":{}}},"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}}}},"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"13":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":5,"docs":{"40":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"55":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":2.23606797749979}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"n":{"d":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"e":{"df":10,"docs":{"5":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"i":{"c":{"df":2,"docs":{"27":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"1":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"40":{"tf":1.0},"51":{"tf":1.0},"69":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":2,"docs":{"16":{"tf":1.4142135623730951},"17":{"tf":1.0}},"e":{"c":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":4,"docs":{"19":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.4142135623730951},"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":6,"docs":{"17":{"tf":1.0},"19":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"g":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"43":{"tf":1.0}}}},"w":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"27":{"tf":1.0},"62":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.0},"42":{"tf":1.0}}}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":6,"docs":{"1":{"tf":1.0},"23":{"tf":1.0},"3":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}}}},"df":4,"docs":{"55":{"tf":1.0},"56":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"1":{"tf":1.4142135623730951},"10":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":5,"docs":{"10":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"6":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"54":{"tf":1.0},"56":{"tf":1.0}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"16":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0}}},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"f":{"df":1,"docs":{"34":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"a":{"d":{"c":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"34":{"tf":1.0}}},"df":0,"docs":{}}},"df":2,"docs":{"5":{"tf":1.0},"56":{"tf":1.7320508075688772}}}}}},"c":{"/":{"c":{"+":{"+":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"a":{"df":1,"docs":{"34":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"12":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":8,"docs":{"10":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"27":{"tf":1.0},"35":{"tf":1.0},"43":{"tf":1.4142135623730951},"6":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":6,"docs":{"15":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":2.6457513110645907}}}}},"df":0,"docs":{}},"p":{"a":{"c":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"d":{"df":2,"docs":{"20":{"tf":1.0},"28":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"e":{"df":2,"docs":{"16":{"tf":1.4142135623730951},"20":{"tf":1.0}}},"t":{"df":1,"docs":{"15":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"36":{"tf":1.0}}}}},"b":{"c":{"_":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"28":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}},"r":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"1":{"tf":1.0},"27":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"6":{"tf":1.0},"9":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":14,"docs":{"13":{"tf":2.0},"33":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":1.7320508075688772}}}},"n":{"df":0,"docs":{},"g":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0},"9":{"tf":1.0}}}},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"10":{"tf":1.0}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"0":{"tf":1.0}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"6":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"33":{"tf":1.0},"34":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"s":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"13":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":2,"docs":{"31":{"tf":1.0},"52":{"tf":1.4142135623730951}}}}}},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"9":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":1,"docs":{"67":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":2,"docs":{"34":{"tf":1.0},"43":{"tf":1.0}}},"df":9,"docs":{"29":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"34":{"tf":2.0},"35":{"tf":1.7320508075688772},"37":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.0}},"’":{"df":1,"docs":{"33":{"tf":1.0}}}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"23":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.7320508075688772},"34":{"tf":1.4142135623730951}}}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":4,"docs":{"3":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}},"m":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"67":{"tf":1.0},"68":{"tf":3.872983346207417}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"3":{"tf":1.0}}}},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"34":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}},"s":{"df":2,"docs":{"36":{"tf":1.0},"43":{"tf":1.0}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":3,"docs":{"25":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":6,"docs":{"1":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0},"33":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"58":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"69":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"65":{"tf":1.0},"68":{"tf":1.7320508075688772},"69":{"tf":2.449489742783178}},"e":{"d":{"\"":{",":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\"":{":":{"0":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"50":{"tf":1.0},"54":{"tf":1.4142135623730951},"58":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"8":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"62":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":2,"docs":{"6":{"tf":1.0},"8":{"tf":2.6457513110645907}}}}}},"i":{"d":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"17":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":7,"docs":{"13":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":1.0},"46":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.0},"61":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":12,"docs":{"0":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":6,"docs":{"12":{"tf":1.0},"15":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.0},"44":{"tf":1.0},"9":{"tf":1.0}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":4,"docs":{"23":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"47":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":5,"docs":{"20":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"39":{"tf":1.0},"9":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"27":{"tf":1.4142135623730951},"30":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":8,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.0},"13":{"tf":1.7320508075688772},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":2.6457513110645907},"59":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}}},"p":{"df":0,"docs":{},"u":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"67":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":10,"docs":{"17":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":2.0},"34":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"61":{"tf":1.0},"9":{"tf":1.0}},"e":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"42":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"8":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":2,"docs":{"31":{"tf":1.0},"6":{"tf":1.0}},"i":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":9,"docs":{"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":11,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"30":{"tf":1.0},"4":{"tf":1.4142135623730951},"59":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}}}},"y":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"_":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.0}}}},"b":{"a":{"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":11,"docs":{"13":{"tf":2.449489742783178},"19":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":2.449489742783178},"28":{"tf":1.0},"31":{"tf":1.0},"35":{"tf":1.4142135623730951},"40":{"tf":1.0},"51":{"tf":1.7320508075688772},"52":{"tf":1.4142135623730951},"9":{"tf":2.449489742783178}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.7320508075688772},"30":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"68":{"tf":1.7320508075688772},"69":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}}}},"y":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"i":{"d":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"48":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"37":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}}}}}},"l":{"a":{"df":0,"docs":{},"y":{"df":3,"docs":{"6":{"tf":1.7320508075688772},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"27":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}}}}}}},"n":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"36":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":2,"docs":{"68":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":3,"docs":{"13":{"tf":1.0},"31":{"tf":1.0},"9":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":4,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"42":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":3,"docs":{"0":{"tf":1.0},"19":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":3,"docs":{"1":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"25":{"tf":1.0},"8":{"tf":1.0}}}}}}}},"i":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"34":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"10":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"5":{"tf":2.23606797749979},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"s":{"c":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"13":{"tf":1.0},"35":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"k":{"df":1,"docs":{"20":{"tf":1.0}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"28":{"tf":1.0},"5":{"tf":2.23606797749979},"67":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"0":{"tf":1.0},"31":{"tf":1.0}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"29":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.4142135623730951}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"1":{"tf":1.0}}}},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":2.0}}}}}},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"13":{"tf":1.7320508075688772},"27":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"36":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"g":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}},"1":{"df":1,"docs":{"13":{"tf":1.0}}},"2":{"df":1,"docs":{"13":{"tf":1.0}}},"3":{"df":1,"docs":{"13":{"tf":1.0}}},"4":{"df":1,"docs":{"13":{"tf":1.0}}},"5":{"df":1,"docs":{"13":{"tf":1.0}}},"a":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"19":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"28":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"d":{"2":{"5":{"5":{"1":{"9":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":1,"docs":{"13":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"28":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}}},"g":{"df":2,"docs":{"48":{"tf":1.0},"49":{"tf":1.0}}},"l":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":1,"docs":{"68":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":1,"docs":{"34":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"n":{"c":{"df":0,"docs":{},"o":{"d":{"df":11,"docs":{"13":{"tf":1.0},"42":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":2.23606797749979},"31":{"tf":2.0}}}}}}},"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"48":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951}}}}}}}},"df":1,"docs":{"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"36":{"tf":1.0},"39":{"tf":1.7320508075688772}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"29":{"tf":1.0},"35":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":10,"docs":{"13":{"tf":2.0},"16":{"tf":1.0},"20":{"tf":1.0},"3":{"tf":2.449489742783178},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.7320508075688772},"57":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"p":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"4":{"tf":1.0}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"28":{"tf":1.0},"40":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"0":{"tf":1.0},"3":{"tf":1.0}}}}}},"t":{"c":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}},"x":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"1":{"tf":1.0},"13":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":18,"docs":{"14":{"tf":1.4142135623730951},"19":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"68":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"\"":{":":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"\"":{":":{"1":{",":{"\"":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"\"":{":":{"[":{"3":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"5":{"0":{",":{"4":{"8":{",":{"5":{"3":{",":{"4":{"8":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"8":{"4":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"9":{"0":{",":{"2":{"5":{"2":{",":{"1":{"0":{",":{"7":{",":{"2":{"8":{",":{"2":{"4":{"6":{",":{"1":{"4":{"0":{",":{"8":{"8":{",":{"1":{"7":{"7":{",":{"9":{"8":{",":{"8":{"2":{",":{"1":{"0":{",":{"2":{"2":{"7":{",":{"8":{"9":{",":{"8":{"1":{",":{"1":{"8":{",":{"3":{"0":{",":{"1":{"9":{"4":{",":{"1":{"0":{"1":{",":{"1":{"9":{"9":{",":{"1":{"6":{",":{"1":{"1":{",":{"7":{"3":{",":{"1":{"3":{"3":{",":{"2":{"0":{",":{"2":{"4":{"6":{",":{"6":{"2":{",":{"1":{"1":{"4":{",":{"3":{"9":{",":{"2":{"0":{",":{"1":{"1":{"3":{",":{"1":{"8":{"9":{",":{"3":{"2":{",":{"5":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"4":{"7":{",":{"1":{"5":{",":{"3":{"6":{",":{"1":{"0":{"2":{",":{"1":{"6":{"7":{",":{"8":{"3":{",":{"2":{"2":{"5":{",":{"4":{"2":{",":{"1":{"3":{"3":{",":{"1":{"2":{"7":{",":{"8":{"2":{",":{"3":{"4":{",":{"3":{"6":{",":{"2":{"2":{"4":{",":{"2":{"0":{"7":{",":{"1":{"3":{"0":{",":{"1":{"0":{"9":{",":{"2":{"3":{"0":{",":{"2":{"2":{"4":{",":{"1":{"8":{"8":{",":{"1":{"6":{"3":{",":{"3":{"3":{",":{"2":{"1":{"3":{",":{"1":{"3":{",":{"5":{",":{"1":{"1":{"7":{",":{"2":{"1":{"1":{",":{"2":{"5":{"1":{",":{"6":{"5":{",":{"1":{"5":{"9":{",":{"1":{"9":{"7":{",":{"5":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{"df":0,"docs":{},"}":{",":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\"":{":":{"0":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":12,"docs":{"1":{"tf":1.0},"3":{"tf":1.4142135623730951},"33":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"40":{"tf":2.449489742783178},"41":{"tf":1.4142135623730951},"43":{"tf":1.0},"5":{"tf":1.0},"56":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"1":{"tf":1.0},"20":{"tf":1.4142135623730951},"42":{"tf":1.0},"9":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"28":{"tf":1.0},"68":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"r":{"df":2,"docs":{"13":{"tf":1.0},"16":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.0}}}}}}}},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.7320508075688772}}}},"r":{"df":1,"docs":{"6":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.7320508075688772}},"l":{"df":1,"docs":{"1":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"51":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"3":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"l":{"df":2,"docs":{"12":{"tf":1.0},"16":{"tf":1.7320508075688772}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.4142135623730951},"3":{"tf":1.0}}}},"d":{"df":3,"docs":{"25":{"tf":1.0},"46":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"13":{"tf":1.0},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}}},"l":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":3.4641016151377544}}},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"13":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"19":{"tf":1.7320508075688772}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":9,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.0},"4":{"tf":1.0},"40":{"tf":1.0},"46":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0}}}}}},"r":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":2,"docs":{"13":{"tf":2.6457513110645907},"16":{"tf":1.0}}},"m":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"45":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"63":{"tf":1.0},"65":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"1":{"tf":1.0},"25":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"3":{"tf":1.0},"37":{"tf":1.0}},"i":{"df":1,"docs":{"61":{"tf":1.0}}},"n":{"df":0,"docs":{},"o":{"d":{"df":10,"docs":{"10":{"tf":1.7320508075688772},"18":{"tf":1.4142135623730951},"19":{"tf":1.0},"20":{"tf":1.7320508075688772},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"27":{"tf":1.7320508075688772},"3":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":5,"docs":{"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.7320508075688772},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"0":{"tf":1.0},"1":{"tf":1.0}}}}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"4":{"tf":1.7320508075688772},"44":{"tf":1.4142135623730951},"58":{"tf":1.0}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"1":{"tf":1.0},"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"13":{"tf":1.4142135623730951},"15":{"tf":1.0},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.0},"51":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":2,"docs":{"50":{"tf":1.0},"56":{"tf":1.4142135623730951}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"50":{"tf":1.0},"55":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"50":{"tf":1.0},"57":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":2,"docs":{"50":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"50":{"tf":1.0},"59":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"a":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"5":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"47":{"tf":1.0}},"n":{"df":4,"docs":{"19":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0}}}},"df":1,"docs":{"31":{"tf":1.0}},"e":{"df":1,"docs":{"1":{"tf":1.0}}},"o":{"d":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951}}}}}}},"p":{"df":0,"docs":{},"u":{"df":4,"docs":{"20":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"40":{"tf":1.0},"43":{"tf":2.23606797749979}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"h":{".":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"h":{"df":10,"docs":{"11":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.7320508075688772},"27":{"tf":2.0},"28":{"tf":1.0},"31":{"tf":3.3166247903554},"52":{"tf":1.4142135623730951},"57":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":2.449489742783178}}}}},"df":10,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"69":{"tf":3.3166247903554}},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"6":{"tf":1.7320508075688772},"9":{"tf":1.4142135623730951}}}}}},"l":{"df":0,"docs":{},"p":{"df":1,"docs":{"69":{"tf":4.898979485566356}}}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"25":{"tf":1.0},"6":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"5":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}},"i":{"df":4,"docs":{"28":{"tf":1.0},"7":{"tf":1.7320508075688772},"8":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"35":{"tf":1.0}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"o":{"d":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{":":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{":":{"/":{"/":{"1":{"9":{"2":{".":{"1":{"6":{"8":{".":{"1":{".":{"8":{"8":{":":{"8":{"8":{"9":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"8":{"8":{"9":{"9":{"df":9,"docs":{"48":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"47":{"tf":1.0},"48":{"tf":1.4142135623730951},"51":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{".":{"df":1,"docs":{"34":{"tf":1.0}}},"d":{"\"":{":":{"1":{"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":7,"docs":{"51":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.7320508075688772}},"e":{"a":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":2.23606797749979},"31":{"tf":4.123105625617661}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":2,"docs":{"35":{"tf":1.0},"51":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"68":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":6,"docs":{"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"42":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"8":{"tf":1.4142135623730951}}}}}}},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"3":{"tf":1.0},"36":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"34":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"i":{"c":{"df":2,"docs":{"56":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"56":{"tf":1.0},"69":{"tf":4.69041575982343}}}}}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"31":{"tf":1.0},"43":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"19":{"tf":1.0},"20":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"47":{"tf":1.0}}},"df":0,"docs":{}},"t":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":1,"docs":{"30":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"1":{"tf":1.7320508075688772},"3":{"tf":1.7320508075688772},"36":{"tf":2.0},"37":{"tf":1.4142135623730951},"4":{"tf":1.0},"40":{"tf":1.0},"43":{"tf":1.4142135623730951},"52":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"42":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":9,"docs":{"51":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.0},"59":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.4142135623730951},"37":{"tf":1.0},"47":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"a":{"c":{"df":4,"docs":{"37":{"tf":1.0},"42":{"tf":1.7320508075688772},"47":{"tf":1.0},"67":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.4142135623730951},"34":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"6":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"51":{"tf":1.0}}},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"41":{"tf":1.4142135623730951}}}}}}},"t":{"'":{"df":5,"docs":{"13":{"tf":1.0},"27":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":4,"docs":{"31":{"tf":1.0},"37":{"tf":1.4142135623730951},"43":{"tf":1.0},"5":{"tf":1.0}}}}}}}},"j":{".":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"47":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"b":{"df":2,"docs":{"13":{"tf":1.0},"19":{"tf":1.0}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"46":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":4,"docs":{"47":{"tf":1.7320508075688772},"51":{"tf":2.23606797749979},"53":{"tf":1.4142135623730951},"56":{"tf":1.0}},"r":{"df":0,"docs":{},"p":{"c":{"\"":{":":{"\"":{"2":{".":{"0":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":4,"docs":{"57":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"\"":{":":{"\"":{"2":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"6":{"c":{"b":{"8":{"df":0,"docs":{},"v":{"a":{"a":{"d":{"9":{"3":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"6":{"df":0,"docs":{},"v":{"d":{"8":{"df":0,"docs":{},"p":{"6":{"7":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"q":{"df":0,"docs":{},"z":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"x":{"4":{"7":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"g":{"9":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"1":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"3":{"a":{"2":{"df":0,"docs":{},"g":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"7":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"j":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"y":{"9":{"b":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"5":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"v":{"df":0,"docs":{},"z":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"x":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"9":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"j":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"j":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"8":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"q":{"df":0,"docs":{},"p":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"4":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"3":{"df":0,"docs":{},"z":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"z":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"v":{"6":{"df":0,"docs":{},"u":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"k":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"w":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"7":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"3":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"7":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"9":{"df":0,"docs":{},"j":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"2":{"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"w":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"3":{"df":0,"docs":{},"z":{"6":{"9":{"df":0,"docs":{},"x":{"df":0,"docs":{},"m":{"1":{"df":0,"docs":{},"z":{"df":0,"docs":{},"e":{"3":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"6":{"df":0,"docs":{},"j":{"c":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"\"":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"0":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"55":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"6":{"8":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"54":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"{":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"\"":{":":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"\"":{":":{"[":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{",":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"\"":{":":{"1":{",":{"\"":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"\"":{":":{"[":{"3":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"5":{"0":{",":{"4":{"8":{",":{"5":{"3":{",":{"4":{"8":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"4":{"5":{",":{"4":{"8":{",":{"4":{"9":{",":{"8":{"4":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"5":{"8":{",":{"4":{"8":{",":{"4":{"8":{",":{"9":{"0":{",":{"2":{"5":{"2":{",":{"1":{"0":{",":{"7":{",":{"2":{"8":{",":{"2":{"4":{"6":{",":{"1":{"4":{"0":{",":{"8":{"8":{",":{"1":{"7":{"7":{",":{"9":{"8":{",":{"8":{"2":{",":{"1":{"0":{",":{"2":{"2":{"7":{",":{"8":{"9":{",":{"8":{"1":{",":{"1":{"8":{",":{"3":{"0":{",":{"1":{"9":{"4":{",":{"1":{"0":{"1":{",":{"1":{"9":{"9":{",":{"1":{"6":{",":{"1":{"1":{",":{"7":{"3":{",":{"1":{"3":{"3":{",":{"2":{"0":{",":{"2":{"4":{"6":{",":{"6":{"2":{",":{"1":{"1":{"4":{",":{"3":{"9":{",":{"2":{"0":{",":{"1":{"1":{"3":{",":{"1":{"8":{"9":{",":{"3":{"2":{",":{"5":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"2":{"4":{"7":{",":{"1":{"5":{",":{"3":{"6":{",":{"1":{"0":{"2":{",":{"1":{"6":{"7":{",":{"8":{"3":{",":{"2":{"2":{"5":{",":{"4":{"2":{",":{"1":{"3":{"3":{",":{"1":{"2":{"7":{",":{"8":{"2":{",":{"3":{"4":{",":{"3":{"6":{",":{"2":{"2":{"4":{",":{"2":{"0":{"7":{",":{"1":{"3":{"0":{",":{"1":{"0":{"9":{",":{"2":{"3":{"0":{",":{"2":{"2":{"4":{",":{"1":{"8":{"8":{",":{"1":{"6":{"3":{",":{"3":{"3":{",":{"2":{"1":{"3":{",":{"1":{"3":{",":{"5":{",":{"1":{"1":{"7":{",":{"2":{"1":{"1":{",":{"2":{"5":{"1":{",":{"6":{"5":{",":{"1":{"5":{"9":{",":{"1":{"9":{"7":{",":{"5":{"1":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{",":{"0":{"]":{"df":0,"docs":{},"}":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"\"":{":":{"1":{"df":1,"docs":{"56":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":9,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":5,"docs":{"51":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"24":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"25":{"tf":1.0}}}}}}}}},"k":{"df":3,"docs":{"15":{"tf":2.0},"17":{"tf":1.0},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":2,"docs":{"16":{"tf":1.0},"27":{"tf":1.0}}}},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}}},"y":{"df":13,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"35":{"tf":1.4142135623730951},"37":{"tf":1.0},"4":{"tf":1.4142135623730951},"41":{"tf":1.0},"5":{"tf":1.0},"52":{"tf":1.7320508075688772},"63":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"p":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":4,"docs":{"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"4":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"4":{"tf":1.0}}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"35":{"tf":1.0}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"5":{"tf":1.0}},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"l":{".":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"1":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"2":{"df":1,"docs":{"13":{"tf":2.0}}},"3":{"df":1,"docs":{"13":{"tf":2.8284271247461903}}},"4":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"g":{"df":3,"docs":{"1":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"15":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"31":{"tf":1.0},"57":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.7320508075688772},"3":{"tf":1.0}}},"df":13,"docs":{"10":{"tf":2.8284271247461903},"11":{"tf":2.0},"12":{"tf":3.3166247903554},"13":{"tf":3.4641016151377544},"15":{"tf":1.4142135623730951},"16":{"tf":2.449489742783178},"17":{"tf":2.23606797749979},"20":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"4":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":12,"docs":{"13":{"tf":1.7320508075688772},"20":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"3":{"tf":2.23606797749979},"31":{"tf":1.7320508075688772},"45":{"tf":1.4142135623730951},"57":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0}}}}}},"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"25":{"tf":1.7320508075688772}}}}}},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"47":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"3":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"1":{"tf":1.0}}}},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"19":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"67":{"tf":1.0}}},"k":{"df":2,"docs":{"13":{"tf":1.0},"9":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"37":{"tf":1.4142135623730951},"58":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"31":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"df":1,"docs":{"34":{"tf":1.0}}}}},"o":{"a":{"d":{"df":5,"docs":{"19":{"tf":2.6457513110645907},"34":{"tf":1.0},"35":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"56":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"58":{"tf":1.0}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"15":{"tf":1.0}}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"31":{"tf":1.0},"44":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"1":{"tf":1.0},"39":{"tf":1.0}}}}}},"m":{"a":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"1":{"tf":1.0},"3":{"tf":1.0},"38":{"tf":1.4142135623730951},"5":{"tf":1.0}}}}},"df":0,"docs":{}}}},"k":{"df":0,"docs":{},"e":{"df":4,"docs":{"1":{"tf":1.0},"19":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"51":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"i":{"df":2,"docs":{"30":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}}}}},"p":{"df":2,"docs":{"39":{"tf":1.0},"41":{"tf":1.4142135623730951}}},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"3":{"tf":1.0},"51":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"46":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.0}}}}}},"x":{"df":1,"docs":{"69":{"tf":1.0}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"19":{"tf":1.0},"27":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"y":{"b":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}},"df":4,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"15":{"tf":2.6457513110645907},"17":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"n":{"df":3,"docs":{"1":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"0":{"tf":1.0},"1":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"10":{"tf":1.0},"8":{"tf":2.23606797749979}}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"36":{"tf":2.23606797749979},"37":{"tf":1.0},"38":{"tf":1.0},"43":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"\"":{":":{"\"":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"51":{"tf":1.0},"55":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"66":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"'":{"df":1,"docs":{"5":{"tf":1.0}}},"df":6,"docs":{"47":{"tf":1.0},"5":{"tf":2.0},"50":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"58":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"4":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"29":{"tf":1.0}}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"27":{"tf":1.0},"8":{"tf":1.0}}}}},"p":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"4":{"tf":1.0}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":2,"docs":{"10":{"tf":1.0},"20":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":3,"docs":{"40":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"8":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":6,"docs":{"3":{"tf":1.0},"31":{"tf":1.7320508075688772},"5":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0},"8":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"42":{"tf":2.0}}}}},"u":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":7,"docs":{"25":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"43":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.0}}}}}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":2,"docs":{"17":{"tf":1.7320508075688772},"8":{"tf":1.0}}}}},"c":{"df":0,"docs":{},"p":{"df":1,"docs":{"23":{"tf":1.4142135623730951}}}},"df":3,"docs":{"11":{"tf":1.4142135623730951},"17":{"tf":1.0},"69":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"37":{"tf":1.0},"5":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"d":{"df":9,"docs":{"13":{"tf":1.0},"19":{"tf":1.0},"27":{"tf":1.0},"31":{"tf":1.4142135623730951},"42":{"tf":1.0},"43":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":14,"docs":{"1":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"13":{"tf":2.23606797749979},"15":{"tf":1.0},"17":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"27":{"tf":2.0},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"31":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951},"6":{"tf":1.0},"69":{"tf":1.7320508075688772}}}}}}},"w":{"df":7,"docs":{"13":{"tf":1.4142135623730951},"17":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"58":{"tf":1.4142135623730951},"61":{"tf":1.0},"8":{"tf":1.0}}},"x":{"df":0,"docs":{},"t":{"df":5,"docs":{"10":{"tf":1.0},"12":{"tf":1.4142135623730951},"16":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"o":{"d":{"df":0,"docs":{},"e":{"df":15,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":2.0},"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"17":{"tf":2.23606797749979},"23":{"tf":1.0},"25":{"tf":1.7320508075688772},"28":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"47":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"69":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}},"e":{"df":2,"docs":{"57":{"tf":1.0},"59":{"tf":1.0}}}},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"58":{"tf":1.0}}},"h":{"df":1,"docs":{"0":{"tf":1.0}}},"i":{"df":0,"docs":{},"f":{"df":4,"docs":{"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}}},"w":{"df":1,"docs":{"6":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"3":{"tf":1.0}}}},"u":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":1,"docs":{"27":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"df":7,"docs":{"17":{"tf":2.23606797749979},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"56":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}},"df":1,"docs":{"69":{"tf":2.23606797749979}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.0},"51":{"tf":1.4142135623730951},"56":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":5,"docs":{"12":{"tf":1.4142135623730951},"13":{"tf":2.23606797749979},"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}},"c":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":4,"docs":{"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"19":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"c":{"df":5,"docs":{"13":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"5":{"tf":1.0},"62":{"tf":1.0}}},"df":12,"docs":{"13":{"tf":1.0},"15":{"tf":1.0},"19":{"tf":2.0},"20":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"5":{"tf":2.0},"58":{"tf":1.0},"6":{"tf":1.0},"9":{"tf":1.0}}},"p":{"a":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"15":{"tf":1.0},"19":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.7320508075688772}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"19":{"tf":1.0},"28":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"12":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":2.8284271247461903}}}}}}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"16":{"tf":1.0},"28":{"tf":1.0},"51":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"16":{"tf":1.0},"25":{"tf":1.0},"6":{"tf":1.0},"8":{"tf":1.0}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"20":{"tf":1.0},"51":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"9":{"tf":1.0}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":2,"docs":{"25":{"tf":1.0},"7":{"tf":1.0}}}}}}}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"35":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"56":{"tf":1.0}}}}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":2,"docs":{"40":{"tf":1.0},"41":{"tf":1.0}}}},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"8":{"tf":1.0},"9":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":2,"docs":{"3":{"tf":1.0},"40":{"tf":1.0}},"i":{"df":0,"docs":{},"z":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"m":{"df":3,"docs":{"51":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":13,"docs":{"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0}}}},"s":{"\"":{":":{"[":{"\"":{"2":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"6":{"c":{"b":{"8":{"df":0,"docs":{},"v":{"a":{"a":{"d":{"9":{"3":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"6":{"df":0,"docs":{},"v":{"d":{"8":{"df":0,"docs":{},"p":{"6":{"7":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"q":{"df":0,"docs":{},"z":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"x":{"4":{"7":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"g":{"9":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"1":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"3":{"a":{"2":{"df":0,"docs":{},"g":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"7":{"df":0,"docs":{},"w":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"j":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"y":{"9":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"g":{"df":0,"docs":{},"v":{"df":0,"docs":{},"k":{"df":0,"docs":{},"y":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"5":{"df":0,"docs":{},"h":{"b":{"2":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"n":{"3":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"v":{"df":0,"docs":{},"w":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"f":{"df":0,"docs":{},"k":{"df":0,"docs":{},"x":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"h":{"df":0,"docs":{},"p":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"56":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}}},"5":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"v":{"df":0,"docs":{},"z":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"v":{"8":{"df":0,"docs":{},"x":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"9":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"j":{"c":{"df":0,"docs":{},"j":{"df":0,"docs":{},"j":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"8":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"q":{"df":0,"docs":{},"p":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"4":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"3":{"df":0,"docs":{},"z":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"z":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"v":{"6":{"df":0,"docs":{},"u":{"df":0,"docs":{},"j":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"k":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"w":{"df":2,"docs":{"54":{"tf":1.0},"58":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"8":{"3":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"t":{"2":{"df":0,"docs":{},"h":{"5":{"df":0,"docs":{},"u":{"1":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"d":{"df":0,"docs":{},"q":{"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"6":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"y":{"3":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"51":{"tf":1.0},"55":{"tf":1.0},"60":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"m":{"7":{"8":{"c":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"8":{"df":0,"docs":{},"o":{"3":{"df":0,"docs":{},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"h":{"df":0,"docs":{},"x":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"df":0,"docs":{},"z":{"df":0,"docs":{},"z":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"4":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"df":0,"docs":{},"k":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"x":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"k":{"df":0,"docs":{},"n":{"df":0,"docs":{},"h":{"1":{"2":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"0":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}}},"[":{"6":{"1":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"29":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"27":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}},"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"t":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"15":{"tf":2.0},"16":{"tf":1.4142135623730951}}}}}},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"34":{"tf":1.0},"35":{"tf":1.0},"43":{"tf":1.0},"6":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"i":{"d":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{".":{"df":0,"docs":{},"o":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":1.7320508075688772}}}},"y":{"df":2,"docs":{"68":{"tf":2.449489742783178},"69":{"tf":1.7320508075688772}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"69":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":10,"docs":{"1":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"27":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.4142135623730951},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"0":{"tf":1.0},"1":{"tf":1.7320508075688772},"19":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"11":{"tf":1.0},"27":{"tf":1.0},"31":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"1":{"tf":1.0},"35":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}}}}},"t":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.0},"35":{"tf":1.7320508075688772}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":1,"docs":{"27":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"19":{"tf":2.8284271247461903},"20":{"tf":2.0},"40":{"tf":1.0}}}}}}}},"l":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"23":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"h":{"'":{"df":1,"docs":{"9":{"tf":1.0}}},"df":8,"docs":{"11":{"tf":1.0},"12":{"tf":2.0},"13":{"tf":3.0},"17":{"tf":2.0},"28":{"tf":1.7320508075688772},"31":{"tf":3.1622776601683795},"8":{"tf":1.4142135623730951},"9":{"tf":1.7320508075688772}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"13":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"41":{"tf":1.7320508075688772},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"41":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.0},"29":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":4,"docs":{"28":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.23606797749979}}}},"t":{"df":3,"docs":{"48":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"5":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"d":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"df":10,"docs":{"51":{"tf":1.7320508075688772},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"4":{"tf":1.0}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"8":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":3,"docs":{"13":{"tf":1.0},"27":{"tf":1.4142135623730951},"58":{"tf":1.0}}}}}},"v":{"df":1,"docs":{"13":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":1,"docs":{"13":{"tf":2.0}},"s":{"df":2,"docs":{"34":{"tf":1.0},"58":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":4.795831523312719}}}}},"o":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"25":{"tf":1.4142135623730951}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"68":{"tf":3.0},"69":{"tf":2.449489742783178}}},"df":0,"docs":{}}},"df":9,"docs":{"11":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.0},"21":{"tf":1.4142135623730951},"25":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":3.0},"58":{"tf":1.0},"69":{"tf":1.7320508075688772}},"i":{"d":{"df":1,"docs":{"68":{"tf":2.23606797749979}}},"df":0,"docs":{}}}}}},"d":{"df":0,"docs":{},"u":{"c":{"df":3,"docs":{"10":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"'":{"df":1,"docs":{"41":{"tf":1.4142135623730951}}},"_":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"68":{"tf":1.0}}},"df":0,"docs":{}}},"df":19,"docs":{"1":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951},"35":{"tf":2.0},"36":{"tf":1.0},"37":{"tf":2.449489742783178},"38":{"tf":1.7320508075688772},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"41":{"tf":1.7320508075688772},"42":{"tf":2.0},"43":{"tf":2.449489742783178},"44":{"tf":1.0},"45":{"tf":1.0},"56":{"tf":1.7320508075688772},"58":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.0}}}}}}}}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"13":{"tf":1.0},"19":{"tf":1.0}}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.4142135623730951},"28":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"f":{"df":8,"docs":{"10":{"tf":1.0},"27":{"tf":2.23606797749979},"28":{"tf":2.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.6457513110645907},"7":{"tf":1.7320508075688772},"8":{"tf":2.8284271247461903}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":5,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"5":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}}},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"e":{"df":3,"docs":{"29":{"tf":1.0},"31":{"tf":1.0},"9":{"tf":1.0}}},"i":{"d":{"df":7,"docs":{"29":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}},"x":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}}},"u":{"b":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":11,"docs":{"3":{"tf":1.4142135623730951},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"52":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"63":{"tf":1.0},"68":{"tf":4.242640687119285},"69":{"tf":3.3166247903554}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":8,"docs":{"27":{"tf":1.0},"3":{"tf":1.4142135623730951},"35":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.4142135623730951},"52":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.4142135623730951}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":2,"docs":{"49":{"tf":1.4142135623730951},"62":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":3,"docs":{"1":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"29":{"tf":1.0}}}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"55":{"tf":1.0},"60":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"55":{"tf":1.0},"56":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}}}}}}}},"r":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":5,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"31":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"df":0,"docs":{},"k":{"df":2,"docs":{"15":{"tf":1.0},"16":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"13":{"tf":1.0},"19":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"12":{"tf":1.0},"15":{"tf":1.4142135623730951}}}},"d":{"df":2,"docs":{"35":{"tf":1.4142135623730951},"56":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}},"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"v":{"df":3,"docs":{"60":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.7320508075688772}}}}},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"d":{"df":3,"docs":{"25":{"tf":1.0},"28":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}},"df":1,"docs":{"15":{"tf":1.0}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"25":{"tf":1.0},"46":{"tf":1.0},"53":{"tf":1.4142135623730951}}}}},"g":{"a":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"1":{"tf":1.0},"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}}}}}}}}}}},"df":0,"docs":{}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"1":{"tf":1.0}}}}},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"z":{"df":0,"docs":{},"v":{"df":1,"docs":{"69":{"tf":1.0}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"i":{"c":{"df":9,"docs":{"25":{"tf":1.7320508075688772},"26":{"tf":1.0},"27":{"tf":2.449489742783178},"28":{"tf":1.7320508075688772},"29":{"tf":1.7320508075688772},"30":{"tf":1.4142135623730951},"31":{"tf":3.3166247903554},"5":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":3,"docs":{"13":{"tf":1.7320508075688772},"56":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"50":{"tf":1.0},"60":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"df":17,"docs":{"35":{"tf":1.0},"47":{"tf":1.0},"51":{"tf":3.1622776601683795},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"69":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"r":{"df":9,"docs":{"13":{"tf":1.0},"27":{"tf":2.23606797749979},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.7320508075688772},"5":{"tf":1.0},"68":{"tf":2.8284271247461903},"69":{"tf":2.0},"8":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"4":{"tf":1.7320508075688772}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"v":{"df":2,"docs":{"16":{"tf":1.0},"9":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":4,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0},"51":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":1,"docs":{"5":{"tf":1.0}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":16,"docs":{"11":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"36":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.7320508075688772},"57":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"59":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951},"63":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"65":{"tf":1.7320508075688772},"66":{"tf":1.4142135623730951}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"58":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":7,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"68":{"tf":3.872983346207417}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":2.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"o":{"a":{"d":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.4142135623730951},"12":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"n":{"d":{"df":4,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"t":{"df":2,"docs":{"39":{"tf":1.0},"6":{"tf":1.0}}}}},"p":{"c":{"df":7,"docs":{"47":{"tf":2.0},"48":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"53":{"tf":1.4142135623730951},"62":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"40":{"tf":1.0}}}},"n":{"df":3,"docs":{"10":{"tf":1.0},"19":{"tf":1.0},"44":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":7,"docs":{"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":2.0},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"43":{"tf":2.23606797749979}}}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":3,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"58":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":11,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":2.449489742783178},"5":{"tf":2.449489742783178},"6":{"tf":1.0}}},"p":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"13":{"tf":1.0}}}},"w":{"df":2,"docs":{"31":{"tf":1.0},"8":{"tf":1.0}}}},"c":{"a":{"df":0,"docs":{},"l":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"25":{"tf":1.0}}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":3,"docs":{"12":{"tf":2.23606797749979},"17":{"tf":1.4142135623730951},"4":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}}},"df":4,"docs":{"13":{"tf":2.0},"16":{"tf":1.7320508075688772},"42":{"tf":1.4142135623730951},"43":{"tf":1.7320508075688772}},"e":{"c":{"df":1,"docs":{"69":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"d":{"df":10,"docs":{"1":{"tf":1.4142135623730951},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"3":{"tf":1.0},"31":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"3":{"tf":1.0},"4":{"tf":1.0},"68":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"46":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"6":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"d":{"df":3,"docs":{"11":{"tf":2.0},"12":{"tf":1.4142135623730951},"31":{"tf":2.0}}},"df":1,"docs":{"1":{"tf":1.0}},"m":{"df":1,"docs":{"5":{"tf":1.0}}}},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"25":{"tf":1.0},"27":{"tf":1.4142135623730951}}}}}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.0},"11":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"d":{"df":6,"docs":{"16":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"51":{"tf":1.4142135623730951},"68":{"tf":2.23606797749979},"69":{"tf":3.605551275463989}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"50":{"tf":1.0},"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":2,"docs":{"19":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"9":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":4,"docs":{"1":{"tf":1.0},"3":{"tf":1.4142135623730951},"31":{"tf":1.0},"51":{"tf":1.0}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"19":{"tf":1.0},"35":{"tf":1.0}}}}}},"h":{"a":{"df":2,"docs":{"52":{"tf":1.0},"6":{"tf":1.0}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"34":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"34":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"44":{"tf":1.0}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":12,"docs":{"1":{"tf":1.0},"11":{"tf":1.4142135623730951},"31":{"tf":2.8284271247461903},"37":{"tf":1.0},"52":{"tf":1.4142135623730951},"54":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"65":{"tf":1.4142135623730951},"68":{"tf":3.605551275463989},"69":{"tf":3.4641016151377544}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"65":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"65":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"50":{"tf":1.0},"66":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":8,"docs":{"3":{"tf":1.0},"31":{"tf":1.7320508075688772},"52":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.0}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"36":{"tf":1.0},"58":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":3,"docs":{"10":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":10,"docs":{"13":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"4":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0}}}}},"z":{"df":0,"docs":{},"e":{"df":1,"docs":{"28":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}},"df":2,"docs":{"13":{"tf":1.4142135623730951},"29":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":3.4641016151377544},"15":{"tf":1.0},"16":{"tf":2.0},"17":{"tf":1.0},"4":{"tf":1.0}}},"w":{"df":1,"docs":{"9":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"25":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"15":{"tf":1.4142135623730951},"16":{"tf":1.0},"5":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"o":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"l":{"a":{"df":0,"docs":{},"n":{"a":{"'":{"df":6,"docs":{"1":{"tf":1.4142135623730951},"25":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":2.0}}},"df":22,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":2.23606797749979},"28":{"tf":1.7320508075688772},"3":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":2.23606797749979},"35":{"tf":1.0},"4":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.7320508075688772},"5":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.0},"67":{"tf":1.7320508075688772},"68":{"tf":3.872983346207417},"69":{"tf":4.69041575982343},"8":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"27":{"tf":1.0},"31":{"tf":1.0}}}},"v":{"df":1,"docs":{"25":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"27":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"a":{"c":{"df":0,"docs":{},"e":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":8,"docs":{"0":{"tf":1.0},"11":{"tf":1.0},"31":{"tf":2.0},"38":{"tf":1.4142135623730951},"47":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":5,"docs":{"13":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.0},"68":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"43":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"9":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":2,"docs":{"19":{"tf":1.7320508075688772},"40":{"tf":1.0}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}},"k":{"df":0,"docs":{},"e":{"df":5,"docs":{"10":{"tf":1.0},"12":{"tf":1.0},"15":{"tf":1.0},"29":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951}}}},"n":{"d":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"1":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.0},"8":{"tf":1.0}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"50":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":3,"docs":{"13":{"tf":1.0},"37":{"tf":2.0},"38":{"tf":1.4142135623730951}}},"u":{"df":2,"docs":{"54":{"tf":1.0},"58":{"tf":1.4142135623730951}},"s":{"df":1,"docs":{"58":{"tf":1.4142135623730951}}}}},"y":{"df":1,"docs":{"28":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"19":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"26":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"31":{"tf":1.4142135623730951},"35":{"tf":2.0},"8":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":4,"docs":{"27":{"tf":1.0},"29":{"tf":1.4142135623730951},"31":{"tf":1.0},"37":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"16":{"tf":1.0},"19":{"tf":1.0},"28":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"56":{"tf":1.0}}}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"5":{"tf":1.0}}}},"n":{"df":0,"docs":{},"g":{"df":11,"docs":{"11":{"tf":1.0},"51":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"58":{"tf":1.7320508075688772},"60":{"tf":2.0},"61":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"65":{"tf":1.4142135623730951}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"u":{"b":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"(":{"df":1,"docs":{"69":{"tf":1.0}}},"df":1,"docs":{"69":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":1,"docs":{"56":{"tf":1.0}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"27":{"tf":1.0},"31":{"tf":2.0},"34":{"tf":1.0},"62":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"63":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":6,"docs":{"50":{"tf":1.0},"62":{"tf":2.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":1,"docs":{"58":{"tf":1.0}},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"51":{"tf":1.0},"58":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"37":{"tf":1.0},"43":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"h":{"df":1,"docs":{"1":{"tf":1.0}}}},"d":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"25":{"tf":1.0},"27":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{},"x":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":6,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"28":{"tf":1.0},"35":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"5":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"12":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}}},"y":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"28":{"tf":1.0}}}}}}},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":14,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"15":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"28":{"tf":1.0},"5":{"tf":2.6457513110645907},"6":{"tf":1.0},"7":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":1,"docs":{"8":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":5,"docs":{"10":{"tf":1.4142135623730951},"28":{"tf":1.0},"42":{"tf":1.4142135623730951},"5":{"tf":2.449489742783178},"68":{"tf":1.0}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{":":{":":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"42":{"tf":1.0},"43":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"31":{"tf":1.0},"41":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"34":{"tf":1.4142135623730951}}}}}}},"df":3,"docs":{"12":{"tf":2.449489742783178},"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":3,"docs":{"1":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"9":{"tf":1.0}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}}}}}}},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":2,"docs":{"2":{"tf":1.4142135623730951},"4":{"tf":1.4142135623730951}}}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.4142135623730951},"5":{"tf":1.0},"6":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"19":{"tf":1.4142135623730951}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":4,"docs":{"31":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"43":{"tf":1.0},"5":{"tf":1.4142135623730951}}}},"r":{"d":{"df":3,"docs":{"19":{"tf":1.4142135623730951},"68":{"tf":1.0},"69":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":4,"docs":{"27":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"37":{"tf":1.0}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"s":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":2,"docs":{"1":{"tf":1.0},"19":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":3,"docs":{"15":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":1.0}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":2.23606797749979}}}}}}}}}},"u":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"37":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":2.23606797749979},"13":{"tf":3.872983346207417},"15":{"tf":1.0},"16":{"tf":1.7320508075688772},"17":{"tf":1.7320508075688772},"3":{"tf":2.23606797749979}}}},"df":1,"docs":{"31":{"tf":1.4142135623730951}},"m":{"df":0,"docs":{},"e":{"df":9,"docs":{"10":{"tf":1.0},"13":{"tf":2.449489742783178},"27":{"tf":1.0},"4":{"tf":1.4142135623730951},"5":{"tf":2.6457513110645907},"6":{"tf":1.0},"68":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"16":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951},"69":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":3,"docs":{"6":{"tf":1.4142135623730951},"68":{"tf":2.6457513110645907},"69":{"tf":3.0}}}}},"df":0,"docs":{}}}}}},"l":{"df":1,"docs":{"69":{"tf":1.0}}},"o":{"d":{"df":0,"docs":{},"o":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":10,"docs":{"29":{"tf":1.0},"3":{"tf":1.0},"35":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"42":{"tf":1.4142135623730951},"43":{"tf":1.0},"56":{"tf":1.4142135623730951},"60":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"69":{"tf":2.23606797749979}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"9":{"tf":1.0}}},"l":{"df":2,"docs":{"19":{"tf":1.0},"67":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"28":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"p":{"df":2,"docs":{"1":{"tf":1.7320508075688772},"3":{"tf":1.0}},"u":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}}},"r":{"a":{"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"16":{"tf":1.0},"3":{"tf":1.0},"9":{"tf":1.0}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":25,"docs":{"1":{"tf":2.449489742783178},"12":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"25":{"tf":1.0},"29":{"tf":1.0},"3":{"tf":2.449489742783178},"36":{"tf":1.0},"37":{"tf":2.0},"39":{"tf":1.4142135623730951},"40":{"tf":2.23606797749979},"41":{"tf":1.0},"43":{"tf":1.7320508075688772},"44":{"tf":1.0},"5":{"tf":1.7320508075688772},"52":{"tf":1.0},"54":{"tf":2.0},"58":{"tf":3.0},"59":{"tf":1.0},"6":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.7320508075688772},"65":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"69":{"tf":3.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"68":{"tf":2.449489742783178},"69":{"tf":3.0}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"t":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"25":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":2,"docs":{"13":{"tf":1.0},"25":{"tf":1.0}}}},"i":{"df":1,"docs":{"5":{"tf":1.0}}},"u":{"df":0,"docs":{},"e":{",":{"\"":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"64":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":1,"docs":{"54":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"27":{"tf":1.0},"5":{"tf":1.4142135623730951},"6":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"6":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"u":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}}},"w":{"df":0,"docs":{},"o":{"df":4,"docs":{"13":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0},"5":{"tf":1.0}}}},"x":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"68":{"tf":3.3166247903554}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":10,"docs":{"37":{"tf":1.0},"51":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}},"i":{"c":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"10":{"tf":1.0},"30":{"tf":1.0}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"5":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"51":{"tf":1.0}}}},"t":{"df":4,"docs":{"19":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"3":{"tf":1.0}}}},"k":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"58":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"68":{"tf":1.0},"69":{"tf":2.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":1,"docs":{"59":{"tf":1.0}}}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":4,"docs":{"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"65":{"tf":1.0},"66":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":2,"docs":{"15":{"tf":1.0},"9":{"tf":1.0}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}},"p":{"df":8,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"11":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"40":{"tf":1.0},"5":{"tf":1.0}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}},"s":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":3.605551275463989}}}},"d":{"df":2,"docs":{"27":{"tf":1.0},"30":{"tf":1.0}}},"df":23,"docs":{"1":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.0},"3":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":2.8284271247461903},"35":{"tf":1.0},"4":{"tf":2.23606797749979},"42":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.4142135623730951},"5":{"tf":1.7320508075688772},"51":{"tf":1.0},"6":{"tf":2.23606797749979},"62":{"tf":1.0},"68":{"tf":1.0},"8":{"tf":2.0},"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":6,"docs":{"37":{"tf":1.0},"40":{"tf":1.0},"56":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":3,"docs":{"35":{"tf":1.0},"4":{"tf":1.0},"42":{"tf":1.7320508075688772}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"'":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"29":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":14,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":2.23606797749979},"15":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"25":{"tf":2.0},"27":{"tf":1.7320508075688772},"29":{"tf":1.7320508075688772},"3":{"tf":1.4142135623730951},"31":{"tf":2.23606797749979},"4":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":2,"docs":{"31":{"tf":1.0},"51":{"tf":1.0}}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"f":{"df":3,"docs":{"6":{"tf":1.4142135623730951},"8":{"tf":1.7320508075688772},"9":{"tf":3.0}}}},"df":3,"docs":{"16":{"tf":1.0},"17":{"tf":1.0},"69":{"tf":3.3166247903554}},"e":{"c":{"<":{"df":0,"docs":{},"u":{"8":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}},"f":{"df":5,"docs":{"1":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"30":{"tf":1.0},"9":{"tf":1.0}},"i":{"df":7,"docs":{"28":{"tf":1.4142135623730951},"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":1.0},"6":{"tf":2.0},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"42":{"tf":1.0},"69":{"tf":4.69041575982343}}}}}}}},"i":{"a":{"df":2,"docs":{"11":{"tf":1.0},"12":{"tf":1.0}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"13":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0}}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":7,"docs":{"11":{"tf":1.0},"12":{"tf":1.4142135623730951},"13":{"tf":2.449489742783178},"15":{"tf":1.7320508075688772},"16":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.0},"69":{"tf":1.0}}}},"l":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"68":{"tf":1.0}}},"df":3,"docs":{"67":{"tf":1.4142135623730951},"68":{"tf":3.872983346207417},"69":{"tf":4.69041575982343}}}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"r":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"h":{"/":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"19":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":2.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":2.23606797749979}}}}},"y":{"df":5,"docs":{"19":{"tf":1.0},"28":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.7320508075688772},"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"'":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.0}}}},"r":{"df":1,"docs":{"5":{"tf":1.0}}}},"b":{"3":{".":{"df":0,"docs":{},"j":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"49":{"tf":1.4142135623730951},"50":{"tf":1.0},"62":{"tf":2.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"15":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"l":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"5":{"tf":1.0},"6":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":2,"docs":{"20":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"37":{"tf":1.0},"5":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"37":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"40":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"d":{"df":1,"docs":{"3":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":4,"docs":{"25":{"tf":1.0},"29":{"tf":1.0},"44":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":3,"docs":{"20":{"tf":1.0},"35":{"tf":1.4142135623730951},"58":{"tf":1.0}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"33":{"tf":1.0},"35":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"s":{":":{"/":{"/":{"<":{"a":{"d":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"8":{"9":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"df":10,"docs":{"13":{"tf":1.7320508075688772},"51":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}},"x":{"df":1,"docs":{"13":{"tf":1.0}}}},"y":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"27":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"'":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"z":{"df":2,"docs":{"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":1,"docs":{"43":{"tf":1.0}}}}}}}},"title":{"root":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"38":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":2,"docs":{"47":{"tf":1.0},"53":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"46":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"67":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":1,"docs":{"8":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"52":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"0":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"48":{"tf":1.0},"49":{"tf":1.0}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"39":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"41":{"tf":1.0}}}}}},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"14":{"tf":1.0},"68":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"40":{"tf":1.0},"41":{"tf":1.0}}}}},"df":0,"docs":{}}}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"13":{"tf":1.0}}},"m":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"45":{"tf":1.0},"51":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"d":{"df":2,"docs":{"18":{"tf":1.0},"20":{"tf":1.0}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"4":{"tf":1.0},"44":{"tf":1.0}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"11":{"tf":1.0}}}}},"t":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"55":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"7":{"tf":1.0}}}}}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":1,"docs":{"48":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"a":{"c":{"df":1,"docs":{"42":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"33":{"tf":1.0},"6":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"41":{"tf":1.0}}}}}}}},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"47":{"tf":1.0},"53":{"tf":1.0}},"r":{"df":0,"docs":{},"p":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"l":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"16":{"tf":1.0}}}}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"45":{"tf":1.0}}}}}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{}}}},"p":{"df":1,"docs":{"41":{"tf":1.0}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"8":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"50":{"tf":1.0}}},"df":0,"docs":{}}}}}},"n":{"c":{"df":0,"docs":{},"p":{"df":1,"docs":{"23":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":2,"docs":{"17":{"tf":1.0},"29":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"43":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":1.0}}}}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"15":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"19":{"tf":1.0},"20":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"h":{"df":1,"docs":{"28":{"tf":1.0}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"41":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"21":{"tf":1.0}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"'":{"df":1,"docs":{"41":{"tf":1.0}}},"df":2,"docs":{"38":{"tf":1.0},"41":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"f":{"df":1,"docs":{"7":{"tf":1.0}}}},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"41":{"tf":1.0}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"u":{"b":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"53":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":2,"docs":{"8":{"tf":1.0},"9":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"25":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":1,"docs":{"51":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"4":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.0},"12":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"c":{"df":4,"docs":{"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"53":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"s":{"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"d":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{}},"n":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"66":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"a":{"df":3,"docs":{"32":{"tf":1.0},"34":{"tf":1.0},"67":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{}}},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"26":{"tf":1.0},"35":{"tf":1.0}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"42":{"tf":1.0}}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"3":{"tf":1.0}}}}}}}}},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":2,"docs":{"2":{"tf":1.0},"4":{"tf":1.0}}}}}}}}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"21":{"tf":1.0},"22":{"tf":1.0},"39":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"21":{"tf":1.0},"22":{"tf":1.0}}}}},"s":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":2,"docs":{"3":{"tf":1.0},"4":{"tf":1.0}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"22":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"f":{"df":2,"docs":{"6":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"67":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"49":{"tf":1.0},"62":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"38":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"44":{"tf":1.0}}}}}}}}},"pipeline":["trimmer","stopWordFilter","stemmer"],"ref":"id","version":"0.9.5"},"results_options":{"limit_results":30,"teaser_word_count":30},"search_options":{"bool":"OR","expand":true,"fields":{"body":{"boost":1},"breadcrumbs":{"boost":1},"title":{"boost":2}}}} \ No newline at end of file diff --git a/book/service.rs b/book/service.rs deleted file mode 100644 index a882181bc4b469..00000000000000 --- a/book/service.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! The `service` module implements a trait used by services and stages. -//! -//! A Service is any object that implements its functionality on a separate thread. It implements a -//! `join()` method, which can be used to wait for that thread to close. -//! -//! The Service trait may also be used to implement a pipeline stage. Like a service, its -//! functionality is also implemented by a thread, but unlike a service, a stage isn't a server -//! that replies to client requests. Instead, a stage acts more like a pure function. It's a oneway -//! street. It processes messages from its input channel and then sends the processed data to an -//! output channel. Stages can be composed to form a linear chain called a pipeline. -//! -//! The approach to creating a pipeline stage in Rust may be unique to Solana. We haven't seen the -//! same technique used in other Rust projects and there may be better ways to do it. The Solana -//! approach defines a stage as an object that communicates to its previous stage and the next -//! stage using channels. By convention, each stage accepts a *receiver* for input and creates a -//! second output channel. The second channel is used to pass data to the next stage, and so its -//! sender is moved into the stage's thread and the receiver is returned from its constructor. -//! -//! A well-written stage should create a thread and call a short `run()` method. The method should -//! read input from its input channel, call a function from another module that processes it, and -//! then send the output to the output channel. The functionality in the second module will likely -//! not use threads or channels. - -use std::thread::Result; - -pub trait Service { - type JoinReturnType; - - fn join(self) -> Result; -} diff --git a/book/signature.rs b/book/signature.rs deleted file mode 100644 index fa283ff885ac5f..00000000000000 --- a/book/signature.rs +++ /dev/null @@ -1,67 +0,0 @@ -//! The `signature` module provides functionality for public, and private keys. - -use rand::{ChaChaRng, Rng, SeedableRng}; -use rayon::prelude::*; -use untrusted::Input; - -pub use solana_sdk::signature::*; - -pub struct GenKeys { - generator: ChaChaRng, -} - -impl GenKeys { - pub fn new(seed: [u8; 32]) -> GenKeys { - let generator = ChaChaRng::from_seed(seed); - GenKeys { generator } - } - - fn gen_seed(&mut self) -> [u8; 32] { - let mut seed = [0u8; 32]; - self.generator.fill(&mut seed); - seed - } - - fn gen_n_seeds(&mut self, n: u64) -> Vec<[u8; 32]> { - (0..n).map(|_| self.gen_seed()).collect() - } - - pub fn gen_n_keypairs(&mut self, n: u64) -> Vec { - self.gen_n_seeds(n) - .into_par_iter() - .map(|seed| Keypair::from_seed_unchecked(Input::from(&seed)).unwrap()) - .collect() - } -} - -#[cfg(test)] -mod tests { - use super::*; - pub use solana_sdk::pubkey::Pubkey; - use std::collections::HashSet; - - #[test] - fn test_new_key_is_deterministic() { - let seed = [0u8; 32]; - let mut gen0 = GenKeys::new(seed); - let mut gen1 = GenKeys::new(seed); - - for _ in 0..100 { - assert_eq!(gen0.gen_seed().to_vec(), gen1.gen_seed().to_vec()); - } - } - - fn gen_n_pubkeys(seed: [u8; 32], n: u64) -> HashSet { - GenKeys::new(seed) - .gen_n_keypairs(n) - .into_iter() - .map(|x| x.pubkey()) - .collect() - } - - #[test] - fn test_gen_n_pubkeys_deterministic() { - let seed = [0u8; 32]; - assert_eq!(gen_n_pubkeys(seed, 50), gen_n_pubkeys(seed, 50)); - } -} diff --git a/book/sigverify.rs b/book/sigverify.rs deleted file mode 100644 index 301b18bf2fa20c..00000000000000 --- a/book/sigverify.rs +++ /dev/null @@ -1,473 +0,0 @@ -//! The `sigverify` module provides digital signature verification functions. -//! By default, signatures are verified in parallel using all available CPU -//! cores. When `--features=cuda` is enabled, signature verification is -//! offloaded to the GPU. -//! - -use byteorder::{LittleEndian, ReadBytesExt}; -use counter::Counter; -use log::Level; -use packet::{Packet, SharedPackets}; -use result::Result; -use signature::Signature; -use solana_sdk::pubkey::Pubkey; -use std::io; -use std::mem::size_of; -use std::sync::atomic::AtomicUsize; -#[cfg(test)] -use transaction::Transaction; - -pub const TX_OFFSET: usize = 0; - -type TxOffsets = (Vec, Vec, Vec, Vec, Vec>); - -#[cfg(feature = "cuda")] -#[repr(C)] -struct Elems { - elems: *const Packet, - num: u32, -} - -#[cfg(feature = "cuda")] -#[link(name = "cuda-crypt")] -extern "C" { - fn ed25519_init() -> bool; - fn ed25519_set_verbose(val: bool); - fn ed25519_verify_many( - vecs: *const Elems, - num: u32, //number of vecs - message_size: u32, //size of each element inside the elems field of the vec - total_packets: u32, - total_signatures: u32, - message_lens: *const u32, - pubkey_offsets: *const u32, - signature_offsets: *const u32, - signed_message_offsets: *const u32, - out: *mut u8, //combined length of all the items in vecs - ) -> u32; - - pub fn chacha_cbc_encrypt_many_sample( - input: *const u8, - sha_state: *mut u8, - in_len: usize, - keys: *const u8, - ivec: *mut u8, - num_keys: u32, - samples: *const u64, - num_samples: u32, - starting_block: u64, - time_us: *mut f32, - ); - - pub fn chacha_init_sha_state(sha_state: *mut u8, num_keys: u32); - pub fn chacha_end_sha_state(sha_state_in: *const u8, out: *mut u8, num_keys: u32); -} - -#[cfg(not(feature = "cuda"))] -pub fn init() { - // stub -} - -fn verify_packet(packet: &Packet) -> u8 { - use ring::signature; - use signature::Signature; - use solana_sdk::pubkey::Pubkey; - use untrusted; - - let (sig_len, sig_start, msg_start, pubkey_start) = get_packet_offsets(packet, 0); - let mut sig_start = sig_start as usize; - let mut pubkey_start = pubkey_start as usize; - let msg_start = msg_start as usize; - - if packet.meta.size <= msg_start { - return 0; - } - - let msg_end = packet.meta.size; - for _ in 0..sig_len { - let pubkey_end = pubkey_start as usize + size_of::(); - let sig_end = sig_start as usize + size_of::(); - - if pubkey_end >= packet.meta.size || sig_end >= packet.meta.size { - return 0; - } - - if signature::verify( - &signature::ED25519, - untrusted::Input::from(&packet.data[pubkey_start..pubkey_end]), - untrusted::Input::from(&packet.data[msg_start..msg_end]), - untrusted::Input::from(&packet.data[sig_start..sig_end]), - ).is_err() - { - return 0; - } - pubkey_start += size_of::(); - sig_start += size_of::(); - } - 1 -} - -fn verify_packet_disabled(_packet: &Packet) -> u8 { - warn!("signature verification is disabled"); - 1 -} - -fn batch_size(batches: &[SharedPackets]) -> usize { - batches - .iter() - .map(|p| p.read().unwrap().packets.len()) - .sum() -} - -#[cfg(not(feature = "cuda"))] -pub fn ed25519_verify(batches: &[SharedPackets]) -> Vec> { - ed25519_verify_cpu(batches) -} - -pub fn get_packet_offsets(packet: &Packet, current_offset: u32) -> (u32, u32, u32, u32) { - // Read in u64 as the size of signatures array - let mut rdr = io::Cursor::new(&packet.data[TX_OFFSET..size_of::()]); - let sig_len = rdr.read_u64::().unwrap() as u32; - - let msg_start_offset = - current_offset + size_of::() as u32 + sig_len * size_of::() as u32; - let pubkey_offset = msg_start_offset + size_of::() as u32; - - let sig_start = TX_OFFSET as u32 + size_of::() as u32; - - (sig_len, sig_start, msg_start_offset, pubkey_offset) -} - -pub fn generate_offsets(batches: &[SharedPackets]) -> Result { - let mut signature_offsets: Vec<_> = Vec::new(); - let mut pubkey_offsets: Vec<_> = Vec::new(); - let mut msg_start_offsets: Vec<_> = Vec::new(); - let mut msg_sizes: Vec<_> = Vec::new(); - let mut current_packet = 0; - let mut v_sig_lens = Vec::new(); - batches.into_iter().for_each(|p| { - let mut sig_lens = Vec::new(); - p.read().unwrap().packets.iter().for_each(|packet| { - let current_offset = current_packet as u32 * size_of::() as u32; - - let (sig_len, _sig_start, msg_start_offset, pubkey_offset) = - get_packet_offsets(packet, current_offset); - let mut pubkey_offset = pubkey_offset; - - sig_lens.push(sig_len); - - trace!("pubkey_offset: {}", pubkey_offset); - let mut sig_offset = current_offset + size_of::() as u32; - for _ in 0..sig_len { - signature_offsets.push(sig_offset); - sig_offset += size_of::() as u32; - - pubkey_offsets.push(pubkey_offset); - pubkey_offset += size_of::() as u32; - - msg_start_offsets.push(msg_start_offset); - - msg_sizes.push(current_offset + (packet.meta.size as u32) - msg_start_offset); - } - current_packet += 1; - }); - v_sig_lens.push(sig_lens); - }); - Ok(( - signature_offsets, - pubkey_offsets, - msg_start_offsets, - msg_sizes, - v_sig_lens, - )) -} - -pub fn ed25519_verify_cpu(batches: &[SharedPackets]) -> Vec> { - use rayon::prelude::*; - let count = batch_size(batches); - info!("CPU ECDSA for {}", batch_size(batches)); - let rv = batches - .into_par_iter() - .map(|p| { - p.read() - .unwrap() - .packets - .par_iter() - .map(verify_packet) - .collect() - }).collect(); - inc_new_counter_info!("ed25519_verify_cpu", count); - rv -} - -pub fn ed25519_verify_disabled(batches: &[SharedPackets]) -> Vec> { - use rayon::prelude::*; - let count = batch_size(batches); - info!("disabled ECDSA for {}", batch_size(batches)); - let rv = batches - .into_par_iter() - .map(|p| { - p.read() - .unwrap() - .packets - .par_iter() - .map(verify_packet_disabled) - .collect() - }).collect(); - inc_new_counter_info!("ed25519_verify_disabled", count); - rv -} - -#[cfg(feature = "cuda")] -pub fn init() { - unsafe { - ed25519_set_verbose(true); - if !ed25519_init() { - panic!("ed25519_init() failed"); - } - ed25519_set_verbose(false); - } -} - -#[cfg(feature = "cuda")] -pub fn ed25519_verify(batches: &[SharedPackets]) -> Vec> { - use packet::PACKET_DATA_SIZE; - let count = batch_size(batches); - - // micro-benchmarks show GPU time for smallest batch around 15-20ms - // and CPU speed for 64-128 sigverifies around 10-20ms. 64 is a nice - // power-of-two number around that accounting for the fact that the CPU - // may be busy doing other things while being a real fullnode - // TODO: dynamically adjust this crossover - if count < 64 { - return ed25519_verify_cpu(batches); - } - - let (signature_offsets, pubkey_offsets, msg_start_offsets, msg_sizes, sig_lens) = - generate_offsets(batches).unwrap(); - - info!("CUDA ECDSA for {}", batch_size(batches)); - let mut out = Vec::new(); - let mut elems = Vec::new(); - let mut locks = Vec::new(); - let mut rvs = Vec::new(); - - for packets in batches { - locks.push(packets.read().unwrap()); - } - let mut num_packets = 0; - for p in locks { - elems.push(Elems { - elems: p.packets.as_ptr(), - num: p.packets.len() as u32, - }); - let mut v = Vec::new(); - v.resize(p.packets.len(), 0); - rvs.push(v); - num_packets += p.packets.len(); - } - out.resize(signature_offsets.len(), 0); - trace!("Starting verify num packets: {}", num_packets); - trace!("elem len: {}", elems.len() as u32); - trace!("packet sizeof: {}", size_of::() as u32); - trace!("len offset: {}", PACKET_DATA_SIZE as u32); - unsafe { - let res = ed25519_verify_many( - elems.as_ptr(), - elems.len() as u32, - size_of::() as u32, - num_packets as u32, - signature_offsets.len() as u32, - msg_sizes.as_ptr(), - pubkey_offsets.as_ptr(), - signature_offsets.as_ptr(), - msg_start_offsets.as_ptr(), - out.as_mut_ptr(), - ); - if res != 0 { - trace!("RETURN!!!: {}", res); - } - } - trace!("done verify"); - let mut num = 0; - for (vs, sig_vs) in rvs.iter_mut().zip(sig_lens.iter()) { - for (mut v, sig_v) in vs.iter_mut().zip(sig_vs.iter()) { - let mut vout = 1; - for _ in 0..*sig_v { - if 0 == out[num] { - vout = 0; - } - num += 1; - } - *v = vout; - if *v != 0 { - trace!("VERIFIED PACKET!!!!!"); - } - } - } - inc_new_counter_info!("ed25519_verify_gpu", count); - rvs -} - -#[cfg(test)] -pub fn make_packet_from_transaction(tx: Transaction) -> Packet { - use bincode::serialize; - - let tx_bytes = serialize(&tx).unwrap(); - let mut packet = Packet::default(); - packet.meta.size = tx_bytes.len(); - packet.data[..packet.meta.size].copy_from_slice(&tx_bytes); - return packet; -} - -#[cfg(test)] -mod tests { - use bincode::serialize; - use budget_program::BudgetState; - use packet::{Packet, SharedPackets}; - use signature::{Keypair, KeypairUtil}; - use sigverify; - use solana_sdk::hash::Hash; - use solana_sdk::system_instruction::SystemInstruction; - use system_program::SystemProgram; - use system_transaction::{memfind, test_tx}; - use transaction; - use transaction::Transaction; - - #[test] - fn test_layout() { - let tx = test_tx(); - let tx_bytes = serialize(&tx).unwrap(); - let packet = serialize(&tx).unwrap(); - assert_matches!(memfind(&packet, &tx_bytes), Some(sigverify::TX_OFFSET)); - assert_matches!(memfind(&packet, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), None); - } - - #[test] - fn test_get_packet_offsets() { - let tx = test_tx(); - let packet = sigverify::make_packet_from_transaction(tx); - let (sig_len, sig_start, msg_start_offset, pubkey_offset) = - sigverify::get_packet_offsets(&packet, 0); - assert_eq!(sig_len, 1); - assert_eq!(sig_start, 8); - assert_eq!(msg_start_offset, 72); - assert_eq!(pubkey_offset, 80); - } - - fn generate_packet_vec( - packet: &Packet, - num_packets_per_batch: usize, - num_batches: usize, - ) -> Vec { - // generate packet vector - let batches: Vec<_> = (0..num_batches) - .map(|_| { - let packets = SharedPackets::default(); - packets - .write() - .unwrap() - .packets - .resize(0, Default::default()); - for _ in 0..num_packets_per_batch { - packets.write().unwrap().packets.push(packet.clone()); - } - assert_eq!(packets.read().unwrap().packets.len(), num_packets_per_batch); - packets - }).collect(); - assert_eq!(batches.len(), num_batches); - - batches - } - - fn test_verify_n(n: usize, modify_data: bool) { - let tx = test_tx(); - let mut packet = sigverify::make_packet_from_transaction(tx); - - // jumble some data to test failure - if modify_data { - packet.data[20] = packet.data[20].wrapping_add(10); - } - - let batches = generate_packet_vec(&packet, n, 2); - - // verify packets - let ans = sigverify::ed25519_verify(&batches); - - // check result - let ref_ans = if modify_data { 0u8 } else { 1u8 }; - assert_eq!(ans, vec![vec![ref_ans; n], vec![ref_ans; n]]); - } - - #[test] - fn test_verify_zero() { - test_verify_n(0, false); - } - - #[test] - fn test_verify_one() { - test_verify_n(1, false); - } - - #[test] - fn test_verify_seventy_one() { - test_verify_n(71, false); - } - - #[test] - fn test_verify_multi_sig() { - use logger; - logger::setup(); - let keypair0 = Keypair::new(); - let keypair1 = Keypair::new(); - let keypairs = vec![&keypair0, &keypair1]; - let tokens = 5; - let fee = 2; - let last_id = Hash::default(); - - let keys = vec![keypair0.pubkey(), keypair1.pubkey()]; - - let system_instruction = SystemInstruction::Move { tokens }; - - let program_ids = vec![SystemProgram::id(), BudgetState::id()]; - - let instructions = vec![transaction::Instruction::new( - 0, - &system_instruction, - vec![0, 1], - )]; - - let tx = Transaction::new_with_instructions( - &keypairs, - &keys, - last_id, - fee, - program_ids, - instructions, - ); - - let mut packet = sigverify::make_packet_from_transaction(tx); - - let n = 4; - let num_batches = 3; - let batches = generate_packet_vec(&packet, n, num_batches); - - packet.data[40] = packet.data[40].wrapping_add(8); - - batches[0].write().unwrap().packets.push(packet); - - // verify packets - let ans = sigverify::ed25519_verify(&batches); - - // check result - let ref_ans = 1u8; - let mut ref_vec = vec![vec![ref_ans; n]; num_batches]; - ref_vec[0].push(0u8); - assert_eq!(ans, ref_vec); - } - - #[test] - fn test_verify_fail() { - test_verify_n(5, true); - } -} diff --git a/book/sigverify_stage.rs b/book/sigverify_stage.rs deleted file mode 100644 index b2c28fe19324fa..00000000000000 --- a/book/sigverify_stage.rs +++ /dev/null @@ -1,156 +0,0 @@ -//! The `sigverify_stage` implements the signature verification stage of the TPU. It -//! receives a list of lists of packets and outputs the same list, but tags each -//! top-level list with a list of booleans, telling the next stage whether the -//! signature in that packet is valid. It assumes each packet contains one -//! transaction. All processing is done on the CPU by default and on a GPU -//! if the `cuda` feature is enabled with `--features=cuda`. - -use counter::Counter; - -use log::Level; -use packet::SharedPackets; -use rand::{thread_rng, Rng}; -use result::{Error, Result}; -use service::Service; -use sigverify; -use solana_metrics::{influxdb, submit}; -use solana_sdk::timing; -use std::sync::atomic::AtomicUsize; -use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender}; -use std::sync::{Arc, Mutex}; -use std::thread::{self, spawn, JoinHandle}; -use std::time::Instant; -use streamer::{self, PacketReceiver}; - -pub type VerifiedPackets = Vec<(SharedPackets, Vec)>; - -pub struct SigVerifyStage { - thread_hdls: Vec>, -} - -impl SigVerifyStage { - pub fn new( - packet_receiver: Receiver, - sigverify_disabled: bool, - ) -> (Self, Receiver) { - sigverify::init(); - let (verified_sender, verified_receiver) = channel(); - let thread_hdls = - Self::verifier_services(packet_receiver, verified_sender, sigverify_disabled); - (SigVerifyStage { thread_hdls }, verified_receiver) - } - - fn verify_batch(batch: Vec, sigverify_disabled: bool) -> VerifiedPackets { - let r = if sigverify_disabled { - sigverify::ed25519_verify_disabled(&batch) - } else { - sigverify::ed25519_verify(&batch) - }; - batch.into_iter().zip(r).collect() - } - - fn verifier( - recvr: &Arc>, - sendr: &Arc>>, - sigverify_disabled: bool, - ) -> Result<()> { - let (batch, len, recv_time) = - streamer::recv_batch(&recvr.lock().expect("'recvr' lock in fn verifier"))?; - inc_new_counter_info!("sigverify_stage-entries_received", len); - - let now = Instant::now(); - let batch_len = batch.len(); - let rand_id = thread_rng().gen_range(0, 100); - info!( - "@{:?} verifier: verifying: {} id: {}", - timing::timestamp(), - batch.len(), - rand_id - ); - - let verified_batch = Self::verify_batch(batch, sigverify_disabled); - inc_new_counter_info!( - "sigverify_stage-verified_entries_send", - verified_batch.len() - ); - - if sendr - .lock() - .expect("lock in fn verify_batch in tpu") - .send(verified_batch) - .is_err() - { - return Err(Error::SendError); - } - - let total_time_ms = timing::duration_as_ms(&now.elapsed()); - let total_time_s = timing::duration_as_s(&now.elapsed()); - inc_new_counter_info!( - "sigverify_stage-time_ms", - (total_time_ms + recv_time) as usize - ); - info!( - "@{:?} verifier: done. batches: {} total verify time: {:?} id: {} verified: {} v/s {}", - timing::timestamp(), - batch_len, - total_time_ms, - rand_id, - len, - (len as f32 / total_time_s) - ); - - submit( - influxdb::Point::new("sigverify_stage-total_verify_time") - .add_field("batch_len", influxdb::Value::Integer(batch_len as i64)) - .add_field("len", influxdb::Value::Integer(len as i64)) - .add_field( - "total_time_ms", - influxdb::Value::Integer(total_time_ms as i64), - ).to_owned(), - ); - - Ok(()) - } - - fn verifier_service( - packet_receiver: Arc>, - verified_sender: Arc>>, - sigverify_disabled: bool, - ) -> JoinHandle<()> { - spawn(move || loop { - if let Err(e) = Self::verifier(&packet_receiver, &verified_sender, sigverify_disabled) { - match e { - Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => break, - Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), - Error::SendError => { - break; - } - _ => error!("{:?}", e), - } - } - }) - } - - fn verifier_services( - packet_receiver: PacketReceiver, - verified_sender: Sender, - sigverify_disabled: bool, - ) -> Vec> { - let sender = Arc::new(Mutex::new(verified_sender)); - let receiver = Arc::new(Mutex::new(packet_receiver)); - (0..4) - .map(|_| Self::verifier_service(receiver.clone(), sender.clone(), sigverify_disabled)) - .collect() - } -} - -impl Service for SigVerifyStage { - type JoinReturnType = (); - - fn join(self) -> thread::Result<()> { - for thread_hdl in self.thread_hdls { - thread_hdl.join()?; - } - Ok(()) - } -} diff --git a/book/storage.html b/book/storage.html deleted file mode 100644 index c42fcff0269929..00000000000000 --- a/book/storage.html +++ /dev/null @@ -1,296 +0,0 @@ - - - - - - Storage - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

Storage

-

Background

-

At full capacity on a 1gbps network Solana would generate 4 petabytes of data -per year. If each fullnode was required to store the full ledger, the cost of -storage would discourage fullnode participation, thus centralizing the network -around those that could afford it. Solana aims to keep the cost of a fullnode -below $5,000 USD to maximize participation. To achieve that, the network needs -to minimize redundant storage while at the same time ensuring the validity and -availability of each copy.

-

To trust storage of ledger segments, Solana has replicators periodically -submit proofs to the network that the data was replicated. Each proof is called -a Proof of Replication. The basic idea of it is to encrypt a dataset with a -public symmetric key and then hash the encrypted dataset. Solana uses CBC -encryption. -To prevent a malicious replicator from deleting the data as soon as it's -hashed, a replicator is required hash random segments of the dataset. -Alternatively, Solana could require hashing the reverse of the encrypted data, -but random sampling is sufficient and much faster. Either solution ensures -that all the data is present during the generation of the proof and also -requires the validator to have the entirety of the encrypted data present for -verification of every proof of every identity. The space required to validate -is:

-

number_of_proofs * data_size

-

Optimization with PoH

-

Solana is not the only distribute systems project using Proof of Replication, -but it might be the most efficient implementation because of its ability to -synchronize nodes with its Proof of History. With PoH, Solana is able to record -a hash of the PoRep samples in the ledger. Thus the blocks stay in the exact -same order for every PoRep and verification can stream the data and verify all -the proofs in a single batch. This way Solana can verify multiple proofs -concurrently, each one on its own GPU core. With the current generation of -graphics cards our network can support up to 14,000 replication identities or -symmetric keys. The total space required for verification is:

-

2 CBC_blocks * number_of_identities

-

with core count of equal to (Number of Identities). A CBC block is expected to -be 1MB in size.

-

Network

-

Validators for PoRep are the same validators that are verifying transactions. -They have some stake that they have put up as collateral that ensures that -their work is honest. If you can prove that a validator verified a fake PoRep, -then the validator's stake is slashed.

-

Replicators are specialized light clients. They download a part of the ledger -and store it and provide proofs of storing the ledger. For each verified proof, -replicators are rewarded tokens from the mining pool.

-

Constraints

-

Solana's PoRep protocol instroduces the following constraints:

-
    -
  • At most 14,000 replication identities can be used, because that is how many GPU -cores are currently available to a computer costing under $5,000 USD.
  • -
  • Verification requires generating the CBC blocks. That requires space of 2 -blocks per identity, and 1 GPU core per identity for the same dataset. As -many identities at once are batched with as many proofs for those identities -verified concurrently for the same dataset.
  • -
-

Validation and Replication Protocol

-
    -
  1. The network sets a replication target number, let's say 1k. 1k PoRep -identities are created from signatures of a PoH hash. They are tied to a -specific PoH hash. It doesn't matter who creates them, or it could simply be -the last 1k validation signatures we saw for the ledger at that count. This is -maybe just the initial batch of identities, because we want to stagger identity -rotation.
  2. -
  3. Any client can use any of these identities to create PoRep proofs. -Replicator identities are the CBC encryption keys.
  4. -
  5. Periodically at a specific PoH count, a replicator that wants to create -PoRep proofs signs the PoH hash at that count. That signature is the seed -used to pick the block and identity to replicate. A block is 1TB of ledger.
  6. -
  7. Periodically at a specific PoH count, a replicator submits PoRep proofs for -their selected block. A signature of the PoH hash at that count is the seed -used to sample the 1TB encrypted block, and hash it. This is done faster than -it takes to encrypt the 1TB block with the original identity.
  8. -
  9. Replicators must submit some number of fake proofs, which they can prove to -be fake by providing the seed for the hash result.
  10. -
  11. Periodically at a specific PoH count, validators sign the hash and use the -signature to select the 1TB block that they need to validate. They batch all -the identities and proofs and submit approval for all the verified ones.
  12. -
  13. After #6, replicator client submit the proofs of fake proofs.
  14. -
-

For any random seed, Solana requires everyone to use a signature that is -derived from a PoH hash. Every node uses the same count so that the same PoH -hash is signed by every participant. The signatures are then each -cryptographically tied to the keypair, which prevents a leader from grinding on -the resulting value for more than 1 identity.

-

Key rotation is staggered. Once going, the next identity is generated by -hashing itself with a PoH hash.

-

Since there are many more client identities then encryption identities, the -reward is split amont multiple clients to prevent Sybil attacks from generating -many clients to acquire the same block of data. To remain BFT, the network -needs to avoid a single human entity from storing all the replications of a -single chunk of the ledger.

-

Solana's solution to this is to require clients to continue using the same -identity. If the first round is used to acquire the same block for many client -identities, the second round for the same client identities will require a -redistribution of the signatures, and therefore PoRep identities and blocks. -Thus to get a reward for storage, clients are not rewarded for storage of the -first block. The network rewards long-lived client identities more than new -ones.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/storage_program.rs b/book/storage_program.rs deleted file mode 100644 index bced49441a89fe..00000000000000 --- a/book/storage_program.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! storage program -//! Receive mining proofs from miners, validate the answers -//! and give reward for good proofs. - -use bincode::deserialize; -use solana_sdk::account::Account; -use solana_sdk::hash::Hash; -use solana_sdk::pubkey::Pubkey; -use transaction::Transaction; - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum StorageProgram { - SubmitMiningProof { sha_state: Hash }, -} - -pub enum StorageError { - InvalidUserData, -} - -const STORAGE_PROGRAM_ID: [u8; 32] = [ - 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, -]; - -impl StorageProgram { - pub fn check_id(program_id: &Pubkey) -> bool { - program_id.as_ref() == STORAGE_PROGRAM_ID - } - - pub fn id() -> Pubkey { - Pubkey::new(&STORAGE_PROGRAM_ID) - } - - pub fn get_balance(account: &Account) -> u64 { - account.tokens - } - - pub fn process_transaction( - tx: &Transaction, - pix: usize, - _accounts: &mut [&mut Account], - ) -> Result<(), StorageError> { - if let Ok(syscall) = deserialize(tx.userdata(pix)) { - match syscall { - StorageProgram::SubmitMiningProof { sha_state } => { - info!("Mining proof submitted with state {:?}", sha_state); - return Ok(()); - } - } - } else { - return Err(StorageError::InvalidUserData); - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use signature::Keypair; - use signature::KeypairUtil; - - #[test] - fn test_storage_tx() { - let keypair = Keypair::new(); - let tx = Transaction::new( - &keypair, - &[], - StorageProgram::id(), - &(), - Default::default(), - 0, - ); - assert!(StorageProgram::process_transaction(&tx, 0, &mut []).is_err()); - } -} diff --git a/book/storage_stage.rs b/book/storage_stage.rs deleted file mode 100644 index 5199c5635181e6..00000000000000 --- a/book/storage_stage.rs +++ /dev/null @@ -1,456 +0,0 @@ -// A stage that handles generating the keys used to encrypt the ledger and sample it -// for storage mining. Replicators submit storage proofs, validator then bundles them -// to submit its proof for mining to be rewarded. - -#[cfg(all(feature = "chacha", feature = "cuda"))] -use chacha_cuda::chacha_cbc_encrypt_file_many_keys; -use entry::EntryReceiver; -use rand::{ChaChaRng, Rng, SeedableRng}; -use result::{Error, Result}; -use service::Service; -use signature::Keypair; -use signature::Signature; -use solana_sdk::hash::Hash; -use solana_sdk::pubkey::Pubkey; -use std::mem::size_of; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc::RecvTimeoutError; -use std::sync::{Arc, RwLock}; -use std::thread::{self, Builder, JoinHandle}; -use std::time::Duration; -use vote_program::VoteProgram; - -// Block of hash answers to validate against -// Vec of [ledger blocks] x [keys] -type StorageResults = Vec; -type StorageKeys = Vec; - -#[derive(Default)] -pub struct StorageState { - storage_results: Arc>, - storage_keys: Arc>, -} - -pub struct StorageStage { - t_storage_mining_verifier: JoinHandle<()>, -} - -macro_rules! cross_boundary { - ($start:expr, $len:expr, $boundary:expr) => { - (($start + $len) & !($boundary - 1)) > $start & !($boundary - 1) - }; -} - -const NUM_HASHES_FOR_STORAGE_ROTATE: u64 = 1024; -// TODO: some way to dynamically size NUM_IDENTITIES -const NUM_IDENTITIES: usize = 1024; -const NUM_SAMPLES: usize = 4; -pub const ENTRIES_PER_SLICE: u64 = 16; -const KEY_SIZE: usize = 64; - -fn get_identity_index_from_pubkey(key: &Pubkey) -> usize { - let rkey = key.as_ref(); - let mut res: usize = (rkey[0] as usize) - | ((rkey[1] as usize) << 8) - | ((rkey[2] as usize) << 16) - | ((rkey[3] as usize) << 24); - res &= NUM_IDENTITIES - 1; - res -} - -impl StorageState { - pub fn new() -> Self { - let storage_keys = Arc::new(RwLock::new(vec![0u8; KEY_SIZE * NUM_IDENTITIES])); - let storage_results = Arc::new(RwLock::new(vec![Hash::default(); NUM_IDENTITIES])); - - StorageState { - storage_keys, - storage_results, - } - } - - pub fn get_mining_key(&self, key: &Pubkey) -> Vec { - let idx = get_identity_index_from_pubkey(key); - self.storage_keys.read().unwrap()[idx..idx + KEY_SIZE].to_vec() - } - - pub fn get_mining_result(&self, key: &Pubkey) -> Hash { - let idx = get_identity_index_from_pubkey(key); - self.storage_results.read().unwrap()[idx] - } -} - -impl StorageStage { - pub fn new( - storage_state: &StorageState, - storage_entry_receiver: EntryReceiver, - ledger_path: Option<&str>, - keypair: Arc, - exit: Arc, - entry_height: u64, - ) -> Self { - let storage_keys_ = storage_state.storage_keys.clone(); - let storage_results_ = storage_state.storage_results.clone(); - let ledger_path = ledger_path.map(String::from); - let t_storage_mining_verifier = Builder::new() - .name("solana-storage-mining-verify-stage".to_string()) - .spawn(move || { - let exit = exit.clone(); - let mut poh_height = 0; - let mut current_key = 0; - let mut entry_height = entry_height; - loop { - if let Some(ref ledger_path_str) = ledger_path { - if let Err(e) = Self::process_entries( - &keypair, - &storage_keys_, - &storage_results_, - &storage_entry_receiver, - ledger_path_str, - &mut poh_height, - &mut entry_height, - &mut current_key, - ) { - match e { - Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => break, - Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), - _ => info!("Error from process_entries: {:?}", e), - } - } - } - if exit.load(Ordering::Relaxed) { - break; - } - } - }).unwrap(); - - StorageStage { - t_storage_mining_verifier, - } - } - - pub fn process_entry_crossing( - _storage_results: &Arc>, - _storage_keys: &Arc>, - keypair: &Arc, - _ledger_path: &str, - entry_id: Hash, - entry_height: u64, - ) -> Result<()> { - let mut seed = [0u8; 32]; - let signature = keypair.sign(&entry_id.as_ref()); - - seed.copy_from_slice(&signature.as_ref()[..32]); - - let mut rng = ChaChaRng::from_seed(seed); - - // Regenerate the answers - let num_slices = (entry_height / ENTRIES_PER_SLICE) as usize; - if num_slices == 0 { - info!("Ledger has 0 slices!"); - return Ok(()); - } - // TODO: what if the validator does not have this slice - let slice = signature.as_ref()[0] as usize % num_slices; - - debug!( - "storage verifying: slice: {} identities: {}", - slice, NUM_IDENTITIES, - ); - - let mut samples = vec![]; - for _ in 0..NUM_SAMPLES { - samples.push(rng.gen_range(0, 10)); - } - debug!("generated samples: {:?}", samples); - // TODO: cuda required to generate the reference values - // but if it is missing, then we need to take care not to - // process storage mining results. - #[cfg(all(feature = "chacha", feature = "cuda"))] - { - let mut storage_results = _storage_results.write().unwrap(); - - // Lock the keys, since this is the IV memory, - // it will be updated in-place by the encryption. - // Should be overwritten by the vote signatures which replace the - // key values by the time it runs again. - let mut storage_keys = _storage_keys.write().unwrap(); - - match chacha_cbc_encrypt_file_many_keys( - _ledger_path, - slice as u64, - &mut storage_keys, - &samples, - ) { - Ok(hashes) => { - debug!("Success! encrypted ledger slice: {}", slice); - storage_results.copy_from_slice(&hashes); - } - Err(e) => { - info!("error encrypting file: {:?}", e); - Err(e)?; - } - } - } - // TODO: bundle up mining submissions from replicators - // and submit them in a tx to the leader to get reward. - Ok(()) - } - - pub fn process_entries( - keypair: &Arc, - storage_keys: &Arc>, - storage_results: &Arc>, - entry_receiver: &EntryReceiver, - ledger_path: &str, - poh_height: &mut u64, - entry_height: &mut u64, - current_key_idx: &mut usize, - ) -> Result<()> { - let timeout = Duration::new(1, 0); - let entries = entry_receiver.recv_timeout(timeout)?; - - for entry in entries { - // Go through the transactions, find votes, and use them to update - // the storage_keys with their signatures. - for tx in entry.transactions { - for program_id in tx.program_ids { - if VoteProgram::check_id(&program_id) { - debug!( - "generating storage_keys from votes current_key_idx: {}", - *current_key_idx - ); - let mut storage_keys = storage_keys.write().unwrap(); - storage_keys[*current_key_idx..*current_key_idx + size_of::()] - .copy_from_slice(tx.signatures[0].as_ref()); - *current_key_idx += size_of::(); - *current_key_idx %= storage_keys.len(); - } - } - } - if cross_boundary!(*poh_height, entry.num_hashes, NUM_HASHES_FOR_STORAGE_ROTATE) { - info!( - "crosses sending at poh_height: {} entry_height: {}! hashes: {}", - *poh_height, entry_height, entry.num_hashes - ); - Self::process_entry_crossing( - &storage_results, - &storage_keys, - &keypair, - &ledger_path, - entry.id, - *entry_height, - )?; - } - *entry_height += 1; - *poh_height += entry.num_hashes; - } - Ok(()) - } -} - -impl Service for StorageStage { - type JoinReturnType = (); - - fn join(self) -> thread::Result<()> { - self.t_storage_mining_verifier.join() - } -} - -#[cfg(test)] -mod tests { - use entry::Entry; - use ledger::make_tiny_test_entries; - use ledger::{create_tmp_sample_ledger, LedgerWriter}; - use logger; - use service::Service; - use signature::{Keypair, KeypairUtil}; - use solana_sdk::hash::Hash; - use std::cmp::{max, min}; - use std::fs::remove_dir_all; - use std::sync::atomic::{AtomicBool, Ordering}; - use std::sync::mpsc::channel; - use std::sync::Arc; - use std::thread::sleep; - use std::time::Duration; - use storage_stage::StorageState; - use storage_stage::NUM_IDENTITIES; - use storage_stage::{get_identity_index_from_pubkey, StorageStage}; - use transaction::Transaction; - use vote_program::Vote; - use vote_transaction::VoteTransaction; - - #[test] - fn test_storage_stage_none_ledger() { - let keypair = Arc::new(Keypair::new()); - let exit = Arc::new(AtomicBool::new(false)); - - let (_storage_entry_sender, storage_entry_receiver) = channel(); - let storage_state = StorageState::new(); - let storage_stage = StorageStage::new( - &storage_state, - storage_entry_receiver, - None, - keypair, - exit.clone(), - 0, - ); - exit.store(true, Ordering::Relaxed); - storage_stage.join().unwrap(); - } - - #[test] - fn test_storage_stage_process_entries() { - logger::setup(); - let keypair = Arc::new(Keypair::new()); - let exit = Arc::new(AtomicBool::new(false)); - - let (_mint, ledger_path, _genesis) = create_tmp_sample_ledger( - "storage_stage_process_entries", - 1000, - 1, - Keypair::new().pubkey(), - 1, - ); - - let entries = make_tiny_test_entries(128); - { - let mut writer = LedgerWriter::open(&ledger_path, true).unwrap(); - writer.write_entries(&entries.clone()).unwrap(); - // drops writer, flushes buffers - } - - let (storage_entry_sender, storage_entry_receiver) = channel(); - let storage_state = StorageState::new(); - let storage_stage = StorageStage::new( - &storage_state, - storage_entry_receiver, - Some(&ledger_path), - keypair, - exit.clone(), - 0, - ); - storage_entry_sender.send(entries.clone()).unwrap(); - - let keypair = Keypair::new(); - let mut result = storage_state.get_mining_result(&keypair.pubkey()); - assert_eq!(result, Hash::default()); - - for _ in 0..9 { - storage_entry_sender.send(entries.clone()).unwrap(); - } - for _ in 0..5 { - result = storage_state.get_mining_result(&keypair.pubkey()); - if result != Hash::default() { - info!("found result = {:?} sleeping..", result); - break; - } - info!("result = {:?} sleeping..", result); - sleep(Duration::new(1, 0)); - } - - info!("joining..?"); - exit.store(true, Ordering::Relaxed); - storage_stage.join().unwrap(); - - #[cfg(not(all(feature = "cuda", feature = "chacha")))] - assert_eq!(result, Hash::default()); - - #[cfg(all(feature = "cuda", feature = "chacha"))] - assert_ne!(result, Hash::default()); - - remove_dir_all(ledger_path).unwrap(); - } - - #[test] - fn test_storage_stage_process_vote_entries() { - logger::setup(); - let keypair = Arc::new(Keypair::new()); - let exit = Arc::new(AtomicBool::new(false)); - - let (_mint, ledger_path, _genesis) = create_tmp_sample_ledger( - "storage_stage_process_entries", - 1000, - 1, - Keypair::new().pubkey(), - 1, - ); - - let entries = make_tiny_test_entries(128); - { - let mut writer = LedgerWriter::open(&ledger_path, true).unwrap(); - writer.write_entries(&entries.clone()).unwrap(); - // drops writer, flushes buffers - } - - let (storage_entry_sender, storage_entry_receiver) = channel(); - let storage_state = StorageState::new(); - let storage_stage = StorageStage::new( - &storage_state, - storage_entry_receiver, - Some(&ledger_path), - keypair, - exit.clone(), - 0, - ); - storage_entry_sender.send(entries.clone()).unwrap(); - - let mut reference_keys; - { - let keys = storage_state.storage_keys.read().unwrap(); - reference_keys = vec![0; keys.len()]; - reference_keys.copy_from_slice(&keys); - } - let mut vote_txs: Vec = Vec::new(); - let vote = Vote { - tick_height: 123456, - }; - let keypair = Keypair::new(); - let vote_tx = VoteTransaction::vote_new(&keypair, vote, Hash::default(), 1); - vote_txs.push(vote_tx); - let vote_entries = vec![Entry::new(&Hash::default(), 1, vote_txs)]; - storage_entry_sender.send(vote_entries).unwrap(); - - for _ in 0..5 { - { - let keys = storage_state.storage_keys.read().unwrap(); - if keys[..] != *reference_keys.as_slice() { - break; - } - } - - sleep(Duration::new(1, 0)); - } - - debug!("joining..?"); - exit.store(true, Ordering::Relaxed); - storage_stage.join().unwrap(); - - { - let keys = storage_state.storage_keys.read().unwrap(); - assert_ne!(keys[..], *reference_keys); - } - - remove_dir_all(ledger_path).unwrap(); - } - - #[test] - fn test_pubkey_distribution() { - logger::setup(); - // See that pub keys have an even-ish distribution.. - let mut hist = [0; NUM_IDENTITIES]; - for _ in 0..(128 * 256) { - let keypair = Keypair::new(); - let ix = get_identity_index_from_pubkey(&keypair.pubkey()); - hist[ix] += 1; - } - let mut hist_max = 0; - let mut hist_min = NUM_IDENTITIES; - for x in hist.iter() { - hist_max = max(*x, hist_max); - hist_min = min(*x, hist_min); - } - info!("min: {} max: {}", hist_min, hist_max); - assert_ne!(hist_min, 0); - } -} diff --git a/book/storage_transaction.rs b/book/storage_transaction.rs deleted file mode 100644 index 70682c04f25068..00000000000000 --- a/book/storage_transaction.rs +++ /dev/null @@ -1,22 +0,0 @@ -use signature::{Keypair, KeypairUtil}; -use solana_sdk::hash::Hash; -use storage_program::StorageProgram; -use transaction::Transaction; - -pub trait StorageTransaction { - fn storage_new_mining_proof(from_keypair: &Keypair, sha_state: Hash, last_id: Hash) -> Self; -} - -impl StorageTransaction for Transaction { - fn storage_new_mining_proof(from_keypair: &Keypair, sha_state: Hash, last_id: Hash) -> Self { - let program = StorageProgram::SubmitMiningProof { sha_state }; - Transaction::new( - from_keypair, - &[from_keypair.pubkey()], - StorageProgram::id(), - &program, - last_id, - 0, - ) - } -} diff --git a/book/store_ledger_stage.rs b/book/store_ledger_stage.rs deleted file mode 100644 index 4c618e0cf2f424..00000000000000 --- a/book/store_ledger_stage.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! The `store_ledger` stores the ledger from received entries for storage nodes - -use counter::Counter; -use entry::EntryReceiver; -use ledger::LedgerWriter; -use log::Level; -use result::{Error, Result}; -use service::Service; -use std::sync::atomic::AtomicUsize; -use std::sync::mpsc::RecvTimeoutError; -use std::thread::{self, Builder, JoinHandle}; -use std::time::Duration; - -pub struct StoreLedgerStage { - thread_hdls: Vec>, -} - -impl StoreLedgerStage { - /// Process entries, already in order - fn store_requests( - window_receiver: &EntryReceiver, - ledger_writer: Option<&mut LedgerWriter>, - ) -> Result<()> { - let timer = Duration::new(1, 0); - let mut entries = window_receiver.recv_timeout(timer)?; - while let Ok(mut more) = window_receiver.try_recv() { - entries.append(&mut more); - } - - inc_new_counter_info!( - "store-transactions", - entries.iter().map(|x| x.transactions.len()).sum() - ); - - if let Some(ledger_writer) = ledger_writer { - ledger_writer.write_entries(&entries)?; - } - - Ok(()) - } - - pub fn new(window_receiver: EntryReceiver, ledger_path: Option<&str>) -> Self { - let mut ledger_writer = ledger_path.map(|p| LedgerWriter::open(p, true).unwrap()); - - let t_store_requests = Builder::new() - .name("solana-store-ledger-stage".to_string()) - .spawn(move || loop { - if let Err(e) = Self::store_requests(&window_receiver, ledger_writer.as_mut()) { - match e { - Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => break, - Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), - _ => error!("{:?}", e), - } - } - }).unwrap(); - - let thread_hdls = vec![t_store_requests]; - - StoreLedgerStage { thread_hdls } - } -} - -impl Service for StoreLedgerStage { - type JoinReturnType = (); - - fn join(self) -> thread::Result<()> { - for thread_hdl in self.thread_hdls { - thread_hdl.join()?; - } - Ok(()) - } -} diff --git a/book/streamer.rs b/book/streamer.rs deleted file mode 100644 index 74b0e1d8ad385a..00000000000000 --- a/book/streamer.rs +++ /dev/null @@ -1,200 +0,0 @@ -//! The `streamer` module defines a set of services for efficiently pulling data from UDP sockets. -//! - -use packet::{Blob, SharedBlobs, SharedPackets}; -use result::{Error, Result}; -use solana_metrics::{influxdb, submit}; -use solana_sdk::timing::duration_as_ms; -use std::net::UdpSocket; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc::{Receiver, RecvTimeoutError, Sender}; -use std::sync::Arc; -use std::thread::{Builder, JoinHandle}; -use std::time::{Duration, Instant}; - -pub type PacketReceiver = Receiver; -pub type PacketSender = Sender; -pub type BlobSender = Sender; -pub type BlobReceiver = Receiver; - -fn recv_loop( - sock: &UdpSocket, - exit: &Arc, - channel: &PacketSender, - channel_tag: &'static str, -) -> Result<()> { - loop { - let msgs = SharedPackets::default(); - loop { - // Check for exit signal, even if socket is busy - // (for instance the leader trasaction socket) - if exit.load(Ordering::Relaxed) { - return Ok(()); - } - if msgs.write().unwrap().recv_from(sock).is_ok() { - let len = msgs.read().unwrap().packets.len(); - submit( - influxdb::Point::new(channel_tag) - .add_field("count", influxdb::Value::Integer(len as i64)) - .to_owned(), - ); - channel.send(msgs)?; - break; - } - } - } -} - -pub fn receiver( - sock: Arc, - exit: Arc, - packet_sender: PacketSender, - sender_tag: &'static str, -) -> JoinHandle<()> { - let res = sock.set_read_timeout(Some(Duration::new(1, 0))); - if res.is_err() { - panic!("streamer::receiver set_read_timeout error"); - } - Builder::new() - .name("solana-receiver".to_string()) - .spawn(move || { - let _ = recv_loop(&sock, &exit, &packet_sender, sender_tag); - () - }).unwrap() -} - -fn recv_send(sock: &UdpSocket, r: &BlobReceiver) -> Result<()> { - let timer = Duration::new(1, 0); - let msgs = r.recv_timeout(timer)?; - Blob::send_to(sock, msgs)?; - Ok(()) -} - -pub fn recv_batch(recvr: &PacketReceiver) -> Result<(Vec, usize, u64)> { - let timer = Duration::new(1, 0); - let msgs = recvr.recv_timeout(timer)?; - let recv_start = Instant::now(); - trace!("got msgs"); - let mut len = msgs.read().unwrap().packets.len(); - let mut batch = vec![msgs]; - while let Ok(more) = recvr.try_recv() { - trace!("got more msgs"); - len += more.read().unwrap().packets.len(); - batch.push(more); - - if len > 100_000 { - break; - } - } - trace!("batch len {}", batch.len()); - Ok((batch, len, duration_as_ms(&recv_start.elapsed()))) -} - -pub fn responder(name: &'static str, sock: Arc, r: BlobReceiver) -> JoinHandle<()> { - Builder::new() - .name(format!("solana-responder-{}", name)) - .spawn(move || loop { - if let Err(e) = recv_send(&sock, &r) { - match e { - Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => break, - Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), - _ => warn!("{} responder error: {:?}", name, e), - } - } - }).unwrap() -} - -//TODO, we would need to stick block authentication before we create the -//window. -fn recv_blobs(sock: &UdpSocket, s: &BlobSender) -> Result<()> { - trace!("recv_blobs: receiving on {}", sock.local_addr().unwrap()); - let dq = Blob::recv_from(sock)?; - if !dq.is_empty() { - s.send(dq)?; - } - Ok(()) -} - -pub fn blob_receiver(sock: Arc, exit: Arc, s: BlobSender) -> JoinHandle<()> { - //DOCUMENTED SIDE-EFFECT - //1 second timeout on socket read - let timer = Duration::new(1, 0); - sock.set_read_timeout(Some(timer)) - .expect("set socket timeout"); - Builder::new() - .name("solana-blob_receiver".to_string()) - .spawn(move || loop { - if exit.load(Ordering::Relaxed) { - break; - } - let _ = recv_blobs(&sock, &s); - }).unwrap() -} - -#[cfg(test)] -mod test { - use packet::{Blob, Packet, Packets, SharedBlob, PACKET_DATA_SIZE}; - use std::io; - use std::io::Write; - use std::net::UdpSocket; - use std::sync::atomic::{AtomicBool, Ordering}; - use std::sync::mpsc::channel; - use std::sync::Arc; - use std::time::Duration; - use streamer::PacketReceiver; - use streamer::{receiver, responder}; - - fn get_msgs(r: PacketReceiver, num: &mut usize) { - for _t in 0..5 { - let timer = Duration::new(1, 0); - match r.recv_timeout(timer) { - Ok(m) => *num += m.read().unwrap().packets.len(), - _ => info!("get_msgs error"), - } - if *num == 10 { - break; - } - } - } - #[test] - pub fn streamer_debug() { - write!(io::sink(), "{:?}", Packet::default()).unwrap(); - write!(io::sink(), "{:?}", Packets::default()).unwrap(); - write!(io::sink(), "{:?}", Blob::default()).unwrap(); - } - #[test] - pub fn streamer_send_test() { - let read = UdpSocket::bind("127.0.0.1:0").expect("bind"); - read.set_read_timeout(Some(Duration::new(1, 0))).unwrap(); - - let addr = read.local_addr().unwrap(); - let send = UdpSocket::bind("127.0.0.1:0").expect("bind"); - let exit = Arc::new(AtomicBool::new(false)); - let (s_reader, r_reader) = channel(); - let t_receiver = receiver(Arc::new(read), exit.clone(), s_reader, "streamer-test"); - let t_responder = { - let (s_responder, r_responder) = channel(); - let t_responder = responder("streamer_send_test", Arc::new(send), r_responder); - let mut msgs = Vec::new(); - for i in 0..10 { - let mut b = SharedBlob::default(); - { - let mut w = b.write().unwrap(); - w.data[0] = i as u8; - w.meta.size = PACKET_DATA_SIZE; - w.meta.set_addr(&addr); - } - msgs.push(b); - } - s_responder.send(msgs).expect("send"); - t_responder - }; - - let mut num = 0; - get_msgs(r_reader, &mut num); - assert_eq!(num, 10); - exit.store(true, Ordering::Relaxed); - t_receiver.join().expect("join"); - t_responder.join().expect("join"); - } -} diff --git a/book/synchronization.html b/book/synchronization.html deleted file mode 100644 index 24f4918763437f..00000000000000 --- a/book/synchronization.html +++ /dev/null @@ -1,235 +0,0 @@ - - - - - - Synchronization - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

Synchronization

-

It's possible for a centralized database to process 710,000 transactions per -second on a standard gigabit network if the transactions are, on average, no -more than 176 bytes. A centralized database can also replicate itself and -maintain high availability without significantly compromising that transaction -rate using the distributed system technique known as Optimistic Concurrency -Control [H.T.Kung, J.T.Robinson -(1981)]. At -Solana, we're demonstrating that these same theoretical limits apply just as -well to blockchain on an adversarial network. The key ingredient? Finding a way -to share time when nodes can't trust one-another. Once nodes can trust time, -suddenly ~40 years of distributed systems research becomes applicable to -blockchain!

-
-

Perhaps the most striking difference between algorithms obtained by our -method and ones based upon timeout is that using timeout produces a -traditional distributed algorithm in which the processes operate -asynchronously, while our method produces a globally synchronous one in which -every process does the same thing at (approximately) the same time. Our -method seems to contradict the whole purpose of distributed processing, which -is to permit different processes to operate independently and perform -different functions. However, if a distributed system is really a single -system, then the processes must be synchronized in some way. Conceptually, -the easiest way to synchronize processes is to get them all to do the same -thing at the same time. Therefore, our method is used to implement a kernel -that performs the necessary synchronization--for example, making sure that -two different processes do not try to modify a file at the same time. -Processes might spend only a small fraction of their time executing the -synchronizing kernel; the rest of the time, they can operate -independently--e.g., accessing different files. This is an approach we have -advocated even when fault-tolerance is not required. The method's basic -simplicity makes it easier to understand the precise properties of a system, -which is crucial if one is to know just how fault-tolerant the system is. -[L.Lamport -(1984)]

-
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/system_program.rs b/book/system_program.rs deleted file mode 100644 index ce9698a1f7af6f..00000000000000 --- a/book/system_program.rs +++ /dev/null @@ -1,306 +0,0 @@ -//! system program - -use bincode::deserialize; -use solana_sdk::account::Account; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::system_instruction::SystemInstruction; -use std; -use transaction::Transaction; - -#[derive(Debug)] -pub enum Error { - InvalidArgument, - AssignOfUnownedAccount, - AccountNotFinalized, - ResultWithNegativeTokens(u8), -} -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "error") - } -} -impl std::error::Error for Error {} - -pub type Result = std::result::Result; - -pub struct SystemProgram {} - -pub const SYSTEM_PROGRAM_ID: [u8; 32] = [0u8; 32]; - -impl SystemProgram { - pub fn check_id(program_id: &Pubkey) -> bool { - program_id.as_ref() == SYSTEM_PROGRAM_ID - } - - pub fn id() -> Pubkey { - Pubkey::new(&SYSTEM_PROGRAM_ID) - } - pub fn get_balance(account: &Account) -> u64 { - account.tokens - } - pub fn process_transaction( - tx: &Transaction, - pix: usize, - accounts: &mut [&mut Account], - ) -> Result<()> { - if let Ok(syscall) = deserialize(tx.userdata(pix)) { - trace!("process_transaction: {:?}", syscall); - match syscall { - SystemInstruction::CreateAccount { - tokens, - space, - program_id, - } => { - if !Self::check_id(&accounts[0].owner) { - info!("Invalid account[0] owner"); - Err(Error::InvalidArgument)?; - } - - if space > 0 - && (!accounts[1].userdata.is_empty() || !Self::check_id(&accounts[1].owner)) - { - info!("Invalid account[1]"); - Err(Error::InvalidArgument)?; - } - if tokens > accounts[0].tokens { - info!("Insufficient tokens in account[0]"); - Err(Error::ResultWithNegativeTokens(pix as u8))?; - } - accounts[0].tokens -= tokens; - accounts[1].tokens += tokens; - accounts[1].owner = program_id; - accounts[1].userdata = vec![0; space as usize]; - accounts[1].executable = false; - accounts[1].loader = Pubkey::default(); - } - SystemInstruction::Assign { program_id } => { - if !Self::check_id(&accounts[0].owner) { - Err(Error::AssignOfUnownedAccount)?; - } - accounts[0].owner = program_id; - } - SystemInstruction::Move { tokens } => { - //bank should be verifying correctness - if tokens > accounts[0].tokens { - info!("Insufficient tokens in account[0]"); - Err(Error::ResultWithNegativeTokens(pix as u8))?; - } - accounts[0].tokens -= tokens; - accounts[1].tokens += tokens; - } - SystemInstruction::Spawn => { - if !accounts[0].executable || accounts[0].loader != Pubkey::default() { - Err(Error::AccountNotFinalized)?; - } - accounts[0].loader = accounts[0].owner; - accounts[0].owner = tx.account_keys[0]; - } - } - Ok(()) - } else { - info!("Invalid transaction userdata: {:?}", tx.userdata(pix)); - Err(Error::InvalidArgument) - } - } -} -#[cfg(test)] -mod test { - use super::*; - use signature::{Keypair, KeypairUtil}; - use solana_sdk::account::Account; - use solana_sdk::hash::Hash; - use solana_sdk::pubkey::Pubkey; - use system_program::SystemProgram; - use system_transaction::SystemTransaction; - use transaction::Transaction; - - fn process_transaction(tx: &Transaction, accounts: &mut [Account]) -> Result<()> { - let mut refs: Vec<&mut Account> = accounts.iter_mut().collect(); - SystemProgram::process_transaction(&tx, 0, &mut refs[..]) - } - - #[test] - fn test_create_noop() { - let from = Keypair::new(); - let to = Keypair::new(); - let mut accounts = vec![Account::default(), Account::default()]; - let tx = Transaction::system_new(&from, to.pubkey(), 0, Hash::default()); - process_transaction(&tx, &mut accounts).unwrap(); - assert_eq!(accounts[0].tokens, 0); - assert_eq!(accounts[1].tokens, 0); - } - #[test] - fn test_create_spend() { - let from = Keypair::new(); - let to = Keypair::new(); - let mut accounts = vec![Account::default(), Account::default()]; - accounts[0].tokens = 1; - let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default()); - process_transaction(&tx, &mut accounts).unwrap(); - assert_eq!(accounts[0].tokens, 0); - assert_eq!(accounts[1].tokens, 1); - } - - #[test] - fn test_create_spend_wrong_source() { - let from = Keypair::new(); - let to = Keypair::new(); - let mut accounts = vec![Account::default(), Account::default()]; - accounts[0].tokens = 1; - accounts[0].owner = from.pubkey(); - let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default()); - if let Ok(()) = process_transaction(&tx, &mut accounts) { - panic!("Account not owned by SystemProgram"); - } - assert_eq!(accounts[0].tokens, 1); - assert_eq!(accounts[1].tokens, 0); - } - #[test] - fn test_create_assign_and_allocate() { - let from = Keypair::new(); - let to = Keypair::new(); - let mut accounts = vec![Account::default(), Account::default()]; - let tx = - Transaction::system_create(&from, to.pubkey(), Hash::default(), 0, 1, to.pubkey(), 0); - process_transaction(&tx, &mut accounts).unwrap(); - assert!(accounts[0].userdata.is_empty()); - assert_eq!(accounts[1].userdata.len(), 1); - assert_eq!(accounts[1].owner, to.pubkey()); - } - #[test] - fn test_create_allocate_wrong_dest_program() { - let from = Keypair::new(); - let to = Keypair::new(); - let mut accounts = vec![Account::default(), Account::default()]; - accounts[1].owner = to.pubkey(); - let tx = Transaction::system_create( - &from, - to.pubkey(), - Hash::default(), - 0, - 1, - Pubkey::default(), - 0, - ); - assert!(process_transaction(&tx, &mut accounts).is_err()); - assert!(accounts[1].userdata.is_empty()); - } - #[test] - fn test_create_allocate_wrong_source_program() { - let from = Keypair::new(); - let to = Keypair::new(); - let mut accounts = vec![Account::default(), Account::default()]; - accounts[0].owner = to.pubkey(); - let tx = Transaction::system_create( - &from, - to.pubkey(), - Hash::default(), - 0, - 1, - Pubkey::default(), - 0, - ); - assert!(process_transaction(&tx, &mut accounts).is_err()); - assert!(accounts[1].userdata.is_empty()); - } - #[test] - fn test_create_allocate_already_allocated() { - let from = Keypair::new(); - let to = Keypair::new(); - let mut accounts = vec![Account::default(), Account::default()]; - accounts[1].userdata = vec![0, 0, 0]; - let tx = Transaction::system_create( - &from, - to.pubkey(), - Hash::default(), - 0, - 2, - Pubkey::default(), - 0, - ); - assert!(process_transaction(&tx, &mut accounts).is_err()); - assert_eq!(accounts[1].userdata.len(), 3); - } - #[test] - fn test_create_assign() { - let from = Keypair::new(); - let program = Keypair::new(); - let mut accounts = vec![Account::default()]; - let tx = Transaction::system_assign(&from, Hash::default(), program.pubkey(), 0); - process_transaction(&tx, &mut accounts).unwrap(); - assert_eq!(accounts[0].owner, program.pubkey()); - } - #[test] - fn test_move() { - let from = Keypair::new(); - let to = Keypair::new(); - let mut accounts = vec![Account::default(), Account::default()]; - accounts[0].tokens = 1; - let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default()); - process_transaction(&tx, &mut accounts).unwrap(); - assert_eq!(accounts[0].tokens, 0); - assert_eq!(accounts[1].tokens, 1); - } - /// Detect binary changes in the serialized program userdata, which could have a downstream - /// affect on SDKs and DApps - #[test] - fn test_sdk_serialize() { - let keypair = Keypair::new(); - use budget_program::BudgetState; - - // CreateAccount - let tx = Transaction::system_create( - &keypair, - keypair.pubkey(), - Hash::default(), - 111, - 222, - BudgetState::id(), - 0, - ); - - assert_eq!( - tx.userdata(0).to_vec(), - vec![ - 0, 0, 0, 0, 111, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 0, 0, 0, 0, 129, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - - // CreateAccount - let tx = Transaction::system_create( - &keypair, - keypair.pubkey(), - Hash::default(), - 111, - 222, - Pubkey::default(), - 0, - ); - - assert_eq!( - tx.userdata(0).to_vec(), - vec![ - 0, 0, 0, 0, 111, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - - // Assign - let tx = Transaction::system_assign(&keypair, Hash::default(), BudgetState::id(), 0); - assert_eq!( - tx.userdata(0).to_vec(), - vec![ - 1, 0, 0, 0, 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - - // Move - let tx = Transaction::system_move(&keypair, keypair.pubkey(), 123, Hash::default(), 0); - assert_eq!( - tx.userdata(0).to_vec(), - vec![2, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0] - ); - } -} diff --git a/book/system_transaction.rs b/book/system_transaction.rs deleted file mode 100644 index fcd0cc29e76e6d..00000000000000 --- a/book/system_transaction.rs +++ /dev/null @@ -1,221 +0,0 @@ -//! The `system_transaction` module provides functionality for creating system transactions. - -use signature::{Keypair, KeypairUtil}; -use solana_sdk::hash::Hash; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::system_instruction::SystemInstruction; -use system_program::SystemProgram; - -use transaction::{Instruction, Transaction}; - -pub trait SystemTransaction { - fn system_create( - from_keypair: &Keypair, - to: Pubkey, - last_id: Hash, - tokens: u64, - space: u64, - program_id: Pubkey, - fee: u64, - ) -> Self; - - fn system_assign(from_keypair: &Keypair, last_id: Hash, program_id: Pubkey, fee: u64) -> Self; - - fn system_new(from_keypair: &Keypair, to: Pubkey, tokens: u64, last_id: Hash) -> Self; - - fn system_move( - from_keypair: &Keypair, - to: Pubkey, - tokens: u64, - last_id: Hash, - fee: u64, - ) -> Self; - - fn system_move_many( - from_keypair: &Keypair, - moves: &[(Pubkey, u64)], - last_id: Hash, - fee: u64, - ) -> Self; - - fn system_spawn(from_keypair: &Keypair, last_id: Hash, fee: u64) -> Self; -} - -impl SystemTransaction for Transaction { - /// Create and sign new SystemInstruction::CreateAccount transaction - fn system_create( - from_keypair: &Keypair, - to: Pubkey, - last_id: Hash, - tokens: u64, - space: u64, - program_id: Pubkey, - fee: u64, - ) -> Self { - let create = SystemInstruction::CreateAccount { - tokens, //TODO, the tokens to allocate might need to be higher then 0 in the future - space, - program_id, - }; - Transaction::new( - from_keypair, - &[to], - SystemProgram::id(), - &create, - last_id, - fee, - ) - } - /// Create and sign new SystemInstruction::Assign transaction - fn system_assign(from_keypair: &Keypair, last_id: Hash, program_id: Pubkey, fee: u64) -> Self { - let assign = SystemInstruction::Assign { program_id }; - Transaction::new( - from_keypair, - &[], - SystemProgram::id(), - &assign, - last_id, - fee, - ) - } - /// Create and sign new SystemInstruction::CreateAccount transaction with some defaults - fn system_new(from_keypair: &Keypair, to: Pubkey, tokens: u64, last_id: Hash) -> Self { - Transaction::system_create(from_keypair, to, last_id, tokens, 0, Pubkey::default(), 0) - } - /// Create and sign new SystemInstruction::Move transaction - fn system_move( - from_keypair: &Keypair, - to: Pubkey, - tokens: u64, - last_id: Hash, - fee: u64, - ) -> Self { - let move_tokens = SystemInstruction::Move { tokens }; - Transaction::new( - from_keypair, - &[to], - SystemProgram::id(), - &move_tokens, - last_id, - fee, - ) - } - /// Create and sign new SystemInstruction::Move transaction to many destinations - fn system_move_many(from: &Keypair, moves: &[(Pubkey, u64)], last_id: Hash, fee: u64) -> Self { - let instructions: Vec<_> = moves - .iter() - .enumerate() - .map(|(i, (_, amount))| { - let spend = SystemInstruction::Move { tokens: *amount }; - Instruction::new(0, &spend, vec![0, i as u8 + 1]) - }).collect(); - let to_keys: Vec<_> = moves.iter().map(|(to_key, _)| *to_key).collect(); - - Transaction::new_with_instructions( - &[from], - &to_keys, - last_id, - fee, - vec![SystemProgram::id()], - instructions, - ) - } - /// Create and sign new SystemInstruction::Spawn transaction - fn system_spawn(from_keypair: &Keypair, last_id: Hash, fee: u64) -> Self { - let spawn = SystemInstruction::Spawn; - Transaction::new(from_keypair, &[], SystemProgram::id(), &spawn, last_id, fee) - } -} - -pub fn test_tx() -> Transaction { - let keypair1 = Keypair::new(); - let pubkey1 = keypair1.pubkey(); - let zero = Hash::default(); - Transaction::system_new(&keypair1, pubkey1, 42, zero) -} - -#[cfg(test)] -pub fn memfind(a: &[A], b: &[A]) -> Option { - assert!(a.len() >= b.len()); - let end = a.len() - b.len() + 1; - for i in 0..end { - if a[i..i + b.len()] == b[..] { - return Some(i); - } - } - None -} - -#[cfg(test)] -mod tests { - use super::*; - use bincode::{deserialize, serialize}; - use packet::PACKET_DATA_SIZE; - use sigverify; - use transaction::SIG_OFFSET; - - #[test] - fn test_layout() { - let tx = test_tx(); - let tx_bytes = serialize(&tx).unwrap(); - let sign_data = tx.get_sign_data(); - let packet = sigverify::make_packet_from_transaction(tx.clone()); - - let (sig_len, sig_start, msg_start_offset, pubkey_offset) = - sigverify::get_packet_offsets(&packet, 0); - - assert_eq!( - memfind(&tx_bytes, &tx.signatures[0].as_ref()), - Some(SIG_OFFSET) - ); - assert_eq!( - memfind(&tx_bytes, &tx.account_keys[0].as_ref()), - Some(pubkey_offset as usize) - ); - assert_eq!( - memfind(&tx_bytes, &sign_data), - Some(msg_start_offset as usize) - ); - assert_eq!( - memfind(&tx_bytes, &tx.signatures[0].as_ref()), - Some(sig_start as usize) - ); - assert_eq!(sig_len, 1); - assert!(tx.verify_signature()); - } - - #[test] - fn test_userdata_layout() { - let mut tx0 = test_tx(); - tx0.instructions[0].userdata = vec![1, 2, 3]; - let sign_data0a = tx0.get_sign_data(); - let tx_bytes = serialize(&tx0).unwrap(); - assert!(tx_bytes.len() < PACKET_DATA_SIZE); - assert_eq!( - memfind(&tx_bytes, &tx0.signatures[0].as_ref()), - Some(SIG_OFFSET) - ); - let tx1 = deserialize(&tx_bytes).unwrap(); - assert_eq!(tx0, tx1); - assert_eq!(tx1.instructions[0].userdata, vec![1, 2, 3]); - - tx0.instructions[0].userdata = vec![1, 2, 4]; - let sign_data0b = tx0.get_sign_data(); - assert_ne!(sign_data0a, sign_data0b); - } - #[test] - fn test_move_many() { - let from = Keypair::new(); - let t1 = Keypair::new(); - let t2 = Keypair::new(); - let moves = vec![(t1.pubkey(), 1), (t2.pubkey(), 2)]; - - let tx = Transaction::system_move_many(&from, &moves, Default::default(), 0); - assert_eq!(tx.account_keys[0], from.pubkey()); - assert_eq!(tx.account_keys[1], t1.pubkey()); - assert_eq!(tx.account_keys[2], t2.pubkey()); - assert_eq!(tx.instructions.len(), 2); - assert_eq!(tx.instructions[0].accounts, vec![0, 1]); - assert_eq!(tx.instructions[1].accounts, vec![0, 2]); - } -} diff --git a/book/terminology.html b/book/terminology.html deleted file mode 100644 index 89ead15c8f9cf5..00000000000000 --- a/book/terminology.html +++ /dev/null @@ -1,230 +0,0 @@ - - - - - - Terminology - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

Terminology

-

Teminology Currently in Use

-

The following list contains words commonly used throughout the Solana architecture.

-
    -
  • account - a persistent file addressed by pubkey and with tokens tracking its lifetime
  • -
  • cluster - a set of fullnodes maintaining a single ledger
  • -
  • finality - the wallclock duration between a leader creating a tick entry and recoginizing -a supermajority of validator votes with a ledger interpretation that matches the leader's
  • -
  • fullnode - a full participant in the cluster - either a leader or validator node
  • -
  • entry - an entry on the ledger - either a tick or a transactions entry
  • -
  • instruction - the smallest unit of a program that a client can include in a transaction
  • -
  • keypair - a public and secret key
  • -
  • node count - the number of fullnodes participating in a cluster
  • -
  • program - the code that interprets instructions
  • -
  • pubkey - the public key of a keypair
  • -
  • tick - a ledger entry that estimates wallclock duration
  • -
  • tick height - the Nth tick in the ledger
  • -
  • tps - transactions per second
  • -
  • transaction - one or more instructions signed by the client and executed atomically
  • -
  • transactions entry - a set of transactions that may be executed in parallel
  • -
-

Terminology Reserved for Future Use

-

The following keywords do not have any functionality but are reserved by Solana -for potential future use.

-
    -
  • epoch - the time in which a leader schedule is valid
  • -
  • mips - millions of instructions per second
  • -
  • public key - We currently use pubkey
  • -
  • slot - the time in which a single leader may produce entries
  • -
  • secret key - Users currently only use keypair
  • -
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/thin_client.rs b/book/thin_client.rs deleted file mode 100644 index c49ab1c8273afc..00000000000000 --- a/book/thin_client.rs +++ /dev/null @@ -1,739 +0,0 @@ -//! The `thin_client` module is a client-side object that interfaces with -//! a server-side TPU. Client code should use this object instead of writing -//! messages to the network directly. The binary encoding of its messages are -//! unstable and may change in future releases. - -use bank::Bank; -use bincode::serialize; -use bs58; -use cluster_info::{ClusterInfo, ClusterInfoError, NodeInfo}; -use log::Level; -use ncp::Ncp; -use packet::PACKET_DATA_SIZE; -use result::{Error, Result}; -use rpc_request::RpcRequest; -use serde_json; -use signature::{Keypair, Signature}; -use solana_metrics; -use solana_metrics::influxdb; -use solana_sdk::account::Account; -use solana_sdk::hash::Hash; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::timing; -use std; -use std::collections::HashMap; -use std::io; -use std::net::{SocketAddr, UdpSocket}; -use std::sync::atomic::AtomicBool; -use std::sync::{Arc, RwLock}; -use std::thread::sleep; -use std::time::Duration; -use std::time::Instant; -use system_transaction::SystemTransaction; -use transaction::Transaction; - -/// An object for querying and sending transactions to the network. -pub struct ThinClient { - rpc_addr: SocketAddr, - transactions_addr: SocketAddr, - transactions_socket: UdpSocket, - last_id: Option, - transaction_count: u64, - balances: HashMap, - signature_status: bool, - finality: Option, -} - -impl ThinClient { - /// Create a new ThinClient that will interface with the Rpc at `rpc_addr` using TCP - /// and the Tpu at `transactions_addr` over `transactions_socket` using UDP. - pub fn new( - rpc_addr: SocketAddr, - transactions_addr: SocketAddr, - transactions_socket: UdpSocket, - ) -> Self { - ThinClient { - rpc_addr, - transactions_addr, - transactions_socket, - last_id: None, - transaction_count: 0, - balances: HashMap::new(), - signature_status: false, - finality: None, - } - } - - /// Send a signed Transaction to the server for processing. This method - /// does not wait for a response. - pub fn transfer_signed(&self, tx: &Transaction) -> io::Result { - let data = serialize(&tx).expect("serialize Transaction in pub fn transfer_signed"); - assert!(data.len() < PACKET_DATA_SIZE); - self.transactions_socket - .send_to(&data, &self.transactions_addr)?; - Ok(tx.signatures[0]) - } - - /// Retry a sending a signed Transaction to the server for processing. - pub fn retry_transfer( - &mut self, - keypair: &Keypair, - tx: &mut Transaction, - tries: usize, - ) -> io::Result { - for x in 0..tries { - tx.sign(&[&keypair], self.get_last_id()); - let data = serialize(&tx).expect("serialize Transaction in pub fn transfer_signed"); - self.transactions_socket - .send_to(&data, &self.transactions_addr)?; - if self.poll_for_signature(&tx.signatures[0]).is_ok() { - return Ok(tx.signatures[0]); - } - info!("{} tries failed transfer to {}", x, self.transactions_addr); - } - Err(io::Error::new( - io::ErrorKind::Other, - "retry_transfer failed", - )) - } - - /// Creates, signs, and processes a Transaction. Useful for writing unit-tests. - pub fn transfer( - &self, - n: u64, - keypair: &Keypair, - to: Pubkey, - last_id: &Hash, - ) -> io::Result { - let now = Instant::now(); - let tx = Transaction::system_new(keypair, to, n, *last_id); - let result = self.transfer_signed(&tx); - solana_metrics::submit( - influxdb::Point::new("thinclient") - .add_tag("op", influxdb::Value::String("transfer".to_string())) - .add_field( - "duration_ms", - influxdb::Value::Integer(timing::duration_as_ms(&now.elapsed()) as i64), - ).to_owned(), - ); - result - } - - pub fn get_account_userdata(&mut self, pubkey: &Pubkey) -> io::Result>> { - let params = json!([format!("{}", pubkey)]); - let rpc_string = format!("http://{}", self.rpc_addr.to_string()); - let resp = RpcRequest::GetAccountInfo.make_rpc_request(&rpc_string, 1, Some(params)); - if let Ok(account_json) = resp { - let account: Account = - serde_json::from_value(account_json).expect("deserialize account"); - return Ok(Some(account.userdata)); - } - Err(io::Error::new( - io::ErrorKind::Other, - "get_account_userdata failed", - )) - } - - /// Request the balance of the user holding `pubkey`. This method blocks - /// until the server sends a response. If the response packet is dropped - /// by the network, this method will hang indefinitely. - pub fn get_balance(&mut self, pubkey: &Pubkey) -> io::Result { - trace!("get_balance sending request to {}", self.rpc_addr); - let params = json!([format!("{}", pubkey)]); - let rpc_string = format!("http://{}", self.rpc_addr.to_string()); - let resp = RpcRequest::GetAccountInfo.make_rpc_request(&rpc_string, 1, Some(params)); - if let Ok(account_json) = resp { - let account: Account = - serde_json::from_value(account_json).expect("deserialize account"); - trace!("Response account {:?} {:?}", pubkey, account); - self.balances.insert(*pubkey, account.clone()); - } else { - debug!("Response account {}: None ", pubkey); - self.balances.remove(&pubkey); - } - trace!("get_balance {:?}", self.balances.get(pubkey)); - // TODO: This is a hard coded call to introspect the balance of a budget_dsl contract - // In the future custom contracts would need their own introspection - self.balances - .get(pubkey) - .map(Bank::read_balance) - .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "AccountNotFound")) - } - - /// Request the finality from the leader node - pub fn get_finality(&mut self) -> usize { - trace!("get_finality"); - let mut done = false; - let rpc_string = format!("http://{}", self.rpc_addr.to_string()); - while !done { - debug!("get_finality send_to {}", &self.rpc_addr); - let resp = RpcRequest::GetFinality.make_rpc_request(&rpc_string, 1, None); - - if let Ok(value) = resp { - done = true; - let finality = value.as_u64().unwrap() as usize; - self.finality = Some(finality); - } else { - debug!("thin_client get_finality error: {:?}", resp); - } - } - self.finality.expect("some finality") - } - - /// Request the transaction count. If the response packet is dropped by the network, - /// this method will try again 5 times. - pub fn transaction_count(&mut self) -> u64 { - debug!("transaction_count"); - let mut tries_left = 5; - let rpc_string = format!("http://{}", self.rpc_addr.to_string()); - while tries_left > 0 { - let resp = RpcRequest::GetTransactionCount.make_rpc_request(&rpc_string, 1, None); - - if let Ok(value) = resp { - debug!("transaction_count recv_response: {:?}", value); - tries_left = 0; - let transaction_count = value.as_u64().unwrap(); - self.transaction_count = transaction_count; - } else { - tries_left -= 1; - } - } - self.transaction_count - } - - /// Request the last Entry ID from the server. This method blocks - /// until the server sends a response. - pub fn get_last_id(&mut self) -> Hash { - trace!("get_last_id"); - let mut done = false; - let rpc_string = format!("http://{}", self.rpc_addr.to_string()); - while !done { - debug!("get_last_id send_to {}", &self.rpc_addr); - let resp = RpcRequest::GetLastId.make_rpc_request(&rpc_string, 1, None); - - if let Ok(value) = resp { - done = true; - let last_id_str = value.as_str().unwrap(); - let last_id_vec = bs58::decode(last_id_str).into_vec().unwrap(); - let last_id = Hash::new(&last_id_vec); - self.last_id = Some(last_id); - } else { - debug!("thin_client get_last_id error: {:?}", resp); - } - } - self.last_id.expect("some last_id") - } - - pub fn submit_poll_balance_metrics(elapsed: &Duration) { - solana_metrics::submit( - influxdb::Point::new("thinclient") - .add_tag("op", influxdb::Value::String("get_balance".to_string())) - .add_field( - "duration_ms", - influxdb::Value::Integer(timing::duration_as_ms(elapsed) as i64), - ).to_owned(), - ); - } - - pub fn poll_balance_with_timeout( - &mut self, - pubkey: &Pubkey, - polling_frequency: &Duration, - timeout: &Duration, - ) -> io::Result { - let now = Instant::now(); - loop { - match self.get_balance(&pubkey) { - Ok(bal) => { - ThinClient::submit_poll_balance_metrics(&now.elapsed()); - return Ok(bal); - } - Err(e) => { - sleep(*polling_frequency); - if now.elapsed() > *timeout { - ThinClient::submit_poll_balance_metrics(&now.elapsed()); - return Err(e); - } - } - }; - } - } - - pub fn poll_get_balance(&mut self, pubkey: &Pubkey) -> io::Result { - self.poll_balance_with_timeout(pubkey, &Duration::from_millis(100), &Duration::from_secs(1)) - } - - /// Poll the server to confirm a transaction. - pub fn poll_for_signature(&mut self, signature: &Signature) -> io::Result<()> { - let now = Instant::now(); - while !self.check_signature(signature) { - if now.elapsed().as_secs() > 1 { - // TODO: Return a better error. - return Err(io::Error::new(io::ErrorKind::Other, "signature not found")); - } - sleep(Duration::from_millis(100)); - } - Ok(()) - } - - /// Check a signature in the bank. This method blocks - /// until the server sends a response. - pub fn check_signature(&mut self, signature: &Signature) -> bool { - trace!("check_signature"); - let params = json!([format!("{}", signature)]); - let now = Instant::now(); - let rpc_string = format!("http://{}", self.rpc_addr.to_string()); - let mut done = false; - while !done { - let resp = RpcRequest::ConfirmTransaction.make_rpc_request( - &rpc_string, - 1, - Some(params.clone()), - ); - - if let Ok(confirmation) = resp { - done = true; - self.signature_status = confirmation.as_bool().unwrap(); - if self.signature_status { - trace!("Response found signature"); - } else { - trace!("Response signature not found"); - } - } - } - solana_metrics::submit( - influxdb::Point::new("thinclient") - .add_tag("op", influxdb::Value::String("check_signature".to_string())) - .add_field( - "duration_ms", - influxdb::Value::Integer(timing::duration_as_ms(&now.elapsed()) as i64), - ).to_owned(), - ); - self.signature_status - } -} - -impl Drop for ThinClient { - fn drop(&mut self) { - solana_metrics::flush(); - } -} - -pub fn poll_gossip_for_leader(leader_ncp: SocketAddr, timeout: Option) -> Result { - let exit = Arc::new(AtomicBool::new(false)); - let (node, gossip_socket) = ClusterInfo::spy_node(); - let my_addr = gossip_socket.local_addr().unwrap(); - let cluster_info = Arc::new(RwLock::new(ClusterInfo::new(node))); - let window = Arc::new(RwLock::new(vec![])); - let ncp = Ncp::new( - &cluster_info.clone(), - window, - None, - gossip_socket, - exit.clone(), - ); - - let leader_entry_point = NodeInfo::new_entry_point(&leader_ncp); - cluster_info - .write() - .unwrap() - .insert_info(leader_entry_point); - - sleep(Duration::from_millis(100)); - - let deadline = match timeout { - Some(timeout) => Duration::new(timeout, 0), - None => Duration::new(std::u64::MAX, 0), - }; - let now = Instant::now(); - // Block until leader's correct contact info is received - let leader; - - loop { - trace!("polling {:?} for leader from {:?}", leader_ncp, my_addr); - - if let Some(l) = cluster_info.read().unwrap().get_gossip_top_leader() { - leader = Some(l.clone()); - break; - } - - if log_enabled!(Level::Trace) { - trace!("{}", cluster_info.read().unwrap().node_info_trace()); - } - - if now.elapsed() > deadline { - return Err(Error::ClusterInfoError(ClusterInfoError::NoLeader)); - } - - sleep(Duration::from_millis(100)); - } - - ncp.close()?; - - if log_enabled!(Level::Trace) { - trace!("{}", cluster_info.read().unwrap().node_info_trace()); - } - - Ok(leader.unwrap().clone()) -} - -pub fn retry_get_balance( - client: &mut ThinClient, - bob_pubkey: &Pubkey, - expected_balance: Option, -) -> Option { - const LAST: usize = 30; - for run in 0..LAST { - let balance_result = client.poll_get_balance(bob_pubkey); - if expected_balance.is_none() { - return balance_result.ok(); - } - trace!( - "retry_get_balance[{}] {:?} {:?}", - run, - balance_result, - expected_balance - ); - if let (Some(expected_balance), Ok(balance_result)) = (expected_balance, balance_result) { - if expected_balance == balance_result { - return Some(balance_result); - } - } - } - None -} - -#[cfg(test)] -mod tests { - use super::*; - use bank::Bank; - use bincode::deserialize; - use cluster_info::Node; - use fullnode::Fullnode; - use leader_scheduler::LeaderScheduler; - use ledger::create_tmp_ledger_with_mint; - use logger; - use mint::Mint; - use signature::{Keypair, KeypairUtil}; - use solana_sdk::system_instruction::SystemInstruction; - use std::fs::remove_dir_all; - use vote_program::VoteProgram; - use vote_transaction::VoteTransaction; - - #[test] - fn test_thin_client() { - logger::setup(); - let leader_keypair = Arc::new(Keypair::new()); - let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - let leader_data = leader.info.clone(); - - let alice = Mint::new(10_000); - let mut bank = Bank::new(&alice); - let bob_pubkey = Keypair::new().pubkey(); - let ledger_path = create_tmp_ledger_with_mint("thin_client", &alice); - - let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( - leader_data.id, - ))); - bank.leader_scheduler = leader_scheduler; - let vote_account_keypair = Arc::new(Keypair::new()); - let last_id = bank.last_id(); - let server = Fullnode::new_with_bank( - leader_keypair, - vote_account_keypair, - bank, - 0, - &last_id, - leader, - None, - &ledger_path, - false, - None, - ); - sleep(Duration::from_millis(900)); - - let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); - - let mut client = ThinClient::new(leader_data.rpc, leader_data.tpu, transactions_socket); - let transaction_count = client.transaction_count(); - assert_eq!(transaction_count, 0); - let finality = client.get_finality(); - assert_eq!(finality, 18446744073709551615); - let last_id = client.get_last_id(); - let signature = client - .transfer(500, &alice.keypair(), bob_pubkey, &last_id) - .unwrap(); - client.poll_for_signature(&signature).unwrap(); - let balance = client.get_balance(&bob_pubkey); - assert_eq!(balance.unwrap(), 500); - let transaction_count = client.transaction_count(); - assert_eq!(transaction_count, 1); - server.close().unwrap(); - remove_dir_all(ledger_path).unwrap(); - } - - // sleep(Duration::from_millis(300)); is unstable - #[test] - #[ignore] - fn test_bad_sig() { - logger::setup(); - let leader_keypair = Arc::new(Keypair::new()); - let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - let alice = Mint::new(10_000); - let mut bank = Bank::new(&alice); - let bob_pubkey = Keypair::new().pubkey(); - let leader_data = leader.info.clone(); - let ledger_path = create_tmp_ledger_with_mint("bad_sig", &alice); - - let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( - leader_data.id, - ))); - bank.leader_scheduler = leader_scheduler; - let vote_account_keypair = Arc::new(Keypair::new()); - let last_id = bank.last_id(); - let server = Fullnode::new_with_bank( - leader_keypair, - vote_account_keypair, - bank, - 0, - &last_id, - leader, - None, - &ledger_path, - false, - None, - ); - //TODO: remove this sleep, or add a retry so CI is stable - sleep(Duration::from_millis(300)); - - let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); - let mut client = ThinClient::new(leader_data.rpc, leader_data.tpu, transactions_socket); - let last_id = client.get_last_id(); - - let tx = Transaction::system_new(&alice.keypair(), bob_pubkey, 500, last_id); - - let _sig = client.transfer_signed(&tx).unwrap(); - - let last_id = client.get_last_id(); - - let mut tr2 = Transaction::system_new(&alice.keypair(), bob_pubkey, 501, last_id); - let mut instruction2 = deserialize(tr2.userdata(0)).unwrap(); - if let SystemInstruction::Move { ref mut tokens } = instruction2 { - *tokens = 502; - } - tr2.instructions[0].userdata = serialize(&instruction2).unwrap(); - let signature = client.transfer_signed(&tr2).unwrap(); - client.poll_for_signature(&signature).unwrap(); - - let balance = client.get_balance(&bob_pubkey); - assert_eq!(balance.unwrap(), 500); - server.close().unwrap(); - remove_dir_all(ledger_path).unwrap(); - } - - #[test] - fn test_client_check_signature() { - logger::setup(); - let leader_keypair = Arc::new(Keypair::new()); - let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - let alice = Mint::new(10_000); - let mut bank = Bank::new(&alice); - let bob_pubkey = Keypair::new().pubkey(); - let leader_data = leader.info.clone(); - let ledger_path = create_tmp_ledger_with_mint("client_check_signature", &alice); - - let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( - leader_data.id, - ))); - bank.leader_scheduler = leader_scheduler; - let vote_account_keypair = Arc::new(Keypair::new()); - let entry_height = alice.create_entries().len() as u64; - let last_id = bank.last_id(); - let server = Fullnode::new_with_bank( - leader_keypair, - vote_account_keypair, - bank, - entry_height, - &last_id, - leader, - None, - &ledger_path, - false, - None, - ); - sleep(Duration::from_millis(300)); - - let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); - let mut client = ThinClient::new(leader_data.rpc, leader_data.tpu, transactions_socket); - let last_id = client.get_last_id(); - let signature = client - .transfer(500, &alice.keypair(), bob_pubkey, &last_id) - .unwrap(); - - assert!(client.poll_for_signature(&signature).is_ok()); - - server.close().unwrap(); - remove_dir_all(ledger_path).unwrap(); - } - - #[test] - fn test_register_vote_account() { - logger::setup(); - let leader_keypair = Arc::new(Keypair::new()); - let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - let mint = Mint::new(10_000); - let mut bank = Bank::new(&mint); - let leader_data = leader.info.clone(); - let ledger_path = create_tmp_ledger_with_mint("client_check_signature", &mint); - - let genesis_entries = &mint.create_entries(); - let entry_height = genesis_entries.len() as u64; - - let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( - leader_data.id, - ))); - bank.leader_scheduler = leader_scheduler; - let leader_vote_account_keypair = Arc::new(Keypair::new()); - let server = Fullnode::new_with_bank( - leader_keypair, - leader_vote_account_keypair.clone(), - bank, - entry_height, - &genesis_entries.last().unwrap().id, - leader, - None, - &ledger_path, - false, - None, - ); - sleep(Duration::from_millis(300)); - - let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); - let mut client = ThinClient::new(leader_data.rpc, leader_data.tpu, transactions_socket); - - // Create the validator account, transfer some tokens to that account - let validator_keypair = Keypair::new(); - let last_id = client.get_last_id(); - let signature = client - .transfer(500, &mint.keypair(), validator_keypair.pubkey(), &last_id) - .unwrap(); - - assert!(client.poll_for_signature(&signature).is_ok()); - - // Create the vote account - let validator_vote_account_keypair = Keypair::new(); - let vote_account_id = validator_vote_account_keypair.pubkey(); - let last_id = client.get_last_id(); - - let transaction = - VoteTransaction::vote_account_new(&validator_keypair, vote_account_id, last_id, 1); - let signature = client.transfer_signed(&transaction).unwrap(); - assert!(client.poll_for_signature(&signature).is_ok()); - - let balance = retry_get_balance(&mut client, &vote_account_id, Some(1)) - .expect("Expected balance for new account to exist"); - assert_eq!(balance, 1); - - // Register the vote account to the validator - let last_id = client.get_last_id(); - let transaction = - VoteTransaction::vote_account_register(&validator_keypair, vote_account_id, last_id, 0); - let signature = client.transfer_signed(&transaction).unwrap(); - assert!(client.poll_for_signature(&signature).is_ok()); - - const LAST: usize = 30; - for run in 0..=LAST { - println!("Checking for account registered: {}", run); - let account_user_data = client - .get_account_userdata(&vote_account_id) - .expect("Expected valid response for account userdata") - .expect("Expected valid account userdata to exist after account creation"); - - let vote_state = VoteProgram::deserialize(&account_user_data); - - if vote_state.map(|vote_state| vote_state.node_id) == Ok(validator_keypair.pubkey()) { - break; - } - - if run == LAST { - panic!("Expected successful vote account registration"); - } - sleep(Duration::from_millis(900)); - } - - server.close().unwrap(); - remove_dir_all(ledger_path).unwrap(); - } - - #[test] - fn test_transaction_count() { - // set a bogus address, see that we don't hang - logger::setup(); - let addr = "0.0.0.0:1234".parse().unwrap(); - let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); - let mut client = ThinClient::new(addr, addr, transactions_socket); - assert_eq!(client.transaction_count(), 0); - } - - #[test] - fn test_zero_balance_after_nonzero() { - logger::setup(); - let leader_keypair = Arc::new(Keypair::new()); - let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - let alice = Mint::new(10_000); - let mut bank = Bank::new(&alice); - let bob_keypair = Keypair::new(); - let leader_data = leader.info.clone(); - let ledger_path = create_tmp_ledger_with_mint("zero_balance_check", &alice); - - let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( - leader_data.id, - ))); - bank.leader_scheduler = leader_scheduler; - let vote_account_keypair = Arc::new(Keypair::new()); - let last_id = bank.last_id(); - let entry_height = alice.create_entries().len() as u64; - let server = Fullnode::new_with_bank( - leader_keypair, - vote_account_keypair, - bank, - entry_height, - &last_id, - leader, - None, - &ledger_path, - false, - None, - ); - sleep(Duration::from_millis(900)); - - let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); - let mut client = ThinClient::new(leader_data.rpc, leader_data.tpu, transactions_socket); - let last_id = client.get_last_id(); - - // give bob 500 tokens - let signature = client - .transfer(500, &alice.keypair(), bob_keypair.pubkey(), &last_id) - .unwrap(); - assert!(client.poll_for_signature(&signature).is_ok()); - - let balance = client.poll_get_balance(&bob_keypair.pubkey()); - assert!(balance.is_ok()); - assert_eq!(balance.unwrap(), 500); - - // take them away - let signature = client - .transfer(500, &bob_keypair, alice.keypair().pubkey(), &last_id) - .unwrap(); - assert!(client.poll_for_signature(&signature).is_ok()); - - // should get an error when bob's account is purged - let balance = client.poll_get_balance(&bob_keypair.pubkey()); - assert!(balance.is_err()); - - server - .close() - .unwrap_or_else(|e| panic!("close() failed! {:?}", e)); - remove_dir_all(ledger_path).unwrap(); - } -} diff --git a/book/token_program.rs b/book/token_program.rs deleted file mode 100644 index e18f51fc03d723..00000000000000 --- a/book/token_program.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! ERC20-like Token program -use native_loader; -use solana_sdk::account::Account; -use solana_sdk::pubkey::Pubkey; - -const ERC20_NAME: &str = "solana_erc20"; -const ERC20_PROGRAM_ID: [u8; 32] = [ - 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, -]; - -pub fn id() -> Pubkey { - Pubkey::new(&ERC20_PROGRAM_ID) -} - -pub fn account() -> Account { - Account { - tokens: 1, - owner: id(), - userdata: ERC20_NAME.as_bytes().to_vec(), - executable: true, - loader: native_loader::id(), - } -} diff --git a/book/tomorrow-night.css b/book/tomorrow-night.css deleted file mode 100644 index 9788e0846b0ce3..00000000000000 --- a/book/tomorrow-night.css +++ /dev/null @@ -1,96 +0,0 @@ -/* Tomorrow Night Theme */ -/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ -/* Original theme - https://github.com/chriskempson/tomorrow-theme */ -/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ - -/* Tomorrow Comment */ -.hljs-comment { - color: #969896; -} - -/* Tomorrow Red */ -.hljs-variable, -.hljs-attribute, -.hljs-tag, -.hljs-regexp, -.ruby .hljs-constant, -.xml .hljs-tag .hljs-title, -.xml .hljs-pi, -.xml .hljs-doctype, -.html .hljs-doctype, -.css .hljs-id, -.css .hljs-class, -.css .hljs-pseudo { - color: #cc6666; -} - -/* Tomorrow Orange */ -.hljs-number, -.hljs-preprocessor, -.hljs-pragma, -.hljs-built_in, -.hljs-literal, -.hljs-params, -.hljs-constant { - color: #de935f; -} - -/* Tomorrow Yellow */ -.ruby .hljs-class .hljs-title, -.css .hljs-rule .hljs-attribute { - color: #f0c674; -} - -/* Tomorrow Green */ -.hljs-string, -.hljs-value, -.hljs-inheritance, -.hljs-header, -.hljs-name, -.ruby .hljs-symbol, -.xml .hljs-cdata { - color: #b5bd68; -} - -/* Tomorrow Aqua */ -.hljs-title, -.css .hljs-hexcolor { - color: #8abeb7; -} - -/* Tomorrow Blue */ -.hljs-function, -.python .hljs-decorator, -.python .hljs-title, -.ruby .hljs-function .hljs-title, -.ruby .hljs-title .hljs-keyword, -.perl .hljs-sub, -.javascript .hljs-title, -.coffeescript .hljs-title { - color: #81a2be; -} - -/* Tomorrow Purple */ -.hljs-keyword, -.javascript .hljs-function { - color: #b294bb; -} - -.hljs { - display: block; - overflow-x: auto; - background: #1d1f21; - color: #c5c8c6; - padding: 0.5em; - -webkit-text-size-adjust: none; -} - -.coffeescript .javascript, -.javascript .xml, -.tex .hljs-formula, -.xml .javascript, -.xml .vbscript, -.xml .css, -.xml .hljs-cdata { - opacity: 0.5; -} diff --git a/book/tpu.html b/book/tpu.html deleted file mode 100644 index d33267ea2b9bd9..00000000000000 --- a/book/tpu.html +++ /dev/null @@ -1,201 +0,0 @@ - - - - - - Tpu - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

The Transaction Processing Unit

-

Tpu block diagram

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/tpu.rs b/book/tpu.rs deleted file mode 100644 index 9678c6ef11cb1f..00000000000000 --- a/book/tpu.rs +++ /dev/null @@ -1,96 +0,0 @@ -//! The `tpu` module implements the Transaction Processing Unit, a -//! 5-stage transaction processing pipeline in software. - -use bank::Bank; -use banking_stage::{BankingStage, BankingStageReturnType}; -use entry::Entry; -use fetch_stage::FetchStage; -use ledger_write_stage::LedgerWriteStage; -use poh_service::Config; -use service::Service; -use sigverify_stage::SigVerifyStage; -use solana_sdk::hash::Hash; -use std::net::UdpSocket; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc::Receiver; -use std::sync::Arc; -use std::thread; - -pub enum TpuReturnType { - LeaderRotation, -} - -pub struct Tpu { - fetch_stage: FetchStage, - sigverify_stage: SigVerifyStage, - banking_stage: BankingStage, - ledger_write_stage: LedgerWriteStage, - exit: Arc, -} - -impl Tpu { - pub fn new( - bank: &Arc, - tick_duration: Config, - transactions_sockets: Vec, - ledger_path: &str, - sigverify_disabled: bool, - max_tick_height: Option, - last_entry_id: &Hash, - ) -> (Self, Receiver>, Arc) { - let exit = Arc::new(AtomicBool::new(false)); - - let (fetch_stage, packet_receiver) = FetchStage::new(transactions_sockets, exit.clone()); - - let (sigverify_stage, verified_receiver) = - SigVerifyStage::new(packet_receiver, sigverify_disabled); - - let (banking_stage, entry_receiver) = BankingStage::new( - &bank, - verified_receiver, - tick_duration, - last_entry_id, - max_tick_height, - ); - - let (ledger_write_stage, entry_forwarder) = - LedgerWriteStage::new(Some(ledger_path), entry_receiver); - - let tpu = Tpu { - fetch_stage, - sigverify_stage, - banking_stage, - ledger_write_stage, - exit: exit.clone(), - }; - - (tpu, entry_forwarder, exit) - } - - pub fn exit(&self) { - self.exit.store(true, Ordering::Relaxed); - } - - pub fn is_exited(&self) -> bool { - self.exit.load(Ordering::Relaxed) - } - - pub fn close(self) -> thread::Result> { - self.fetch_stage.close(); - self.join() - } -} - -impl Service for Tpu { - type JoinReturnType = Option; - - fn join(self) -> thread::Result<(Option)> { - self.fetch_stage.join()?; - self.sigverify_stage.join()?; - self.ledger_write_stage.join()?; - match self.banking_stage.join()? { - Some(BankingStageReturnType::LeaderRotation) => Ok(Some(TpuReturnType::LeaderRotation)), - _ => Ok(None), - } - } -} diff --git a/book/tpu_forwarder.rs b/book/tpu_forwarder.rs deleted file mode 100644 index cd30ac1e07aea3..00000000000000 --- a/book/tpu_forwarder.rs +++ /dev/null @@ -1,198 +0,0 @@ -//! The `tpu_forwarder` module implements a validator's -//! transaction processing unit responsibility, which -//! forwards received packets to the current leader - -use cluster_info::ClusterInfo; -use contact_info::ContactInfo; -use counter::Counter; -use log::Level; -use result::Result; -use service::Service; -use std::net::UdpSocket; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::sync::mpsc::channel; -use std::sync::{Arc, RwLock}; -use std::thread::{self, Builder, JoinHandle}; -use streamer::{self, PacketReceiver}; - -pub struct TpuForwarder { - exit: Arc, - thread_hdls: Vec>, -} - -impl TpuForwarder { - fn forward(receiver: &PacketReceiver, cluster_info: &Arc>) -> Result<()> { - let socket = UdpSocket::bind("0.0.0.0:0")?; - - let my_id = cluster_info - .read() - .expect("cluster_info.read() in TpuForwarder::forward()") - .id(); - - loop { - let msgs = receiver.recv()?; - - inc_new_counter_info!( - "tpu_forwarder-msgs_received", - msgs.read().unwrap().packets.len() - ); - - if let Some(leader_data) = cluster_info - .read() - .expect("cluster_info.read() in TpuForwarder::forward()") - .leader_data() - .cloned() - { - if leader_data.id == my_id || !ContactInfo::is_valid_address(&leader_data.tpu) { - // weird cases, but we don't want to broadcast, send to ANY, or - // induce an infinite loop, but this shouldn't happen, or shouldn't be true for long... - continue; - } - - for m in msgs.write().unwrap().packets.iter_mut() { - m.meta.set_addr(&leader_data.tpu); - } - msgs.read().unwrap().send_to(&socket)? - } - } - } - - pub fn new(sockets: Vec, cluster_info: Arc>) -> Self { - let exit = Arc::new(AtomicBool::new(false)); - let (sender, receiver) = channel(); - - let mut thread_hdls: Vec<_> = sockets - .into_iter() - .map(|socket| { - streamer::receiver( - Arc::new(socket), - exit.clone(), - sender.clone(), - "tpu-forwarder", - ) - }).collect(); - - let thread_hdl = Builder::new() - .name("solana-tpu_forwarder".to_string()) - .spawn(move || { - let _ignored = Self::forward(&receiver, &cluster_info); - () - }).unwrap(); - - thread_hdls.push(thread_hdl); - - TpuForwarder { exit, thread_hdls } - } - - pub fn close(&self) { - self.exit.store(true, Ordering::Relaxed); - } -} - -impl Service for TpuForwarder { - type JoinReturnType = (); - - fn join(self) -> thread::Result<()> { - self.close(); - for thread_hdl in self.thread_hdls { - thread_hdl.join()?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use cluster_info::ClusterInfo; - use contact_info::ContactInfo; - use netutil::bind_in_range; - use solana_sdk::pubkey::Pubkey; - use std::net::UdpSocket; - use std::net::{Ipv4Addr, SocketAddr}; - use std::thread::sleep; - use std::time::Duration; - - #[test] - #[ignore] - pub fn test_tpu_forwarder() { - let nodes: Vec<_> = (0..3) - .map(|_| { - let (port, s) = bind_in_range((8000, 10000)).unwrap(); - s.set_nonblocking(true).unwrap(); - ( - s, - ContactInfo::new_with_socketaddr(&socketaddr!([127, 0, 0, 1], port)), - ) - }).collect(); - - let mut cluster_info = ClusterInfo::new(nodes[0].1.clone()); - - cluster_info.insert_info(nodes[1].1.clone()); - cluster_info.insert_info(nodes[2].1.clone()); - cluster_info.insert_info(Default::default()); - - let cluster_info = Arc::new(RwLock::new(cluster_info)); - - let (transaction_port, transaction_socket) = bind_in_range((8000, 10000)).unwrap(); - let transaction_addr = socketaddr!([127, 0, 0, 1], transaction_port); - - let tpu_forwarder = TpuForwarder::new(vec![transaction_socket], cluster_info.clone()); - - let test_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); - - // no leader set in cluster_info, drop the "transaction" - test_socket - .send_to(b"alice pays bob", &transaction_addr) - .unwrap(); - sleep(Duration::from_millis(100)); - - let mut data = vec![0u8; 64]; - // should be nothing on any socket - assert!(nodes[0].0.recv_from(&mut data).is_err()); - assert!(nodes[1].0.recv_from(&mut data).is_err()); - assert!(nodes[2].0.recv_from(&mut data).is_err()); - - // set leader to host with no tpu - cluster_info.write().unwrap().set_leader(Pubkey::default()); - test_socket - .send_to(b"alice pays bart", &transaction_addr) - .unwrap(); - sleep(Duration::from_millis(100)); - - let mut data = vec![0u8; 64]; - // should be nothing on any socket ncp - assert!(nodes[0].0.recv_from(&mut data).is_err()); - assert!(nodes[1].0.recv_from(&mut data).is_err()); - assert!(nodes[2].0.recv_from(&mut data).is_err()); - - cluster_info.write().unwrap().set_leader(nodes[0].1.id); // set leader to myself, bytes get dropped :-( - - test_socket - .send_to(b"alice pays bill", &transaction_addr) - .unwrap(); - sleep(Duration::from_millis(100)); - - // should *still* be nothing on any socket - assert!(nodes[0].0.recv_from(&mut data).is_err()); - assert!(nodes[1].0.recv_from(&mut data).is_err()); - assert!(nodes[2].0.recv_from(&mut data).is_err()); - - cluster_info.write().unwrap().set_leader(nodes[1].1.id); // set leader to node[1] - - test_socket - .send_to(b"alice pays chuck", &transaction_addr) - .unwrap(); - sleep(Duration::from_millis(100)); - - // should only be data on node[1]'s socket - assert!(nodes[0].0.recv_from(&mut data).is_err()); - assert!(nodes[2].0.recv_from(&mut data).is_err()); - - assert!(nodes[1].0.recv_from(&mut data).is_ok()); - assert_eq!(&data[..b"alice pays chuck".len()], b"alice pays chuck"); - - assert!(tpu_forwarder.join().is_ok()); - } - -} diff --git a/book/transaction.rs b/book/transaction.rs deleted file mode 100644 index e70dc793af8442..00000000000000 --- a/book/transaction.rs +++ /dev/null @@ -1 +0,0 @@ -pub use solana_sdk::transaction::*; diff --git a/book/tvu.html b/book/tvu.html deleted file mode 100644 index 59c9571b313779..00000000000000 --- a/book/tvu.html +++ /dev/null @@ -1,201 +0,0 @@ - - - - - - Tvu - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

The Transaction Validation Unit

-

Tvu block diagram

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/tvu.rs b/book/tvu.rs deleted file mode 100644 index 45a9da86f406b5..00000000000000 --- a/book/tvu.rs +++ /dev/null @@ -1,350 +0,0 @@ -//! The `tvu` module implements the Transaction Validation Unit, a -//! 3-stage transaction validation pipeline in software. -//! -//! 1. Fetch Stage -//! - Incoming blobs are picked up from the replicate socket and repair socket. -//! 2. SharedWindow Stage -//! - Blobs are windowed until a contiguous chunk is available. This stage also repairs and -//! retransmits blobs that are in the queue. -//! 3. Replicate Stage -//! - Transactions in blobs are processed and applied to the bank. -//! - TODO We need to verify the signatures in the blobs. - -use bank::Bank; -use blob_fetch_stage::BlobFetchStage; -use cluster_info::ClusterInfo; -use ledger_write_stage::LedgerWriteStage; -use replicate_stage::{ReplicateStage, ReplicateStageReturnType}; -use retransmit_stage::RetransmitStage; -use service::Service; -use signature::Keypair; -use solana_sdk::hash::Hash; -use std::net::UdpSocket; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, RwLock}; -use std::thread; -use storage_stage::{StorageStage, StorageState}; -use window::SharedWindow; - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum TvuReturnType { - LeaderRotation(u64, u64, Hash), -} - -pub struct Tvu { - replicate_stage: ReplicateStage, - fetch_stage: BlobFetchStage, - retransmit_stage: RetransmitStage, - ledger_write_stage: LedgerWriteStage, - storage_stage: StorageStage, - exit: Arc, -} - -impl Tvu { - /// This service receives messages from a leader in the network and processes the transactions - /// on the bank state. - /// # Arguments - /// * `bank` - The bank state. - /// * `keypair` - Node's key pair for signing - /// * `vote_account_keypair` - Vote key pair - /// * `entry_height` - Initial ledger height - /// * `cluster_info` - The cluster_info state. - /// * `window` - The window state. - /// * `replicate_socket` - my replicate socket - /// * `repair_socket` - my repair socket - /// * `retransmit_socket` - my retransmit socket - /// * `ledger_path` - path to the ledger file - #[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] - pub fn new( - keypair: Arc, - vote_account_keypair: Arc, - bank: &Arc, - entry_height: u64, - last_entry_id: Hash, - cluster_info: Arc>, - window: SharedWindow, - replicate_sockets: Vec, - repair_socket: UdpSocket, - retransmit_socket: UdpSocket, - ledger_path: Option<&str>, - ) -> Self { - let exit = Arc::new(AtomicBool::new(false)); - - let repair_socket = Arc::new(repair_socket); - let mut blob_sockets: Vec> = - replicate_sockets.into_iter().map(Arc::new).collect(); - blob_sockets.push(repair_socket.clone()); - let (fetch_stage, blob_fetch_receiver) = - BlobFetchStage::new_multi_socket(blob_sockets, exit.clone()); - //TODO - //the packets coming out of blob_receiver need to be sent to the GPU and verified - //then sent to the window, which does the erasure coding reconstruction - let (retransmit_stage, blob_window_receiver) = RetransmitStage::new( - &cluster_info, - window, - bank.tick_height(), - entry_height, - Arc::new(retransmit_socket), - repair_socket, - blob_fetch_receiver, - bank.leader_scheduler.clone(), - ); - - let (replicate_stage, ledger_entry_receiver) = ReplicateStage::new( - keypair.clone(), - vote_account_keypair, - bank.clone(), - cluster_info, - blob_window_receiver, - exit.clone(), - entry_height, - last_entry_id, - ); - - let (ledger_write_stage, storage_entry_receiver) = - LedgerWriteStage::new(ledger_path, ledger_entry_receiver); - - let storage_state = StorageState::new(); - let storage_stage = StorageStage::new( - &storage_state, - storage_entry_receiver, - ledger_path, - keypair, - exit.clone(), - entry_height, - ); - - Tvu { - replicate_stage, - fetch_stage, - retransmit_stage, - ledger_write_stage, - storage_stage, - exit, - } - } - - pub fn is_exited(&self) -> bool { - self.exit.load(Ordering::Relaxed) - } - - pub fn exit(&self) { - self.exit.store(true, Ordering::Relaxed); - } - - pub fn close(self) -> thread::Result> { - self.fetch_stage.close(); - self.join() - } -} - -impl Service for Tvu { - type JoinReturnType = Option; - - fn join(self) -> thread::Result> { - self.retransmit_stage.join()?; - self.fetch_stage.join()?; - self.ledger_write_stage.join()?; - self.storage_stage.join()?; - match self.replicate_stage.join()? { - Some(ReplicateStageReturnType::LeaderRotation( - tick_height, - entry_height, - last_entry_id, - )) => Ok(Some(TvuReturnType::LeaderRotation( - tick_height, - entry_height, - last_entry_id, - ))), - _ => Ok(None), - } - } -} - -#[cfg(test)] -pub mod tests { - use bank::Bank; - use bincode::serialize; - use cluster_info::{ClusterInfo, Node}; - use entry::Entry; - use leader_scheduler::LeaderScheduler; - use logger; - use mint::Mint; - use ncp::Ncp; - use packet::SharedBlob; - use service::Service; - use signature::{Keypair, KeypairUtil}; - use solana_sdk::hash::Hash; - use std::net::UdpSocket; - use std::sync::atomic::{AtomicBool, Ordering}; - use std::sync::mpsc::channel; - use std::sync::{Arc, RwLock}; - use std::time::Duration; - use streamer; - use system_transaction::SystemTransaction; - use transaction::Transaction; - use tvu::Tvu; - use window::{self, SharedWindow}; - - fn new_ncp( - cluster_info: Arc>, - gossip: UdpSocket, - exit: Arc, - ) -> (Ncp, SharedWindow) { - let window = Arc::new(RwLock::new(window::default_window())); - let ncp = Ncp::new(&cluster_info, window.clone(), None, gossip, exit); - (ncp, window) - } - - /// Test that message sent from leader to target1 and replicated to target2 - #[test] - #[ignore] - fn test_replicate() { - logger::setup(); - let leader = Node::new_localhost(); - let target1_keypair = Keypair::new(); - let target1 = Node::new_localhost_with_pubkey(target1_keypair.pubkey()); - let target2 = Node::new_localhost(); - let exit = Arc::new(AtomicBool::new(false)); - - //start cluster_info_l - let mut cluster_info_l = ClusterInfo::new(leader.info.clone()); - cluster_info_l.set_leader(leader.info.id); - - let cref_l = Arc::new(RwLock::new(cluster_info_l)); - let dr_l = new_ncp(cref_l, leader.sockets.gossip, exit.clone()); - - //start cluster_info2 - let mut cluster_info2 = ClusterInfo::new(target2.info.clone()); - cluster_info2.insert_info(leader.info.clone()); - cluster_info2.set_leader(leader.info.id); - let leader_id = leader.info.id; - let cref2 = Arc::new(RwLock::new(cluster_info2)); - let dr_2 = new_ncp(cref2, target2.sockets.gossip, exit.clone()); - - // setup some blob services to send blobs into the socket - // to simulate the source peer and get blobs out of the socket to - // simulate target peer - let (s_reader, r_reader) = channel(); - let blob_sockets: Vec> = target2 - .sockets - .replicate - .into_iter() - .map(Arc::new) - .collect(); - - let t_receiver = streamer::blob_receiver(blob_sockets[0].clone(), exit.clone(), s_reader); - - // simulate leader sending messages - let (s_responder, r_responder) = channel(); - let t_responder = streamer::responder( - "test_replicate", - Arc::new(leader.sockets.retransmit), - r_responder, - ); - - let starting_balance = 10_000; - let mint = Mint::new(starting_balance); - let replicate_addr = target1.info.tvu; - let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( - leader_id, - ))); - let mut bank = Bank::new(&mint); - bank.leader_scheduler = leader_scheduler; - let bank = Arc::new(bank); - - //start cluster_info1 - let mut cluster_info1 = ClusterInfo::new(target1.info.clone()); - cluster_info1.insert_info(leader.info.clone()); - cluster_info1.set_leader(leader.info.id); - let cref1 = Arc::new(RwLock::new(cluster_info1)); - let dr_1 = new_ncp(cref1.clone(), target1.sockets.gossip, exit.clone()); - - let vote_account_keypair = Arc::new(Keypair::new()); - let mut cur_hash = Hash::default(); - let tvu = Tvu::new( - Arc::new(target1_keypair), - vote_account_keypair, - &bank, - 0, - cur_hash, - cref1, - dr_1.1, - target1.sockets.replicate, - target1.sockets.repair, - target1.sockets.retransmit, - None, - ); - - let mut alice_ref_balance = starting_balance; - let mut msgs = Vec::new(); - let mut blob_idx = 0; - let num_transfers = 10; - let transfer_amount = 501; - let bob_keypair = Keypair::new(); - for i in 0..num_transfers { - let entry0 = Entry::new(&cur_hash, i, vec![]); - cur_hash = entry0.id; - bank.register_tick(&cur_hash); - let entry_tick0 = Entry::new(&cur_hash, i + 1, vec![]); - cur_hash = entry_tick0.id; - - let tx0 = Transaction::system_new( - &mint.keypair(), - bob_keypair.pubkey(), - transfer_amount, - cur_hash, - ); - bank.register_tick(&cur_hash); - let entry_tick1 = Entry::new(&cur_hash, i + 1, vec![]); - cur_hash = entry_tick1.id; - let entry1 = Entry::new(&cur_hash, i + num_transfers, vec![tx0]); - bank.register_tick(&entry1.id); - let entry_tick2 = Entry::new(&entry1.id, i + 1, vec![]); - cur_hash = entry_tick2.id; - - alice_ref_balance -= transfer_amount; - - for entry in vec![entry0, entry_tick0, entry_tick1, entry1, entry_tick2] { - let mut b = SharedBlob::default(); - { - let mut w = b.write().unwrap(); - w.set_index(blob_idx).unwrap(); - blob_idx += 1; - w.set_id(&leader_id).unwrap(); - - let serialized_entry = serialize(&entry).unwrap(); - - w.data_mut()[..serialized_entry.len()].copy_from_slice(&serialized_entry); - w.set_size(serialized_entry.len()); - w.meta.set_addr(&replicate_addr); - } - msgs.push(b); - } - } - - // send the blobs into the socket - s_responder.send(msgs).expect("send"); - drop(s_responder); - - // receive retransmitted messages - let timer = Duration::new(1, 0); - while let Ok(_msg) = r_reader.recv_timeout(timer) { - trace!("got msg"); - } - - let alice_balance = bank.get_balance(&mint.keypair().pubkey()); - assert_eq!(alice_balance, alice_ref_balance); - - let bob_balance = bank.get_balance(&bob_keypair.pubkey()); - assert_eq!(bob_balance, starting_balance - alice_ref_balance); - - tvu.close().expect("close"); - exit.store(true, Ordering::Relaxed); - dr_l.0.join().expect("join"); - dr_2.0.join().expect("join"); - dr_1.0.join().expect("join"); - t_receiver.join().expect("join"); - t_responder.join().expect("join"); - } -} diff --git a/book/vdf.html b/book/vdf.html deleted file mode 100644 index 5ea4f5442901e5..00000000000000 --- a/book/vdf.html +++ /dev/null @@ -1,214 +0,0 @@ - - - - - - Introduction to VDFs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

Introduction to VDFs

-

A Verifiable Delay Function is conceptually a water clock where its water marks -can be recorded and later verified that the water most certainly passed -through. Anatoly describes the water clock analogy in detail here:

-

water clock analogy

-

The same technique has been used in Bitcoin since day one. The Bitcoin feature -is called nLocktime and it can be used to postdate transactions using block -height instead of a timestamp. As a Bitcoin client, you'd use block height -instead of a timestamp if you don't trust the network. Block height turns out -to be an instance of what's being called a Verifiable Delay Function in -cryptography circles. It's a cryptographically secure way to say time has -passed. In Solana, we use a far more granular verifiable delay function, a SHA -256 hash chain, to checkpoint the ledger and coordinate consensus. With it, we -implement Optimistic Concurrency Control and are now well en route towards that -theoretical limit of 710,000 transactions per second.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/vote_program.rs b/book/vote_program.rs deleted file mode 100644 index 1c7edf97364882..00000000000000 --- a/book/vote_program.rs +++ /dev/null @@ -1,175 +0,0 @@ -//! Vote program -//! Receive and processes votes from validators - -use bincode::{deserialize, serialize}; -use byteorder::{ByteOrder, LittleEndian}; -use solana_sdk::account::Account; -use solana_sdk::pubkey::Pubkey; -use std; -use std::collections::VecDeque; -use std::mem; -use transaction::Transaction; - -// Maximum number of votes to keep around -const MAX_VOTE_HISTORY: usize = 32; - -#[derive(Debug, PartialEq)] -pub enum Error { - UserdataDeserializeFailure, - InvalidArguments, - InvalidUserdata, - UserdataTooSmall, -} -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "error") - } -} -pub type Result = std::result::Result; - -#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)] -pub struct Vote { - // TODO: add signature of the state here as well - /// A vote for height tick_height - pub tick_height: u64, -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub enum VoteInstruction { - /// Register a new "vote account" to represent a particular validator in the Vote Contract, - /// and initialize the VoteState for this "vote account" - /// * Transaction::keys[0] - the validator id - /// * Transaction::keys[1] - the new "vote account" to be associated with the validator - /// identified by keys[0] for voting - RegisterAccount, - NewVote(Vote), -} - -#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -pub struct VoteProgram { - pub votes: VecDeque, - pub node_id: Pubkey, -} - -const VOTE_PROGRAM_ID: [u8; 32] = [ - 132, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, -]; - -impl VoteProgram { - pub fn check_id(program_id: &Pubkey) -> bool { - program_id.as_ref() == VOTE_PROGRAM_ID - } - - pub fn id() -> Pubkey { - Pubkey::new(&VOTE_PROGRAM_ID) - } - - pub fn deserialize(input: &[u8]) -> Result { - let len = LittleEndian::read_u16(&input[0..2]) as usize; - - if len == 0 || input.len() < len + 2 { - Err(Error::InvalidUserdata) - } else { - deserialize(&input[2..=len + 1]).map_err(|err| { - error!("Unable to deserialize vote state: {:?}", err); - Error::InvalidUserdata - }) - } - } - - pub fn serialize(self: &VoteProgram, output: &mut [u8]) -> Result<()> { - let self_serialized = serialize(self).unwrap(); - - if output.len() + 2 < self_serialized.len() { - warn!( - "{} bytes required to serialize but only have {} bytes", - self_serialized.len(), - output.len() + 2, - ); - return Err(Error::UserdataTooSmall); - } - - let serialized_len = self_serialized.len() as u16; - LittleEndian::write_u16(&mut output[0..2], serialized_len); - output[2..=serialized_len as usize + 1].clone_from_slice(&self_serialized); - Ok(()) - } - - pub fn process_transaction( - tx: &Transaction, - instruction_index: usize, - accounts: &mut [&mut Account], - ) -> Result<()> { - match deserialize(tx.userdata(instruction_index)) { - Ok(VoteInstruction::RegisterAccount) => { - // TODO: a single validator could register multiple "vote accounts" - // which would clutter the "accounts" structure. See github issue 1654. - accounts[1].owner = Self::id(); - - let mut vote_state = VoteProgram { - votes: VecDeque::new(), - node_id: *tx.from(), - }; - - vote_state.serialize(&mut accounts[1].userdata)?; - - Ok(()) - } - Ok(VoteInstruction::NewVote(vote)) => { - if !Self::check_id(&accounts[0].owner) { - error!("accounts[0] is not assigned to the VOTE_PROGRAM"); - Err(Error::InvalidArguments)?; - } - - let mut vote_state = Self::deserialize(&accounts[0].userdata)?; - - // TODO: Integrity checks - // a) Verify the vote's bank hash matches what is expected - // b) Verify vote is older than previous votes - - // Only keep around the most recent MAX_VOTE_HISTORY votes - if vote_state.votes.len() == MAX_VOTE_HISTORY { - vote_state.votes.pop_front(); - } - - vote_state.votes.push_back(vote); - vote_state.serialize(&mut accounts[0].userdata)?; - - Ok(()) - } - Err(_) => { - info!( - "Invalid vote transaction userdata: {:?}", - tx.userdata(instruction_index) - ); - Err(Error::UserdataDeserializeFailure) - } - } - } - - pub fn get_max_size() -> usize { - // Upper limit on the size of the Vote State. Equal to - // sizeof(VoteProgram) + MAX_VOTE_HISTORY * sizeof(Vote) + - // 32 (the size of the Pubkey) + 2 (2 bytes for the size) - mem::size_of::() - + MAX_VOTE_HISTORY * mem::size_of::() - + mem::size_of::() - + mem::size_of::() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_serde() -> Result<()> { - let mut buffer: Vec = vec![0; VoteProgram::get_max_size()]; - let mut vote_program = VoteProgram::default(); - vote_program.votes = (0..MAX_VOTE_HISTORY).map(|_| Vote::default()).collect(); - vote_program.serialize(&mut buffer).unwrap(); - assert_eq!(VoteProgram::deserialize(&buffer).unwrap(), vote_program); - Ok(()) - } -} diff --git a/book/vote_stage.rs b/book/vote_stage.rs deleted file mode 100644 index 430ed2f04a8785..00000000000000 --- a/book/vote_stage.rs +++ /dev/null @@ -1,82 +0,0 @@ -//! The `vote_stage` votes on the `last_id` of the bank at a regular cadence - -use bank::Bank; -use bincode::serialize; -use cluster_info::ClusterInfo; -use counter::Counter; -use log::Level; -use packet::SharedBlob; -use result::{Error, Result}; -use signature::Keypair; -use solana_sdk::hash::Hash; -use std::net::SocketAddr; -use std::sync::atomic::AtomicUsize; -use std::sync::{Arc, RwLock}; -use streamer::BlobSender; -use transaction::Transaction; -use vote_program::Vote; -use vote_transaction::VoteTransaction; - -#[derive(Debug, PartialEq, Eq)] -pub enum VoteError { - NoValidSupermajority, - NoLeader, - LeaderInfoNotFound, -} - -// TODO: Change voting to be on fixed tick intervals based on bank state -pub fn create_new_signed_vote_blob( - last_id: &Hash, - vote_account: &Keypair, - bank: &Arc, - cluster_info: &Arc>, -) -> Result { - let shared_blob = SharedBlob::default(); - let tick_height = bank.tick_height(); - - let leader_tpu = get_leader_tpu(&bank, cluster_info)?; - //TODO: doesn't seem like there is a synchronous call to get height and id - debug!("voting on {:?}", &last_id.as_ref()[..8]); - let vote = Vote { tick_height }; - let tx = Transaction::vote_new(&vote_account, vote, *last_id, 0); - { - let mut blob = shared_blob.write().unwrap(); - let bytes = serialize(&tx)?; - let len = bytes.len(); - blob.data[..len].copy_from_slice(&bytes); - blob.meta.set_addr(&leader_tpu); - blob.meta.size = len; - }; - - Ok(shared_blob) -} - -fn get_leader_tpu(bank: &Bank, cluster_info: &Arc>) -> Result { - let leader_id = match bank.get_current_leader() { - Some((leader_id, _)) => leader_id, - None => return Err(Error::VoteError(VoteError::NoLeader)), - }; - - let rcluster_info = cluster_info.read().unwrap(); - let leader_tpu = rcluster_info.lookup(leader_id).map(|leader| leader.tpu); - if let Some(leader_tpu) = leader_tpu { - Ok(leader_tpu) - } else { - Err(Error::VoteError(VoteError::LeaderInfoNotFound)) - } -} - -pub fn send_validator_vote( - bank: &Arc, - vote_account: &Keypair, - cluster_info: &Arc>, - vote_blob_sender: &BlobSender, -) -> Result<()> { - let last_id = bank.last_id(); - - let shared_blob = create_new_signed_vote_blob(&last_id, vote_account, bank, cluster_info)?; - inc_new_counter_info!("replicate-vote_sent", 1); - vote_blob_sender.send(vec![shared_blob])?; - - Ok(()) -} diff --git a/book/vote_transaction.rs b/book/vote_transaction.rs deleted file mode 100644 index c53b6dc9020780..00000000000000 --- a/book/vote_transaction.rs +++ /dev/null @@ -1,118 +0,0 @@ -//! The `vote_transaction` module provides functionality for creating vote transactions. - -#[cfg(test)] -use bank::Bank; -use bincode::deserialize; -#[cfg(test)] -use result::Result; -use signature::Keypair; -#[cfg(test)] -use signature::KeypairUtil; -use solana_sdk::hash::Hash; -use solana_sdk::pubkey::Pubkey; -use system_transaction::SystemTransaction; -use transaction::Transaction; -use vote_program::{Vote, VoteInstruction, VoteProgram}; - -pub trait VoteTransaction { - fn vote_new(vote_account: &Keypair, vote: Vote, last_id: Hash, fee: u64) -> Self; - fn vote_account_new( - validator_id: &Keypair, - new_vote_account_id: Pubkey, - last_id: Hash, - num_tokens: u64, - ) -> Self; - fn vote_account_register( - validator_id: &Keypair, - vote_account_id: Pubkey, - last_id: Hash, - fee: u64, - ) -> Self; - fn get_votes(&self) -> Vec<(Pubkey, Vote, Hash)>; -} - -impl VoteTransaction for Transaction { - fn vote_new(vote_account: &Keypair, vote: Vote, last_id: Hash, fee: u64) -> Self { - let instruction = VoteInstruction::NewVote(vote); - Transaction::new( - vote_account, - &[], - VoteProgram::id(), - &instruction, - last_id, - fee, - ) - } - - fn vote_account_new( - validator_id: &Keypair, - new_vote_account_id: Pubkey, - last_id: Hash, - num_tokens: u64, - ) -> Self { - Transaction::system_create( - validator_id, - new_vote_account_id, - last_id, - num_tokens, - VoteProgram::get_max_size() as u64, - VoteProgram::id(), - 0, - ) - } - - fn vote_account_register( - validator_id: &Keypair, - vote_account_id: Pubkey, - last_id: Hash, - fee: u64, - ) -> Self { - let register_tx = VoteInstruction::RegisterAccount; - Transaction::new( - validator_id, - &[vote_account_id], - VoteProgram::id(), - ®ister_tx, - last_id, - fee, - ) - } - - fn get_votes(&self) -> Vec<(Pubkey, Vote, Hash)> { - let mut votes = vec![]; - for i in 0..self.instructions.len() { - let tx_program_id = self.program_id(i); - if VoteProgram::check_id(&tx_program_id) { - if let Ok(Some(VoteInstruction::NewVote(vote))) = deserialize(&self.userdata(i)) { - votes.push((self.account_keys[0], vote, self.last_id)) - } - } - } - votes - } -} - -#[cfg(test)] -pub fn create_vote_account( - node_keypair: &Keypair, - bank: &Bank, - num_tokens: u64, - last_id: Hash, -) -> Result { - let new_vote_account = Keypair::new(); - - // Create the new vote account - let tx = - Transaction::vote_account_new(node_keypair, new_vote_account.pubkey(), last_id, num_tokens); - bank.process_transaction(&tx)?; - - // Register the vote account to the validator - let tx = - Transaction::vote_account_register(node_keypair, new_vote_account.pubkey(), last_id, 0); - bank.process_transaction(&tx)?; - - Ok(new_vote_account) -} - -#[cfg(test)] -mod tests {} diff --git a/book/wallet.html b/book/wallet.html deleted file mode 100644 index a866d614004c99..00000000000000 --- a/book/wallet.html +++ /dev/null @@ -1,473 +0,0 @@ - - - - - - solana-wallet CLI - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

solana-wallet CLI

-

The solana crate is distributed with a command-line interface tool

-

Examples

-

Get Pubkey

-
// Command
-$ solana-wallet address
-
-// Return
-<PUBKEY>
-
-

Airdrop Tokens

-
// Command
-$ solana-wallet airdrop 123
-
-// Return
-"Your balance is: 123"
-
-

Get Balance

-
// Command
-$ solana-wallet balance
-
-// Return
-"Your balance is: 123"
-
-

Confirm Transaction

-
// Command
-$ solana-wallet confirm <TX_SIGNATURE>
-
-// Return
-"Confirmed" / "Not found"
-
-

Deploy program

-
// Command
-$ solana-wallet deploy <PATH>
-
-// Return
-<PROGRAM_ID>
-
-

Unconditional Immediate Transfer

-
// Command
-$ solana-wallet pay <PUBKEY> 123
-
-// Return
-<TX_SIGNATURE>
-
-

Post-Dated Transfer

-
// Command
-$ solana-wallet pay <PUBKEY> 123 \
-    --after 2018-12-24T23:59:00 --require-timestamp-from <PUBKEY>
-
-// Return
-{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
-
-

require-timestamp-from is optional. If not provided, the transaction will expect a timestamp signed by this wallet's secret key

-

Authorized Transfer

-

A third party must send a signature to unlock the tokens.

-
// Command
-$ solana-wallet pay <PUBKEY> 123 \
-    --require-signature-from <PUBKEY>
-
-// Return
-{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
-
-

Post-Dated and Authorized Transfer

-
// Command
-$ solana-wallet pay <PUBKEY> 123 \
-    --after 2018-12-24T23:59 --require-timestamp-from <PUBKEY> \
-    --require-signature-from <PUBKEY>
-
-// Return
-{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
-
-

Multiple Witnesses

-
// Command
-$ solana-wallet pay <PUBKEY> 123 \
-    --require-signature-from <PUBKEY> \
-    --require-signature-from <PUBKEY>
-
-// Return
-{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
-
-

Cancelable Transfer

-
// Command
-$ solana-wallet pay <PUBKEY> 123 \
-    --require-signature-from <PUBKEY> \
-    --cancelable
-
-// Return
-{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
-
-

Cancel Transfer

-
// Command
-$ solana-wallet cancel <PROCESS_ID>
-
-// Return
-<TX_SIGNATURE>
-
-

Send Signature

-
// Command
-$ solana-wallet send-signature <PUBKEY> <PROCESS_ID>
-
-// Return
-<TX_SIGNATURE>
-
-

Indicate Elapsed Time

-

Use the current system time:

-
// Command
-$ solana-wallet send-timestamp <PUBKEY> <PROCESS_ID>
-
-// Return
-<TX_SIGNATURE>
-
-

Or specify some other arbitrary timestamp:

-
// Command
-$ solana-wallet send-timestamp <PUBKEY> <PROCESS_ID> --date 2018-12-24T23:59:00
-
-// Return
-<TX_SIGNATURE>
-
-

Usage

-
solana-wallet 0.11.0
-
-USAGE:
-    solana-wallet [OPTIONS] [SUBCOMMAND]
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-OPTIONS:
-    -k, --keypair <PATH>         /path/to/id.json
-    -n, --network <HOST:PORT>    Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001
-        --proxy <URL>            Address of TLS proxy
-        --port <NUM>             Optional rpc-port configuration to connect to non-default nodes
-        --timeout <SECS>         Max seconds to wait to get necessary gossip from the network
-
-SUBCOMMANDS:
-    address                  Get your public key
-    airdrop                  Request a batch of tokens
-    balance                  Get your balance
-    cancel                   Cancel a transfer
-    confirm                  Confirm transaction by signature
-    deploy                   Deploy a program
-    get-transaction-count    Get current transaction count
-    help                     Prints this message or the help of the given subcommand(s)
-    pay                      Send a payment
-    send-signature           Send a signature to authorize a transfer
-    send-timestamp           Send a timestamp to unlock a transfer
-
-
solana-wallet-address 
-Get your public key
-
-USAGE:
-    solana-wallet address
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-
solana-wallet-airdrop 
-Request a batch of tokens
-
-USAGE:
-    solana-wallet airdrop <NUM>
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-ARGS:
-    <NUM>    The number of tokens to request
-
-
solana-wallet-balance 
-Get your balance
-
-USAGE:
-    solana-wallet balance
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-
solana-wallet-cancel 
-Cancel a transfer
-
-USAGE:
-    solana-wallet cancel <PROCESS_ID>
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-ARGS:
-    <PROCESS_ID>    The process id of the transfer to cancel
-
-
solana-wallet-confirm 
-Confirm transaction by signature
-
-USAGE:
-    solana-wallet confirm <SIGNATURE>
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-ARGS:
-    <SIGNATURE>    The transaction signature to confirm
-
-
solana-wallet-deploy 
-Deploy a program
-
-USAGE:
-    solana-wallet deploy <PATH>
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-ARGS:
-    <PATH>    /path/to/program.o
-
-
solana-wallet-get-transaction-count 
-Get current transaction count
-
-USAGE:
-    solana-wallet get-transaction-count
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-
solana-wallet-pay 
-Send a payment
-
-USAGE:
-    solana-wallet pay [FLAGS] [OPTIONS] <PUBKEY> <NUM>
-
-FLAGS:
-        --cancelable    
-    -h, --help          Prints help information
-    -V, --version       Prints version information
-
-OPTIONS:
-        --after <DATETIME>                      A timestamp after which transaction will execute
-        --require-timestamp-from <PUBKEY>       Require timestamp from this third party
-        --require-signature-from <PUBKEY>...    Any third party signatures required to unlock the tokens
-
-ARGS:
-    <PUBKEY>    The pubkey of recipient
-    <NUM>       The number of tokens to send
-
-
solana-wallet-send-signature 
-Send a signature to authorize a transfer
-
-USAGE:
-    solana-wallet send-signature <PUBKEY> <PROCESS_ID>
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-ARGS:
-    <PUBKEY>        The pubkey of recipient
-    <PROCESS_ID>    The process id of the transfer to authorize
-
-
solana-wallet-send-timestamp 
-Send a timestamp to unlock a transfer
-
-USAGE:
-    solana-wallet send-timestamp [OPTIONS] <PUBKEY> <PROCESS_ID>
-
-FLAGS:
-    -h, --help       Prints help information
-    -V, --version    Prints version information
-
-OPTIONS:
-        --date <DATETIME>    Optional arbitrary timestamp to apply
-
-ARGS:
-    <PUBKEY>        The pubkey of recipient
-    <PROCESS_ID>    The process id of the transfer to unlock
-
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/book/wallet.rs b/book/wallet.rs deleted file mode 100644 index b7b451be0df9ec..00000000000000 --- a/book/wallet.rs +++ /dev/null @@ -1,1610 +0,0 @@ -use bincode::serialize; -use bpf_loader; -use bs58; -use budget_program::BudgetState; -use budget_transaction::BudgetTransaction; -use chrono::prelude::*; -use clap::ArgMatches; -use elf; -use fullnode::Config; -use loader_transaction::LoaderTransaction; -use ring::rand::SystemRandom; -use ring::signature::Ed25519KeyPair; -use rpc::RpcSignatureStatus; -use rpc_request::RpcRequest; -use serde_json; -use signature::{Keypair, KeypairUtil, Signature}; -use solana_drone::drone::{request_airdrop_transaction, DRONE_PORT}; -use solana_sdk::hash::Hash; -use solana_sdk::pubkey::Pubkey; -use std::fs::{self, File}; -use std::io::Write; -use std::net::{Ipv4Addr, SocketAddr}; -use std::path::Path; -use std::str::FromStr; -use std::{error, fmt, mem}; -use system_transaction::SystemTransaction; -use thin_client::poll_gossip_for_leader; -use transaction::Transaction; - -const PLATFORM_SECTION_C: &str = ".text.entrypoint"; -const USERDATA_CHUNK_SIZE: usize = 256; - -#[derive(Debug, PartialEq)] -pub enum WalletCommand { - Address, - AirDrop(u64), - Balance, - Cancel(Pubkey), - Confirm(Signature), - Deploy(String), - GetTransactionCount, - // Pay(tokens, to, timestamp, timestamp_pubkey, witness(es), cancelable) - Pay( - u64, - Pubkey, - Option>, - Option, - Option>, - Option, - ), - // TimeElapsed(to, process_id, timestamp) - TimeElapsed(Pubkey, Pubkey, DateTime), - // Witness(to, process_id) - Witness(Pubkey, Pubkey), -} - -#[derive(Debug, Clone)] -pub enum WalletError { - CommandNotRecognized(String), - BadParameter(String), - DynamicProgramError(String), - RpcRequestError(String), -} - -impl fmt::Display for WalletError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "invalid") - } -} - -impl error::Error for WalletError { - fn description(&self) -> &str { - "invalid" - } - - fn cause(&self) -> Option<&error::Error> { - // Generic error, underlying cause isn't tracked. - None - } -} - -pub struct WalletConfig { - pub id: Keypair, - pub command: WalletCommand, - pub network: SocketAddr, - pub timeout: Option, - pub proxy: Option, - pub drone_port: Option, -} - -impl Default for WalletConfig { - fn default() -> WalletConfig { - let default_addr = socketaddr!(0, 8000); - WalletConfig { - id: Keypair::new(), - command: WalletCommand::Balance, - network: default_addr, - timeout: None, - proxy: None, - drone_port: None, - } - } -} - -impl WalletConfig { - pub fn drone_addr(&self, tpu_addr: SocketAddr) -> SocketAddr { - let mut drone_addr = tpu_addr; - drone_addr.set_port(self.drone_port.unwrap_or(DRONE_PORT)); - drone_addr - } - - pub fn rpc_addr(&self, rpc_addr: SocketAddr) -> String { - let rpc_addr_str = format!("http://{}", rpc_addr.to_string()); - self.proxy.clone().unwrap_or(rpc_addr_str) - } -} - -pub fn parse_command( - pubkey: Pubkey, - matches: &ArgMatches, -) -> Result> { - let response = match matches.subcommand() { - ("address", Some(_address_matches)) => Ok(WalletCommand::Address), - ("airdrop", Some(airdrop_matches)) => { - let tokens = airdrop_matches.value_of("tokens").unwrap().parse()?; - Ok(WalletCommand::AirDrop(tokens)) - } - ("balance", Some(_balance_matches)) => Ok(WalletCommand::Balance), - ("cancel", Some(cancel_matches)) => { - let pubkey_vec = bs58::decode(cancel_matches.value_of("process-id").unwrap()) - .into_vec() - .expect("base58-encoded public key"); - - if pubkey_vec.len() != mem::size_of::() { - eprintln!("{}", cancel_matches.usage()); - Err(WalletError::BadParameter("Invalid public key".to_string()))?; - } - let process_id = Pubkey::new(&pubkey_vec); - Ok(WalletCommand::Cancel(process_id)) - } - ("confirm", Some(confirm_matches)) => { - let signatures = bs58::decode(confirm_matches.value_of("signature").unwrap()) - .into_vec() - .expect("base58-encoded signature"); - - if signatures.len() == mem::size_of::() { - let signature = Signature::new(&signatures); - Ok(WalletCommand::Confirm(signature)) - } else { - eprintln!("{}", confirm_matches.usage()); - Err(WalletError::BadParameter("Invalid signature".to_string())) - } - } - ("deploy", Some(deploy_matches)) => Ok(WalletCommand::Deploy( - deploy_matches - .value_of("program-location") - .unwrap() - .to_string(), - )), - ("get-transaction-count", Some(_matches)) => Ok(WalletCommand::GetTransactionCount), - ("pay", Some(pay_matches)) => { - let tokens = pay_matches.value_of("tokens").unwrap().parse()?; - let to = if pay_matches.is_present("to") { - let pubkey_vec = bs58::decode(pay_matches.value_of("to").unwrap()) - .into_vec() - .expect("base58-encoded public key"); - - if pubkey_vec.len() != mem::size_of::() { - eprintln!("{}", pay_matches.usage()); - Err(WalletError::BadParameter( - "Invalid to public key".to_string(), - ))?; - } - Pubkey::new(&pubkey_vec) - } else { - pubkey - }; - let timestamp = if pay_matches.is_present("timestamp") { - // Parse input for serde_json - let date_string = if !pay_matches.value_of("timestamp").unwrap().contains('Z') { - format!("\"{}Z\"", pay_matches.value_of("timestamp").unwrap()) - } else { - format!("\"{}\"", pay_matches.value_of("timestamp").unwrap()) - }; - Some(serde_json::from_str(&date_string)?) - } else { - None - }; - let timestamp_pubkey = if pay_matches.is_present("timestamp-pubkey") { - let pubkey_vec = bs58::decode(pay_matches.value_of("timestamp-pubkey").unwrap()) - .into_vec() - .expect("base58-encoded public key"); - - if pubkey_vec.len() != mem::size_of::() { - eprintln!("{}", pay_matches.usage()); - Err(WalletError::BadParameter( - "Invalid timestamp public key".to_string(), - ))?; - } - Some(Pubkey::new(&pubkey_vec)) - } else { - None - }; - let witness_vec = if pay_matches.is_present("witness") { - let witnesses = pay_matches.values_of("witness").unwrap(); - let mut collection = Vec::new(); - for witness in witnesses { - let pubkey_vec = bs58::decode(witness) - .into_vec() - .expect("base58-encoded public key"); - - if pubkey_vec.len() != mem::size_of::() { - eprintln!("{}", pay_matches.usage()); - Err(WalletError::BadParameter( - "Invalid witness public key".to_string(), - ))?; - } - collection.push(Pubkey::new(&pubkey_vec)); - } - Some(collection) - } else { - None - }; - let cancelable = if pay_matches.is_present("cancelable") { - Some(pubkey) - } else { - None - }; - - Ok(WalletCommand::Pay( - tokens, - to, - timestamp, - timestamp_pubkey, - witness_vec, - cancelable, - )) - } - ("send-signature", Some(sig_matches)) => { - let pubkey_vec = bs58::decode(sig_matches.value_of("to").unwrap()) - .into_vec() - .expect("base58-encoded public key"); - - if pubkey_vec.len() != mem::size_of::() { - eprintln!("{}", sig_matches.usage()); - Err(WalletError::BadParameter("Invalid public key".to_string()))?; - } - let to = Pubkey::new(&pubkey_vec); - - let pubkey_vec = bs58::decode(sig_matches.value_of("process-id").unwrap()) - .into_vec() - .expect("base58-encoded public key"); - - if pubkey_vec.len() != mem::size_of::() { - eprintln!("{}", sig_matches.usage()); - Err(WalletError::BadParameter("Invalid public key".to_string()))?; - } - let process_id = Pubkey::new(&pubkey_vec); - Ok(WalletCommand::Witness(to, process_id)) - } - ("send-timestamp", Some(timestamp_matches)) => { - let pubkey_vec = bs58::decode(timestamp_matches.value_of("to").unwrap()) - .into_vec() - .expect("base58-encoded public key"); - - if pubkey_vec.len() != mem::size_of::() { - eprintln!("{}", timestamp_matches.usage()); - Err(WalletError::BadParameter("Invalid public key".to_string()))?; - } - let to = Pubkey::new(&pubkey_vec); - - let pubkey_vec = bs58::decode(timestamp_matches.value_of("process-id").unwrap()) - .into_vec() - .expect("base58-encoded public key"); - - if pubkey_vec.len() != mem::size_of::() { - eprintln!("{}", timestamp_matches.usage()); - Err(WalletError::BadParameter("Invalid public key".to_string()))?; - } - let process_id = Pubkey::new(&pubkey_vec); - let dt = if timestamp_matches.is_present("datetime") { - // Parse input for serde_json - let date_string = if !timestamp_matches - .value_of("datetime") - .unwrap() - .contains('Z') - { - format!("\"{}Z\"", timestamp_matches.value_of("datetime").unwrap()) - } else { - format!("\"{}\"", timestamp_matches.value_of("datetime").unwrap()) - }; - serde_json::from_str(&date_string)? - } else { - Utc::now() - }; - Ok(WalletCommand::TimeElapsed(to, process_id, dt)) - } - ("", None) => { - eprintln!("{}", matches.usage()); - Err(WalletError::CommandNotRecognized( - "no subcommand given".to_string(), - )) - } - _ => unreachable!(), - }?; - Ok(response) -} - -pub fn process_command(config: &WalletConfig) -> Result> { - if let WalletCommand::Address = config.command { - // Get address of this client - return Ok(format!("{}", config.id.pubkey())); - } - - let leader = poll_gossip_for_leader(config.network, config.timeout)?; - let tpu_addr = leader.tpu; - let drone_addr = config.drone_addr(tpu_addr); - let rpc_addr = config.rpc_addr(leader.rpc); - - match config.command { - // Get address of this client - WalletCommand::Address => unreachable!(), - // Request an airdrop from Solana Drone; - WalletCommand::AirDrop(tokens) => { - println!( - "Requesting airdrop of {:?} tokens from {}", - tokens, drone_addr - ); - let params = json!([format!("{}", config.id.pubkey())]); - let previous_balance = match RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params))? - .as_u64() - { - Some(tokens) => tokens, - None => Err(WalletError::RpcRequestError( - "Received result of an unexpected type".to_string(), - ))?, - }; - - let last_id = get_last_id(&rpc_addr)?; - let transaction = - request_airdrop_transaction(&drone_addr, &config.id.pubkey(), tokens, last_id)?; - send_and_confirm_tx(&rpc_addr, &transaction)?; - - let params = json!([format!("{}", config.id.pubkey())]); - let current_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params))? - .as_u64() - .unwrap_or(previous_balance); - - if current_balance - previous_balance < tokens { - Err("Airdrop failed!")?; - } - Ok(format!("Your balance is: {:?}", current_balance)) - } - // Check client balance - WalletCommand::Balance => { - println!("Balance requested..."); - let params = json!([format!("{}", config.id.pubkey())]); - let balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params))? - .as_u64(); - match balance { - Some(0) => Ok("No account found! Request an airdrop to get started.".to_string()), - Some(tokens) => Ok(format!("Your balance is: {:?}", tokens)), - None => Err(WalletError::RpcRequestError( - "Received result of an unexpected type".to_string(), - ))?, - } - } - // Cancel a contract by contract Pubkey - WalletCommand::Cancel(pubkey) => { - let last_id = get_last_id(&rpc_addr)?; - let tx = - Transaction::budget_new_signature(&config.id, pubkey, config.id.pubkey(), last_id); - let signature_str = send_tx(&rpc_addr, &tx)?; - Ok(signature_str.to_string()) - } - // Confirm the last client transaction by signature - WalletCommand::Confirm(signature) => { - let params = json!([format!("{}", signature)]); - let confirmation = RpcRequest::ConfirmTransaction - .make_rpc_request(&rpc_addr, 1, Some(params))? - .as_bool(); - match confirmation { - Some(b) => { - if b { - Ok("Confirmed".to_string()) - } else { - Ok("Not found".to_string()) - } - } - None => Err(WalletError::RpcRequestError( - "Received result of an unexpected type".to_string(), - ))?, - } - } - // Deploy a custom program to the chain - WalletCommand::Deploy(ref program_location) => { - let params = json!([format!("{}", config.id.pubkey())]); - let balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params))? - .as_u64(); - if let Some(tokens) = balance { - if tokens < 1 { - Err(WalletError::DynamicProgramError( - "Insufficient funds".to_string(), - ))? - } - } - - let last_id = get_last_id(&rpc_addr)?; - let program = Keypair::new(); - let program_userdata = elf::File::open_path(program_location) - .map_err(|_| { - WalletError::DynamicProgramError("Could not parse program file".to_string()) - })?.get_section(PLATFORM_SECTION_C) - .ok_or_else(|| { - WalletError::DynamicProgramError( - "Could not find entrypoint in program file".to_string(), - ) - })?.data - .clone(); - - let tx = Transaction::system_create( - &config.id, - program.pubkey(), - last_id, - 1, - program_userdata.len() as u64, - bpf_loader::id(), - 0, - ); - send_and_confirm_tx(&rpc_addr, &tx).map_err(|_| { - WalletError::DynamicProgramError("Program allocate space failed".to_string()) - })?; - - let mut offset = 0; - for chunk in program_userdata.chunks(USERDATA_CHUNK_SIZE) { - let tx = Transaction::loader_write( - &program, - bpf_loader::id(), - offset, - chunk.to_vec(), - last_id, - 0, - ); - send_and_confirm_tx(&rpc_addr, &tx).map_err(|_| { - WalletError::DynamicProgramError(format!( - "Program write failed at offset {:?}", - offset - )) - })?; - offset += USERDATA_CHUNK_SIZE as u32; - } - - let last_id = get_last_id(&rpc_addr)?; - let tx = Transaction::loader_finalize(&program, bpf_loader::id(), last_id, 0); - send_and_confirm_tx(&rpc_addr, &tx).map_err(|_| { - WalletError::DynamicProgramError("Program finalize transaction failed".to_string()) - })?; - - let tx = Transaction::system_spawn(&program, last_id, 0); - send_and_confirm_tx(&rpc_addr, &tx).map_err(|_| { - WalletError::DynamicProgramError("Program spawn failed".to_string()) - })?; - - Ok(json!({ - "programId": format!("{}", program.pubkey()), - }).to_string()) - } - WalletCommand::GetTransactionCount => { - let transaction_count = RpcRequest::GetTransactionCount - .make_rpc_request(&rpc_addr, 1, None)? - .as_u64(); - match transaction_count { - Some(count) => Ok(count.to_string()), - None => Err(WalletError::RpcRequestError( - "Received result of an unexpected type".to_string(), - ))?, - } - } - // If client has positive balance, pay tokens to another address - WalletCommand::Pay(tokens, to, timestamp, timestamp_pubkey, ref witnesses, cancelable) => { - let last_id = get_last_id(&rpc_addr)?; - - if timestamp == None && *witnesses == None { - let tx = Transaction::system_new(&config.id, to, tokens, last_id); - let signature_str = send_tx(&rpc_addr, &tx)?; - Ok(signature_str.to_string()) - } else if *witnesses == None { - let dt = timestamp.unwrap(); - let dt_pubkey = match timestamp_pubkey { - Some(pubkey) => pubkey, - None => config.id.pubkey(), - }; - - let contract_funds = Keypair::new(); - let contract_state = Keypair::new(); - let budget_program_id = BudgetState::id(); - - // Create account for contract funds - let tx = Transaction::system_create( - &config.id, - contract_funds.pubkey(), - last_id, - tokens, - 0, - budget_program_id, - 0, - ); - let _signature_str = send_tx(&rpc_addr, &tx)?; - - // Create account for contract state - let tx = Transaction::system_create( - &config.id, - contract_state.pubkey(), - last_id, - 1, - 196, - budget_program_id, - 0, - ); - let _signature_str = send_tx(&rpc_addr, &tx)?; - - // Initializing contract - let tx = Transaction::budget_new_on_date( - &contract_funds, - to, - contract_state.pubkey(), - dt, - dt_pubkey, - cancelable, - tokens, - last_id, - ); - let signature_str = send_tx(&rpc_addr, &tx)?; - - Ok(json!({ - "signature": signature_str, - "processId": format!("{}", contract_state.pubkey()), - }).to_string()) - } else if timestamp == None { - let last_id = get_last_id(&rpc_addr)?; - - let witness = if let Some(ref witness_vec) = *witnesses { - witness_vec[0] - } else { - Err(WalletError::BadParameter( - "Could not parse required signature pubkey(s)".to_string(), - ))? - }; - - let contract_funds = Keypair::new(); - let contract_state = Keypair::new(); - let budget_program_id = BudgetState::id(); - - // Create account for contract funds - let tx = Transaction::system_create( - &config.id, - contract_funds.pubkey(), - last_id, - tokens, - 0, - budget_program_id, - 0, - ); - let _signature_str = send_tx(&rpc_addr, &tx)?; - - // Create account for contract state - let tx = Transaction::system_create( - &config.id, - contract_state.pubkey(), - last_id, - 1, - 196, - budget_program_id, - 0, - ); - let _signature_str = send_tx(&rpc_addr, &tx)?; - - // Initializing contract - let tx = Transaction::budget_new_when_signed( - &contract_funds, - to, - contract_state.pubkey(), - witness, - cancelable, - tokens, - last_id, - ); - let signature_str = send_tx(&rpc_addr, &tx)?; - - Ok(json!({ - "signature": signature_str, - "processId": format!("{}", contract_state.pubkey()), - }).to_string()) - } else { - Ok("Combo transactions not yet handled".to_string()) - } - } - // Apply time elapsed to contract - WalletCommand::TimeElapsed(to, pubkey, dt) => { - let params = json!(format!("{}", config.id.pubkey())); - let balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params))? - .as_u64(); - - if let Some(0) = balance { - let params = json!([format!("{}", config.id.pubkey()), 1]); - RpcRequest::RequestAirdrop - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap(); - } - - let last_id = get_last_id(&rpc_addr)?; - - let tx = Transaction::budget_new_timestamp(&config.id, pubkey, to, dt, last_id); - let signature_str = send_tx(&rpc_addr, &tx)?; - - Ok(signature_str.to_string()) - } - // Apply witness signature to contract - WalletCommand::Witness(to, pubkey) => { - let params = json!([format!("{}", config.id.pubkey())]); - let balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params))? - .as_u64(); - - if let Some(0) = balance { - let params = json!([format!("{}", config.id.pubkey()), 1]); - RpcRequest::RequestAirdrop - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap(); - } - - let last_id = get_last_id(&rpc_addr)?; - let tx = Transaction::budget_new_signature(&config.id, pubkey, to, last_id); - let signature_str = send_tx(&rpc_addr, &tx)?; - - Ok(signature_str.to_string()) - } - } -} - -pub fn read_leader(path: &str) -> Result { - let file = File::open(path.to_string()).or_else(|err| { - Err(WalletError::BadParameter(format!( - "{}: Unable to open leader file: {}", - err, path - ))) - })?; - - serde_json::from_reader(file).or_else(|err| { - Err(WalletError::BadParameter(format!( - "{}: Failed to parse leader file: {}", - err, path - ))) - }) -} - -pub fn gen_keypair_file(outfile: String) -> Result> { - let rnd = SystemRandom::new(); - let pkcs8_bytes = Ed25519KeyPair::generate_pkcs8(&rnd)?; - let serialized = serde_json::to_string(&pkcs8_bytes.to_vec())?; - - if outfile != "-" { - if let Some(outdir) = Path::new(&outfile).parent() { - fs::create_dir_all(outdir)?; - } - let mut f = File::create(outfile)?; - f.write_all(&serialized.clone().into_bytes())?; - } - Ok(serialized) -} - -fn get_last_id(rpc_addr: &str) -> Result> { - let result = RpcRequest::GetLastId.make_rpc_request(&rpc_addr, 1, None)?; - if result.as_str().is_none() { - Err(WalletError::RpcRequestError( - "Received bad last_id".to_string(), - ))? - } - let last_id_str = result.as_str().unwrap(); - let last_id_vec = bs58::decode(last_id_str) - .into_vec() - .map_err(|_| WalletError::RpcRequestError("Received bad last_id".to_string()))?; - Ok(Hash::new(&last_id_vec)) -} - -fn send_tx(rpc_addr: &str, tx: &Transaction) -> Result> { - let serialized = serialize(tx).unwrap(); - let params = json!([serialized]); - let signature = RpcRequest::SendTransaction.make_rpc_request(&rpc_addr, 2, Some(params))?; - if signature.as_str().is_none() { - Err(WalletError::RpcRequestError( - "Received result of an unexpected type".to_string(), - ))? - } - Ok(signature.as_str().unwrap().to_string()) -} - -fn confirm_tx(rpc_addr: &str, signature: &str) -> Result> { - let params = json!([signature.to_string()]); - let signature_status = - RpcRequest::GetSignatureStatus.make_rpc_request(&rpc_addr, 1, Some(params))?; - if let Some(status) = signature_status.as_str() { - let rpc_status = RpcSignatureStatus::from_str(status).map_err(|_| { - WalletError::RpcRequestError("Unable to parse signature status".to_string()) - })?; - Ok(rpc_status) - } else { - Err(WalletError::RpcRequestError( - "Received result of an unexpected type".to_string(), - ))? - } -} - -fn send_and_confirm_tx(rpc_addr: &str, tx: &Transaction) -> Result<(), Box> { - let mut send_retries = 3; - while send_retries > 0 { - let mut status_retries = 4; - let signature_str = send_tx(rpc_addr, tx)?; - let status = loop { - let status = confirm_tx(rpc_addr, &signature_str)?; - if status == RpcSignatureStatus::SignatureNotFound { - status_retries -= 1; - if status_retries == 0 { - break status; - } - } else { - break status; - } - }; - match status { - RpcSignatureStatus::AccountInUse => { - send_retries -= 1; - } - RpcSignatureStatus::Confirmed => { - return Ok(()); - } - _ => { - return Err(WalletError::RpcRequestError(format!( - "Transaction {:?} failed: {:?}", - signature_str, status - )))?; - } - } - } - Err(WalletError::RpcRequestError(format!( - "AccountInUse after 3 retries: {:?}", - tx.account_keys[0] - )))? -} - -#[cfg(test)] -mod tests { - use super::*; - use bank::Bank; - use clap::{App, Arg, SubCommand}; - use cluster_info::Node; - use fullnode::Fullnode; - use leader_scheduler::LeaderScheduler; - use ledger::create_tmp_genesis; - use serde_json::Value; - use signature::{read_keypair, read_pkcs8, Keypair, KeypairUtil}; - use solana_drone::drone::run_local_drone; - use std::fs::remove_dir_all; - use std::sync::mpsc::channel; - use std::sync::{Arc, RwLock}; - use std::thread::sleep; - use std::time::Duration; - - #[test] - fn test_wallet_parse_command() { - let test_commands = App::new("test") - .subcommand(SubCommand::with_name("address").about("Get your public key")) - .subcommand( - SubCommand::with_name("airdrop") - .about("Request a batch of tokens") - .arg( - Arg::with_name("tokens") - .index(1) - .value_name("NUM") - .takes_value(true) - .required(true) - .help("The number of tokens to request"), - ), - ).subcommand(SubCommand::with_name("balance").about("Get your balance")) - .subcommand( - SubCommand::with_name("cancel") - .about("Cancel a transfer") - .arg( - Arg::with_name("process-id") - .index(1) - .value_name("PROCESS_ID") - .takes_value(true) - .required(true) - .help("The process id of the transfer to cancel"), - ), - ).subcommand( - SubCommand::with_name("confirm") - .about("Confirm transaction by signature") - .arg( - Arg::with_name("signature") - .index(1) - .value_name("SIGNATURE") - .takes_value(true) - .required(true) - .help("The transaction signature to confirm"), - ), - ).subcommand( - SubCommand::with_name("deploy") - .about("Deploy a program") - .arg( - Arg::with_name("program-location") - .index(1) - .value_name("PATH") - .takes_value(true) - .required(true) - .help("/path/to/program.o"), - ), // TODO: Add "loader" argument; current default is bpf_loader - ).subcommand( - SubCommand::with_name("get-transaction-count") - .about("Get current transaction count"), - ).subcommand( - SubCommand::with_name("pay") - .about("Send a payment") - .arg( - Arg::with_name("to") - .index(1) - .value_name("PUBKEY") - .takes_value(true) - .required(true) - .help("The pubkey of recipient"), - ).arg( - Arg::with_name("tokens") - .index(2) - .value_name("NUM") - .takes_value(true) - .required(true) - .help("The number of tokens to send"), - ).arg( - Arg::with_name("timestamp") - .long("after") - .value_name("DATETIME") - .takes_value(true) - .help("A timestamp after which transaction will execute"), - ).arg( - Arg::with_name("timestamp-pubkey") - .long("require-timestamp-from") - .value_name("PUBKEY") - .takes_value(true) - .requires("timestamp") - .help("Require timestamp from this third party"), - ).arg( - Arg::with_name("witness") - .long("require-signature-from") - .value_name("PUBKEY") - .takes_value(true) - .multiple(true) - .use_delimiter(true) - .help("Any third party signatures required to unlock the tokens"), - ).arg( - Arg::with_name("cancelable") - .long("cancelable") - .takes_value(false), - ), - ).subcommand( - SubCommand::with_name("send-signature") - .about("Send a signature to authorize a transfer") - .arg( - Arg::with_name("to") - .index(1) - .value_name("PUBKEY") - .takes_value(true) - .required(true) - .help("The pubkey of recipient"), - ).arg( - Arg::with_name("process-id") - .index(2) - .value_name("PROCESS_ID") - .takes_value(true) - .required(true) - .help("The process id of the transfer to authorize"), - ), - ).subcommand( - SubCommand::with_name("send-timestamp") - .about("Send a timestamp to unlock a transfer") - .arg( - Arg::with_name("to") - .index(1) - .value_name("PUBKEY") - .takes_value(true) - .required(true) - .help("The pubkey of recipient"), - ).arg( - Arg::with_name("process-id") - .index(2) - .value_name("PROCESS_ID") - .takes_value(true) - .required(true) - .help("The process id of the transfer to unlock"), - ).arg( - Arg::with_name("datetime") - .long("date") - .value_name("DATETIME") - .takes_value(true) - .help("Optional arbitrary timestamp to apply"), - ), - ); - let pubkey = Keypair::new().pubkey(); - let pubkey_string = format!("{}", pubkey); - let witness0 = Keypair::new().pubkey(); - let witness0_string = format!("{}", witness0); - let witness1 = Keypair::new().pubkey(); - let witness1_string = format!("{}", witness1); - let dt = Utc.ymd(2018, 9, 19).and_hms(17, 30, 59); - - // Test Airdrop Subcommand - let test_airdrop = test_commands - .clone() - .get_matches_from(vec!["test", "airdrop", "50"]); - assert_eq!( - parse_command(pubkey, &test_airdrop).unwrap(), - WalletCommand::AirDrop(50) - ); - let test_bad_airdrop = test_commands - .clone() - .get_matches_from(vec!["test", "airdrop", "notint"]); - assert!(parse_command(pubkey, &test_bad_airdrop).is_err()); - - // Test Cancel Subcommand - let test_cancel = - test_commands - .clone() - .get_matches_from(vec!["test", "cancel", &pubkey_string]); - assert_eq!( - parse_command(pubkey, &test_cancel).unwrap(), - WalletCommand::Cancel(pubkey) - ); - - // Test Confirm Subcommand - let signature = Signature::new(&vec![1; 64]); - let signature_string = format!("{:?}", signature); - let test_confirm = - test_commands - .clone() - .get_matches_from(vec!["test", "confirm", &signature_string]); - assert_eq!( - parse_command(pubkey, &test_confirm).unwrap(), - WalletCommand::Confirm(signature) - ); - let test_bad_signature = test_commands - .clone() - .get_matches_from(vec!["test", "confirm", "deadbeef"]); - assert!(parse_command(pubkey, &test_bad_signature).is_err()); - - // Test Deploy Subcommand - let test_deploy = - test_commands - .clone() - .get_matches_from(vec!["test", "deploy", "/Users/test/program.o"]); - assert_eq!( - parse_command(pubkey, &test_deploy).unwrap(), - WalletCommand::Deploy("/Users/test/program.o".to_string()) - ); - - // Test Simple Pay Subcommand - let test_pay = - test_commands - .clone() - .get_matches_from(vec!["test", "pay", &pubkey_string, "50"]); - assert_eq!( - parse_command(pubkey, &test_pay).unwrap(), - WalletCommand::Pay(50, pubkey, None, None, None, None) - ); - let test_bad_pubkey = test_commands - .clone() - .get_matches_from(vec!["test", "pay", "deadbeef", "50"]); - assert!(parse_command(pubkey, &test_bad_pubkey).is_err()); - - // Test Pay Subcommand w/ Witness - let test_pay_multiple_witnesses = test_commands.clone().get_matches_from(vec![ - "test", - "pay", - &pubkey_string, - "50", - "--require-signature-from", - &witness0_string, - "--require-signature-from", - &witness1_string, - ]); - assert_eq!( - parse_command(pubkey, &test_pay_multiple_witnesses).unwrap(), - WalletCommand::Pay(50, pubkey, None, None, Some(vec![witness0, witness1]), None) - ); - let test_pay_single_witness = test_commands.clone().get_matches_from(vec![ - "test", - "pay", - &pubkey_string, - "50", - "--require-signature-from", - &witness0_string, - ]); - assert_eq!( - parse_command(pubkey, &test_pay_single_witness).unwrap(), - WalletCommand::Pay(50, pubkey, None, None, Some(vec![witness0]), None) - ); - - // Test Pay Subcommand w/ Timestamp - let test_pay_timestamp = test_commands.clone().get_matches_from(vec![ - "test", - "pay", - &pubkey_string, - "50", - "--after", - "2018-09-19T17:30:59", - "--require-timestamp-from", - &witness0_string, - ]); - assert_eq!( - parse_command(pubkey, &test_pay_timestamp).unwrap(), - WalletCommand::Pay(50, pubkey, Some(dt), Some(witness0), None, None) - ); - - // Test Send-Signature Subcommand - let test_send_signature = test_commands.clone().get_matches_from(vec![ - "test", - "send-signature", - &pubkey_string, - &pubkey_string, - ]); - assert_eq!( - parse_command(pubkey, &test_send_signature).unwrap(), - WalletCommand::Witness(pubkey, pubkey) - ); - let test_pay_multiple_witnesses = test_commands.clone().get_matches_from(vec![ - "test", - "pay", - &pubkey_string, - "50", - "--after", - "2018-09-19T17:30:59", - "--require-signature-from", - &witness0_string, - "--require-timestamp-from", - &witness0_string, - "--require-signature-from", - &witness1_string, - ]); - assert_eq!( - parse_command(pubkey, &test_pay_multiple_witnesses).unwrap(), - WalletCommand::Pay( - 50, - pubkey, - Some(dt), - Some(witness0), - Some(vec![witness0, witness1]), - None - ) - ); - - // Test Send-Timestamp Subcommand - let test_send_timestamp = test_commands.clone().get_matches_from(vec![ - "test", - "send-timestamp", - &pubkey_string, - &pubkey_string, - "--date", - "2018-09-19T17:30:59", - ]); - assert_eq!( - parse_command(pubkey, &test_send_timestamp).unwrap(), - WalletCommand::TimeElapsed(pubkey, pubkey, dt) - ); - let test_bad_timestamp = test_commands.clone().get_matches_from(vec![ - "test", - "send-timestamp", - &pubkey_string, - &pubkey_string, - "--date", - "20180919T17:30:59", - ]); - assert!(parse_command(pubkey, &test_bad_timestamp).is_err()); - } - #[test] - #[ignore] - fn test_wallet_process_command() { - let bob_pubkey = Keypair::new().pubkey(); - - let leader_keypair = Arc::new(Keypair::new()); - let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - let leader_data = leader.info.clone(); - let (alice, ledger_path) = - create_tmp_genesis("wallet_process_command", 10_000_000, leader_data.id, 1000); - let mut bank = Bank::new(&alice); - - let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( - leader_data.id, - ))); - bank.leader_scheduler = leader_scheduler; - let vote_account_keypair = Arc::new(Keypair::new()); - let last_id = bank.last_id(); - - let server = Fullnode::new_with_bank( - leader_keypair, - vote_account_keypair, - bank, - 0, - &last_id, - leader, - None, - &ledger_path, - false, - None, - ); - sleep(Duration::from_millis(900)); - - let (sender, receiver) = channel(); - run_local_drone(alice.keypair(), sender); - let drone_addr = receiver.recv().unwrap(); - - let mut config = WalletConfig::default(); - config.network = leader_data.ncp; - config.drone_port = Some(drone_addr.port()); - - let tokens = 50; - config.command = WalletCommand::AirDrop(tokens); - assert_eq!( - process_command(&config).unwrap(), - format!("Your balance is: {:?}", tokens) - ); - - config.command = WalletCommand::Balance; - assert_eq!( - process_command(&config).unwrap(), - format!("Your balance is: {:?}", tokens) - ); - - config.command = WalletCommand::Pay(10, bob_pubkey, None, None, None, None); - let sig_response = process_command(&config); - assert!(sig_response.is_ok()); - - let signatures = bs58::decode(sig_response.unwrap()) - .into_vec() - .expect("base58-encoded signature"); - let signature = Signature::new(&signatures); - config.command = WalletCommand::Confirm(signature); - assert_eq!(process_command(&config).unwrap(), "Confirmed"); - - config.command = WalletCommand::Balance; - assert_eq!( - process_command(&config).unwrap(), - format!("Your balance is: {:?}", tokens - 10) - ); - - server.close().unwrap(); - remove_dir_all(ledger_path).unwrap(); - } - #[test] - #[ignore] - fn test_wallet_request_airdrop() { - let leader_keypair = Arc::new(Keypair::new()); - let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - let leader_data = leader.info.clone(); - let (alice, ledger_path) = - create_tmp_genesis("wallet_request_airdrop", 10_000_000, leader_data.id, 1000); - let mut bank = Bank::new(&alice); - - let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( - leader_data.id, - ))); - bank.leader_scheduler = leader_scheduler; - let vote_account_keypair = Arc::new(Keypair::new()); - let last_id = bank.last_id(); - let entry_height = alice.create_entries().len() as u64; - let server = Fullnode::new_with_bank( - leader_keypair, - vote_account_keypair, - bank, - entry_height, - &last_id, - leader, - None, - &ledger_path, - false, - None, - ); - sleep(Duration::from_millis(900)); - - let (sender, receiver) = channel(); - run_local_drone(alice.keypair(), sender); - let drone_addr = receiver.recv().unwrap(); - - let mut bob_config = WalletConfig::default(); - bob_config.network = leader_data.ncp; - bob_config.drone_port = Some(drone_addr.port()); - bob_config.command = WalletCommand::AirDrop(50); - - let sig_response = process_command(&bob_config); - assert!(sig_response.is_ok()); - - let rpc_addr = format!("http://{}", leader_data.rpc.to_string()); - let params = json!([format!("{}", bob_config.id.pubkey())]); - let balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(balance, 50); - - server.close().unwrap(); - remove_dir_all(ledger_path).unwrap(); - } - - fn tmp_file_path(name: &str) -> String { - use std::env; - let out_dir = env::var("OUT_DIR").unwrap_or_else(|_| "target".to_string()); - let keypair = Keypair::new(); - - format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey()).to_string() - } - - #[test] - fn test_wallet_gen_keypair_file() { - let outfile = tmp_file_path("test_gen_keypair_file.json"); - let serialized_keypair = gen_keypair_file(outfile.to_string()).unwrap(); - let keypair_vec: Vec = serde_json::from_str(&serialized_keypair).unwrap(); - assert!(Path::new(&outfile).exists()); - assert_eq!(keypair_vec, read_pkcs8(&outfile).unwrap()); - assert!(read_keypair(&outfile).is_ok()); - assert_eq!( - read_keypair(&outfile).unwrap().pubkey().as_ref().len(), - mem::size_of::() - ); - fs::remove_file(&outfile).unwrap(); - assert!(!Path::new(&outfile).exists()); - } - #[test] - #[ignore] - fn test_wallet_timestamp_tx() { - let bob_pubkey = Keypair::new().pubkey(); - - let leader_keypair = Arc::new(Keypair::new()); - let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - let leader_data = leader.info.clone(); - let (alice, ledger_path) = - create_tmp_genesis("wallet_timestamp_tx", 10_000_000, leader_data.id, 1000); - let mut bank = Bank::new(&alice); - - let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( - leader_data.id, - ))); - bank.leader_scheduler = leader_scheduler; - let vote_account_keypair = Arc::new(Keypair::new()); - let last_id = bank.last_id(); - let server = Fullnode::new_with_bank( - leader_keypair, - vote_account_keypair, - bank, - 0, - &last_id, - leader, - None, - &ledger_path, - false, - None, - ); - sleep(Duration::from_millis(900)); - - let (sender, receiver) = channel(); - run_local_drone(alice.keypair(), sender); - let drone_addr = receiver.recv().unwrap(); - - let rpc_addr = format!("http://{}", leader_data.rpc.to_string()); - - let mut config_payer = WalletConfig::default(); - config_payer.network = leader_data.ncp; - config_payer.drone_port = Some(drone_addr.port()); - - let mut config_witness = WalletConfig::default(); - config_witness.network = leader_data.ncp; - config_witness.drone_port = Some(drone_addr.port()); - - assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey()); - - let last_id = get_last_id(&rpc_addr).unwrap(); - let transaction = - request_airdrop_transaction(&drone_addr, &config_payer.id.pubkey(), 50, last_id) - .unwrap(); - send_and_confirm_tx(&rpc_addr, &transaction).unwrap(); - - // Make transaction (from config_payer to bob_pubkey) requiring timestamp from config_witness - let date_string = "\"2018-09-19T17:30:59Z\""; - let dt: DateTime = serde_json::from_str(&date_string).unwrap(); - config_payer.command = WalletCommand::Pay( - 10, - bob_pubkey, - Some(dt), - Some(config_witness.id.pubkey()), - None, - None, - ); - let sig_response = process_command(&config_payer); - assert!(sig_response.is_ok()); - - let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap(); - let process_id_str = object.get("processId").unwrap().as_str().unwrap(); - let process_id_vec = bs58::decode(process_id_str) - .into_vec() - .expect("base58-encoded public key"); - let process_id = Pubkey::new(&process_id_vec); - - let params = json!([format!("{}", config_payer.id.pubkey())]); - let config_payer_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(config_payer_balance, 39); - let params = json!([format!("{}", process_id)]); - let contract_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(contract_balance, 11); - let params = json!([format!("{}", bob_pubkey)]); - let recipient_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(recipient_balance, 0); - - // Sign transaction by config_witness - config_witness.command = WalletCommand::TimeElapsed(bob_pubkey, process_id, dt); - let sig_response = process_command(&config_witness); - assert!(sig_response.is_ok()); - - let params = json!([format!("{}", config_payer.id.pubkey())]); - let config_payer_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(config_payer_balance, 39); - let params = json!([format!("{}", process_id)]); - let contract_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(contract_balance, 1); - let params = json!([format!("{}", bob_pubkey)]); - let recipient_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(recipient_balance, 10); - - server.close().unwrap(); - remove_dir_all(ledger_path).unwrap(); - } - #[test] - #[ignore] - fn test_wallet_witness_tx() { - let bob_pubkey = Keypair::new().pubkey(); - let leader_keypair = Arc::new(Keypair::new()); - let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - let leader_data = leader.info.clone(); - let (alice, ledger_path) = - create_tmp_genesis("wallet_witness_tx", 10_000_000, leader_data.id, 1000); - let mut bank = Bank::new(&alice); - - let mut config_payer = WalletConfig::default(); - let mut config_witness = WalletConfig::default(); - let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( - leader_data.id, - ))); - bank.leader_scheduler = leader_scheduler; - let vote_account_keypair = Arc::new(Keypair::new()); - let last_id = bank.last_id(); - let server = Fullnode::new_with_bank( - leader_keypair, - vote_account_keypair, - bank, - 0, - &last_id, - leader, - None, - &ledger_path, - false, - None, - ); - sleep(Duration::from_millis(900)); - - let (sender, receiver) = channel(); - run_local_drone(alice.keypair(), sender); - let drone_addr = receiver.recv().unwrap(); - - let rpc_addr = format!("http://{}", leader_data.rpc.to_string()); - - assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey()); - - let last_id = get_last_id(&rpc_addr).unwrap(); - let transaction = - request_airdrop_transaction(&drone_addr, &config_payer.id.pubkey(), 50, last_id) - .unwrap(); - send_and_confirm_tx(&rpc_addr, &transaction).unwrap(); - - // Make transaction (from config_payer to bob_pubkey) requiring witness signature from config_witness - config_payer.command = WalletCommand::Pay( - 10, - bob_pubkey, - None, - None, - Some(vec![config_witness.id.pubkey()]), - None, - ); - let sig_response = process_command(&config_payer); - assert!(sig_response.is_ok()); - - let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap(); - let process_id_str = object.get("processId").unwrap().as_str().unwrap(); - let process_id_vec = bs58::decode(process_id_str) - .into_vec() - .expect("base58-encoded public key"); - let process_id = Pubkey::new(&process_id_vec); - - let params = json!([format!("{}", config_payer.id.pubkey())]); - let config_payer_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(config_payer_balance, 39); - let params = json!([format!("{}", process_id)]); - let contract_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(contract_balance, 11); - let params = json!([format!("{}", bob_pubkey)]); - let recipient_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(recipient_balance, 0); - - // Sign transaction by config_witness - config_witness.command = WalletCommand::Witness(bob_pubkey, process_id); - let sig_response = process_command(&config_witness); - assert!(sig_response.is_ok()); - - let params = json!([format!("{}", config_payer.id.pubkey())]); - let config_payer_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(config_payer_balance, 39); - let params = json!([format!("{}", process_id)]); - let contract_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(contract_balance, 1); - let params = json!([format!("{}", bob_pubkey)]); - let recipient_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(recipient_balance, 10); - - server.close().unwrap(); - remove_dir_all(ledger_path).unwrap(); - } - #[test] - #[ignore] - fn test_wallet_cancel_tx() { - let bob_pubkey = Keypair::new().pubkey(); - let leader_keypair = Arc::new(Keypair::new()); - let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - let leader_data = leader.info.clone(); - - let (alice, ledger_path) = - create_tmp_genesis("wallet_cancel_tx", 10_000_000, leader_data.id, 1000); - let mut bank = Bank::new(&alice); - - let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( - leader_data.id, - ))); - bank.leader_scheduler = leader_scheduler; - let vote_account_keypair = Arc::new(Keypair::new()); - let last_id = bank.last_id(); - let server = Fullnode::new_with_bank( - leader_keypair, - vote_account_keypair, - bank, - 0, - &last_id, - leader, - None, - &ledger_path, - false, - None, - ); - sleep(Duration::from_millis(900)); - - let (sender, receiver) = channel(); - run_local_drone(alice.keypair(), sender); - let drone_addr = receiver.recv().unwrap(); - - let rpc_addr = format!("http://{}", leader_data.rpc.to_string()); - - let mut config_payer = WalletConfig::default(); - config_payer.network = leader_data.ncp; - config_payer.drone_port = Some(drone_addr.port()); - - let mut config_witness = WalletConfig::default(); - config_witness.network = leader_data.ncp; - config_witness.drone_port = Some(drone_addr.port()); - - assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey()); - - let last_id = get_last_id(&rpc_addr).unwrap(); - let transaction = - request_airdrop_transaction(&drone_addr, &config_payer.id.pubkey(), 50, last_id) - .unwrap(); - send_and_confirm_tx(&rpc_addr, &transaction).unwrap(); - - // Make transaction (from config_payer to bob_pubkey) requiring witness signature from config_witness - config_payer.command = WalletCommand::Pay( - 10, - bob_pubkey, - None, - None, - Some(vec![config_witness.id.pubkey()]), - Some(config_payer.id.pubkey()), - ); - let sig_response = process_command(&config_payer); - assert!(sig_response.is_ok()); - - let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap(); - let process_id_str = object.get("processId").unwrap().as_str().unwrap(); - let process_id_vec = bs58::decode(process_id_str) - .into_vec() - .expect("base58-encoded public key"); - let process_id = Pubkey::new(&process_id_vec); - - let params = json!([format!("{}", config_payer.id.pubkey())]); - let config_payer_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(config_payer_balance, 39); - let params = json!([format!("{}", process_id)]); - let contract_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(contract_balance, 11); - let params = json!([format!("{}", bob_pubkey)]); - let recipient_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(recipient_balance, 0); - - // Sign transaction by config_witness - config_payer.command = WalletCommand::Cancel(process_id); - let sig_response = process_command(&config_payer); - assert!(sig_response.is_ok()); - - let params = json!([format!("{}", config_payer.id.pubkey())]); - let config_payer_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(config_payer_balance, 49); - let params = json!([format!("{}", process_id)]); - let contract_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(contract_balance, 1); - let params = json!([format!("{}", bob_pubkey)]); - let recipient_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_addr, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(recipient_balance, 0); - - server.close().unwrap(); - remove_dir_all(ledger_path).unwrap(); - } -} diff --git a/book/window.rs b/book/window.rs deleted file mode 100644 index bafdead363617f..00000000000000 --- a/book/window.rs +++ /dev/null @@ -1,555 +0,0 @@ -//! The `window` module defines data structure for storing the tail of the ledger. -//! -use cluster_info::ClusterInfo; -use counter::Counter; -use entry::reconstruct_entries_from_blobs; -use entry::Entry; -#[cfg(feature = "erasure")] -use erasure; -use leader_scheduler::LeaderScheduler; -use log::Level; -use packet::SharedBlob; -use solana_sdk::pubkey::Pubkey; -use std::cmp; -use std::mem; -use std::net::SocketAddr; -use std::sync::atomic::AtomicUsize; -use std::sync::{Arc, RwLock}; - -#[derive(Default, Clone)] -pub struct WindowSlot { - pub data: Option, - pub coding: Option, - pub leader_unknown: bool, -} - -impl WindowSlot { - fn blob_index(&self) -> Option { - match self.data { - Some(ref blob) => blob.read().unwrap().index().ok(), - None => None, - } - } - - fn clear_data(&mut self) { - self.data.take(); - } -} - -type Window = Vec; -pub type SharedWindow = Arc>; - -#[derive(Debug)] -pub struct WindowIndex { - pub data: u64, - pub coding: u64, -} - -pub trait WindowUtil { - /// Finds available slots, clears them, and returns their indices. - fn clear_slots(&mut self, consumed: u64, received: u64) -> Vec; - - fn window_size(&self) -> u64; - - fn repair( - &mut self, - cluster_info: &Arc>, - id: &Pubkey, - times: usize, - consumed: u64, - received: u64, - tick_height: u64, - max_entry_height: u64, - leader_scheduler_option: &Arc>, - ) -> Vec<(SocketAddr, Vec)>; - - fn print(&self, id: &Pubkey, consumed: u64) -> String; - - fn process_blob( - &mut self, - id: &Pubkey, - blob: SharedBlob, - pix: u64, - consume_queue: &mut Vec, - consumed: &mut u64, - tick_height: &mut u64, - leader_unknown: bool, - pending_retransmits: &mut bool, - ); - - fn blob_idx_in_window(&self, id: &Pubkey, pix: u64, consumed: u64, received: &mut u64) -> bool; -} - -impl WindowUtil for Window { - fn clear_slots(&mut self, consumed: u64, received: u64) -> Vec { - (consumed..received) - .filter_map(|pix| { - let i = (pix % self.window_size()) as usize; - if let Some(blob_idx) = self[i].blob_index() { - if blob_idx == pix { - return None; - } - } - self[i].clear_data(); - Some(pix) - }).collect() - } - - fn blob_idx_in_window(&self, id: &Pubkey, pix: u64, consumed: u64, received: &mut u64) -> bool { - // Prevent receive window from running over - // Got a blob which has already been consumed, skip it - // probably from a repair window request - if pix < consumed { - trace!( - "{}: received: {} but older than consumed: {} skipping..", - id, - pix, - consumed - ); - false - } else { - // received always has to be updated even if we don't accept the packet into - // the window. The worst case here is the server *starts* outside - // the window, none of the packets it receives fits in the window - // and repair requests (which are based on received) are never generated - *received = cmp::max(pix, *received); - - if pix >= consumed + self.window_size() { - trace!( - "{}: received: {} will overrun window: {} skipping..", - id, - pix, - consumed + self.window_size() - ); - false - } else { - true - } - } - } - - fn window_size(&self) -> u64 { - self.len() as u64 - } - - fn repair( - &mut self, - cluster_info: &Arc>, - id: &Pubkey, - times: usize, - consumed: u64, - received: u64, - tick_height: u64, - max_entry_height: u64, - leader_scheduler_option: &Arc>, - ) -> Vec<(SocketAddr, Vec)> { - let rcluster_info = cluster_info.read().unwrap(); - let mut is_next_leader = false; - { - let ls_lock = leader_scheduler_option.read().unwrap(); - if !ls_lock.use_only_bootstrap_leader { - // Calculate the next leader rotation height and check if we are the leader - if let Some(next_leader_rotation_height) = - ls_lock.max_height_for_leader(tick_height) - { - match ls_lock.get_scheduled_leader(next_leader_rotation_height) { - Some((leader_id, _)) if leader_id == *id => is_next_leader = true, - // In the case that we are not in the current scope of the leader schedule - // window then either: - // - // 1) The replicate stage hasn't caught up to the "consumed" entries we sent, - // in which case it will eventually catch up - // - // 2) We are on the border between seed_rotation_intervals, so the - // schedule won't be known until the entry on that cusp is received - // by the replicate stage (which comes after this stage). Hence, the next - // leader at the beginning of that next epoch will not know they are the - // leader until they receive that last "cusp" entry. The leader also won't ask for repairs - // for that entry because "is_next_leader" won't be set here. In this case, - // everybody will be blocking waiting for that "cusp" entry instead of repairing, - // until the leader hits "times" >= the max times in calculate_max_repair(). - // The impact of this, along with the similar problem from broadcast for the transitioning - // leader, can be observed in the multinode test, test_full_leader_validator_network(), - None => (), - _ => (), - } - } - } - } - - let num_peers = rcluster_info.tvu_peers().len() as u64; - let max_repair = if max_entry_height == 0 { - calculate_max_repair( - num_peers, - consumed, - received, - times, - is_next_leader, - self.window_size(), - ) - } else { - max_entry_height + 1 - }; - - let idxs = self.clear_slots(consumed, max_repair); - let reqs: Vec<_> = idxs - .into_iter() - .filter_map(|pix| rcluster_info.window_index_request(pix).ok()) - .collect(); - - drop(rcluster_info); - - inc_new_counter_info!("streamer-repair_window-repair", reqs.len()); - - if log_enabled!(Level::Trace) { - trace!( - "{}: repair_window counter times: {} consumed: {} received: {} max_repair: {} missing: {}", - id, - times, - consumed, - received, - max_repair, - reqs.len() - ); - for (to, _) in &reqs { - trace!("{}: repair_window request to {}", id, to); - } - } - reqs - } - - fn print(&self, id: &Pubkey, consumed: u64) -> String { - let pointer: Vec<_> = self - .iter() - .enumerate() - .map(|(i, _v)| { - if i == (consumed % self.window_size()) as usize { - "V" - } else { - " " - } - }).collect(); - - let buf: Vec<_> = self - .iter() - .map(|v| { - if v.data.is_none() && v.coding.is_none() { - "O" - } else if v.data.is_some() && v.coding.is_some() { - "D" - } else if v.data.is_some() { - // coding.is_none() - "d" - } else { - // data.is_none() - "c" - } - }).collect(); - format!( - "\n{}: WINDOW ({}): {}\n{}: WINDOW ({}): {}", - id, - consumed, - pointer.join(""), - id, - consumed, - buf.join("") - ) - } - - /// process a blob: Add blob to the window. If a continuous set of blobs - /// starting from consumed is thereby formed, add that continuous - /// range of blobs to a queue to be sent on to the next stage. - /// - /// * `self` - the window we're operating on - /// * `id` - this node's id - /// * `blob` - the blob to be processed into the window and rebroadcast - /// * `pix` - the index of the blob, corresponds to - /// the entry height of this blob - /// * `consume_queue` - output, blobs to be rebroadcast are placed here - /// * `consumed` - input/output, the entry-height to which this - /// node has populated and rebroadcast entries - fn process_blob( - &mut self, - id: &Pubkey, - blob: SharedBlob, - pix: u64, - consume_queue: &mut Vec, - consumed: &mut u64, - tick_height: &mut u64, - leader_unknown: bool, - pending_retransmits: &mut bool, - ) { - let w = (pix % self.window_size()) as usize; - - let is_coding = blob.read().unwrap().is_coding(); - - // insert a newly received blob into a window slot, clearing out and recycling any previous - // blob unless the incoming blob is a duplicate (based on idx) - // returns whether the incoming is a duplicate blob - fn insert_blob_is_dup( - id: &Pubkey, - blob: SharedBlob, - pix: u64, - window_slot: &mut Option, - c_or_d: &str, - ) -> bool { - if let Some(old) = mem::replace(window_slot, Some(blob)) { - let is_dup = old.read().unwrap().index().unwrap() == pix; - trace!( - "{}: occupied {} window slot {:}, is_dup: {}", - id, - c_or_d, - pix, - is_dup - ); - is_dup - } else { - trace!("{}: empty {} window slot {:}", id, c_or_d, pix); - false - } - } - - // insert the new blob into the window, overwrite and recycle old (or duplicate) entry - let is_duplicate = if is_coding { - insert_blob_is_dup(id, blob, pix, &mut self[w].coding, "coding") - } else { - insert_blob_is_dup(id, blob, pix, &mut self[w].data, "data") - }; - - if is_duplicate { - return; - } - - self[w].leader_unknown = leader_unknown; - *pending_retransmits = true; - - #[cfg(feature = "erasure")] - { - let window_size = self.window_size(); - if erasure::recover(id, self, *consumed, (*consumed % window_size) as usize).is_err() { - trace!("{}: erasure::recover failed", id); - } - } - - // push all contiguous blobs into consumed queue, increment consumed - loop { - let k = (*consumed % self.window_size()) as usize; - trace!("{}: k: {} consumed: {}", id, k, *consumed,); - - let k_data_blob; - let k_data_slot = &mut self[k].data; - if let Some(blob) = k_data_slot { - if blob.read().unwrap().index().unwrap() < *consumed { - // window wrap-around, end of received - break; - } - k_data_blob = (*blob).clone(); - } else { - // self[k].data is None, end of received - break; - } - - // Check that we can get the entries from this blob - match reconstruct_entries_from_blobs(vec![k_data_blob]) { - Ok((entries, num_ticks)) => { - consume_queue.extend(entries); - *tick_height += num_ticks; - } - Err(_) => { - // If the blob can't be deserialized, then remove it from the - // window and exit. *k_data_slot cannot be None at this point, - // so it's safe to unwrap. - k_data_slot.take(); - break; - } - } - - *consumed += 1; - } - } -} - -fn calculate_max_repair( - num_peers: u64, - consumed: u64, - received: u64, - times: usize, - is_next_leader: bool, - window_size: u64, -) -> u64 { - // Calculate the highest blob index that this node should have already received - // via avalanche. The avalanche splits data stream into nodes and each node retransmits - // the data to their peer nodes. So there's a possibility that a blob (with index lower - // than current received index) is being retransmitted by a peer node. - let max_repair = if times >= 8 || is_next_leader { - // if repair backoff is getting high, or if we are the next leader, - // don't wait for avalanche - cmp::max(consumed, received) - } else { - cmp::max(consumed, received.saturating_sub(num_peers)) - }; - - // This check prevents repairing a blob that will cause window to roll over. Even if - // the highes_lost blob is actually missing, asking to repair it might cause our - // current window to move past other missing blobs - cmp::min(consumed + window_size - 1, max_repair) -} - -pub fn new_window(window_size: usize) -> Window { - (0..window_size).map(|_| WindowSlot::default()).collect() -} - -pub fn default_window() -> Window { - (0..2048).map(|_| WindowSlot::default()).collect() -} - -#[cfg(test)] -mod test { - use packet::{Blob, Packet, Packets, SharedBlob, PACKET_DATA_SIZE}; - use solana_sdk::pubkey::Pubkey; - use std::io; - use std::io::Write; - use std::net::UdpSocket; - use std::sync::atomic::{AtomicBool, Ordering}; - use std::sync::mpsc::channel; - use std::sync::Arc; - use std::time::Duration; - use streamer::{receiver, responder, PacketReceiver}; - use window::{calculate_max_repair, new_window, Window, WindowUtil}; - - fn get_msgs(r: PacketReceiver, num: &mut usize) { - for _t in 0..5 { - let timer = Duration::new(1, 0); - match r.recv_timeout(timer) { - Ok(m) => *num += m.read().unwrap().packets.len(), - e => info!("error {:?}", e), - } - if *num == 10 { - break; - } - } - } - #[test] - pub fn streamer_debug() { - write!(io::sink(), "{:?}", Packet::default()).unwrap(); - write!(io::sink(), "{:?}", Packets::default()).unwrap(); - write!(io::sink(), "{:?}", Blob::default()).unwrap(); - } - #[test] - pub fn streamer_send_test() { - let read = UdpSocket::bind("127.0.0.1:0").expect("bind"); - read.set_read_timeout(Some(Duration::new(1, 0))).unwrap(); - - let addr = read.local_addr().unwrap(); - let send = UdpSocket::bind("127.0.0.1:0").expect("bind"); - let exit = Arc::new(AtomicBool::new(false)); - let (s_reader, r_reader) = channel(); - let t_receiver = receiver( - Arc::new(read), - exit.clone(), - s_reader, - "window-streamer-test", - ); - let t_responder = { - let (s_responder, r_responder) = channel(); - let t_responder = responder("streamer_send_test", Arc::new(send), r_responder); - let mut msgs = Vec::new(); - for i in 0..10 { - let mut b = SharedBlob::default(); - { - let mut w = b.write().unwrap(); - w.data[0] = i as u8; - w.meta.size = PACKET_DATA_SIZE; - w.meta.set_addr(&addr); - } - msgs.push(b); - } - s_responder.send(msgs).expect("send"); - t_responder - }; - - let mut num = 0; - get_msgs(r_reader, &mut num); - assert_eq!(num, 10); - exit.store(true, Ordering::Relaxed); - t_receiver.join().expect("join"); - t_responder.join().expect("join"); - } - - #[test] - pub fn test_calculate_max_repair() { - const WINDOW_SIZE: u64 = 200; - - assert_eq!(calculate_max_repair(0, 10, 90, 0, false, WINDOW_SIZE), 90); - assert_eq!(calculate_max_repair(15, 10, 90, 32, false, WINDOW_SIZE), 90); - assert_eq!(calculate_max_repair(15, 10, 90, 0, false, WINDOW_SIZE), 75); - assert_eq!(calculate_max_repair(90, 10, 90, 0, false, WINDOW_SIZE), 10); - assert_eq!(calculate_max_repair(90, 10, 50, 0, false, WINDOW_SIZE), 10); - assert_eq!(calculate_max_repair(90, 10, 99, 0, false, WINDOW_SIZE), 10); - assert_eq!(calculate_max_repair(90, 10, 101, 0, false, WINDOW_SIZE), 11); - assert_eq!( - calculate_max_repair(90, 10, 95 + WINDOW_SIZE, 0, false, WINDOW_SIZE), - WINDOW_SIZE + 5 - ); - assert_eq!( - calculate_max_repair(90, 10, 99 + WINDOW_SIZE, 0, false, WINDOW_SIZE), - WINDOW_SIZE + 9 - ); - assert_eq!( - calculate_max_repair(90, 10, 100 + WINDOW_SIZE, 0, false, WINDOW_SIZE), - WINDOW_SIZE + 9 - ); - assert_eq!( - calculate_max_repair(90, 10, 120 + WINDOW_SIZE, 0, false, WINDOW_SIZE), - WINDOW_SIZE + 9 - ); - assert_eq!( - calculate_max_repair(50, 100, 50 + WINDOW_SIZE, 0, false, WINDOW_SIZE), - WINDOW_SIZE - ); - assert_eq!( - calculate_max_repair(50, 100, 50 + WINDOW_SIZE, 0, true, WINDOW_SIZE), - 50 + WINDOW_SIZE - ); - } - - fn wrap_blob_idx_in_window( - window: &Window, - id: &Pubkey, - pix: u64, - consumed: u64, - received: u64, - ) -> (bool, u64) { - let mut received = received; - let is_in_window = window.blob_idx_in_window(&id, pix, consumed, &mut received); - (is_in_window, received) - } - #[test] - pub fn test_blob_idx_in_window() { - let id = Pubkey::default(); - const WINDOW_SIZE: u64 = 200; - let window = new_window(WINDOW_SIZE as usize); - - assert_eq!( - wrap_blob_idx_in_window(&window, &id, 90 + WINDOW_SIZE, 90, 100), - (false, 90 + WINDOW_SIZE) - ); - assert_eq!( - wrap_blob_idx_in_window(&window, &id, 91 + WINDOW_SIZE, 90, 100), - (false, 91 + WINDOW_SIZE) - ); - assert_eq!( - wrap_blob_idx_in_window(&window, &id, 89, 90, 100), - (false, 100) - ); - - assert_eq!( - wrap_blob_idx_in_window(&window, &id, 91, 90, 100), - (true, 100) - ); - assert_eq!( - wrap_blob_idx_in_window(&window, &id, 101, 90, 100), - (true, 101) - ); - } -} diff --git a/book/window_service.rs b/book/window_service.rs deleted file mode 100644 index abfc9293bb13f0..00000000000000 --- a/book/window_service.rs +++ /dev/null @@ -1,626 +0,0 @@ -//! The `window_service` provides a thread for maintaining a window (tail of the ledger). -//! -use cluster_info::{ClusterInfo, NodeInfo}; -use counter::Counter; -use entry::EntrySender; - -use leader_scheduler::LeaderScheduler; -use log::Level; -use packet::SharedBlob; -use rand::{thread_rng, Rng}; -use result::{Error, Result}; -use solana_metrics::{influxdb, submit}; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::timing::duration_as_ms; -use std::net::UdpSocket; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::sync::mpsc::RecvTimeoutError; -use std::sync::{Arc, RwLock}; -use std::thread::{Builder, JoinHandle}; -use std::time::{Duration, Instant}; -use streamer::{BlobReceiver, BlobSender}; -use window::{SharedWindow, WindowUtil}; - -pub const MAX_REPAIR_BACKOFF: usize = 128; - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum WindowServiceReturnType { - LeaderRotation(u64), -} - -fn repair_backoff(last: &mut u64, times: &mut usize, consumed: u64) -> bool { - //exponential backoff - if *last != consumed { - //start with a 50% chance of asking for repairs - *times = 1; - } - *last = consumed; - *times += 1; - - // Experiment with capping repair request duration. - // Once nodes are too far behind they can spend many - // seconds without asking for repair - if *times > MAX_REPAIR_BACKOFF { - // 50% chance that a request will fire between 64 - 128 tries - *times = MAX_REPAIR_BACKOFF / 2; - } - - //if we get lucky, make the request, which should exponentially get less likely - thread_rng().gen_range(0, *times as u64) == 0 -} - -fn add_block_to_retransmit_queue( - b: &SharedBlob, - leader_id: Pubkey, - retransmit_queue: &mut Vec, -) { - let p = b.read().unwrap(); - //TODO this check isn't safe against adverserial packets - //we need to maintain a sequence window - trace!( - "idx: {} addr: {:?} id: {:?} leader: {:?}", - p.index() - .expect("get_index in fn add_block_to_retransmit_queue"), - p.id() - .expect("get_id in trace! fn add_block_to_retransmit_queue"), - p.meta.addr(), - leader_id - ); - if p.id().expect("get_id in fn add_block_to_retransmit_queue") == leader_id { - //TODO - //need to copy the retransmitted blob - //otherwise we get into races with which thread - //should do the recycling - // - let nv = SharedBlob::default(); - { - let mut mnv = nv.write().unwrap(); - let sz = p.meta.size; - mnv.meta.size = sz; - mnv.data[..sz].copy_from_slice(&p.data[..sz]); - } - retransmit_queue.push(nv); - } -} - -fn retransmit_all_leader_blocks( - window: &SharedWindow, - maybe_leader: Option, - dq: &[SharedBlob], - id: &Pubkey, - consumed: u64, - received: u64, - retransmit: &BlobSender, - pending_retransmits: &mut bool, -) -> Result<()> { - let mut retransmit_queue: Vec = Vec::new(); - if let Some(leader) = maybe_leader { - let leader_id = leader.id; - for b in dq { - add_block_to_retransmit_queue(b, leader_id, &mut retransmit_queue); - } - - if *pending_retransmits { - for w in window - .write() - .expect("Window write failed in retransmit_all_leader_blocks") - .iter_mut() - { - *pending_retransmits = false; - if w.leader_unknown { - if let Some(ref b) = w.data { - add_block_to_retransmit_queue(b, leader_id, &mut retransmit_queue); - w.leader_unknown = false; - } - } - } - } - submit( - influxdb::Point::new("retransmit-queue") - .add_field( - "count", - influxdb::Value::Integer(retransmit_queue.len() as i64), - ).to_owned(), - ); - } else { - warn!("{}: no leader to retransmit from", id); - } - if !retransmit_queue.is_empty() { - trace!( - "{}: RECV_WINDOW {} {}: retransmit {}", - id, - consumed, - received, - retransmit_queue.len(), - ); - inc_new_counter_info!("streamer-recv_window-retransmit", retransmit_queue.len()); - retransmit.send(retransmit_queue)?; - } - Ok(()) -} - -#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] -fn recv_window( - window: &SharedWindow, - id: &Pubkey, - cluster_info: &Arc>, - consumed: &mut u64, - received: &mut u64, - tick_height: &mut u64, - max_ix: u64, - r: &BlobReceiver, - s: &EntrySender, - retransmit: &BlobSender, - pending_retransmits: &mut bool, - done: &Arc, -) -> Result<()> { - let timer = Duration::from_millis(200); - let mut dq = r.recv_timeout(timer)?; - let maybe_leader: Option = cluster_info - .read() - .expect("'cluster_info' read lock in fn recv_window") - .leader_data() - .cloned(); - let leader_unknown = maybe_leader.is_none(); - while let Ok(mut nq) = r.try_recv() { - dq.append(&mut nq) - } - let now = Instant::now(); - inc_new_counter_info!("streamer-recv_window-recv", dq.len(), 100); - - submit( - influxdb::Point::new("recv-window") - .add_field("count", influxdb::Value::Integer(dq.len() as i64)) - .to_owned(), - ); - - trace!( - "{}: RECV_WINDOW {} {}: got packets {}", - id, - *consumed, - *received, - dq.len(), - ); - - retransmit_all_leader_blocks( - window, - maybe_leader, - &dq, - id, - *consumed, - *received, - retransmit, - pending_retransmits, - )?; - - let mut pixs = Vec::new(); - //send a contiguous set of blocks - let mut consume_queue = Vec::new(); - for b in dq { - let (pix, meta_size) = { - let p = b.read().unwrap(); - (p.index()?, p.meta.size) - }; - pixs.push(pix); - - if !window - .read() - .unwrap() - .blob_idx_in_window(&id, pix, *consumed, received) - { - continue; - } - - // For downloading storage blobs, - // we only want up to a certain index - // then stop - if max_ix != 0 && pix > max_ix { - continue; - } - - trace!("{} window pix: {} size: {}", id, pix, meta_size); - - window.write().unwrap().process_blob( - id, - b, - pix, - &mut consume_queue, - consumed, - tick_height, - leader_unknown, - pending_retransmits, - ); - - // Send a signal when we hit the max entry_height - if max_ix != 0 && *consumed == (max_ix + 1) { - done.store(true, Ordering::Relaxed); - } - } - if log_enabled!(Level::Trace) { - trace!("{}", window.read().unwrap().print(id, *consumed)); - trace!( - "{}: consumed: {} received: {} sending consume.len: {} pixs: {:?} took {} ms", - id, - *consumed, - *received, - consume_queue.len(), - pixs, - duration_as_ms(&now.elapsed()) - ); - } - if !consume_queue.is_empty() { - inc_new_counter_info!("streamer-recv_window-consume", consume_queue.len()); - s.send(consume_queue)?; - } - Ok(()) -} - -#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] -pub fn window_service( - cluster_info: Arc>, - window: SharedWindow, - tick_height: u64, - entry_height: u64, - max_entry_height: u64, - r: BlobReceiver, - s: EntrySender, - retransmit: BlobSender, - repair_socket: Arc, - leader_scheduler: Arc>, - done: Arc, -) -> JoinHandle<()> { - Builder::new() - .name("solana-window".to_string()) - .spawn(move || { - let mut tick_height_ = tick_height; - let mut consumed = entry_height; - let mut received = entry_height; - let mut last = entry_height; - let mut times = 0; - let id = cluster_info.read().unwrap().my_data().id; - let mut pending_retransmits = false; - trace!("{}: RECV_WINDOW started", id); - loop { - // Check if leader rotation was configured - if let Err(e) = recv_window( - &window, - &id, - &cluster_info, - &mut consumed, - &mut received, - &mut tick_height_, - max_entry_height, - &r, - &s, - &retransmit, - &mut pending_retransmits, - &done, - ) { - match e { - Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => break, - Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), - _ => { - inc_new_counter_info!("streamer-window-error", 1, 1); - error!("window error: {:?}", e); - } - } - } - - submit( - influxdb::Point::new("window-stage") - .add_field("consumed", influxdb::Value::Integer(consumed as i64)) - .to_owned(), - ); - - if received <= consumed { - trace!( - "{} we have everything received:{} consumed:{}", - id, - received, - consumed - ); - continue; - } - - //exponential backoff - if !repair_backoff(&mut last, &mut times, consumed) { - trace!("{} !repair_backoff() times = {}", id, times); - continue; - } - trace!("{} let's repair! times = {}", id, times); - - let mut window = window.write().unwrap(); - let reqs = window.repair( - &cluster_info, - &id, - times, - consumed, - received, - tick_height_, - max_entry_height, - &leader_scheduler, - ); - for (to, req) in reqs { - repair_socket.send_to(&req, to).unwrap_or_else(|e| { - info!("{} repair req send_to({}) error {:?}", id, to, e); - 0 - }); - } - } - }).unwrap() -} - -#[cfg(test)] -mod test { - use cluster_info::{ClusterInfo, Node}; - use entry::Entry; - use leader_scheduler::LeaderScheduler; - use logger; - use packet::{make_consecutive_blobs, SharedBlob, PACKET_DATA_SIZE}; - use solana_sdk::hash::Hash; - use std::net::UdpSocket; - use std::sync::atomic::{AtomicBool, Ordering}; - use std::sync::mpsc::{channel, Receiver}; - use std::sync::{Arc, RwLock}; - use std::time::Duration; - use streamer::{blob_receiver, responder}; - use window::default_window; - use window_service::{repair_backoff, window_service}; - - fn get_entries(r: Receiver>, num: &mut usize) { - for _t in 0..5 { - let timer = Duration::new(1, 0); - match r.recv_timeout(timer) { - Ok(m) => { - *num += m.len(); - } - e => info!("error {:?}", e), - } - if *num == 10 { - break; - } - } - } - - #[test] - pub fn window_send_test() { - logger::setup(); - let tn = Node::new_localhost(); - let exit = Arc::new(AtomicBool::new(false)); - let mut cluster_info_me = ClusterInfo::new(tn.info.clone()); - let me_id = cluster_info_me.my_data().id; - cluster_info_me.set_leader(me_id); - let subs = Arc::new(RwLock::new(cluster_info_me)); - - let (s_reader, r_reader) = channel(); - let t_receiver = blob_receiver(Arc::new(tn.sockets.gossip), exit.clone(), s_reader); - let (s_window, r_window) = channel(); - let (s_retransmit, r_retransmit) = channel(); - let win = Arc::new(RwLock::new(default_window())); - let done = Arc::new(AtomicBool::new(false)); - let t_window = window_service( - subs, - win, - 0, - 0, - 0, - r_reader, - s_window, - s_retransmit, - Arc::new(tn.sockets.repair), - Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader(me_id))), - done, - ); - let t_responder = { - let (s_responder, r_responder) = channel(); - let blob_sockets: Vec> = - tn.sockets.replicate.into_iter().map(Arc::new).collect(); - - let t_responder = responder("window_send_test", blob_sockets[0].clone(), r_responder); - let mut num_blobs_to_make = 10; - let gossip_address = &tn.info.ncp; - let msgs = make_consecutive_blobs( - me_id, - num_blobs_to_make, - 0, - Hash::default(), - &gossip_address, - ).into_iter() - .rev() - .collect();; - s_responder.send(msgs).expect("send"); - t_responder - }; - - let mut num = 0; - get_entries(r_window, &mut num); - assert_eq!(num, 10); - let mut q = r_retransmit.recv().unwrap(); - while let Ok(mut nq) = r_retransmit.try_recv() { - q.append(&mut nq); - } - assert_eq!(q.len(), 10); - exit.store(true, Ordering::Relaxed); - t_receiver.join().expect("join"); - t_responder.join().expect("join"); - t_window.join().expect("join"); - } - - #[test] - pub fn window_send_no_leader_test() { - logger::setup(); - let tn = Node::new_localhost(); - let exit = Arc::new(AtomicBool::new(false)); - let cluster_info_me = ClusterInfo::new(tn.info.clone()); - let me_id = cluster_info_me.my_data().id; - let subs = Arc::new(RwLock::new(cluster_info_me)); - - let (s_reader, r_reader) = channel(); - let t_receiver = blob_receiver(Arc::new(tn.sockets.gossip), exit.clone(), s_reader); - let (s_window, _r_window) = channel(); - let (s_retransmit, r_retransmit) = channel(); - let win = Arc::new(RwLock::new(default_window())); - let done = Arc::new(AtomicBool::new(false)); - let t_window = window_service( - subs.clone(), - win, - 0, - 0, - 0, - r_reader, - s_window, - s_retransmit, - Arc::new(tn.sockets.repair), - // TODO: For now, the window still checks the ClusterInfo for the current leader - // to determine whether to retransmit a block. In the future when we rely on - // the LeaderScheduler for retransmits, this test will need to be rewritten - // because a leader should only be unknown in the window when the write stage - // hasn't yet calculated the leaders for slots in the next epoch (on entries - // at heights that are multiples of seed_rotation_interval in LeaderScheduler) - Arc::new(RwLock::new(LeaderScheduler::default())), - done, - ); - let t_responder = { - let (s_responder, r_responder) = channel(); - let blob_sockets: Vec> = - tn.sockets.replicate.into_iter().map(Arc::new).collect(); - let t_responder = responder("window_send_test", blob_sockets[0].clone(), r_responder); - let mut msgs = Vec::new(); - for v in 0..10 { - let i = 9 - v; - let b = SharedBlob::default(); - { - let mut w = b.write().unwrap(); - w.set_index(i).unwrap(); - w.set_id(&me_id).unwrap(); - assert_eq!(i, w.index().unwrap()); - w.meta.size = PACKET_DATA_SIZE; - w.meta.set_addr(&tn.info.ncp); - } - msgs.push(b); - } - s_responder.send(msgs).expect("send"); - t_responder - }; - - assert!(r_retransmit.recv_timeout(Duration::new(3, 0)).is_err()); - exit.store(true, Ordering::Relaxed); - t_receiver.join().expect("join"); - t_responder.join().expect("join"); - t_window.join().expect("join"); - } - - #[test] - pub fn window_send_late_leader_test() { - logger::setup(); - let tn = Node::new_localhost(); - let exit = Arc::new(AtomicBool::new(false)); - let cluster_info_me = ClusterInfo::new(tn.info.clone()); - let me_id = cluster_info_me.my_data().id; - let subs = Arc::new(RwLock::new(cluster_info_me)); - - let (s_reader, r_reader) = channel(); - let t_receiver = blob_receiver(Arc::new(tn.sockets.gossip), exit.clone(), s_reader); - let (s_window, _r_window) = channel(); - let (s_retransmit, r_retransmit) = channel(); - let win = Arc::new(RwLock::new(default_window())); - let done = Arc::new(AtomicBool::new(false)); - let t_window = window_service( - subs.clone(), - win, - 0, - 0, - 0, - r_reader, - s_window, - s_retransmit, - Arc::new(tn.sockets.repair), - // TODO: For now, the window still checks the ClusterInfo for the current leader - // to determine whether to retransmit a block. In the future when we rely on - // the LeaderScheduler for retransmits, this test will need to be rewritten - // becasue a leader should only be unknown in the window when the write stage - // hasn't yet calculated the leaders for slots in the next epoch (on entries - // at heights that are multiples of seed_rotation_interval in LeaderScheduler) - Arc::new(RwLock::new(LeaderScheduler::default())), - done, - ); - let t_responder = { - let (s_responder, r_responder) = channel(); - let blob_sockets: Vec> = - tn.sockets.replicate.into_iter().map(Arc::new).collect(); - let t_responder = responder("window_send_test", blob_sockets[0].clone(), r_responder); - let mut msgs = Vec::new(); - for v in 0..10 { - let i = 9 - v; - let b = SharedBlob::default(); - { - let mut w = b.write().unwrap(); - w.set_index(i).unwrap(); - w.set_id(&me_id).unwrap(); - assert_eq!(i, w.index().unwrap()); - w.meta.size = PACKET_DATA_SIZE; - w.meta.set_addr(&tn.info.ncp); - } - msgs.push(b); - } - s_responder.send(msgs).expect("send"); - - assert!(r_retransmit.recv_timeout(Duration::new(3, 0)).is_err()); - - subs.write().unwrap().set_leader(me_id); - - let mut msgs1 = Vec::new(); - for v in 1..5 { - let i = 9 + v; - let b = SharedBlob::default(); - { - let mut w = b.write().unwrap(); - w.set_index(i).unwrap(); - w.set_id(&me_id).unwrap(); - assert_eq!(i, w.index().unwrap()); - w.meta.size = PACKET_DATA_SIZE; - w.meta.set_addr(&tn.info.ncp); - } - msgs1.push(b); - } - s_responder.send(msgs1).expect("send"); - t_responder - }; - let mut q = r_retransmit.recv().unwrap(); - while let Ok(mut nq) = r_retransmit.recv_timeout(Duration::from_millis(100)) { - q.append(&mut nq); - } - assert!(q.len() > 10); - exit.store(true, Ordering::Relaxed); - t_receiver.join().expect("join"); - t_responder.join().expect("join"); - t_window.join().expect("join"); - } - - #[test] - pub fn test_repair_backoff() { - let num_tests = 100; - let res: usize = (0..num_tests) - .map(|_| { - let mut last = 0; - let mut times = 0; - let total: usize = (0..127) - .map(|x| { - let rv = repair_backoff(&mut last, &mut times, 1) as usize; - assert_eq!(times, x + 2); - rv - }).sum(); - assert_eq!(times, 128); - assert_eq!(last, 1); - repair_backoff(&mut last, &mut times, 1); - assert_eq!(times, 64); - repair_backoff(&mut last, &mut times, 2); - assert_eq!(times, 2); - assert_eq!(last, 2); - total - }).sum(); - let avg = res / num_tests; - assert!(avg >= 3); - assert!(avg <= 5); - } -} From 8cedc3de00d699a5908e6e69cd41f11695065534 Mon Sep 17 00:00:00 2001 From: Jack May Date: Wed, 28 Nov 2018 10:16:36 -0800 Subject: [PATCH 5/8] move llvm, solana docker images to sdk --- ci/docker-llvm/Dockerfile | 67 ---------------------------------- ci/docker-llvm/build.sh | 7 ---- ci/docker-solana/.gitignore | 2 - ci/docker-solana/Dockerfile | 13 ------- ci/docker-solana/README.md | 17 --------- ci/docker-solana/build.sh | 43 ---------------------- ci/docker-solana/entrypoint.sh | 24 ------------ programs/bpf/c/sdk/bpf.mk | 16 ++++---- 8 files changed, 9 insertions(+), 180 deletions(-) delete mode 100644 ci/docker-llvm/Dockerfile delete mode 100755 ci/docker-llvm/build.sh delete mode 100644 ci/docker-solana/.gitignore delete mode 100644 ci/docker-solana/Dockerfile delete mode 100644 ci/docker-solana/README.md delete mode 100755 ci/docker-solana/build.sh delete mode 100755 ci/docker-solana/entrypoint.sh diff --git a/ci/docker-llvm/Dockerfile b/ci/docker-llvm/Dockerfile deleted file mode 100644 index 4effa39cc223b0..00000000000000 --- a/ci/docker-llvm/Dockerfile +++ /dev/null @@ -1,67 +0,0 @@ -# This docker file is based on the llvm docker file example located here: -# https://github.com/llvm-mirror/llvm/blob/master/utils/docker/debian8/Dockerfile - -FROM launcher.gcr.io/google/debian8:latest as builder -LABEL maintainer "Solana Maintainers" - -# Install build dependencies of llvm. -# First, Update the apt's source list and include the sources of the packages. -RUN grep deb /etc/apt/sources.list | \ - sed 's/^deb/deb-src /g' >> /etc/apt/sources.list - -# Install compiler, python and subversion. -RUN apt-get update && \ - apt-get install -y \ - --no-install-recommends \ - ca-certificates gnupg \ - build-essential \ - python \ - wget \ - unzip \ - git \ - ssh && \ - rm -rf /var/lib/apt/lists/* - -# Install a newer ninja release. It seems the older version in the debian repos -# randomly crashes when compiling llvm. -RUN wget "https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-linux.zip" && \ - echo "d2fea9ff33b3ef353161ed906f260d565ca55b8ca0568fa07b1d2cab90a84a07 ninja-linux.zip" \ - | sha256sum -c && \ - unzip ninja-linux.zip -d /usr/local/bin && \ - rm ninja-linux.zip - -# Import public key required for verifying signature of cmake download. -RUN gpg --no-tty --keyserver hkp://pgp.mit.edu --recv 0x2D2CEF1034921684 - -# Download, verify and install cmake version that can compile clang into /usr/local. -# (Version in debian8 repos is too old) -RUN mkdir /tmp/cmake-install && cd /tmp/cmake-install && \ - wget "https://cmake.org/files/v3.7/cmake-3.7.2-SHA-256.txt.asc" && \ - wget "https://cmake.org/files/v3.7/cmake-3.7.2-SHA-256.txt" && \ - # gpg --verify cmake-3.7.2-SHA-256.txt.asc cmake-3.7.2-SHA-256.txt && \ - wget "https://cmake.org/files/v3.7/cmake-3.7.2-Linux-x86_64.tar.gz" && \ - ( grep "cmake-3.7.2-Linux-x86_64.tar.gz" cmake-3.7.2-SHA-256.txt | \ - sha256sum -c - ) && \ - tar xzf cmake-3.7.2-Linux-x86_64.tar.gz -C /usr/local --strip-components=1 && \ - cd / && rm -rf /tmp/cmake-install - -# ADD checksums /tmp/checksums -# ADD scripts /tmp/scripts - -# Checkout the source code -RUN git clone https://github.com/solana-labs/llvm.git && \ - git clone https://github.com/solana-labs/clang.git llvm/tools/clang && \ - git clone https://github.com/solana-labs/clang-tools-extra.git llvm/tools/clang/tools/extra && \ - git clone https://github.com/solana-labs/lld.git llvm/tools/lld && \ - git clone https://github.com/solana-labs/compiler-rt.git llvm/projects/compiler-rt - -RUN mkdir /llvm/build && \ - cd /llvm/build && \ - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_INSTALL_PREFIX=$HOME/local -G "Ninja" .. && \ - ninja -j6 && \ - ninja install - -# Produce stage 2 docker with just the peices needed -FROM launcher.gcr.io/google/debian8:latest -LABEL maintainer "Solana Maintainers" -COPY --from=builder root/local/bin /usr/local/bin diff --git a/ci/docker-llvm/build.sh b/ci/docker-llvm/build.sh deleted file mode 100755 index abc953acc0512b..00000000000000 --- a/ci/docker-llvm/build.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash -set -ex - -cd "$(dirname "$0")" - -docker build -t solanalabs/llvm . -docker push solanalabs/llvm diff --git a/ci/docker-solana/.gitignore b/ci/docker-solana/.gitignore deleted file mode 100644 index 79163e181bd429..00000000000000 --- a/ci/docker-solana/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -cargo-install/ -usr/ diff --git a/ci/docker-solana/Dockerfile b/ci/docker-solana/Dockerfile deleted file mode 100644 index 9916955b55360e..00000000000000 --- a/ci/docker-solana/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM debian:stretch - -# JSON RPC port -EXPOSE 8899/tcp - -# Install libssl -RUN apt update && \ - apt-get install -y libssl-dev && \ - rm -rf /var/lib/apt/lists/* - -COPY usr/bin /usr/bin/ -ENTRYPOINT [ "/usr/bin/solana-entrypoint.sh" ] -CMD [""] diff --git a/ci/docker-solana/README.md b/ci/docker-solana/README.md deleted file mode 100644 index 2a9ea9b7857aed..00000000000000 --- a/ci/docker-solana/README.md +++ /dev/null @@ -1,17 +0,0 @@ -## Minimal Solana Docker image -This image is automatically updated by CI - -https://hub.docker.com/r/solanalabs/solana/ - -### Usage: -Run the latest beta image: -```bash -$ docker run --rm -p 8899:8899 solanalabs/solana:beta -``` - -Run the latest edge image: -```bash -$ docker run --rm -p 8899:8899 solanalabs/solana:edge -``` - -Port *8899* is the JSON RPC port, which is used by clients to communicate with the network. diff --git a/ci/docker-solana/build.sh b/ci/docker-solana/build.sh deleted file mode 100755 index ba5967200aa010..00000000000000 --- a/ci/docker-solana/build.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash -set -ex - -cd "$(dirname "$0")" -eval "$(../channel-info.sh)" - -if [[ $BUILDKITE_BRANCH = "$STABLE_CHANNEL" ]]; then - CHANNEL=stable -elif [[ $BUILDKITE_BRANCH = "$EDGE_CHANNEL" ]]; then - CHANNEL=edge -elif [[ $BUILDKITE_BRANCH = "$BETA_CHANNEL" ]]; then - CHANNEL=beta -fi - -if [[ -z $CHANNEL ]]; then - echo Unable to determine channel to publish into, exiting. - exit 0 -fi - -rm -rf usr/ -../docker-run.sh solanalabs/rust:1.30.1 bash -c " - set -ex - cargo install --path drone --root ci/docker-solana/usr - cargo install --path . --root ci/docker-solana/usr -" -cp -f entrypoint.sh usr/bin/solana-entrypoint.sh -../../scripts/install-native-programs.sh usr/bin/ - -docker build -t solanalabs/solana:$CHANNEL . - -maybeEcho= -if [[ -z $CI ]]; then - echo "Not CI, skipping |docker push|" - maybeEcho="echo" -else - ( - set +x - if [[ -n $DOCKER_PASSWORD && -n $DOCKER_USERNAME ]]; then - echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin - fi - ) -fi -$maybeEcho docker push solanalabs/solana:$CHANNEL diff --git a/ci/docker-solana/entrypoint.sh b/ci/docker-solana/entrypoint.sh deleted file mode 100755 index 84b15967f34d22..00000000000000 --- a/ci/docker-solana/entrypoint.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash -set -ex - -export RUST_LOG=${RUST_LOG:-solana=info} # if RUST_LOG is unset, default to info -export RUST_BACKTRACE=1 - -solana-keygen -o /config/leader-keypair.json -solana-keygen -o /config/drone-keypair.json - -solana-fullnode-config --keypair=/config/leader-keypair.json -l > /config/leader-config.json -solana-genesis --num_tokens 1000000000 --mint /config/drone-keypair.json --bootstrap_leader /config/leader-config.json --ledger /ledger - -solana-drone --keypair /config/drone-keypair.json & -drone=$! -solana-fullnode --identity /config/leader-config.json --ledger /ledger/ --rpc 8899 & -fullnode=$! - -abort() { - kill "$drone" "$fullnode" -} - -trap abort SIGINT SIGTERM -wait "$fullnode" -kill "$drone" "$fullnode" diff --git a/programs/bpf/c/sdk/bpf.mk b/programs/bpf/c/sdk/bpf.mk index 10f418d54509cf..68f92d53470ecf 100644 --- a/programs/bpf/c/sdk/bpf.mk +++ b/programs/bpf/c/sdk/bpf.mk @@ -33,14 +33,14 @@ LLC := llc-7 OBJ_DUMP := llvm-objdump-7 endif -SYSTEM_INC_DIRS := -isystem $(LOCAL_PATH)inc +SYSTEM_INC_DIRS := $(LOCAL_PATH)inc C_FLAGS := \ -Werror \ -O2 \ -fno-builtin \ -std=c17 \ - $(SYSTEM_INC_DIRS) \ + $(addprefix -isystem,$(SYSTEM_INC_DIRS)) \ $(addprefix -I,$(INC_DIRS)) CXX_FLAGS := \ @@ -92,6 +92,7 @@ TEST_CXX_FLAGS := \ $(TESTFRAMEWORK_FLAGS) \ help: + @echo '' @echo 'BPF Program makefile' @echo '' @echo 'This makefile will build BPF Programs from C or C++ source files into ELFs' @@ -106,10 +107,10 @@ help: @echo '' @echo 'User settings' @echo ' - The following setting are overridable on the command line, default values shown:' - @echo ' - Show commands while building:' - @echo ' V=1' - @echo ' - Use LLVM from docker:' - @echo ' DOCKER=1' + @echo ' - Show commands while building: V=1' + @echo ' V=$(V)' + @echo ' - Use LLVM from docker: DOCKER=1' + @echo ' DOCKER=$(DOCKER)' @echo ' - List of include directories:' @echo ' INC_DIRS=$(INC_DIRS)' @echo ' - List of system include directories:' @@ -125,7 +126,7 @@ help: @echo '' @echo 'Usage:' @echo ' - make help - This help message' - @echo ' - make all - Builds all the programs' + @echo ' - make all - Build all the programs' @echo ' - make test - Build and run all tests' @echo ' - make dump_ - Dumps the contents of the program to stdout' @echo ' - make - Build a single program by name' @@ -139,6 +140,7 @@ help: @echo ' - Assuming a programed named foo (src/foo.c)' @echo ' - make foo' @echo ' - make dump_foo' + @echo '' .PRECIOUS: $(OUT_DIR)/%.bc $(OUT_DIR)/%.bc: $(SRC_DIR)/%.c From f3eb95a4b934eda66d846960b967bfc911083621 Mon Sep 17 00:00:00 2001 From: Jack May Date: Wed, 28 Nov 2018 10:18:56 -0800 Subject: [PATCH 6/8] add dockers to sdk --- sdk/docker-llvm/Dockerfile | 67 +++++++++++++++++++++++++++++++++ sdk/docker-llvm/README.md | 16 ++++++++ sdk/docker-llvm/build.sh | 7 ++++ sdk/docker-solana/.gitignore | 2 + sdk/docker-solana/Dockerfile | 13 +++++++ sdk/docker-solana/README.md | 17 +++++++++ sdk/docker-solana/build.sh | 43 +++++++++++++++++++++ sdk/docker-solana/entrypoint.sh | 24 ++++++++++++ 8 files changed, 189 insertions(+) create mode 100644 sdk/docker-llvm/Dockerfile create mode 100644 sdk/docker-llvm/README.md create mode 100755 sdk/docker-llvm/build.sh create mode 100644 sdk/docker-solana/.gitignore create mode 100644 sdk/docker-solana/Dockerfile create mode 100644 sdk/docker-solana/README.md create mode 100755 sdk/docker-solana/build.sh create mode 100755 sdk/docker-solana/entrypoint.sh diff --git a/sdk/docker-llvm/Dockerfile b/sdk/docker-llvm/Dockerfile new file mode 100644 index 00000000000000..4effa39cc223b0 --- /dev/null +++ b/sdk/docker-llvm/Dockerfile @@ -0,0 +1,67 @@ +# This docker file is based on the llvm docker file example located here: +# https://github.com/llvm-mirror/llvm/blob/master/utils/docker/debian8/Dockerfile + +FROM launcher.gcr.io/google/debian8:latest as builder +LABEL maintainer "Solana Maintainers" + +# Install build dependencies of llvm. +# First, Update the apt's source list and include the sources of the packages. +RUN grep deb /etc/apt/sources.list | \ + sed 's/^deb/deb-src /g' >> /etc/apt/sources.list + +# Install compiler, python and subversion. +RUN apt-get update && \ + apt-get install -y \ + --no-install-recommends \ + ca-certificates gnupg \ + build-essential \ + python \ + wget \ + unzip \ + git \ + ssh && \ + rm -rf /var/lib/apt/lists/* + +# Install a newer ninja release. It seems the older version in the debian repos +# randomly crashes when compiling llvm. +RUN wget "https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-linux.zip" && \ + echo "d2fea9ff33b3ef353161ed906f260d565ca55b8ca0568fa07b1d2cab90a84a07 ninja-linux.zip" \ + | sha256sum -c && \ + unzip ninja-linux.zip -d /usr/local/bin && \ + rm ninja-linux.zip + +# Import public key required for verifying signature of cmake download. +RUN gpg --no-tty --keyserver hkp://pgp.mit.edu --recv 0x2D2CEF1034921684 + +# Download, verify and install cmake version that can compile clang into /usr/local. +# (Version in debian8 repos is too old) +RUN mkdir /tmp/cmake-install && cd /tmp/cmake-install && \ + wget "https://cmake.org/files/v3.7/cmake-3.7.2-SHA-256.txt.asc" && \ + wget "https://cmake.org/files/v3.7/cmake-3.7.2-SHA-256.txt" && \ + # gpg --verify cmake-3.7.2-SHA-256.txt.asc cmake-3.7.2-SHA-256.txt && \ + wget "https://cmake.org/files/v3.7/cmake-3.7.2-Linux-x86_64.tar.gz" && \ + ( grep "cmake-3.7.2-Linux-x86_64.tar.gz" cmake-3.7.2-SHA-256.txt | \ + sha256sum -c - ) && \ + tar xzf cmake-3.7.2-Linux-x86_64.tar.gz -C /usr/local --strip-components=1 && \ + cd / && rm -rf /tmp/cmake-install + +# ADD checksums /tmp/checksums +# ADD scripts /tmp/scripts + +# Checkout the source code +RUN git clone https://github.com/solana-labs/llvm.git && \ + git clone https://github.com/solana-labs/clang.git llvm/tools/clang && \ + git clone https://github.com/solana-labs/clang-tools-extra.git llvm/tools/clang/tools/extra && \ + git clone https://github.com/solana-labs/lld.git llvm/tools/lld && \ + git clone https://github.com/solana-labs/compiler-rt.git llvm/projects/compiler-rt + +RUN mkdir /llvm/build && \ + cd /llvm/build && \ + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_INSTALL_PREFIX=$HOME/local -G "Ninja" .. && \ + ninja -j6 && \ + ninja install + +# Produce stage 2 docker with just the peices needed +FROM launcher.gcr.io/google/debian8:latest +LABEL maintainer "Solana Maintainers" +COPY --from=builder root/local/bin /usr/local/bin diff --git a/sdk/docker-llvm/README.md b/sdk/docker-llvm/README.md new file mode 100644 index 00000000000000..7868b33b70a51f --- /dev/null +++ b/sdk/docker-llvm/README.md @@ -0,0 +1,16 @@ +## Solana Customized LLVM + +This Docker contains LLVM binaries that incorporate customizations and fixes required +by Solana but not yet upstreamed into the LLVM mainline. + +https://hub.docker.com/r/solanalabs/llvm/ + +### Usage: + +This Docker is optionally used by the SDK build system. + +For more information: + +```bash +$ make help +``` diff --git a/sdk/docker-llvm/build.sh b/sdk/docker-llvm/build.sh new file mode 100755 index 00000000000000..abc953acc0512b --- /dev/null +++ b/sdk/docker-llvm/build.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -ex + +cd "$(dirname "$0")" + +docker build -t solanalabs/llvm . +docker push solanalabs/llvm diff --git a/sdk/docker-solana/.gitignore b/sdk/docker-solana/.gitignore new file mode 100644 index 00000000000000..79163e181bd429 --- /dev/null +++ b/sdk/docker-solana/.gitignore @@ -0,0 +1,2 @@ +cargo-install/ +usr/ diff --git a/sdk/docker-solana/Dockerfile b/sdk/docker-solana/Dockerfile new file mode 100644 index 00000000000000..9916955b55360e --- /dev/null +++ b/sdk/docker-solana/Dockerfile @@ -0,0 +1,13 @@ +FROM debian:stretch + +# JSON RPC port +EXPOSE 8899/tcp + +# Install libssl +RUN apt update && \ + apt-get install -y libssl-dev && \ + rm -rf /var/lib/apt/lists/* + +COPY usr/bin /usr/bin/ +ENTRYPOINT [ "/usr/bin/solana-entrypoint.sh" ] +CMD [""] diff --git a/sdk/docker-solana/README.md b/sdk/docker-solana/README.md new file mode 100644 index 00000000000000..2a9ea9b7857aed --- /dev/null +++ b/sdk/docker-solana/README.md @@ -0,0 +1,17 @@ +## Minimal Solana Docker image +This image is automatically updated by CI + +https://hub.docker.com/r/solanalabs/solana/ + +### Usage: +Run the latest beta image: +```bash +$ docker run --rm -p 8899:8899 solanalabs/solana:beta +``` + +Run the latest edge image: +```bash +$ docker run --rm -p 8899:8899 solanalabs/solana:edge +``` + +Port *8899* is the JSON RPC port, which is used by clients to communicate with the network. diff --git a/sdk/docker-solana/build.sh b/sdk/docker-solana/build.sh new file mode 100755 index 00000000000000..5942e06ff42154 --- /dev/null +++ b/sdk/docker-solana/build.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -ex + +cd "$(dirname "$0")" +eval "$(../channel-info.sh)" + +if [[ $BUILDKITE_BRANCH = "$STABLE_CHANNEL" ]]; then + CHANNEL=stable +elif [[ $BUILDKITE_BRANCH = "$EDGE_CHANNEL" ]]; then + CHANNEL=edge +elif [[ $BUILDKITE_BRANCH = "$BETA_CHANNEL" ]]; then + CHANNEL=beta +fi + +if [[ -z $CHANNEL ]]; then + echo Unable to determine channel to publish into, exiting. + exit 0 +fi + +rm -rf usr/ +../../ci/docker-run.sh solanalabs/rust:1.30.1 bash -c " + set -ex + cargo install --path drone --root sdk/docker-solana/usr + cargo install --path . --root sdk/docker-solana/usr +" +cp -f entrypoint.sh usr/bin/solana-entrypoint.sh +../../scripts/install-native-programs.sh usr/bin/ + +docker build -t solanalabs/solana:$CHANNEL . + +maybeEcho= +if [[ -z $CI ]]; then + echo "Not CI, skipping |docker push|" + maybeEcho="echo" +else + ( + set +x + if [[ -n $DOCKER_PASSWORD && -n $DOCKER_USERNAME ]]; then + echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin + fi + ) +fi +$maybeEcho docker push solanalabs/solana:$CHANNEL diff --git a/sdk/docker-solana/entrypoint.sh b/sdk/docker-solana/entrypoint.sh new file mode 100755 index 00000000000000..84b15967f34d22 --- /dev/null +++ b/sdk/docker-solana/entrypoint.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -ex + +export RUST_LOG=${RUST_LOG:-solana=info} # if RUST_LOG is unset, default to info +export RUST_BACKTRACE=1 + +solana-keygen -o /config/leader-keypair.json +solana-keygen -o /config/drone-keypair.json + +solana-fullnode-config --keypair=/config/leader-keypair.json -l > /config/leader-config.json +solana-genesis --num_tokens 1000000000 --mint /config/drone-keypair.json --bootstrap_leader /config/leader-config.json --ledger /ledger + +solana-drone --keypair /config/drone-keypair.json & +drone=$! +solana-fullnode --identity /config/leader-config.json --ledger /ledger/ --rpc 8899 & +fullnode=$! + +abort() { + kill "$drone" "$fullnode" +} + +trap abort SIGINT SIGTERM +wait "$fullnode" +kill "$drone" "$fullnode" From 71a7ac38e55f416dbf5eeca6ca8725f73044f8aa Mon Sep 17 00:00:00 2001 From: Jack May Date: Wed, 28 Nov 2018 11:26:50 -0800 Subject: [PATCH 7/8] nudge --- programs/bpf/c/sdk/llvm/llvm-docker/bin/clang | 3 ++- programs/bpf/c/sdk/llvm/llvm-docker/bin/clang++ | 3 ++- programs/bpf/c/sdk/llvm/llvm-docker/bin/llc | 3 ++- programs/bpf/c/sdk/llvm/llvm-docker/bin/llvm-objdump | 3 ++- programs/bpf/c/sdk/llvm/llvm-docker/generate.sh | 3 ++- sdk/docker-llvm/Dockerfile | 5 +++-- sdk/docker-llvm/README.md | 2 +- 7 files changed, 14 insertions(+), 8 deletions(-) diff --git a/programs/bpf/c/sdk/llvm/llvm-docker/bin/clang b/programs/bpf/c/sdk/llvm/llvm-docker/bin/clang index a4e86ef94f7935..2aef38dba862ac 100755 --- a/programs/bpf/c/sdk/llvm/llvm-docker/bin/clang +++ b/programs/bpf/c/sdk/llvm/llvm-docker/bin/clang @@ -1,3 +1,4 @@ -#!/usr/bin/env bash -ex +#!/usr/bin/env bash +set -ex SDKPATH="$( cd "$(dirname "$0")" ; pwd -P )"/../../../.. docker run --workdir /solana_sdk --volume $SDKPATH:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" diff --git a/programs/bpf/c/sdk/llvm/llvm-docker/bin/clang++ b/programs/bpf/c/sdk/llvm/llvm-docker/bin/clang++ index a4e86ef94f7935..2aef38dba862ac 100755 --- a/programs/bpf/c/sdk/llvm/llvm-docker/bin/clang++ +++ b/programs/bpf/c/sdk/llvm/llvm-docker/bin/clang++ @@ -1,3 +1,4 @@ -#!/usr/bin/env bash -ex +#!/usr/bin/env bash +set -ex SDKPATH="$( cd "$(dirname "$0")" ; pwd -P )"/../../../.. docker run --workdir /solana_sdk --volume $SDKPATH:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" diff --git a/programs/bpf/c/sdk/llvm/llvm-docker/bin/llc b/programs/bpf/c/sdk/llvm/llvm-docker/bin/llc index a4e86ef94f7935..2aef38dba862ac 100755 --- a/programs/bpf/c/sdk/llvm/llvm-docker/bin/llc +++ b/programs/bpf/c/sdk/llvm/llvm-docker/bin/llc @@ -1,3 +1,4 @@ -#!/usr/bin/env bash -ex +#!/usr/bin/env bash +set -ex SDKPATH="$( cd "$(dirname "$0")" ; pwd -P )"/../../../.. docker run --workdir /solana_sdk --volume $SDKPATH:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" diff --git a/programs/bpf/c/sdk/llvm/llvm-docker/bin/llvm-objdump b/programs/bpf/c/sdk/llvm/llvm-docker/bin/llvm-objdump index a4e86ef94f7935..2aef38dba862ac 100755 --- a/programs/bpf/c/sdk/llvm/llvm-docker/bin/llvm-objdump +++ b/programs/bpf/c/sdk/llvm/llvm-docker/bin/llvm-objdump @@ -1,3 +1,4 @@ -#!/usr/bin/env bash -ex +#!/usr/bin/env bash +set -ex SDKPATH="$( cd "$(dirname "$0")" ; pwd -P )"/../../../.. docker run --workdir /solana_sdk --volume $SDKPATH:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" diff --git a/programs/bpf/c/sdk/llvm/llvm-docker/generate.sh b/programs/bpf/c/sdk/llvm/llvm-docker/generate.sh index 9bb705270502b3..55be032875b679 100755 --- a/programs/bpf/c/sdk/llvm/llvm-docker/generate.sh +++ b/programs/bpf/c/sdk/llvm/llvm-docker/generate.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash read -r -d '' SCRIPT << 'EOM' -#!/usr/bin/env bash -ex +#!/usr/bin/env bash +set -ex SDKPATH="$( cd "$(dirname "$0")" ; pwd -P )"/../../../.. docker run --workdir /solana_sdk --volume $SDKPATH:/solana_sdk --rm solanalabs/llvm `basename "$0"` "$@" EOM diff --git a/sdk/docker-llvm/Dockerfile b/sdk/docker-llvm/Dockerfile index 4effa39cc223b0..152aa1bbc9631d 100644 --- a/sdk/docker-llvm/Dockerfile +++ b/sdk/docker-llvm/Dockerfile @@ -38,12 +38,13 @@ RUN gpg --no-tty --keyserver hkp://pgp.mit.edu --recv 0x2D2CEF1034921684 RUN mkdir /tmp/cmake-install && cd /tmp/cmake-install && \ wget "https://cmake.org/files/v3.7/cmake-3.7.2-SHA-256.txt.asc" && \ wget "https://cmake.org/files/v3.7/cmake-3.7.2-SHA-256.txt" && \ - # gpg --verify cmake-3.7.2-SHA-256.txt.asc cmake-3.7.2-SHA-256.txt && \ + gpg --verify cmake-3.7.2-SHA-256.txt.asc cmake-3.7.2-SHA-256.txt && \ wget "https://cmake.org/files/v3.7/cmake-3.7.2-Linux-x86_64.tar.gz" && \ ( grep "cmake-3.7.2-Linux-x86_64.tar.gz" cmake-3.7.2-SHA-256.txt | \ sha256sum -c - ) && \ tar xzf cmake-3.7.2-Linux-x86_64.tar.gz -C /usr/local --strip-components=1 && \ - cd / && rm -rf /tmp/cmake-install + cd / && \ + rm -rf /tmp/cmake-install # ADD checksums /tmp/checksums # ADD scripts /tmp/scripts diff --git a/sdk/docker-llvm/README.md b/sdk/docker-llvm/README.md index 7868b33b70a51f..e607e02d936e2d 100644 --- a/sdk/docker-llvm/README.md +++ b/sdk/docker-llvm/README.md @@ -7,7 +7,7 @@ https://hub.docker.com/r/solanalabs/llvm/ ### Usage: -This Docker is optionally used by the SDK build system. +This Docker is optionally used by the SDK BPF build system. For more information: From 0d2c02054465c8e7f0feca8e29469870fa3086b5 Mon Sep 17 00:00:00 2001 From: Jack May Date: Wed, 28 Nov 2018 12:02:40 -0800 Subject: [PATCH 8/8] Document user must pull docker solanalabs/llvm --- programs/bpf/c/sdk/bpf.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/programs/bpf/c/sdk/bpf.mk b/programs/bpf/c/sdk/bpf.mk index 68f92d53470ecf..ea4b7cf847784b 100644 --- a/programs/bpf/c/sdk/bpf.mk +++ b/programs/bpf/c/sdk/bpf.mk @@ -110,6 +110,7 @@ help: @echo ' - Show commands while building: V=1' @echo ' V=$(V)' @echo ' - Use LLVM from docker: DOCKER=1' + @echo ' Docker image must be pulled first: docker pull solanalabs/llvm' @echo ' DOCKER=$(DOCKER)' @echo ' - List of include directories:' @echo ' INC_DIRS=$(INC_DIRS)'