exonum-supervisor 1.0.0

Exonum supervisor service.
Documentation
// Copyright 2020 The Exonum Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use exonum::{
    crypto::Hash,
    runtime::{versioning::Version, ExecutionError},
};
use exonum_derive::*;
use exonum_proto::ProtobufConvert;
use serde_derive::{Deserialize, Serialize};

use super::{proto, AsyncEventState, MigrationError};

/// State of a migration.
#[derive(Debug, Clone)]
#[derive(ProtobufConvert, BinaryValue)]
#[derive(Serialize, Deserialize)]
#[protobuf_convert(source = "proto::MigrationState")]
#[non_exhaustive]
pub struct MigrationState {
    /// Migration process state.
    #[serde(rename = "state")]
    pub inner: AsyncEventState,

    /// Current artifact data version.
    #[protobuf_convert(with = "exonum::helpers::pb_version")]
    pub version: Version,

    /// Reference state hash. Equals to the first obtained local migration state hash.
    /// For a good scenario, all the hashes should be equal between each other.
    /// For the bad scenario, at least one node obtains the different hash and that's enough
    /// to consider migration failed.
    #[protobuf_convert(with = "exonum::helpers::pb_optional_hash")]
    #[serde(skip)]
    pub(crate) reference_state_hash: Option<Hash>,
}

impl MigrationState {
    /// Creates a new `MigrationState` object.
    pub fn new(inner: AsyncEventState, version: Version) -> Self {
        Self {
            inner,
            version,
            reference_state_hash: None,
        }
    }

    /// Adds a new state hash to the migration state.
    /// If this is a first hash, the `expected_hash` value will be initialized.
    /// Otherwise, provided hash will be compared to `expected_hash`.
    pub fn add_state_hash(&mut self, state_hash: Hash) -> Result<(), ExecutionError> {
        if let Some(reference_state_hash) = self.reference_state_hash {
            // We already have an expected hash, so we compare a new one against it.
            if reference_state_hash == state_hash {
                // Hashes match, that's OK.
            } else {
                // Hashes do not match, report an error.
                return Err(MigrationError::StateHashDivergence.into());
            }
        } else {
            // No state hash yet, initialize it with the provided value.
            self.reference_state_hash = Some(state_hash);
        }
        Ok(())
    }

    /// Checks whether migration is failed.
    pub fn is_failed(&self) -> bool {
        self.inner.is_failed()
    }

    /// Checks whether migration is pending.
    pub fn is_pending(&self) -> bool {
        self.inner.is_pending()
    }

    /// Updates migration state to the new state and artifact.
    pub fn update(&mut self, new_state: AsyncEventState, version: Version) {
        self.inner = new_state;
        self.version = version;
    }

    /// Marks migration as failed.
    pub fn fail(&mut self, new_state: AsyncEventState) {
        debug_assert!(new_state.is_failed());

        self.inner = new_state;
    }

    /// Returns the reference state hash.
    #[doc(hidden)] // Public for tests.
    pub fn reference_state_hash(&self) -> &Option<Hash> {
        &self.reference_state_hash
    }
}