diff --git a/Dockerfile b/Dockerfile index 7e12d4e14..c953401f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -83,13 +83,33 @@ RUN addgroup --system entropy \ entropy \ && chown -R entropy:entropy /srv/entropy +# Despite statically linking our binaries, we will still need these +# libraries to process the /etc/nsswitch.conf file and perform DNS +# lookups, as we've built with glibc, but Alpine provides musl libc. +# This is a notorious issue in GNU glibc, currently without a fix: +# https://sourceware.org/bugzilla/show_bug.cgi?id=27959 +COPY --from=build \ + /lib/x86_64-linux-gnu/libnss_* \ + /lib/x86_64-linux-gnu/libc.so.6 \ + /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 \ + /lib/x86_64-linux-gnu/libresolv.so.2 \ + /lib/x86_64-linux-gnu/ + +# Lastly, we copy our own files into the final container image stage. COPY --from=build --chown=entropy:entropy --chmod=554 /usr/local/bin/${PACKAGE} /usr/local/bin/${PACKAGE} COPY --chown=entropy:entropy --chmod=554 bin/entrypoint.sh /usr/local/bin/entrypoint.sh + +# Don't run as the `root` user within the container. USER entropy ### -# Describe the available ports to expose. -## +# Describe the available ports to expose for `server`. +### +# TSS server's REST-style HTTP API port. +EXPOSE 3001 +### +# Describe the available ports to expose for `entropy`. +### # Substrate's default Prometheus endpoint. EXPOSE 9615 # Substrate's default RPC port. diff --git a/docker-compose.yaml b/docker-compose.yaml index 1aee61c17..2a224bd6f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -16,8 +16,8 @@ secrets: services: # Threshold Signature Scheme server - # In a local devnet setup, for now, this is "Alice's server." - tss-server: + # In a local devnet setup, for now, this is "Alice's TSS server." + alice-tss-server: build: args: PACKAGE: server @@ -34,11 +34,11 @@ services: - "--threshold-url" - "0.0.0.0:3001" - "--chain-endpoint" - - "ws://chain-node:9944" + - "ws://alice-chain-node:9944" # Sometimes also called simply a "chain," or a "validator." # In a local devnet setup, for now, this is "Alice's chain." - chain-node: + alice-chain-node: build: ssh: - default @@ -47,16 +47,19 @@ services: tags: - entropy:chain-node depends_on: - - tss-server + - alice-tss-server ports: - # Enables other chain nodes speak to the validator. - # We comment this out because we're working only within - # the Docker network layer, not the host's network stack. + # Enables other chain nodes speak to the validator. We comment + # this out because we're working only within the Docker network + # layer, not the host's network stack. I.e., the port is open + # locally, but we do not publish it to the Docker host itself. #- "127.0.0.1:30333:30333/tcp" # P2P Port. - # Enables network clients to speak to the node's API. - - "127.0.0.1:9944:9944/tcp" # RPC Port. + # Enables network clients to speak to the chain node's REST API. + - "127.0.0.1:9944:9944/tcp" # "RPC Port." command: - - "--alice" + - "--chain" + - "local-devnet" + - "--alice" # Shortcut for `--name Alice --validator` - "--base-path" - ".entropy/alice" - "--rpc-port" @@ -64,15 +67,15 @@ services: - "--unsafe-rpc-external" # Intentional, for TSS's access. - "--rpc-cors" - "all" - - "--validator" # This is what makes the node a validator. - "--node-key=0000000000000000000000000000000000000000000000000000000000000001" - "--tss-server-endpoint" - - "http://tss-server:3001" + - "http://alice-tss-server:3001" - bob-tss: + # "Bob's TSS server." + bob-tss-server: image: entropy:tss-server depends_on: - - tss-server + - alice-tss-server ports: - "127.0.0.1:3002:3001/tcp" command: @@ -82,19 +85,17 @@ services: - "--chain-endpoint" - "ws://bob-chain-node:9944" + # "Bob's chain node." bob-chain-node: image: entropy:chain-node depends_on: - - bob-tss + - bob-tss-server ports: - # Enables other chain nodes speak to the validator. - # We comment this out because we're working only within - # the Docker network layer, not the host's network stack. - #- "127.0.0.1:30333:30333/tcp" # P2P Port. - # Enables network clients to speak to the node's API. - - "127.0.0.1:9945:9944/tcp" # RPC Port. + - "127.0.0.1:9945:9944/tcp" command: - - "--bob" + - "--chain" + - "local-devnet" + - "--bob" # Shortcut for `--name Bob --validator` - "--base-path" - ".entropy/bob" - "--rpc-port" @@ -102,8 +103,7 @@ services: - "--unsafe-rpc-external" # Intentional, for TSS's access. - "--rpc-cors" - "all" - - "--validator" # This is what makes the node a validator. - "--bootnodes" - - "/dns4/chain-node/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp" + - "/dns4/alice-chain-node/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp" - "--tss-server-endpoint" - - "http://bob-tss:3001" + - "http://bob-tss-server:3001" diff --git a/node/cli/src/admin.rs b/node/cli/src/admin.rs index 61b6fbf54..79bd304c9 100644 --- a/node/cli/src/admin.rs +++ b/node/cli/src/admin.rs @@ -8,7 +8,8 @@ use sp_consensus_babe::AuthorityId as BabeId; use sp_core::{crypto::UncheckedInto, sr25519}; use crate::chain_spec::{ - authority_keys_from_seed, devnet_genesis, get_account_id_from_seed, testing, testnet_genesis, + authority_keys_from_seed, devnet_genesis, get_account_id_from_seed, local_devnet_genesis, + testing, testnet_genesis, }; pub fn devnet_config_genesis() -> RuntimeGenesisConfig { @@ -225,7 +226,15 @@ pub fn development_config_genesis() -> RuntimeGenesisConfig { ) } -pub fn testing_config_genesis() -> RuntimeGenesisConfig { +pub fn local_devnet_config_genesis() -> GenesisConfig { + local_devnet_genesis( + vec![authority_keys_from_seed("Alice"), authority_keys_from_seed("Bob")], + vec![], + get_account_id_from_seed::("Alice"), + ) +} + +pub fn testing_config_genesis() -> GenesisConfig { testing( vec![authority_keys_from_seed("Alice"), authority_keys_from_seed("Bob")], vec![], diff --git a/node/cli/src/chain_spec.rs b/node/cli/src/chain_spec.rs index f2a48e459..631a77512 100644 --- a/node/cli/src/chain_spec.rs +++ b/node/cli/src/chain_spec.rs @@ -115,7 +115,9 @@ pub fn get_from_seed(seed: &str) -> ::Pu /// Helper function to generate an account ID from seed pub fn get_account_id_from_seed(seed: &str) -> AccountId -where AccountPublic: From<::Public> { +where + AccountPublic: From<::Public>, +{ AccountPublic::from(get_from_seed::(seed)).into_account() } @@ -291,7 +293,187 @@ pub fn testnet_genesis( } } -/// Helper function to create RuntimeGenesisConfig for testing +/// Generates a [Substrate chain spec] for use during "[local devnet]" +/// operation to ensure proper startup of the network. +/// +/// Poper startup means informing each of the [chain nodes] that make +/// up the initial network participants of each other's reachable +/// network addresses, such thtat they can all find communicate with +/// one another, along with other information such as initial funding +/// balances, and the initial ("genesis") values of certain key data. +/// This network-wide, shared data is termed "on-chain storage." The +/// generated chain spec is thus the genesis data (initial values) of +/// the on-chain storage for the network. +/// +/// [Substrate chain spec]: https://docs.substrate.io/build/chain-spec/ +/// [local devnet]: https://github.com/entropyxyz/meta/wiki/Local-devnet +/// [chain nodes]: https://github.com/entropyxyz/meta/wiki/Glossary#chain-node +pub fn local_devnet_genesis( + initial_authorities: Vec<( + AccountId, + AccountId, + GrandpaId, + BabeId, + ImOnlineId, + AuthorityDiscoveryId, + )>, + initial_nominators: Vec, + root_key: AccountId, +) -> GenesisConfig { + let mut endowed_accounts = endowed_accounts_dev(); + // endow all authorities and nominators. + initial_authorities.iter().map(|x| &x.0).chain(initial_nominators.iter()).for_each(|x| { + if !endowed_accounts.contains(x) { + endowed_accounts.push(x.clone()) + } + }); + + // stakers: all validators and nominators. + let mut rng = rand::thread_rng(); + let stakers = initial_authorities + .iter() + .map(|x| (x.0.clone(), x.1.clone(), STASH, StakerStatus::Validator)) + .chain(initial_nominators.iter().map(|x| { + use rand::{seq::SliceRandom, Rng}; + let limit = (MaxNominations::get() as usize).min(initial_authorities.len()); + let count = rng.gen::() % limit; + let nominations = initial_authorities + .as_slice() + .choose_multiple(&mut rng, count) + .map(|choice| choice.0.clone()) + .collect::>(); + (x.clone(), x.clone(), STASH, StakerStatus::Nominator(nominations)) + })) + .collect::>(); + + let num_endowed_accounts = endowed_accounts.len(); + + const ENDOWMENT: Balance = 10_000_000 * DOLLARS; + const STASH: Balance = ENDOWMENT / 1000; + + GenesisConfig { + system: SystemConfig { code: wasm_binary_unwrap().to_vec() }, + balances: BalancesConfig { + balances: endowed_accounts.iter().cloned().map(|x| (x, ENDOWMENT)).collect(), + }, + indices: IndicesConfig { indices: vec![] }, + session: SessionConfig { + keys: initial_authorities + .iter() + .map(|x| { + ( + x.0.clone(), + x.0.clone(), + session_keys(x.2.clone(), x.3.clone(), x.4.clone(), x.5.clone()), + ) + }) + .collect::>(), + }, + staking: StakingConfig { + validator_count: initial_authorities.len() as u32, + minimum_validator_count: 0, + invulnerables: vec![], + slash_reward_fraction: Perbill::from_percent(10), + stakers, + ..Default::default() + }, + staking_extension: StakingExtensionConfig { + threshold_servers: vec![ + ( + get_account_id_from_seed::("Alice//stash"), + ServerInfo { + tss_account: hex![ + "e0543c102def9f6ef0e8b8ffa31aa259167a9391566929fd718a1ccdaabdb876" + ] + .into(), + endpoint: "alice-tss-server:3001".as_bytes().to_vec(), + x25519_public_key: [ + 10, 192, 41, 240, 184, 83, 178, 59, 237, 101, 45, 109, 13, 230, 155, + 124, 195, 141, 148, 249, 55, 50, 238, 252, 133, 181, 134, 30, 144, 247, + 58, 34, + ], + }, + ), + ( + get_account_id_from_seed::("Bob//stash"), + ServerInfo { + tss_account: hex![ + "2a8200850770290c7ea3b50a8ff64c6761c882ff8393dc95fccb5d1475eff17f" + ] + .into(), + endpoint: "bob-tss-server:3001".as_bytes().to_vec(), + x25519_public_key: [ + 225, 48, 135, 211, 227, 213, 170, 21, 1, 189, 118, 158, 255, 87, 245, + 89, 36, 170, 169, 181, 68, 201, 210, 178, 237, 247, 101, 80, 153, 136, + 102, 10, + ], + }, + ), + ], + signing_groups: vec![ + (0, vec![get_account_id_from_seed::("Alice//stash")]), + (1, vec![get_account_id_from_seed::("Bob//stash")]), + ], + }, + democracy: DemocracyConfig::default(), + elections: ElectionsConfig { + members: endowed_accounts + .iter() + .take((num_endowed_accounts + 1) / 2) + .cloned() + .map(|member| (member, STASH)) + .collect(), + }, + council: CouncilConfig::default(), + technical_committee: TechnicalCommitteeConfig { + members: endowed_accounts + .iter() + .take((num_endowed_accounts + 1) / 2) + .cloned() + .collect(), + phantom: Default::default(), + }, + sudo: SudoConfig { key: Some(root_key) }, + babe: BabeConfig { + authorities: vec![], + epoch_config: Some(entropy_runtime::BABE_GENESIS_EPOCH_CONFIG), + }, + im_online: ImOnlineConfig { keys: vec![] }, + authority_discovery: AuthorityDiscoveryConfig { keys: vec![] }, + grandpa: GrandpaConfig { authorities: vec![] }, + technical_membership: Default::default(), + treasury: Default::default(), + society: SocietyConfig { + members: endowed_accounts + .iter() + .take((num_endowed_accounts + 1) / 2) + .cloned() + .collect(), + pot: 0, + max_members: 999, + }, + relayer: RelayerConfig { + registered_accounts: vec![ + (get_account_id_from_seed::("Dave"), 0, None), + ( + get_account_id_from_seed::("Eve"), + 1, + Some([ + 28, 63, 144, 84, 78, 147, 195, 214, 190, 234, 111, 101, 117, 133, 9, 198, + 96, 96, 76, 140, 152, 251, 255, 28, 167, 38, 157, 185, 192, 42, 201, 82, + ]), + ), + (get_account_id_from_seed::("Ferdie"), 2, None), + ], + }, + vesting: Default::default(), + transaction_storage: Default::default(), + transaction_payment: Default::default(), + nomination_pools: Default::default(), + } +} + +/// Helper function to create GenesisConfig for testing pub fn devnet_genesis( initial_authorities: Vec<( AccountId, @@ -614,6 +796,23 @@ pub fn development_config() -> ChainSpec { ) } +/// Local devnet configuration, used when invoked with the +/// `--chain local-devnet` option. +pub fn local_devnet_config() -> ChainSpec { + ChainSpec::from_genesis( + "Entropy Local Devnet", + "EDevLocal", + ChainType::Development, + admin::local_devnet_config_genesis, + vec![], + None, + None, + None, + None, + Default::default(), + ) +} + /// Testing config (single validator Alice) pub fn testing_config() -> ChainSpec { ChainSpec::from_genesis( @@ -737,14 +936,22 @@ pub(crate) mod tests { } #[test] - fn test_create_development_chain_spec() { development_config().build_storage().unwrap(); } + fn test_create_development_chain_spec() { + development_config().build_storage().unwrap(); + } #[test] - fn test_create_local_testnet_chain_spec() { local_testnet_config().build_storage().unwrap(); } + fn test_create_local_testnet_chain_spec() { + local_testnet_config().build_storage().unwrap(); + } #[test] - fn test_staging_test_net_chain_spec() { staging_testnet_config().build_storage().unwrap(); } + fn test_staging_test_net_chain_spec() { + staging_testnet_config().build_storage().unwrap(); + } #[test] - fn test_create_devnet_chain_spec() { devnet_config().build_storage().unwrap(); } + fn test_create_devnet_chain_spec() { + devnet_config().build_storage().unwrap(); + } } diff --git a/node/cli/src/command.rs b/node/cli/src/command.rs index 06dea2b21..9b774f5c6 100644 --- a/node/cli/src/command.rs +++ b/node/cli/src/command.rs @@ -28,6 +28,7 @@ impl SubstrateCli for Cli { Ok(match id { "dev" => Box::new(chain_spec::development_config()), "test" => Box::new(chain_spec::testing_config()), + "local-devnet" => Box::new(chain_spec::local_devnet_config()), "" | "local" => Box::new(chain_spec::local_testnet_config()), "devnet" => Box::new(chain_spec::devnet_config()), path =>