Skip to content

Commit

Permalink
#235 introduce group to support 3 validators of 3x3x3 nodes in 1 arch…
Browse files Browse the repository at this point in the history
…ipel chain with 2/3 consensus finality
  • Loading branch information
branciard committed Aug 3, 2020
1 parent 4fba958 commit 7b49d46
Show file tree
Hide file tree
Showing 41 changed files with 1,284 additions and 776 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ To federate several nodes and have a shared state to elect a leader, we created

[Substrate](https://substrate.dev/) is a Parity framework that allows creating application-specific blockchains.

We created a Substrate runtime that collects all nodes metrics and sets federation leadership. This helps Archipel orchestrator to select the best leader appropriately in the federation. We call this specific blockchain the Archipel Substrate Chain or Archipel Chain.
We created a Substrate runtime that collects all nodes heartbeats and sets federation leadership. This helps Archipel orchestrator to select the best leader appropriately in the federation. We call this specific blockchain the Archipel Substrate Chain or Archipel Chain.

All nodes inside a federation, run Archipel Chain. In the current implementation, an Archipel must be composed of at least 3 nodes. That means that to operate, you have to set up at least 3 nodes. Try to set up nodes in different locations.

Expand All @@ -71,7 +71,7 @@ Archipel federation support only 3 nodes or 6 nodes.
In 3 nodes federation, all 3 nodes are operator. Passive nodes act as sentry nodes for the current validator on a 3 nodes federation.
That means that at some point a validator node can be exposed if passive (sentry). To have a more secure setup use the 6 nodes federation config.
In 6 nodes federation, sentry nodes never because passive and active nodes. And passive and active nods never become sentry nodes during the HA orchestration.
- **External Sentry Role** - external sentry have the same purpuse as sentry role for the service. Service sentry information peers are used without the use of an archipel node, metrics, orchestrator etc ...
- **External Sentry Role** - external sentry have the same purpuse as sentry role for the service. Service sentry information peers are used without the use of an archipel node, heartbeats, orchestrator etc ...

#### 3 nodes federation roles

Expand Down Expand Up @@ -101,8 +101,8 @@ You must have the 3 operator nodes running to operate and at least one sentry no
### Archipel Orchestrator Workflow

- Launch external service in passive mode or sentry node according to node role
- Send node metrics to Archipel Chain
- Retrieve other nodes metrics from Archipel Chain
- Send node heartbeats to Archipel Chain
- Retrieve other nodes heartbeats from Archipel Chain
- Retrieve current leader from Archipel Chain and determine its availability
- If the current leader is alive, do nothing and ensure that the service was launched in passive mode
- If the current leader is not alive, try to take its place by making a transaction to Archipel Chain
Expand Down
9 changes: 6 additions & 3 deletions chain/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,17 @@ cargo test -p archipel-runtime

## Archipel Runtime Functions

- `add_metrics(origin, metrics_value: u32)` - function that allows every node in Archipel federation report its metrics. The metrics report also plays the role of a heartbeat transaction that allows checking node liveness. If a node has not reported its metrics for a certain period, it can be considered as down. If the federation leader is down, the Archipel Orchestrator will react, and a new leader will be elected.
- `add_heartbeat(origin, group_id: u32)` - function that allows every node in Archipel federation report its heartbeats. The heartbeats report also plays the role of a heartbeat transaction that allows checking node liveness. If a node has not reported its heartbeats for a certain period, it can be considered as down. If the federation leader is down, the Archipel Orchestrator will react, and a new leader will be elected.

- `metrics_value` - parameter is not used by Archipel orchestrator at the moment. In the future, metrics will be used to enable a more accurate and fine-tuned leader election mechanism.
- `group_id` - use to filter different archipel group nodes for different HA service

- `set_leader(origin, old_leader: T::AccountId, group_id: u32)` - function that is used in the case when the algorithm in the Archipel Orchestrator detects that the federation leader is down. Before starting service in active mode, Archipel Orchestrator must assure that it can take leadership, and everybody is aware of its decision. In this case, `set_leader` transaction is propagated by the Archipel Orchestrator. If this transaction succeeds, the orchestrator can be sure that everybody is aware that it takes leadership in the federation. It guarantees that nobody else will launch the service in active mode. So the orchestrator can safely launch service in active mode.

- `set_leader(origin, old_leader: T::AccountId)` - function that is used in the case when the algorithm in the Archipel Orchestrator detects that the federation leader is down. Before starting service in active mode, Archipel Orchestrator must assure that it can take leadership, and everybody is aware of its decision. In this case, `set_leader` transaction is propagated by the Archipel Orchestrator. If this transaction succeeds, the orchestrator can be sure that everybody is aware that it takes leadership in the federation. It guarantees that nobody else will launch the service in active mode. So the orchestrator can safely launch service in active mode.
- `old_leader` - the current leader that is known and considered down by current orchestrator. The use of `old_leader` parameter assures that there are no two orchestrators that can take the leadership at the same time.
- this function is also be called by orchestrators when the leader's place is free. The first orchestrator that will succeed the transaction will be the leader in Archipel federation.

- `group_id` - use to filter different archipel group nodes for different HA service

## References

- [Based on Substrate Node Template](https://github.com/substrate-developer-hub/substrate-node-template/releases/tag/v2.0.0-rc5)
97 changes: 65 additions & 32 deletions chain/pallets/archipel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,14 @@ decl_storage! {
// https://substrate.dev/docs/en/knowledgebase/runtime/storage#declaring-storage-items

// Heartbeats storage
Heartbeats get( fn get_heartbeats): map hasher(blake2_128_concat) T::AccountId => T::BlockNumber;
// Metrics storage
Metrics get(fn get_metrics): map hasher(blake2_128_concat) T::AccountId => u32;
// Current leader storage
Leader get(fn get_leader): Option<T::AccountId> = None;
Heartbeats get( fn get_heartbeat): map hasher(blake2_128_concat) T::AccountId => T::BlockNumber;
// Status storage
NodesStatus get( fn get_node_status): map hasher(blake2_128_concat) T::AccountId => u32 = 0;
// Groups storage
Groups get(fn get_group): map hasher(blake2_128_concat) T::AccountId => u32;
// Current leaders storage
Leaders get(fn get_leader): map hasher(blake2_128_concat) u32 => T::AccountId;
LeadedGroup get(fn get_leaded_group): map hasher(blake2_128_concat) u32 => bool = false;
// Accounts storage
Accounts get(fn get_account): map hasher(blake2_128_concat) u32 => T::AccountId;
AccountsCount get(fn get_accounts_count): u32 = 0;
Expand All @@ -74,11 +77,14 @@ decl_event!(
AccountId = <T as frame_system::Trait>::AccountId,
BlockNumber = <T as system::Trait>::BlockNumber,
{
// Metics updated event
MetricsUpdated(AccountId, u32, BlockNumber),

// Leader changed event
NewLeader(AccountId),
// Heartbeat event
NewHeartbeat(AccountId, u32, u32, BlockNumber),

// New leader event
NewLeader(AccountId, u32),

// Give up Leader event
GiveUpLeader(AccountId, u32),
}
);

Expand All @@ -91,46 +97,73 @@ decl_module! {
// Events must be initialized if they are used by the pallet.
fn deposit_event() = default;

#[weight = 10_000 + T::DbWeight::get().reads_writes(1,1)]
pub fn set_leader(origin, old_leader: T::AccountId) -> dispatch::DispatchResult {
let sender = ensure_signed(origin)?;
#[weight = 10_000 + T::DbWeight::get().reads_writes(1,2)]
pub fn set_leader(origin, old_leader: T::AccountId, group_id: u32) -> dispatch::DispatchResult {
let sender: T::AccountId = ensure_signed(origin)?;

let leader = Self::get_leader();
// If leader is already set by someone in this group
if <Leaders<T>>::contains_key(&group_id) {
let leader = Self::get_leader(group_id);
// Checking if leader can be set
ensure!(sender != old_leader, "You are already leader.");
ensure!(old_leader == leader, "Incorrect old leader report.");
}

// If leader is already set by someone
if let Some(current_leader) = leader {
// Checking if leader can be set
ensure!(sender != old_leader, "You are already leader.");
ensure!(old_leader == current_leader, "Incorrect old leader report.");
}
// Updating leader for group id
<Leaders<T>>::insert(group_id, &sender);

// Updating leader
<Leader<T>>::put(&sender);
<LeadedGroup>::insert(group_id, true);

// Triggering leader update event
Self::deposit_event(RawEvent::NewLeader(sender));
Self::deposit_event(RawEvent::NewLeader(sender, group_id));

Ok(())
}

#[weight = 10_000 + T::DbWeight::get().reads_writes(1,5)]
// Add metrics
pub fn add_metrics(origin, metrics_value: u32) -> dispatch::DispatchResult {
#[weight = 10_000 + T::DbWeight::get().reads_writes(2,2)]
pub fn give_up_leadership(origin, group_id: u32) -> dispatch::DispatchResult {

let sender: T::AccountId = ensure_signed(origin)?;

let leaded_group = Self::get_leaded_group(group_id);

ensure!(leaded_group == true, "No Leader in this group.");

let leader = Self::get_leader(group_id);

ensure!(leader == sender, "You are not the current leader.");

<LeadedGroup>::insert(group_id, false);

<Leaders<T>>::remove(group_id);

Self::deposit_event(RawEvent::GiveUpLeader(sender, group_id));

Ok(())
}


#[weight = 10_000 + T::DbWeight::get().reads_writes(2,6)]
// Add hearthbeats
pub fn add_heartbeat(origin, group_id: u32, node_status: u32) -> dispatch::DispatchResult {
let sender = ensure_signed(origin)?;

let now = <system::Module<T>>::block_number();

// Adding account in map
Self::add_account(&sender)?;

// Adding metrics into metrics map
<Metrics<T>>::insert(&sender, metrics_value);
// Adding sender into groups map
<Groups<T>>::insert(&sender, group_id);

// Adding Moment into Heartbeats map
<Heartbeats<T>>::insert(&sender, now);
// Adding Now into Heartbeats map
<Heartbeats<T>>::insert(&sender, now);

// Adding node status into NodesStatus map
<NodesStatus<T>>::insert(&sender, node_status);

// Triggering metrics update event
Self::deposit_event(RawEvent::MetricsUpdated(sender, metrics_value, now));
// Triggering heartbeats update event
Self::deposit_event(RawEvent::NewHeartbeat(sender, group_id, node_status, now));

Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion chain/pallets/archipel/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ impl ExtBuilder {
.build_storage::<TestArchipel>()
.unwrap();
let mut ext = TestExternalities::from(storage);
ext.execute_with(|| System::set_block_number(1));
ext.execute_with(|| System::set_block_number(42));
ext
}
}
Loading

0 comments on commit 7b49d46

Please sign in to comment.