From 5b4562e32100174674b2909d652b61415d77ef62 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Sat, 11 Jan 2020 07:29:14 +0100 Subject: [PATCH 01/13] Boilerplate: Add lite-node crate - ran `abscissa new lite-node` - added deps (Cargo.toml) and minimal changes to README.md - add to root workspace --- Cargo.toml | 1 + lite-node/.gitignore | 2 + lite-node/Cargo.toml | 22 ++++++ lite-node/README.md | 14 ++++ lite-node/src/application.rs | 109 ++++++++++++++++++++++++++++ lite-node/src/bin/lite_node/main.rs | 11 +++ lite-node/src/commands.rs | 69 ++++++++++++++++++ lite-node/src/commands/start.rs | 47 ++++++++++++ lite-node/src/commands/version.rs | 17 +++++ lite-node/src/config.rs | 45 ++++++++++++ lite-node/src/error.rs | 80 ++++++++++++++++++++ lite-node/src/lib.rs | 22 ++++++ lite-node/src/prelude.rs | 9 +++ lite-node/tests/acceptance.rs | 91 +++++++++++++++++++++++ 14 files changed, 539 insertions(+) create mode 100644 lite-node/.gitignore create mode 100644 lite-node/Cargo.toml create mode 100644 lite-node/README.md create mode 100644 lite-node/src/application.rs create mode 100644 lite-node/src/bin/lite_node/main.rs create mode 100644 lite-node/src/commands.rs create mode 100644 lite-node/src/commands/start.rs create mode 100644 lite-node/src/commands/version.rs create mode 100644 lite-node/src/config.rs create mode 100644 lite-node/src/error.rs create mode 100644 lite-node/src/lib.rs create mode 100644 lite-node/src/prelude.rs create mode 100644 lite-node/tests/acceptance.rs diff --git a/Cargo.toml b/Cargo.toml index 08269b4ce..86efdef5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,4 +3,5 @@ members = [ "tendermint", "tendermint-lite", + "lite-node", ] diff --git a/lite-node/.gitignore b/lite-node/.gitignore new file mode 100644 index 000000000..53eaa2196 --- /dev/null +++ b/lite-node/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/lite-node/Cargo.toml b/lite-node/Cargo.toml new file mode 100644 index 000000000..0a51df783 --- /dev/null +++ b/lite-node/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "lite_node" +authors = ["Ethan Buchman ", "Ismail Khoffi "] +version = "0.1.0" +edition = "2018" + +[dependencies] +gumdrop = "0.7" +serde = { version = "1", features = ["serde_derive"] } +tendermint = { version = "0.11", path = "../tendermint" } +tokio = "0.2" + +[dependencies.abscissa_core] +version = "0.5.0" +# optional: use `gimli` to capture backtraces +# see https://github.com/rust-lang/backtrace-rs/issues/189 +# features = ["gimli-backtrace"] + +[dev-dependencies] +abscissa_core = { version = "0.5.0", features = ["testing"] } +once_cell = "1.2" + diff --git a/lite-node/README.md b/lite-node/README.md new file mode 100644 index 000000000..6281b8ccc --- /dev/null +++ b/lite-node/README.md @@ -0,0 +1,14 @@ +# LiteNode + +Tendermint light client node. + +## Getting Started + +This application is authored using [Abscissa], a Rust application framework. + +For more information, see: + +[Documentation] + +[Abscissa]: https://github.com/iqlusioninc/abscissa +[Documentation]: https://docs.rs/abscissa_core/ diff --git a/lite-node/src/application.rs b/lite-node/src/application.rs new file mode 100644 index 000000000..cd4ccd111 --- /dev/null +++ b/lite-node/src/application.rs @@ -0,0 +1,109 @@ +//! LiteNode Abscissa Application + +use crate::{commands::LiteNodeCmd, config::LiteNodeConfig}; +use abscissa_core::{ + application::{self, AppCell}, + config, trace, Application, EntryPoint, FrameworkError, StandardPaths, +}; + +/// Application state +pub static APPLICATION: AppCell = AppCell::new(); + +/// Obtain a read-only (multi-reader) lock on the application state. +/// +/// Panics if the application state has not been initialized. +pub fn app_reader() -> application::lock::Reader { + APPLICATION.read() +} + +/// Obtain an exclusive mutable lock on the application state. +pub fn app_writer() -> application::lock::Writer { + APPLICATION.write() +} + +/// Obtain a read-only (multi-reader) lock on the application configuration. +/// +/// Panics if the application configuration has not been loaded. +pub fn app_config() -> config::Reader { + config::Reader::new(&APPLICATION) +} + +/// LiteNode Application +#[derive(Debug)] +pub struct LiteNodeApp { + /// Application configuration. + config: Option, + + /// Application state. + state: application::State, +} + +/// Initialize a new application instance. +/// +/// By default no configuration is loaded, and the framework state is +/// initialized to a default, empty state (no components, threads, etc). +impl Default for LiteNodeApp { + fn default() -> Self { + Self { + config: None, + state: application::State::default(), + } + } +} + +impl Application for LiteNodeApp { + /// Entrypoint command for this application. + type Cmd = EntryPoint; + + /// Application configuration. + type Cfg = LiteNodeConfig; + + /// Paths to resources within the application. + type Paths = StandardPaths; + + /// Accessor for application configuration. + fn config(&self) -> &LiteNodeConfig { + self.config.as_ref().expect("config not loaded") + } + + /// Borrow the application state immutably. + fn state(&self) -> &application::State { + &self.state + } + + /// Borrow the application state mutably. + fn state_mut(&mut self) -> &mut application::State { + &mut self.state + } + + /// Register all components used by this application. + /// + /// If you would like to add additional components to your application + /// beyond the default ones provided by the framework, this is the place + /// to do so. + fn register_components(&mut self, command: &Self::Cmd) -> Result<(), FrameworkError> { + let components = self.framework_components(command)?; + self.state.components.register(components) + } + + /// Post-configuration lifecycle callback. + /// + /// Called regardless of whether config is loaded to indicate this is the + /// time in app lifecycle when configuration would be loaded if + /// possible. + fn after_config(&mut self, config: Self::Cfg) -> Result<(), FrameworkError> { + // Configure components + self.state.components.after_config(&config)?; + self.config = Some(config); + Ok(()) + } + + /// Get tracing configuration from command-line options + fn tracing_config(&self, command: &EntryPoint) -> trace::Config { + if command.verbose { + trace::Config::verbose() + } else { + trace::Config::default() + } + } +} diff --git a/lite-node/src/bin/lite_node/main.rs b/lite-node/src/bin/lite_node/main.rs new file mode 100644 index 000000000..28868068c --- /dev/null +++ b/lite-node/src/bin/lite_node/main.rs @@ -0,0 +1,11 @@ +//! Main entry point for LiteNode + +#![deny(warnings, missing_docs, trivial_casts, unused_qualifications)] +#![forbid(unsafe_code)] + +use lite_node::application::APPLICATION; + +/// Boot LiteNode +fn main() { + abscissa_core::boot(&APPLICATION); +} diff --git a/lite-node/src/commands.rs b/lite-node/src/commands.rs new file mode 100644 index 000000000..ce39d4216 --- /dev/null +++ b/lite-node/src/commands.rs @@ -0,0 +1,69 @@ +//! LiteNode Subcommands +//! +//! This is where you specify the subcommands of your application. +//! +//! The default application comes with two subcommands: +//! +//! - `start`: launches the application +//! - `version`: print application version +//! +//! See the `impl Configurable` below for how to specify the path to the +//! application's configuration file. + +mod start; +mod version; + +use self::{start::StartCmd, version::VersionCmd}; +use crate::config::LiteNodeConfig; +use abscissa_core::{ + config::Override, Command, Configurable, FrameworkError, Help, Options, Runnable, +}; +use std::path::PathBuf; + +/// LiteNode Configuration Filename +pub const CONFIG_FILE: &str = "lite_node.toml"; + +/// LiteNode Subcommands +#[derive(Command, Debug, Options, Runnable)] +pub enum LiteNodeCmd { + /// The `help` subcommand + #[options(help = "get usage information")] + Help(Help), + + /// The `start` subcommand + #[options(help = "start the application")] + Start(StartCmd), + + /// The `version` subcommand + #[options(help = "display version information")] + Version(VersionCmd), +} + +/// This trait allows you to define how application configuration is loaded. +impl Configurable for LiteNodeCmd { + /// Location of the configuration file + fn config_path(&self) -> Option { + // Check if the config file exists, and if it does not, ignore it. + // If you'd like for a missing configuration file to be a hard error + // instead, always return `Some(CONFIG_FILE)` here. + let filename = PathBuf::from(CONFIG_FILE); + + if filename.exists() { + Some(filename) + } else { + None + } + } + + /// Apply changes to the config after it's been loaded, e.g. overriding + /// values in a config file using command-line options. + /// + /// This can be safely deleted if you don't want to override config + /// settings from command-line options. + fn process_config(&self, config: LiteNodeConfig) -> Result { + match self { + LiteNodeCmd::Start(cmd) => cmd.override_config(config), + _ => Ok(config), + } + } +} diff --git a/lite-node/src/commands/start.rs b/lite-node/src/commands/start.rs new file mode 100644 index 000000000..6a97033c6 --- /dev/null +++ b/lite-node/src/commands/start.rs @@ -0,0 +1,47 @@ +//! `start` subcommand - example of how to write a subcommand + +/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()` +/// accessors along with logging macros. Customize as you see fit. +use crate::prelude::*; + +use crate::config::LiteNodeConfig; +use abscissa_core::{config, Command, FrameworkError, Options, Runnable}; + +/// `start` subcommand +/// +/// The `Options` proc macro generates an option parser based on the struct +/// definition, and is defined in the `gumdrop` crate. See their documentation +/// for a more comprehensive example: +/// +/// +#[derive(Command, Debug, Options)] +pub struct StartCmd { + /// To whom are we saying hello? + #[options(free)] + recipient: Vec, +} + +impl Runnable for StartCmd { + /// Start the application. + fn run(&self) { + let config = app_config(); + // TODO(liamsi): light client loop goes here... + println!("Hello, {}!", &config.hello.recipient); + } +} + +impl config::Override for StartCmd { + // Process the given command line options, overriding settings from + // a configuration file using explicit flags taken from command-line + // arguments. + fn override_config( + &self, + mut config: LiteNodeConfig, + ) -> Result { + if !self.recipient.is_empty() { + config.hello.recipient = self.recipient.join(" "); + } + + Ok(config) + } +} diff --git a/lite-node/src/commands/version.rs b/lite-node/src/commands/version.rs new file mode 100644 index 000000000..4654b305d --- /dev/null +++ b/lite-node/src/commands/version.rs @@ -0,0 +1,17 @@ +//! `version` subcommand + +#![allow(clippy::never_loop)] + +use super::LiteNodeCmd; +use abscissa_core::{Command, Options, Runnable}; + +/// `version` subcommand +#[derive(Command, Debug, Default, Options)] +pub struct VersionCmd {} + +impl Runnable for VersionCmd { + /// Print version message + fn run(&self) { + println!("{} {}", LiteNodeCmd::name(), LiteNodeCmd::version()); + } +} diff --git a/lite-node/src/config.rs b/lite-node/src/config.rs new file mode 100644 index 000000000..fef4f73c0 --- /dev/null +++ b/lite-node/src/config.rs @@ -0,0 +1,45 @@ +//! LiteNode Config +//! +//! See instructions in `commands.rs` to specify the path to your +//! application's configuration file and/or command-line options +//! for specifying it. + +use serde::{Deserialize, Serialize}; + +/// LiteNode Configuration +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct LiteNodeConfig { + /// An example configuration section + pub hello: ExampleSection, +} + +/// Default configuration settings. +/// +/// Note: if your needs are as simple as below, you can +/// use `#[derive(Default)]` on LiteNodeConfig instead. +impl Default for LiteNodeConfig { + fn default() -> Self { + Self { + hello: ExampleSection::default(), + } + } +} + +/// Example configuration section. +/// +/// Delete this and replace it with your actual configuration structs. +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ExampleSection { + /// Example configuration value + pub recipient: String, +} + +impl Default for ExampleSection { + fn default() -> Self { + Self { + recipient: "world".to_owned(), + } + } +} diff --git a/lite-node/src/error.rs b/lite-node/src/error.rs new file mode 100644 index 000000000..d6713c5bf --- /dev/null +++ b/lite-node/src/error.rs @@ -0,0 +1,80 @@ +//! Error types + +use abscissa_core::error::{BoxError, Context}; +use std::{ + fmt::{self, Display}, + io, + ops::Deref, +}; + +/// Kinds of errors +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ErrorKind { + /// Error in configuration file + Config, + + /// Input/output error + Io, +} + +impl ErrorKind { + /// Create an error context from this error + pub fn context(self, source: impl Into) -> Context { + Context::new(self, Some(source.into())) + } +} + +impl Display for ErrorKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let description = match self { + ErrorKind::Config => "config error", + ErrorKind::Io => "I/O error", + }; + + f.write_str(description) + } +} + +impl std::error::Error for ErrorKind {} + +/// Error type +#[derive(Debug)] +pub struct Error(Box>); + +impl Deref for Error { + type Target = Context; + + fn deref(&self) -> &Context { + &self.0 + } +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.0.source() + } +} + +impl From for Error { + fn from(kind: ErrorKind) -> Self { + Context::new(kind, None).into() + } +} + +impl From> for Error { + fn from(context: Context) -> Self { + Error(Box::new(context)) + } +} + +impl From for Error { + fn from(err: io::Error) -> Self { + ErrorKind::Io.context(err).into() + } +} diff --git a/lite-node/src/lib.rs b/lite-node/src/lib.rs new file mode 100644 index 000000000..b53680cb3 --- /dev/null +++ b/lite-node/src/lib.rs @@ -0,0 +1,22 @@ +//! LiteNode +//! +//! Application based on the [Abscissa] framework. +//! +//! [Abscissa]: https://github.com/iqlusioninc/abscissa + +// Tip: Deny warnings with `RUSTFLAGS="-D warnings"` environment variable in CI + +#![forbid(unsafe_code)] +#![warn( + missing_docs, + rust_2018_idioms, + trivial_casts, + unused_lifetimes, + unused_qualifications +)] + +pub mod application; +pub mod commands; +pub mod config; +pub mod error; +pub mod prelude; diff --git a/lite-node/src/prelude.rs b/lite-node/src/prelude.rs new file mode 100644 index 000000000..b22d0225d --- /dev/null +++ b/lite-node/src/prelude.rs @@ -0,0 +1,9 @@ +//! Application-local prelude: conveniently import types/functions/macros +//! which are generally useful and should be available in every module with +//! `use crate::prelude::*; + +/// Abscissa core prelude +pub use abscissa_core::prelude::*; + +/// Application state accessors +pub use crate::application::{app_config, app_reader, app_writer}; diff --git a/lite-node/tests/acceptance.rs b/lite-node/tests/acceptance.rs new file mode 100644 index 000000000..024378ff6 --- /dev/null +++ b/lite-node/tests/acceptance.rs @@ -0,0 +1,91 @@ +//! Acceptance test: runs the application as a subprocess and asserts its +//! output for given argument combinations matches what is expected. +//! +//! Modify and/or delete these as you see fit to test the specific needs of +//! your application. +//! +//! For more information, see: +//! + +// Tip: Deny warnings with `RUSTFLAGS="-D warnings"` environment variable in CI + +#![forbid(unsafe_code)] +#![warn( + missing_docs, + rust_2018_idioms, + trivial_casts, + unused_lifetimes, + unused_qualifications +)] + +use abscissa_core::testing::prelude::*; +use lite_node::config::LiteNodeConfig; +use once_cell::sync::Lazy; + +/// Executes your application binary via `cargo run`. +/// +/// Storing this value as a [`Lazy`] static ensures that all instances of +/// the runner acquire a mutex when executing commands and inspecting +/// exit statuses, serializing what would otherwise be multithreaded +/// invocations as `cargo test` executes tests in parallel by default. +pub static RUNNER: Lazy = Lazy::new(CmdRunner::default); + +/// Use `LiteNodeConfig::default()` value if no config or args +#[test] +fn start_no_args() { + let mut runner = RUNNER.clone(); + let mut cmd = runner.arg("start").capture_stdout().run(); + cmd.stdout().expect_line("Hello, world!"); + cmd.wait().unwrap().expect_success(); +} + +/// Use command-line argument value +#[test] +fn start_with_args() { + let mut runner = RUNNER.clone(); + let mut cmd = runner + .args(&["start", "acceptance", "test"]) + .capture_stdout() + .run(); + + cmd.stdout().expect_line("Hello, acceptance test!"); + cmd.wait().unwrap().expect_success(); +} + +/// Use configured value +#[test] +fn start_with_config_no_args() { + let mut config = LiteNodeConfig::default(); + config.hello.recipient = "configured recipient".to_owned(); + let expected_line = format!("Hello, {}!", &config.hello.recipient); + + let mut runner = RUNNER.clone(); + let mut cmd = runner.config(&config).arg("start").capture_stdout().run(); + cmd.stdout().expect_line(&expected_line); + cmd.wait().unwrap().expect_success(); +} + +/// Override configured value with command-line argument +#[test] +fn start_with_config_and_args() { + let mut config = LiteNodeConfig::default(); + config.hello.recipient = "configured recipient".to_owned(); + + let mut runner = RUNNER.clone(); + let mut cmd = runner + .config(&config) + .args(&["start", "acceptance", "test"]) + .capture_stdout() + .run(); + + cmd.stdout().expect_line("Hello, acceptance test!"); + cmd.wait().unwrap().expect_success(); +} + +/// Example of a test which matches a regular expression +#[test] +fn version_no_args() { + let mut runner = RUNNER.clone(); + let mut cmd = runner.arg("version").capture_stdout().run(); + cmd.stdout().expect_regex(r"\A\w+ [\d\.\-]+\z"); +} From 73004d84c728065089625f57ea52888d32bbb537 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Sat, 11 Jan 2020 08:57:20 +0100 Subject: [PATCH 02/13] Added config options & copied code into new app crate - copied from tendermint-lite/src/main.rs to lite-node/src/command/start.rs --- lite-node/src/commands/start.rs | 145 ++++++++++++++++++++++++++++++-- lite-node/src/config.rs | 33 +++++--- lite-node/src/lib.rs | 5 ++ lite-node/src/requester.rs | 83 ++++++++++++++++++ lite-node/src/state.rs | 30 +++++++ lite-node/src/store.rs | 42 +++++++++ lite-node/src/threshold.rs | 11 +++ lite-node/tests/acceptance.rs | 16 ++-- 8 files changed, 343 insertions(+), 22 deletions(-) create mode 100644 lite-node/src/requester.rs create mode 100644 lite-node/src/state.rs create mode 100644 lite-node/src/store.rs create mode 100644 lite-node/src/threshold.rs diff --git a/lite-node/src/commands/start.rs b/lite-node/src/commands/start.rs index 6a97033c6..05fc16482 100644 --- a/lite-node/src/commands/start.rs +++ b/lite-node/src/commands/start.rs @@ -4,8 +4,24 @@ /// accessors along with logging macros. Customize as you see fit. use crate::prelude::*; +use core::future::Future; +use tendermint::hash; +use tendermint::lite; +use tendermint::lite::{Error, Header, Requester, SignedHeader, Store, TrustedState}; +use tendermint::rpc; +use tendermint::{block::Height, Hash}; +use tokio::runtime::Builder; + +use tendermint::lite::ValidatorSet as _; + use crate::config::LiteNodeConfig; +use crate::requester::RPCRequester; +use crate::state::State; +use crate::store::MemStore; +use crate::threshold::TrustThresholdOneThird; + use abscissa_core::{config, Command, FrameworkError, Options, Runnable}; +use std::time::{Duration, SystemTime}; /// `start` subcommand /// @@ -16,17 +32,82 @@ use abscissa_core::{config, Command, FrameworkError, Options, Runnable}; /// #[derive(Command, Debug, Options)] pub struct StartCmd { - /// To whom are we saying hello? + /// RPC address to request headers and validators from. #[options(free)] - recipient: Vec, + rpc_addr: String, } +// TODO: this should also somehow be configurable ... +// we can't simply add this as a field in the config because this either would +// be a trait (`TrustThreshold`) or immediately and impl thereof (`TrustThresholdOneThird`). +static THRESHOLD: &TrustThresholdOneThird = &TrustThresholdOneThird {}; + impl Runnable for StartCmd { /// Start the application. fn run(&self) { let config = app_config(); - // TODO(liamsi): light client loop goes here... - println!("Hello, {}!", &config.hello.recipient); + + let client = block_on(rpc::Client::new(&config.rpc_address.parse().unwrap())).unwrap(); + let req = RPCRequester::new(client); + let mut store = MemStore::new(); + + let vals_hash = Hash::from_hex_upper( + hash::Algorithm::Sha256, + &config.subjective_init.validators_hash, + ) + .unwrap(); + + println!("Requesting from {}.", config.rpc_address); + + subjective_init( + Height::from(config.subjective_init.height), + vals_hash, + &mut store, + &req, + ) + .unwrap(); + + loop { + let latest = (&req).signed_header(0).unwrap(); + let latest_peer_height = latest.header().height(); + + let latest = store.get(Height::from(0)).unwrap(); + let latest_height = latest.last_header().header().height(); + + // only bisect to higher heights + if latest_peer_height <= latest_height { + std::thread::sleep(Duration::new(1, 0)); + continue; + } + + println!( + "attempting bisection from height {:?} to height {:?}", + store + .get(Height::from(0)) + .unwrap() + .last_header() + .header() + .height(), + latest_peer_height, + ); + + let now = &SystemTime::now(); + lite::verify_and_update_bisection( + latest_peer_height, + THRESHOLD, // TODO + &config.trusting_period, + now, + &req, + &mut store, + ) + .unwrap(); + + println!("Succeeded bisecting!"); + + // notifications ? + + // sleep for a few secs ? + } } } @@ -38,10 +119,62 @@ impl config::Override for StartCmd { &self, mut config: LiteNodeConfig, ) -> Result { - if !self.recipient.is_empty() { - config.hello.recipient = self.recipient.join(" "); + if !self.rpc_addr.is_empty() { + config.rpc_address = self.rpc_addr.to_owned(); } Ok(config) } } + +/* + * The following is initialization logic that should have a + * function in the lite crate like: + * `subjective_init(height, vals_hash, store, requester) -> Result<(), Error` + * it would fetch the initial header/vals from the requester and populate a + * trusted state and store it in the store ... + * TODO: this should take traits ... but how to deal with the State ? + * TODO: better name ? +*/ +fn subjective_init( + height: Height, + vals_hash: Hash, + store: &mut MemStore, + req: &RPCRequester, +) -> Result<(), Error> { + if store.get(height).is_ok() { + // we already have this ! + return Ok(()); + } + + // check that the val hash matches + let vals = req.validator_set(height)?; + + if vals.hash() != vals_hash { + // TODO + panic!("vals hash dont match") + } + + let signed_header = req.signed_header(height)?; + + // TODO: validate signed_header.commit() with the vals ... + + let next_vals = req.validator_set(height.increment())?; + + // TODO: check next_vals ... + + let trusted_state = &State::new(&signed_header, &next_vals); + + store.add(trusted_state)?; + + Ok(()) +} + +fn block_on(future: F) -> F::Output { + Builder::new() + .basic_scheduler() + .enable_all() + .build() + .unwrap() + .block_on(future) +} diff --git a/lite-node/src/config.rs b/lite-node/src/config.rs index fef4f73c0..9ae811c56 100644 --- a/lite-node/src/config.rs +++ b/lite-node/src/config.rs @@ -5,13 +5,20 @@ //! for specifying it. use serde::{Deserialize, Serialize}; +use std::time::Duration; + +use tendermint::lite::TrustThreshold; /// LiteNode Configuration #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields)] pub struct LiteNodeConfig { - /// An example configuration section - pub hello: ExampleSection, + /// RPC address to request headers and validators from. + pub rpc_address: String, + /// The duration until we consider a trusted state as expired. + pub trusting_period: Duration, + /// Subjective initialization. + pub subjective_init: SubjectiveInit, } /// Default configuration settings. @@ -21,25 +28,31 @@ pub struct LiteNodeConfig { impl Default for LiteNodeConfig { fn default() -> Self { Self { - hello: ExampleSection::default(), + rpc_address: "localhost:26657".to_owned(), + trusting_period: Duration::new(6000, 0), + subjective_init: SubjectiveInit::default(), } } } -/// Example configuration section. +/// Configuration for subjective initialization. /// -/// Delete this and replace it with your actual configuration structs. +/// Contains the subjective height and validators hash (as a string formatted as hex). #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields)] -pub struct ExampleSection { - /// Example configuration value - pub recipient: String, +pub struct SubjectiveInit { + /// Subjective height. + pub height: u64, + /// Subjective validators hash. + pub validators_hash: String, } -impl Default for ExampleSection { +impl Default for SubjectiveInit { fn default() -> Self { Self { - recipient: "world".to_owned(), + height: 1, + validators_hash: "A5A7DEA707ADE6156F8A981777CA093F178FC790475F6EC659B6617E704871DD" + .to_owned(), } } } diff --git a/lite-node/src/lib.rs b/lite-node/src/lib.rs index b53680cb3..ba32ce846 100644 --- a/lite-node/src/lib.rs +++ b/lite-node/src/lib.rs @@ -20,3 +20,8 @@ pub mod commands; pub mod config; pub mod error; pub mod prelude; + +pub mod requester; +pub mod state; +pub mod store; +pub mod threshold; diff --git a/lite-node/src/requester.rs b/lite-node/src/requester.rs new file mode 100644 index 000000000..b7ffdb042 --- /dev/null +++ b/lite-node/src/requester.rs @@ -0,0 +1,83 @@ +use tendermint::block; +use tendermint::lite; +use tendermint::rpc; +use tendermint::validator; + +use core::future::Future; +use tokio::runtime::Builder; + +/// RPCRequester wraps the Tendermint rpc::Client. +pub struct RPCRequester { + client: rpc::Client, +} + +impl RPCRequester { + pub fn new(client: rpc::Client) -> Self { + RPCRequester { client } + } +} + +impl lite::types::Requester for RPCRequester { + type SignedHeader = block::signed_header::SignedHeader; + type ValidatorSet = validator::Set; + + /// Request the signed header at height h. + /// If h==0, request the latest signed header. + /// TODO: use an enum instead of h==0. + fn signed_header(&self, h: H) -> Result + where + H: Into, + { + let height: block::Height = h.into(); + let r = match height.value() { + 0 => block_on(self.client.latest_commit()), + _ => block_on(self.client.commit(height)), + }; + match r { + Ok(response) => Ok(response.signed_header), + Err(_error) => Err(lite::Error::RequestFailed), + } + } + + /// Request the validator set at height h. + fn validator_set(&self, h: H) -> Result + where + H: Into, + { + let r = block_on(self.client.validators(h)); + match r { + Ok(response) => Ok(validator::Set::new(response.validators)), + Err(_error) => Err(lite::Error::RequestFailed), + } + } +} + +pub fn block_on(future: F) -> F::Output { + Builder::new() + .basic_scheduler() + .enable_all() + .build() + .unwrap() + .block_on(future) +} + +#[cfg(test)] +mod tests { + use super::*; + use tendermint::lite::types::Header as LiteHeader; + use tendermint::lite::types::Requester as LiteRequester; + use tendermint::lite::types::SignedHeader as LiteSignedHeader; + use tendermint::lite::types::ValidatorSet as LiteValSet; + use tendermint::rpc; + + // TODO: integration test + #[test] + #[ignore] + fn test_val_set() { + let client = block_on(rpc::Client::new(&"localhost:26657".parse().unwrap())).unwrap(); + let req = RPCRequester::new(client); + let r1 = req.validator_set(5).unwrap(); + let r2 = req.signed_header(5).unwrap(); + assert_eq!(r1.hash(), r2.header().validators_hash()); + } +} diff --git a/lite-node/src/state.rs b/lite-node/src/state.rs new file mode 100644 index 000000000..e1601080e --- /dev/null +++ b/lite-node/src/state.rs @@ -0,0 +1,30 @@ +use tendermint::lite::TrustedState; +use tendermint::{block::signed_header::SignedHeader, validator::Set}; + +#[derive(Clone)] +pub struct State { + last_header: SignedHeader, + vals: Set, +} + +impl TrustedState for State { + type LastHeader = SignedHeader; + type ValidatorSet = Set; + + fn new(last_header: &Self::LastHeader, vals: &Self::ValidatorSet) -> Self { + State { + last_header: last_header.clone(), + vals: vals.clone(), + } + } + + // height H-1 + fn last_header(&self) -> &Self::LastHeader { + &self.last_header + } + + // height H + fn validators(&self) -> &Self::ValidatorSet { + &self.vals + } +} diff --git a/lite-node/src/store.rs b/lite-node/src/store.rs new file mode 100644 index 000000000..cbebe6697 --- /dev/null +++ b/lite-node/src/store.rs @@ -0,0 +1,42 @@ +use crate::state::State; +use tendermint::block::Height; +use tendermint::lite::{Error, Header, SignedHeader, Store, TrustedState}; + +use std::collections::HashMap; + +#[derive(Default)] +pub struct MemStore { + height: Height, + store: HashMap, +} + +impl MemStore { + pub fn new() -> MemStore { + MemStore { + height: Height::from(0), + store: HashMap::new(), + } + } +} + +impl Store for MemStore { + type TrustedState = State; + + fn add(&mut self, trusted: &Self::TrustedState) -> Result<(), Error> { + let height = trusted.last_header().header().height(); + self.height = height; + self.store.insert(height, trusted.clone()); + Ok(()) + } + + fn get(&self, h: Height) -> Result<&Self::TrustedState, Error> { + let mut height = h; + if h.value() == 0 { + height = self.height + } + match self.store.get(&height) { + Some(state) => Ok(state), + None => Err(Error::RequestFailed), + } + } +} diff --git a/lite-node/src/threshold.rs b/lite-node/src/threshold.rs new file mode 100644 index 000000000..b1978fa73 --- /dev/null +++ b/lite-node/src/threshold.rs @@ -0,0 +1,11 @@ +use tendermint::lite::TrustThreshold; + +pub struct TrustThresholdOneThird {} +impl TrustThreshold for TrustThresholdOneThird {} + +pub struct TrustThresholdTwoThirds {} +impl TrustThreshold for TrustThresholdTwoThirds { + fn is_enough_power(&self, signed_voting_power: u64, total_voting_power: u64) -> bool { + signed_voting_power * 3 > total_voting_power * 2 + } +} diff --git a/lite-node/tests/acceptance.rs b/lite-node/tests/acceptance.rs index 024378ff6..12257128f 100644 --- a/lite-node/tests/acceptance.rs +++ b/lite-node/tests/acceptance.rs @@ -32,15 +32,17 @@ pub static RUNNER: Lazy = Lazy::new(CmdRunner::default); /// Use `LiteNodeConfig::default()` value if no config or args #[test] +#[ignore] fn start_no_args() { let mut runner = RUNNER.clone(); let mut cmd = runner.arg("start").capture_stdout().run(); - cmd.stdout().expect_line("Hello, world!"); + cmd.stdout().expect_line(""); cmd.wait().unwrap().expect_success(); } /// Use command-line argument value #[test] +#[ignore] fn start_with_args() { let mut runner = RUNNER.clone(); let mut cmd = runner @@ -54,10 +56,11 @@ fn start_with_args() { /// Use configured value #[test] +#[ignore] fn start_with_config_no_args() { let mut config = LiteNodeConfig::default(); - config.hello.recipient = "configured recipient".to_owned(); - let expected_line = format!("Hello, {}!", &config.hello.recipient); + config.rpc_address = "localhost:26657".to_owned(); + let expected_line = format!("Requesting from {}.", &config.rpc_address); let mut runner = RUNNER.clone(); let mut cmd = runner.config(&config).arg("start").capture_stdout().run(); @@ -67,18 +70,19 @@ fn start_with_config_no_args() { /// Override configured value with command-line argument #[test] +#[ignore] fn start_with_config_and_args() { let mut config = LiteNodeConfig::default(); - config.hello.recipient = "configured recipient".to_owned(); + config.rpc_address = "localhost:26657".to_owned(); let mut runner = RUNNER.clone(); let mut cmd = runner .config(&config) - .args(&["start", "acceptance", "test"]) + .args(&["start", "other:26657"]) .capture_stdout() .run(); - cmd.stdout().expect_line("Hello, acceptance test!"); + cmd.stdout().expect_line("Requesting from other:26657."); cmd.wait().unwrap().expect_success(); } From 85f2bb3abdc8a244652fe00f86825a0726166027 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Sat, 11 Jan 2020 08:57:51 +0100 Subject: [PATCH 03/13] Delete tendermint-lite: replaced by lite-node --- Cargo.toml | 1 - lite-node/src/config.rs | 2 - lite-node/src/lib.rs | 4 +- tendermint-lite/Cargo.toml | 11 --- tendermint-lite/src/lib.rs | 2 - tendermint-lite/src/main.rs | 131 ------------------------------- tendermint-lite/src/requester.rs | 78 ------------------ tendermint-lite/src/store.rs | 41 ---------- 8 files changed, 2 insertions(+), 268 deletions(-) delete mode 100644 tendermint-lite/Cargo.toml delete mode 100644 tendermint-lite/src/lib.rs delete mode 100644 tendermint-lite/src/main.rs delete mode 100644 tendermint-lite/src/requester.rs delete mode 100644 tendermint-lite/src/store.rs diff --git a/Cargo.toml b/Cargo.toml index 86efdef5e..cf3828540 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,5 @@ members = [ "tendermint", - "tendermint-lite", "lite-node", ] diff --git a/lite-node/src/config.rs b/lite-node/src/config.rs index 9ae811c56..5a09b8942 100644 --- a/lite-node/src/config.rs +++ b/lite-node/src/config.rs @@ -7,8 +7,6 @@ use serde::{Deserialize, Serialize}; use std::time::Duration; -use tendermint::lite::TrustThreshold; - /// LiteNode Configuration #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields)] diff --git a/lite-node/src/lib.rs b/lite-node/src/lib.rs index ba32ce846..dc6200397 100644 --- a/lite-node/src/lib.rs +++ b/lite-node/src/lib.rs @@ -8,19 +8,19 @@ #![forbid(unsafe_code)] #![warn( - missing_docs, rust_2018_idioms, trivial_casts, unused_lifetimes, unused_qualifications )] +// TODO(ismail): add proper docs and remove this! +#![allow(missing_docs)] pub mod application; pub mod commands; pub mod config; pub mod error; pub mod prelude; - pub mod requester; pub mod state; pub mod store; diff --git a/tendermint-lite/Cargo.toml b/tendermint-lite/Cargo.toml deleted file mode 100644 index 85e388609..000000000 --- a/tendermint-lite/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "tendermint-lite" -version = "0.1.0" -authors = ["Ethan Buchman "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -tendermint = { path = "../tendermint" } -tokio = "0.2" diff --git a/tendermint-lite/src/lib.rs b/tendermint-lite/src/lib.rs deleted file mode 100644 index 226aebff2..000000000 --- a/tendermint-lite/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod requester; -pub mod store; diff --git a/tendermint-lite/src/main.rs b/tendermint-lite/src/main.rs deleted file mode 100644 index c6b9fce69..000000000 --- a/tendermint-lite/src/main.rs +++ /dev/null @@ -1,131 +0,0 @@ -use tendermint::hash; -use tendermint::lite; -use tendermint::lite::{Error, TrustThresholdFraction}; -use tendermint::lite::{Header as _, Requester as _, ValidatorSet as _}; -use tendermint::rpc; -use tendermint::{block::Height, Hash}; - -use tendermint_lite::{requester::RPCRequester, store::MemStore}; - -use core::future::Future; -use std::time::{Duration, SystemTime}; -use tendermint_lite::store::State; -use tokio::runtime::Builder; - -// TODO: these should be config/args -static SUBJECTIVE_HEIGHT: u64 = 1; -static SUBJECTIVE_VALS_HASH_HEX: &str = - "A5A7DEA707ADE6156F8A981777CA093F178FC790475F6EC659B6617E704871DD"; -static RPC_ADDR: &str = "localhost:26657"; - -pub fn block_on(future: F) -> F::Output { - Builder::new() - .basic_scheduler() - .enable_all() - .build() - .unwrap() - .block_on(future) -} - -fn main() { - // TODO: this should be config - let trusting_period = Duration::new(6000, 0); - - // setup requester for primary peer - let client = block_on(rpc::Client::new(&RPC_ADDR.parse().unwrap())).unwrap(); - let req = RPCRequester::new(client); - let mut store = MemStore::new(); - - let vals_hash = - Hash::from_hex_upper(hash::Algorithm::Sha256, SUBJECTIVE_VALS_HASH_HEX).unwrap(); - - subjective_init(Height::from(SUBJECTIVE_HEIGHT), vals_hash, &mut store, &req).unwrap(); - - loop { - let latest = (&req).signed_header(0).unwrap(); - let latest_peer_height = latest.header().height(); - - let latest = store.get(0).unwrap(); - let latest_height = latest.last_header().header().height(); - - // only bisect to higher heights - if latest_peer_height <= latest_height { - std::thread::sleep(Duration::new(1, 0)); - continue; - } - - println!( - "attempting bisection from height {:?} to height {:?}", - store.get(0).unwrap().last_header().header().height(), - latest_peer_height, - ); - - let now = &SystemTime::now(); - let trusted_state = store.get(0).expect("can not read trusted state"); - - let new_states = lite::verify_bisection( - trusted_state.clone(), - latest_peer_height, - TrustThresholdFraction::default(), - &trusting_period, - now, - &req, - ) - .unwrap(); - - for new_state in new_states { - store - .add(new_state) - .expect("couldn't store new trusted state"); - } - - println!("Succeeded bisecting!"); - - // notifications ? - - // sleep for a few secs ? - } -} - -/* - * The following is initialization logic that should have a - * function in the lite crate like: - * `subjective_init(height, vals_hash, store, requester) -> Result<(), Error` - * it would fetch the initial header/vals from the requester and populate a - * trusted state and store it in the store ... - * TODO: this should take traits ... but how to deal with the State ? - * TODO: better name ? - */ -fn subjective_init( - height: Height, - vals_hash: Hash, - store: &mut MemStore, - req: &RPCRequester, -) -> Result<(), Error> { - if store.get(height.value()).is_ok() { - // we already have this ! - return Ok(()); - } - - // check that the val hash matches - let vals = req.validator_set(height.value())?; - - if vals.hash() != vals_hash { - // TODO - panic!("vals hash dont match") - } - - let signed_header = req.signed_header(SUBJECTIVE_HEIGHT)?; - - // TODO: validate signed_header.commit() with the vals ... - - let next_vals = req.validator_set(height.increment().value())?; - - // TODO: check next_vals ... - - let trusted_state = State::new(&signed_header, &next_vals); - - store.add(trusted_state)?; - - Ok(()) -} diff --git a/tendermint-lite/src/requester.rs b/tendermint-lite/src/requester.rs deleted file mode 100644 index f27292a93..000000000 --- a/tendermint-lite/src/requester.rs +++ /dev/null @@ -1,78 +0,0 @@ -use tendermint::block::signed_header::SignedHeader as TMCommit; -use tendermint::block::Header as TMHeader; -use tendermint::rpc; -use tendermint::validator; -use tendermint::{block, lite}; - -use core::future::Future; -use tendermint::lite::{Height, SignedHeader}; -use tendermint::validator::Set; -use tokio::runtime::Builder; - -/// RPCRequester wraps the Tendermint rpc::Client. -pub struct RPCRequester { - client: rpc::Client, -} - -impl RPCRequester { - pub fn new(client: rpc::Client) -> Self { - RPCRequester { client } - } -} - -type TMSignedHeader = SignedHeader; - -impl lite::types::Requester for RPCRequester { - /// Request the signed header at height h. - /// If h==0, request the latest signed header. - /// TODO: use an enum instead of h==0. - fn signed_header(&self, h: Height) -> Result { - let height: block::Height = h.into(); - let r = match height.value() { - 0 => block_on(self.client.latest_commit()), - _ => block_on(self.client.commit(height)), - }; - match r { - Ok(response) => Ok(response.signed_header.into()), - Err(_error) => Err(lite::Error::RequestFailed), - } - } - - /// Request the validator set at height h. - fn validator_set(&self, h: Height) -> Result { - let r = block_on(self.client.validators(h)); - match r { - Ok(response) => Ok(validator::Set::new(response.validators)), - Err(_error) => Err(lite::Error::RequestFailed), - } - } -} - -pub fn block_on(future: F) -> F::Output { - Builder::new() - .basic_scheduler() - .enable_all() - .build() - .unwrap() - .block_on(future) -} - -#[cfg(test)] -mod tests { - use super::*; - use tendermint::lite::types::Header as LiteHeader; - use tendermint::lite::types::Requester as LiteRequester; - use tendermint::lite::types::ValidatorSet as LiteValSet; - use tendermint::rpc; - - // TODO: integration test - #[test] - #[ignore] - fn test_val_set() { - let client = block_on(rpc::Client::new(&"localhost:26657".parse().unwrap())).unwrap(); - let req = RPCRequester::new(client); - let r1 = req.validator_set(5).unwrap(); - let r2 = req.signed_header(5).unwrap(); - assert_eq!(r1.hash(), r2.header().validators_hash()); - } -} diff --git a/tendermint-lite/src/store.rs b/tendermint-lite/src/store.rs deleted file mode 100644 index c78eb01dd..000000000 --- a/tendermint-lite/src/store.rs +++ /dev/null @@ -1,41 +0,0 @@ -use tendermint::lite::{Error, Header, Height, TrustedState}; - -use std::collections::HashMap; -use tendermint::block; - -pub type State = TrustedState; - -#[derive(Default)] -pub struct MemStore { - height: Height, - store: HashMap, -} - -impl MemStore { - pub fn new() -> MemStore { - MemStore { - height: 0, - store: HashMap::new(), - } - } -} - -impl MemStore { - pub fn add(&mut self, trusted: State) -> Result<(), Error> { - let height = trusted.last_header().header().height(); - self.height = height; - self.store.insert(height, trusted); - Ok(()) - } - - pub fn get(&self, h: Height) -> Result<&State, Error> { - let mut height = h; - if h == 0 { - height = self.height - } - match self.store.get(&height) { - Some(state) => Ok(state), - None => Err(Error::RequestFailed), - } - } -} From 4ee6dfd17520c5812a41f79d2ed9959a89770975 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Sat, 11 Jan 2020 18:36:22 +0100 Subject: [PATCH 04/13] lite -> light --- Cargo.toml | 2 +- {lite-node => light-node}/.gitignore | 0 {lite-node => light-node}/Cargo.toml | 2 +- {lite-node => light-node}/README.md | 2 +- {lite-node => light-node}/src/application.rs | 30 +++++++++---------- .../src/bin/light_node}/main.rs | 6 ++-- {lite-node => light-node}/src/commands.rs | 18 +++++------ .../src/commands/start.rs | 8 ++--- .../src/commands/version.rs | 4 +-- {lite-node => light-node}/src/config.rs | 10 +++---- {lite-node => light-node}/src/error.rs | 0 {lite-node => light-node}/src/lib.rs | 2 +- {lite-node => light-node}/src/prelude.rs | 0 {lite-node => light-node}/src/requester.rs | 0 {lite-node => light-node}/src/state.rs | 0 {lite-node => light-node}/src/store.rs | 0 {lite-node => light-node}/src/threshold.rs | 0 {lite-node => light-node}/tests/acceptance.rs | 8 ++--- 18 files changed, 46 insertions(+), 46 deletions(-) rename {lite-node => light-node}/.gitignore (100%) rename {lite-node => light-node}/Cargo.toml (96%) rename {lite-node => light-node}/README.md (95%) rename {lite-node => light-node}/src/application.rs (79%) rename {lite-node/src/bin/lite_node => light-node/src/bin/light_node}/main.rs (61%) rename {lite-node => light-node}/src/commands.rs (80%) rename {lite-node => light-node}/src/commands/start.rs (96%) rename {lite-node => light-node}/src/commands/version.rs (74%) rename {lite-node => light-node}/src/config.rs (89%) rename {lite-node => light-node}/src/error.rs (100%) rename {lite-node => light-node}/src/lib.rs (97%) rename {lite-node => light-node}/src/prelude.rs (100%) rename {lite-node => light-node}/src/requester.rs (100%) rename {lite-node => light-node}/src/state.rs (100%) rename {lite-node => light-node}/src/store.rs (100%) rename {lite-node => light-node}/src/threshold.rs (100%) rename {lite-node => light-node}/tests/acceptance.rs (93%) diff --git a/Cargo.toml b/Cargo.toml index cf3828540..95befcacc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,5 +2,5 @@ members = [ "tendermint", - "lite-node", + "light-node", ] diff --git a/lite-node/.gitignore b/light-node/.gitignore similarity index 100% rename from lite-node/.gitignore rename to light-node/.gitignore diff --git a/lite-node/Cargo.toml b/light-node/Cargo.toml similarity index 96% rename from lite-node/Cargo.toml rename to light-node/Cargo.toml index 0a51df783..9a47833f9 100644 --- a/lite-node/Cargo.toml +++ b/light-node/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "lite_node" +name = "light_node" authors = ["Ethan Buchman ", "Ismail Khoffi "] version = "0.1.0" edition = "2018" diff --git a/lite-node/README.md b/light-node/README.md similarity index 95% rename from lite-node/README.md rename to light-node/README.md index 6281b8ccc..544af9540 100644 --- a/lite-node/README.md +++ b/light-node/README.md @@ -1,4 +1,4 @@ -# LiteNode +# LightNode Tendermint light client node. diff --git a/lite-node/src/application.rs b/light-node/src/application.rs similarity index 79% rename from lite-node/src/application.rs rename to light-node/src/application.rs index cd4ccd111..36b32fc6a 100644 --- a/lite-node/src/application.rs +++ b/light-node/src/application.rs @@ -1,38 +1,38 @@ -//! LiteNode Abscissa Application +//! LightNode Abscissa Application -use crate::{commands::LiteNodeCmd, config::LiteNodeConfig}; +use crate::{commands::LightNodeCmd, config::LightNodeConfig}; use abscissa_core::{ application::{self, AppCell}, config, trace, Application, EntryPoint, FrameworkError, StandardPaths, }; /// Application state -pub static APPLICATION: AppCell = AppCell::new(); +pub static APPLICATION: AppCell = AppCell::new(); /// Obtain a read-only (multi-reader) lock on the application state. /// /// Panics if the application state has not been initialized. -pub fn app_reader() -> application::lock::Reader { +pub fn app_reader() -> application::lock::Reader { APPLICATION.read() } /// Obtain an exclusive mutable lock on the application state. -pub fn app_writer() -> application::lock::Writer { +pub fn app_writer() -> application::lock::Writer { APPLICATION.write() } /// Obtain a read-only (multi-reader) lock on the application configuration. /// /// Panics if the application configuration has not been loaded. -pub fn app_config() -> config::Reader { +pub fn app_config() -> config::Reader { config::Reader::new(&APPLICATION) } -/// LiteNode Application +/// LightNode Application #[derive(Debug)] -pub struct LiteNodeApp { +pub struct LightNodeApp { /// Application configuration. - config: Option, + config: Option, /// Application state. state: application::State, @@ -42,7 +42,7 @@ pub struct LiteNodeApp { /// /// By default no configuration is loaded, and the framework state is /// initialized to a default, empty state (no components, threads, etc). -impl Default for LiteNodeApp { +impl Default for LightNodeApp { fn default() -> Self { Self { config: None, @@ -51,18 +51,18 @@ impl Default for LiteNodeApp { } } -impl Application for LiteNodeApp { +impl Application for LightNodeApp { /// Entrypoint command for this application. - type Cmd = EntryPoint; + type Cmd = EntryPoint; /// Application configuration. - type Cfg = LiteNodeConfig; + type Cfg = LightNodeConfig; /// Paths to resources within the application. type Paths = StandardPaths; /// Accessor for application configuration. - fn config(&self) -> &LiteNodeConfig { + fn config(&self) -> &LightNodeConfig { self.config.as_ref().expect("config not loaded") } @@ -99,7 +99,7 @@ impl Application for LiteNodeApp { } /// Get tracing configuration from command-line options - fn tracing_config(&self, command: &EntryPoint) -> trace::Config { + fn tracing_config(&self, command: &EntryPoint) -> trace::Config { if command.verbose { trace::Config::verbose() } else { diff --git a/lite-node/src/bin/lite_node/main.rs b/light-node/src/bin/light_node/main.rs similarity index 61% rename from lite-node/src/bin/lite_node/main.rs rename to light-node/src/bin/light_node/main.rs index 28868068c..253ff7cc5 100644 --- a/lite-node/src/bin/lite_node/main.rs +++ b/light-node/src/bin/light_node/main.rs @@ -1,11 +1,11 @@ -//! Main entry point for LiteNode +//! Main entry point for LightNode #![deny(warnings, missing_docs, trivial_casts, unused_qualifications)] #![forbid(unsafe_code)] -use lite_node::application::APPLICATION; +use light_node::application::APPLICATION; -/// Boot LiteNode +/// Boot LightNode fn main() { abscissa_core::boot(&APPLICATION); } diff --git a/lite-node/src/commands.rs b/light-node/src/commands.rs similarity index 80% rename from lite-node/src/commands.rs rename to light-node/src/commands.rs index ce39d4216..e03da0376 100644 --- a/lite-node/src/commands.rs +++ b/light-node/src/commands.rs @@ -1,4 +1,4 @@ -//! LiteNode Subcommands +//! LightNode Subcommands //! //! This is where you specify the subcommands of your application. //! @@ -14,18 +14,18 @@ mod start; mod version; use self::{start::StartCmd, version::VersionCmd}; -use crate::config::LiteNodeConfig; +use crate::config::LightNodeConfig; use abscissa_core::{ config::Override, Command, Configurable, FrameworkError, Help, Options, Runnable, }; use std::path::PathBuf; -/// LiteNode Configuration Filename -pub const CONFIG_FILE: &str = "lite_node.toml"; +/// LightNode Configuration Filename +pub const CONFIG_FILE: &str = "light_node.toml"; -/// LiteNode Subcommands +/// LightNode Subcommands #[derive(Command, Debug, Options, Runnable)] -pub enum LiteNodeCmd { +pub enum LightNodeCmd { /// The `help` subcommand #[options(help = "get usage information")] Help(Help), @@ -40,7 +40,7 @@ pub enum LiteNodeCmd { } /// This trait allows you to define how application configuration is loaded. -impl Configurable for LiteNodeCmd { +impl Configurable for LightNodeCmd { /// Location of the configuration file fn config_path(&self) -> Option { // Check if the config file exists, and if it does not, ignore it. @@ -60,9 +60,9 @@ impl Configurable for LiteNodeCmd { /// /// This can be safely deleted if you don't want to override config /// settings from command-line options. - fn process_config(&self, config: LiteNodeConfig) -> Result { + fn process_config(&self, config: LightNodeConfig) -> Result { match self { - LiteNodeCmd::Start(cmd) => cmd.override_config(config), + LightNodeCmd::Start(cmd) => cmd.override_config(config), _ => Ok(config), } } diff --git a/lite-node/src/commands/start.rs b/light-node/src/commands/start.rs similarity index 96% rename from lite-node/src/commands/start.rs rename to light-node/src/commands/start.rs index 05fc16482..e62b078ee 100644 --- a/lite-node/src/commands/start.rs +++ b/light-node/src/commands/start.rs @@ -14,7 +14,7 @@ use tokio::runtime::Builder; use tendermint::lite::ValidatorSet as _; -use crate::config::LiteNodeConfig; +use crate::config::LightNodeConfig; use crate::requester::RPCRequester; use crate::state::State; use crate::store::MemStore; @@ -111,14 +111,14 @@ impl Runnable for StartCmd { } } -impl config::Override for StartCmd { +impl config::Override for StartCmd { // Process the given command line options, overriding settings from // a configuration file using explicit flags taken from command-line // arguments. fn override_config( &self, - mut config: LiteNodeConfig, - ) -> Result { + mut config: LightNodeConfig, + ) -> Result { if !self.rpc_addr.is_empty() { config.rpc_address = self.rpc_addr.to_owned(); } diff --git a/lite-node/src/commands/version.rs b/light-node/src/commands/version.rs similarity index 74% rename from lite-node/src/commands/version.rs rename to light-node/src/commands/version.rs index 4654b305d..1c141eeb7 100644 --- a/lite-node/src/commands/version.rs +++ b/light-node/src/commands/version.rs @@ -2,7 +2,7 @@ #![allow(clippy::never_loop)] -use super::LiteNodeCmd; +use super::LightNodeCmd; use abscissa_core::{Command, Options, Runnable}; /// `version` subcommand @@ -12,6 +12,6 @@ pub struct VersionCmd {} impl Runnable for VersionCmd { /// Print version message fn run(&self) { - println!("{} {}", LiteNodeCmd::name(), LiteNodeCmd::version()); + println!("{} {}", LightNodeCmd::name(), LightNodeCmd::version()); } } diff --git a/lite-node/src/config.rs b/light-node/src/config.rs similarity index 89% rename from lite-node/src/config.rs rename to light-node/src/config.rs index 5a09b8942..9146bf9ee 100644 --- a/lite-node/src/config.rs +++ b/light-node/src/config.rs @@ -1,4 +1,4 @@ -//! LiteNode Config +//! LightNode Config //! //! See instructions in `commands.rs` to specify the path to your //! application's configuration file and/or command-line options @@ -7,10 +7,10 @@ use serde::{Deserialize, Serialize}; use std::time::Duration; -/// LiteNode Configuration +/// LightNode Configuration #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields)] -pub struct LiteNodeConfig { +pub struct LightNodeConfig { /// RPC address to request headers and validators from. pub rpc_address: String, /// The duration until we consider a trusted state as expired. @@ -22,8 +22,8 @@ pub struct LiteNodeConfig { /// Default configuration settings. /// /// Note: if your needs are as simple as below, you can -/// use `#[derive(Default)]` on LiteNodeConfig instead. -impl Default for LiteNodeConfig { +/// use `#[derive(Default)]` on LightNodeConfig instead. +impl Default for LightNodeConfig { fn default() -> Self { Self { rpc_address: "localhost:26657".to_owned(), diff --git a/lite-node/src/error.rs b/light-node/src/error.rs similarity index 100% rename from lite-node/src/error.rs rename to light-node/src/error.rs diff --git a/lite-node/src/lib.rs b/light-node/src/lib.rs similarity index 97% rename from lite-node/src/lib.rs rename to light-node/src/lib.rs index dc6200397..90b4e1caf 100644 --- a/lite-node/src/lib.rs +++ b/light-node/src/lib.rs @@ -1,4 +1,4 @@ -//! LiteNode +//! LightNode //! //! Application based on the [Abscissa] framework. //! diff --git a/lite-node/src/prelude.rs b/light-node/src/prelude.rs similarity index 100% rename from lite-node/src/prelude.rs rename to light-node/src/prelude.rs diff --git a/lite-node/src/requester.rs b/light-node/src/requester.rs similarity index 100% rename from lite-node/src/requester.rs rename to light-node/src/requester.rs diff --git a/lite-node/src/state.rs b/light-node/src/state.rs similarity index 100% rename from lite-node/src/state.rs rename to light-node/src/state.rs diff --git a/lite-node/src/store.rs b/light-node/src/store.rs similarity index 100% rename from lite-node/src/store.rs rename to light-node/src/store.rs diff --git a/lite-node/src/threshold.rs b/light-node/src/threshold.rs similarity index 100% rename from lite-node/src/threshold.rs rename to light-node/src/threshold.rs diff --git a/lite-node/tests/acceptance.rs b/light-node/tests/acceptance.rs similarity index 93% rename from lite-node/tests/acceptance.rs rename to light-node/tests/acceptance.rs index 12257128f..c112e7525 100644 --- a/lite-node/tests/acceptance.rs +++ b/light-node/tests/acceptance.rs @@ -19,7 +19,7 @@ )] use abscissa_core::testing::prelude::*; -use lite_node::config::LiteNodeConfig; +use light_node::config::LightNodeConfig; use once_cell::sync::Lazy; /// Executes your application binary via `cargo run`. @@ -30,7 +30,7 @@ use once_cell::sync::Lazy; /// invocations as `cargo test` executes tests in parallel by default. pub static RUNNER: Lazy = Lazy::new(CmdRunner::default); -/// Use `LiteNodeConfig::default()` value if no config or args +/// Use `LightNodeConfig::default()` value if no config or args #[test] #[ignore] fn start_no_args() { @@ -58,7 +58,7 @@ fn start_with_args() { #[test] #[ignore] fn start_with_config_no_args() { - let mut config = LiteNodeConfig::default(); + let mut config = LightNodeConfig::default(); config.rpc_address = "localhost:26657".to_owned(); let expected_line = format!("Requesting from {}.", &config.rpc_address); @@ -72,7 +72,7 @@ fn start_with_config_no_args() { #[test] #[ignore] fn start_with_config_and_args() { - let mut config = LiteNodeConfig::default(); + let mut config = LightNodeConfig::default(); config.rpc_address = "localhost:26657".to_owned(); let mut runner = RUNNER.clone(); From 7cc055fa9dade8347ddc351cc8a4e6fdff897d94 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Thu, 16 Jan 2020 19:14:08 +0100 Subject: [PATCH 05/13] minor improvements to comments / docs minor improvements to comments / docs --- light-node/src/commands.rs | 12 +++++------- light-node/src/commands/start.rs | 2 +- light-node/src/config.rs | 2 ++ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/light-node/src/commands.rs b/light-node/src/commands.rs index e03da0376..2ee520dd8 100644 --- a/light-node/src/commands.rs +++ b/light-node/src/commands.rs @@ -1,10 +1,8 @@ //! LightNode Subcommands //! -//! This is where you specify the subcommands of your application. +//! The light client supports the following subcommands: //! -//! The default application comes with two subcommands: -//! -//! - `start`: launches the application +//! - `start`: launches the light client //! - `version`: print application version //! //! See the `impl Configurable` below for how to specify the path to the @@ -30,11 +28,11 @@ pub enum LightNodeCmd { #[options(help = "get usage information")] Help(Help), - /// The `start` subcommand - #[options(help = "start the application")] + /// `start` the light client + #[options(help = "start the light client daemon with the given config or command line params")] Start(StartCmd), - /// The `version` subcommand + /// `version` of the light client #[options(help = "display version information")] Version(VersionCmd), } diff --git a/light-node/src/commands/start.rs b/light-node/src/commands/start.rs index e62b078ee..e0a1685e4 100644 --- a/light-node/src/commands/start.rs +++ b/light-node/src/commands/start.rs @@ -1,4 +1,4 @@ -//! `start` subcommand - example of how to write a subcommand +//! `start` subcommand - start the light node. /// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()` /// accessors along with logging macros. Customize as you see fit. diff --git a/light-node/src/config.rs b/light-node/src/config.rs index 9146bf9ee..63ac9bbb0 100644 --- a/light-node/src/config.rs +++ b/light-node/src/config.rs @@ -49,6 +49,8 @@ impl Default for SubjectiveInit { fn default() -> Self { Self { height: 1, + // TODO(liamsi): a default hash here does not make sense unless it is a valid hash + // from a public network validators_hash: "A5A7DEA707ADE6156F8A981777CA093F178FC790475F6EC659B6617E704871DD" .to_owned(), } From 02cb77675632dc54e21026d9b954fc1b15847cd4 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Sat, 8 Feb 2020 12:58:36 +0000 Subject: [PATCH 06/13] Fix a few merge hicks (catch up with latest changes from master) rename some vars and more logical bisection in cmd --- light-node/src/commands/start.rs | 49 ++++++++++---------------------- light-node/src/lib.rs | 2 -- light-node/src/state.rs | 30 ------------------- light-node/src/threshold.rs | 11 ------- 4 files changed, 15 insertions(+), 77 deletions(-) delete mode 100644 light-node/src/state.rs delete mode 100644 light-node/src/threshold.rs diff --git a/light-node/src/commands/start.rs b/light-node/src/commands/start.rs index e0a1685e4..0de9339d9 100644 --- a/light-node/src/commands/start.rs +++ b/light-node/src/commands/start.rs @@ -7,19 +7,16 @@ use crate::prelude::*; use core::future::Future; use tendermint::hash; use tendermint::lite; -use tendermint::lite::{Error, Header, Requester, SignedHeader, Store, TrustedState}; +use tendermint::lite::{Error, Header, Height, Requester, TrustThresholdFraction}; use tendermint::rpc; -use tendermint::{block::Height, Hash}; +use tendermint::Hash; use tokio::runtime::Builder; use tendermint::lite::ValidatorSet as _; use crate::config::LightNodeConfig; use crate::requester::RPCRequester; -use crate::state::State; -use crate::store::MemStore; -use crate::threshold::TrustThresholdOneThird; - +use crate::store::{MemStore, State}; use abscissa_core::{config, Command, FrameworkError, Options, Runnable}; use std::time::{Duration, SystemTime}; @@ -37,11 +34,6 @@ pub struct StartCmd { rpc_addr: String, } -// TODO: this should also somehow be configurable ... -// we can't simply add this as a field in the config because this either would -// be a trait (`TrustThreshold`) or immediately and impl thereof (`TrustThresholdOneThird`). -static THRESHOLD: &TrustThresholdOneThird = &TrustThresholdOneThird {}; - impl Runnable for StartCmd { /// Start the application. fn run(&self) { @@ -59,46 +51,35 @@ impl Runnable for StartCmd { println!("Requesting from {}.", config.rpc_address); - subjective_init( - Height::from(config.subjective_init.height), - vals_hash, - &mut store, - &req, - ) - .unwrap(); + subjective_init(config.subjective_init.height, vals_hash, &mut store, &req).unwrap(); loop { - let latest = (&req).signed_header(0).unwrap(); - let latest_peer_height = latest.header().height(); + let latest_sh = (&req).signed_header(0).unwrap(); + let latest_peer_height = latest_sh.header().height(); - let latest = store.get(Height::from(0)).unwrap(); - let latest_height = latest.last_header().header().height(); + let latest_trusted = store.get(0).unwrap(); + let latest_trusted_height = latest_trusted.last_header().header().height(); // only bisect to higher heights - if latest_peer_height <= latest_height { + if latest_peer_height <= latest_trusted_height { std::thread::sleep(Duration::new(1, 0)); continue; } println!( "attempting bisection from height {:?} to height {:?}", - store - .get(Height::from(0)) - .unwrap() - .last_header() - .header() - .height(), + latest_trusted_height, latest_peer_height, ); let now = &SystemTime::now(); - lite::verify_and_update_bisection( + lite::verify_bisection( + latest_trusted.to_owned(), latest_peer_height, - THRESHOLD, // TODO + TrustThresholdFraction::default(), // TODO &config.trusting_period, now, &req, - &mut store, ) .unwrap(); @@ -159,13 +140,13 @@ fn subjective_init( // TODO: validate signed_header.commit() with the vals ... - let next_vals = req.validator_set(height.increment())?; + let next_vals = req.validator_set(height + 1)?; // TODO: check next_vals ... let trusted_state = &State::new(&signed_header, &next_vals); - store.add(trusted_state)?; + store.add(trusted_state.to_owned())?; Ok(()) } diff --git a/light-node/src/lib.rs b/light-node/src/lib.rs index 90b4e1caf..22abf3d77 100644 --- a/light-node/src/lib.rs +++ b/light-node/src/lib.rs @@ -22,6 +22,4 @@ pub mod config; pub mod error; pub mod prelude; pub mod requester; -pub mod state; pub mod store; -pub mod threshold; diff --git a/light-node/src/state.rs b/light-node/src/state.rs deleted file mode 100644 index e1601080e..000000000 --- a/light-node/src/state.rs +++ /dev/null @@ -1,30 +0,0 @@ -use tendermint::lite::TrustedState; -use tendermint::{block::signed_header::SignedHeader, validator::Set}; - -#[derive(Clone)] -pub struct State { - last_header: SignedHeader, - vals: Set, -} - -impl TrustedState for State { - type LastHeader = SignedHeader; - type ValidatorSet = Set; - - fn new(last_header: &Self::LastHeader, vals: &Self::ValidatorSet) -> Self { - State { - last_header: last_header.clone(), - vals: vals.clone(), - } - } - - // height H-1 - fn last_header(&self) -> &Self::LastHeader { - &self.last_header - } - - // height H - fn validators(&self) -> &Self::ValidatorSet { - &self.vals - } -} diff --git a/light-node/src/threshold.rs b/light-node/src/threshold.rs deleted file mode 100644 index b1978fa73..000000000 --- a/light-node/src/threshold.rs +++ /dev/null @@ -1,11 +0,0 @@ -use tendermint::lite::TrustThreshold; - -pub struct TrustThresholdOneThird {} -impl TrustThreshold for TrustThresholdOneThird {} - -pub struct TrustThresholdTwoThirds {} -impl TrustThreshold for TrustThresholdTwoThirds { - fn is_enough_power(&self, signed_voting_power: u64, total_voting_power: u64) -> bool { - signed_voting_power * 3 > total_voting_power * 2 - } -} From 5cd3724e90c0c2cf6647fe70d4debc5324603760 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Sat, 8 Feb 2020 13:13:11 +0000 Subject: [PATCH 07/13] fix rebasing hicks --- light-node/Cargo.toml | 2 +- light-node/src/commands/start.rs | 3 +-- light-node/src/requester.rs | 25 ++++++++++--------------- light-node/src/store.rs | 21 ++++++++++----------- 4 files changed, 22 insertions(+), 29 deletions(-) diff --git a/light-node/Cargo.toml b/light-node/Cargo.toml index 9a47833f9..a81e074e4 100644 --- a/light-node/Cargo.toml +++ b/light-node/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [dependencies] gumdrop = "0.7" serde = { version = "1", features = ["serde_derive"] } -tendermint = { version = "0.11", path = "../tendermint" } +tendermint = { version = "0.12.0-rc0", path = "../tendermint" } tokio = "0.2" [dependencies.abscissa_core] diff --git a/light-node/src/commands/start.rs b/light-node/src/commands/start.rs index 0de9339d9..f8e4e6f7f 100644 --- a/light-node/src/commands/start.rs +++ b/light-node/src/commands/start.rs @@ -68,8 +68,7 @@ impl Runnable for StartCmd { println!( "attempting bisection from height {:?} to height {:?}", - latest_trusted_height, - latest_peer_height, + latest_trusted_height, latest_peer_height, ); let now = &SystemTime::now(); diff --git a/light-node/src/requester.rs b/light-node/src/requester.rs index b7ffdb042..f27292a93 100644 --- a/light-node/src/requester.rs +++ b/light-node/src/requester.rs @@ -1,9 +1,12 @@ -use tendermint::block; -use tendermint::lite; +use tendermint::block::signed_header::SignedHeader as TMCommit; +use tendermint::block::Header as TMHeader; use tendermint::rpc; use tendermint::validator; +use tendermint::{block, lite}; use core::future::Future; +use tendermint::lite::{Height, SignedHeader}; +use tendermint::validator::Set; use tokio::runtime::Builder; /// RPCRequester wraps the Tendermint rpc::Client. @@ -17,33 +20,26 @@ impl RPCRequester { } } -impl lite::types::Requester for RPCRequester { - type SignedHeader = block::signed_header::SignedHeader; - type ValidatorSet = validator::Set; +type TMSignedHeader = SignedHeader; +impl lite::types::Requester for RPCRequester { /// Request the signed header at height h. /// If h==0, request the latest signed header. /// TODO: use an enum instead of h==0. - fn signed_header(&self, h: H) -> Result - where - H: Into, - { + fn signed_header(&self, h: Height) -> Result { let height: block::Height = h.into(); let r = match height.value() { 0 => block_on(self.client.latest_commit()), _ => block_on(self.client.commit(height)), }; match r { - Ok(response) => Ok(response.signed_header), + Ok(response) => Ok(response.signed_header.into()), Err(_error) => Err(lite::Error::RequestFailed), } } /// Request the validator set at height h. - fn validator_set(&self, h: H) -> Result - where - H: Into, - { + fn validator_set(&self, h: Height) -> Result { let r = block_on(self.client.validators(h)); match r { Ok(response) => Ok(validator::Set::new(response.validators)), @@ -66,7 +62,6 @@ mod tests { use super::*; use tendermint::lite::types::Header as LiteHeader; use tendermint::lite::types::Requester as LiteRequester; - use tendermint::lite::types::SignedHeader as LiteSignedHeader; use tendermint::lite::types::ValidatorSet as LiteValSet; use tendermint::rpc; diff --git a/light-node/src/store.rs b/light-node/src/store.rs index cbebe6697..c78eb01dd 100644 --- a/light-node/src/store.rs +++ b/light-node/src/store.rs @@ -1,8 +1,9 @@ -use crate::state::State; -use tendermint::block::Height; -use tendermint::lite::{Error, Header, SignedHeader, Store, TrustedState}; +use tendermint::lite::{Error, Header, Height, TrustedState}; use std::collections::HashMap; +use tendermint::block; + +pub type State = TrustedState; #[derive(Default)] pub struct MemStore { @@ -13,25 +14,23 @@ pub struct MemStore { impl MemStore { pub fn new() -> MemStore { MemStore { - height: Height::from(0), + height: 0, store: HashMap::new(), } } } -impl Store for MemStore { - type TrustedState = State; - - fn add(&mut self, trusted: &Self::TrustedState) -> Result<(), Error> { +impl MemStore { + pub fn add(&mut self, trusted: State) -> Result<(), Error> { let height = trusted.last_header().header().height(); self.height = height; - self.store.insert(height, trusted.clone()); + self.store.insert(height, trusted); Ok(()) } - fn get(&self, h: Height) -> Result<&Self::TrustedState, Error> { + pub fn get(&self, h: Height) -> Result<&State, Error> { let mut height = h; - if h.value() == 0 { + if h == 0 { height = self.height } match self.store.get(&height) { From bacca0350ae869bc3fdae71c0ed808a6134f96d6 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 12 Feb 2020 09:19:39 -0500 Subject: [PATCH 08/13] Bucky/abscissify adr (#148) * adr-002 lite client (#54) * adr-002 lite client * finish adr * address Dev's comments * lite -> light * Apply suggestions from code review Co-Authored-By: Ismail Khoffi * updates from review * Apply suggestions from code review Co-Authored-By: Ismail Khoffi * update for better abstraction and latest changes * note about detection * update image. manager -> syncer * update image * update image * More detailed diagram of lite client data flow (#106) * refactor * refactor into more adrs * minor fixes from review * sync adr-003 with latest and call it * rust code blocks Co-authored-by: Ismail Khoffi Co-authored-by: jibrown * working on adr-004 Co-authored-by: Ismail Khoffi Co-authored-by: jibrown --- .../adr-002-light-client-adr-index.md | 70 ++++ .../adr-003-light-client-core-verification.md | 357 ++++++++++++++++++ docs/architecture/adr-004-light-client-cli.md | 91 +++++ .../adr-005-light-client-fork-detection.md | 91 +++++ docs/architecture/assets/light-node.png | Bin 0 -> 122270 bytes tendermint/src/lite/types.rs | 12 +- 6 files changed, 613 insertions(+), 8 deletions(-) create mode 100644 docs/architecture/adr-002-light-client-adr-index.md create mode 100644 docs/architecture/adr-003-light-client-core-verification.md create mode 100644 docs/architecture/adr-004-light-client-cli.md create mode 100644 docs/architecture/adr-005-light-client-fork-detection.md create mode 100644 docs/architecture/assets/light-node.png diff --git a/docs/architecture/adr-002-light-client-adr-index.md b/docs/architecture/adr-002-light-client-adr-index.md new file mode 100644 index 000000000..cb43b8d65 --- /dev/null +++ b/docs/architecture/adr-002-light-client-adr-index.md @@ -0,0 +1,70 @@ +# ADR 002: Light Client ADR Index + +## Changelog + +- 2019-10-30: First draft. +- 2019-12-27: Add diagram and align with the code. +- 2020-01-22: Refactor into multiple ADRs + +## Status + +ADR Index. + +## Context + +This is the first of multiple ADRs about the Tendermint light client in Rust. +Light client specification and implementation is an ongoing work in progress, in +[English][english-spec], +[TLA+][tla-spec], +[Go][go-impl], +and [Rust][rust-impl]. Note/TODO these links will soon be stale and need +updating. + +It is especially important to review the Motivation and Structure of the Light +Client as described in the English Specification before reading these ADRs for +the necessary context. + +Here we are concerned primarily with the implementation of a light node in Rust. +The light node process makes RPC requests to full nodes for blockchain data, uses +the core verification library to verify this data, and updates its state accordingly. +It also makes requests to other full nodes to detect and report on forks. + +That said, as much as possible, we would like the implementation here to be +re-usable for the IBC protocol, which supports communiction between blockchains. +In this case, instead of making RPC requests, IBC-enabled blockchains receive the relevant data in transactions and +verify it using the same core verification library. Thus implementations should +abstract over the source of data as necessary. +For more information on IBC, see the +[spec repo](https://github.com/cosmos/ics), +especially the specifications for +[client +semantics](https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics) +and [handler +interface](https://github.com/cosmos/ics/tree/master/spec/ics-025-handler-interface) + +A complete implementation of the light node includes many components, each of +which deserves their own ADR. This ADR thus serves as an index for those, and +may be updated over time as other components come into view or +existing components are iterated upon. + +Components include (TODO: links): + +- ADR-003 - Core Verification Library: Data types, traits, and public API for + the core verification protocol, with support for use in light nodes and IBC + implemented +- ADR-004 - Command Line Interface: Choice of framework for the CLI and + daemon, including config, arguments, logging, errors, etc. +- ADR-005 - Fork Detection Module: Simple module to manage an address book of + peers and search for conflicting commits + + +A schematic diagram of the light node, as taken from the [English +specification][english-spec], is provided below: + +![Light Node Diagram](assets/light-node.png). + + +[english-spec]: https://github.com/tendermint/spec/tree/bucky/light-reorg/spec/consensus/light +[tla-spec]: https://github.com/interchainio/verification/tree/igor/lite/spec/light-client +[go-impl]: https://github.com/tendermint/tendermint/tree/master/lite2 +[rust-impl]: https://github.com/interchainio/tendermint-rs/tree/master/tendermint-lite diff --git a/docs/architecture/adr-003-light-client-core-verification.md b/docs/architecture/adr-003-light-client-core-verification.md new file mode 100644 index 000000000..1e085a3ff --- /dev/null +++ b/docs/architecture/adr-003-light-client-core-verification.md @@ -0,0 +1,357 @@ +# ADR 003: Light Client Core Verification + +## Changelog + +This used to be part of ADR-002 but was factored out. + +- 2020-02-09: Realignment with latest code and finalize. +- 2020-01-22: Factor this new ADR out of ADR-002 and reduce to just the core +verification component. + +## Status + +Approved. Implemented. + +## Context + +The high level context for the light client is described in +[ADR-002](adr-002-light-client-adr-index.md). + +For reference, a schematic of the light node is below: + +![Light Node Diagram](assets/light-node.png). + +Here we focus on the core verification library, which is reflected in the +diagram as the "Light Client Verifier" and "Bisector". + +The light node is subjectively initialized, and then attempts to sync to a most +recent height by skipping straight to it. The verifier is the core logic +to check if a header can be trusted and if we can skip to it. +If the validator set has changed too much, the header can't be trusted, +and we'll have to request intermediate headers as necessary to sync through the validator set changes. + +Note the verifier should also work for IBC, though in IBC bisection can't be performed directly +since blockchains can't make HTTP requests. There, bisection is expected to be done by an external relayer +process that can make requests to full nodes and submit the results to an IBC enabled chain. + +The library should lend itself smoothly to both use cases and should be difficult to use incorrectly. + +The core verification operates primarily on data types that are already implemented +in tendermint-rs (headers, public keys, signatures, votes, etc.). The crux of it +is verifying validator sets by computing their merkle root, and verifying +commits by checking validator signatures. The particular data structures used by +Tendermint have considerably more features/functionality than needed for this, +hence the core verification library should abstract over it. + +Here we describe the core verification library, including: + +- traits +- public functions for IBC and light nodes +- implementations of traits for the existing Tendermint data-structures + +## Decision + +The implementation of the core verification involves two components: + +- a new `light` crate, containing traits and functions defining the core verification + logic +- a `light-impl` module within the `tendermint` crate, containing implementations of + the traits for the tendermint specific data structures + +The `light` crate should have minimal dependencies and not depend on +`tendermint`. This way it will be kept clean, easy to test, and easily used for +variatons on tendermint with different header and vote data types. + +The light crate exposes traits for the block header, validator set, and commit +with the minimum information necessary to support general light client logic. + +According to the specification, the key functionality the light client +verification requires is to determine the set of signers in a commit, and +to determine the voting power of those signers in another validator set. +Hence we can abstract over the lower-level detail of how signatures are +validated. + +### Header + +A Header must contain a height, time, and the hashes of the current and next validator sets. +It can be uniquely identified by its hash: + +```rust +pub trait Header { + fn height(&self) -> Height; + fn bft_time(&self) -> Time; + fn validators_hash(&self) -> Hash; + fn next_validators_hash(&self) -> Hash; + + fn hash(&self) -> Hash; +} +``` + +### Commit + +A commit in the blockchain contains the underlying signatures from validators, typically in +the form of votes. From the perspective of the light client, +we don't care about the signatures themselves. +We're only interested in knowing the total voting power from a given validator +set that signed for the given block. Hence we can abstract over underlying votes and +signatures and just expose a `voting_power_in`, as per the spec: + +```rust +pub trait Commit { + type ValidatorSet: ValidatorSet; + + fn header_hash(&self) -> Hash; + fn validate(&self, &Self::ValidatorSet) -> Result<(), Error>; + fn voting_power_in(&self, vals: &Self::ValidatorSet) -> Result; +} +``` +The `header_hash` is the header being committed to, +and `validate` performs Commit-structure-specific validations, +for instance checking the number of votes is correct, or that they are +all for the right block ID. + +Computing `voting_power_in` require access to a validator set +to get the public keys to verify signatures. But this specific relationship +between validators and commit structure isn't the business of the `light` crate; +it's rather how a kind of Tendermint Commit structure implements the `light` traits. +By making ValidatorSet an associated type of Commit, the light crate doesn't +have to know much about validators or how they relate to commits, so the ValidatorSet +trait can be much smaller, and we never even define the concept of an individual +validator. + +Note this means `Commit` is expected to have access to the ids of the validators that +signed, which can then be used to look them up in their ValidatorSet implementation. +This is presently true, since Commit's contain the validator addresses +along with the signatures. But if the addresses were removed, for instance to +save space, the Commit trait would need access to the relevant validator set to +get the Ids, and this validator set may be different than the one being passed +in `voting_power_in`. + +By abstracting over the underlying vote type, this trait can support +optimizations like batch verification of signatures, and the use of +agreggate signatures instead of individual votes. +So long as it can be determined what voting power of a given validator set +signed correctly for the commit. + +The method `voting_power_in` performs the underlying signature verifications. +It should return an error if any of them fail or are for the wrong header hash. + +Note the specification introduces a `signers(commit) -> validators` method that +returns the list of validators that signed a commit. However, such a method +would require access to the underlying validator set in order to verify the +commits, and it is only ever used in computing `voting_power_in`. Hence, we +dispence with it here in favour of a `voting_power_in` that operates on a +`Commit` and `ValidatorSet`. However, this also means that ValidatorSet will +need to expose facilities for determining wheter a validator signed correctly in +order for implementations to make use of it to compute `voting_power_in`. + +Note also that in Tendermint, commits are for a particular block ID, which +includes both a header hash and a "parts set hash". The latter is completely +irelevant to the light client, and can only be verified by downloading the full +block. Hence it is effectively ignored here. It would be great if Tendermint +could disentangle commits to the proposal block parts for gossip (ie. the parts +set hash) from commits to the header itself (ie. the header hash), but that's +left for the future. + +For more background on implementation of Tendermint commits and votes, see: +- [ADR-025](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-025-commit.md) +- [Validator Signing Spec](https://github.com/tendermint/tendermint/blob/master/docs/spec/consensus/signing.md) +- [Tendermint consensus specification](https://arxiv.org/abs/1807.04938) + +### Validator Set + +A validator set has a unique hash which must match what's in the header. +It also has a total power used for determining if the result of `voting_power_in` is greater +than a fraction of the total power. + +ValidatorSet is implemented as an associated type of Commit, where it's +necessary to compute `voting_power_in`, so the underyling implementation must +have some way to determine the voting power of the validators that signed, +since voting power is not found in the commit itself. + +Note we don't need to define individual validators since all the details of how validators relates to commits +is encapsulated in the Commit. + +```rust +pub trait ValidatorSet { + fn hash(&self) -> Hash; + fn total_power(&self) -> u64; +} +``` + +### State + +According to the spec, the light client is expected to have a store that it can +persist trusted headers and validators to. This is necessary to fetch the last trusted +validators to be used in verifying a new header, but it's also needed in case +any conflicting commits are discovered and they need to be published to the +blockchain. That said, it's not needed for the core verification, so we don't +include it here. Users of the `light` crate like a light node decide how to +manage the state. We do include some convenience structus: + +```rust +pub struct SignedHeader +where + C: Commit, + H: Header, +{ + commit: C, + header: H, +} + +pub struct TrustedState{ +where + C: Commit, + H: Header, +{ + last_header: SignedHeader, + validators: C::ValidatorSet, +} +``` + +Here the trusted state combines both the signed header and the validator set, +ready to be persisted to some external store. + +### TrustThreshold + +The amount of validator set change that occur when skipping to a higher height depends on the +trust threshold, as per the spec. Here we define it as a trait that encapsulated the math of what percent +of validators need to sign: + +```rust +pub trait TrustThreshold: Copy + Clone { + fn is_enough_power(&self, signed_voting_power: u64, total_voting_power: u64) -> bool; +} +``` + +We provide a conenvient implementation that takes a numerator and a denominator. The default is of course 1/3. + +### Requester + +The light node needs to make requests to full nodes during bisection for intermediate signed headers and validator sets: + +```rust +pub trait Requester +where + C: Commit, + H: Header, +{ + fn signed_header(&self, h: Height) -> Result, Error>; + fn validator_set(&self, h: Height) -> Result; +} +``` + +In practice, this can be implemented as a Tendermint RPC client making requests +to the `/commit` and `/validators` endpoints of full nodes. +For testing, the Requester can be implemented by JSON files. + +### Verification + +Both IBC and full node syncing have to perform a common set of checks: + +- validate the hashes +- if the header is sequential, validate the next validator set +- if the header is not sequential, check if the trust threshold is reached + - this uses `voting_power_in` with a validator set that may be different from +the one that actually created the commit. +- check that +2/3 of the validators signed + - this uses `voting_power_in` with the actual validator set + +These are implemented in a common function, `verify_single_inner`: + +```rust +fn verify_single_inner( + trusted_state: &TrustedState, + untrusted_sh: &SignedHeader, + untrusted_vals: &C::ValidatorSet, + untrusted_next_vals: &C::ValidatorSet, + trust_threshold: L, +) -> Result<(), Error> +``` + +Note however that light client security model is highly sensitive to time, so the public functions +exposed for IBC and bisection, which will call `verify_single_inner`, must take a current time +and check we haven't expired. + +For IBC, since it can't make its own requests, the public function just takes the untrusted state +in full, and return it as a TrustedState if it verifies: + +```rust +pub fn verify_single( + trusted_state: TrustedState, + untrusted_sh: &SignedHeader, + untrusted_vals: &C::ValidatorSet, + untrusted_next_vals: &C::ValidatorSet, + trust_threshold: T, + trusting_period: &Duration, + now: &SystemTime, +) -> Result, Error> +``` + +For the light node, we pass in a Requester, and specify a height we want to sync to. +It will fetch that header and try to verify it using the skipping method, +and will run a bisection algorithm to recursively request headers of lower height +as needed. It returns a list of headers it verified along the way: + +```rust +pub fn verify_bisection( + trusted_state: TrustedState, + untrusted_height: Height, + trust_threshold: L, + trusting_period: &Duration, + now: &SystemTime, + req: &R, +) -> Result>, Error> +``` + +### Implementing Traits + +The core `light` traits can be implemented by the Tendermint data structures and +their variations. For instance, v0.33 of Tendermint Core introduced a breaking +change to the Commit structure to make it much smaller. We can implement the +`light` traits for both versions of the Commit structure. + +The `light` abstractions also facilitate testing, as complete Tendermint data structures +are not required to test the light client logic, only +the elements it cares about. This means we can implement mock commits and validators, +where validators are just numbered 0,1,2... and both commits and validators are +simply lists of integers representing the validators that signed or are in the +validator set. This aligns closely with how these structures are represented in +the [TLA+ +spec](https://github.com/interchainio/verification/blob/develop/spec/light-client/Blockchain.tla). + +While this provides a lot of flexibility in mocking out +the types, we must be careful to ensure they match the semantics of the actual +Tendermint types, and that we still test the verification logic sufficiently for +the actual types. + +### Other Validation + +Some fields in the header are left explicitly unvalidated as they have minimal bearing on the correctness of the light client. +These include: + +- LastCommitHash + - In the skipping case, it's not possible to verify the header refers to the correct previous block without reverting to the sequential case. So in the sequential case, we don't validate this either. If it's incorrect, in indicates the validators are misbehaving, though we can only detect it as a light client if there's a fork. +- BlockID + - As mentioned, this includes a merkle root of the entire block and is not verifiable without downloading the whole block, which would defeat the purpose of the light client! +- Time + - Verifying the time would require us to download the commit for the previous block, and to take the median of the timestamps from that commit. This would add significant overhead to the light client (an extra commit to validate for every block!). If the time is incorrect, in indicates that the validators are explicitly violating the protocol rules in a detectable way which full nodes should detect in the first place and shouldn't forward to light clients, so there would probably be bigger issues at foot. + +There are likely a few other instances of things the light client is not validating that it in theory could but likely indicate some larger problem afoot that the client can't do anything about anyways. Hence we really only focus on the correctness of commits and validator sets and detecting forks! + + +## Consequences + +### Positive + +- Clear separation between verification logic and the actual data types +- Traits can be easily mocked without real keys/signatures +- Public interface is hard to misuse. + +### Negative + +- Abstract traits requires more coding, more opportunity for bugs in trait implementation + +### Neutral + +- Certain validity checks are ommitted since they have little bearing diff --git a/docs/architecture/adr-004-light-client-cli.md b/docs/architecture/adr-004-light-client-cli.md new file mode 100644 index 000000000..50c4d0ea1 --- /dev/null +++ b/docs/architecture/adr-004-light-client-cli.md @@ -0,0 +1,91 @@ +# ADR 004: Light Client CLI + +## Changelog + +- 2020-02-09: Update about Abscissa +- 2020-01-22: Some content copied from old ADR-002 + +## Status + +WIP. + +## Context + +The high level context for the light client is described in +[ADR-002](adr-002-light-client-adr-index.md). + +For reference, a schematic of the light node is below: + +![Light Node Diagram](assets/light-node.png). + +Here we focus on how the Light Node process itself is composed. +The light node process must consider the following features: + +- command line UX and flags +- config file +- logging +- error handling +- state management +- exposing RPC servers + +Ideally, it can support all of this with a minimum of dependencies. + +We'd like to be able to start a light node process and have it sync to the +latest height and stay synced while it runs. + +## Decision + +### Abscissa + +[Abscissa](https://github.com/iqlusioninc/abscissa) is a framework for building CLI +tools in Rust by Tony Arcieri of Iqlusion. +It's focus is on security and minimizing dependencies. +The full list of dependencies can be found [here](https://github.com/iqlusioninc/abscissa#depencencies). + +For instance, while it includes functionality for command-line option parsing like that +provided by `structopt` + `clap`, it does so with far less dependencies. + +[Users](https://github.com/iqlusioninc/abscissa#projects-using-abscissa) +of note include the [Tendermint KMS](https://github.com/tendermint/kms) +for validators and the new +[Zebra ZCash full node](https://github.com/ZcashFoundation/zebra). + +See the [introductory blog +post](https://iqlusion.blog/introducing-abscissa-rust-application-framework) +for more details. + +### Config + +Config includes: + +- trusting period +- initial list of full nodes +- method (sequential or skipping) +- trust level (if method==skipping) + +The configuration contains an initial list of full nodes (peers). +For the sake of simplicity, one of the peers is selected as the "primary", while the +rest are considered "backups". Most of the data is downloaded from the primary, +and double checked against the backups. + +The state is considered "expired" if the difference between the current time and +the time from the trusted header is greater than a configurable "trusting +period". If at any point the state is expired, the node should log an error and +exit - it's needs to be manually reset. + + +### Initialization + +The node is initialized with a trusted header for some height and a validator set for the next height. + +The node may be initialized by the user with only a height and header hash, and +proceed to request the full header and validator set from a full node. This +reduces the initialization burden on the user, and simplifies passing this +information into the process, but for the state to be properly initialized it +will need to get the correct header and validator set before starting the light +client syncing protocol. + +### State + +The light node will need to maintain state including the current height, the +last verified and trusted header, and the current set of trusted validators. diff --git a/docs/architecture/adr-005-light-client-fork-detection.md b/docs/architecture/adr-005-light-client-fork-detection.md new file mode 100644 index 000000000..3030278d6 --- /dev/null +++ b/docs/architecture/adr-005-light-client-fork-detection.md @@ -0,0 +1,91 @@ +# ADR 005: Light Client Fork Detection + +## Changelog + +2020-01-22: Some content copied from old ADR-002 + +## Status + +WIP. Just copied over from old ADR. Needs rework + +## Context + +In addition to the core verification logic, the light node needs a way to +receive data from full nodes, to detect conflicting information, and to report +on conflicts. While there are many ways for a full node to provide bad +information, what we're really looking for is misbehaviour by the validators, +which is reflected in conflicting commits (ie. commits for different blocks at +the same height). + +### Detect + +The detection module is for checking if any of the backup nodes +are reporting conflicting information. It requests headers from each backup node +and compares them with a verified header from the primary. If there is a +conflict, it attempts to verify the conflicting header via the verifier. If it +can be verified, it indicates an attack on the light clients that should be +punishable. The relevant information (ie. the two conflicting commits) are +passed to the publisher. + +### Publisher + +For now, the publisher just logs an error, writes the conflicting commits to a +file, and exits. We leave it to a future document to describe how this +information can actually be published to the blockchain so the validators can be +punished. Tendermint may need to expose a new RPC endpoint to facilitate this. + +See related [Tendermint issue #3244](https://github.com/tendermint/tendermint/issues/3244). + +### Address Book + +For now this is a simple list of HTTPS addresses corresponding to full nodes +that the node connects to. One is randomly selected to be the primary, while +others serve as backups. It's essential that the light node connect to at least +one correct full node in order to detect conflicts in a timely fashion. We keep +this mechanism simple for now, but in the future a more advanced peer discovery +mechanism may be utilized. + + +#### Sequential Sync + +Inital state: + + - time T + - height H + - header[H-1] + - vals[H] + +Here we describe the happy path: + +1) Request header[H], commit[H], and vals[H+1] from the primary, and check that they are well formed and from the correct height +2) Pass header[H], commit[H], vals[H], and vals[H+1] to the verification library, which will: + + - check that vals[H] and vals[H+1] are correctly reflected in header[H] + - check that commit[H] is for header[H] + - check that +2/3 of the validators correctly signed the hash of header[H] + +3) Request header[H] from each of the backups and check that they match header[H] received from the primary +4) Update the state with header[H] and vals[H+1], and increment H +5) return to (1) + +If (1) or (2) fails, mark the primary as bad and select a new peer to be the +primary. + +If (3) returns a conflicting header, verify the header by requesting the +corresponding commit and running the verification of (2). If the verification +passes, there is a fork, and evidence should be published so the validators get +slashed. We leave the mechanics of evidence to a future document. For now, the +light client will just log an error and exit. If the verification fails, it +means the backup that provided the conflict is bad and should be removed. + +#### Skipping Sync + +Skipping sync is essentially the same as sequential, except for a few points: + +- instead of verifying sequential headers, we attempt to "skip" ahead to the + full node's most recent height +- skipping is only permitted if the validator set has not changed too much - ie. + if +1/3 of the last trusted validator set has signed the commit for the height we're attempting to skip to +- if the validator set changes too much, we "bisect" the height space, + attempting to skip to a lower height, recursively. +- in the worst case, the bisection takes us to a sequential height diff --git a/docs/architecture/assets/light-node.png b/docs/architecture/assets/light-node.png new file mode 100644 index 0000000000000000000000000000000000000000..f0b93c6e415ebc39bc2313c885faacb35aa7af9a GIT binary patch literal 122270 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ɸa2^db4^;{u=6(Fa8oE-0a}M)8Mf z2Wdt?orq+%Updy+L}e~vd(n;WciZ8bf5_FgUV>dGX?wb^B!yHSl1FoCo|8U*nVB>0 zzeIa*=kI@KI(YhI8}**J)s=P3vcTK?FM^0(xEs{=VM@QGriNS0>Kd*OLs8oL-0h3y6~w4(mJy$+ZdW!(UcXwtk;@hg^1ED|)e&w@Z+jmp zV*1JKQ)A9hpux#{yl~kyA(xuX%LJHs&k%VB_P=HLLVS_OqDv~ufih4+^V3JWmV@E+ zUi8X(;H^xo1$=aA`&QfGFl-vBq+e~>MQRD?!$c-M+b3HX=fDt^V97$xJKS4R*Pa2GZUw@0rF$q3)`|CkHx>^;Wdm(Xt#OeItWEpPe4>vD{wiPZp z))M*81ODys`0v3X3ntaN=pAu=-k3!Af(|YnsIFz1*>Y$%dR4iae}W1sM~v|c2g%$0 zBUCMl9k^X!A}$f`2cNtOYLdt7dYQ-fAI3K3T`}Ru9~<1^^gVD2%M$RUoI~n7%_X_Z z>zqy<3GC$LI(e-rT(6;*xE3mq)N)pC4q49q49RQJl580m;1e8^kUpQ+Qr|CBMhxr` z>{)(@VvH>GvV`5X4$Vg?YJIIx(;*yBdgqjF3w+I_nJKLaVE+g2JRG0LsTCfNG7gk-(F2|VN7E6B#5 z2x9R1!gfO@KTlfzU{k4!-6g!XGKw)g#|wUow&!(_D?|1WmOp>PtRpC~xkW32$3m2t z$H>xpr%Aw4BvTX5wiQ_AQWsn$yT>GCZY0XrBWX~*{q^&iPLh<{IDJ@-mt|;Eo{!8C zQ?a=}lMX8J3&NuK^%YYOQSkKFF{fv)+bZ+9Z58QXfKy#CLR)<2=_0}c{h@+1g6%Yi ztEZVz&x(j5vUTD%@+*v57&<*HpKY(mrupF*e_i)KS|0iYp4=9X$abOyNZ-G8Rv5T*1LLGnH)Tq*sEjI$NBXPP(}ugL##aXB8s zL?fEY+5_07Esv&#-qqBjeg?U`*h{S%@r<#UPpFG6&Gyx^4e@eAf%w{~kBuTr?&I{Y zKY77r7dM+ZRcF0g$JCE425qxSdqiE6VNakpd)b~^_bYM5IWJ$n+HdRJzGSv{FYCW) z?Pz%sU+xRKL%52L(w|F~zc9DtWyNY&soipFdNwjjdEd$%Kw)K9AI;}9P0S{SR3w3S zqg1DiES{k&u3eXPxv{f0R8NmD{AiukwKZ8oz;H3YldfM5R(d*T>tzO@i364hQ%CgCiL#yXDo!u6nt$3r)l@ zq0jVHo{IdL4IZhv90V+4KCT=$gycXcZQIMK@V2y^54ver%o^NZLwWo~wJQ*fqnfE8 zb=3=$T`0wViXtRmuukE$=d5_AEj8DKf8KCH2sMaZvK@HmbZhNR@dwQl1|-?qc0KrW zSE9C$%LiJkD3Y;(u0b-_zFgXWMrKZFsDLee7CQm!Y2&%&s`=l%qDD<+z=t8YJ(aww ze;^i%!H;5k=+%TzFU2Rna_27mx#bNhHe}kevdIjGcen|d^}H7j9^~B*^TXFlOrH$@ z9gY4dm|u60l^5vx_Qka9s6JuRFBl!Qn6hozeIzH$A$rp4+R&Jg*5NlEbXiZPrfqZb z$IfY87!+qWmg!^2I$x!q@I#S9^pY1YH4#g6uepD83p)t@W6Ah1#|i$pH*7#>CaKiK zmLwom{bBEO$_7ICXScxngwc0?`0Ar2FUx3td+WnH6}s))_l3Rw%)8e6nc0(a&3rWv z#O?*9eZKvd>dx-kOV@|$1128UelZvResgvdP+-O*`Osvs-G!ik=^E$c#c~xbzu!B^ z$*lS1(}5^p)6am}9{pFFR|1t|r|EnVCLRVI4atmMKkw^_SfC^Q@D-~Vdu)vUm5i*W zKU?UE_THe0W-Z@Nk}f8G98Ed>{Zd}b{njAjICg00rH{41mkUOyJTT&xThlBjA2zyc zND+H+wH}9ZC7T=fD!xN_Gj&XqWK`sSE5jOlQbo!5v1K0&BUssr9^Rc}Bc*3(-`M?6qkg_0bu|a8I z{qyG-;^E24BZn)iNR*CQT=#5nPL1e>skg{jGEI4JKzror9mP^nsF?$G4>5olue7`U zqI%xK0IoSrFy}al>kWQDYGNH4WP69ZSTBGMk8nkQg|~h?ZRV%j>&I8s*tf`Pxuxc^ z60q7^N4xF6yWH=H(@%y&)K~Vy_lob zfe<4%7_ZAohTpH|eh<$H-po8|JIY!xXG+h z^Ut&(mJi9XsUtDzN-eiH5M@`zoH|crN23pnlMOPfejePI>wYUvj;4A6?k99NCTKT_ zOnHFPr#Mgh`!K7;-r; zP%SjYKX7|t>d1uh%|(@Pr{{;)QTI{A-$1be8<$zosH(5t+rRI#A0@#GTUqlNs$Aaf zng#l%#89xhn0DQNo7&Ghzx6#$wp5oK%c8tVOTfb6HLTzoDJp0xS)$?dJI~P@shITX zuJHs~>A*AJ%H}4cOIF^%6rD=6uRc`PbIMXb7yZ)z15ya+$5*C!r^+h{!-6X~)CXLJA zEk)A4t}I&nq!2m(+b6v>w!=eZ?RdM;Av6@+xZaKW$pKp8O52m1i7SCiNB*X%?wpIf zmt%v)8GdM|mA;VI@>#kPi6PQv;wbhDGa5b2*6Uhi(+38PKgK48^%%VXx4$4fy{@Sv zl1$rC;nLMkyKnE@Y(MX{B$5&+&6s(uY$m(ZtidBK#r@>RccjhYuiAMY;}5ryOc`@a ztDpDsl@eAX=yo1<=ARSeDZTXK!nAxrMHv=T1UriF?%z~zoNCSE8WBHCt9)yfrhd7d0bNL}rl zA%2ZlKd}ccugcFgJq(&G`10cV;zK=~f&Aoq=*r1>;kWFUhhH^J_9>SYrkxwVxN)=A zZUHsi75gT}ov(_~yy*do_)|M|lIqzj6$(Pyd)iv*|y_)&Y^Kn$KTJ-B; z$x}_vj=$@J5P7lxJ?i3aIE>;hf(K0$CwQIw`F^bx({Q9H8yDhw&sqK2xBBW?yZFu7 zO5Q2Nk@(||Gl8DZwi(TpKqw!O)t3T~s|o+vqbtX6eV&!MF_X#@wj`X#_-yzyU}-k3 z155XQ(bm0ISzcS20=^D}<+<@Y9^#M6*H(d3F=Kns zbuFt23)IDtaAP%FU6#2f{;rQnG~ri2Y`~K_JY|-;f|41pRYa{6Tx{gp-mCSl$*8&2 zZbR{w)aXk5wDm$s&qiH47hJUkT?t?0Jiq*vq@DMJt})>ymhzZaG;{@joio`x*58#k zkUKo4)$-LT-+{+3M{J3m!}?0W&UpfsafL8?;O+vE0#FhI?Nbv;Rm*FWAsqrm0&|%; z-=BF6H0jggYCq0y8n9W^aerUy&yNb@ITpN<{)HBB=h^RU;QjUYGmPV80p8VOJJR7p6#`%*cmSZq4`Wj;ZE};pgC9TV^ugV#pyYTp7T8OJx7Rj! zb^j|Hmb(M`7SL#h_77#I$T%bmjy&YQ)&z9F~$TywIb=P&y;fwUvOwM1Uf4hPo z&`vmc-dUOIytzUvlVaCh-iUq#x^QvdW#sY73@O6hTfvHrwQHlB7etCxn zCb{b3n)JPIqDRlA-DgZ97-JawZ%hY^-zprbPQgVpt@0QKRh|hndh0H4$$ZaymcJ;H z1#=JO8EDV2a{PwSUFd4Iyty^})60z)%d%XoSzJaW59sa+t3EYn9Ge*)DvR;)lw7NOdUR*E6 zpKH2FpU-tc;cfnS%I4Z?Y5V^S_{;xK%ZO{b0Lqju?Ih{94*oQho198iL6gt6Gro(; zJ4;Kj0Q0Dd;WPv-m{^WRdu-L8AD33o^5atzLM%5EItrG1$KPBkq$O&mFyYO;{Z6CM zM6hF_1-Fyenj=>5BM+1JXv9qVC^D;~<(QH3B)I^e3a&&I^9W*$g5v2oWNf@jiuy9I zK}fFJt*(FnIPQ$rJ<9=$g+Jt30yATk_YLfw)#cxuLu5qBDdp2(DxD8g$ezi>?W?9P zBsa=K4$=GFZzEmtL=J~lz;S+PRm|FjvQZ`T3C>nW4!p46CsW{RPjx zX1*TZLv|_mr6w&RE+`66^{aSYv_5<^Q&8ZgZ8_i~-A}wKcicDfq60ybOA}|095TB? zlb*I=du{nEKlQAApa9kG!uLgqB^l0D&0NHba<1##4IYMHjph!e3=z!uX+5rgJm7X7 zpO(Hsew0)5iwsn-(5qSLfCt24k8)PMxfDICLD3^WXLWKUxA!sT16yGgnvBD3#{OuN zsphc^YH57WaLrzMp+^sUYak%W z*f%S?T9-5~|IWSpD>DHr(OEI=*xm0)riIh7s}XJZY1vHPK4;7a?`h~-GUFMpeFOiV z!7`B+tmD5E`kO)Cv~?N0&WJMOLS&I*y-s4Xy~OZnd-$;%OjZ%-ehm_p+bNF4%1Q2sPyWmE@F(HOcilk-Dq@AUC7)Iw;J|KXKoA znE29w6!yal9^`C&$UhS`fZ3NEF<<%pL)ZD%pu zMAOK%Dk0gdftEDBoK$4I75RB|hEmMXQY0$_A~IG!+9LNZt~}vs8AOd)9_YVf(n$1| zZ8yhL#CeKXCgr3G#5M5I87Ol4T{zDV>r($*c&c}{p4ymjwT8Cjczhr_;d0D5(wF#O zaXG=iyUE98w@@6PwWN%1ozE8A@d~UzkrUwDBsqGh%C!O$;1BXc;UTk?XL*RgHV&3P zta8$CE-yE|IQ<7e`7&J-{R#`vmPP_^8Cs2b6&Te{Nh8f#BaX)MCUuD->r^}!pW2_d zM3O0;-MJ%?;d^*x@=s3Ve#q`iXuzk+F0>^_j=odiNSv>-V)DPa!K5`sHxh#nfaSt9 zh*}cHW>|aDV~uO3=S1&3F%TWK_l5#I;&15jGyT#oPzl$*7$@H(;mdbD#BDNhHT^A$ z!zGLEg~WGv?`;-x0)X6-FMOJ6Wv7ya)uvK6Zb(E2CO0fUTjW8p6yAPl^O;6EEC1xs z?oIqByrzwq@GZLmh(!;GkrnhsN|=59>fFR0zzlHI0-%rR>&;cguAeMN1pFI@fevde5Qi=^8zwRi3vV#XWO42;onDc8@&-c(_-u}LDt zH%+WS}aW!zawU&ZNj~@`%Wtn5{C)I}^zqO?|#Z zTHy?0xaC{m1UoI?tW{7b=aH~^KW9Rcg^I%bwtoJ2{(yr~YFuJQ6buAZ?$%lPM_O@P#v75Q zg8UoS`7t3K3x5q{6@4pP_qNrb5HpZdgd4)1*wk0@EtCG+dtJ}d-ab4HNIN68_S3L& zIgI-`k*;>j=Rvd1R5XFatD~&gGxqWOGoWLbX&>x2w*wn3CxokiVo&NCkI67wtUp^0 zY)c`dWQyXEa#v?j@&1+D@eT5lq;6_`Z3l3h3)c}0Kzvj%s6RkoM`z9mE^cXVylZvd z4=l2c*uSAYyqGyKoA~?2%S@_kmA$f4=bfLgH>0#i)7jajzhuB_3%Yhxpb!(EpAT%U zW9RR-e#m-L$#LMnN_^G&HstYyQAX?=Z634gq(XrpeI_xam3peZ((cffpLU5Y=_jQg zQ2YQ+1{8qUqD`Eqd~QECmfM10z;{=9c!U)@A4-6P-02fFS-+L`R_E<(#j-&&;@dU*zOEOmsJ z-t`9`b7c>j+T8~j&1dCba_cnHFQC(NQw`i3--BB#5xM#my=q2fB_i5NzcWjg?ore* za29yP+_m14ubqV2X=f_YWr|73j`@4MD#JB$Ybkx>kTHkaQBX;M(}o$_AVyM0FyP?e z>NiwA#P->q`pH?Xi^+!!p?_EZEZkh0#*Qn!g+p_k*M7O9d`#o=?sN#H;|pig!_+{j zl7iPO?RV0x_kFzweHGs2#-kka6|j(}?{a74!}>2S{0oPaFWvZ$VD|z0JQO-}(QT)n zzAl`+v)vvF8V5^doK8j07B}>2-F2Elop^4Vy1|Sa4gB%#Wul&Y(>X`iVKb!V{r^5T zRxnG}gyWcOXic7)-8P3G;T!x@KU58$Xn$M0n$OrAm%>?4tiY;hJ{aY&Z%@-kD1i6C z_lgF}MK;%eq&xp=UkO)^2I+r_>OWf+Z|yMs8N{b#PA9MDu*+eD5W%|YlMAHif0TI4 zWoW@PJ>q(mcH`1)p{?-TEXi>jKcSVI-gJHfQ8oX$J6h9mR3S8-w{C?x3Xd;QS4n~yeh;a864 z>CVAK=Q(VJYNyLae)JPwMw23wX+lLXt@JUasQa1^?sx&+8}iQ$pjTD06+4?4&gh?p zeA}renx_W^hlM7^Yx7VX!EW?`j8z z{d?aIFWc(%=^;AUHfJa+9KpPSsu(;u>>MGmXL-;vXv{=J${+EQ@hZEkSmm+U^SiExgDP?!k&aC?=9Mi{?(<@8U)huu>g z4i0vwylc?hBu>A1TAh=NsrZIw0A`TwH*6O9JKHd@Asf!wWlV8%j+R=bLdw9)t2PR? zSWn<#JXRl7PTKGg);8C7C{PK_HnJhkP%%)SKex8~0(K`QCL8p-oSXZ(E+P`_4 z_4|l#rrgyGO0X3HDMs4gCvCxAT@c}|hqCn4=MhZ2RhyUcX3kA(&V%P1ys*s1?>fXR4rSj`Z8(9;h)h)}pl=GDa zq>g?jqT~g>u0JXi_6G~F!uEZ*%O(SfFF|(9pMp<^d^tSABFI$+lty#1%Awgc_a^N> z(tMOiOqz1;!~*DA<}6!?JMr4G$gx8SRV=c87)ntSYbkOBd(i{h{lkzc4{}`3$?wBE z+QtAh30sTO6RXW_{dXour$?fF+iu;^ENAhgK6Tjf)%)DaQ68sG>|)Cx0Gz!YBBMp} zMLMtPN8`DYhlA;RdRJ&rL3%9nPAO}FfX~32h(He(xlNuqTmTSoF397J#?jLAtv|al zZCWTx;UY<-V3loGN#3we}j%K>3Eizj|&@=e%la>|qlscV-(5QY>f<|7z?4`_(hxd>9K`hmIiRnR6Z zS&3i^hb(vQIMJNDs01oU1NYKa68tH&vxRrIm3)^Jw1kWQ_8sW~6`^%m5W92$Q~t%X4~v4*L<=_AcWFP4~L0viW~$rCU>eR3s;Q9g44S-r8M zlZ`M#@hueUQ~~zrPE-dhT|Wb0@mH|URZl0+iQa!&9~Q0yKH;cdh4HLIa9(@0V%5ov zQ0ko-%n6fOlkK(dH#gFp)t@4;5jh-57B*6TS?j@P zb?&7#_zhN2K4g(BG?jQ!`D1{q3&vC8E-H|J_c`^1;$zLu4(_R>()nuBYHjJyc;zG5 zgC0=MAMaxXE*+TNQcV3(T*Trl2oGW2cW1}F@RFcoY>BUGKv;r;2XHi!B|~ z#ytlMFu_oiyA}RT1VftN75)6`)5aE6N&4C~~JDshcl=e61*~S;4D#Vyk1;Hq} zXpZIKg#%38_Whk_m7>*^-)DCze{0>TjJ@=qNUV1W=<%pHjoNAzB@ikT8c5xqa$9{?SzyLmd-A7P@e*$Ae6ZwQv^F5F=8?|Tr-Z;53iApeou_i-}W8baSpxE^?05mcXbQXMG~GFdc3~I}|ET`0%pSEw=eMy%KRC zGaMJaZ`iY-%Ls7)@#ZsJYP&v#f6g0Ys(KZk0SsbujHtKC!H09{%77x_hLbA~?pvEB zd@67va0q^|8fA!oTkzq(?ZZAVmu{zi--9#O`ZLa@d)Odw9M0La+MI~3&=q*~ZJJpo zs5E{msHs?&U}a%sKU%svbSjuvGdhnuB^OZ=YN&EoPQf9Icn=YekY+!OuXiSYORuDTbUp*fy(Hm3C4?UX_nJbV zp-IS7qrP2f+8T@6N~zM&%l@X#G31t7)`yXEFT(?*G27;L|7n06@g)t{(rSCNu(ac4 z`3W&sM{-N~8OJ}A6OjaAXr(M@CME5s{d$W;- z(w!H717O&u^V*gB)6A3Bdq-%iF~Ce!d;ZVTf!y~GR)=$Gm3OArYH4}sLtn^f&DA4d)&7b95!_cp zV97J52A#b54Rw~6(qWYYIF2*7N3x4YLSa;bKE%lj$`8Y zt)L#Lp+pro=1O39nO0!ef%!$V{ju_VPqkkY&y0idkB6xCC|_Es9Xuw}WM9WZ$JtZy zbD#$@LhQ@OegpI$&PXZ8kK{RNr1O8*K#+v({4mi=;V{VWbm)X|HasHwr_2&$BXas&2OF zSDbZNT#k!$p?SEZQnquHO};A5f-uG3L2C;HDEry8&tqM3rl;6v)3+_Fj?s-B-BcIg2fZ+d%1qzdIslx+a=(a`M*wW$!rX?HUX{}zF01<*E zeeX*+gVaCB8XH{5K4!Rtm$LI?2s+&B5%)Gbf2_2{JmkyM3@Qo}7&^t5S=lLM!j)6gGq@~Gs#P62Sl8?GLUKIYzUlBtB$|8--L#_cp zD7Y5{0@6lZ*mYox3zL<>%YD?cCI@d8KD=>ZuYC4nO=*d!AARFl!jNlT(7Y8B2FY62 zE>*f2fLF}%Z`5d2=nh>22_!Dj=PG@F9WWD0OcKfXo2RAM-e}k*BNl4}RYgt_CDI6Zry+a;`T_ARC<2 zE67)MNa>x(>u9#w=hq>D#4uI&Jo3IQ5(I0tUJpp$?#5!%`P+27iN~p_$M_e+|BO3) zMGHwFgc{ehZs;azzUx=Y@Xsw=AEvh7ov!5o^u0>#+84Go5qNakS*6BzDT;%KD6_~B z7l27GMelQ&=d+n?fVn>x6X+2$ONg>z`AaLvkDSaV&sbXnfxaGieQ}YZZ{Ps=<>H!b zsaJr*w@UJondrFpS%g|I$kb=~-Vsbu+(lMaz1jT+(=kkd+_#VNV{RRs6PZ&N7H8Z0 z@-^Dz9aJPQ7(w|JUG>|cNp{F{1iJLOUOBstL4Nl2U}c+0_B;iTEg`~l8M&uaVXw7&s)&m74;42}(h zB&h>c)3b<$+2~^Or&(p=LOhJSl=h1hH@Q{_xWOe_>(wOzFO|0oZ^cW2KeID{y)l(=@#(sEz>%Wb zXiW^ZG^KaL{88;9?5@m{Iw6UAe~~hhz2|Tlb;BF70iCkZM~Xs2C_1!m23n{$;GVyd zvs+N3-hY4Z8(MptDVtogB?Y~Xi#yAujlnv)24t)-2NCsLUXSivRSl9U@d?;vaUcB53BUj2ULoaaW>VlqqMZ=MGTmSY$-Z8zE5I&KbWFJ9DS{_!2m)qC&k$!f{$@o~ay z$Dt(Nf5Nv9xKIVvgION3kgDUDP(RMKV9 zi}-AxJAeh+_X5Dj<$kTLknB_X#W5gi^V`IU*r?yBA?`kZn18GLyyd?c%xWYnWK?no zaJh64d*8*yT}QdzUbA9}SQ}ovM(&ZXn#b+K{qm7u^&0+5*E|3%m)N%mm8-6h0?*!$ z-!~o|Y%@Jqv6##13XS_%OjDaIfP=4W&j2{g|H`$t8z_r5yE!xXFy09JiXMI2AW4_* zy)M}KCLR)l<(-FBIrq2CkBZA{ST08X7VxH%&(PX8En)9AO=Q94t8aSTL60_snN9vp zUTLga+fTwx((Z>HY9VU-=>LcfoNe-68~kxNCMi*`KwMur(J_ zNT~-`h3+|%RV3n&XPOIFZO%^sFSq?gisFQrscfre&kE&(x6g2@-^-nBhq z1RIqr^YC?!CO6B9G=+|o*H$vTN8q8`$r(ULdVPk_5sXPJ@H4y&zgS|qLoRHEH?4XL z;Ke%~bt=0a3BpvN$^&(UnLT~98Uj%xj9M}u3tXCOrb>ST`dOYf3_7Eq=Zt`8#U|bE zZ44c0(=*&NUhqnbs$peL*yMb^s<xlR29pI$O78{j8b#aagRe5b%iri9A!ZrxQd+;*5&zfx6?dy_Zijr zHV~*M*!&TJHj9p}%5%zn3sLG8Za-_!D4$#TBVCVdGxC=lNzbamz!>O0*yg|8d+E|b z8mGn4b8vk!T)(f+Y=Od2wxkeH1moN57~+*sO2WxJTLWv<^pY=w$E2J{nKORy%*@g8m7w|E3TM`^&C0ZbUiis z?g(1}rUXiVM)aI5uA=KBMBI%2A&D)#^tyK1;>GuC3f@*J?eiC03M?3lj-@8xIxvLM z?2v2Fx$p%EQ&>&%rSk(0-1R*!{a6ge28Z5#@H6O)2nw&s*pRuOGyTy{$f@uJokpK6 z|73l~XzEc23BBI!1^~ET@%?gPREchoU0o46|UbqHCs?JL_^j1weH!Lj-yO+3-yUqBBJ8VlR zsr#1+#-&MCvUMUbawlkXd)uEgb=~&TOdyL;W7Z~dz4=~b$I!@V4th|)`htQq*M>D0 zP7==sHD@=xQ6ICXTARR;M;P2*IB79!#bW} z1?B*X$F_S=yx@+%xkyI3$+wTnK>G9OI{a(?3s}iKB2Z&2YL=W8#Zdk>9g9bI$(9R- z&j$1MO*d$Nd%^biu~O@JmKB%r4D^F1f>t}c!YFqlCSE&hwi|`WE=<2;*Sz04NTs?3-5l~w|QAq3#%bLudcs* z#Qs0qhM>u(9oc4=$ed#)^ym}yMuqf<1yek>7?L3fdfOJU>xOEJI3j!uteGraQS8V< z^(fEKO-|i{JWkTtmirZp^<0!y>R;hKK13ne|@2tEB=!DKNB#g?vnpR+$R42XEGKQ<>a&9LC){G;C{sxDNK%Rt!{Q+~J z?IB5-SM60pl0rKM6fev+XK3FO*(N2~l-{G78}8c6DZkvx7S}x1SiG{ZOtb$swJP$= z#DeJMBi;3JLA)8P{BvXThNZF6avJ}C#zzm>{?6(JrL1A6JE8+o$Tsj`D~oc0X6orl z#EM57r7>aMyLH^DXnQhwo1<0)tyC|#B1M=a4;B3~`VSJw+guT;ez%?CQtxpd9r|?6 z9O|gDF7))e%yW?yScp5aW_nRa4(ukwu4EAwgYDf%tJz%LouLXAuPFtj*Qi#{S=+SE ztlLckdyd)-2C=}wPC7acd%woGPOa)Y2Ce4vWv`E~6tN!m*6Xxg<2#y$x8ZM`i?hz-tHYY+=$kS$M^p3@q=)#u?o~_6wd}D~-+Pws z);yMpYkcYXxFBhrQ)vJk^*ejy95ppb@*I$v{5I}dPq8r7C7(Fe-l!*#27oyBgpp_% z+f9RNP7{R#Q!_yhM9)uF7JtLS_s~S_43T~`tcID&Z}s@x#h@ODHodLf{Ji>c`hSkS zLN;DCxBpZ+`uDMlB7l7b_P7_hqG)T-9BXhb9v^5qLe#;i>~yGI{Kf|sC||<<-ySd`}Z7Wv+AabCvrnhm}g_}H8SgiepTdVC8qr5Z1IR|E+PfOhVS3~h4N{5!D zx9zJO54#v|LFjyQ`jA4%3q0Y*$7$A1lhhx?CKU^Ms8ey^t%DVAjBj5LpgH;FsPd<6l=XYV+(R|#KdB*X|X zv1!`U-pT^Yimay>{pw@`z7>o0_ertt(!PyPR`squqChB(+QXg^ILR zHKFeEl}BsI=`Z|8lQ^~;PVTPz5ob!a7nf$zOuS{I4D;OrV#8QcYyWh2Q?2;g75 zR`JPacv^mGi3t53@ZCG5e}4$Kttd8##bS4gq67^gUyQ40%|VjPnRB{PP&##8tg)t}dXSs?$t1f(V~qT*P|4{Uh?X zL|l03szl@WXdidY6JCD{j7uEWtjq}!cVjN$8jG0{?CdX5}Mop2n_DCeNw@MHq5)p0lK`4 zDHZA|wVV3T$4>7*+On^Ll`N0!$i1?^?3WxZ2}7DR(wN^XGS#`1GNv5S&M!_hyWmW@Y9+R=@^L0g5$ zY-_6%ro{@mjX&S+pjQaqy~3Lr{F?#2P>uw|kPCa@b}F>UR0m^`GrKh+3+Oi|M_O=# zs;msi-XGJUhj2K3FerXT9~zrM;K{*a;O$En0g!#m5~K1WN#Z2PgdrlSnzQD~G#ek1 z0@enS@2sTp4gw5-&~n{-u8f`yoh zRCvu&d#^F{9_3ZYeY9MJso44#Yvs7iThMwk?PEo_1?8fA^8Kq4`)+m!n>-e8tx*X& zlSYLmE}gzK17b2Wr#sc5Rej<9%0)wr4E$7i1bCSzQu6pr*mR-7=fIyZ(Ib zJtSuX-S8MC7K$hUXJN%5hG>1=&n)NFadBuG9Dnj@yJKN9ka#X?b^gO8^xMVR$_4y~ z;1)Xoi+$^T`vkcHs4)Gljybm`J=>iZu7)JK)(4|I|+} z|NHRq*4a(qLtx7Wl4+sN-oq64GS+H?a-tLY^shmSXukx%+VV+dR(cs*BEG$qXJlv=qz z_2S6OdEef|6fbRBI=TMd2P!h`9_f4^V^Qp~nes3W3%YlM@vzBugsf9Q&KAj-0FBN$ z*g$4LP%E2$D>kRxitt{1SpmGY7Y`AjT7R~rypMrXwd#HeteHUYWqf3C261nMn*K(+ zQ-?@4qx9>pa&&fwHN!!GOuR;VfNyho_{!ul8Iu z+_)l4K8t&~4#2uZp6Rp>zX)}2Ecm@}7`I0jGI7_1myegKWm^duzs@Ff;9r%7m-~5a z!xCf*P=23cIZu4L@na>@YP;4!I$WQKi_Pgv<=Cejevt|n`GGZKh!rp(Zi2mg164wq z*W0PiQz}vBu%^iB2W<8-<}#t{T05@g=p84QQjXA(zsL-oXUQ= z#VnTo2iMysCzV?NJw#Td>EFmb8rX^gm1*kw%2Ngw{{V#It8$o{|JI*%{% zNMjR(ZefAv5)9PL28|mj8x3#H_}QBv6~ij3IWn&rJ`4&1_)?B|{?YPw=5w&g&)%_M zuEDp%HGG2#BSTZ=ttGD}CELM21&*PA&u2w$HGk278aZsH4q2duDguIiu*=lu?>YnA zruhrRCi47c_V^c9<(%*GTNX9~?cic#Ux9Gj5STdyM{@1^=krGq@fqV9~q1;Cy zOYAVsi@?extog5|cPlVXc}U}Y`)?smef!^5qtu=5BRtEI{A#nK!gP;!SHZPQay=la zvufmQkPOKjiN~6NQK8Zt`Pop{5_dh!(@;H7)x;F})$rQWs`S8`m(A!hV6z4rrbfIb zgB?@QY^_?Ue=K%epfY}St*wCpV-X9JAL~1DI(V0T5j&nkzW4CO(rg(e*p1Gr>{&oF zCsIJ~ph8FnD3F%Z<{NeK9(ds6rfV<%%=so&c1CEW4 zVJom(18{X^z6Rs79W81^p$x_B>-+WOW?%C48%V2r()>mbCLV5{-bFJwVbjvD>v{#y zmmI{9nWvCnJ`_8x9bx+YM$78!Ldy`ye)YMtI@IVP3Tr$)Xc;z?78SRpU8{c;-fj1r z>lZ~TAMY)z+JJ=RAPR7mz&@!*Rl?vx>C){#bNMJYP zxjQhu6PFNWnTA(K3&K*3;*W26tI-_(ko;xnD<>F}^7K%KK@bk>MTve3V3kF+C1x><)wp~+^lNt}NY0hJKt3hW10)3KjH;f2`8054u8 zs>!WzxP0qH?zLOcM{bMTiaqk4D}>Pr^Zhvjf~{6mREjZ0u`1k_%w=@o(|ZU7^p07e zE60kH)(WA*@1NEma5rQ#3IrLyHfQdy`P#sLK68=STp+TfwZ{x z!QpOn9FleUJAbOd%Wj^B^;L|dq7Iv)zx5lX{ci&}H=Z) z%TMdvJ=G@=vCfzB@%g1UJ)<>N2ubDO27|lotL)#HTf_aWd+49VOD8at?m1mwbQO9Z zm=c)*K<$+0Wf&&=P{E9u%pglM5$WIVDDKlUVkU-fQLqo^24^dqGo+%MT>xfN4YA-g~WFe4tuby zsIX41#7!z)pjkCPr>6$nm)kzXVmp3er51iZAC8^;E_vsUsweg$+`DXM=I=aygKL?N zigBi|aNkx1O6-L@uml|kUInVtGBI20&&teGd1^D_6odpzY{;RUW-NQRCWD+Zd{+px zJ#hTw$ITXs;>Q|xlrpxRqicttcHv`}4jJoc&&>WXKCdin02S$wXeQ24FL()cfS;As7)hi~ndJmbdB z3VD$n83(4u0#1_(!>Z6E+$Pz(EnDSiRL0rJ4bsYSGnotc$*J55OAo%g4a|#O+$6ml zoHg4xJ^ZpYJR0x>?H`lENCNzSmaGrXPaCagxHG#4&4XYK2RhSikEN@m8dB~r{}}F9 z&uE2cMv*e?E%Z;ekxyLiXDg8e^tK-bw|Yy$6O+Xh&Yu=$B3o)j9%8p-s2mwkT9_1rRo`a`_V`CLa`0p{ zOgC!!Cxuhm#q_|$ML;*%ucXPMd7SsKdC%)3 zAW-qC-W+z)`a#i2=e2H!%+3eUubVB}G#%K|#XRqq6!7g26|N4?m(u9bNpmyr9?J)9 zO)Bs!g3B#z&BjSRa}S-h6na5cpN8vYzzpy1av?eN_aB@X?&s8h0{NMwZ&nWqIKe3X z4k|tf9bGlHB423p(Yie+9UT}p43DU;vxn54a~7Vmzp21Fo-5|uBQu+w&YLvzZ!cuT zTz&O7lCubW*L>?mKa>8mrum#VTEK%ZJk-&K)~d!F7%J+{x} zW|<~i01WtCy-@lTgN@#M5X`ZcvN)uXntq^F^j|f)N_{6*=!345g?rCrfVRYay>O4k zs_(UPQJ>j(27PUxKzIEGJ^Qp6UoCT^kkrg%I!}MjPZk)VNwZiM7Mx!FyvNpS4fWVz zY#o=3TxA-GqWc4Vo)~UbR?TY z1i)@A+5A^zBGf+1Tm@~`?rt%9^5)l&kx_5?s~=Bf~kCO?R=F#vNy{;+^9dmi)VY#u*gV4Z0^frclZgdJY;Z+=Gd#}wR z>JM@yzbm|iqH2~5V3dKXM=DT{bm9`vvCD$5i*?m1j%)SW_%5i{?T7OgBK0rc#^k3w z@;$Y-WY{r93MNgpZFnck!c_HGz4_&t?00$+PyWql+qX~(|Jq9VQcpO%mJJiy0XP7{ zRv1Jp_TYm<`5*g$a!Zb$6VC52j~%-)zxJXg?IymO+)78Mc)=aoxB>0InQj5KA=*|; z*qi2%DeFj&#p$p)GH?5QO5))qk%iV~TZ=z}jdbyQgbr+9HJ zvUKvgMf&P`8az`V*e6oi{KYfE2?tob*Q4(5*{)siRR9?MQap(Y}SB?&qGG@bHC=!h7E8~8Nc%8{0EvAO3J_MU(kS-y~d3TrC zzhpfHpLA~T1+yL?Vv&f;75^Vn;E6*!o>q|IT~t1mKMRp>!gHaFWM3mA07G-}nTGY7ag+AX1zlP! zxT1u%Qw|s{6wunUpqY6-$ONe*- z9A2|lQ1jBLq~iEFzkX1wl8Tncn1Mfsvh!YocAS0Q)tjl&O_63HB!pBEN{*Omk$3;@ zhZ`WbQgxjo>k-V=diiBe0O{j=3luXiTkk-()MuttdiBp1ccSbq7K%`Tj;`k%2_t2~ z0+y8Zp&SL5v^>Cb2Jz7%&DNx0ekM}1q;Iro`hAZYnNB8+moxvA0D_?ny^%{XSb1P_ z?J#}x;Jewy{oR1SwnJYot&Fr%(6&JjUKza(Nx0Zex-?Hu2YcavVyJxAFJDZde4wbq z7*U8z0^>4U&|N{z|@N&C|DWLs< z61p(hQqcS&pyrYvY1y=X{$lduV(@8`v|x73rz;uq69HCpRI-Ewh*zNXCzep zk>@Nth})4hVf&M;`a@KSxcAVHk$BY??7_qUWh}haoN1C)IaT<9M+{)MuTb`MN2e^$ za0ifFT16TW2y408s_ODgCOt&UR5)!v~w zb_F%n-)tdTB7?0K$b_;>>jSX-RY8!Sj+h#uG!PZuKSl2X6eoV*AMIy82!>6*fc7*zne$lZB#+x=77{MKD5@AZ*OxZ)>0o!7ldWT`m{-BvMy}g2C;&Emw}0ipz}F%3h?1hpc-(d{xU<*__$-Uv~3ULB7Di7yLG z%6F%dEUhY$${k;!%q{TGmtm0iIX|rO7fCmKSvLR9bDbZ{dA@TPf3hf^IX=^aH}@YI zp#*IaQ2J00kD243<1$SJDMadc{ZQKFpD_)T4u|GObg;HB_CEop>G4%f?40;XX!qoA zWJzgEI7DM6%JzoR6H?jgdZ!pGqx51S-Mavb0zJMr5E?-X>u7_=AH_G1lN=e;j+C(; zcfNU;v1jh<GF+MP)MD==>)+>3d;#R1X#gXE>3AwzjM6$M zP3{Xf^8@!!6IF(so%~J74pArG^rbww9f|)6x<69^YGVPUmw(H3z7~&m7_Tj6@XXEL z!23ZnCdFChKuj;7C~I90okJCLm@Pjt;Gdf@HPdvfKD;LD*Y(S1uaCh`@9nF5pEt>m zfM3$tH!n=(bbnnd$c)2&bQJD1@peQcmr2NQh9{} zfS9qTQN>Et6&*T$N;|;3#49!ZROley&1oe|260EFx3=O~*3=_nGTp@cb$xNBar%0# zfbM-Y=W$fW{}yfr=X;L!$Br^91c#A)OWi1ByK~@{4IXs=F!FtoT^J6IlDf;>NzZ^T z1QKgPB0&o|p~y80!68pu+&5Qlfi`Ddyc4|7Xoz!Z+djHD;Pg_%c78XVF|Oo%=JBz* zpt9c`w9JA*r}Y*rC)43p?l0e>5KFx(($EyXKdQ7Ci{UA}aJrB0#6P0y5gBsGs=yw& zH32J5a?cp&{}f-58G2+s@nQp8mlk~UVkU!0*;&D~wcS6xUlZjn5yGvpzR7Kfz42ca zlR4dEAemD@XjF%LcV)W9KX!YLJoHBq@W2vs)91s(f@J>=!TgF%oA%5us37-G!nwL_ zq5t`h$^s&s>1tIxz+h(JWPapQ{e$H7&Q|{g`{siCf5gr9cSineI#VurZn>}3c)91w z9dauPh7l}qUz-EGdAtAnnLLAG%DO~_roy|8A+su2HVrq-;Qj^?T9uXSL3L!$g5S7H zU7(N3PT(Kn*tvs-%>S>cX+2Y~EzA82Q*WMkM;^hFvqGQE-jhVttb+4|OhVWiQpC2^ zlf0^+;M33q$Ninao$YEXsml=lk{l&0gB$*e;P(5xA6 z#t_!=hzqW3dl`gKKkfc%?7~J9?s8Bw@;fULUpCqCo_d^B6K`|+Ei^d3#)W!-X*Ir| z2Jhq-Z|xG6pBRH>`9GI6FhPt)8#ZK{JGKi$SRNrz=V&>qbO`B9kfkp*Rmx3I38`Eg z>P)ZnALTRhSi$&zH|?k@eR^3Q1L&Fj$uDYgx|nj@IA+w`rwQ)CC!U>bh@M!Koj?Oe z-)k+~HSPC$mDrGVxQ2z%gu$Nmm-!l<#2@H)TyplFpyqs^ixlu*?j{ZUoljMiK-Ah+ zsiq)ko8FDVYnadj zGYTA+#&eoHHV=)(4&VWmI5&5B_!}vt(YuNo`{PTWuG?+_7Es{T+GStE479=UG5JHh zedxlh3oKPCANwPXBQ$^5sAY#!f$nx^;SJ>X?t~{pib83A zt){=cRE^S;cBKQ@UwoaIG_{Nz5hFi*_u<&4I5&mtdsltIU+djK5qI6GBU%r=R^JYy zv|MSsHsX#5eEJd+^)IVVepLoYkroY4NRCwKCg}`!!UUn4b# ztY|ew;2Exb%KE9+!JTPVk5w?6hyX`2*5HvJ=88{%laGWXoaD9Y^LJj({ktQVK-R)qUlnPR|v5zPNc>+&4#el`eLMNp%!NNvFBbnZI1-n>Mq0j zZ9q!tCjCjbg(PQ~zHuLKa@*=_OI=lM)i3%h$ZWo=V0@sfm$yg#dQC8o1F}6a+z&`7 zbC80wL7t*!j3TQr*+Y#UybvNDJN$jb6$L=c4qTEX4U?0`9@ki$p&GMi%%C0ztWC`fqArHF?LqO}(N2hEZ<)aVG$Hf@wMY|@BUH5#BD}W?p{FK~J#DuKI@iv$3-v=+ND=mvWs>1|WOchH;O)Wf5n;zg^%W@gPi558ba;o$F&mYP&7OgNZ2Ec88pl~?6HzP7Yj?>J|!h*N`l zpl=BOd0e$HG5->&Rx-8o>*BrAQS8KaWX~b*ev6Pw+s`Xu-O07PX+sp{m_ofBOx)OH zp8>)=m_*0H5eK*8fBZe+H}&2Nx70&1WPHX5>&R}PB}$}|ZG~{TR&2_Co`4Irgc9S8 zbp2RN$V~wmzIfpdpCSFC)+nhSd?-7KWI$N@v7@%`_X{_iS=LfgY^bLxw5BDU+#zPD zx2w+JTiWP75*cPzi|0_Rs;_Hj+Z`Y`B6$yWAR_GTUA_PU&57|Ptx>0ePx*0lEl-AQTK||U^XUk`HnE57=DX?h zt#d_USW@Nd)9jh??*o*CkQt2r1r)=71mE|}8{=M)dcLzhxT^`p_Ate8OuvGE!RmW+ z46cc@4;fn$w0nPQdchWb2lht?w>{$fIacEXQB`d|oIzO8etd||ruW~sPr*(K$+$2NUXgSNG`)aAM)jf#bN{2jLI`Y@{>U2+87HhXMzVb^yligN|vCB?S_)} zPtHc`Q;N?l2kWjXHpy33(4V6lbSSqY>#*UE&eG5V1t9i_>XSw!SlJi?&pj1#t{TSuz`ycHtyA@1;}Ef@auqR}{vK(#y7%K(l; z+3b7d)67hu*+Y7V+H#ff?45pJhT0(@1CM+2^!}{J!sE&p z=Oq9N#ltj5*Rsvz6rmh8=X2^AT*1 zyLQ{3-1EKrjM<`(Qa}E=(a4ZvM-Se6U~YYh!mt4DbB|@i_8%eR@ky@?9(tYp#k)JgGo46(MZ+tzNDZJy-XG_>H`dyzgfb#boRE;wb@Nxr9cpp{mQ2)Q4c>B{^JSK((>o8 zj3#b0q{&qK_hrR#2@%AM9$HT;;k}Fbkfl*zfTRJ-Im?(xS6v@=+2=zUoh>?(!2eVR zexj)FO^gN<$jIz{rqkqS2h}SzcSdJ0N)L7=wte|gOz7ii5!^w>`v0TqD+8i_o`(-m zK}khGT0}s)yHspS=}wUrkUBUH4y6PHk#3ak?jw|x6p)4k56J_LqmJfT{Qmwgo>%wc zGdDXkJG(nOJCXEv!Qs$7jrw@+vxOH%itm`YrRNuy7|U~6$FjXUsJZ>>q__HO(}orxw?;oam*87G2tLP;8NoTH_!O+avbOlLXkJ>zu~#Ne=M7ymCYPn^T-& z4?oWL07Gebm(298s;NqORIo_tU=sE5=bq>nVpd{IFJ#W69^jP#6 zEjG;J%tk=bUKuI)mT}~4R$*a}?C7W>G)LKDFkZ01CzjLT%x-uNzCFi}!cT5LU$FOB z-QYc%=JXy;8o!U)Ms#TK3Y?QQ5xbRHDj44$_XgX9INzLwQw^U2jmU$i`Y_4WiVz(a zFHIzNX9IWk7Cy#b9_dN)N}>IHHE=15i*-oVj2bw<-}5guek_QhkV8AV9uS$Rt;FFxfW>5HW{Am2qzo2^&mGoCuNeGo>= zn}P0mRAl|5W!x8s4gQL!i>{?Z)H;NWNielt1$|@ZxN&DOf`s$DZHurV6j1{i z-Bjn6FlYu3{%Qn#-w5W{hbTSIa3*gj83e?);?F~6Br)Gm$`Gwnn?7;t9Q06J!X(pz znCyQ1A3Kv<3PGAr9Fsw};ZrsJy%zwIO(hOXO}b4y&&~Ko?^1;loMdjnd{w(~Rdo7~ zrYdVmOM@utQzCh?714pHL36wkwl)<{u>Si7R{dB)l@-x3wXs8;5M4A*kL|oag@X`_ zEgj8CClWE}e=g>i3eap;9U(R=w>vg|^KenOP}E>C1dkB6uoQ1jh?dTXygWJeUP)?k zS_}@Moo-Il=wey&&gP2Qz5rSa`-->h{mJyJ`#mo|XrytvPumyTRqwsX#RsdN&muCj zDDY8tqR5CoQW{snUFe#mlVZ90Do9@I(9Q=;{ko>>?Fvb~wjjFp=yvT^`*Ce( zKa6`QInxmSkVEh@qj&37`0LkLJpfeP7nxCGF0o8cBIeYQeB89!KeQ3v=m=&tYWUf& z97*C|cK61to(oe9GUD2q!^ua2gE<3{j6^emuAtitZx=Teg7Q9or6 zY@wxyK+60dH9aq1k2I&wrVjiPQ@hR7Nj#UFKI=+B*w5I%6p;)DJW3Th0DW;L7I%w_ zdDNB=Z`zNesccXaX9`!8g9y~87?0eZDt;MtUzPxO-i3UV?b78eCa z;KS(#KY0?T2Hf9CnqZn*?UxQbyFLN%h-}xKA!Q72bUz|*y^TJ7P9j$7n4WSICxs_F zBA%nh5=>`VzvUTDRmdkMiNw{%d<~Ew?6+SX)8C)&Jvj(Gdn*mdO*Vf&X&Wd3X7Mx} z%R|#stq|h0jp(oS3|wU#y1&Be!`frW;{419eN|%NHHFPnq$1`RD4d6#WW39l`(tLw zIpM8wvb;J5n$p!+xJ)$E?ZhhLfTT2QZ+^UX) zvewvOyai7A|J<_mQ+bFaUS}V$OI#gf{&6*+XREq~4HfJFa~fMZhMYV*eysvn`}#L4 z%tbYm!}&_v_>@RYQz#IS!`oen*+;aW`!ioe-a!%37D!@mpD}v7A~f6IEDtcw>0tOS zCFyP#dwO!{22|R#EN}v#wM@+=?crpil(wys^7iA54#P%y>RAq}zN$!&RZAqnuuuE3 z%CrC11%xwlyya;Q2Z@-Vo1yyd>GQ}>E?2sre|`OlIW6( zxsH~q5*-gl;JlAPJ>i&Cm$XmN8>A`Ugr=F5kd*r6)#I+NkqW-KFt>>PAvs9It)6=g zbIIiD7XGfc-ug!Nm8{EC;?4qJV=CwS<&O@G3l3wb`uOGN6ZFo}27s;TTM_v0a(R>s zjWgD+PvN~5MbxoLf;gZ(YO!a-GEKmJ*w=ZyG0jjf5RQG9-XA7a_)&SnAz?;r_25L!72Mh6 z(r@3ZR)ImxV&mgrFUmHUtrWJ*xeMXSSDVb+Of~bUzJqEh6R(zR;3{8;SQ-MoxLS2S zqCyRy=3kKto7_)GfwGqLtKLptwtjfEKhu?L5Pf+|rx-tJ{P-ULlvRx_5_+CBb5uwd zTv1)lAr?7sbvcT8KIIvvQE|Efb*;4&Fa=snlsH)SVQz(a3H0+?07|NZg6*SfMjRjg z4^5vD2-m1A#(U5lXQU|a{F|m?wPm!ycRiJPu_p^~u$7E2`JII8lX8u~$bl;C_lU)5 zUV9#-=+6^q9V1ov#Q){S%qV!v*K$01a|yhRmz78MDtm;v;Kl7bUTd zq}sa?1KjoPMEk>%NzJ)hL@8Z1NXop3r$|3Bqen^P>AFJpe;mA@E{pw{`AxLRT}7j1 z2L|#oh=x6KN=-!*pie*a?>K)chD|@DY07jZwEIak!>-vz^%H90!LPvQ$dw#hqx7Q?=A|?)rlz695zY-QMw;`sz!4E{_+kA2e`-2m?p;q7Yp>uuHx*QN6)7* z+eS!QqH>s0;IF0cK*QlZj_3RDtS@z1FbFxY3W_yJ0BOt!W%~A(pr^|}Ty++4wJonY zc?*ID)ob|w#LB=PZGG?b%TRjA5JD=IuVbwL6-;Emkpa_Cc4zS702_(aV7$n3*iv62t$D59EP8bxVU z{?a9aGvRnzM?bx%$niG|JyV^**wI%EbE!3@`NY58GQF*d;v22XY2m(ouk(hM_3>js zT7PG3b$utrb)PsJBa-E|Qla@;JzMM7Z9t1(7nggcg5wkP-*Ygkqu-GmPk6X6Wx(ni zNYJAFBJ;s_pcg;;0v)D_moKtObQp9_ptoSZ8Pv&68VPGKF(p1zQYr)pktHWV`h*~{Tf=*l->&%Dy?UuKoIzdbc% zZQX(hYTW$ku0VEa>q|Y<>2w1C#eZV|JcJ=R*5@Pe3# z&|+c#XsZ4x$=^sdHV5%mU6{I++Ur4(_tp8GemmgJp%dM-_rWy;KXo^`;2~qK^~1;~ zbCE4~QAA_ZJ!7qvqnDi(EV&yi;La2vuGx{-Uh`qb^QLA~F$Na`;(N&|Mit@Lq*BYF zrV|QM!O+rPAjK+FGL1bf?d^HAEJP%hi^t@iWi`38jHRx@aW2Su7oV)Dh#P~oy>pEJ zJ9au}-s(47vs^T1%hYyxo*cZ}R45=pMg#z=(a@IuAspLGyta8xK}7KEch#?9Z^~O{ ze5mR3ATE@Bg`<_@n^QE|;8i$Yw{Hl-^s=yCMR)DV7G$F5cM=`(l+#Cik^>A~=)59A zF*MF>0Q>2uYBU zBg?gH&TNMG`uHt_CsOwU#HzP6sZobP+R!|LIQ+R5l+cu-QZj#5SY-3ID9=#&x){k^Wz(JvKI9}5D(1{ z3SuJ?=_Yf};E-$Xih^aHe_qMYvx%AyeSEIp4&#@&?D^-&bCuXJ`KPEdk+ae^MqUAs z@+9Hot3Xm+Pms+qT_eS9Tz9^cUX$yvZhyJ%>ruIEu&_Sjde9R+DPis~XlYwbH2lR$ z<@*wG5jDAHqAgcXwVyGUS}&QudDTcAi3!2WXeEOL$H3zj$ja1S1iTpX^kgD6(~*D@ zq26coCXkdD?2ZNwQ@9K(=Won4v*8=Hjypt<&Qs_OXY&^1J{6pq!O%5#J<|>U)Ot zd_8yv-E<}SY$LduzJ8TG@aC#*mSrhpD~+Pt@^6*rkUpW|Tg%;Pk3sT@L~%hxVvGa1 zYGDfbMlP{gEvrZAK;jojLR6AX_=pyj7RZMD9>)Nzw2oX4{FZ4t*d*CfR8N zVG#jkF7>6JBgf5p9~O3gs-`#pHR_Z8ZjzYk6_aDU+w1Tv^K8^k%~MgkAW_mxS?|P_ zg7N0aZc+cICPB!_OBY)`OQKsAcX*c~vLw+>-WI(>TvaH;yCanYlYJ445DF>pXurC3 z%6ZAuk3}mzKb`}ay}0kvbetT9S;Lxs(_(Bi2=WLD(M&CX2F);CUy%o5z)kA^@RaBO zh^(ygW;h7fqJ=QIRQ>FX4U?Z|Ha8<|;=M?ZAg>{_aDQzWVD_jBUv(HjsyH9M@@slD zikXjqQu_*nS?XkE2(%Ox=fxf25Jp9&+K<1Q=?c+hN;Xx`zarGpo>Q&p;0zZ41R1`l zsX|R>4{Q31R%Egu*dyGRUltIka1Z$5>qYj!3GqEH$lmzFn9Jv!4iUpRb4b~b@}?`? zqmZ0i6NWh+Z>R)tCer19SWU+z@}+o&DoTCYTw$&Blm3Tz(aPW6nPr{VR2IW z=6V5AA1C3JV+Yy1hgTnZW5sXQ6Bip1TK_)~E7Vuh&t_rv@Aeo-u*STK zA@g@pmes=4d-6Vr=Q`3G+sq8f=vz%5W0_=V12tRh7z?DsH7dWveX^e%x#8SBV2%Bx zlv;;y`chdLqv-?%5Oamne6>}Yg_-JU^UnQx_3jcO#iVC+3v8GIqTMhO{Z08o{YI0pld1ZoaXs#0!)3n+ox7Vyc7o)42v%H< zN_3hu?Hxn#p^mpcsK}3N0LfShNX&n>Q@3!&*ijy2%QQ+36dQv+Nuh8*z3F$4%VGen zvofp@|7mw$)UU9s_oGL;P>teOBcY{wNA6JX$EZr}oUgRM8jl;vF5N?wpI^r0L0l^; zW&hw)nEKdJy!&wTI77VOgM7CW)KDs^)ZIDIak9stHKno(B|GN*0I6%QR2)|wH#S`1 z!haRw{H)2c!X2$8Ou5xNX$u?w!L$>CxBG~Vzamjxq5I98d409e55hE#z4d8zm^Kb! zt%mh+FOmSsL73xN=3^#(m9EZe#?1qW9{Z#ZxiwI%fk{_kSl8R-k9fOSQ$~!v&e)Xl z%0%5*v4{L^=Z>Xc^AnZct-O0b)jRRK80DDQ z{3^@kv5ABP@XZcu&q?9SShl+BU4Vi(jQcwP#(FT6 zFD&ed-B3g7u3p)4Ub=QsEF z5<+4xe_E}s&Jtvg<~w!q8=ZDO{AO;=n}j8ZchI{F8r`>MZ2vB_L(hw+fLy5YdMO%S z`;GB@?7-JU7U!Qq55vP2Ba=v`*2SRFpOridb6ifJYy^S zP-6eqUOfgn3KD;`{KD|-7Mp~ikAv78k-+ojAbhFoO^D4%Dnp$p9WQXL#`ZY15Ed1= z`v+~N^nLG?zirep{uC;pP)zLOvN_Mh)%6ce`qW0gfR&W0>!u*{iAkjYGcwRXFnj#@Ob#nFO)chj!f(LHc zV_a8T?DF|LWwi`OgQv(g#$TK@#je0RYN~oz}g*6lf+EG{PVMH zT30(K2m)pu^oXvu=#(8*xVKT3YLs^U&fb5V9ZFn{G$1z{$nSS~yT3#Q$f=nK?a&(t z(K2ncZ6-Dv_O0)T%pZkxYFT^8G)^XirLj7F`g#F9Kw{n#8-K+Hz=$9%lktA*OAFqu zl!qVtT_XNNBoPv8+?P6Z4?7_3o~npog`>KI*t!r8=9UujVq}wMLh#q%VOZ|4@|5fO zXSFhW`gahl+N|mGsg3y*2w7{|-|SEAT|qipbH7H0;Rtj6+LMB~0lro?^n!NC({hBg zwZI#Drj33!RN-zmW0B`x{M|Qy1)UA2MN;8^oD4^4{kB)1WSD30MMT zDFq6fl`@RyK=JNcO}*$`FbW2o{Rhz7kQ{%nm68i(;y2wgb&*2X%;di96(O?z4JjME z1Z!nG4VLwh;U{oao4HdJnL-ZgWVtsNFX||~V3vt3DBPfLoFJljj%u-F1VEO}j%)=l zH#Sqjy)9KR`g1zND zlbX$2;F^`?dkzt_W}`9d^CG3U3QJ3F0=Mr#0M(%;x&sN1$t$9Tpo=1QP{{PHdTN|Ka-UDL_*4 zoTjCm@rflN#ZQ#H%SI~lA%U-y(@i+`-CLk(JBMD5n7{a&VvCWBcY+6R-ghqW+>yTI zxdv>L;-X?B-&|_!=pl0GM|@yyS>*O(aR-MhQd#g?hO|m)Gd1t2&#Uc=9MOR8xwEbh zIk+kLg6Yk*ouEC!gUQA(s;k?NS)EhXzK%M%3*dp`(j58xTAp=!NHdw5=^SI_iF$t< zqNqG4=VTcs(|XIr;J#afB)E1C#5)##tK5|Q8ooa8N_yj-x%Z9v2v7OutJbcqF+2NI z@BV{YR<)OCElrZ-g0Qp^bf(mV^lzY;&`mNR>0W8N`a=NPQ*t31 z5{&d}(cI<}!Inb0rH;J&SAD-GqMrUZ1czkLeF*G9j>Ls7aZbe9I)?-VwV$roc z2M&jypuwqxuxHH66KDh{|HV)aJAAZ4OGR(eV{@YQ+yEIMMnWD|R!QS-`k54$h zRN#j5gIuVKqsp|ve7J5P{a23%6o`$AXbsPe6Qv81hjJfB7v;I>sx=fQu4%6R<*5^aGnhJO}aE=^oG#iBB4=ZffB)t{{s=~ z2(zE8qjl~qjOyfeGH?BSlr>iOYQ@H6KJYcX=XttG5K_PAA@sen6M9pNg70Pm#bs5?=Y|$EJcM*n1{4mBD$mc>Z~+^H!0U$lk5dV zMcS4*lb94-q=ON=YCmolE`D zMX3Z7Pt;cEIHoJCod2S=s(2gFAsX5xHpnE_`gtiO%zE?jYp^`GZ5enQnPf_Iyam<& zAyHKpVNIJR65vtuavLjzn>by=nZ&9arl$NBxZ-#xThhjD8B#(OsmtzBO-S}!?dNQo zoEb2Ew^vhGUF4lpraG$AQsfX+atgo=ENu3~bKp0ux^g=l?9bcWC|KFK{`_x&5^baw z9YP`UvJ!Er8iH<3Gbe^bkQQ8jM?a$Qnv^YkQL77W>KfUwiF~*?xTil>4($VDVr$QB)ocE&c2E2zT3FMD92x{m{ zY`r#Dph5)amabb6CJd!%&=tb#{ejjvch|FG#D3&>xHxt)v(5*doz0lQw*Rs?_e;kP zx5u|WLF)b;p`MP>C_bLbZCdqv07MjeJS4qtcR3*{Mc4wl_xeI764lim62n$MJhJ+O z*ko#@!D|jpWKjuyQ?$Bb{-=KdWTf9=MZ>Wu&kxpMwY)*4JcJC+Mh=+k^0tPUKYRY| zwD1vX{UONQ{oCfo#K{l+s@qVfv(ZloaqMX{&H+B$>xcByO|aimz?9DNIS=z3P$T=r zuj`$>r2Y}IH4We3+|CxIbMECnew=GPqm=RiTG}1Fr<1MNBJe=7v2H9rRTMHetLW#B zMPE9|kX5_`YChdil*>$`^Fw5@1^2>X}B$_@&Jf3FHsl`xwh2ZU{EiL!s$3 z6IJ!@9676xDS$b0oSg_~qXEH_*F3r{P1G^ftGkb@6EwH;x0Vc z{;3Gu)h~S?GCo?@Exe2~`9z7arvHq-F3qSpdYlddg zDYf#LU&XH!ftjK{dNWM@W+&w%w6yaWI>;Gp5`}?j6*qAY-ug>AuaDb$(%gCKurd&Z zYkncDt*32_|d49yFp_zRP+U-5IUedVH;g?Nbo%$ z+~JUvIF96K@5hT{IyJMFeJKjmOTIM*=(Gvy0BQAjS)-t+l}jqY7j zI@}&bUZZHuS6g|%RjLbg9mtJUlx{@uMQu&!EjZ<^m(7jsMQg&in1K=K=23C(A{<>a zP?~h)9?jv)H*yngSnK>}($a5U>*JY7zNw)BIS`YU6P7>qSZ6D+r?~hU_SzK$X^*F? z)bO;d2(0I?g-mi-uWpN>o!HbM`npzgE(d)>!G0*{mRordt zYiQ`sBYQ~Tpw--1s^*kwco9M97SngzGnQ=W6B0terG-Xj=Y;g)kW z@SR=!@wv}VM9*Xbi^BCw>$)wC2`wRbxQjl$LIuUnAnu}^ggEUaB-rh!(sRjN1Dp#u z3Dk05$8H4R)cbsF2eNOks50Ocj#muAv%j8dk%T1C{`=Wr0wn0jAW!JN8u0lrrv9c-oBiWS+QY(XL^fawQi#Ig_8 zNOz`B6&D-KN&L8C^jT41vs57e0HnLD&KavXffnVsceaiTQ`%%@8l_E=U4ywRpauY{ z;}}(sy~KF;8h&ES`MZKy)8^N49hJq#WoSA|K^V@6Js$iZOu(dVpU%257pDW3H3Jsi zw6$H!3FWD+DWFfimZGjEucuSpyPbLfqsHdK4y_tGU6HJBM{<)9OC2>jjt1Ky6jFCb zObEl8jM%}=YQY#;3O0In;iH{zM|8O6q`6K< zG1KO?#nf3YBVvT5_Xc@U8b#TKTm?|^ZYI!tEvQU){pCvoTo%%$BK$pCXXDV8GbXH+ z{r%CgaY;M)r%l^YP@s19^IB!WK&G<7`RZ1M{;WjDp5l%3VIt?CCosxK=s#M0ioj67 zE}6bDQP}YZbLBImPE=-^UYaxfbYvRD+4V-i@;`I!Vu(%KkMr#mZ?4BHZj4O#jwBN? zeZdHPIr6>=?1-pl{l|No3(hG3!O8kDQPFsoa$R~DMi32GeEX6neuViu2877d{#^WW z(;`n|pwPAeclA35*DO)jqlVlt>okS?zyiPtbi%xKNq}l=7qwjJTM5i#z*W1vImkLx zRD@TYRBj9Kg}m_bgF{Ni8-mrL_1UAqpVOHPos-6YGGhl4{TZvY4!?51Hrhl?Nflw_ zX^HH4iqipx1CA3#>P}DP@v6RByTmzM0YbDUmUM&(4wT+SL+n;I`*EHg%wXe@#5HeM z<&uIG{-ySsTnq8cXg|g}*kA6q8$>){6U7v3xwZZfOrk2%Pg0P2Q?tH$wCpUR>k!fQ zm;Fpkl|Gs%Pjkh$W&>*j%4Rb-q2VtacH(~knatc^BUqfNx(;s@W>03FZeg53YM4dh4st`?9`2M7Nbm(m?#hLrg4(BXW2qktb4*~#zra5y zgFtetl4KXOP1=jW71gtKuwxuF;}Q>)JmZkP3xPOi!qjg$)mAZ3Vco&C|7rF3&6aB0oedj4nH{zR#oxSI69+wmUdo8n z;yjN8=Kb%ej+|qT&X1?^NcEoXJd@T(;%f_lN8hV8zXC-*VXJrDFb~GfvImJ{XmC5H zLuIwv9-;TrwB1&L}AWBAZ1F<7;SIa9m1^-f6k6uzRuo|QvJ5Mdcb=v*}nME#drg7#tG#=M#VptuNxy4f~57& zQS;p)S*fr0N6*&|$qw}n@%=uH~WBXVs_hd27cd;c9-VZ4GY%Vw?YPuxKJK~f*S_T1`rM$2 zu6^pbYq1^1(cXLGZN8^5NfAI&>KxApu`_-GN;#8!Sn8TA$1m!ln{Jd&J5N+eT)Nha zuO2sCAy5NP%v{5=$^+*Ytb&PFuo^Ze0fS6VT`Qx(u}GVP|+yvCyAtUhR=Pil{n)=Py(OGWDOP<(O)XsZWGh#1chpHwY z$Uh{d3r+TOmm_RP1K+)|B#Z;WHo5uLZy9es#HE3?)(f3$+&7-j(9%ClD=vm8U&s27 ztfaEj{ohwXn=Fcem%7dBiJz|OeFW3=Sf`0dKwX*Pr_ZKK=87@SA3xN*`Gt0qrDE%K6Rn}Vr-%9S>t`oep4njH9 zhE|J8i8YZITO3I0D}hrVd~En~fnXoRA?4np&3N2kBA4)QhIMf!D@I4;n7qQPzh#ao z2$+VcvHqefGp8IW|Gs9@aI1cL$^8jY0Y2h(&V`* zBwcbQ0U~%%;9~B0|33vm%X3V8zQ;>iR!w8Z3KEsl$0mQ--k<(Kjg5uUU|rF0v)`ka ztKLo$&xe45X*Rkx4(YZ^Q;nb!XL})ZutewkE1hO+_jQD$1}YBmwrPObeg_c>-Xz3L zI*tRBAJ~AI5goH4iUV9tt)?)I2WtI~XN=JaxhyyD9O?T@X7pfS<8Lnap1=G@)JB+z z?RYcsiJI}DLbea(1jk|uh0U1VOtpN`1;f(>wF7X<1Lj3ot8P2&zGSK^#}6JbS=G!! zjRt`s+w$|g0H1!TivZm00$&Z$+YoEEZ$6YzuuItFxT<%xUQp;rEVpi_ouI&%q6)@Y zI-@GPievf3H%6E8koP}q$zws!^$(lh?A|b=1{mXiK_`2~)n=-0;7rB|tQl(GAew&1 zVcq+QiOO`BX56nj_inND?jo!BP{i(!IZzlGGl^pIn}AH^Y}av)R&J5^lHMk=l$-Bg zAjjQXg>echF<1gY5dK>HLc62f+R^zxYAK5QNkx0`2vAD{p;O+Rwj@?oE)7aN!im>s zY9WvB%@HXa)sEvA6oueah*~?i_N(ht6Di=#t|6j5ViEcN?kX5k@f1iJ}14&JQm|z<%qdBvW zY4JK9sve*-=LpzSwOeDfuN?siUj+GPL?UNa^UHc4CJLRBgJ8GPC))?TpyUiuPLbA2 zjb>u-974QH-(wLKryD0p@y8u}8ih`^^N^Q~{A17aY!H)-O9%amTQ!xUs(x+vSbe?_ zz@3YPqgUWH(8Ba)d%tjPfFP<$e z2?9=C0^@g%n!=S3&#EuLB$w-ieRV?creruoBSn6@Qk%v5xx#tRKv!YHtyj5@bKF+h z5+KTE6s(Z8g zZbQzysY)^fM!N_EKivJd+k|~6y{t|~DikyG_f<1-A0y}T3F8Pc!2#D;H7wI72$giU zPj+D--C9;UOZ~y8Q}sH1TfR)6OYCrby!KDa*v{}dw#6DFbE$Bw+RJjHu^H@>-7Il% zSv>ju4+f)2@E&|Cr-c+vBR6%99~5ytzl2I5d^-GnlPfR_aZ=n}^-d6(ys7C>fb~Ig z(!h%1b1!rlqBH?(vEf z@huD|;5rzp*xn$7f8Lg=GPKGu1F!ZkUaKeB{QO#G$xrNbP6$8upj*Zm)N*!5#x+&U z+y{ssBPrc)QS1EusQg&-Yk>r7t8Rb7M&Rm#6tvI6f$y7dWb5CCg0HsoRyyGLY=dJQPy3+FWrHrw9@uQW+{Fj9WMpaC_3i^q+)Hdcxknvqc^|kLIQYB+CKNcO8=4bx_qTxLV(Y}Chf4>$1z)xY=Pvc!o zM?#7L9006(??fs9EP1$Jj#_@D#aFvGa8+|kXh(;(n^T1Jfvqpe#}YI=w{W=&m>lAl za4guWf|?ZV(IujT4&bC*gazCksyY+Tg@Q1HMK)AdJ8Y&~^V9olkCgxxP9{E7i~Jk9 z7W#QA`44IYnUCDBI1AlVJ z0u>^Q4p(6f_+c3_!?=BwDWkBv1oIVP2?B8<>fE>95vB4x9u0_4#rP9{{NNO--em-i z%!e8FFSL|!CU;O6DU0t-?3?)v8&Q=stj@WvAPyauV0;pUn02E8XT-24u*#zv^}%C_ z+)MGww59_DEcQdk=3rOKuT-M*RUQ;3DH{uX|97}?Ea`bXTHV?W#jDsWWkF%)2Rp)z z&^E&Lo0voKcn+cQq`j)!DBgE;SYA~_@mAvsjjy)@?ips2Lce$D#0hq-qiZj%XWAF8 zemSHDRiW>A;+kLku(9MbcxPg=NThytXy9vsviV;s3(G0akJ>^n8S^Si1K#MsCve0w9o z5MP356-esQBI`i10m|Z6G`H!}?lF2Rc@ZXDC6=T=^zFM&f7xV23yHO4@!<#KfoeD246tG(i?G;NSYD0$Td# zE90j^`wIrpO<}rBDfhJx6>@zj$pSu%@9=jqY2JC?q9tCSU}n6*48*05L+uiLe^}74 zB)h*zbS3-S-dYW_>j04qRCzMtV{p9He_$KlwC`cTk*;~(9(nl(1X7&t&O~|qT}I`N zmyy}4M;%r;@Iv3lhNoarP#fY|OB&ZYg)l9kFa6{P6NFSZi&h(43=_Ad48b!{=nL&Z zobuL^N{wdvh+p<~aO6!#BtE?B6N@=qj2rCK|Kg#99Cz-1zgz%S()WKblIDj{W+0xs zG9BU7>th>GJPrx(9n5FzJMd+P(|d`ihP!2Wo*|d&56T;*`ipL#D3NXIhv}jGgvBrI>nnoNbLUtNt^h$3-${)AOgO5cD0;Fr?-~D0ZD)uz8lgQ4Cso^zizz+5 zNXRHxFVtrr6x9bU;2gAh42%thnW!I3zx>1UHfP$?as2{$fn28RJga;Mj6vJ<4ysUN zc8U207Wb;u&fW3Hnn3{Hp>F|yFn@b#x^A)J#F&-%jA`jvLtalqW5=;h$XRJ=&-Q*- zw%*(ww}-#8p(m|D$@}D8a=wox6oWl?ix5DUlrPwW=|8r!tK5nMUEjT5up!tbRN_Ad zzc!tlz2f=Np(}FD5J?wx@^WSXcgAq5Kp?49X?wEeWap4$8%OktlSSdPpX{DfYDlH~ z5Ab4R&?{vP5|Von8(ru>QfmF5PqLW*P z8+$v^s~euDEgyaSSEdUN$-9s;YUW2z$?`PqxGTYQkb~(cwC$u|kyq2f}$$5l0r8;E)Q&GhUsxg?T6Z zXTp|pZg$^5!;~j@eT?)va12vnC12-E*C^1F%3ZYOntzY2WDh%>y)GDLA2!X%nO*^X zSGdtt2vs}{+}%ewIPB4joXi-Ei?zL&L7!en+okeD2i7H(FacPu)qsIT3wYpkh*lKD|jl=JXra5`0aqg5qZX0%k|xJz5w2o#{OMHsI0 z?l{Y1k0!WA8cF9KefXz?cLWX|_KFB6X)1!#%9Rd#2yfH9U=6n+^GfdSj@^;6#U3Hr5h>`z z%B`CtH>bPGvuWzC{f-yQs<>pCeoaqp@MT{B5Dch?3e=AE`3M|i zT_`;&q0C8L6D-WG-uZl_{h-As6n~nFsy%UOP~BdPJDnC*Y>t~as7H)e?REo=1dR|x zlndq*7zoTYt9jx^t4W4xt8;&zvZ{}4IEB5j+KD)b9#7uhe-83@up>S_XMgOw$@vwU zXz)exR?`Lt4>#qrefoGYc8Z|#E06=IYU)p_XH3ep&%sk0e{5`8=;Gp7-S;yJ*85TM z@PN1eG*uck$lX-;X$SMIs^QF`9$CKWcstgy35)Ub2O4Wq(3Ex+ssrZ353o9zv?KS=r%lxeItkI z>MhY3ZIka9u=G8-LL#{3U*P@*ucr$V-Aod#A|J?bmd6%se@Kh^f#_E#eaE122>(3L z1)FCZ?ea?OioctrHmc-ls1c*@_B?%|=|slGM0Z?~W#o}4MH5|{X&ssS@jE8(xft7B zEBd~)Sp6dOd#R`?+)7-`$1J{^zI*xL*=WH;44}M!`M^-IzbsJ)w0{UW%1f7B zpMK3^sVG?4KPE4>C-UBU?R!1k?5NvL%6HcVBmuDe$#-*$tSH$BtxQBb!GZm~^?DQ7 zJn6s`3#BhOMdd^q3+X5IT=5r)8a?tjNGXrb8MM^@j6EL~W8U^k%_*ckl88Q3VXe2jbB?b2c3d%VEcK zt1OCJ7-2~*u0J0hKBWRD74Sw_E%|q*h7nU*-q0B*d|D^fZZ;^4O{^fAC>RmB8SP#T z8coKo=6+a8375anfQ#g}T&sIIKI_KkXEF-Fp>?wV5ifNc#N))-U3m{~&_&=gC&o?^ zETMm&?vH+ap+Gn*qrvFR0-g-hzU_~|-W_QlA7h}v4%(V#CAN5{)~Qg?iLYp&ec<8} z93|f+C%8;Fqm8NKyUM?D0KX^ z-lf*E0Po-2+>5tUTL@(9p^3Lq?Dw)Gz9w*B>}oISSAUueg+?}M^)C4rZ+e0B+B$l{C4lXxn^ua(*yvkuNar$vQfDlUqoF$E zv_&P4g^Z9YRM_iO{HNb&zyKFSE_!?1=0Izj4oSb$P*74c+Jn{PEE z*e(;%@SjUxCyKSJ8fpL)5C8n5!r8_vDP=pv8?0NIS?F)TM(dh^8SB6QU&CRsk85R%R8SkTP^2GP5mK?yrX;aj41MBjr*d|60j7&ll|IviR1tr|* z#eC_m_H_B&&$?#E3$U4SAOfYE)uS(G@86Cv;WQAQJQuj$Vv-nwCkZejOy>3-3K~n< z0MTgIu{a{_PocYksC@Y;+Vy>;v zq!+IWlDOuV$hJw%Qr;#WM+btgUYvmfP%bYsZQLERZ9){xDp^5cl1-|~W)Qizy-cznrh?RMr>xEE3YNe{vQt*z1XWA)??uoD@D*9^m z7xaw-Zhx8^+qO4xEKl7-2R>FU--fJyzwQO<2kENxpU?hnQ_eZ$d7!2Lu7AQ-c$Tje z4Ufv1JTt_}ck%re!w}>ui}U$si(7-HiVvIQKo_6LOfC7LEP3KHO&V-2F_{odw#z5b-s$9FdS;T)IdtaAi@Z|Lz>JM2Kcs~h&fegFjG#s1At zhDNgH0kd%}L5584FNk~SHMYlC=;_HmK0Tmi85Y*@tgZd^GJ;9LDHI<(0xnNyBf{Gz z3KKcv)^vdU3c+B2pnJ5s{yp6bv70X;VSkC0+GlHY4tH6u7jm8N=?$~KjXItl4?SOk z&awDKZb~n`7|Mr8MaM;fMtkevsDEAp9vv3wD%J&n`7z}>U!SSevx$bsTs{20*WK&u zLmF2o`BL<({G>=ihNThDB&swqe}|bJdpW-H6oh_)gt`+I?bQ$aOa~i){5Xv$KFZ!P z{WB-Nn{vg_UKWZw_LDwA+3drp@HF=69R2ih`~5xF9;xhM$v-m>liB{hnjmMvKd( z##tI9iTsv(ndnP$lBM%Itxd;oE8#tKj+|8M%y0jU<+blJ7T>0Ig<5Jt;ctkTFvZ_2 z=^yiVW^OUvxWv}MEOnRjo@$bq@RK?h#v&PRijXYV+y42(m$kC5GR1NRwvKY==q^4? z4ds&ZnoJ78jF@5_E{^mQ50DatNjc(s{gEhFODhY)fLAW zROQds>b2XyU{r#fFHo63cC<6_2Cuxa4a~RQcv4rTlkBO8=7|@pV~49Z<^R|^qviFc zRDPa`-L!kr_hzzxq1Jix9euPBMawtc5mZENB7@X1HRMBhgU6?)W-bla5=hhdHGdQl zzly<#YaXVTpcO)gmzGa8IDYAs*wh){2h~32i?@{_KPNRL?7gNiX);*DL8lbjHSOcR zECh+TM;?QGYByj7hyqwt-DE~w-SBmggZpsR^7P&2JH3Kkdfi<;9h{)9)9(=LVsAh3 z(=%wY?0OjS1M@p^kI48)vIuugc!_pdYzSASAwJ9K@)CCVuc%F2i(rdcl|Aq9NI$zM zhpK9j%OP)evZBessT}GJlL6`bAMG#OG+jS`^B`Yo(z*A4{|`1QRb;)N%(x;?Lc&hk zk?r5q@DuRZL()Xtup&l7M%7e;DI~Zr%l0Oh)k#$CB>~&eZSgMA47@j>-%%@R|E8_% zB|1oH27#JUWNkeH^}+ep=cEP3U$Cb8SbqtqcSOW|R*8}`!B!ZR>~xzW1rB|`ASCma zc<$256e8{(fQ$JB%OH1Lm2Mb5AlM3(YaKebN!I@>;yJ4ai7>wtlvU_sRB#`=r||&m zukbmIsFm)ain`*|5|tz^LhJw0bkzY-K27*20YSQ?8{UULKC2n27~W;|1uM9-VpnvQX-ZS8qsauFhH>rs1+ z4z8xW55X26|B{_=nSF#eK)qzkn+xjVwG3kE8y8^_M4Pj)R(mc6xjNCFTR@#r4%?8a_oldifp)dZoQP49-v)PZ?9{ode%NlfQr<51UM}Jb zVAtX8`)&3a9DX4`n<1b6u*hLk0be_(2>900*g#3o8lyf`YW?uLjFeF}-prmo!6saJ zcpL;b6(k2prD$NylEZ;tWZaSrX1k*3WQ}JP>e{95UwFj`#vQ6pxOxsBg*>x=W0;=s zB_CrOVx5bS70s{mEfXNJ*8bT`o1S=jEecX6-h&$NS72=>M2QKoZCyHJ=dtU!*%fSV zgTU44_rQei`SyE>-SEs=Zz&S@Aad2uTsj1ck}KW{Z@x5n-&|tSv6PBo=(v3YsEiLN zbYQD7%3nk3qP3Zji@{Cw-{3{_373<&;_R#Yfc%(%{B(f)O7`H`+j`e?U6=S=jG;;F zxn=ryf=G?NDXKI}8S*Wwkl2K$5;|@|f5GjmBN>aUg443NBxt#SS+_xJVQKjR1;b2- zk3eY0{F;I6a{WKO1=4)<3(C0+~8hW%dGUc;$1>MK?xnpj@E0+hpp+1 zrX9-HQJ8e3(>s?*-7m2|d<%lwTLa&Z!LU^u{quH6=;|CnMhndHeF}{?_14MlBZ&P; zKY}dtJFa+E?NsKN=O7XnZYnzF`v(25H_f1l3}2DtD1{I_QDb-$mA>rAn*b*KbSx+x z$SoIXU@>{qHp?Qc1bFK2>(5G)sN_Axpo_7>4L>?r-5fs!D1elD59vIH3EkEF=EG7% z7ZvvU&5MxODESAZI&N?PfMFTH#c{s@i=c_NR0=p0U}GY4mn@=Mq@KvyHu5;+$^gp1cH`%)$U@_tl zIA`9Fug$jLx68ipdBO5m!s56hRB;sznnl6XH;2+{Hgjx)?76b85#*mz22tiEx@1ZN=3Pq6L!Z|VhfG4Dds3_ac zi!CqGu9Bmn#I(mvkHlu23%X*W9z7e7h2_gGj}l#c0!UFCywhrDmf!&>Cm2Sx_jjgQ zmLNo?Se*XJJNYMHo>ox^^9y4@;?VtNo~;NjOt^W&{KWc?fqzE<82xZAhJ4}Mu}KCJ zw8hosHNv)e?M>cv>js09PW4V{P=l*sBvPzbc$i#&J9-R#+(Q|x9Vw5YDZaV)HQ%a& z&#|fs<%C`{y35lc3m*K`=X#y}aepgd8wkOV1 zs)*JYZEgl$6!5>4#^`Q zyj`-Upzcf8mt~nHag&Bz?am#OYm-iHq>?e*bxhGW0FN~6rg^Qw00FOd(A5`i=B#ILrilK-J~~Iz`4C}D|30EfTQ6auDGnIM$jK5q2p$^ zuR{e^YjbMa=`HVCr*HA@0`?V6A$=FeT`!xzy((W{C(&eT<>N>iK6!{E7;9>-} zWToZD+k;S}%*d?;t&}YFq;c#!wFIvw+*$?k(8LRAfw}lJg3{aC9J7hqv8l)7flj31 zDD9LRB+7ZXvfXEhdG1avZ#gsYNYt~gv!W@0gSKx4w97`HXK-NxYbBg@v%P(>v+~Bu zXsTBT11|0TwNv&ex)DyR{byf!O2Mn04w#5*fxypgaTDz;#9+roLSXO;x&MwB_QwYq@h$-MvdENd%IJ@0tZV6hc+|_%+Nuv}+vYsE-k}}o61-D{ z22f;y<@v}^!NgyY!cdx+jS{ei;aBeO-|&nDLwjjMVc<)U#}UTe40J$vvCy4FcnOf( z08TiDgUaIku4)qKbo%m32`52fuR>;4zOP1ZzlQVue_j0d(k1Cu7A4Y?-UDL!Pu&Yi z7Fpy@sdsla!Bnwe1QhXF?NWQb!<$d|^iD2g-v_y2l?h;=DlSj6|v%Q){0 z)Unx(J#+E+g&wGh|8rXY!S5zM1r2pKSy(6s;1j;z`t6Nxb8jg&`03343K#)L#s~in zMN+8^X)nE^;hee6wx#LsqPd%hzP_;}a&EKy6Whl0>*pC_vyr4wQ5iOxsq<@gQkOLPM@zis)_=5yenDjO^s;1y(f!qs63?oDoa`2uA%I? zhI;)S3JhsJ8ftXm9mNQ*x(tE8!8wiH3wbGavK#zvpLKKT`kT`)Mda3eg5YE~uu1;E z|7hV$dVgKZ@}_#bIKB{D{5RgQd71b-D@5}@iD>{rNnt~XhJSe=a(RX%8y)#?rgsgM z-)FQ23wW&n__`P&Sz+OE@lE@pAh-oTzmki&Jim-12Wr`T!k*xc4}xFHEp?75MN0*M zkg!=oSB&=Z%gj7zuHVUG;z7{Mo+v~P)u6%T-3g3~Vs&OpVA01SnJJ&ogi}kjr-LrYCnk-?o zi5@wN*wv+8ixH@-Y(LSRO-1b-p(on~sNaFbUKz}P0mF1+B(R@7B8%ubYXUL%X;_RD zYQdJz0>r*kY-9ZwrY<6$BDB;zh62xJ2I>Oe)5`o^m4Y}M4r+whmlr}X{DO!x*)};f zE9j=?>5VCJyYcR3Gw_~hPk9}RTINPl9&S(rw3Dce$FV^)NdCeugaxC~vTt(6YCSoT zYE$xZ#=L_~V!gq>llJ>1ABv7a#}Z(kJ4?vcA*OQndguf~R>FzYZKTm+t{zE}F%kp% zk9gO)e#Quf!SZArzo-nrpxe*I!s{o}9 zpUiDgdL3P|PIssa$(c4%$(Vl~)+_0||IlJCudPW}^8_(^-)_8rVTJSw2%bQ!@InFFNLTQYVW^AC+qNru`5&z z$Go5;Y+QBa(f3onh|qzg>AqxPkmr#E|CR|?>JlyM<{a9c7yNi-<4cR{8A(D(XdZ)h zp?Z3fVSdB=#f&-#5b7Qjm;^dYW#tr$V$tNjbL!=^a)-F(c`+sxH%?OG6}oCyM;UMF z_D06A<)i&a>|W6{$L1yM-vF~q*-3}@0KHMonJMXljiNe#a8sj zmCQ;_xF8L?FU=%0yW_}>R zA(n=rApN{UQs}$}&2=?>ji!(b3}=Q>9^*e7KkYNNL5fLmus@1@`xTwL8{Xl%7)D`- zF+!R^rX;A1d+*KxX!Fa?Zpf-WaTT&vm7^6M%feJx6Yp`87^tefG9U$wAwQ~zwVzKX z#Rs6wFr}ELWYuyJE9pkb7)zPiq$ceWMGZJ_GDmJa^;bJ^8J~Rp=%w_Hf*5@aNx9wQ z<&q0?R#OQ25OCEsFy#0Ef?fU7)cw&BT|R!lh&kH5V@7xBW;0okIdLAolBoGH_4duk zWa#0abT zEVay|kh?*=e%q}6w!q`=W?jS84)M4kVB{>`z;x+ISTb~UuRd<~=7L&@ZfDU=E6kYR zE{HJRAy%q`?sNfjEP^nL)gGfLZv`~o-^5~?afG{lgPh#CYSL-yh5Sd{_@j`?Aurj2)1tlE2LE2s!Le%>9nd)&m}E}x2F6=#*o5~hyT}j zgYPBQX3kkaJc_km%*M!U%LvvV>GTts^3sZ^dMIK;cMb+is@zK2>baONKh;G@!0Gqn z^#@dq4e&JOUrP3|K?onpciB&<8ma8r(Saa=f#S)5nbgS`GSYief^My)=|&%&kQy*b z4>MDviom4mpQstp(v9-b;iWvpO4iis{G&>}p!zB40*EuSJZL}Rm83*o!+lCw*mBND zT!USi9pres1Im@m=nEe0IUWr#s|rzt@POcj_qdYv!~4sjz#oH^C2s1Opo2S4-Hl4??DWou2_dP00hPYX1Fh+AWt`0} zo1}#Uv(x^{ZzLx^?Sla2g}Km$g<2~BMkV z?9vYvg6#9p5gC#LHBAaEkhRy4YA@-5c`x9Mdv@V5U{AS%5z1g1(}Q@!FnMgh z+6OxXuhWVFjSLZ9uRb`$h?5r8#Gn?g5ltv5Zhx00g6;E<0kf7bO{cMt~m!c<( zJCp=de~;ew@;$z%nmb~7lb6$v&~u!#$?6xJr>rOtR~C#<;;#OGa{Tsxd3&fw12ITI zSoqlHBm7Mq16raxr69}t-TNlD9NSXOpE{n9q(NtK`$S;}Fy z1y_iSo+A!O_}ZJAe@#pKg81sFP3lX*~bF8yQO~V!ie+2CQn(2r!|Mk3X z;^~{z`P2d`0KidOEfppcnv!oz&f&tHvLUfR{uJPx1fiCNh0%H7H2b*eNc-D>;k}uK zyqD*E0xRcm(_uqO(qL#Pmu*$rX#t`KHeJ@G{H3hqC!;g?Wc!9jo)fh=)u7jLO=DAp z!Bik~wBA2?YptVaT=l2dSkbKF^nKY55`+Ga%4_vJlV3C6RD(Mtnmo&7Om7Mhp$j^i zdW6@aGUC6>7E;uxR9;gw?0?20J&0Oj6(&B9-F1!=1xvOt+l}%DE|-=*el3g(KtjHK zgO@aMvn;$2l>z?her59bm!{OiDc5p8Thq~AYTS1V81mlI^t*>iO4rHWzimmim;nj|hy7g#$|l3VQMb>B+cu{?zB3nO3v7CP+r0Ao z>yRo@9M0RVE%Ul=xmeb!M4)zf0#w#B^L&;|$RSD2o4q`vm9 zb{(IyiO#d!St^b0t#9^|E>GdFweYdKgs=T_*#7y&BWh2ib4JKE3sPqy9a~v)5K5(s zv#laG1A1(!I6|^FGH^btT9*FwOdnP(?_sM0vzh*0JGxn38?!&^)0;ginf_a_DZ6$5 zj5bH{xrL%nBB5F3&l62&r4N{Nj(7V^Gk#aiy>e*WM11tyVq6*tZ%iAPV!L1U>3HU6 zrz9eUCt7CN*EWp6*JpZ*tLwaOo0b+NPI941sYp4tH`O7hNTu{jj!zO;$^0ylPQKxO zQu=n-&q&JQ0Y&XphWw>UMqW_9`oYND)X&XKgC3L1tl2CQn~IWk+c)3NN9~@j+?1ai zzfc|<$GSV^J-CSy`(yw~#cq3$0ezJ{-A;wvw6*bwr!m5-uhsaroO7KD$@$~3znG5n zcR}?(6GwgiT1GA3I*GIm!oA*Mn*aUbbHZA9hbQKAq!69uBTlV;<5JzqB zeNEH_Ycn}2p{=5*q9P&&>I`-}uP_c+I6JMA4#A|;_y^0A__XgAxd6Vb^bP&!=mWi-fSUYB(oL9ij$MYKw zCthj5y9PzfN)Z*a!%aCx&H;kPw>A`BKJ@AokZO_X1c_P#S%GKooB;&T?+_qn0T3Vr zFH1_RZDe)sO6j859=(<{Z1)%gERmXCN_`=M242+il^sJo8tr%>xs0}ddt&v>9`9`w zbt~#)&g_jY(Q z$Gp-#%Bfb7YVq|M%Y?GXDg8^sV&k)IJ~2JZ=GlvdfwfTjT+5eDqhQoQ%(r@@we3Kx zCE*6ygfk|`(-5(nf-W%?v{TYT^k$&5e ztbLGMHg-!jf^W8A+_%*~UYf(+4jWgz=`KTXPCQ{u6@}ip>(A1trcoJTWlL-KZ>u{o_Shxkb@U^*bXxRJ0-jLk8;XF%oXgy89`EKI_zhoCvs?e)@yRb$4b{y#^3U+?q zC?0)Cefnqcj&sN~wPBx0U;XX0%9BQkEXPKb;=s$|P5Z-}Vo`w$4GoOa4p^D+LoVuf zh_UONM~bEm3TAzQHNiJs{^`=SjVr~ptfXe~_FAxJpDP~cd*4+!_;v&~bOdY`ok#tC zd--V=r&@8i!G?jyd2UX3r$@i8xgQG#keuY?_$*7GDetk){*g~ zQYJi!Sll_nndxp#1`D603M}9(d>A#`{*cnldWs3rBE;M)UxvF=9m|h8SPnd!L3adV zKxaD0kX#?_pgR!o?t}M4^A8O!e*TLSD_wA>3@)|I(~iF6l_+V}KpP&WYTQH*IRD&D zX#jSi8~U5gNtmvf=tG&k)kXUOr^_snM^&TXWgD$uVuE_l-K@2zDoiA{J5}7{4=k?h zfBrCnclcKOqOwb?A6P-{L3g3~Qz>Rv3{;B_@AWU&UwzbvrR$}S((ms|n(F@a6fzoN z;L>~gD$D!g;FmT0c2QKwb9hg+&4W1e-Rz-qz`Q$WWjT15aI9*n zl@5soN;J%*L0R~f{^c1d@!7VyeQn^>-TaBITfEa3`9Qw^>s$r zG;q#qQE&-V{rPp4)6iknKk{1K^lBq!cT|q@*$&4j*z9aDf7P(O!SbD%Sqed2pY zZY^fwJ3eADFMjxb+Lj`5`-XOxvF(x#A&?2yJ2I|v*aY}Fbp4Pn6&~^kb)Y{YE_(0z6OUC!J3{N=6YUl~SyiSp|CcfgV;8FUz^*aL&Y@WZ_`C&)fp$T>K$T7<-TE>R)!5 zv}ul>C0u2sesxP&;8_kY$#rhB0JJX6Ks%Uj5<0;l@h)cVgS_^nL)$0qD zdi?${mVSj$K-TS`XU`O8WG5ab)7;&FK2-tpJIM9yQRtdE>yW8h?I_!!LPF+Dd_SjR zab??@k?yt{HK_L`-I*^i;m5m(^0Ch6=h9`_qjyG{GohD zF_`JE4xvQ)f4=s~wA$UhetuB8Ct}2ykmjkgM;3c$ecsO@7exY5EwY_rQ;p}m(T>^O z{Zqc$m4drwDs*G#Emv>T!vNWcmKE6fo&L#28n~c(ZQvd0V|jt4!eg~`l?LV`Xp}#> zq1^27xxbj|$lbt4Xc`EA$8~I$2PAp^f6prVL8>vU=c-p~Q=@k1aV4x2E~p;6j&xRk zomIDh#~kO#Q7ioi?am@R78EOU9DHBWW)va|&gZSO*;`mHZ%Nm5-yy$7l~>)Q^DzUt z7{8NM!n+Pqs~Toeh=Ff{6-?s-$!k?5)!MG-^|BjCgUvsW*or^3D~wb-vn;@qfzNnJ z-0>u%mwGMM=e4|yTT{eQ#++05>krjphuoJPp1*NA(;64M>S;N6m1x#t!LNIOH*EjE z^IYuZ?5Wi>#s^~e_}7nt-j&w?#=j6Pdy3w@%Go-u3z#1z4~PqKl*Hd1|4aKz9~aj4 zdOg?Qp4AQcO85tvu{tuNZGk$E6A)UwpZ2yLdm@{oLn!5<>ZkpGv!FldrPzygk?J8J zHFAG^C@)S=S@JI4k8du!Ds$t3j;rYh?isoba9IBxw8kG)K3HZ{7)FSwF=Iw~6`YBj z-I_Zpcc3iax{A0UzZO<4uCZw2x>$WCN8Q;FPNXHSCTXx5-C|akl|MXw{B2jtQoa~s zqyV0?-?AY|#_s(*LNc0+d3VW^8)aw#<*$6!9Lig$x;iwTF6gWadwwG2@f*iWJtKc6 zKJheE*#fFv`3&>!gpnIV)%!F>CEB-0pG~?yE&n^o8`asE3kA~P>ep}UU6bCH1YKru z_C8HiQ8m2N;|fQnlI@!7=tBcV0Q7=-yW_s#_KXLMboVAb@ZPBT4RoeSYcr@JBP?fz z%g2oNM@ zGxZTtxyV{>;4ld+_oOy=XmX0%xqSEE@7;GRgDv|MmG{p*f{sbb1YGI1ekp0OXlrP8 zOJUA|^+LOf!?-+6@Q+ouhWFnpGF_Oa_E|kKP+QZd7gZ4x zUb+>Ri+0WZocQ$4Vd12vU(~#L2x3d4l#Z~M^VdA6N9rFe<>PE>^r*SvR`+S&Z49Uw zzZrUe*n9A&_I|*&hXBp8+aiQ_u==CMigc0V;vo(<1^qhsnZh0Pia081&oVxR4KiOC z&4rj{0Q;EiNRk$$m>yv!YdT6R^xX zG#Qd+VrEI&%tzcjtMqh-_s=(>yBlBhXJI6ygzvJL?pc$xuhiG2W#`;iyRKUD`74dH zAIwyqB|gpj#U6gbU7)05!E_;bOF}~0jTZo-F_4n++8arBLSxAs*(Tquj;% z^dyxSiQDzv?dg-)IOc=B0v2KGWEX9$e8*s`hWgzs7ur{C*~vlQ-^F~FP&~)xd%iHK zqV(k-M#p>)esof>4Zg(ISKuII7FEiGkpDEedeB<1WGPYUqu8j=v|gk((d2vHii_SD?g68|K_gO(@+hh}sH>{62- zcyIim0FPr=)P|OdJuqu(D8^m9&196yf$NXZ~o7yBZOx9azsgAqYs+vHT^Rurn+t!NQjT#$>{o$(T))fm<>&Aln) zNphGtJ>87o0PaHcmkRqC`8jZ2-tWkHE-=6TeK6vHKd?qPFnCGgO#rl^3DMW476&~2 zXkyJnlo?li%w1_elog-*NO5WH+l2KR_zzFJU%QXOPN7-a6Co? zZ|>#i-v-E0SF&eH7O|ZLd5GN#(Y?)@f{jcGxaF${C@)dq0d=bj(}MC4E^cz3F4B5J zL%BulB<%N#Askm&*x_3nXmP_Mj;}Gjv~eoWVZSSo<>C#L;O{OfJHLJ*h{*s$S)yjK zqQuN=t+u^IYPOg*Hm|YV?@_KpADo)}g2YL`kG}v>-UV}0ClyK+x}&yZK)?A5ch|yo0!pF!tOog3Z(LdIUaZq z*P)N?&0`F_PkS`Ale(2CQm5@ftYnL$b z86>?JPGv}Ud9KtE?tA5HRJ8Kgvc(c1oxp=At4ZQu4)nmrUw(RQA1etv{7 z0WPhJYM7t^`ZVczYf7M;M@nysyJ9<8>NNA%00{j%ED_i`qZJ=SN!-vGg(0X6)yan! zkNK3=yo-T5N0d2+4gJ|7IrhU;dh zx{8T5#Rqe7(_CafbuXrhtpwo4Cz(RB7RK)g>I$F4Erdw}Kx5rm zBX9bZHney? zN+o?&rt3b0a@eN|Lv;mh%!ijWt+@j2fj%ThhnU4Yvs#X zX!`x!P}9}J3+O&Cxu*{|?J7U>g@4S|k$-_ViBWXDj4UpP=6u20{eRmR1?7m0IuHUY zaDHv%>ig36A+H#lSzvcAqD!*X@<4egikN);J?Y|UYtOnUduIw=%Bu4pN6QaHBGXrd zln@yo=K4)T2@xz(Hn|HY#RTlkD!`~+8ERK+6oB%mqg!Ldh>i|mJd1~=Rejj$q z=ijjV1u-4&)PTJ|Rv51^zH1Xp|FTbfn{=(cLGy~uFxci|UjMQ}=;r!|i~o)gz`WS0 zvjCP0qi5Caroa5FHosBqGM1Qq>ptnr$qKgvKX0h~Z*!ZWtNCJF561X~}uu`Flt;ev?tkpq(Rd#rw|v!(YXe ztXH-;PQxOzl)iyLK_C?cxtGqXi?>s{-%R)Ki}x9CSO8;!f}w&X z3s%UQSl~aF#tK{yb3~Yq1%JdiCw)mVGuoD_`Em0NP?a@kf!^vga^KA37wJV!i~e+^ ztZA%FO_XcZx`h+p;b?P?erRtdV=xqtU57iu>|11y=6BcJCAV<2hkt~m>!L=QsqzqA z^j?-Q( zc*c4M`$mv2g3lsGPmfsTwWH;AAG9=J7ned_W!GH} zz~|>Ql_ZH=OMrOsCKu7B?yqZY zR*KZ>WVLr#{^4i99o%+ZB^#2dRS!5>h;rT&nL^9by>9(JvS#sQwTMGtZkqO<#u*;J ztx1RYG*Y24jibpiV9DGan~EVVE9v(eZZ&` zt@K?sN6#wG8t<8RvQXk>m?<6xDsDq%ysb0N{pxjl0E=`1B}frYaHvjRqsSEOe%R2l zI}M=!r!NcX*MCIM#ZBCf5<(ZQ74*KK<@usk+Enm|&SQ%gfjxqEE~0gQ7g9MQz8>

#_$y479`m zuUO;A?pr21D$~BM{GDG()QWg0Z7D1?E5UjQ1Q%d0>GX3_e+Sl{X8=Z^9p0k?l94*_Sb=$oq#Mt ze`NtLIAWS~0J0QQ0ZHAih+kvTftX6hk1CQti89z>hc|uRN7=_rigCS}-au6syDwXW zlo`m+QVReQBU{^C4&oK|8!Q-6`=ZOtm`)yX+Y^e|%x7+620DBII&5md^5@`KU82y* z_wi{M&~1Efq!L+ss>$QhZo(J36-QdQAJQf!%22-41SD=>f%>%cV-48aLtoHEuYCoe z?;C8fok?#K{Qa9%Bc`&U2-w@M#!qh>SX99h-M+WlqqSfFCM=*H3jP{a+eX3KrN0eG zcAl;_Y>$fYpA(td4q_U8rLY-3TXR-2F<~Qx3+Ty=0n8%>X*w&vAM|X-9e5iWr1dj; zuQf$zc((=vU(;aSAZ`e+bEgtIVK4qyE};Fxx_rdkYdjsCSOanWG`RVBiT+!pM9d6$yv$F(1(s@?BD&*WF%!LgUpmQ@BNU!&Z^bNXE--c3d4Hf(r4%?uJBzaCB@@6C}OP%Phoo#|2Aqgg!FqWBMs-=0WKDyQ+R@UIc zl-@Ax87F#hvq@aETuT?;n6D+iatAgpIs`kC7)yYU?mfB7k?HKz^Ou7Cat+-tnipA# z??z1fH=^5b*r0bP;>#v4k0$ejkI+BXiBw5=?I4J#(oRtb4~Q3d^g{Y!|LBxpp`W&W zw${;5uT5#e|H(@)u0AN^{SEX{S=jKn`1MdthfGPjW+Q ztaoS-Y!0R^y>B0Gl7=63)?aXq)X6s>A7$!kQ5>33yV)2}*JD7?FP{TO<4{rJ-l*mu zdw67Dtwzq)f`akx7h`Yq-VLo-Jm~^@1%hC25wk7CT}egBc=HuY8u04uzb!FG$nhs& zAG-Riz2H5iiHD!scMC(t7#!UPEZey5x+KG!>eMqhtan zLNb=r$BgV9NppW%^G$ZscH)@nG_xBs%)V$R#1OB ziV!Smg23mc*g&VrMJT?@nrTJvk_I$PeimSssbz$le7Q8Q-*Z?l)Ir0wH0>*Vs|#`I zFb$-n`RE!9Adz1qtNW*w7-R?#oR{VL*1pfvluR(0ZCsaJR*Qk)UO=84>=Ckhb14Hj zAQ1q>9PnFPmjlTI&8to)VWf|uOF3gH`}f}<7LH%SxVEL9g5XeYHoK%?p!PGA=K91J z7%rpm86TDG?}Iu8*1h~2wrNB+?^39kZRzdVr^+Fz=AT^u5G z?FdX5xJjpSmmp{q5F7ak>-okf#MM5JsB*H)L(UX{z$SZRt}ABaG}U&t$JH2D&cKeM zJbT0c>tW9M!NxczrC~5MBmCy^CQzmsBn8-wq6a3kuJDq>{ukzG62s#Y!Zy(!eHeqM zS;IymsE@??G&)|jPz$niV+uEwipt^PUamqdZcF(Vl$2Q?d!6&`>lMT^!~m$^z@blk zbku%<0B@1%m>T_)FgW6YQR!;y#j_8^H-S)UBWn{Ecfbv00+jN-%kW!g4uq*xX`-{} zuO?ls?dbt>o{g;|lMX{^FtnpmaaEhQ1~7(t*3x^-380wRiTOH!4{NJebJCVGAjK?$ zi`M#W1P6q2C5jPiBxm{ZL{p^E!hDrv46?Js>=3X7ARRg&-D@2W zdJtfMN18t;PkX;VCOXcY&EnM5J12TbJ2M#>6A!%nlr@7K#i}P!@`bcC!oRW{Q;Cm{U^Bcsy%OXTvEhL2kDlHSB4BMH+28#LvauG#; zwrX+_SfxP3GWI5)eMZi_78X#(irFSQhz&6dAah$L+qH3H2b%>bkG%7$1KO@IkNK`8 zaVq1JpaaRmu4n0k0~~xh@z4%qIf%ZJ->PF2hV4cuRHoD?gU9Dob-2w8Y!)~I2{hS_ zgob{Ug~dz;h=0Wj!U2lIrH1y1^8%UG6Q_I{OfIBH5VWKBgx!W*6=0W3_Uog1dd>r8 zV6B<-$86u5CG(bz{*54bEt!lN1nl83G#qX^1`|!O*nAhG%@IYH6$o9esm(c+Ys=0g zzN~tnw?PbF^Nw@bn=;)eHbzN6l7SZpaifT`K9B+y0F;Fkbl2DACno3eq}v8vjJ*eyPT|=(lab3~Y*yg_UN<8jNI)m>bD;~uToXnb&!uaSb9@;(3h=@2#m z>wExW0<28^X9PZa`%vTwq+rXv#uv|T(O4oyb*x+?Me~Oi(QPGS&?*-k(6d|sZH$-D z@RT-|1TgEsfHcWw`%3~}`}ql=p2cmhx!cMX`>;uM_WPmZlTtD&)zUIsL7m6-z3dY& z^`32?z!ScC$V3&(KpgZ5)$KKh)m46%QSxcMfeFNyy%sq<41h0@PB4jK14YNq!B>;% ztRP^oNpJg{{z3F=kK|KsPZ#YusWH`816WVTeDUChM)!b^V_+MnqqZELUcia~C@}bL zMthph_ADSCk7y`4wln-B*%G3)XI z^)ZWYRBzNN;t7CHW{{dL-*m17#KC6dl z_SYM)N~Vj96GC$Q8mL@mb^`ZXA2Nzq=<*(6Lz=Ma++>-7C?V1+Y&}{oSJVOVOe#fi zW?;ZcvRu)&Aw0(W5$NxLF=t2)=QW%-AVsHi12;Z*bvsgE_S4w`SS`Pa12MP4msa@`2({xN~;A} zE-3&>|IPwlQOjNov2y8_uSv$UWtHAQAM-W&bo`nGBmoFZ;r)1^erPgj z5;hCLmRQCIH3j7;0Lu$S3-{4I*{Q!R&Ml^1xB5Ktvt!B1HX!bidwi=c#r8Q|A#4CE z7tv@-vP^N01Tdhxtlr}RQjap9M@!qOW)<|){KJAT_0C)kHB9u(I8rIkZjT#7zPL{f z_bHbAU)e^6$6mj2K#R(hZt|(_`QyFGVyfTDeOX{_ zSh>S9X!BE$p!{D2>=`An3qSxfbBE56gDxibW#|S{-%HKf5&?NX04ipzFLaoTHL9;}ptIwJt$5wceWzrf z16q&oX@E!5qCPuL@;^2>k%d+A7pDu|CjmCehXMgS@68UaHGt7{ZN~g4xG)PGdjdV-H4BarqzyL$sT>-i)stx*T7Hx}81je?2# z5%58Z`u%MudRMkt4nYIg;bM$&=0j;rzI3x8Y;$N7 yG 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 NF6my(J#Y2ke*p{XTJ-<` literal 0 HcmV?d00001 diff --git a/tendermint/src/lite/types.rs b/tendermint/src/lite/types.rs index 1de20c37c..1ffd058bc 100644 --- a/tendermint/src/lite/types.rs +++ b/tendermint/src/lite/types.rs @@ -27,11 +27,7 @@ pub trait Header: Clone { } /// ValidatorSet is the full validator set. -/// It exposes its hash, which should match whats in a header, -/// and its total power. It also has an underlying -/// Validator type which can be used for verifying signatures. -/// It also provides a lookup method to fetch a validator by -/// its identifier. +/// It exposes its hash and its total power. pub trait ValidatorSet: Clone { /// Hash of the validator set. fn hash(&self) -> Hash; @@ -40,9 +36,9 @@ pub trait ValidatorSet: Clone { fn total_power(&self) -> u64; } -/// Commit is proof a Header is valid. -/// It has an underlying Vote type with the relevant vote data -/// for verification. +/// Commit is used to prove a Header can be trusted. +/// Verifying the Commit requires access to an associated ValidatorSet +/// to determine what voting power signed the commit. pub trait Commit: Clone { type ValidatorSet: ValidatorSet; From d41487eb6f9dd66610770a48ae23112f7106795b Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Wed, 12 Feb 2020 17:02:47 +0000 Subject: [PATCH 09/13] Dealing with the merging in master aftermath --- light-node/src/commands/start.rs | 3 ++- tendermint-lite/src/main.rs | 0 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 tendermint-lite/src/main.rs diff --git a/light-node/src/commands/start.rs b/light-node/src/commands/start.rs index f8e4e6f7f..121ffafcc 100644 --- a/light-node/src/commands/start.rs +++ b/light-node/src/commands/start.rs @@ -7,7 +7,7 @@ use crate::prelude::*; use core::future::Future; use tendermint::hash; use tendermint::lite; -use tendermint::lite::{Error, Header, Height, Requester, TrustThresholdFraction}; +use tendermint::lite::{Header, Height, Requester, TrustThresholdFraction}; use tendermint::rpc; use tendermint::Hash; use tokio::runtime::Builder; @@ -19,6 +19,7 @@ use crate::requester::RPCRequester; use crate::store::{MemStore, State}; use abscissa_core::{config, Command, FrameworkError, Options, Runnable}; use std::time::{Duration, SystemTime}; +use tendermint::lite::error::Error; /// `start` subcommand /// diff --git a/tendermint-lite/src/main.rs b/tendermint-lite/src/main.rs deleted file mode 100644 index e69de29bb..000000000 From 42ef409b96aa5c5ba99d907230b2f3c11578b15b Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Fri, 14 Feb 2020 08:42:07 +0100 Subject: [PATCH 10/13] merged in master --- light-node/src/commands/start.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/light-node/src/commands/start.rs b/light-node/src/commands/start.rs index 121ffafcc..b974c4ab4 100644 --- a/light-node/src/commands/start.rs +++ b/light-node/src/commands/start.rs @@ -72,12 +72,12 @@ impl Runnable for StartCmd { latest_trusted_height, latest_peer_height, ); - let now = &SystemTime::now(); + let now = SystemTime::now(); lite::verify_bisection( latest_trusted.to_owned(), latest_peer_height, TrustThresholdFraction::default(), // TODO - &config.trusting_period, + config.trusting_period, now, &req, ) From 43c74a5bf4f37b08377bc6a26bdea6d9b08d0638 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Tue, 10 Mar 2020 16:41:10 +0100 Subject: [PATCH 11/13] Fix merge master fallout (related to #169) --- light-node/src/commands/start.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/light-node/src/commands/start.rs b/light-node/src/commands/start.rs index b974c4ab4..382b3529e 100644 --- a/light-node/src/commands/start.rs +++ b/light-node/src/commands/start.rs @@ -4,15 +4,12 @@ /// accessors along with logging macros. Customize as you see fit. use crate::prelude::*; -use core::future::Future; use tendermint::hash; use tendermint::lite; +use tendermint::lite::ValidatorSet as _; use tendermint::lite::{Header, Height, Requester, TrustThresholdFraction}; use tendermint::rpc; use tendermint::Hash; -use tokio::runtime::Builder; - -use tendermint::lite::ValidatorSet as _; use crate::config::LightNodeConfig; use crate::requester::RPCRequester; @@ -40,7 +37,7 @@ impl Runnable for StartCmd { fn run(&self) { let config = app_config(); - let client = block_on(rpc::Client::new(&config.rpc_address.parse().unwrap())).unwrap(); + let client = rpc::Client::new(config.rpc_address.parse().unwrap()); let req = RPCRequester::new(client); let mut store = MemStore::new(); @@ -150,12 +147,3 @@ fn subjective_init( Ok(()) } - -fn block_on(future: F) -> F::Output { - Builder::new() - .basic_scheduler() - .enable_all() - .build() - .unwrap() - .block_on(future) -} From 6150a7248c8e6f0cd6dadc5ae311af079e3dcf89 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Thu, 12 Mar 2020 21:53:16 +0100 Subject: [PATCH 12/13] Use `abscissa_tokio` and resolve merge conflicts --- light-node/Cargo.toml | 4 +- light-node/src/application.rs | 4 +- light-node/src/commands/start.rs | 98 ++++++++++++++++++-------------- 3 files changed, 60 insertions(+), 46 deletions(-) diff --git a/light-node/Cargo.toml b/light-node/Cargo.toml index a81e074e4..85d8fab51 100644 --- a/light-node/Cargo.toml +++ b/light-node/Cargo.toml @@ -8,7 +8,9 @@ edition = "2018" gumdrop = "0.7" serde = { version = "1", features = ["serde_derive"] } tendermint = { version = "0.12.0-rc0", path = "../tendermint" } -tokio = "0.2" +async-trait = "0.1" +tokio = { version = "0.2", features = ["full"] } +abscissa_tokio = "0.5" [dependencies.abscissa_core] version = "0.5.0" diff --git a/light-node/src/application.rs b/light-node/src/application.rs index 36b32fc6a..1ab05ac9e 100644 --- a/light-node/src/application.rs +++ b/light-node/src/application.rs @@ -5,6 +5,7 @@ use abscissa_core::{ application::{self, AppCell}, config, trace, Application, EntryPoint, FrameworkError, StandardPaths, }; +use abscissa_tokio::TokioComponent; /// Application state pub static APPLICATION: AppCell = AppCell::new(); @@ -82,7 +83,8 @@ impl Application for LightNodeApp { /// beyond the default ones provided by the framework, this is the place /// to do so. fn register_components(&mut self, command: &Self::Cmd) -> Result<(), FrameworkError> { - let components = self.framework_components(command)?; + let mut components = self.framework_components(command)?; + components.push(Box::new(TokioComponent::new()?)); self.state.components.register(components) } diff --git a/light-node/src/commands/start.rs b/light-node/src/commands/start.rs index 382b3529e..a0961825d 100644 --- a/light-node/src/commands/start.rs +++ b/light-node/src/commands/start.rs @@ -11,10 +11,12 @@ use tendermint::lite::{Header, Height, Requester, TrustThresholdFraction}; use tendermint::rpc; use tendermint::Hash; +use crate::application::APPLICATION; use crate::config::LightNodeConfig; use crate::requester::RPCRequester; use crate::store::{MemStore, State}; use abscissa_core::{config, Command, FrameworkError, Options, Runnable}; +use std::process; use std::time::{Duration, SystemTime}; use tendermint::lite::error::Error; @@ -35,56 +37,64 @@ pub struct StartCmd { impl Runnable for StartCmd { /// Start the application. fn run(&self) { - let config = app_config(); + if let Err(err) = abscissa_tokio::run(&APPLICATION, async { + let config = app_config(); - let client = rpc::Client::new(config.rpc_address.parse().unwrap()); - let req = RPCRequester::new(client); - let mut store = MemStore::new(); + let client = rpc::Client::new(config.rpc_address.parse().unwrap()); + let req = RPCRequester::new(client); + let mut store = MemStore::new(); - let vals_hash = Hash::from_hex_upper( - hash::Algorithm::Sha256, - &config.subjective_init.validators_hash, - ) - .unwrap(); + let vals_hash = Hash::from_hex_upper( + hash::Algorithm::Sha256, + &config.subjective_init.validators_hash, + ) + .unwrap(); - println!("Requesting from {}.", config.rpc_address); + println!("Requesting from {}.", config.rpc_address); - subjective_init(config.subjective_init.height, vals_hash, &mut store, &req).unwrap(); + subjective_init(config.subjective_init.height, vals_hash, &mut store, &req) + .await + .unwrap(); - loop { - let latest_sh = (&req).signed_header(0).unwrap(); - let latest_peer_height = latest_sh.header().height(); + loop { + let latest_sh = (&req).signed_header(0).await.unwrap(); + let latest_peer_height = latest_sh.header().height(); - let latest_trusted = store.get(0).unwrap(); - let latest_trusted_height = latest_trusted.last_header().header().height(); + let latest_trusted = store.get(0).unwrap(); + let latest_trusted_height = latest_trusted.last_header().header().height(); - // only bisect to higher heights - if latest_peer_height <= latest_trusted_height { - std::thread::sleep(Duration::new(1, 0)); - continue; - } + // only bisect to higher heights + if latest_peer_height <= latest_trusted_height { + std::thread::sleep(Duration::new(1, 0)); + continue; + } - println!( - "attempting bisection from height {:?} to height {:?}", - latest_trusted_height, latest_peer_height, - ); - - let now = SystemTime::now(); - lite::verify_bisection( - latest_trusted.to_owned(), - latest_peer_height, - TrustThresholdFraction::default(), // TODO - config.trusting_period, - now, - &req, - ) - .unwrap(); + println!( + "attempting bisection from height {:?} to height {:?}", + latest_trusted_height, latest_peer_height, + ); + + let now = SystemTime::now(); + lite::verify_bisection( + latest_trusted.to_owned(), + latest_peer_height, + TrustThresholdFraction::default(), // TODO + config.trusting_period, + now, + &req, + ) + .await + .unwrap(); - println!("Succeeded bisecting!"); + println!("Succeeded bisecting!"); - // notifications ? + // notifications ? - // sleep for a few secs ? + // sleep for a few secs ? + } + }) { + eprintln!("Error while running application: {}", err); + process::exit(1); } } } @@ -114,7 +124,7 @@ impl config::Override for StartCmd { * TODO: this should take traits ... but how to deal with the State ? * TODO: better name ? */ -fn subjective_init( +async fn subjective_init( height: Height, vals_hash: Hash, store: &mut MemStore, @@ -126,22 +136,22 @@ fn subjective_init( } // check that the val hash matches - let vals = req.validator_set(height)?; + let vals = req.validator_set(height).await?; if vals.hash() != vals_hash { // TODO panic!("vals hash dont match") } - let signed_header = req.signed_header(height)?; + let signed_header = req.signed_header(height).await?; // TODO: validate signed_header.commit() with the vals ... - let next_vals = req.validator_set(height + 1)?; + let next_vals = req.validator_set(height + 1).await?; // TODO: check next_vals ... - let trusted_state = &State::new(&signed_header, &next_vals); + let trusted_state = &State::new(signed_header, next_vals); store.add(trusted_state.to_owned())?; From a005ae91a1906338a76d106187eda803abc99d9d Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Thu, 12 Mar 2020 22:03:43 +0100 Subject: [PATCH 13/13] New stable rust -> new clippy errs -> fixed --- tendermint/src/abci/transaction.rs | 2 +- tendermint/src/evidence.rs | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/tendermint/src/abci/transaction.rs b/tendermint/src/abci/transaction.rs index 690a02eb9..d5f078b31 100644 --- a/tendermint/src/abci/transaction.rs +++ b/tendermint/src/abci/transaction.rs @@ -90,6 +90,6 @@ impl Data { impl AsRef<[Transaction]> for Data { fn as_ref(&self) -> &[Transaction] { - self.txs.as_ref().map(Vec::as_slice).unwrap_or_else(|| &[]) + self.txs.as_deref().unwrap_or_else(|| &[]) } } diff --git a/tendermint/src/evidence.rs b/tendermint/src/evidence.rs index ff0d81d73..69486152f 100644 --- a/tendermint/src/evidence.rs +++ b/tendermint/src/evidence.rs @@ -80,10 +80,7 @@ impl Data { impl AsRef<[Evidence]> for Data { fn as_ref(&self) -> &[Evidence] { - self.evidence - .as_ref() - .map(Vec::as_slice) - .unwrap_or_else(|| &[]) + self.evidence.as_deref().unwrap_or_else(|| &[]) } }