Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial staking pallet #188

Closed
wants to merge 99 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
afb385f
Use spin's Once for OnceLock
kayabaNerve Jul 26, 2023
0eb5640
Further dependency minimization for build times
kayabaNerve Jul 26, 2023
39eae27
Update Dockerfiles to bookworm, successfully
kayabaNerve Jul 26, 2023
8ec4fee
Refactor out external parts to generics
kayabaNerve Oct 16, 2022
96eee31
Successfully compiling
kayabaNerve Oct 16, 2022
6813fe0
Misc cleanup
kayabaNerve Oct 16, 2022
c54f8db
Implement serialization via parity's scale codec
kayabaNerve Oct 16, 2022
5a9671f
Provide a dedicated signature in Precommit of just the block hash
kayabaNerve Oct 17, 2022
4c4c43c
Litany of bug fixes
kayabaNerve Oct 17, 2022
1044ed0
Remove async recursion
kayabaNerve Oct 17, 2022
6430775
Replace MultiSignature with sr25519::Signature
kayabaNerve Oct 20, 2022
94dd66c
Map TM SignatureScheme to Substrate's sr25519
kayabaNerve Oct 20, 2022
5412105
Initial work on an import queue
kayabaNerve Oct 20, 2022
9c44b24
Implement tendermint_machine::Block for Substrate Blocks
kayabaNerve Oct 21, 2022
332e997
Dummy Weights
kayabaNerve Oct 21, 2022
9e076c9
Move documentation to the top of the file
kayabaNerve Oct 21, 2022
30c9b1d
Update Cargo.tomls for substrate packages
kayabaNerve Oct 21, 2022
7742cd7
Move the node over to the new SelectChain
kayabaNerve Oct 21, 2022
dde4041
Minor tweaks
kayabaNerve Oct 21, 2022
f3a464e
Reduce chain_spec and use more accurate naming
kayabaNerve Oct 22, 2022
8fc29a0
Implement block proposal logic
kayabaNerve Oct 22, 2022
03d354f
Get the result of block importing
kayabaNerve Oct 22, 2022
b155305
Provide a way to create the machine
kayabaNerve Oct 22, 2022
0bf669f
Announce blocks
kayabaNerve Oct 22, 2022
846b8f8
Move Commit from including the round to including the round's end_time
kayabaNerve Oct 24, 2022
b01d029
Misc bug fixes
kayabaNerve Oct 24, 2022
0c63e6e
More misc bug fixes
kayabaNerve Oct 24, 2022
50a6fff
Add pallet sessions to runtime, create pallet-tendermint
kayabaNerve Oct 27, 2022
441cba1
Update node to use pallet sessions
kayabaNerve Oct 27, 2022
f7a3c6b
Partial work on correcting pallet calls
kayabaNerve Oct 27, 2022
465c1aa
Redo Tendermint folder structure
kayabaNerve Oct 27, 2022
c2ae8e1
TendermintApi, compilation fixes
kayabaNerve Oct 27, 2022
9f7eb72
Basic Gossip Validator
kayabaNerve Oct 30, 2022
46dab42
Clean generics in Tendermint with a monolith with associated types
kayabaNerve Oct 30, 2022
7bccc51
Remove the Future triggering the machine for an async fn
kayabaNerve Oct 30, 2022
a15fe97
Connect the Tendermint machine to a GossipEngine
kayabaNerve Oct 30, 2022
85b4134
Create a dedicated file for being a Tendermint authority
kayabaNerve Oct 30, 2022
fac4787
Move serai_runtime specific code from tendermint/client to node
kayabaNerve Oct 30, 2022
dbdbdd5
Make sign asynchronous
kayabaNerve Nov 1, 2022
5dbdddd
Implement proper checking of inherents
kayabaNerve Nov 1, 2022
48de7ae
Take in a Keystore and validator ID
kayabaNerve Nov 2, 2022
e6c84f5
Update node to latest sc_tendermint
kayabaNerve Nov 2, 2022
c83c83f
Configure node for a multi-node testnet
kayabaNerve Nov 2, 2022
cc700dd
Fix handling of the GossipEngine
kayabaNerve Nov 2, 2022
b418293
Correct Serai d-f names in Docker
kayabaNerve Nov 2, 2022
cdc04e4
Update the consensus documentation
kayabaNerve Nov 2, 2022
5ee9a59
Make the dev profile a local testnet profile
kayabaNerve Nov 2, 2022
5562d01
Reduce Arcs in TendermintMachine, split Signer from SignatureScheme
kayabaNerve Nov 3, 2022
47dd92c
Update sc_tendermint per previous commit
kayabaNerve Nov 3, 2022
cdc2025
Correct Dave, Eve, and Ferdie to not run as validators
kayabaNerve Nov 3, 2022
8ee3c31
Rename dev to devnet
kayabaNerve Nov 3, 2022
028cf56
Localize the LibP2P protocol to the blockchain
kayabaNerve Nov 3, 2022
0367e31
Add missing trait
kayabaNerve Nov 3, 2022
62a63df
Bump Substrate dependency
kayabaNerve Nov 4, 2022
f733bbe
Implement Schnorr half-aggregation from https://eprint.iacr.org/2021/…
kayabaNerve Nov 4, 2022
e2e1ae3
cargo update (tendermint)
kayabaNerve Nov 4, 2022
849cb3c
Correct protocol name handling
kayabaNerve Nov 8, 2022
80d3ce6
Use futures mpsc instead of tokio
kayabaNerve Nov 9, 2022
a037853
Update Substrate to the new TendermintHandle
kayabaNerve Nov 9, 2022
fcfa6be
Separate the block processing time from the latency
kayabaNerve Nov 11, 2022
e165a03
Add notes to the runtime
kayabaNerve Nov 11, 2022
1795c3c
Don't spam slash
kayabaNerve Nov 11, 2022
99672e1
Support running TendermintMachine when not a validator
kayabaNerve Nov 11, 2022
6a502c2
Properly define and pass around the block size
kayabaNerve Nov 11, 2022
72aed6f
Have the machine respond to advances made by an external sync loop
kayabaNerve Nov 12, 2022
9ac40ae
Clean up time code in tendermint-machine
kayabaNerve Nov 12, 2022
6331c5f
BlockData and RoundData structs
kayabaNerve Nov 12, 2022
5c77026
Rename Round to RoundNumber
kayabaNerve Nov 12, 2022
75b3783
Move BlockData to a new file
kayabaNerve Nov 12, 2022
5b36231
Move Round to an Option due to the pseudo-uninitialized state we create
kayabaNerve Nov 12, 2022
998c420
Clear the Queue instead of draining and filtering
kayabaNerve Nov 12, 2022
fe02c47
BlockData::new
kayabaNerve Nov 12, 2022
c02f274
Move more code into block.rs
kayabaNerve Nov 13, 2022
3edcd8a
Have verify_precommit_signature return if it verified the signature
kayabaNerve Nov 13, 2022
efbfce9
Slight doc changes
kayabaNerve Nov 13, 2022
da996e1
Use a tmp DB for Serai in Docker
kayabaNerve Nov 14, 2022
61925e8
created shared volume between containers
vrx00 Nov 16, 2022
05e8d06
Complete the sh scripts
kayabaNerve Nov 17, 2022
c3c5807
Pass in the genesis time to Substrate
kayabaNerve Nov 17, 2022
814de9e
Correct pupulate_end_time
kayabaNerve Nov 17, 2022
5aa4a6a
Correct race conditions between add_block and step
kayabaNerve Nov 17, 2022
f4bd9c2
Update cargo deny
kayabaNerve Nov 17, 2022
634d657
rename genesis-service to genesis
TheArchitect108 Nov 17, 2022
4437318
Update Cargo.lock
kayabaNerve Nov 18, 2022
68fb8e1
Implement a basic staking pallet
kayabaNerve Nov 18, 2022
0a23b80
Add staking crates to Cargo deny
kayabaNerve Nov 18, 2022
ae16f3c
Add staking to the runtime
kayabaNerve Nov 18, 2022
de9780d
Correct runtime Cargo.toml whitespace
kayabaNerve Nov 20, 2022
8ff7a71
Misc lints
kayabaNerve Nov 21, 2022
a48e3bc
Resolve low-hanging review comments
kayabaNerve Nov 21, 2022
8296451
Mark genesis/entry-dev.sh as executable
kayabaNerve Nov 22, 2022
5120c1d
Prevent a commit from including the same signature multiple times
kayabaNerve Nov 23, 2022
c478be4
Update to latest nightly clippy
kayabaNerve Nov 23, 2022
f1f8e86
Improve documentation
kayabaNerve Nov 24, 2022
3a40232
Add log statements
kayabaNerve Nov 24, 2022
96e00e6
Clean TendermintAuthority::authority as possible
kayabaNerve Nov 26, 2022
42051f1
Have the devnet use the current time as the genesis
kayabaNerve Nov 29, 2022
d3403c7
Misc lints
kayabaNerve Dec 2, 2022
4157796
Rebase onto develop
kayabaNerve Jul 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ members = [
"substrate/in-instructions/primitives",
"substrate/in-instructions/pallet",

"substrate/staking/primitives",
"substrate/staking/pallet",

"substrate/validator-sets/primitives",
"substrate/validator-sets/pallet",

Expand Down
2 changes: 1 addition & 1 deletion common/std-shims/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
spin = "0.9"
spin = { version = "0.9", features = ["mutex", "once"] }
hashbrown = "0.14"

[features]
Expand Down
32 changes: 6 additions & 26 deletions common/std-shims/src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,42 +29,22 @@ pub use mutex_shim::{ShimMutex as Mutex, MutexGuard};
pub use std::sync::OnceLock;
#[cfg(not(feature = "std"))]
mod oncelock_shim {
use super::Mutex;
use spin::Once;

pub struct OnceLock<T>(Mutex<bool>, Option<T>);
pub struct OnceLock<T>(Once<T>);
impl<T> OnceLock<T> {
pub const fn new() -> OnceLock<T> {
OnceLock(Mutex::new(false), None)
OnceLock(Once::new())
}

// These return a distinct Option in case of None so another caller using get_or_init doesn't
// transform it from None to Some
pub fn get(&self) -> Option<&T> {
if !*self.0.lock() {
None
} else {
self.1.as_ref()
}
self.0.poll()
}
pub fn get_mut(&mut self) -> Option<&mut T> {
if !*self.0.lock() {
None
} else {
self.1.as_mut()
}
self.0.get_mut()
}

pub fn get_or_init<F: FnOnce() -> T>(&self, f: F) -> &T {
let mut lock = self.0.lock();
if !*lock {
unsafe {
core::ptr::addr_of!(self.1).cast_mut().write_unaligned(Some(f()));
}
}
*lock = true;
drop(lock);

self.get().unwrap()
self.0.call_once(f)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion common/zalloc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
zeroize = "^1.5"
zeroize = { version = "^1.5", default-features = false }

[features]
# Commented for now as it requires nightly and we don't use nightly
Expand Down
2 changes: 1 addition & 1 deletion coordinator/tributary/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ scale = { package = "parity-scale-codec", version = "3", features = ["derive"] }
futures = "0.3"
tendermint = { package = "tendermint-machine", path = "./tendermint" }

tokio = { version = "1", features = ["macros", "sync", "time", "rt"] }
tokio = { version = "1", features = ["sync", "time", "rt"] }

[features]
tests = []
5 changes: 4 additions & 1 deletion coordinator/tributary/tendermint/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ log = "0.4"
parity-scale-codec = { version = "3", features = ["derive"] }

futures = "0.3"
tokio = { version = "1", features = ["macros", "sync", "time", "rt"] }
tokio = { version = "1", features = ["sync", "time"] }

[dev-dependencies]
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
3 changes: 3 additions & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ exceptions = [

{ allow = ["AGPL-3.0"], name = "serai-validator-sets-pallet" },

{ allow = ["AGPL-3.0"], name = "serai-staking-primitives" },
{ allow = ["AGPL-3.0"], name = "serai-staking-pallet" },

{ allow = ["AGPL-3.0"], name = "serai-runtime" },
{ allow = ["AGPL-3.0"], name = "serai-node" },

Expand Down
5 changes: 4 additions & 1 deletion deploy/coins/bitcoin/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@ RUN grep bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz SHA256SUMS | sha256s
# Prepare Image
RUN tar xzvf bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz

FROM debian:bullseye-slim as image
FROM debian:bookworm-slim as image

WORKDIR /home/bitcoin
COPY --from=builder /home/bitcoin/* .
RUN mv bin/* /bin && mv lib/* /lib
COPY ./scripts /scripts

# Upgrade packages
RUN apt update && apt upgrade -y

EXPOSE 8332 8333 18332 18333 18443 18444
VOLUME ["/home/bitcoin/.bitcoin"]
5 changes: 4 additions & 1 deletion deploy/coins/monero/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ RUN gpg --keyserver hkp://keyserver.ubuntu.com:80 --keyserver-options no-self-si
RUN tar -xvjf monero-linux-x64-v${MONERO_VERSION}.tar.bz2 --strip-components=1

# Prepare Image
FROM debian:bullseye-slim as image
FROM debian:bookworm-slim as image

WORKDIR /home/monero
COPY --from=builder /home/monero/* .
RUN mv * /bin/
COPY ./scripts /scripts

# Upgrade packages
RUN apt update && apt upgrade -y

EXPOSE 18080 18081
VOLUME /home/monero/.bitmonero
7 changes: 5 additions & 2 deletions deploy/message-queue/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM rust:1.71 as builder
FROM rust:1.71-slim-bookworm as builder
LABEL description="STAGE 1: Build"

# Add files for build
Expand Down Expand Up @@ -29,7 +29,7 @@ RUN --mount=type=cache,target=/root/.cargo \
mv /serai/target/release/serai-message-queue /serai/bin

# Prepare Image
FROM debian:bullseye-slim as image
FROM debian:bookworm-slim as image
LABEL description="STAGE 2: Copy and Run"

WORKDIR /home/serai
Expand All @@ -38,6 +38,9 @@ WORKDIR /home/serai
COPY --from=builder /serai/bin/* /bin/
COPY --from=builder /serai/AGPL-3.0 .

# Upgrade packages
RUN apt update && apt upgrade -y

# Run message-queue
EXPOSE 2287
CMD ["serai-message-queue"]
13 changes: 9 additions & 4 deletions deploy/processor/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM docker.io/paritytech/ci-linux:production as builder
FROM rust:1.71-slim-bookworm as builder
LABEL description="STAGE 1: Build"

# Add files for build
Expand All @@ -16,6 +16,11 @@ ADD AGPL-3.0 /serai

WORKDIR /serai

RUN apt update && apt upgrade -y && apt install -y clang libssl-dev

# Add the wasm toolchain
RUN rustup target add wasm32-unknown-unknown

# Mount the caches and build
RUN --mount=type=cache,target=/root/.cargo \
--mount=type=cache,target=/usr/local/cargo/registry \
Expand All @@ -27,7 +32,7 @@ RUN --mount=type=cache,target=/root/.cargo \
mv /serai/target/release/serai-processor /serai/bin

# Prepare Image
FROM debian:bullseye-slim as image
FROM debian:bookworm-slim as image
LABEL description="STAGE 2: Copy and Run"

WORKDIR /home/serai
Expand All @@ -36,8 +41,8 @@ WORKDIR /home/serai
COPY --from=builder /serai/bin/* /bin/
COPY --from=builder /serai/AGPL-3.0 .

# Install openssl
RUN apt update && apt upgrade -y && apt install -y libssl
# Upgrade packages and install openssl
RUN apt update && apt upgrade -y && apt install -y libssl-dev

# Run processor
CMD ["serai-processor"]
37 changes: 13 additions & 24 deletions deploy/serai/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM docker.io/paritytech/ci-linux:production as builder
FROM rust:1.71-slim-bookworm as builder
LABEL description="STAGE 1: Build"

# Add files for build
Expand All @@ -16,34 +16,20 @@ ADD AGPL-3.0 /serai

WORKDIR /serai

# Update Rust
RUN rustup update

# Install Solc @ 0.8.16
RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/root/.local \
--mount=type=cache,target=/root/.solc-select \
pip3 install solc-select==0.2.1
RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/root/.local \
--mount=type=cache,target=/root/.solc-select \
solc-select install 0.8.16
RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/root/.local \
--mount=type=cache,target=/root/.solc-select \
solc-select use 0.8.16

# Mount cargo and the Serai cache
RUN --mount=type=cache,target=/root/.local \
--mount=type=cache,target=/root/.solc-select \
--mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/usr/local/cargo/git \
RUN apt update && apt upgrade -y && apt install -y make clang libssl-dev protobuf-compiler

# Add the wasm toolchain
RUN rustup target add wasm32-unknown-unknown

# Mount the caches and build
RUN --mount=type=cache,target=/root/.cargo \
--mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/serai/target \
cd substrate/node && cargo build --release

# Prepare Image
FROM ubuntu:latest as image
FROM debian:bookworm-slim as image
LABEL description="STAGE 2: Copy and Run"

WORKDIR /home/serai
Expand All @@ -52,6 +38,9 @@ WORKDIR /home/serai
COPY --from=builder /serai/target/release/ /bin/
COPY --from=builder /serai/AGPL-3.0 .

# Upgrade packages
RUN apt update && apt upgrade -y

# Run node
EXPOSE 30333 9615 9933 9944
CMD ["serai-node"]
9 changes: 7 additions & 2 deletions substrate/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ pallet-grandpa = { git = "https://github.com/serai-dex/substrate", default-featu

pallet-authority-discovery = { git = "https://github.com/serai-dex/substrate", default-features = false }

pallet-session = { git = "https://github.com/serai-dex/substrate", default-features = false }
staking-pallet = { package = "serai-staking-pallet", path = "../staking/pallet", default-features = false }

frame-system-rpc-runtime-api = { git = "https://github.com/serai-dex/substrate", default-features = false }
pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/serai-dex/substrate", default-features = false }

Expand Down Expand Up @@ -114,6 +117,8 @@ std = [
]

runtime-benchmarks = [
"hex-literal",

"sp-runtime/runtime-benchmarks",

"frame-system/runtime-benchmarks",
Expand All @@ -125,8 +130,8 @@ runtime-benchmarks = [
"pallet-balances/runtime-benchmarks",
"pallet-assets/runtime-benchmarks",

"pallet-babe/runtime-benchmarks",
"pallet-grandpa/runtime-benchmarks",
"pallet-babe/runtime-benchmarks",
"pallet-grandpa/runtime-benchmarks",
]

default = ["std"]
29 changes: 29 additions & 0 deletions substrate/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ use babe::AuthorityId as BabeId;
use grandpa::AuthorityId as GrandpaId;
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;

use pallet_session::PeriodicSessions;

/// An index to a block.
pub type BlockNumber = u64;

Expand Down Expand Up @@ -354,6 +356,32 @@ impl authority_discovery::Config for Runtime {
type MaxAuthorities = MaxAuthorities;
}

const SESSION_LENGTH: BlockNumber = 5 * DAYS;
type Sessions = PeriodicSessions<ConstU32<{ SESSION_LENGTH }>, ConstU32<{ SESSION_LENGTH }>>;

pub struct IdentityValidatorIdOf;
impl Convert<Public, Option<Public>> for IdentityValidatorIdOf {
fn convert(key: Public) -> Option<Public> {
Some(key)
}
}

impl pallet_session::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type ValidatorId = AccountId;
type ValidatorIdOf = IdentityValidatorIdOf;
type ShouldEndSession = Sessions;
type NextSessionRotation = Sessions;
type SessionManager = ();
type SessionHandler = <SessionKeys as OpaqueKeys>::KeyTypeIdProviders;
type Keys = SessionKeys;
type WeightInfo = pallet_session::weights::SubstrateWeight<Runtime>;
}

impl staking_pallet::Config for Runtime {
type Currency = Balances;
}

pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
pub type SignedExtra = (
Expand Down Expand Up @@ -393,6 +421,7 @@ construct_runtime!(
ValidatorSets: validator_sets,

Session: session,
Staking: staking,
Babe: babe,
Grandpa: grandpa,

Expand Down
36 changes: 36 additions & 0 deletions substrate/staking/pallet/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[package]
name = "serai-staking-pallet"
version = "0.1.0"
description = "Staking pallet for Serai"
license = "AGPL-3.0-only"
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/staking/pallet"
authors = ["Luke Parker <[email protected]>"]
edition = "2021"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
parity-scale-codec = { version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2", default-features = false, features = ["derive"] }

sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }

frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }

staking-primitives = { package = "serai-staking-primitives", path = "../primitives", default-features = false }

[features]
std = [
"frame-system/std",
"frame-support/std",
]

runtime-benchmarks = [
"frame-system/runtime-benchmarks",
"frame-support/runtime-benchmarks",
]

default = ["std"]
15 changes: 15 additions & 0 deletions substrate/staking/pallet/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
AGPL-3.0-only license

Copyright (c) 2022 Luke Parker

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License Version 3 as
published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Loading