Phase B: identifier+key on the wire, JCS envelope signing (FRX/0.5), registry binding
This commit is contained in:
@@ -17,7 +17,8 @@
|
|||||||
- I2 is not blanket anti-centralization: shared coordination (identity, admission, contract) is centralized in the MA because common state is cheaper held once; decisions that consume local information (matching, relevance, sharing, retention) stay local. Off-wire conduct (link handling, retention, gating) is contract, not conformance.
|
- I2 is not blanket anti-centralization: shared coordination (identity, admission, contract) is centralized in the MA because common state is cheaper held once; decisions that consume local information (matching, relevance, sharing, retention) stay local. Off-wire conduct (link handling, retention, gating) is contract, not conformance.
|
||||||
|
|
||||||
## Implementation notes
|
## Implementation notes
|
||||||
- Draft 0.5 specifies JCS (RFC 8785) envelope signing over `{type, from, key, ts, nonce, body}` with `from` = MA-hosted identifier and `key` = pubkey. The code still signs the provisional `FRX/0.4` scheme (`src/crypto.rs`) until Phase B lands — never present current code as interoperable.
|
- Wire format matches Draft 0.5: envelope `{type, from, key, ts, nonce, body, sig}` with `from` = identifier, `key` = pubkey; signatures cover the JCS canonical form of the unsigned envelope under prefix `FRX/0.5` (`src/crypto.rs`). Our `canonical_json` is JCS-compatible only for the restricted schema (ASCII keys, integers, no floats) — golden vectors in `tests/conformance.rs` pin the bytes and signature; revisit before claiming interop with non-Rust stacks.
|
||||||
|
- Relay addresses transport mailboxes by `key` (unicast `to` = recipient pubkey; queues keyed by pubkey); the identifier is protocol identity only. Registry binding checks `map[key].id == from`.
|
||||||
- Built: envelope/query/response, Tantivy index, aggregates, member directory. Not built: dashboard UI, directory watching, TLS, lineage/delegation. Responses travel relay-mediated unicast; transport is HTTP long-poll, not SSE.
|
- Built: envelope/query/response, Tantivy index, aggregates, member directory. Not built: dashboard UI, directory watching, TLS, lineage/delegation. Responses travel relay-mediated unicast; transport is HTTP long-poll, not SSE.
|
||||||
- Economics is out of protocol scope (I4: aggregates advise, contracts govern): no receipt, citation, pricing, or settlement fields or message types exist or may be added.
|
- Economics is out of protocol scope (I4: aggregates advise, contracts govern): no receipt, citation, pricing, or settlement fields or message types exist or may be added.
|
||||||
- Relay verifies signatures and ±300s timestamp skew, carries only `query` broadcasts, holds no history (queue drained on poll), and returns 429 + Retry-After under backpressure — never silent drops. Mailbox polls require proof of key possession: `GET /v1/challenge` then a signed single-use nonce, so knowing a pubkey is not enough to drain its queue.
|
- Relay verifies signatures and ±300s timestamp skew, carries only `query` broadcasts, holds no history (queue drained on poll), and returns 429 + Retry-After under backpressure — never silent drops. Mailbox polls require proof of key possession: `GET /v1/challenge` then a signed single-use nonce, so knowing a pubkey is not enough to drain its queue.
|
||||||
@@ -45,4 +46,4 @@
|
|||||||
- frxd modes (one binary, config toggles, no code required of publishers): querier (broadcast/local-first search), responder (match incoming queries against shared collections, sign), local index (watch dirs, extract text, explicit shared marking per I9). Use RFC terms querier/responder, not "subscriber/publisher".
|
- frxd modes (one binary, config toggles, no code required of publishers): querier (broadcast/local-first search), responder (match incoming queries against shared collections, sign), local index (watch dirs, extract text, explicit shared marking per I9). Use RFC terms querier/responder, not "subscriber/publisher".
|
||||||
- Roles are not exclusive: a single node may issue queries and answer them concurrently (I5, §3 "any member"). Implement querier/responder as independent enable flags — never an exclusive mode enum or fixed deployment role.
|
- Roles are not exclusive: a single node may issue queries and answer them concurrently (I5, §3 "any member"). Implement querier/responder as independent enable flags — never an exclusive mode enum or fixed deployment role.
|
||||||
- Matching accuracy is a project-health concern: start lexical (Tantivy), plan a hybrid cheap lexical gate + optional local embedding rerank (two-stage ingestion, Appendix A); embedding model stays local and replaceable (I2/I5).
|
- Matching accuracy is a project-health concern: start lexical (Tantivy), plan a hybrid cheap lexical gate + optional local embedding rerank (two-stage ingestion, Appendix A); embedding model stays local and replaceable (I2/I5).
|
||||||
- Identity/registry (RFC Draft 0.5 §4/§6): MA-hosted FQDN identifiers first (`<label>.frx.<ma-domain>`, no DNS needed by users), signed versioned registry snapshot with the MA key pinned; envelope `from` = identifier, `key` = pubkey; registry outage fails static. Member-hosted identities, MA anchor rollover, and unicast confidentiality are §10 open. Implementation phases: A = signed registry snapshot with the current wire format (built and tested, no wire change); B = identifier + `key` + JCS on the wire. Prioritize frictionless onboarding (users may be department-level and cannot create DNS).
|
- Identity/registry (RFC Draft 0.5 §4/§6): MA-hosted FQDN identifiers first (`<label>.frx.<ma-domain>`, no DNS needed by users), signed versioned registry snapshot with the MA key pinned; envelope `from` = identifier, `key` = pubkey; registry outage fails static. Member-hosted identities, MA anchor rollover, and unicast confidentiality are §10 open. Implementation phases: A (signed registry snapshot) and B (identifier + `key` + JCS on the wire) are built and tested. Prioritize frictionless onboarding (users may be department-level and cannot create DNS).
|
||||||
|
|||||||
+22
-13
@@ -121,20 +121,19 @@ pub fn verify_signature(public_hex: &str, message: &[u8], sig_hex: &str) -> Resu
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn signing_bytes(envelope: &Envelope) -> Vec<u8> {
|
pub fn signing_bytes(envelope: &Envelope) -> Vec<u8> {
|
||||||
format!(
|
let unsigned = serde_json::json!({
|
||||||
"{}\n{}\n{}\n{}\n{}\n{}",
|
"type": envelope.msg_type,
|
||||||
PROTOCOL,
|
"from": envelope.from,
|
||||||
envelope.msg_type,
|
"key": envelope.key,
|
||||||
envelope.from,
|
"ts": envelope.ts,
|
||||||
envelope.ts,
|
"nonce": envelope.nonce,
|
||||||
envelope.nonce,
|
"body": envelope.body,
|
||||||
canonical_json(&envelope.body)
|
});
|
||||||
)
|
format!("{}\n{}", PROTOCOL, canonical_json(&unsigned)).into_bytes()
|
||||||
.into_bytes()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_envelope(envelope: &Envelope) -> Result<()> {
|
pub fn verify_envelope(envelope: &Envelope) -> Result<()> {
|
||||||
verify_signature(&envelope.from, &signing_bytes(envelope), &envelope.sig)
|
verify_signature(&envelope.key, &signing_bytes(envelope), &envelope.sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn now_ts() -> u64 {
|
pub fn now_ts() -> u64 {
|
||||||
@@ -160,14 +159,24 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn envelope_sign_verify_roundtrip() {
|
fn envelope_sign_verify_roundtrip() {
|
||||||
let key = Keypair::generate();
|
let key = Keypair::generate();
|
||||||
let env = Envelope::new(&key, crate::message::TYPE_QUERY, json!({"text": "hello"}));
|
let env = Envelope::new(
|
||||||
|
&key,
|
||||||
|
"alice.example",
|
||||||
|
crate::message::TYPE_QUERY,
|
||||||
|
json!({"text": "hello"}),
|
||||||
|
);
|
||||||
verify_envelope(&env).unwrap();
|
verify_envelope(&env).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tampered_body_fails_verification() {
|
fn tampered_body_fails_verification() {
|
||||||
let key = Keypair::generate();
|
let key = Keypair::generate();
|
||||||
let mut env = Envelope::new(&key, crate::message::TYPE_QUERY, json!({"text": "hello"}));
|
let mut env = Envelope::new(
|
||||||
|
&key,
|
||||||
|
"alice.example",
|
||||||
|
crate::message::TYPE_QUERY,
|
||||||
|
json!({"text": "hello"}),
|
||||||
|
);
|
||||||
env.body = json!({"text": "hello", "extra": true});
|
env.body = json!({"text": "hello", "extra": true});
|
||||||
assert!(verify_envelope(&env).is_err());
|
assert!(verify_envelope(&env).is_err());
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -10,4 +10,4 @@ pub mod relay;
|
|||||||
pub mod render;
|
pub mod render;
|
||||||
|
|
||||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
pub const PROTOCOL: &str = "FRX/0.4";
|
pub const PROTOCOL: &str = "FRX/0.5";
|
||||||
|
|||||||
+5
-3
@@ -22,6 +22,7 @@ pub struct Envelope {
|
|||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub msg_type: String,
|
pub msg_type: String,
|
||||||
pub from: String,
|
pub from: String,
|
||||||
|
pub key: String,
|
||||||
pub ts: u64,
|
pub ts: u64,
|
||||||
pub nonce: String,
|
pub nonce: String,
|
||||||
pub body: Value,
|
pub body: Value,
|
||||||
@@ -29,10 +30,11 @@ pub struct Envelope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Envelope {
|
impl Envelope {
|
||||||
pub fn new(key: &Keypair, msg_type: &str, body: Value) -> Self {
|
pub fn new(key: &Keypair, identifier: &str, msg_type: &str, body: Value) -> Self {
|
||||||
let mut envelope = Self {
|
let mut envelope = Self {
|
||||||
msg_type: msg_type.to_string(),
|
msg_type: msg_type.to_string(),
|
||||||
from: key.public_hex(),
|
from: identifier.to_string(),
|
||||||
|
key: key.public_hex(),
|
||||||
ts: now_ts(),
|
ts: now_ts(),
|
||||||
nonce: random_nonce(),
|
nonce: random_nonce(),
|
||||||
body,
|
body,
|
||||||
@@ -183,7 +185,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn envelope_rejects_wrong_type() {
|
fn envelope_rejects_wrong_type() {
|
||||||
let key = Keypair::generate();
|
let key = Keypair::generate();
|
||||||
let envelope = Envelope::new(&key, TYPE_RESPONSE, json!({}));
|
let envelope = Envelope::new(&key, "bob.example", TYPE_RESPONSE, json!({}));
|
||||||
assert!(require_type(&envelope, TYPE_QUERY).is_err());
|
assert!(require_type(&envelope, TYPE_QUERY).is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-17
@@ -284,6 +284,14 @@ impl Node {
|
|||||||
self.index.doc_count()
|
self.index.doc_count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn identifier(&self) -> String {
|
||||||
|
self.config
|
||||||
|
.node
|
||||||
|
.id
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| self.key.public_hex())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sent(&self) -> u64 {
|
pub fn sent(&self) -> u64 {
|
||||||
self.sent.load(Ordering::SeqCst)
|
self.sent.load(Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
@@ -359,7 +367,12 @@ impl Node {
|
|||||||
let mut aggregates = self.aggregates.lock().expect("aggregates lock");
|
let mut aggregates = self.aggregates.lock().expect("aggregates lock");
|
||||||
*aggregates.sent.entry(current_period()).or_default() += 1;
|
*aggregates.sent.entry(current_period()).or_default() += 1;
|
||||||
}
|
}
|
||||||
let envelope = Envelope::new(&self.key, TYPE_QUERY, serde_json::to_value(&query)?);
|
let envelope = Envelope::new(
|
||||||
|
&self.key,
|
||||||
|
&self.identifier(),
|
||||||
|
TYPE_QUERY,
|
||||||
|
serde_json::to_value(&query)?,
|
||||||
|
);
|
||||||
self.publish(&envelope).await;
|
self.publish(&envelope).await;
|
||||||
let timeout = Duration::from_millis(timeout_ms.unwrap_or(self.config.query.timeout_ms));
|
let timeout = Duration::from_millis(timeout_ms.unwrap_or(self.config.query.timeout_ms));
|
||||||
let deadline = Instant::now() + timeout;
|
let deadline = Instant::now() + timeout;
|
||||||
@@ -450,19 +463,19 @@ impl Node {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|signed| authorized_keys(signed, now_ts()));
|
.map(|signed| authorized_keys(signed, now_ts()));
|
||||||
match authorized {
|
match authorized {
|
||||||
Some(map) => {
|
Some(map) => match map.get(&envelope.key) {
|
||||||
let entry = map.get(&envelope.from);
|
Some((id, class)) if id == &envelope.from => (Some(class.clone()), true),
|
||||||
(entry.map(|(_, class)| class.clone()), entry.is_some())
|
_ => (None, false),
|
||||||
}
|
},
|
||||||
None => (None, false),
|
None => (None, false),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let members = self.members.read().expect("members lock");
|
let members = self.members.read().expect("members lock");
|
||||||
let class = self.member_class(&members, &envelope.from);
|
let class = self.member_class(&members, &envelope.key);
|
||||||
let listed = if members.is_empty() {
|
let listed = if members.is_empty() {
|
||||||
self.config.node.dev_bootstrap
|
self.config.node.dev_bootstrap
|
||||||
} else {
|
} else {
|
||||||
class.is_some()
|
class.is_some() && envelope.from == envelope.key
|
||||||
};
|
};
|
||||||
(class, listed)
|
(class, listed)
|
||||||
};
|
};
|
||||||
@@ -471,7 +484,7 @@ impl Node {
|
|||||||
}
|
}
|
||||||
match envelope.msg_type.as_str() {
|
match envelope.msg_type.as_str() {
|
||||||
TYPE_QUERY => {
|
TYPE_QUERY => {
|
||||||
if envelope.from == self.key.public_hex() || !self.config.node.responder {
|
if envelope.from == self.identifier() || !self.config.node.responder {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let Ok(query) = envelope.parse_body::<QueryBody>() else {
|
let Ok(query) = envelope.parse_body::<QueryBody>() else {
|
||||||
@@ -491,7 +504,7 @@ impl Node {
|
|||||||
}
|
}
|
||||||
let node = self.clone();
|
let node = self.clone();
|
||||||
let relay = relay.to_string();
|
let relay = relay.to_string();
|
||||||
let querier = envelope.from.clone();
|
let querier = envelope.key.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(error) = node.respond(&query, &querier, &relay).await {
|
if let Err(error) = node.respond(&query, &querier, &relay).await {
|
||||||
eprintln!("responder failed: {error}");
|
eprintln!("responder failed: {error}");
|
||||||
@@ -535,10 +548,12 @@ impl Node {
|
|||||||
}
|
}
|
||||||
let node = self.clone();
|
let node = self.clone();
|
||||||
let period = period.to_string();
|
let period = period.to_string();
|
||||||
let requester = envelope.from.clone();
|
let requester_id = envelope.from.clone();
|
||||||
|
let requester_key = envelope.key.clone();
|
||||||
let relay = relay.to_string();
|
let relay = relay.to_string();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
node.serve_aggregate(&period, &requester, &relay).await;
|
node.serve_aggregate(&period, &requester_id, &requester_key, &relay)
|
||||||
|
.await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -552,7 +567,12 @@ impl Node {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let body = build_response(&query.qid, response_items(&hits), total, max);
|
let body = build_response(&query.qid, response_items(&hits), total, max);
|
||||||
let envelope = Envelope::new(&self.key, TYPE_RESPONSE, serde_json::to_value(&body)?);
|
let envelope = Envelope::new(
|
||||||
|
&self.key,
|
||||||
|
&self.identifier(),
|
||||||
|
TYPE_RESPONSE,
|
||||||
|
serde_json::to_value(&body)?,
|
||||||
|
);
|
||||||
self.send_unicast(querier, envelope, relay).await
|
self.send_unicast(querier, envelope, relay).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,7 +592,12 @@ impl Node {
|
|||||||
.first()
|
.first()
|
||||||
.ok_or_else(|| anyhow!("no relays configured"))?
|
.ok_or_else(|| anyhow!("no relays configured"))?
|
||||||
.clone();
|
.clone();
|
||||||
let envelope = Envelope::new(&self.key, TYPE_AGGREGATE, json!({ "period": period }));
|
let envelope = Envelope::new(
|
||||||
|
&self.key,
|
||||||
|
&self.identifier(),
|
||||||
|
TYPE_AGGREGATE,
|
||||||
|
json!({ "period": period }),
|
||||||
|
);
|
||||||
self.send_unicast(to, envelope, &relay).await?;
|
self.send_unicast(to, envelope, &relay).await?;
|
||||||
let key = (to.to_string(), period.to_string());
|
let key = (to.to_string(), period.to_string());
|
||||||
let deadline = Instant::now()
|
let deadline = Instant::now()
|
||||||
@@ -594,13 +619,19 @@ impl Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn serve_aggregate(&self, period: &str, requester: &str, relay: &str) {
|
async fn serve_aggregate(
|
||||||
let body = self.aggregate_for(period, Some(requester));
|
&self,
|
||||||
|
period: &str,
|
||||||
|
requester_id: &str,
|
||||||
|
requester_key: &str,
|
||||||
|
relay: &str,
|
||||||
|
) {
|
||||||
|
let body = self.aggregate_for(period, Some(requester_id));
|
||||||
let Ok(value) = serde_json::to_value(&body) else {
|
let Ok(value) = serde_json::to_value(&body) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let envelope = Envelope::new(&self.key, TYPE_AGGREGATE, value);
|
let envelope = Envelope::new(&self.key, &self.identifier(), TYPE_AGGREGATE, value);
|
||||||
if let Err(error) = self.send_unicast(requester, envelope, relay).await {
|
if let Err(error) = self.send_unicast(requester_key, envelope, relay).await {
|
||||||
eprintln!("aggregate reply failed: {error}");
|
eprintln!("aggregate reply failed: {error}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -72,7 +72,7 @@ impl Relay {
|
|||||||
Ok(1)
|
Ok(1)
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let from = envelope.from.clone();
|
let from = envelope.key.clone();
|
||||||
inner.members.entry(from).or_default();
|
inner.members.entry(from).or_default();
|
||||||
let mut delivered = 0;
|
let mut delivered = 0;
|
||||||
for queue in inner.members.values_mut() {
|
for queue in inner.members.values_mut() {
|
||||||
|
|||||||
+11
-2
@@ -59,9 +59,18 @@ pub fn free_port() -> u16 {
|
|||||||
listener.local_addr().unwrap().port()
|
listener.local_addr().unwrap().port()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query_envelope(key: &Keypair, text: &str, max_results: usize) -> Envelope {
|
pub fn query_envelope(key: &Keypair, identifier: &str, text: &str, max_results: usize) -> Envelope {
|
||||||
let body = QueryBody::new(text, max_results);
|
let body = QueryBody::new(text, max_results);
|
||||||
Envelope::new(key, TYPE_QUERY, serde_json::to_value(&body).unwrap())
|
Envelope::new(
|
||||||
|
key,
|
||||||
|
identifier,
|
||||||
|
TYPE_QUERY,
|
||||||
|
serde_json::to_value(&body).unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn test_envelope(key: &Keypair, msg_type: &str, body: Value) -> Envelope {
|
||||||
|
Envelope::new(key, &key.public_hex(), msg_type, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn challenge(client: &reqwest::Client, relay_url: &str, member: &str) -> Option<String> {
|
pub async fn challenge(client: &reqwest::Client, relay_url: &str, member: &str) -> Option<String> {
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ async fn multi_relay_deduplicates_and_responds_once() {
|
|||||||
let alice = Keypair::generate();
|
let alice = Keypair::generate();
|
||||||
register(&http, &relay_one, &alice).await;
|
register(&http, &relay_one, &alice).await;
|
||||||
register(&http, &relay_two, &alice).await;
|
register(&http, &relay_two, &alice).await;
|
||||||
let envelope = query_envelope(&alice, "rust", 5);
|
let envelope = query_envelope(&alice, &alice.public_hex(), "rust", 5);
|
||||||
assert!(
|
assert!(
|
||||||
publish(&http, &relay_one, &envelope)
|
publish(&http, &relay_one, &envelope)
|
||||||
.await
|
.await
|
||||||
@@ -192,7 +192,7 @@ async fn poll_returns_queued_batch_in_one_call() {
|
|||||||
let member = Keypair::generate();
|
let member = Keypair::generate();
|
||||||
register(&http, &relay_url, &member).await;
|
register(&http, &relay_url, &member).await;
|
||||||
for index in 0..3 {
|
for index in 0..3 {
|
||||||
let envelope = query_envelope(&member, &format!("query {index}"), 5);
|
let envelope = query_envelope(&member, &member.public_hex(), &format!("query {index}"), 5);
|
||||||
assert!(
|
assert!(
|
||||||
publish(&http, &relay_url, &envelope)
|
publish(&http, &relay_url, &envelope)
|
||||||
.await
|
.await
|
||||||
|
|||||||
+31
-23
@@ -7,7 +7,7 @@ use std::time::{Duration, Instant};
|
|||||||
|
|
||||||
use common::{
|
use common::{
|
||||||
ask, challenge, client, collection, config_for, messages_of_type, poll, poll_messages, publish,
|
ask, challenge, client, collection, config_for, messages_of_type, poll, poll_messages, publish,
|
||||||
query_envelope, register, spawn_relay, spawn_relay_with_capacity, unicast,
|
query_envelope, register, spawn_relay, spawn_relay_with_capacity, test_envelope, unicast,
|
||||||
};
|
};
|
||||||
use frxd::crypto::Keypair;
|
use frxd::crypto::Keypair;
|
||||||
use frxd::index::LocalIndex;
|
use frxd::index::LocalIndex;
|
||||||
@@ -87,7 +87,7 @@ async fn raw_query(
|
|||||||
timeout_ms: u64,
|
timeout_ms: u64,
|
||||||
) -> Vec<Value> {
|
) -> Vec<Value> {
|
||||||
register(http, relay_url, asker).await;
|
register(http, relay_url, asker).await;
|
||||||
let envelope = query_envelope(asker, text, 5);
|
let envelope = query_envelope(asker, &asker.public_hex(), text, 5);
|
||||||
assert!(
|
assert!(
|
||||||
publish(http, relay_url, &envelope)
|
publish(http, relay_url, &envelope)
|
||||||
.await
|
.await
|
||||||
@@ -100,13 +100,14 @@ async fn raw_query(
|
|||||||
#[test]
|
#[test]
|
||||||
fn envelope_field_set_is_fixed() {
|
fn envelope_field_set_is_fixed() {
|
||||||
let key = Keypair::generate();
|
let key = Keypair::generate();
|
||||||
let envelope = Envelope::new(&key, TYPE_QUERY, json!({"qid": "q", "text": "t"}));
|
let envelope = test_envelope(&key, TYPE_QUERY, json!({"qid": "q", "text": "t"}));
|
||||||
let value = serde_json::to_value(&envelope).unwrap();
|
let value = serde_json::to_value(&envelope).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
keys(&value),
|
keys(&value),
|
||||||
BTreeSet::from([
|
BTreeSet::from([
|
||||||
"body".to_string(),
|
"body".to_string(),
|
||||||
"from".to_string(),
|
"from".to_string(),
|
||||||
|
"key".to_string(),
|
||||||
"nonce".to_string(),
|
"nonce".to_string(),
|
||||||
"sig".to_string(),
|
"sig".to_string(),
|
||||||
"ts".to_string(),
|
"ts".to_string(),
|
||||||
@@ -337,7 +338,7 @@ async fn entities_are_optional_hints() {
|
|||||||
"entities": ["Q999999", "not-a-real-entity"],
|
"entities": ["Q999999", "not-a-real-entity"],
|
||||||
"budget": {"max_results": 5}
|
"budget": {"max_results": 5}
|
||||||
});
|
});
|
||||||
let envelope = Envelope::new(&alice, TYPE_QUERY, body);
|
let envelope = test_envelope(&alice, TYPE_QUERY, body);
|
||||||
assert!(
|
assert!(
|
||||||
publish(&http, &relay_url, &envelope)
|
publish(&http, &relay_url, &envelope)
|
||||||
.await
|
.await
|
||||||
@@ -384,7 +385,7 @@ async fn member_directory_filters_senders() {
|
|||||||
assert_eq!(trusted.len(), 1, "trusted member got no response");
|
assert_eq!(trusted.len(), 1, "trusted member got no response");
|
||||||
|
|
||||||
register(&http, &relay_url, &untrusted).await;
|
register(&http, &relay_url, &untrusted).await;
|
||||||
let envelope = query_envelope(&untrusted, "rust", 5);
|
let envelope = query_envelope(&untrusted, &untrusted.public_hex(), "rust", 5);
|
||||||
assert!(
|
assert!(
|
||||||
publish(&http, &relay_url, &envelope)
|
publish(&http, &relay_url, &envelope)
|
||||||
.await
|
.await
|
||||||
@@ -424,7 +425,7 @@ async fn rotated_keys_are_accepted_through_previous_listing() {
|
|||||||
|
|
||||||
let http = client();
|
let http = client();
|
||||||
register(&http, &relay_url, &old_key).await;
|
register(&http, &relay_url, &old_key).await;
|
||||||
let envelope = query_envelope(&old_key, "rust", 5);
|
let envelope = query_envelope(&old_key, &old_key.public_hex(), "rust", 5);
|
||||||
assert!(
|
assert!(
|
||||||
publish(&http, &relay_url, &envelope)
|
publish(&http, &relay_url, &envelope)
|
||||||
.await
|
.await
|
||||||
@@ -436,7 +437,7 @@ async fn rotated_keys_are_accepted_through_previous_listing() {
|
|||||||
|
|
||||||
tokio::time::sleep(Duration::from_millis(20)).await;
|
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||||
frxd::config::save_members(&config.members_path(), &listing(Vec::new())).unwrap();
|
frxd::config::save_members(&config.members_path(), &listing(Vec::new())).unwrap();
|
||||||
let envelope = query_envelope(&old_key, "rust", 5);
|
let envelope = query_envelope(&old_key, &old_key.public_hex(), "rust", 5);
|
||||||
assert!(
|
assert!(
|
||||||
publish(&http, &relay_url, &envelope)
|
publish(&http, &relay_url, &envelope)
|
||||||
.await
|
.await
|
||||||
@@ -453,7 +454,7 @@ async fn stale_envelopes_are_rejected() {
|
|||||||
let http = client();
|
let http = client();
|
||||||
let key = Keypair::generate();
|
let key = Keypair::generate();
|
||||||
|
|
||||||
let mut envelope = query_envelope(&key, "stale", 5);
|
let mut envelope = query_envelope(&key, &key.public_hex(), "stale", 5);
|
||||||
envelope.ts = frxd::crypto::now_ts().saturating_sub(3600);
|
envelope.ts = frxd::crypto::now_ts().saturating_sub(3600);
|
||||||
envelope.sig = key.sign(&frxd::crypto::signing_bytes(&envelope));
|
envelope.sig = key.sign(&frxd::crypto::signing_bytes(&envelope));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -461,7 +462,7 @@ async fn stale_envelopes_are_rejected() {
|
|||||||
reqwest::StatusCode::BAD_REQUEST
|
reqwest::StatusCode::BAD_REQUEST
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut response = Envelope::new(
|
let mut response = test_envelope(
|
||||||
&key,
|
&key,
|
||||||
TYPE_RESPONSE,
|
TYPE_RESPONSE,
|
||||||
json!({"qid": "q1", "results": [], "truncated": false, "more_available": 0, "cursor": null}),
|
json!({"qid": "q1", "results": [], "truncated": false, "more_available": 0, "cursor": null}),
|
||||||
@@ -484,12 +485,12 @@ async fn backpressure_is_visible_and_recoverable() {
|
|||||||
let publisher = Keypair::generate();
|
let publisher = Keypair::generate();
|
||||||
register(&http, &relay_url, &member).await;
|
register(&http, &relay_url, &member).await;
|
||||||
|
|
||||||
let first = query_envelope(&publisher, "one", 5);
|
let first = query_envelope(&publisher, &publisher.public_hex(), "one", 5);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
publish(&http, &relay_url, &first).await.status(),
|
publish(&http, &relay_url, &first).await.status(),
|
||||||
reqwest::StatusCode::ACCEPTED
|
reqwest::StatusCode::ACCEPTED
|
||||||
);
|
);
|
||||||
let second = query_envelope(&publisher, "two", 5);
|
let second = query_envelope(&publisher, &publisher.public_hex(), "two", 5);
|
||||||
let response = publish(&http, &relay_url, &second).await;
|
let response = publish(&http, &relay_url, &second).await;
|
||||||
assert_eq!(response.status(), reqwest::StatusCode::TOO_MANY_REQUESTS);
|
assert_eq!(response.status(), reqwest::StatusCode::TOO_MANY_REQUESTS);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -511,7 +512,7 @@ async fn backpressure_is_visible_and_recoverable() {
|
|||||||
1
|
1
|
||||||
);
|
);
|
||||||
|
|
||||||
let third = query_envelope(&publisher, "three", 5);
|
let third = query_envelope(&publisher, &publisher.public_hex(), "three", 5);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
publish(&http, &relay_url, &third).await.status(),
|
publish(&http, &relay_url, &third).await.status(),
|
||||||
reqwest::StatusCode::ACCEPTED
|
reqwest::StatusCode::ACCEPTED
|
||||||
@@ -527,7 +528,7 @@ async fn unicast_is_need_to_know() {
|
|||||||
register(&http, &relay_url, &alice).await;
|
register(&http, &relay_url, &alice).await;
|
||||||
register(&http, &relay_url, &bob).await;
|
register(&http, &relay_url, &bob).await;
|
||||||
|
|
||||||
let query = query_envelope(&alice, "rust", 5);
|
let query = query_envelope(&alice, &alice.public_hex(), "rust", 5);
|
||||||
assert!(
|
assert!(
|
||||||
publish(&http, &relay_url, &query)
|
publish(&http, &relay_url, &query)
|
||||||
.await
|
.await
|
||||||
@@ -535,7 +536,7 @@ async fn unicast_is_need_to_know() {
|
|||||||
.is_success()
|
.is_success()
|
||||||
);
|
);
|
||||||
|
|
||||||
let response = Envelope::new(
|
let response = test_envelope(
|
||||||
&bob,
|
&bob,
|
||||||
TYPE_RESPONSE,
|
TYPE_RESPONSE,
|
||||||
json!({"qid": "q1", "results": [], "truncated": false, "more_available": 0, "cursor": null}),
|
json!({"qid": "q1", "results": [], "truncated": false, "more_available": 0, "cursor": null}),
|
||||||
@@ -561,7 +562,7 @@ async fn unicast_to_unknown_member_is_visible() {
|
|||||||
let relay_url = spawn_relay().await;
|
let relay_url = spawn_relay().await;
|
||||||
let http = client();
|
let http = client();
|
||||||
let sender = Keypair::generate();
|
let sender = Keypair::generate();
|
||||||
let response = Envelope::new(
|
let response = test_envelope(
|
||||||
&sender,
|
&sender,
|
||||||
TYPE_RESPONSE,
|
TYPE_RESPONSE,
|
||||||
json!({"qid": "q1", "results": [], "truncated": false, "more_available": 0, "cursor": null}),
|
json!({"qid": "q1", "results": [], "truncated": false, "more_available": 0, "cursor": null}),
|
||||||
@@ -580,12 +581,12 @@ async fn tampered_envelopes_are_rejected() {
|
|||||||
let relay_url = spawn_relay().await;
|
let relay_url = spawn_relay().await;
|
||||||
let http = client();
|
let http = client();
|
||||||
let key = Keypair::generate();
|
let key = Keypair::generate();
|
||||||
let mut envelope = query_envelope(&key, "rust", 5);
|
let mut envelope = query_envelope(&key, &key.public_hex(), "rust", 5);
|
||||||
envelope.body = json!({"qid": "q1", "text": "forged", "budget": {"max_results": 5}});
|
envelope.body = json!({"qid": "q1", "text": "forged", "budget": {"max_results": 5}});
|
||||||
let publish_response = publish(&http, &relay_url, &envelope).await;
|
let publish_response = publish(&http, &relay_url, &envelope).await;
|
||||||
assert_eq!(publish_response.status(), reqwest::StatusCode::BAD_REQUEST);
|
assert_eq!(publish_response.status(), reqwest::StatusCode::BAD_REQUEST);
|
||||||
|
|
||||||
let mut response = Envelope::new(
|
let mut response = test_envelope(
|
||||||
&key,
|
&key,
|
||||||
TYPE_RESPONSE,
|
TYPE_RESPONSE,
|
||||||
json!({"qid": "q1", "results": [], "truncated": false, "more_available": 0, "cursor": null}),
|
json!({"qid": "q1", "results": [], "truncated": false, "more_available": 0, "cursor": null}),
|
||||||
@@ -854,7 +855,7 @@ async fn aggregates_are_unicast_only_and_ignored_by_nodes() {
|
|||||||
let sender = Keypair::generate();
|
let sender = Keypair::generate();
|
||||||
register(&http, &relay_url, &bob.node.key).await;
|
register(&http, &relay_url, &bob.node.key).await;
|
||||||
|
|
||||||
let broadcast = Envelope::new(
|
let broadcast = test_envelope(
|
||||||
&sender,
|
&sender,
|
||||||
TYPE_AGGREGATE,
|
TYPE_AGGREGATE,
|
||||||
json!({"period": "2026-03", "sent": 1, "passed": 0}),
|
json!({"period": "2026-03", "sent": 1, "passed": 0}),
|
||||||
@@ -865,7 +866,7 @@ async fn aggregates_are_unicast_only_and_ignored_by_nodes() {
|
|||||||
"aggregates must not travel the broadcast channel"
|
"aggregates must not travel the broadcast channel"
|
||||||
);
|
);
|
||||||
|
|
||||||
let unicast_envelope = Envelope::new(
|
let unicast_envelope = test_envelope(
|
||||||
&sender,
|
&sender,
|
||||||
TYPE_AGGREGATE,
|
TYPE_AGGREGATE,
|
||||||
json!({"period": "2026-03", "sent": 1, "passed": 0}),
|
json!({"period": "2026-03", "sent": 1, "passed": 0}),
|
||||||
@@ -927,7 +928,7 @@ async fn duplicate_delivery_is_answered_once() {
|
|||||||
let alice = Keypair::generate();
|
let alice = Keypair::generate();
|
||||||
let http = client();
|
let http = client();
|
||||||
register(&http, &relay_url, &alice).await;
|
register(&http, &relay_url, &alice).await;
|
||||||
let envelope = query_envelope(&alice, "rust", 5);
|
let envelope = query_envelope(&alice, &alice.public_hex(), "rust", 5);
|
||||||
assert!(
|
assert!(
|
||||||
publish(&http, &relay_url, &envelope)
|
publish(&http, &relay_url, &envelope)
|
||||||
.await
|
.await
|
||||||
@@ -1000,7 +1001,8 @@ async fn multiple_responders_merge_with_provenance() {
|
|||||||
fn canonical_signing_bytes_are_stable() {
|
fn canonical_signing_bytes_are_stable() {
|
||||||
let mut envelope = Envelope {
|
let mut envelope = Envelope {
|
||||||
msg_type: TYPE_QUERY.to_string(),
|
msg_type: TYPE_QUERY.to_string(),
|
||||||
from: "aa".repeat(32),
|
from: "alice.frx.example".to_string(),
|
||||||
|
key: "aa".repeat(32),
|
||||||
ts: 1_700_000_000,
|
ts: 1_700_000_000,
|
||||||
nonce: "00112233445566778899aabbccddeeff".to_string(),
|
nonce: "00112233445566778899aabbccddeeff".to_string(),
|
||||||
body: json!({"text": "rust", "qid": "q1", "budget": {"max_results": 5}, "entities": []}),
|
body: json!({"text": "rust", "qid": "q1", "budget": {"max_results": 5}, "entities": []}),
|
||||||
@@ -1011,9 +1013,15 @@ fn canonical_signing_bytes_are_stable() {
|
|||||||
json!({"entities": [], "budget": {"max_results": 5}, "qid": "q1", "text": "rust"});
|
json!({"entities": [], "budget": {"max_results": 5}, "qid": "q1", "text": "rust"});
|
||||||
let second = frxd::crypto::signing_bytes(&envelope);
|
let second = frxd::crypto::signing_bytes(&envelope);
|
||||||
assert_eq!(first, second);
|
assert_eq!(first, second);
|
||||||
|
let key = frxd::crypto::Keypair::from_hex(&"00".repeat(32)).unwrap();
|
||||||
|
let signature = key.sign(&first);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
String::from_utf8(first).unwrap(),
|
String::from_utf8(first).unwrap(),
|
||||||
"FRX/0.4\nquery\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n1700000000\n00112233445566778899aabbccddeeff\n{\"budget\":{\"max_results\":5},\"entities\":[],\"qid\":\"q1\",\"text\":\"rust\"}"
|
"FRX/0.5\n{\"body\":{\"budget\":{\"max_results\":5},\"entities\":[],\"qid\":\"q1\",\"text\":\"rust\"},\"from\":\"alice.frx.example\",\"key\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"nonce\":\"00112233445566778899aabbccddeeff\",\"ts\":1700000000,\"type\":\"query\"}"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
signature,
|
||||||
|
"2d5df0f85b5ea6b97d5dbc53d316b410c848d52e407583f9740d10889ba5a40fbcf03a930fa7d351689aa657884e3a32e0c6941ff11653a71d75dea58b3e9b09"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1033,7 +1041,7 @@ async fn query_body_without_entities_parses() {
|
|||||||
let alice = Keypair::generate();
|
let alice = Keypair::generate();
|
||||||
let http = client();
|
let http = client();
|
||||||
register(&http, &relay_url, &alice).await;
|
register(&http, &relay_url, &alice).await;
|
||||||
let envelope = Envelope::new(
|
let envelope = test_envelope(
|
||||||
&alice,
|
&alice,
|
||||||
TYPE_QUERY,
|
TYPE_QUERY,
|
||||||
json!({"qid": "min", "text": "rust", "budget": {"max_results": 5}}),
|
json!({"qid": "min", "text": "rust", "budget": {"max_results": 5}}),
|
||||||
|
|||||||
+3
-3
@@ -2,9 +2,9 @@ mod common;
|
|||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use common::{ask, client, collection, config_for, publish, spawn_relay};
|
use common::{ask, client, collection, config_for, publish, spawn_relay, test_envelope};
|
||||||
use frxd::index::LocalIndex;
|
use frxd::index::LocalIndex;
|
||||||
use frxd::message::{EXPOSURE_FULL, Envelope, TYPE_RESPONSE};
|
use frxd::message::{EXPOSURE_FULL, TYPE_RESPONSE};
|
||||||
use frxd::node::Node;
|
use frxd::node::Node;
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||||
@@ -106,7 +106,7 @@ async fn two_node_broadcast_query_flow() {
|
|||||||
async fn relay_rejects_non_query_broadcasts() {
|
async fn relay_rejects_non_query_broadcasts() {
|
||||||
let relay_url = spawn_relay().await;
|
let relay_url = spawn_relay().await;
|
||||||
let key = frxd::crypto::Keypair::generate();
|
let key = frxd::crypto::Keypair::generate();
|
||||||
let envelope = Envelope::new(
|
let envelope = test_envelope(
|
||||||
&key,
|
&key,
|
||||||
TYPE_RESPONSE,
|
TYPE_RESPONSE,
|
||||||
serde_json::json!({"qid": "x", "results": []}),
|
serde_json::json!({"qid": "x", "results": []}),
|
||||||
|
|||||||
+11
-11
@@ -3,11 +3,11 @@ mod common;
|
|||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use common::{client, collection, config_for, poll, publish, spawn_relay, unicast};
|
use common::{client, collection, config_for, poll, publish, spawn_relay, test_envelope, unicast};
|
||||||
use frxd::crypto::Keypair;
|
use frxd::crypto::Keypair;
|
||||||
use frxd::index::LocalIndex;
|
use frxd::index::LocalIndex;
|
||||||
use frxd::message::{
|
use frxd::message::{
|
||||||
AggregateBody, Envelope, QueryBody, ResponseItem, TYPE_AGGREGATE, TYPE_QUERY, TYPE_RESPONSE,
|
AggregateBody, QueryBody, ResponseItem, TYPE_AGGREGATE, TYPE_QUERY, TYPE_RESPONSE,
|
||||||
build_response,
|
build_response,
|
||||||
};
|
};
|
||||||
use frxd::node::{self, Node};
|
use frxd::node::{self, Node};
|
||||||
@@ -39,7 +39,7 @@ fn no_bounty_winner_selection_or_slashing() {
|
|||||||
let query = serde_json::to_value(QueryBody::new("rust", 5)).unwrap();
|
let query = serde_json::to_value(QueryBody::new("rust", 5)).unwrap();
|
||||||
assert_exact_keys(&query, &["qid", "text", "entities", "budget"]);
|
assert_exact_keys(&query, &["qid", "text", "entities", "budget"]);
|
||||||
assert_exact_keys(query.get("budget").unwrap(), &["max_results"]);
|
assert_exact_keys(query.get("budget").unwrap(), &["max_results"]);
|
||||||
let envelope = Envelope::new(&Keypair::generate(), TYPE_QUERY, query.clone());
|
let envelope = test_envelope(&Keypair::generate(), TYPE_QUERY, query.clone());
|
||||||
let envelope = serde_json::to_value(&envelope).unwrap();
|
let envelope = serde_json::to_value(&envelope).unwrap();
|
||||||
for value in [&query, &envelope] {
|
for value in [&query, &envelope] {
|
||||||
assert_absent_fields(
|
assert_absent_fields(
|
||||||
@@ -64,7 +64,7 @@ async fn no_in_protocol_citation_accounting_or_settlement() {
|
|||||||
let key = Keypair::generate();
|
let key = Keypair::generate();
|
||||||
|
|
||||||
for msg_type in ["receipt", "settlement", "citation", "invoice"] {
|
for msg_type in ["receipt", "settlement", "citation", "invoice"] {
|
||||||
let envelope = Envelope::new(&key, msg_type, serde_json::json!({}));
|
let envelope = test_envelope(&key, msg_type, serde_json::json!({}));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
publish(&http, &relay_url, &envelope).await.status(),
|
publish(&http, &relay_url, &envelope).await.status(),
|
||||||
reqwest::StatusCode::BAD_REQUEST,
|
reqwest::StatusCode::BAD_REQUEST,
|
||||||
@@ -126,7 +126,7 @@ async fn no_protocol_query_dedup() {
|
|||||||
let relay_url = spawn_relay().await;
|
let relay_url = spawn_relay().await;
|
||||||
let http = client();
|
let http = client();
|
||||||
let key = Keypair::generate();
|
let key = Keypair::generate();
|
||||||
let envelope = Envelope::new(
|
let envelope = test_envelope(
|
||||||
&key,
|
&key,
|
||||||
TYPE_QUERY,
|
TYPE_QUERY,
|
||||||
serde_json::json!({"qid": "q1", "text": "rust"}),
|
serde_json::json!({"qid": "q1", "text": "rust"}),
|
||||||
@@ -225,7 +225,7 @@ async fn no_aggregate_appeals() {
|
|||||||
let sender = Keypair::generate();
|
let sender = Keypair::generate();
|
||||||
let member = Keypair::generate();
|
let member = Keypair::generate();
|
||||||
|
|
||||||
let dispute = Envelope::new(
|
let dispute = test_envelope(
|
||||||
&sender,
|
&sender,
|
||||||
"dispute",
|
"dispute",
|
||||||
serde_json::json!({"about": "counter", "reason": "unfair"}),
|
serde_json::json!({"about": "counter", "reason": "unfair"}),
|
||||||
@@ -245,7 +245,7 @@ async fn no_aggregate_appeals() {
|
|||||||
poll(&http, &relay_url, &member, 30).await.status(),
|
poll(&http, &relay_url, &member, 30).await.status(),
|
||||||
reqwest::StatusCode::NO_CONTENT
|
reqwest::StatusCode::NO_CONTENT
|
||||||
);
|
);
|
||||||
let aggregate = Envelope::new(
|
let aggregate = test_envelope(
|
||||||
&sender,
|
&sender,
|
||||||
TYPE_AGGREGATE,
|
TYPE_AGGREGATE,
|
||||||
serde_json::to_value(AggregateBody {
|
serde_json::to_value(AggregateBody {
|
||||||
@@ -304,7 +304,7 @@ async fn no_topic_channels() {
|
|||||||
poll(&http, &relay_url, &bob, 30).await.status(),
|
poll(&http, &relay_url, &bob, 30).await.status(),
|
||||||
reqwest::StatusCode::NO_CONTENT
|
reqwest::StatusCode::NO_CONTENT
|
||||||
);
|
);
|
||||||
let envelope = Envelope::new(
|
let envelope = test_envelope(
|
||||||
&alice,
|
&alice,
|
||||||
TYPE_QUERY,
|
TYPE_QUERY,
|
||||||
serde_json::json!({"qid": "q1", "text": "rust"}),
|
serde_json::json!({"qid": "q1", "text": "rust"}),
|
||||||
@@ -335,7 +335,7 @@ async fn no_broadcast_responses() {
|
|||||||
let http = client();
|
let http = client();
|
||||||
let key = Keypair::generate();
|
let key = Keypair::generate();
|
||||||
for msg_type in [TYPE_RESPONSE, "evidence", "commons", "broadcast_response"] {
|
for msg_type in [TYPE_RESPONSE, "evidence", "commons", "broadcast_response"] {
|
||||||
let envelope = Envelope::new(&key, msg_type, serde_json::json!({}));
|
let envelope = test_envelope(&key, msg_type, serde_json::json!({}));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
publish(&http, &relay_url, &envelope).await.status(),
|
publish(&http, &relay_url, &envelope).await.status(),
|
||||||
reqwest::StatusCode::BAD_REQUEST,
|
reqwest::StatusCode::BAD_REQUEST,
|
||||||
@@ -382,7 +382,7 @@ async fn no_supply_announce_firehose() {
|
|||||||
let http = client();
|
let http = client();
|
||||||
let key = Keypair::generate();
|
let key = Keypair::generate();
|
||||||
for msg_type in ["announce", "supply", "documents", "publish"] {
|
for msg_type in ["announce", "supply", "documents", "publish"] {
|
||||||
let envelope = Envelope::new(&key, msg_type, serde_json::json!({}));
|
let envelope = test_envelope(&key, msg_type, serde_json::json!({}));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
publish(&http, &relay_url, &envelope).await.status(),
|
publish(&http, &relay_url, &envelope).await.status(),
|
||||||
reqwest::StatusCode::BAD_REQUEST,
|
reqwest::StatusCode::BAD_REQUEST,
|
||||||
@@ -404,7 +404,7 @@ async fn no_durable_replayable_broadcast_stream() {
|
|||||||
let relay_url = spawn_relay().await;
|
let relay_url = spawn_relay().await;
|
||||||
let http = client();
|
let http = client();
|
||||||
let key = Keypair::generate();
|
let key = Keypair::generate();
|
||||||
let envelope = Envelope::new(
|
let envelope = test_envelope(
|
||||||
&key,
|
&key,
|
||||||
TYPE_QUERY,
|
TYPE_QUERY,
|
||||||
serde_json::json!({"qid": "q1", "text": "rust"}),
|
serde_json::json!({"qid": "q1", "text": "rust"}),
|
||||||
|
|||||||
+21
-11
@@ -78,11 +78,12 @@ async fn responses_for(
|
|||||||
http: &reqwest::Client,
|
http: &reqwest::Client,
|
||||||
relay_url: &str,
|
relay_url: &str,
|
||||||
asker: &Keypair,
|
asker: &Keypair,
|
||||||
|
identifier: &str,
|
||||||
text: &str,
|
text: &str,
|
||||||
timeout_ms: u64,
|
timeout_ms: u64,
|
||||||
) -> Vec<Value> {
|
) -> Vec<Value> {
|
||||||
register(http, relay_url, asker).await;
|
register(http, relay_url, asker).await;
|
||||||
let envelope = query_envelope(asker, text, 5);
|
let envelope = query_envelope(asker, identifier, text, 5);
|
||||||
assert!(
|
assert!(
|
||||||
publish(http, relay_url, &envelope)
|
publish(http, relay_url, &envelope)
|
||||||
.await
|
.await
|
||||||
@@ -133,16 +134,24 @@ async fn registry_gates_membership_and_revocation_propagates() {
|
|||||||
.await;
|
.await;
|
||||||
let http = client();
|
let http = client();
|
||||||
|
|
||||||
let accepted = responses_for(&http, &relay_url, &alice, "rust", 700).await;
|
let accepted = responses_for(&http, &relay_url, &alice, "alice.frx.example", "rust", 700).await;
|
||||||
assert_eq!(accepted.len(), 1, "listed member got no response");
|
assert_eq!(accepted.len(), 1, "listed member got no response");
|
||||||
|
|
||||||
let stranger = Keypair::generate();
|
let stranger = Keypair::generate();
|
||||||
let denied = responses_for(&http, &relay_url, &stranger, "rust", 400).await;
|
let denied = responses_for(
|
||||||
|
&http,
|
||||||
|
&relay_url,
|
||||||
|
&stranger,
|
||||||
|
&stranger.public_hex(),
|
||||||
|
"rust",
|
||||||
|
400,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
assert!(denied.is_empty(), "unlisted key was answered");
|
assert!(denied.is_empty(), "unlisted key was answered");
|
||||||
|
|
||||||
tokio::time::sleep(Duration::from_millis(20)).await;
|
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||||
registry::save_registry(®istry_path, &ma_registry(&ma, Vec::new(), 2)).unwrap();
|
registry::save_registry(®istry_path, &ma_registry(&ma, Vec::new(), 2)).unwrap();
|
||||||
let revoked = responses_for(&http, &relay_url, &alice, "rust", 500).await;
|
let revoked = responses_for(&http, &relay_url, &alice, "alice.frx.example", "rust", 500).await;
|
||||||
assert!(revoked.is_empty(), "revoked member still answered");
|
assert!(revoked.is_empty(), "revoked member still answered");
|
||||||
|
|
||||||
tokio::time::sleep(Duration::from_millis(20)).await;
|
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||||
@@ -155,7 +164,8 @@ async fn registry_gates_membership_and_revocation_propagates() {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let rolled_back = responses_for(&http, &relay_url, &alice, "rust", 500).await;
|
let rolled_back =
|
||||||
|
responses_for(&http, &relay_url, &alice, "alice.frx.example", "rust", 500).await;
|
||||||
assert!(
|
assert!(
|
||||||
rolled_back.is_empty(),
|
rolled_back.is_empty(),
|
||||||
"registry rollback re-admitted a revoked member"
|
"registry rollback re-admitted a revoked member"
|
||||||
@@ -188,7 +198,7 @@ async fn forged_or_wrong_key_snapshot_closes_the_registry() {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let http = client();
|
let http = client();
|
||||||
let denied = responses_for(&http, &relay_url, &alice, "rust", 400).await;
|
let denied = responses_for(&http, &relay_url, &alice, "alice.frx.example", "rust", 400).await;
|
||||||
assert!(
|
assert!(
|
||||||
denied.is_empty(),
|
denied.is_empty(),
|
||||||
"snapshot signed by wrong key was trusted"
|
"snapshot signed by wrong key was trusted"
|
||||||
@@ -226,7 +236,7 @@ async fn expired_key_is_not_authorized() {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let http = client();
|
let http = client();
|
||||||
let denied = responses_for(&http, &relay_url, &alice, "rust", 400).await;
|
let denied = responses_for(&http, &relay_url, &alice, "alice.frx.example", "rust", 400).await;
|
||||||
assert!(denied.is_empty(), "expired key was answered");
|
assert!(denied.is_empty(), "expired key was answered");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +253,7 @@ async fn fail_static_uses_last_validated_snapshot() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let cached_dir = root.path().join("bob-cached");
|
let cached_dir = root.path().join("bob-cached");
|
||||||
let mut config = config_with_registry(
|
let config = config_with_registry(
|
||||||
&cached_dir,
|
&cached_dir,
|
||||||
"bob",
|
"bob",
|
||||||
&relay_url,
|
&relay_url,
|
||||||
@@ -264,7 +274,7 @@ async fn fail_static_uses_last_validated_snapshot() {
|
|||||||
let _bob = Node::start(config.clone()).await.unwrap();
|
let _bob = Node::start(config.clone()).await.unwrap();
|
||||||
|
|
||||||
let http = client();
|
let http = client();
|
||||||
let accepted = responses_for(&http, &relay_url, &alice, "rust", 700).await;
|
let accepted = responses_for(&http, &relay_url, &alice, "alice.frx.example", "rust", 700).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
accepted.len(),
|
accepted.len(),
|
||||||
1,
|
1,
|
||||||
@@ -287,7 +297,7 @@ async fn fail_static_uses_last_validated_snapshot() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
let _bob2 = Node::start(fresh).await.unwrap();
|
let _bob2 = Node::start(fresh).await.unwrap();
|
||||||
let denied = responses_for(&http, &relay_url, &alice, "rust", 400).await;
|
let denied = responses_for(&http, &relay_url, &alice, "alice.frx.example", "rust", 400).await;
|
||||||
assert!(
|
assert!(
|
||||||
denied.is_empty(),
|
denied.is_empty(),
|
||||||
"unreachable registry without cache must close, not open"
|
"unreachable registry without cache must close, not open"
|
||||||
@@ -337,7 +347,7 @@ async fn registry_can_be_served_over_http() {
|
|||||||
|
|
||||||
let _bob = bob_with_docs(root.path(), &relay_url, ®istry_url, &ma.public_hex()).await;
|
let _bob = bob_with_docs(root.path(), &relay_url, ®istry_url, &ma.public_hex()).await;
|
||||||
let http = client();
|
let http = client();
|
||||||
let accepted = responses_for(&http, &relay_url, &alice, "rust", 900).await;
|
let accepted = responses_for(&http, &relay_url, &alice, "alice.frx.example", "rust", 900).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
accepted.len(),
|
accepted.len(),
|
||||||
1,
|
1,
|
||||||
|
|||||||
Reference in New Issue
Block a user