use std::num::NonZeroU64;
use sha2::{Digest, Sha256};
use crate::{
AdministrationSequence, CommitSequence, DatabaseId, DualFrontier, LeadershipEpochV1,
ReplicationFollowerAuditTargetV1, ReplicationSourceHoldIdV1, RequestId,
};
pub const MAX_REPLICATION_VOTERS_V1: usize = 15;
const VOTE_REQUEST_MAGIC_V1: &[u8; 9] = b"RIFF_EVR1";
const VOTE_RESPONSE_MAGIC_V1: &[u8; 9] = b"RIFF_EVV1";
const CERTIFICATE_DOMAIN_V1: &[u8; 9] = b"RIFF_QFC1";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum QuorumFenceCertificateError {
InvalidEncoding,
InvalidVoterConfiguration,
CandidateNotVoter,
InvalidProposedEpoch,
IdentityMismatch,
CandidateFrontierLagging,
InsufficientQuorum,
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct ReplicationEpochVotePositionV1 {
transaction_sequence: NonZeroU64,
history_hash: [u8; 32],
frontier: DualFrontier,
}
impl ReplicationEpochVotePositionV1 {
#[must_use]
pub const fn new(
transaction_sequence: NonZeroU64,
history_hash: [u8; 32],
frontier: DualFrontier,
) -> Self {
Self {
transaction_sequence,
history_hash,
frontier,
}
}
#[must_use]
pub const fn transaction_sequence(self) -> NonZeroU64 {
self.transaction_sequence
}
#[must_use]
pub const fn history_hash(self) -> [u8; 32] {
self.history_hash
}
#[must_use]
pub const fn frontier(self) -> DualFrontier {
self.frontier
}
#[must_use]
pub fn is_at_least_as_up_to_date_as(self, voter: Self) -> bool {
if self.transaction_sequence < voter.transaction_sequence
|| self.frontier.application() < voter.frontier.application()
|| self.frontier.administration() < voter.frontier.administration()
{
return false;
}
if self.transaction_sequence == voter.transaction_sequence && self != voter {
return false;
}
true
}
fn encode_into(self, out: &mut Vec<u8>) {
out.extend_from_slice(&self.transaction_sequence.get().to_be_bytes());
out.extend_from_slice(&self.history_hash);
out.extend_from_slice(
&self
.frontier
.application()
.map_or(0_u64, CommitSequence::get)
.to_be_bytes(),
);
out.extend_from_slice(
&self
.frontier
.administration()
.map_or(0_u64, AdministrationSequence::get)
.to_be_bytes(),
);
}
fn decode_from(bytes: &[u8]) -> Result<Self, QuorumFenceCertificateError> {
if bytes.len() != 56 {
return Err(QuorumFenceCertificateError::InvalidEncoding);
}
let seq = NonZeroU64::new(u64::from_be_bytes(
bytes[0..8]
.try_into()
.map_err(|_| QuorumFenceCertificateError::InvalidEncoding)?,
))
.ok_or(QuorumFenceCertificateError::InvalidEncoding)?;
let history_hash: [u8; 32] = bytes[8..40]
.try_into()
.map_err(|_| QuorumFenceCertificateError::InvalidEncoding)?;
let raw_app = u64::from_be_bytes(
bytes[40..48]
.try_into()
.map_err(|_| QuorumFenceCertificateError::InvalidEncoding)?,
);
let raw_admin = u64::from_be_bytes(
bytes[48..56]
.try_into()
.map_err(|_| QuorumFenceCertificateError::InvalidEncoding)?,
);
let app = if raw_app == 0 {
None
} else {
Some(CommitSequence::new(raw_app).ok_or(QuorumFenceCertificateError::InvalidEncoding)?)
};
let admin = if raw_admin == 0 {
None
} else {
Some(
AdministrationSequence::new(raw_admin)
.ok_or(QuorumFenceCertificateError::InvalidEncoding)?,
)
};
Ok(Self::new(seq, history_hash, DualFrontier::new(app, admin)))
}
}
impl std::fmt::Debug for ReplicationEpochVotePositionV1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("ReplicationEpochVotePositionV1([REDACTED])")
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct ReplicationEpochVoteRequestV1 {
request_id: RequestId,
candidate_target: ReplicationFollowerAuditTargetV1,
proposed_epoch: LeadershipEpochV1,
candidate_applied: ReplicationEpochVotePositionV1,
}
impl ReplicationEpochVoteRequestV1 {
#[must_use]
pub fn new(
request_id: RequestId,
candidate_target: ReplicationFollowerAuditTargetV1,
proposed_epoch: LeadershipEpochV1,
candidate_applied: ReplicationEpochVotePositionV1,
) -> Option<Self> {
if candidate_target.leadership_epoch().checked_next() != Some(proposed_epoch) {
return None;
}
Some(Self {
request_id,
candidate_target,
proposed_epoch,
candidate_applied,
})
}
#[must_use]
pub const fn request_id(self) -> RequestId {
self.request_id
}
#[must_use]
pub const fn candidate_target(self) -> ReplicationFollowerAuditTargetV1 {
self.candidate_target
}
#[must_use]
pub const fn proposed_epoch(self) -> LeadershipEpochV1 {
self.proposed_epoch
}
#[must_use]
pub const fn candidate_applied(self) -> ReplicationEpochVotePositionV1 {
self.candidate_applied
}
#[must_use]
pub fn encode_v1(self) -> Vec<u8> {
let mut out = Vec::with_capacity(161);
out.extend_from_slice(VOTE_REQUEST_MAGIC_V1);
out.extend_from_slice(&self.request_id.into_bytes());
out.extend_from_slice(&self.candidate_target.database_id().into_bytes());
out.extend_from_slice(&self.candidate_target.history_incarnation().to_be_bytes());
out.extend_from_slice(&self.candidate_target.leadership_epoch().get().to_be_bytes());
out.extend_from_slice(self.candidate_target.hold_id().as_bytes());
out.extend_from_slice(&self.proposed_epoch.get().to_be_bytes());
self.candidate_applied.encode_into(&mut out);
let checksum: [u8; 32] = Sha256::digest(&out).into();
out.extend_from_slice(&checksum);
out
}
pub fn decode_v1(bytes: &[u8]) -> Result<Self, QuorumFenceCertificateError> {
if bytes.len() != 161 || &bytes[0..9] != VOTE_REQUEST_MAGIC_V1 {
return Err(QuorumFenceCertificateError::InvalidEncoding);
}
let expected_checksum: [u8; 32] = Sha256::digest(&bytes[0..129]).into();
if bytes[129..161] != expected_checksum {
return Err(QuorumFenceCertificateError::InvalidEncoding);
}
let request_id = RequestId::from_bytes(
bytes[9..25]
.try_into()
.map_err(|_| QuorumFenceCertificateError::InvalidEncoding)?,
)
.map_err(|_| QuorumFenceCertificateError::InvalidEncoding)?;
let database_id = DatabaseId::from_bytes(
bytes[25..41]
.try_into()
.map_err(|_| QuorumFenceCertificateError::InvalidEncoding)?,
)
.map_err(|_| QuorumFenceCertificateError::InvalidEncoding)?;
let history_incarnation = u64::from_be_bytes(
bytes[41..49]
.try_into()
.map_err(|_| QuorumFenceCertificateError::InvalidEncoding)?,
);
let source_epoch = LeadershipEpochV1::new(u64::from_be_bytes(
bytes[49..57]
.try_into()
.map_err(|_| QuorumFenceCertificateError::InvalidEncoding)?,
))
.ok_or(QuorumFenceCertificateError::InvalidEncoding)?;
let hold_id = ReplicationSourceHoldIdV1::new(
bytes[57..73]
.try_into()
.map_err(|_| QuorumFenceCertificateError::InvalidEncoding)?,
)
.ok_or(QuorumFenceCertificateError::InvalidEncoding)?;
let proposed_epoch = LeadershipEpochV1::new(u64::from_be_bytes(
bytes[73..81]
.try_into()
.map_err(|_| QuorumFenceCertificateError::InvalidEncoding)?,
))
.ok_or(QuorumFenceCertificateError::InvalidEncoding)?;
let candidate_target = ReplicationFollowerAuditTargetV1::new(
database_id,
history_incarnation,
source_epoch,
hold_id,
)
.ok_or(QuorumFenceCertificateError::InvalidEncoding)?;
let candidate_applied = ReplicationEpochVotePositionV1::decode_from(&bytes[81..137])?;
Self::new(
request_id,
candidate_target,
proposed_epoch,
candidate_applied,
)
.ok_or(QuorumFenceCertificateError::InvalidProposedEpoch)
}
}
impl std::fmt::Debug for ReplicationEpochVoteRequestV1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("ReplicationEpochVoteRequestV1([REDACTED])")
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct ReplicationEpochVoteV1 {
voter_hold_id: ReplicationSourceHoldIdV1,
database_id: DatabaseId,
history_incarnation: NonZeroU64,
promised_epoch: LeadershipEpochV1,
candidate_hold_id: ReplicationSourceHoldIdV1,
candidate_applied: ReplicationEpochVotePositionV1,
voter_applied: ReplicationEpochVotePositionV1,
granted: bool,
}
impl ReplicationEpochVoteV1 {
#[allow(clippy::too_many_arguments)]
#[must_use]
pub fn new(
voter_hold_id: ReplicationSourceHoldIdV1,
database_id: DatabaseId,
history_incarnation: u64,
promised_epoch: LeadershipEpochV1,
candidate_hold_id: ReplicationSourceHoldIdV1,
candidate_applied: ReplicationEpochVotePositionV1,
voter_applied: ReplicationEpochVotePositionV1,
granted: bool,
) -> Option<Self> {
let history_incarnation = NonZeroU64::new(history_incarnation)?;
if granted && !candidate_applied.is_at_least_as_up_to_date_as(voter_applied) {
return None;
}
Some(Self {
voter_hold_id,
database_id,
history_incarnation,
promised_epoch,
candidate_hold_id,
candidate_applied,
voter_applied,
granted,
})
}
#[must_use]
pub const fn voter_hold_id(self) -> ReplicationSourceHoldIdV1 {
self.voter_hold_id
}
#[must_use]
pub const fn database_id(self) -> DatabaseId {
self.database_id
}
#[must_use]
pub const fn history_incarnation(self) -> u64 {
self.history_incarnation.get()
}
#[must_use]
pub const fn promised_epoch(self) -> LeadershipEpochV1 {
self.promised_epoch
}
#[must_use]
pub const fn candidate_hold_id(self) -> ReplicationSourceHoldIdV1 {
self.candidate_hold_id
}
#[must_use]
pub const fn candidate_applied(self) -> ReplicationEpochVotePositionV1 {
self.candidate_applied
}
#[must_use]
pub const fn voter_applied(self) -> ReplicationEpochVotePositionV1 {
self.voter_applied
}
#[must_use]
pub const fn granted(self) -> bool {
self.granted
}
#[must_use]
pub fn encode_v1(self) -> Vec<u8> {
let mut out = Vec::with_capacity(218);
out.extend_from_slice(VOTE_RESPONSE_MAGIC_V1);
out.extend_from_slice(self.voter_hold_id.as_bytes());
out.extend_from_slice(&self.database_id.into_bytes());
out.extend_from_slice(&self.history_incarnation.get().to_be_bytes());
out.extend_from_slice(&self.promised_epoch.get().to_be_bytes());
out.extend_from_slice(self.candidate_hold_id.as_bytes());
self.candidate_applied.encode_into(&mut out);
self.voter_applied.encode_into(&mut out);
out.push(u8::from(self.granted));
let checksum: [u8; 32] = Sha256::digest(&out).into();
out.extend_from_slice(&checksum);
out
}
pub fn decode_v1(bytes: &[u8]) -> Result<Self, QuorumFenceCertificateError> {
if bytes.len() != 218 || &bytes[0..9] != VOTE_RESPONSE_MAGIC_V1 {
return Err(QuorumFenceCertificateError::InvalidEncoding);
}
let expected_checksum: [u8; 32] = Sha256::digest(&bytes[0..186]).into();
if bytes[186..218] != expected_checksum {
return Err(QuorumFenceCertificateError::InvalidEncoding);
}
let voter_hold_id = ReplicationSourceHoldIdV1::new(
bytes[9..25]
.try_into()
.map_err(|_| QuorumFenceCertificateError::InvalidEncoding)?,
)
.ok_or(QuorumFenceCertificateError::InvalidEncoding)?;
let database_id = DatabaseId::from_bytes(
bytes[25..41]
.try_into()
.map_err(|_| QuorumFenceCertificateError::InvalidEncoding)?,
)
.map_err(|_| QuorumFenceCertificateError::InvalidEncoding)?;
let history_incarnation = u64::from_be_bytes(
bytes[41..49]
.try_into()
.map_err(|_| QuorumFenceCertificateError::InvalidEncoding)?,
);
let promised_epoch = LeadershipEpochV1::new(u64::from_be_bytes(
bytes[49..57]
.try_into()
.map_err(|_| QuorumFenceCertificateError::InvalidEncoding)?,
))
.ok_or(QuorumFenceCertificateError::InvalidEncoding)?;
let candidate_hold_id = ReplicationSourceHoldIdV1::new(
bytes[57..73]
.try_into()
.map_err(|_| QuorumFenceCertificateError::InvalidEncoding)?,
)
.ok_or(QuorumFenceCertificateError::InvalidEncoding)?;
let candidate_applied = ReplicationEpochVotePositionV1::decode_from(&bytes[73..129])?;
let voter_applied = ReplicationEpochVotePositionV1::decode_from(&bytes[129..185])?;
let granted = match bytes[185] {
0 => false,
1 => true,
_ => return Err(QuorumFenceCertificateError::InvalidEncoding),
};
Self::new(
voter_hold_id,
database_id,
history_incarnation,
promised_epoch,
candidate_hold_id,
candidate_applied,
voter_applied,
granted,
)
.ok_or(QuorumFenceCertificateError::CandidateFrontierLagging)
}
}
impl std::fmt::Debug for ReplicationEpochVoteV1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("ReplicationEpochVoteV1([REDACTED])")
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct QuorumFenceCertificateV1 {
database_id: DatabaseId,
history_incarnation: NonZeroU64,
source_epoch: LeadershipEpochV1,
promised_epoch: LeadershipEpochV1,
candidate_hold_id: ReplicationSourceHoldIdV1,
candidate_applied: ReplicationEpochVotePositionV1,
votes: Vec<ReplicationEpochVoteV1>,
}
impl QuorumFenceCertificateV1 {
#[allow(clippy::too_many_arguments)]
pub fn new(
database_id: DatabaseId,
history_incarnation: u64,
source_epoch: LeadershipEpochV1,
promised_epoch: LeadershipEpochV1,
candidate_hold_id: ReplicationSourceHoldIdV1,
candidate_applied: ReplicationEpochVotePositionV1,
configured_voters: &[ReplicationSourceHoldIdV1],
mut votes: Vec<ReplicationEpochVoteV1>,
) -> Result<Self, QuorumFenceCertificateError> {
let history_incarnation = NonZeroU64::new(history_incarnation)
.ok_or(QuorumFenceCertificateError::IdentityMismatch)?;
if source_epoch.checked_next() != Some(promised_epoch) {
return Err(QuorumFenceCertificateError::InvalidProposedEpoch);
}
if configured_voters.is_empty() || configured_voters.len() > MAX_REPLICATION_VOTERS_V1 {
return Err(QuorumFenceCertificateError::InvalidVoterConfiguration);
}
let mut sorted_voters = configured_voters.to_vec();
sorted_voters.sort_unstable();
if sorted_voters.windows(2).any(|w| w[0] == w[1]) {
return Err(QuorumFenceCertificateError::InvalidVoterConfiguration);
}
if sorted_voters.binary_search(&candidate_hold_id).is_err() {
return Err(QuorumFenceCertificateError::CandidateNotVoter);
}
votes.sort_by_key(|vote| vote.voter_hold_id());
if votes
.windows(2)
.any(|w| w[0].voter_hold_id() == w[1].voter_hold_id())
{
return Err(QuorumFenceCertificateError::InvalidVoterConfiguration);
}
for vote in &votes {
if sorted_voters.binary_search(&vote.voter_hold_id()).is_err() {
return Err(QuorumFenceCertificateError::InvalidVoterConfiguration);
}
if vote.database_id() != database_id
|| vote.history_incarnation() != history_incarnation.get()
|| vote.promised_epoch() != promised_epoch
|| vote.candidate_hold_id() != candidate_hold_id
|| vote.candidate_applied() != candidate_applied
{
return Err(QuorumFenceCertificateError::IdentityMismatch);
}
if !vote.granted()
|| !candidate_applied.is_at_least_as_up_to_date_as(vote.voter_applied())
{
return Err(QuorumFenceCertificateError::CandidateFrontierLagging);
}
}
let required_majority = (configured_voters.len() / 2) + 1;
if votes.len() < required_majority {
return Err(QuorumFenceCertificateError::InsufficientQuorum);
}
Ok(Self {
database_id,
history_incarnation,
source_epoch,
promised_epoch,
candidate_hold_id,
candidate_applied,
votes,
})
}
#[must_use]
pub const fn database_id(&self) -> DatabaseId {
self.database_id
}
#[must_use]
pub const fn history_incarnation(&self) -> u64 {
self.history_incarnation.get()
}
#[must_use]
pub const fn source_epoch(&self) -> LeadershipEpochV1 {
self.source_epoch
}
#[must_use]
pub const fn promised_epoch(&self) -> LeadershipEpochV1 {
self.promised_epoch
}
#[must_use]
pub const fn candidate_hold_id(&self) -> ReplicationSourceHoldIdV1 {
self.candidate_hold_id
}
#[must_use]
pub const fn candidate_applied(&self) -> ReplicationEpochVotePositionV1 {
self.candidate_applied
}
#[must_use]
pub fn votes(&self) -> &[ReplicationEpochVoteV1] {
&self.votes
}
#[must_use]
pub fn certificate_digest(&self) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(CERTIFICATE_DOMAIN_V1);
hasher.update(self.database_id.into_bytes());
hasher.update(self.history_incarnation.get().to_be_bytes());
hasher.update(self.source_epoch.get().to_be_bytes());
hasher.update(self.promised_epoch.get().to_be_bytes());
hasher.update(self.candidate_hold_id.as_bytes());
let mut pos_bytes = Vec::with_capacity(56);
self.candidate_applied.encode_into(&mut pos_bytes);
hasher.update(&pos_bytes);
hasher.update((self.votes.len() as u32).to_be_bytes());
for vote in &self.votes {
hasher.update(vote.encode_v1());
}
hasher.finalize().into()
}
}
impl std::fmt::Debug for QuorumFenceCertificateV1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("QuorumFenceCertificateV1([REDACTED])")
}
}