aboutsummaryrefslogtreecommitdiffstats
path: root/src/remake/vote.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/remake/vote.rs')
-rw-r--r--src/remake/vote.rs54
1 files changed, 47 insertions, 7 deletions
diff --git a/src/remake/vote.rs b/src/remake/vote.rs
index 12c1333..c231afa 100644
--- a/src/remake/vote.rs
+++ b/src/remake/vote.rs
@@ -38,7 +38,7 @@ use super::Host;
use super::content;
use super::records::{self, Decision, EpochRecord, Phase, Record, Register, Vote};
use crate::clock::UnixMillis;
-use crate::document::store::StoreError;
+use crate::document::store::{self, StoreError};
/// A majority of `electorate` members.
pub(crate) fn quorum(electorate: usize) -> usize {
@@ -281,7 +281,7 @@ pub(super) async fn cast<H: Host>(host: &H) {
// Usually there is nothing to vote on: look first, without writing.
let (voter, wanted) = (me.clone(), epoch.clone());
let now = host.now_millis();
- let (due, missed) = host
+ let (due, missed, swarm) = host
.read_doc(move |doc| {
let all = records::load_all(doc);
let register = records::load_register(doc);
@@ -294,9 +294,17 @@ pub(super) async fn cast<H: Host>(host: &H) {
return None;
}
let content = record.proposal.content.clone();
- Some((id, content))
+ Some((id, content, record.proposal.kind.clone()))
});
- (due, missed)
+ let swarm = store::read_path(
+ doc,
+ &crate::document::meta::path(&[crate::document::meta::SWARM]),
+ )
+ .ok()
+ .flatten()
+ .and_then(|swarm| swarm.as_str().map(str::to_owned))
+ .unwrap_or_default();
+ (due, missed, swarm)
})
.await;
for id in missed {
@@ -311,13 +319,24 @@ pub(super) async fn cast<H: Host>(host: &H) {
}
}
}
- let Some((chosen, content)) = due else {
+ let Some((chosen, content, kind)) = due else {
return;
};
// Only with the content in hand: a committed remake must have it on a majority.
- if let Some(reference) = &content
- && content::available_or_fetch(host, reference).is_none()
+ let bytes = match &content {
+ Some(reference) => match content::available_or_fetch(host, reference) {
+ Some(bytes) => Some(bytes),
+ None => return,
+ },
+ None => None,
+ };
+ // A strict remake: only with its manifest signed by a key this node was given.
+ if kind == "strict"
+ && let Err(why) = strict_taken(host, bytes.as_deref().map(Vec::as_slice), &swarm)
{
+ if host.remake().refuses_strict(&chosen) {
+ warn!(remake = %chosen, why = %why, "Not voting for a strict remake");
+ }
return;
}
// Spread over half a keepalive interval, so later voters see the earlier votes
@@ -355,6 +374,27 @@ pub(super) async fn cast<H: Host>(host: &H) {
}
}
+/// Whether this node takes a strict remake's manifest (`content`, for `swarm`): signed
+/// by one of its `--strict-keys`; without any, if it follows strict remakes all the same
+/// (`--follow-strict-remakes`). Why not, if not.
+pub(super) fn strict_taken<H: Host>(
+ host: &H,
+ content: Option<&[u8]>,
+ swarm: &str,
+) -> Result<(), String> {
+ let text = content
+ .and_then(|bytes| std::str::from_utf8(bytes).ok())
+ .ok_or("no manifest")?;
+ if host.strict_keys().is_empty() {
+ return if host.follows_strict_remakes() {
+ crate::access::strict::read(text).map(|_| ())
+ } else {
+ Err("this node has no --strict-keys (nor --follow-strict-remakes)".to_owned())
+ };
+ }
+ crate::access::strict::verify(text, host.strict_keys(), swarm).map(|_| ())
+}
+
/// A random duration below `limit`.
fn random_part(limit: Duration) -> Duration {
let mut bytes = [0u8; 8];