huskies: merge 854
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
//! Benchmarks for BFT JSON CRDT operation throughput.
|
||||||
use bft_json_crdt::{
|
use bft_json_crdt::{
|
||||||
json_crdt::JsonValue, keypair::make_author, list_crdt::ListCrdt, op::Op, op::ROOT_ID,
|
json_crdt::JsonValue, keypair::make_author, list_crdt::ListCrdt, op::Op, op::ROOT_ID,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
//! Procedural macros for the BFT JSON CRDT library.
|
||||||
|
//!
|
||||||
|
//! Provides `#[add_crdt_fields]` to inject `path` and `id` fields into a struct,
|
||||||
|
//! and `#[derive(CrdtNode)]` to auto-implement the [`CrdtNode`] trait for structs
|
||||||
|
//! whose fields are themselves [`CrdtNode`]s.
|
||||||
|
|
||||||
use proc_macro::TokenStream as OgTokenStream;
|
use proc_macro::TokenStream as OgTokenStream;
|
||||||
use proc_macro2::{Ident, Span, TokenStream};
|
use proc_macro2::{Ident, Span, TokenStream};
|
||||||
use proc_macro_crate::{crate_name, FoundCrate};
|
use proc_macro_crate::{crate_name, FoundCrate};
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
//! Debug helpers and the [`DebugView`] trait for rendering CRDT internals.
|
||||||
|
//!
|
||||||
|
//! Most items in this module are no-ops in release builds. They are activated by
|
||||||
|
//! the `logging-base`, `logging-json`, and `logging-list` Cargo features so that
|
||||||
|
//! debug output can be toggled without changing production code.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
json_crdt::{BaseCrdt, CrdtNode, SignedOp},
|
json_crdt::{BaseCrdt, CrdtNode, SignedOp},
|
||||||
keypair::SignedDigest,
|
keypair::SignedDigest,
|
||||||
@@ -37,6 +43,7 @@ fn display_op_id<T: CrdtNode>(op: &Op<T>) -> String {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Log a type-mismatch warning when deserialising a JSON value into a CRDT node fails.
|
||||||
pub fn debug_type_mismatch(_msg: String) {
|
pub fn debug_type_mismatch(_msg: String) {
|
||||||
#[cfg(feature = "logging-base")]
|
#[cfg(feature = "logging-base")]
|
||||||
{
|
{
|
||||||
@@ -44,6 +51,7 @@ pub fn debug_type_mismatch(_msg: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Log a path-mismatch warning when an operation's path does not match the CRDT's path.
|
||||||
pub fn debug_path_mismatch(_our_path: Vec<PathSegment>, _op_path: Vec<PathSegment>) {
|
pub fn debug_path_mismatch(_our_path: Vec<PathSegment>, _op_path: Vec<PathSegment>) {
|
||||||
#[cfg(feature = "logging-base")]
|
#[cfg(feature = "logging-base")]
|
||||||
{
|
{
|
||||||
@@ -56,6 +64,7 @@ pub fn debug_path_mismatch(_our_path: Vec<PathSegment>, _op_path: Vec<PathSegmen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Log a warning when an operation is applied to a primitive (terminal) CRDT node.
|
||||||
pub fn debug_op_on_primitive(_op_path: Vec<PathSegment>) {
|
pub fn debug_op_on_primitive(_op_path: Vec<PathSegment>) {
|
||||||
#[cfg(feature = "logging-base")]
|
#[cfg(feature = "logging-base")]
|
||||||
{
|
{
|
||||||
@@ -79,16 +88,20 @@ fn display_author(author: AuthorId) -> String {
|
|||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render CRDT state as an indented human-readable string for debugging.
|
||||||
pub trait DebugView {
|
pub trait DebugView {
|
||||||
|
/// Return a multi-line debug string for this CRDT node, indented by `indent` spaces.
|
||||||
fn debug_view(&self, indent: usize) -> String;
|
fn debug_view(&self, indent: usize) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: CrdtNode + DebugView> BaseCrdt<T> {
|
impl<T: CrdtNode + DebugView> BaseCrdt<T> {
|
||||||
|
/// Print the current document state as an indented debug tree (no-op in release builds).
|
||||||
pub fn debug_view(&self) {
|
pub fn debug_view(&self) {
|
||||||
#[cfg(feature = "logging-json")]
|
#[cfg(feature = "logging-json")]
|
||||||
println!("document is now:\n{}", self.doc.debug_view(0));
|
println!("document is now:\n{}", self.doc.debug_view(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Log an attempt to apply `op` before the result is known (no-op in release builds).
|
||||||
pub fn log_try_apply(&self, _op: &SignedOp) {
|
pub fn log_try_apply(&self, _op: &SignedOp) {
|
||||||
#[cfg(feature = "logging-json")]
|
#[cfg(feature = "logging-json")]
|
||||||
println!(
|
println!(
|
||||||
@@ -99,6 +112,7 @@ impl<T: CrdtNode + DebugView> BaseCrdt<T> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Log a signature-digest verification failure for `op` (no-op in release builds).
|
||||||
pub fn debug_digest_failure(&self, _op: SignedOp) {
|
pub fn debug_digest_failure(&self, _op: SignedOp) {
|
||||||
#[cfg(feature = "logging-json")]
|
#[cfg(feature = "logging-json")]
|
||||||
println!(
|
println!(
|
||||||
@@ -108,6 +122,7 @@ impl<T: CrdtNode + DebugView> BaseCrdt<T> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Log that a causal dependency identified by `missing` has not yet been received.
|
||||||
pub fn log_missing_causal_dep(&self, _missing: &SignedDigest) {
|
pub fn log_missing_causal_dep(&self, _missing: &SignedDigest) {
|
||||||
#[cfg(feature = "logging-json")]
|
#[cfg(feature = "logging-json")]
|
||||||
println!(
|
println!(
|
||||||
@@ -117,6 +132,7 @@ impl<T: CrdtNode + DebugView> BaseCrdt<T> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Log that `op` is about to be integrated into the document (no-op in release builds).
|
||||||
pub fn log_actually_apply(&self, _op: &SignedOp) {
|
pub fn log_actually_apply(&self, _op: &SignedOp) {
|
||||||
#[cfg(feature = "logging-json")]
|
#[cfg(feature = "logging-json")]
|
||||||
{
|
{
|
||||||
@@ -133,6 +149,7 @@ impl<T> Op<T>
|
|||||||
where
|
where
|
||||||
T: CrdtNode,
|
T: CrdtNode,
|
||||||
{
|
{
|
||||||
|
/// Log an operation hash verification failure showing expected and computed IDs.
|
||||||
pub fn debug_hash_failure(&self) {
|
pub fn debug_hash_failure(&self) {
|
||||||
#[cfg(feature = "logging-base")]
|
#[cfg(feature = "logging-base")]
|
||||||
{
|
{
|
||||||
@@ -191,6 +208,7 @@ impl<T> ListCrdt<T>
|
|||||||
where
|
where
|
||||||
T: CrdtNode,
|
T: CrdtNode,
|
||||||
{
|
{
|
||||||
|
/// Print the full operation log as a tree, optionally highlighting one operation (no-op in release builds).
|
||||||
pub fn log_ops(&self, _highlight: Option<OpId>) {
|
pub fn log_ops(&self, _highlight: Option<OpId>) {
|
||||||
#[cfg(feature = "logging-list")]
|
#[cfg(feature = "logging-list")]
|
||||||
{
|
{
|
||||||
@@ -289,6 +307,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Log the insert or delete being performed for `op` (no-op in release builds).
|
||||||
pub fn log_apply(&self, _op: &Op<T>) {
|
pub fn log_apply(&self, _op: &Op<T>) {
|
||||||
#[cfg(feature = "logging-list")]
|
#[cfg(feature = "logging-list")]
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
//! Ed25519 keypair utilities and type aliases for node identity and signing.
|
||||||
|
//!
|
||||||
|
//! Provides the [`AuthorId`] and [`SignedDigest`] type aliases, a SHA-256 helper,
|
||||||
|
//! and convenience wrappers around the `fastcrypto` Ed25519 primitives used
|
||||||
|
//! throughout the CRDT codebase.
|
||||||
|
|
||||||
use fastcrypto::traits::VerifyingKey;
|
use fastcrypto::traits::VerifyingKey;
|
||||||
pub use fastcrypto::{
|
pub use fastcrypto::{
|
||||||
ed25519::{
|
ed25519::{
|
||||||
|
|||||||
@@ -1,8 +1,21 @@
|
|||||||
|
//! BFT JSON CRDT library — a Byzantine Fault-Tolerant replicated JSON document
|
||||||
|
//! built on an RGA list CRDT, an LWW register CRDT, and a signed-op substrate.
|
||||||
|
//!
|
||||||
|
//! Each document is identified by an Ed25519 keypair. Operations are signed and
|
||||||
|
//! carry causal dependencies so that every node converges to the same value
|
||||||
|
//! regardless of message delivery order.
|
||||||
|
|
||||||
|
/// Debug helpers and the [`DebugView`] trait for rendering CRDT internals.
|
||||||
pub mod debug;
|
pub mod debug;
|
||||||
|
/// JSON CRDT public interface: core traits, types, and signed-op substrate.
|
||||||
pub mod json_crdt;
|
pub mod json_crdt;
|
||||||
|
/// Ed25519 keypair utilities and primitive type aliases used throughout the crate.
|
||||||
pub mod keypair;
|
pub mod keypair;
|
||||||
|
/// RGA-style list CRDT that can store any [`CrdtNode`] as its element type.
|
||||||
pub mod list_crdt;
|
pub mod list_crdt;
|
||||||
|
/// Last-writer-wins (LWW) register CRDT for single-value fields.
|
||||||
pub mod lww_crdt;
|
pub mod lww_crdt;
|
||||||
|
/// Core operation types: [`Op`], [`PathSegment`], and hashing helpers.
|
||||||
pub mod op;
|
pub mod op;
|
||||||
|
|
||||||
extern crate self as bft_json_crdt;
|
extern crate self as bft_json_crdt;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
//! RGA-style list CRDT that stores any [`CrdtNode`] as its element type.
|
||||||
|
//!
|
||||||
|
//! Implements the Replicated Growable Array (RGA) algorithm with causal ordering.
|
||||||
|
//! Concurrent inserts at the same position are resolved by sequence number then
|
||||||
|
//! by author public key so that all replicas converge to the same sequence.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
debug::debug_path_mismatch,
|
debug::debug_path_mismatch,
|
||||||
json_crdt::{CrdtNode, JsonValue, OpState},
|
json_crdt::{CrdtNode, JsonValue, OpState},
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
//! Last-writer-wins (LWW) register CRDT.
|
||||||
|
//!
|
||||||
|
//! Implements a delete-wins LWW register for primitive values inside a nested
|
||||||
|
//! JSON CRDT. Concurrent writes are resolved by sequence number; ties are broken
|
||||||
|
//! by author public key so every node converges to the same value.
|
||||||
|
|
||||||
use crate::debug::DebugView;
|
use crate::debug::DebugView;
|
||||||
use crate::json_crdt::{CrdtNode, JsonValue, OpState};
|
use crate::json_crdt::{CrdtNode, JsonValue, OpState};
|
||||||
use crate::op::{join_path, print_path, Op, PathSegment, SequenceNumber};
|
use crate::op::{join_path, print_path, Op, PathSegment, SequenceNumber};
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
//! Core operation types for the BFT JSON CRDT.
|
||||||
|
//!
|
||||||
|
//! Defines [`Op`] (the fundamental unit of change), [`PathSegment`] (for
|
||||||
|
//! addressing nested CRDTs), and [`SequenceNumber`] / [`OpId`] type aliases.
|
||||||
|
//! Also provides hashing utilities used when computing operation identifiers.
|
||||||
|
|
||||||
use crate::debug::{debug_path_mismatch, debug_type_mismatch};
|
use crate::debug::{debug_path_mismatch, debug_type_mismatch};
|
||||||
use crate::json_crdt::{CrdtNode, CrdtNodeFromValue, IntoCrdtNode, JsonValue, SignedOp};
|
use crate::json_crdt::{CrdtNode, CrdtNodeFromValue, IntoCrdtNode, JsonValue, SignedOp};
|
||||||
use crate::keypair::{sha256, AuthorId};
|
use crate::keypair::{sha256, AuthorId};
|
||||||
@@ -113,6 +119,7 @@ where
|
|||||||
|
|
||||||
/// Conversion from Op<Value> -> Op<T> given that T is a CRDT that can be created from a JSON value
|
/// Conversion from Op<Value> -> Op<T> given that T is a CRDT that can be created from a JSON value
|
||||||
impl Op<JsonValue> {
|
impl Op<JsonValue> {
|
||||||
|
/// Convert this `Op<JsonValue>` into an `Op<T>` by deserialising the content via `T::node_from`.
|
||||||
pub fn into<T: CrdtNodeFromValue + CrdtNode>(self) -> Op<T> {
|
pub fn into<T: CrdtNodeFromValue + CrdtNode>(self) -> Op<T> {
|
||||||
let content = if let Some(inner_content) = self.content {
|
let content = if let Some(inner_content) = self.content {
|
||||||
match inner_content.into_node(self.id, self.path.clone()) {
|
match inner_content.into_node(self.id, self.path.clone()) {
|
||||||
@@ -141,10 +148,12 @@ impl<T> Op<T>
|
|||||||
where
|
where
|
||||||
T: CrdtNode,
|
T: CrdtNode,
|
||||||
{
|
{
|
||||||
|
/// Sign this operation with `keypair`, producing a [`SignedOp`] with no causal dependencies.
|
||||||
pub fn sign(self, keypair: &Ed25519KeyPair) -> SignedOp {
|
pub fn sign(self, keypair: &Ed25519KeyPair) -> SignedOp {
|
||||||
SignedOp::from_op(self, keypair, vec![])
|
SignedOp::from_op(self, keypair, vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sign this operation and attach explicit causal `dependencies`.
|
||||||
pub fn sign_with_dependencies(
|
pub fn sign_with_dependencies(
|
||||||
self,
|
self,
|
||||||
keypair: &Ed25519KeyPair,
|
keypair: &Ed25519KeyPair,
|
||||||
@@ -160,14 +169,17 @@ where
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the [`AuthorId`] (Ed25519 public key) of the node that created this operation.
|
||||||
pub fn author(&self) -> AuthorId {
|
pub fn author(&self) -> AuthorId {
|
||||||
self.author
|
self.author
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the Lamport sequence number carried by this operation.
|
||||||
pub fn sequence_num(&self) -> SequenceNumber {
|
pub fn sequence_num(&self) -> SequenceNumber {
|
||||||
self.seq
|
self.seq
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct a new operation, computing its [`OpId`] hash from the supplied fields.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
origin: OpId,
|
origin: OpId,
|
||||||
author: AuthorId,
|
author: AuthorId,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//! Integration tests verifying Byzantine fault tolerance of the CRDT.
|
||||||
use bft_json_crdt::{
|
use bft_json_crdt::{
|
||||||
json_crdt::{add_crdt_fields, BaseCrdt, CrdtNode, IntoCrdtNode, OpState},
|
json_crdt::{add_crdt_fields, BaseCrdt, CrdtNode, IntoCrdtNode, OpState},
|
||||||
keypair::make_keypair,
|
keypair::make_keypair,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//! Integration tests verifying commutativity of CRDT operations.
|
||||||
use bft_json_crdt::{
|
use bft_json_crdt::{
|
||||||
json_crdt::{CrdtNode, JsonValue},
|
json_crdt::{CrdtNode, JsonValue},
|
||||||
keypair::make_author,
|
keypair::make_author,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//! Tests for squash-merge orchestration — advanced and regression cases.
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//! Tests for squash-merge orchestration — basic cases.
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//! Tests for agent pipeline completion handling.
|
||||||
use super::super::super::AgentPool;
|
use super::super::super::AgentPool;
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::agents::{AgentEvent, AgentStatus, CompletionReport};
|
use crate::agents::{AgentEvent, AgentStatus, CompletionReport};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//! Tests for project configuration parsing and validation.
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//! Tests for CRDT snapshot compaction and cross-node coordination.
|
||||||
use super::*;
|
use super::*;
|
||||||
use bft_json_crdt::json_crdt::{BaseCrdt, JsonValue, SignedOp};
|
use bft_json_crdt::json_crdt::{BaseCrdt, JsonValue, SignedOp};
|
||||||
use bft_json_crdt::keypair::make_keypair;
|
use bft_json_crdt::keypair::make_keypair;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//! Tests for the HTTP agent endpoints.
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::agents::AgentStatus;
|
use crate::agents::AgentStatus;
|
||||||
use std::path;
|
use std::path;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//! Tests for the filesystem config watcher.
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
// ── is_config_file ────────────────────────────────────────────────────────
|
// ── is_config_file ────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//! Tests for the typed pipeline state machine.
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user