Skip to content
This repository has been archived by the owner on Jun 3, 2020. It is now read-only.

Commit

Permalink
Merge pull request #205 from tendermint/chain-registry
Browse files Browse the repository at this point in the history
Initial chain registry (fixes #60, #178)
  • Loading branch information
tarcieri authored Mar 10, 2019
2 parents c4b5b88 + e1d356e commit f8b07d9
Show file tree
Hide file tree
Showing 47 changed files with 1,046 additions and 510 deletions.
7 changes: 5 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,11 @@ jobs:
- run:
name: validate against test harness
command: |
cargo build --features=softsign
TMKMS_BIN=./target/debug/tmkms sh tests/support/run-harness-tests.sh
echo "TODO(tarcieri): re-enable me!!!"
# TODO(tarcieri): update Tendermint config files and re-enabled test harness
# See: https://github.com/tendermint/kms/issues/207
# cargo build --features=softsign
# TMKMS_BIN=./target/debug/tmkms sh tests/support/run-harness-tests.sh
- save_cache:
key: cache-2019-01-25-v0 # bump restore_cache key above too
paths:
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/target
**/*.rs.bk
tmkms.toml
**/*.rs.bk
**/*priv_validator_state.json

# Ignore VIM swap files
*.swp
Expand Down
26 changes: 18 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ signatory-ledger-tm = { version = "0.11", optional = true }
subtle-encoding = { version = "0.3", features = ["bech32-preview"] }
tendermint = { version = "0.3", path = "tendermint-rs" }
tiny-bip39 = "0.6"
wait-timeout = "0.2"
yubihsm = { version = "0.22", features = ["setup", "usb"], optional = true }
zeroize = "0.5"

Expand Down
5 changes: 3 additions & 2 deletions README.yubihsm.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ Below is an example of the command to generate and export an encrypted backup
of an Ed25519 signing key:

```
$ tmkms yubihsm keys generate 1 -l "steakz4u-validator:2019-03-06T01:25:39Z" -b steakz4u-validator-key.enc
$ tmkms yubihsm keys generate 1 -p cosmosvalconspub -b steakz4u-validator-key.enc
Generated key #1: cosmosvalconspub1zcjduepqtvzxa733n7dhrjf247n0jtdwsvvsd4jgqvzexj5tkwerpzy5sugsvmfja3
Wrote backup of key 1 (encrypted under wrap key 1) to steakz4u-validator-key.enc
```
Expand All @@ -327,6 +327,7 @@ the following sets of credentials:

- `tmkms yubihsm keys generate 1` - generates asymmetric key 0x0001, which is by
default an Ed25519 signing key.
- `-p` (or `--prefix`): Bech32 prefix to serialize key with (automatically sets label)
- `-l` (or `--label`): an up-to-40-character label describing the key
- `-b` (or `--backup`): path to a file where an *encrypted* backup of the
generated key should be written
Expand All @@ -342,7 +343,7 @@ The following command lists keys in the HSM:
```
$ tmkms yubihsm keys list
Listing keys in YubiHSM #9876543211:
- #1: cosmosvalconspub1zcjduepqtvzxa733n7dhrjf247n0jtdwsvvsd4jgqvzexj5tkwerpzy5sugsvmfja3
- 0x#0001: 1624DE64200FB6DB3175225219D290497E3B78190A3EEDA89AEBBC2E2294547CA98E76F9D5
```

## Exporting and Importing Keys
Expand Down
19 changes: 19 additions & 0 deletions src/chain/guard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use super::{Chain, Id};
use std::{collections::BTreeMap, sync::RwLockReadGuard};

/// Wrapper for a `RwLockReadGuard<'static, BTreeMap<Id, Chain>>`, allowing access to
/// global information about particular Tendermint networks / "chains"
pub struct Guard<'lock>(RwLockReadGuard<'lock, BTreeMap<Id, Chain>>);

impl<'lock> From<RwLockReadGuard<'lock, BTreeMap<Id, Chain>>> for Guard<'lock> {
fn from(guard: RwLockReadGuard<'lock, BTreeMap<Id, Chain>>) -> Guard<'lock> {
Guard(guard)
}
}

impl<'lock> Guard<'lock> {
/// Get information about a particular chain ID (if registered)
pub fn chain(&self, chain_id: Id) -> Option<&Chain> {
self.0.get(&chain_id)
}
}
47 changes: 47 additions & 0 deletions src/chain/key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! Chain-specific key configuration

use super::{Id, REGISTRY};
use tendermint::TendermintKey;

/// Options for how keys for this chain are represented
#[derive(Clone, Debug, Deserialize)]
#[serde(tag = "type")]
pub enum Format {
/// Use the Bech32 serialization format with the given key prefixes
#[serde(rename = "bech32")]
Bech32 {
/// Prefix to use for Account keys
account_key_prefix: String,

/// Prefix to use for Consensus keys
consensus_key_prefix: String,
},

/// Hex is a baseline representation
#[serde(rename = "hex")]
Hex,
}

impl Format {
/// Serialize a `TendermintKey` according to chain-specific rules
pub fn serialize(&self, public_key: TendermintKey) -> String {
match self {
Format::Bech32 {
account_key_prefix,
consensus_key_prefix,
} => match public_key {
TendermintKey::AccountKey(pk) => pk.to_bech32(account_key_prefix),
TendermintKey::ConsensusKey(pk) => pk.to_bech32(consensus_key_prefix),
},
Format::Hex => public_key.to_hex(),
}
}
}

/// Serialize a key according to chain-specific serialization rules
pub fn serialize(chain_id: Id, public_key: TendermintKey) -> Option<String> {
let registry = REGISTRY.get();
registry
.chain(chain_id)
.map(|chain| chain.key_format.serialize(public_key))
}
55 changes: 55 additions & 0 deletions src/chain/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//! Information about particular Tendermint blockchain networks

mod guard;
pub mod key;
pub mod registry;
pub mod state;

pub use self::{guard::Guard, registry::REGISTRY, state::State};
use crate::{config::chain::ChainConfig, error::KmsError};
use std::{path::PathBuf, sync::Mutex};
pub use tendermint::chain::Id;

/// Information about a particular Tendermint blockchain network
pub struct Chain {
/// ID of a particular chain
pub id: Id,

/// Key format configuration
pub key_format: key::Format,

/// State from the last block signed for this chain
pub state: Mutex<State>,
}

impl Chain {
/// Attempt to create a `Chain` state from the given configuration
pub fn from_config(config: &ChainConfig) -> Result<Chain, KmsError> {
let state_file = match config.state_file {
Some(ref path) => path.to_owned(),
None => PathBuf::from(&format!("{}_priv_validator_state.json", config.id)),
};

let mut state = State::load_state(state_file)?;

if let Some(ref hook) = config.state_hook {
match state::hook::run(hook) {
Ok(hook_output) => state.update_from_hook_output(hook_output)?,
Err(e) => {
if hook.fail_closed {
return Err(e);
} else {
// fail open: note the error to the log and proceed anyway
error!("error invoking state hook for chain {}: {}", config.id, e);
}
}
}
}

Ok(Self {
id: config.id,
key_format: config.key_format.clone(),
state: Mutex::new(state),
})
}
}
54 changes: 54 additions & 0 deletions src/chain/registry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//! Registry of information about known Tendermint blockchain networks

use super::{Chain, Guard, Id};
use crate::{
config::chain::ChainConfig,
error::{KmsError, KmsErrorKind::ConfigError},
};
use std::{collections::BTreeMap, sync::RwLock};

lazy_static! {
pub static ref REGISTRY: Registry = Registry::default();
}

/// Initialize the chain registry from the configuration file
pub fn load_from_config(chain_configs: &[ChainConfig]) -> Result<(), KmsError> {
for config in chain_configs {
REGISTRY.register(Chain::from_config(config)?)?;
}

Ok(())
}

/// Registry of blockchain networks known to the KMS
// The `RwLock` is a bit of futureproofing as this data structure is for the
// most part "immutable". New chains should be registered at boot time.
// The only case in which this structure may change is in the event of
// runtime configuration reloading, so the `RwLock` is included as
// futureproofing for such a feature.
// See: <https://github.com/tendermint/kms/issues/183>
#[derive(Default)]
pub struct Registry(RwLock<BTreeMap<Id, Chain>>);

impl Registry {
/// Acquire a read-only (concurrent) lock to the internal chain registry
pub fn get(&self) -> Guard {
// TODO(tarcieri): better handle `PoisonError` here?
self.0.read().unwrap().into()
}

/// Register a chain with the registry
pub fn register(&self, chain: Chain) -> Result<(), KmsError> {
// TODO(tarcieri): better handle `PoisonError` here?
let mut chain_map = self.0.write().unwrap();

let chain_id = chain.id;

if chain_map.insert(chain_id, chain).is_none() {
Ok(())
} else {
// TODO(tarcieri): handle updating the set of registered chains
fail!(ConfigError, "chain ID already registered: {}", chain_id);
}
}
}
42 changes: 42 additions & 0 deletions src/chain/state/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use abscissa::Error;
use std::fmt::{self, Display};

/// Error type
#[derive(Debug)]
pub struct StateError(pub(crate) Error<StateErrorKind>);

/// Kinds of errors
#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
pub enum StateErrorKind {
/// Height regressed
#[fail(display = "height regression")]
HeightRegression,

/// Step regressed
#[fail(display = "step regression")]
StepRegression,

/// Round regressed
#[fail(display = "round regression")]
RoundRegression,

/// Double sign detected
#[fail(display = "double sign detected")]
DoubleSign,

/// Error syncing state to disk
#[fail(display = "error syncing state to disk")]
SyncError,
}

impl From<Error<StateErrorKind>> for StateError {
fn from(other: Error<StateErrorKind>) -> Self {
StateError(other)
}
}

impl Display for StateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
Loading

0 comments on commit f8b07d9

Please sign in to comment.