use anyhow::{bail, ensure, format_err};
use exonum_crypto::{Hash, KeyPair, PublicKey, SecretKey, HASH_SIZE};
use exonum_derive::{BinaryValue, ObjectHash};
use exonum_merkledb::{
impl_binary_key_for_binary_value,
indexes::proof_map::RawKey,
validation::{is_allowed_index_name_char, is_valid_index_name_component},
BinaryKey, BinaryValue, ObjectHash,
};
use exonum_proto::ProtobufConvert;
use protobuf::well_known_types::Empty;
use semver::Version;
use serde_derive::{Deserialize, Serialize};
use std::{
borrow::Cow,
fmt::{self, Display},
str::FromStr,
};
use super::InstanceDescriptor;
use crate::{
blockchain::config::InstanceInitParams, helpers::ValidateInput, messages::Verified,
proto::schema,
};
pub type InstanceId = u32;
pub type MethodId = u32;
#[derive(Default, Clone, PartialEq, Eq, Ord, PartialOrd, Debug)]
#[derive(Serialize, Deserialize)]
#[derive(ProtobufConvert)]
#[protobuf_convert(source = "schema::base::CallInfo")]
#[non_exhaustive]
pub struct CallInfo {
pub instance_id: InstanceId,
pub method_id: MethodId,
}
impl CallInfo {
pub fn new(instance_id: u32, method_id: u32) -> Self {
Self {
instance_id,
method_id,
}
}
}
#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Debug)]
#[derive(Serialize, Deserialize)]
#[derive(ProtobufConvert, BinaryValue)]
#[protobuf_convert(source = "schema::base::AnyTx")]
#[non_exhaustive]
pub struct AnyTx {
pub call_info: CallInfo,
pub arguments: Vec<u8>,
}
impl AnyTx {
pub fn new(call_info: CallInfo, arguments: Vec<u8>) -> Self {
Self {
call_info,
arguments,
}
}
pub fn sign(self, public_key: PublicKey, secret_key: &SecretKey) -> Verified<Self> {
Verified::from_value(self, public_key, secret_key)
}
pub fn sign_with_keypair(self, keypair: &KeyPair) -> Verified<Self> {
Verified::from_value(self, keypair.public_key(), keypair.secret_key())
}
pub fn parse<T: BinaryValue>(&self) -> anyhow::Result<T> {
T::from_bytes(Cow::Borrowed(&self.arguments))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Serialize, Deserialize)]
#[derive(BinaryValue, ObjectHash, ProtobufConvert)]
#[protobuf_convert(source = "schema::base::ArtifactId")]
#[non_exhaustive]
pub struct ArtifactId {
pub runtime_id: u32,
pub name: String,
#[protobuf_convert(with = "crate::helpers::pb_version")]
pub version: Version,
}
#[allow(clippy::needless_pass_by_value)]
impl ArtifactId {
pub fn new(
runtime_id: impl Into<u32>,
name: impl Into<String>,
version: Version,
) -> anyhow::Result<Self> {
let artifact = Self::from_raw_parts(runtime_id.into(), name.into(), version);
artifact.validate()?;
Ok(artifact)
}
pub fn from_raw_parts(runtime_id: u32, name: String, version: Version) -> Self {
Self {
runtime_id,
name,
version,
}
}
pub fn is_upgrade_of(&self, other: &Self) -> bool {
self.name == other.name && self.version > other.version
}
pub fn into_default_instance(
self,
id: InstanceId,
name: impl Into<String>,
) -> InstanceInitParams {
InstanceInitParams::new(id, name, self, ())
}
}
impl ValidateInput for ArtifactId {
type Error = anyhow::Error;
fn validate(&self) -> Result<(), Self::Error> {
fn is_valid_identifier(name: &str) -> bool {
name.as_bytes()
.iter()
.all(|&c| is_allowed_index_name_char(c) || c == b'.' || c == b'/')
}
ensure!(!self.name.is_empty(), "Artifact name should not be empty");
ensure!(
is_valid_identifier(&self.name),
"Artifact name contains an illegal character, use only: `a-zA-Z0-9` and `/_.-`"
);
Ok(())
}
}
impl_binary_key_for_binary_value! { ArtifactId }
impl Display for ArtifactId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"{}:{}:{}",
self.runtime_id, self.name, self.version
)
}
}
impl FromStr for ArtifactId {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let split = s.splitn(3, ':').collect::<Vec<_>>();
match &split[..] {
[runtime_id, name, version] => {
let artifact = Self::new(
u32::from_str(runtime_id)?,
(*name).to_string(),
version.parse()?,
)?;
artifact.validate()?;
Ok(artifact)
}
_ => Err(anyhow::format_err!(
"Wrong `ArtifactId` format, should be in form \"runtime_id:name:version\""
)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Serialize, Deserialize)]
#[derive(ProtobufConvert, BinaryValue, ObjectHash)]
#[protobuf_convert(source = "schema::base::ArtifactSpec")]
#[non_exhaustive]
pub struct ArtifactSpec {
pub artifact: ArtifactId,
pub payload: Vec<u8>,
}
impl ArtifactSpec {
pub fn new(artifact: ArtifactId, deploy_spec: impl BinaryValue) -> Self {
Self {
artifact,
payload: deploy_spec.into_bytes(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Serialize, Deserialize)]
#[derive(ProtobufConvert, BinaryValue, ObjectHash)]
#[protobuf_convert(source = "schema::base::InstanceSpec")]
#[non_exhaustive]
pub struct InstanceSpec {
pub id: InstanceId,
pub name: String,
pub artifact: ArtifactId,
}
impl InstanceSpec {
pub fn new(
id: InstanceId,
name: impl Into<String>,
artifact: impl AsRef<str>,
) -> anyhow::Result<Self> {
let spec = Self::from_raw_parts(id, name.into(), artifact.as_ref().parse()?);
spec.validate()?;
Ok(spec)
}
pub fn from_raw_parts(id: InstanceId, name: String, artifact: ArtifactId) -> Self {
Self { id, name, artifact }
}
pub fn is_valid_name(name: impl AsRef<str>) -> anyhow::Result<()> {
let name = name.as_ref();
ensure!(!name.is_empty(), "Service name is empty");
ensure!(
is_valid_index_name_component(name),
"Service name contains illegal character, use only: a-zA-Z0-9 and _-"
);
Ok(())
}
pub fn as_descriptor(&self) -> InstanceDescriptor {
InstanceDescriptor::new(self.id, &self.name)
}
}
impl ValidateInput for InstanceSpec {
type Error = anyhow::Error;
fn validate(&self) -> Result<(), Self::Error> {
self.artifact.validate()?;
Self::is_valid_name(&self.name)
}
}
impl Display for InstanceSpec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}@{}:{}", self.artifact, self.id, self.name)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum InstanceQuery<'a> {
Id(InstanceId),
Name(&'a str),
}
impl From<InstanceId> for InstanceQuery<'_> {
fn from(value: InstanceId) -> Self {
InstanceQuery::Id(value)
}
}
impl<'a> From<&'a str> for InstanceQuery<'a> {
fn from(value: &'a str) -> Self {
InstanceQuery::Name(value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum ArtifactStatus {
Unloading,
Deploying,
Active,
}
impl Display for ArtifactStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unloading => f.write_str("unloading"),
Self::Active => f.write_str("active"),
Self::Deploying => f.write_str("deploying"),
}
}
}
impl ProtobufConvert for ArtifactStatus {
type ProtoStruct = schema::lifecycle::ArtifactState_Status;
fn to_pb(&self) -> Self::ProtoStruct {
use self::schema::lifecycle::ArtifactState_Status::*;
match self {
Self::Unloading => UNLOADING,
Self::Active => ACTIVE,
Self::Deploying => DEPLOYING,
}
}
fn from_pb(pb: Self::ProtoStruct) -> anyhow::Result<Self> {
use self::schema::lifecycle::ArtifactState_Status::*;
Ok(match pb {
UNLOADING => Self::Unloading,
ACTIVE => Self::Active,
DEPLOYING => Self::Deploying,
})
}
}
#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)]
#[derive(ProtobufConvert, BinaryValue)]
#[protobuf_convert(source = "schema::lifecycle::InstanceMigration")]
#[non_exhaustive]
pub struct InstanceMigration {
pub target: ArtifactId,
#[protobuf_convert(with = "crate::helpers::pb_version")]
pub end_version: Version,
#[protobuf_convert(with = "crate::helpers::pb_optional_hash")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub completed_hash: Option<Hash>,
}
impl InstanceMigration {
pub(super) fn new(target: ArtifactId, end_version: Version) -> Self {
Self::from_raw_parts(target, end_version, None)
}
pub(super) fn from_raw_parts(
target: ArtifactId,
end_version: Version,
completed_hash: Option<Hash>,
) -> Self {
Self {
target,
end_version,
completed_hash,
}
}
pub fn is_completed(&self) -> bool {
self.completed_hash.is_some()
}
}
#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)]
#[derive(BinaryValue)]
#[serde(tag = "type", rename_all = "snake_case")]
#[non_exhaustive]
pub enum InstanceStatus {
Active,
Stopped,
Frozen,
Migrating(Box<InstanceMigration>),
}
impl InstanceStatus {
pub(super) fn migrating(migration: InstanceMigration) -> Self {
Self::Migrating(Box::new(migration))
}
pub fn is_active(&self) -> bool {
*self == Self::Active
}
pub fn provides_read_access(&self) -> bool {
match self {
Self::Active | Self::Frozen | Self::Migrating(_) => true,
_ => false,
}
}
pub fn can_be_resumed(&self) -> bool {
match self {
Self::Stopped | Self::Frozen => true,
_ => false,
}
}
pub fn can_be_stopped(&self) -> bool {
match self {
Self::Active | Self::Frozen => true,
_ => false,
}
}
pub fn can_be_frozen(&self) -> bool {
match self {
Self::Active => true,
_ => false,
}
}
pub(super) fn ongoing_migration_target(&self) -> Option<&ArtifactId> {
match self {
Self::Migrating(migration) if !migration.is_completed() => Some(&migration.target),
_ => None,
}
}
pub(super) fn completed_migration_hash(&self) -> Option<Hash> {
match self {
Self::Migrating(migration) => migration.completed_hash,
_ => None,
}
}
}
impl Display for InstanceStatus {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Active => "active",
Self::Stopped => "stopped",
Self::Frozen => "frozen",
Self::Migrating(..) => "migrating",
})
}
}
impl InstanceStatus {
#[allow(clippy::wrong_self_convention)]
pub(super) fn to_pb(status: &Option<Self>) -> schema::lifecycle::InstanceStatus {
Self::create_pb(status.as_ref())
}
fn create_pb(status: Option<&Self>) -> schema::lifecycle::InstanceStatus {
use schema::lifecycle::InstanceStatus_Simple::*;
let mut pb = schema::lifecycle::InstanceStatus::new();
match status {
None => pb.set_simple(NONE),
Some(Self::Active) => pb.set_simple(ACTIVE),
Some(Self::Stopped) => pb.set_simple(STOPPED),
Some(Self::Frozen) => pb.set_simple(FROZEN),
Some(Self::Migrating(migration)) => pb.set_migration(migration.to_pb()),
}
pb
}
pub(super) fn from_pb(
mut pb: schema::lifecycle::InstanceStatus,
) -> anyhow::Result<Option<Self>> {
use schema::lifecycle::InstanceStatus_Simple::*;
if pb.has_simple() {
Ok(match pb.get_simple() {
NONE => None,
ACTIVE => Some(Self::Active),
STOPPED => Some(Self::Stopped),
FROZEN => Some(Self::Frozen),
})
} else if pb.has_migration() {
InstanceMigration::from_pb(pb.take_migration())
.map(|migration| Some(Self::migrating(migration)))
} else {
Err(format_err!("No variant specified for `InstanceStatus`"))
}
}
}
impl ProtobufConvert for InstanceStatus {
type ProtoStruct = schema::lifecycle::InstanceStatus;
fn to_pb(&self) -> Self::ProtoStruct {
Self::create_pb(Some(self))
}
fn from_pb(pb: Self::ProtoStruct) -> anyhow::Result<Self> {
let maybe_self = Self::from_pb(pb)?;
maybe_self
.ok_or_else(|| format_err!("Cannot create `InstanceStatus` from `None` serialization"))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Serialize, Deserialize)]
#[derive(ProtobufConvert, BinaryValue, ObjectHash)]
#[protobuf_convert(source = "schema::lifecycle::ArtifactState")]
#[non_exhaustive]
pub struct ArtifactState {
pub deploy_spec: Vec<u8>,
pub status: ArtifactStatus,
}
impl ArtifactState {
pub(super) fn new(deploy_spec: Vec<u8>, status: ArtifactStatus) -> Self {
Self {
deploy_spec,
status,
}
}
}
#[derive(Debug, Clone, PartialEq, Hash)]
#[derive(Serialize, Deserialize)]
#[derive(ProtobufConvert, BinaryValue, ObjectHash)]
#[protobuf_convert(source = "schema::lifecycle::InstanceState")]
#[non_exhaustive]
pub struct InstanceState {
pub spec: InstanceSpec,
#[protobuf_convert(with = "self::pb_optional_version")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data_version: Option<Version>,
#[protobuf_convert(with = "InstanceStatus")]
pub status: Option<InstanceStatus>,
#[protobuf_convert(with = "InstanceStatus")]
pub pending_status: Option<InstanceStatus>,
}
mod pb_optional_version {
use super::*;
#[allow(clippy::needless_pass_by_value)]
pub fn from_pb(pb: String) -> anyhow::Result<Option<Version>> {
if pb.is_empty() {
Ok(None)
} else {
pb.parse().map(Some).map_err(From::from)
}
}
pub fn to_pb(value: &Option<Version>) -> String {
if let Some(value) = value.as_ref() {
value.to_string()
} else {
String::new()
}
}
}
impl InstanceState {
pub(crate) fn from_raw_parts(
spec: InstanceSpec,
data_version: Option<Version>,
status: Option<InstanceStatus>,
pending_status: Option<InstanceStatus>,
) -> Self {
Self {
spec,
data_version,
status,
pending_status,
}
}
pub fn data_version(&self) -> &Version {
self.data_version
.as_ref()
.unwrap_or(&self.spec.artifact.version)
}
pub fn associated_artifact(&self) -> Option<&ArtifactId> {
if self.data_version.is_some() {
None
} else {
Some(&self.spec.artifact)
}
}
pub(super) fn is_readable(&self) -> bool {
let status = self
.status
.as_ref()
.or_else(|| self.pending_status.as_ref());
status.map_or(false, InstanceStatus::provides_read_access)
}
pub(super) fn commit_pending_status(&mut self) {
assert!(
self.pending_status.is_some(),
"Next instance status should not be `None`"
);
self.status = self.pending_status.take();
}
}
#[derive(Debug, Clone)]
#[derive(BinaryValue, ObjectHash)]
pub struct MigrationStatus(pub Result<Hash, String>);
impl From<Result<Hash, String>> for MigrationStatus {
fn from(res: Result<Hash, String>) -> Self {
Self(res)
}
}
impl ProtobufConvert for MigrationStatus {
type ProtoStruct = schema::lifecycle::MigrationStatus;
fn to_pb(&self) -> Self::ProtoStruct {
let mut pb = Self::ProtoStruct::new();
match self.0 {
Ok(hash) => pb.set_hash(hash.to_pb()),
Err(ref e) => pb.set_error(e.clone()),
}
pb
}
fn from_pb(mut pb: Self::ProtoStruct) -> anyhow::Result<Self> {
let inner = if pb.has_hash() {
Ok(Hash::from_pb(pb.take_hash())?)
} else if pb.has_error() {
Err(pb.take_error())
} else {
return Err(format_err!(
"Invalid Protobuf for `MigrationStatus`: neither of variants is specified"
));
};
Ok(Self(inner))
}
}
#[derive(Debug, PartialEq, Clone)]
#[derive(BinaryValue, ObjectHash)]
#[non_exhaustive]
pub enum Caller {
Transaction {
author: PublicKey,
},
Service {
instance_id: InstanceId,
},
Blockchain,
}
impl Caller {
pub fn author(&self) -> Option<PublicKey> {
if let Self::Transaction { author } = self {
Some(*author)
} else {
None
}
}
pub fn as_service(&self) -> Option<InstanceId> {
if let Self::Service { instance_id } = self {
Some(*instance_id)
} else {
None
}
}
pub fn as_supervisor(&self) -> Option<()> {
self.as_service().and_then(|instance_id| {
if instance_id == super::SUPERVISOR_INSTANCE_ID {
Some(())
} else {
None
}
})
}
pub fn address(&self) -> CallerAddress {
CallerAddress(self.object_hash())
}
}
impl ProtobufConvert for Caller {
type ProtoStruct = schema::auth::Caller;
fn to_pb(&self) -> Self::ProtoStruct {
let mut pb = Self::ProtoStruct::new();
match self {
Self::Transaction { author } => pb.set_transaction_author(author.to_pb()),
Self::Service { instance_id } => pb.set_instance_id(*instance_id),
Self::Blockchain => pb.set_blockchain(Empty::new()),
}
pb
}
fn from_pb(mut pb: Self::ProtoStruct) -> anyhow::Result<Self> {
Ok(if pb.has_transaction_author() {
let author = PublicKey::from_pb(pb.take_transaction_author())?;
Self::Transaction { author }
} else if pb.has_instance_id() {
Self::Service {
instance_id: pb.get_instance_id(),
}
} else if pb.has_blockchain() {
Self::Blockchain
} else {
bail!("No variant specified for `Caller`");
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Serialize, Deserialize)]
#[derive(BinaryValue, ObjectHash)]
#[serde(transparent)]
pub struct CallerAddress(Hash);
impl CallerAddress {
pub fn from_key(public_key: PublicKey) -> Self {
Caller::Transaction { author: public_key }.address()
}
}
impl AsRef<[u8]> for CallerAddress {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl ProtobufConvert for CallerAddress {
type ProtoStruct = exonum_crypto::proto::types::Hash;
fn to_pb(&self) -> Self::ProtoStruct {
self.0.to_pb()
}
fn from_pb(pb: Self::ProtoStruct) -> anyhow::Result<Self> {
Hash::from_pb(pb).map(Self)
}
}
impl BinaryKey for CallerAddress {
fn size(&self) -> usize {
self.0.size()
}
fn write(&self, buffer: &mut [u8]) -> usize {
self.0.write(buffer)
}
fn read(buffer: &[u8]) -> Self::Owned {
Self(Hash::read(buffer))
}
}
#[allow(unsafe_code)]
unsafe impl RawKey for CallerAddress {
fn to_raw_key(&self) -> [u8; HASH_SIZE] {
self.0.as_bytes()
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use serde_json::json;
use super::*;
use exonum_crypto as crypto;
#[test]
fn parse_artifact_id_correct() {
let artifact_id = "0:my-service:1.0.0".parse::<ArtifactId>().unwrap();
assert_eq!(artifact_id.runtime_id, 0);
assert_eq!(artifact_id.name, "my-service");
assert_eq!(artifact_id.version, Version::new(1, 0, 0));
let artifact_id = "1:com.my.java.service:3.1.5-beta.2"
.parse::<ArtifactId>()
.unwrap();
assert_eq!(artifact_id.runtime_id, 1);
assert_eq!(artifact_id.name, "com.my.java.service");
assert_eq!(artifact_id.version.major, 3);
assert_eq!(artifact_id.version.minor, 1);
assert_eq!(artifact_id.version.patch, 5);
let artifact_id = "0:my-service/additional:1.0.0"
.parse::<ArtifactId>()
.unwrap();
assert_eq!(artifact_id.runtime_id, 0);
assert_eq!(artifact_id.name, "my-service/additional");
assert_eq!(artifact_id.version, Version::new(1, 0, 0));
}
#[test]
fn artifact_id_in_json() {
let artifact_id = "0:my-service:1.0.0".parse::<ArtifactId>().unwrap();
assert_eq!(
serde_json::to_value(artifact_id).unwrap(),
json!({
"runtime_id": 0,
"name": "my-service",
"version": "1.0.0",
})
);
let artifact_id = "0:my-service:2.0.0-rc.3".parse::<ArtifactId>().unwrap();
assert_eq!(
serde_json::to_value(artifact_id).unwrap(),
json!({
"runtime_id": 0,
"name": "my-service",
"version": "2.0.0-rc.3",
})
);
}
#[test]
fn parse_artifact_id_incorrect_layout() {
let artifacts = [
("15", "Wrong `ArtifactId` format"),
("0::3.1.0", "Artifact name should not be empty"),
(":test:1.0.0", "cannot parse integer from empty string"),
("-1:test:1.0.0", "invalid digit found in string"),
("ava:test:0.0.1", "invalid digit found in string"),
(
"123:I am a service!:1.0.0",
"Artifact name contains an illegal character",
),
(
"123:\u{44e}\u{43d}\u{438}\u{43a}\u{43e}\u{434}\u{44b}:1.0.0",
"Artifact name contains an illegal character",
),
("1:test:1", "Expected dot"),
("1:test:3.141593", "Expected dot"),
("1:test:what_are_versions", "Error parsing major identifier"),
("1:test:1.x.0", "Error parsing minor identifier"),
("1:test:1.0.x", "Error parsing patch identifier"),
("1:test:1.0.0:garbage", "Extra junk after valid version"),
];
for (artifact, expected_err) in &artifacts {
let actual_err = artifact.parse::<ArtifactId>().unwrap_err().to_string();
assert!(
actual_err.contains(expected_err),
"artifact: '{}' actual_err '{}', expected_err '{}'",
artifact,
actual_err,
expected_err
);
}
}
#[test]
fn test_instance_spec_validate_correct() {
InstanceSpec::new(15, "foo-service", "0:my-service:1.0.0").unwrap();
}
#[test]
fn test_instance_spec_validate_incorrect() {
let specs = [
(
InstanceSpec::new(1, "", "0:my-service:1.0.0"),
"Service name is empty",
),
(
InstanceSpec::new(2,
"\u{440}\u{443}\u{441}\u{441}\u{43a}\u{438}\u{439}_\u{441}\u{435}\u{440}\u{432}\u{438}\u{441}",
"0:my-service:1.0.0"
),
"Service name contains illegal character",
),
(
InstanceSpec::new(3, "space service", "1:java.runtime.service:1.0.0"),
"Service name contains illegal character",
),
(
InstanceSpec::new(4, "foo_service", ""),
"Wrong `ArtifactId` format",
),
(
InstanceSpec::new(5, "dot.service", "1:java.runtime.service:1.0.0"),
"Service name contains illegal character",
),
(
InstanceSpec::new(6, "foo_service", ":test:1.0.0"),
"cannot parse integer from empty string",
),
];
for (instance_spec, expected_err) in &specs {
let actual_err = instance_spec.as_ref().unwrap_err().to_string();
assert!(
actual_err.contains(expected_err),
"actual_err '{:?}', expected_err '{}'",
instance_spec,
expected_err,
);
}
}
#[test]
fn caller_addresses() {
let blockchain_addr = Caller::Blockchain.address();
let supervisor_addr = Caller::Service { instance_id: 0 }.address();
assert_ne!(blockchain_addr.0, crypto::hash(&[]));
assert_ne!(supervisor_addr.0, crypto::hash(&[]));
assert_ne!(blockchain_addr, supervisor_addr);
}
}