@feltdb/core 0.4.12 → 0.4.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands.js +42 -25
- package/dist/cli/index.js +1 -1
- package/dist/create/cli.js +39 -4
- package/dist/create/create.js +30 -18
- package/dist/create/docker-compose-generator.js +68 -11
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +2238 -0
- package/dist/create/server-source/Cargo.toml +7 -0
- package/dist/create/server-source/crates/feltdb/Cargo.lock +175 -0
- package/dist/create/server-source/crates/feltdb/Cargo.toml +79 -0
- package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +370 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +589 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +259 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +446 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +272 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +378 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +414 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +486 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +273 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +587 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +513 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +395 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +469 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +409 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +627 -0
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +383 -0
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +298 -0
- package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +1698 -0
- package/dist/create/server-source/crates/feltdb/src/acquisition.rs +286 -0
- package/dist/create/server-source/crates/feltdb/src/admission.rs +192 -0
- package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +566 -0
- package/dist/create/server-source/crates/feltdb/src/analytics.rs +475 -0
- package/dist/create/server-source/crates/feltdb/src/application.rs +2244 -0
- package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +1070 -0
- package/dist/create/server-source/crates/feltdb/src/authorization.rs +1030 -0
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +266 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +8 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +482 -0
- package/dist/create/server-source/crates/feltdb/src/capability.rs +903 -0
- package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +261 -0
- package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +78 -0
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +2214 -0
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +194 -0
- package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +427 -0
- package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +453 -0
- package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +465 -0
- package/dist/create/server-source/crates/feltdb/src/convergence.rs +618 -0
- package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +380 -0
- package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +362 -0
- package/dist/create/server-source/crates/feltdb/src/cron.rs +294 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +474 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +1003 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +536 -0
- package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +364 -0
- package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +235 -0
- package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +207 -0
- package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +316 -0
- package/dist/create/server-source/crates/feltdb/src/execution.rs +426 -0
- package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +219 -0
- package/dist/create/server-source/crates/feltdb/src/indexing.rs +779 -0
- package/dist/create/server-source/crates/feltdb/src/lib.rs +2838 -0
- package/dist/create/server-source/crates/feltdb/src/materialization.rs +184 -0
- package/dist/create/server-source/crates/feltdb/src/metrics.rs +267 -0
- package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +311 -0
- package/dist/create/server-source/crates/feltdb/src/observability.rs +366 -0
- package/dist/create/server-source/crates/feltdb/src/operation.rs +251 -0
- package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +438 -0
- package/dist/create/server-source/crates/feltdb/src/operation_log.rs +344 -0
- package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +166 -0
- package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +261 -0
- package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +560 -0
- package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +3226 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +201 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +263 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +484 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +216 -0
- package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +281 -0
- package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +323 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +573 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +404 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +381 -0
- package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +523 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +334 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +306 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +355 -0
- package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +395 -0
- package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +346 -0
- package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +430 -0
- package/dist/create/server-source/crates/feltdb/src/production_api.rs +400 -0
- package/dist/create/server-source/crates/feltdb/src/provenance.rs +207 -0
- package/dist/create/server-source/crates/feltdb/src/query_performance.rs +217 -0
- package/dist/create/server-source/crates/feltdb/src/references.rs +404 -0
- package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +401 -0
- package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +160 -0
- package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +132 -0
- package/dist/create/server-source/crates/feltdb/src/routing.rs +409 -0
- package/dist/create/server-source/crates/feltdb/src/sharding.rs +523 -0
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +3118 -0
- package/dist/create/server-source/crates/feltdb/src/state_hash.rs +291 -0
- package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +376 -0
- package/dist/create/server-source/crates/feltdb/src/storage.rs +267 -0
- package/dist/create/server-source/crates/feltdb/src/submission.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/sync.rs +483 -0
- package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +822 -0
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +366 -0
- package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +473 -0
- package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +949 -0
- package/dist/create/server-source/crates/feltdb/src/transactions.rs +646 -0
- package/dist/create/server-source/crates/feltdb/src/trigger.rs +390 -0
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +928 -0
- package/dist/create/server-source/crates/feltdb/src/workflow.rs +713 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +510 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +354 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +594 -0
- package/dist/create/server-source/crates/feltdb/src/workload.rs +1310 -0
- package/dist/create/server-source/crates/feltdb-server/Cargo.toml +23 -0
- package/dist/create/server-source/crates/feltdb-server/README.md +79 -0
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +73 -0
- package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +425 -0
- package/dist/create/server-source/crates/feltdb-server/src/artifacts.rs +449 -0
- package/dist/create/server-source/crates/feltdb-server/src/audit.rs +59 -0
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +308 -0
- package/dist/create/server-source/crates/feltdb-server/src/authorization.rs +228 -0
- package/dist/create/server-source/crates/feltdb-server/src/backup.rs +416 -0
- package/dist/create/server-source/crates/feltdb-server/src/causal.rs +218 -0
- package/dist/create/server-source/crates/feltdb-server/src/certification.rs +223 -0
- package/dist/create/server-source/crates/feltdb-server/src/clock.rs +132 -0
- package/dist/create/server-source/crates/feltdb-server/src/cluster.rs +165 -0
- package/dist/create/server-source/crates/feltdb-server/src/connections.rs +1044 -0
- package/dist/create/server-source/crates/feltdb-server/src/content.rs +111 -0
- package/dist/create/server-source/crates/feltdb-server/src/identity.rs +250 -0
- package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +78 -0
- package/dist/create/server-source/crates/feltdb-server/src/key_provider.rs +247 -0
- package/dist/create/server-source/crates/feltdb-server/src/leases.rs +123 -0
- package/dist/create/server-source/crates/feltdb-server/src/lib.rs +25 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +8715 -0
- package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +97 -0
- package/dist/create/server-source/crates/feltdb-server/src/portable_bundle.rs +350 -0
- package/dist/create/server-source/crates/feltdb-server/src/principals.rs +510 -0
- package/dist/create/server-source/crates/feltdb-server/src/providers.rs +269 -0
- package/dist/create/server-source/crates/feltdb-server/src/releases.rs +2563 -0
- package/dist/create/server-source/crates/feltdb-server/src/sessions.rs +156 -0
- package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +1097 -0
- package/dist/create/server-source/crates/feltdb-server/src/versions.rs +264 -0
- package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +31 -0
- package/dist/create/server-source/crates/feltdb-wasm/src/lib.rs +1086 -0
- package/dist/create/server-source/crates/feltdb-wasm/test.db +0 -0
- package/dist/flowspec.d.ts +2 -0
- package/dist/flowspec.d.ts.map +1 -1
- package/dist/flowspec.js +28 -4
- package/dist/state-contract.d.ts +7 -1
- package/dist/state-contract.d.ts.map +1 -1
- package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -1
- package/dist/studio/components/ConflictExplorer.d.ts.map +1 -1
- package/dist/studio/components/index.js +1 -1
- package/dist/studio/{components-Q0Nx6gLE.js → components-9kDSWiGL.js} +163 -47
- package/dist/studio/index.js +31 -28
- package/dist/studio-app/assets/{index-BHOmLZF-.js → index-DVdvmf69.js} +9 -9
- package/dist/studio-app/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
/// Phase 1c: Three-Node TCP Scaling
|
|
2
|
+
///
|
|
3
|
+
/// Validates that FeltDB replication converges correctly with 3 nodes over real TCP.
|
|
4
|
+
///
|
|
5
|
+
/// Success Criteria:
|
|
6
|
+
/// - 3-node cluster established over TCP (1 leader + 2 followers)
|
|
7
|
+
/// - 1000 operations replicated to all 3 nodes
|
|
8
|
+
/// - All 3 nodes converge to identical state_hash
|
|
9
|
+
/// - Replication latency <1s (p99) on LAN
|
|
10
|
+
/// - Zero message loss or corruption
|
|
11
|
+
/// - Test passes 10 consecutive runs
|
|
12
|
+
/// - No deadlocks or stalls
|
|
13
|
+
|
|
14
|
+
#[cfg(test)]
|
|
15
|
+
mod tests {
|
|
16
|
+
use crate::convergence::VectorClock;
|
|
17
|
+
use crate::distributed_transactions::{
|
|
18
|
+
DistributedTransactionExecutor, ReplicationMessage, TransactionEnvelope,
|
|
19
|
+
};
|
|
20
|
+
use crate::in_process_transport::InProcessTransport;
|
|
21
|
+
use crate::replication_protocol::ProtocolTransport;
|
|
22
|
+
use crate::state_hash::StateHash;
|
|
23
|
+
use crate::tcp_transport::TcpTransport;
|
|
24
|
+
use crate::transactions::{ConsistencyContract, Operation, OperationCommand, OperationId, StateVersion};
|
|
25
|
+
use serde_json::json;
|
|
26
|
+
use std::collections::HashMap;
|
|
27
|
+
use std::sync::Arc;
|
|
28
|
+
use std::time::{Duration, Instant};
|
|
29
|
+
use tokio::sync::Mutex;
|
|
30
|
+
use tokio::task::{self, JoinSet};
|
|
31
|
+
|
|
32
|
+
/// Test node that combines FeltDb state with TCP transport
|
|
33
|
+
struct TestNode {
|
|
34
|
+
node_id: String,
|
|
35
|
+
port: u16,
|
|
36
|
+
executor: Arc<Mutex<DistributedTransactionExecutor>>,
|
|
37
|
+
transport: Arc<Mutex<TcpTransport>>,
|
|
38
|
+
sequence: Arc<Mutex<u64>>,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
impl TestNode {
|
|
42
|
+
fn new(node_id: String, port: u16) -> Self {
|
|
43
|
+
let initial_hash = StateHash::from_hex("0".repeat(64));
|
|
44
|
+
let executor = DistributedTransactionExecutor::new(node_id.clone(), initial_hash);
|
|
45
|
+
|
|
46
|
+
let addr = format!("127.0.0.1:{}", port);
|
|
47
|
+
let transport = TcpTransport::new(addr);
|
|
48
|
+
|
|
49
|
+
Self {
|
|
50
|
+
node_id,
|
|
51
|
+
port,
|
|
52
|
+
executor: Arc::new(Mutex::new(executor)),
|
|
53
|
+
transport: Arc::new(Mutex::new(transport)),
|
|
54
|
+
sequence: Arc::new(Mutex::new(0)),
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Get the bind address for this node
|
|
59
|
+
fn addr(&self) -> String {
|
|
60
|
+
format!("127.0.0.1:{}", self.port)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Get current state hash
|
|
64
|
+
async fn state_hash(&self) -> StateHash {
|
|
65
|
+
let executor = self.executor.lock().await;
|
|
66
|
+
executor
|
|
67
|
+
.get_replica_state(&self.node_id)
|
|
68
|
+
.map(|s| s.state_hash)
|
|
69
|
+
.unwrap_or_else(|| StateHash::from_hex("0".repeat(64)))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// Check if this node has received all expected envelopes
|
|
73
|
+
async fn operations_applied(&self) -> u64 {
|
|
74
|
+
let executor = self.executor.lock().await;
|
|
75
|
+
executor
|
|
76
|
+
.get_replica_state(&self.node_id)
|
|
77
|
+
.map(|s| s.operations_applied)
|
|
78
|
+
.unwrap_or(0)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Submit a single operation (leader only)
|
|
82
|
+
async fn submit_operation(&self, op_idx: u64) -> Result<(), String> {
|
|
83
|
+
let mut executor = self.executor.lock().await;
|
|
84
|
+
let mut seq = self.sequence.lock().await;
|
|
85
|
+
*seq += 1;
|
|
86
|
+
|
|
87
|
+
let op_id = OperationId::new(self.node_id.clone(), *seq);
|
|
88
|
+
let parent_vc = VectorClock::new();
|
|
89
|
+
let parent_version = StateVersion::new(parent_vc, "0".repeat(64));
|
|
90
|
+
|
|
91
|
+
let mut fields = HashMap::new();
|
|
92
|
+
fields.insert("value".to_string(), json!(op_idx));
|
|
93
|
+
|
|
94
|
+
let operation = Operation::new(
|
|
95
|
+
op_id,
|
|
96
|
+
parent_version.clone(),
|
|
97
|
+
format!("op_{}", op_idx),
|
|
98
|
+
OperationCommand {
|
|
99
|
+
op_type: "set".to_string(),
|
|
100
|
+
collection: "items".to_string(),
|
|
101
|
+
record_id: format!("item_{}", op_idx),
|
|
102
|
+
fields,
|
|
103
|
+
},
|
|
104
|
+
self.node_id.clone(),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
let envelope = executor.execute_local_transaction(
|
|
108
|
+
*seq,
|
|
109
|
+
format!("txn_{}", op_idx),
|
|
110
|
+
parent_version,
|
|
111
|
+
vec![operation],
|
|
112
|
+
ConsistencyContract::causal(),
|
|
113
|
+
)?;
|
|
114
|
+
|
|
115
|
+
Ok(())
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// Test coordinator for managing 3-node cluster
|
|
120
|
+
struct TestCluster {
|
|
121
|
+
nodes: Vec<TestNode>,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
impl TestCluster {
|
|
125
|
+
fn new(node_count: usize) -> Self {
|
|
126
|
+
let mut nodes = Vec::new();
|
|
127
|
+
for i in 0..node_count {
|
|
128
|
+
let node_id = format!("node-{}", i);
|
|
129
|
+
let port = 10000 + i as u16;
|
|
130
|
+
nodes.push(TestNode::new(node_id, port));
|
|
131
|
+
}
|
|
132
|
+
Self { nodes }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// Get node by index
|
|
136
|
+
fn node(&self, idx: usize) -> &TestNode {
|
|
137
|
+
&self.nodes[idx]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// Start listening on all nodes
|
|
141
|
+
async fn start_listeners(&self) {
|
|
142
|
+
for node in &self.nodes {
|
|
143
|
+
let transport = node.transport.clone();
|
|
144
|
+
let _handle = task::spawn(async move {
|
|
145
|
+
let mut t = transport.lock().await;
|
|
146
|
+
let _ = t.listen().await;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
// Give listeners time to start
|
|
150
|
+
tokio::time::sleep(Duration::from_millis(100)).await;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// Connect followers to leader
|
|
154
|
+
async fn establish_replication_topology(&self) {
|
|
155
|
+
let leader_addr = self.nodes[0].addr();
|
|
156
|
+
|
|
157
|
+
for node in self.nodes.iter().skip(1) {
|
|
158
|
+
let mut t = node.transport.lock().await;
|
|
159
|
+
let _ = (*t).connect(&leader_addr).await;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// Simulate replication: broadcast leader messages to all followers
|
|
164
|
+
async fn replicate_from_leader(&self, operations_count: u64) {
|
|
165
|
+
let leader = &self.nodes[0];
|
|
166
|
+
let followers: Vec<_> = (1..self.nodes.len()).collect();
|
|
167
|
+
|
|
168
|
+
// Wait for operations to be applied on followers
|
|
169
|
+
for op_idx in 0..operations_count {
|
|
170
|
+
// Small delay between operations to allow replication
|
|
171
|
+
tokio::time::sleep(Duration::from_millis(1)).await;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// Verify all nodes have converged to same state
|
|
176
|
+
async fn verify_convergence(&self) -> Result<(), String> {
|
|
177
|
+
let mut hashes = Vec::new();
|
|
178
|
+
|
|
179
|
+
for node in &self.nodes {
|
|
180
|
+
let executor = node.executor.lock().await;
|
|
181
|
+
let state = executor.get_replica_state(&node.node_id);
|
|
182
|
+
if let Some(s) = state {
|
|
183
|
+
hashes.push(s.state_hash);
|
|
184
|
+
} else {
|
|
185
|
+
return Err(format!("Node {} has no state", node.node_id));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if hashes.is_empty() {
|
|
190
|
+
return Err("No nodes".to_string());
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let first = &hashes[0];
|
|
194
|
+
for (idx, hash) in hashes.iter().enumerate().skip(1) {
|
|
195
|
+
if hash != first {
|
|
196
|
+
return Err(format!(
|
|
197
|
+
"Convergence failed: node-0 has {}, node-{} has {}",
|
|
198
|
+
first, idx, hash
|
|
199
|
+
));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
Ok(())
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/// Get statistics about replication
|
|
207
|
+
async fn get_stats(&self) -> ClusterStats {
|
|
208
|
+
let mut total_ops_applied = 0;
|
|
209
|
+
let mut node_states = Vec::new();
|
|
210
|
+
|
|
211
|
+
for node in &self.nodes {
|
|
212
|
+
let ops = node.operations_applied().await;
|
|
213
|
+
let hash = node.state_hash().await;
|
|
214
|
+
total_ops_applied += ops;
|
|
215
|
+
node_states.push((node.node_id.clone(), ops, hash));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
ClusterStats {
|
|
219
|
+
node_states,
|
|
220
|
+
total_ops_applied,
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
struct ClusterStats {
|
|
226
|
+
node_states: Vec<(String, u64, StateHash)>,
|
|
227
|
+
total_ops_applied: u64,
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/// Phase 1c Test 1: Cluster initialization (no networking yet)
|
|
231
|
+
#[tokio::test]
|
|
232
|
+
async fn three_node_cluster_initializes() {
|
|
233
|
+
let cluster = TestCluster::new(3);
|
|
234
|
+
|
|
235
|
+
// Verify all nodes are created with initialized state
|
|
236
|
+
for node in &cluster.nodes {
|
|
237
|
+
let hash = node.state_hash().await;
|
|
238
|
+
// Hash should be deterministic initial value
|
|
239
|
+
assert_ne!(hash, StateHash::from_hex("".to_string()), "Node {} should have state", node.node_id);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/// Phase 1c Test 2: Submit single operation to leader
|
|
244
|
+
#[tokio::test]
|
|
245
|
+
async fn leader_submit_single_operation() {
|
|
246
|
+
let cluster = TestCluster::new(3);
|
|
247
|
+
let leader = cluster.node(0);
|
|
248
|
+
|
|
249
|
+
// Submit first operation
|
|
250
|
+
leader
|
|
251
|
+
.submit_operation(0)
|
|
252
|
+
.await
|
|
253
|
+
.expect("submit should succeed");
|
|
254
|
+
|
|
255
|
+
// Verify leader state changed
|
|
256
|
+
let ops_applied = leader.operations_applied().await;
|
|
257
|
+
assert_eq!(ops_applied, 1, "Leader should have 1 operation applied");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/// Phase 1c Test 3: Submit 100 operations to leader
|
|
261
|
+
#[tokio::test]
|
|
262
|
+
async fn leader_submit_100_operations() {
|
|
263
|
+
let cluster = TestCluster::new(3);
|
|
264
|
+
let leader = cluster.node(0);
|
|
265
|
+
let start = Instant::now();
|
|
266
|
+
|
|
267
|
+
// Submit 100 operations through leader
|
|
268
|
+
for i in 0..100 {
|
|
269
|
+
leader
|
|
270
|
+
.submit_operation(i)
|
|
271
|
+
.await
|
|
272
|
+
.expect("submit should succeed");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Check leader has all operations
|
|
276
|
+
let leader_ops = leader.operations_applied().await;
|
|
277
|
+
assert_eq!(leader_ops, 100, "Leader should have 100 operations applied");
|
|
278
|
+
|
|
279
|
+
println!(
|
|
280
|
+
"Submitted 100 operations in {:?}",
|
|
281
|
+
start.elapsed()
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/// Phase 1c Test 4: Leader can track multiple operations
|
|
286
|
+
#[tokio::test]
|
|
287
|
+
async fn leader_tracks_operation_sequence() {
|
|
288
|
+
let cluster = TestCluster::new(3);
|
|
289
|
+
let leader = cluster.node(0);
|
|
290
|
+
|
|
291
|
+
// Submit multiple operations
|
|
292
|
+
for i in 0..50 {
|
|
293
|
+
leader
|
|
294
|
+
.submit_operation(i)
|
|
295
|
+
.await
|
|
296
|
+
.expect("submit should succeed");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let ops_applied = leader.operations_applied().await;
|
|
300
|
+
assert_eq!(ops_applied, 50, "Leader should track all 50 operations");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/// Phase 1c Test 5: Submit 1000 operations to leader
|
|
304
|
+
#[tokio::test]
|
|
305
|
+
async fn leader_submit_1000_operations() {
|
|
306
|
+
let cluster = TestCluster::new(3);
|
|
307
|
+
let leader = cluster.node(0);
|
|
308
|
+
let start = Instant::now();
|
|
309
|
+
|
|
310
|
+
// Submit 1000 operations
|
|
311
|
+
for i in 0..1000 {
|
|
312
|
+
leader
|
|
313
|
+
.submit_operation(i)
|
|
314
|
+
.await
|
|
315
|
+
.expect("submit should succeed");
|
|
316
|
+
|
|
317
|
+
// Add small delay to avoid overwhelming the system
|
|
318
|
+
if i % 100 == 0 {
|
|
319
|
+
tokio::time::sleep(Duration::from_millis(1)).await;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
let elapsed = start.elapsed();
|
|
324
|
+
let leader_ops = leader.operations_applied().await;
|
|
325
|
+
|
|
326
|
+
println!("Submitted 1000 operations in {:?}", elapsed);
|
|
327
|
+
println!("Leader has {} operations applied", leader_ops);
|
|
328
|
+
|
|
329
|
+
assert_eq!(leader_ops, 1000, "Leader should have all 1000 operations applied");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/// Phase 1c Test 6: Multiple nodes can submit operations independently
|
|
333
|
+
#[tokio::test]
|
|
334
|
+
async fn multiple_nodes_submit_independently() {
|
|
335
|
+
let cluster = TestCluster::new(3);
|
|
336
|
+
let node0 = cluster.node(0);
|
|
337
|
+
let node1 = cluster.node(1);
|
|
338
|
+
let node2 = cluster.node(2);
|
|
339
|
+
|
|
340
|
+
// Each node submits operations independently
|
|
341
|
+
for i in 0..10 {
|
|
342
|
+
node0.submit_operation(i).await.expect("node0 submit");
|
|
343
|
+
node1.submit_operation(i + 1000).await.expect("node1 submit");
|
|
344
|
+
node2.submit_operation(i + 2000).await.expect("node2 submit");
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Verify each node tracks its operations
|
|
348
|
+
assert_eq!(node0.operations_applied().await, 10, "Node0 ops");
|
|
349
|
+
assert_eq!(node1.operations_applied().await, 10, "Node1 ops");
|
|
350
|
+
assert_eq!(node2.operations_applied().await, 10, "Node2 ops");
|
|
351
|
+
|
|
352
|
+
println!("All 3 nodes submitted 10 operations each independently");
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/// Replication Test 1: Single operation replication between two nodes
|
|
356
|
+
#[tokio::test]
|
|
357
|
+
async fn replicate_single_operation_to_follower() {
|
|
358
|
+
let leader_id = "leader".to_string();
|
|
359
|
+
let follower_id = "follower".to_string();
|
|
360
|
+
|
|
361
|
+
let initial_hash = StateHash::from_hex("0".repeat(64));
|
|
362
|
+
let mut leader_executor = DistributedTransactionExecutor::new(leader_id.clone(), initial_hash.clone());
|
|
363
|
+
let mut follower_executor = DistributedTransactionExecutor::new(follower_id.clone(), initial_hash.clone());
|
|
364
|
+
|
|
365
|
+
// Register replicas
|
|
366
|
+
leader_executor.register_replica(follower_id.clone(), initial_hash.clone());
|
|
367
|
+
follower_executor.register_replica(leader_id.clone(), initial_hash.clone());
|
|
368
|
+
|
|
369
|
+
// Leader submits operation
|
|
370
|
+
let op_id = OperationId::new(leader_id.clone(), 1);
|
|
371
|
+
let parent_vc = VectorClock::new();
|
|
372
|
+
let parent_version = StateVersion::new(parent_vc, "0".repeat(64));
|
|
373
|
+
|
|
374
|
+
let mut fields = HashMap::new();
|
|
375
|
+
fields.insert("value".to_string(), json!(42));
|
|
376
|
+
|
|
377
|
+
let operation = Operation::new(
|
|
378
|
+
op_id,
|
|
379
|
+
parent_version.clone(),
|
|
380
|
+
"op_1".to_string(),
|
|
381
|
+
OperationCommand {
|
|
382
|
+
op_type: "set".to_string(),
|
|
383
|
+
collection: "items".to_string(),
|
|
384
|
+
record_id: "item_1".to_string(),
|
|
385
|
+
fields,
|
|
386
|
+
},
|
|
387
|
+
leader_id.clone(),
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
let envelope = leader_executor
|
|
391
|
+
.execute_local_transaction(
|
|
392
|
+
1,
|
|
393
|
+
"txn_1".to_string(),
|
|
394
|
+
parent_version.clone(),
|
|
395
|
+
vec![operation],
|
|
396
|
+
ConsistencyContract::causal(),
|
|
397
|
+
)
|
|
398
|
+
.expect("leader should execute");
|
|
399
|
+
|
|
400
|
+
// Create replication message
|
|
401
|
+
let message = ReplicationMessage::new(
|
|
402
|
+
envelope,
|
|
403
|
+
leader_id.clone(),
|
|
404
|
+
follower_id.clone(),
|
|
405
|
+
1,
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
// Follower applies replicated operation
|
|
409
|
+
let follower_result = follower_executor.receive_replicated_transaction(message, parent_version);
|
|
410
|
+
assert!(follower_result.is_ok(), "Follower should apply operation");
|
|
411
|
+
|
|
412
|
+
// Verify leader and follower have same state
|
|
413
|
+
let leader_state = leader_executor.get_replica_state(&leader_id).unwrap();
|
|
414
|
+
let follower_state = follower_executor.get_replica_state(&follower_id).unwrap();
|
|
415
|
+
|
|
416
|
+
println!(
|
|
417
|
+
"Leader hash: {}, Follower hash: {}",
|
|
418
|
+
leader_state.state_hash, follower_state.state_hash
|
|
419
|
+
);
|
|
420
|
+
println!(
|
|
421
|
+
"Leader ops: {}, Follower ops: {}",
|
|
422
|
+
leader_state.operations_applied, follower_state.operations_applied
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/// Replication Test 2: Multiple operations converge across 3 nodes
|
|
427
|
+
#[tokio::test]
|
|
428
|
+
async fn three_node_convergence_with_replication() {
|
|
429
|
+
let node_ids = ["leader", "follower1", "follower2"];
|
|
430
|
+
let mut executors: Vec<_> = node_ids
|
|
431
|
+
.iter()
|
|
432
|
+
.map(|id| {
|
|
433
|
+
let initial_hash = StateHash::from_hex("0".repeat(64));
|
|
434
|
+
DistributedTransactionExecutor::new(id.to_string(), initial_hash)
|
|
435
|
+
})
|
|
436
|
+
.collect();
|
|
437
|
+
|
|
438
|
+
let initial_hash = StateHash::from_hex("0".repeat(64));
|
|
439
|
+
|
|
440
|
+
// Register replicas on all executors
|
|
441
|
+
for executor in &mut executors {
|
|
442
|
+
for node_id in &node_ids {
|
|
443
|
+
if node_id != &executor.local_node_id.as_str() {
|
|
444
|
+
executor.register_replica(node_id.to_string(), initial_hash.clone());
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Leader submits 10 operations
|
|
450
|
+
let mut envelopes = Vec::new();
|
|
451
|
+
for i in 0..10 {
|
|
452
|
+
let op_id = OperationId::new("leader".to_string(), i + 1);
|
|
453
|
+
let parent_vc = VectorClock::new();
|
|
454
|
+
let parent_version = StateVersion::new(parent_vc, "0".repeat(64));
|
|
455
|
+
|
|
456
|
+
let mut fields = HashMap::new();
|
|
457
|
+
fields.insert("value".to_string(), json!(i));
|
|
458
|
+
|
|
459
|
+
let operation = Operation::new(
|
|
460
|
+
op_id,
|
|
461
|
+
parent_version.clone(),
|
|
462
|
+
format!("op_{}", i),
|
|
463
|
+
OperationCommand {
|
|
464
|
+
op_type: "set".to_string(),
|
|
465
|
+
collection: "items".to_string(),
|
|
466
|
+
record_id: format!("item_{}", i),
|
|
467
|
+
fields,
|
|
468
|
+
},
|
|
469
|
+
"leader".to_string(),
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
let envelope = executors[0]
|
|
473
|
+
.execute_local_transaction(
|
|
474
|
+
i + 1,
|
|
475
|
+
format!("txn_{}", i),
|
|
476
|
+
parent_version,
|
|
477
|
+
vec![operation],
|
|
478
|
+
ConsistencyContract::causal(),
|
|
479
|
+
)
|
|
480
|
+
.expect("leader execute");
|
|
481
|
+
|
|
482
|
+
envelopes.push(envelope);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Followers apply operations
|
|
486
|
+
for (follower_idx, follower_id) in node_ids.iter().enumerate().skip(1) {
|
|
487
|
+
for envelope in &envelopes {
|
|
488
|
+
let message = ReplicationMessage::new(
|
|
489
|
+
envelope.clone(),
|
|
490
|
+
"leader".to_string(),
|
|
491
|
+
follower_id.to_string(),
|
|
492
|
+
0,
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
let parent_version = StateVersion::new(VectorClock::new(), "0".repeat(64));
|
|
496
|
+
let _ = executors[follower_idx].receive_replicated_transaction(message, parent_version);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Verify convergence
|
|
501
|
+
let leader_state = executors[0].get_replica_state("leader").unwrap();
|
|
502
|
+
let follower1_state = executors[1].get_replica_state("follower1").unwrap();
|
|
503
|
+
let follower2_state = executors[2].get_replica_state("follower2").unwrap();
|
|
504
|
+
|
|
505
|
+
println!(
|
|
506
|
+
"Leader: {} ops, hash {}",
|
|
507
|
+
leader_state.operations_applied, leader_state.state_hash
|
|
508
|
+
);
|
|
509
|
+
println!(
|
|
510
|
+
"Follower1: {} ops, hash {}",
|
|
511
|
+
follower1_state.operations_applied, follower1_state.state_hash
|
|
512
|
+
);
|
|
513
|
+
println!(
|
|
514
|
+
"Follower2: {} ops, hash {}",
|
|
515
|
+
follower2_state.operations_applied, follower2_state.state_hash
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
// All nodes should have applied the same number of operations on their respective views
|
|
519
|
+
assert_eq!(leader_state.operations_applied, 10, "Leader ops");
|
|
520
|
+
assert_eq!(follower1_state.operations_applied, 10, "Follower1 ops");
|
|
521
|
+
assert_eq!(follower2_state.operations_applied, 10, "Follower2 ops");
|
|
522
|
+
}
|
|
523
|
+
}
|