1use std::cmp;
2use std::collections::{HashMap, VecDeque};
3use std::net::SocketAddr;
4use std::sync::Arc;
5use std::time::{Duration, Instant};
6
7use antithesis_sdk::assert_sometimes;
8
9use bytes::{BufMut, BytesMut};
10use futures::stream::FuturesUnordered;
11use futures::{Future, Stream, TryFutureExt, TryStreamExt};
12use itertools::Itertools;
13use klukai_types::actor::ClusterId;
14use klukai_types::agent::{Agent, SplitPool};
15use klukai_types::base::{CrsqlDbVersion, CrsqlDbVersionRange, CrsqlSeq, CrsqlSeqRange};
16use klukai_types::broadcast::{
17 BiPayload, BiPayloadV1, ChangeSource, ChangeV1, Changeset, Timestamp,
18};
19use klukai_types::change::{Change, ChunkedChanges, row_to_change};
20use klukai_types::config::{GossipConfig, TlsClientConfig};
21use klukai_types::sync::{
22 SyncMessage, SyncMessageEncodeError, SyncMessageV1, SyncNeedV1, SyncRejectionV1, SyncRequestV1,
23 SyncStateV1, SyncTraceContextV1, generate_sync,
24};
25use metrics::counter;
26use quinn::crypto::rustls::{QuicClientConfig, QuicServerConfig};
27use quinn::{RecvStream, SendStream, WriteError};
28use rangemap::RangeInclusiveSet;
29use rusqlite::{Connection, named_params};
30use speedy::Writable;
31use std::string::String;
32use tokio::io::AsyncWriteExt;
33use tokio::sync::mpsc::{self, Sender, unbounded_channel};
34use tokio::task::block_in_place;
35use tokio::time::timeout;
36use tokio_stream::StreamExt as TokioStreamExt;
37use tokio_stream::wrappers::{ReceiverStream, UnboundedReceiverStream};
38use tokio_util::codec::{Encoder, FramedRead, LengthDelimitedCodec};
40use tracing::{Instrument, debug, error, info, info_span, trace, warn};
41use tracing_opentelemetry::OpenTelemetrySpanExt;
42
43use crate::agent::SyncRecvError;
44use crate::quinn_plaintext;
45use crate::transport::{Transport, TransportError};
46
47use klukai_types::{actor::ActorId, agent::Bookie};
48
49#[derive(Debug, thiserror::Error)]
50pub enum SyncError {
51 #[error(transparent)]
52 Send(#[from] SyncSendError),
53 #[error(transparent)]
54 BiPayloadSend(#[from] BiPayloadSendError),
55 #[error(transparent)]
56 Recv(#[from] SyncRecvError),
57 #[error(transparent)]
58 Rejection(#[from] SyncRejectionV1),
59 #[error(transparent)]
60 Transport(#[from] TransportError),
61}
62
63#[derive(Debug, thiserror::Error)]
64pub enum SyncSendError {
65 #[error(transparent)]
66 Io(#[from] std::io::Error),
67
68 #[error(transparent)]
69 Write(#[from] quinn::WriteError),
70
71 #[error(transparent)]
72 Rusqlite(#[from] rusqlite::Error),
73
74 #[error("expected a column, there were none")]
75 NoMoreColumns,
76
77 #[error("expected column '{0}', got none")]
78 MissingColumn(&'static str),
79
80 #[error("expected column '{0}', got '{1}'")]
81 WrongColumn(&'static str, String),
82
83 #[error("unexpected column '{0}'")]
84 UnexpectedColumn(String),
85
86 #[error("unknown resource state")]
87 UnknownResourceState,
88
89 #[error("unknown resource type")]
90 UnknownResourceType,
91
92 #[error("wrong ip byte len: {0}")]
93 WrongIpByteLength(usize),
94
95 #[error("could not encode message: {0}")]
96 Encode(#[from] SyncMessageEncodeError),
97
98 #[error("sync send channel is closed")]
99 ChannelClosed,
100}
101
102#[derive(Debug, thiserror::Error)]
103pub enum BiPayloadSendError {
104 #[error("could not encode payload: {0}")]
105 Encode(#[from] BiPayloadEncodeError),
106 #[error(transparent)]
107 Io(#[from] std::io::Error),
108 #[error(transparent)]
109 Write(#[from] quinn::WriteError),
110}
111
112#[derive(Debug, thiserror::Error)]
113pub enum BiPayloadEncodeError {
114 #[error(transparent)]
115 Encode(#[from] speedy::Error),
116 #[error(transparent)]
117 Io(#[from] std::io::Error),
118}
119
120fn build_quinn_transport_config(config: &GossipConfig) -> quinn::TransportConfig {
121 let mut transport_config = quinn::TransportConfig::default();
122
123 transport_config.max_idle_timeout(Some(
125 Duration::from_secs(config.idle_timeout_secs as u64)
126 .try_into()
127 .unwrap(),
128 ));
129
130 transport_config.max_concurrent_bidi_streams(32u32.into());
132
133 transport_config.max_concurrent_uni_streams(256u32.into());
135
136 if let Some(max_mtu) = config.max_mtu {
137 info!("Setting maximum MTU for QUIC at {max_mtu}");
138 transport_config.initial_mtu(max_mtu);
139 transport_config.min_mtu(max_mtu);
140 transport_config.mtu_discovery_config(None);
142 }
143
144 if config.disable_gso {
145 transport_config.enable_segmentation_offload(false);
146 }
147
148 transport_config
149}
150
151async fn build_quinn_server_config(config: &GossipConfig) -> eyre::Result<quinn::ServerConfig> {
152 let mut server_config = if config.plaintext {
153 quinn_plaintext::server_config()
154 } else {
155 let tls = config
156 .tls
157 .as_ref()
158 .ok_or_else(|| eyre::eyre!("either plaintext or a tls config is required"))?;
159
160 let key = tokio::fs::read(&tls.key_file).await?;
161 let key = if tls.key_file.extension() == Some("der") {
162 rustls_pki_types::PrivateKeyDer::Pkcs8(rustls_pki_types::PrivatePkcs8KeyDer::from(key))
163 } else {
164 let pkcs8 = {
165 let result: Result<Vec<_>, _> =
166 rustls_pemfile::pkcs8_private_keys(&mut &*key).collect();
167 result?
168 };
169 match pkcs8.into_iter().next() {
170 Some(x) => rustls_pki_types::PrivateKeyDer::Pkcs8(x),
171 None => {
172 let rsa = {
173 let result: Result<Vec<_>, _> =
174 rustls_pemfile::rsa_private_keys(&mut &*key).collect();
175 result?
176 };
177 match rsa.into_iter().next() {
178 Some(x) => rustls_pki_types::PrivateKeyDer::Pkcs1(x),
179 None => {
180 eyre::bail!("no private keys found");
181 }
182 }
183 }
184 }
185 };
186
187 let certs = tokio::fs::read(&tls.cert_file).await?;
188 let certs = if tls.cert_file.extension() == Some("der") {
189 vec![rustls_pki_types::CertificateDer::from(certs)]
190 } else {
191 {
192 let result: Result<Vec<_>, _> = rustls_pemfile::certs(&mut &*certs).collect();
193 result?
194 }
195 .into_iter()
196 .map(|x| rustls_pki_types::CertificateDer::from(x.as_ref().to_vec()))
197 .collect()
198 };
199
200 let server_crypto = rustls::ServerConfig::builder();
201
202 let server_crypto = if tls.client.is_some() {
203 let ca_file = match &tls.ca_file {
204 None => {
205 eyre::bail!(
206 "ca_file required in tls config for server client cert auth verification"
207 );
208 }
209 Some(ca_file) => ca_file,
210 };
211
212 let ca_certs = tokio::fs::read(&ca_file).await?;
213 let ca_certs = if ca_file.extension() == Some("der") {
214 vec![rustls_pki_types::CertificateDer::from(ca_certs)]
215 } else {
216 {
217 let result: Result<Vec<_>, _> =
218 rustls_pemfile::certs(&mut &*ca_certs).collect();
219 result?
220 }
221 .into_iter()
222 .map(|x| rustls_pki_types::CertificateDer::from(x.as_ref().to_vec()))
223 .collect()
224 };
225
226 let mut root_store = rustls::RootCertStore::empty();
227
228 for cert in ca_certs {
229 root_store.add(cert)?;
230 }
231
232 server_crypto.with_client_cert_verifier(
233 rustls::server::WebPkiClientVerifier::builder(Arc::new(root_store))
234 .build()
235 .unwrap(),
236 )
237 } else {
238 server_crypto.with_no_client_auth()
239 };
240
241 quinn::ServerConfig::with_crypto(Arc::new(QuicServerConfig::try_from(
242 server_crypto.with_single_cert(certs, key)?,
243 )?))
244 };
245
246 let transport_config = build_quinn_transport_config(config);
247
248 server_config.transport_config(Arc::new(transport_config));
249
250 Ok(server_config)
251}
252
253pub async fn gossip_server_endpoint(config: &GossipConfig) -> eyre::Result<quinn::Endpoint> {
254 let server_config = build_quinn_server_config(config).await?;
255
256 Ok(quinn::Endpoint::server(server_config, config.bind_addr)?)
257}
258
259fn client_cert_auth(
260 config: &TlsClientConfig,
261) -> eyre::Result<(
262 Vec<rustls_pki_types::CertificateDer<'_>>,
263 rustls_pki_types::PrivateKeyDer<'_>,
264)> {
265 let mut cert_file = std::io::BufReader::new(
266 std::fs::OpenOptions::new()
267 .read(true)
268 .open(&config.cert_file)?,
269 );
270 let certs = {
271 let result: Result<Vec<_>, _> = rustls_pemfile::certs(&mut cert_file).collect();
272 result?
273 }
274 .into_iter()
275 .map(|x| rustls_pki_types::CertificateDer::from(x.as_ref().to_vec()))
276 .collect();
277
278 let mut key_file = std::io::BufReader::new(
279 std::fs::OpenOptions::new()
280 .read(true)
281 .open(&config.key_file)?,
282 );
283 let key = {
284 let result: Result<Vec<_>, _> = rustls_pemfile::pkcs8_private_keys(&mut key_file).collect();
285 result?
286 }
287 .into_iter()
288 .map(rustls_pki_types::PrivateKeyDer::Pkcs8)
289 .next()
290 .ok_or_else(|| eyre::eyre!("could not find client tls key"))?;
291
292 Ok((certs, key))
293}
294
295async fn build_quinn_client_config(config: &GossipConfig) -> eyre::Result<quinn::ClientConfig> {
296 let mut client_config = if config.plaintext {
297 quinn_plaintext::client_config()
298 } else {
299 let tls = config
300 .tls
301 .as_ref()
302 .ok_or_else(|| eyre::eyre!("tls config required"))?;
303
304 let client_crypto = rustls::ClientConfig::builder();
305
306 let client_crypto = if let Some(ca_file) = &tls.ca_file {
307 let ca_certs = tokio::fs::read(&ca_file).await?;
308 let ca_certs = if ca_file.extension() == Some("der") {
309 vec![rustls_pki_types::CertificateDer::from(ca_certs)]
310 } else {
311 {
312 let result: Result<Vec<_>, _> =
313 rustls_pemfile::certs(&mut &*ca_certs).collect();
314 result?
315 }
316 .into_iter()
317 .map(|x| rustls_pki_types::CertificateDer::from(x.as_ref().to_vec()))
318 .collect()
319 };
320
321 let mut root_store = rustls::RootCertStore::empty();
322
323 for cert in ca_certs {
324 root_store.add(cert)?;
325 }
326
327 let client_crypto = client_crypto.with_root_certificates(root_store);
328
329 if let Some(client_config) = &tls.client {
330 let (certs, key) = client_cert_auth(client_config)?;
331 let certs: Vec<rustls_pki_types::CertificateDer<'static>> =
332 certs.into_iter().map(|c| c.into_owned()).collect();
333 let key: rustls_pki_types::PrivateKeyDer<'static> = key.clone_key();
334 client_crypto.with_client_auth_cert(certs, key)?
335 } else {
336 client_crypto.with_no_client_auth()
337 }
338 } else {
339 if !tls.insecure {
340 eyre::bail!(
341 "insecure setting needs to be explicitly true if no ca_file is provided"
342 );
343 }
344 let client_crypto = client_crypto
345 .dangerous()
346 .with_custom_certificate_verifier(SkipServerVerification::new());
347
348 if let Some(client_config) = &tls.client {
349 let (certs, key) = client_cert_auth(client_config)?;
350 let certs: Vec<rustls_pki_types::CertificateDer<'static>> =
351 certs.into_iter().map(|c| c.into_owned()).collect();
352 let key: rustls_pki_types::PrivateKeyDer<'static> = key.clone_key();
353 client_crypto.with_client_auth_cert(certs, key)?
354 } else {
355 client_crypto.with_no_client_auth()
356 }
357 };
358
359 quinn::ClientConfig::new(Arc::new(QuicClientConfig::try_from(client_crypto)?))
360 };
361
362 let mut transport_config = build_quinn_transport_config(config);
363
364 transport_config.keep_alive_interval(Some(Duration::from_secs(
366 config.idle_timeout_secs as u64 / 2,
367 )));
368
369 client_config.transport_config(Arc::new(transport_config));
370
371 Ok(client_config)
372}
373
374pub async fn gossip_client_endpoint(config: &GossipConfig) -> eyre::Result<quinn::Endpoint> {
375 let client_config = build_quinn_client_config(config).await?;
376
377 let mut client = quinn::Endpoint::client(config.client_addr)?;
378
379 client.set_default_client_config(client_config);
380 Ok(client)
381}
382
383#[derive(Debug)]
386struct SkipServerVerification;
387
388impl SkipServerVerification {
389 fn new() -> Arc<Self> {
390 Arc::new(Self)
391 }
392}
393
394impl rustls::client::danger::ServerCertVerifier for SkipServerVerification {
395 fn verify_server_cert(
396 &self,
397 _end_entity: &rustls_pki_types::CertificateDer,
398 _intermediates: &[rustls_pki_types::CertificateDer],
399 _server_name: &rustls_pki_types::ServerName,
400 _ocsp_response: &[u8],
401 _now: rustls_pki_types::UnixTime,
402 ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
403 Ok(rustls::client::danger::ServerCertVerified::assertion())
404 }
405
406 fn verify_tls12_signature(
407 &self,
408 _message: &[u8],
409 _cert: &rustls_pki_types::CertificateDer,
410 _dss: &rustls::DigitallySignedStruct,
411 ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
412 Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
413 }
414
415 fn verify_tls13_signature(
416 &self,
417 _message: &[u8],
418 _cert: &rustls_pki_types::CertificateDer,
419 _dss: &rustls::DigitallySignedStruct,
420 ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
421 Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
422 }
423
424 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
425 vec![
426 rustls::SignatureScheme::RSA_PKCS1_SHA1,
427 rustls::SignatureScheme::ECDSA_SHA1_Legacy,
428 rustls::SignatureScheme::RSA_PKCS1_SHA256,
429 rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
430 rustls::SignatureScheme::RSA_PKCS1_SHA384,
431 rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
432 rustls::SignatureScheme::RSA_PKCS1_SHA512,
433 rustls::SignatureScheme::ECDSA_NISTP521_SHA512,
434 rustls::SignatureScheme::RSA_PSS_SHA256,
435 rustls::SignatureScheme::RSA_PSS_SHA384,
436 rustls::SignatureScheme::RSA_PSS_SHA512,
437 rustls::SignatureScheme::ED25519,
438 rustls::SignatureScheme::ED448,
439 ]
440 }
441}
442
443const MAX_CHANGES_BYTES_PER_MESSAGE: usize = 8 * 1024;
444const MIN_CHANGES_BYTES_PER_MESSAGE: usize = 1024;
445
446const ADAPT_CHUNK_SIZE_THRESHOLD: Duration = Duration::from_millis(500);
447
448#[allow(clippy::too_many_arguments)]
449fn handle_need(
450 conn: &mut Connection,
451 actor_id: ActorId,
452 need: SyncNeedV1,
453 sender: &Sender<SyncMessage>,
454) -> eyre::Result<()> {
455 debug!(%actor_id, "handle known versions! need: {need:?}");
456
457 let mut empties: RangeInclusiveSet<CrsqlDbVersion> = RangeInclusiveSet::new();
458
459 assert_sometimes!(true, "Corrosion handles sync requests from other nodes");
460 let tx = conn.transaction()?;
462
463 let mut prepped = tx.prepare_cached(
464 "
465 SELECT db_version, MAX(seq) AS last_seq, MAX(ts) AS ts
466 FROM crsql_changes
467 WHERE site_id = :actor_id
468 AND db_version BETWEEN :start AND :end
469 GROUP BY db_version
470 ORDER BY db_version DESC, seq ASC
471 ",
472 )?;
473
474 match need {
475 SyncNeedV1::Full { versions } => {
476 let mut rows = prepped.query(named_params! {
477 ":actor_id": actor_id,
478 ":start": versions.start(),
479 ":end": versions.end(),
480 })?;
481
482 let mut unprocessed = RangeInclusiveSet::new();
483 unprocessed.insert(versions.into());
484
485 loop {
486 let row = match rows.next()? {
487 Some(row) => row,
488 None => {
489 break;
490 }
491 };
492
493 let version: CrsqlDbVersion = row.get(0)?;
494
495 unprocessed.remove(version..=version);
496
497 let last_seq: CrsqlSeq = row.get(1)?;
498 let ts: Timestamp = row.get(2)?;
499 debug!(%actor_id, ?version, %ts, "not empty");
500
501 let mut prepped = tx.prepare_cached(
502 r#"
503 SELECT "table", pk, cid, val, col_version, db_version, seq, site_id, cl, ts
504 FROM crsql_changes
505 WHERE site_id = :actor_id
506 AND db_version = :version
507 ORDER BY seq ASC
508 "#,
509 )?;
510
511 let rows = prepped.query_map(
512 named_params! {
513 ":actor_id": actor_id,
514 ":version": version
515 },
516 row_to_change,
517 )?;
518
519 debug!(%actor_id, ?version, ?last_seq, "not empty");
520
521 send_change_chunks(
522 sender,
523 ChunkedChanges::new(rows, CrsqlSeq(0), last_seq, MAX_CHANGES_BYTES_PER_MESSAGE),
524 actor_id,
525 version,
526 last_seq,
527 ts,
528 )?;
529 }
530
531 for versions in unprocessed {
533 for version in CrsqlDbVersionRange::from(versions) {
534 let (in_gaps, buffered): (bool, bool) = tx
535 .prepare_cached(
536 "
537 SELECT
538 EXISTS(
539 SELECT 1
540 FROM __corro_bookkeeping_gaps
541 WHERE actor_id = :actor_id
542 AND :version BETWEEN start AND end
543 ) AS in_gaps,
544 EXISTS(
545 SELECT 1
546 FROM __corro_buffered_changes
547 WHERE site_id = :actor_id
548 AND db_version = :version
549 ) AS buffered",
550 )?
551 .query_row(
552 named_params! {
553 ":actor_id": actor_id,
554 ":version": version
555 },
556 |row| Ok((row.get(0)?, row.get(1)?)),
557 )?;
558
559 if !buffered {
560 if !in_gaps {
561 empties.insert(version..=version);
563 }
564 continue;
565 }
566
567 let seqs = tx
568 .prepare_cached("
569 SELECT start_seq, end_seq, last_seq, ts FROM __corro_seq_bookkeeping WHERE site_id = :actor_id AND db_version = :db_version
570 ")?.query_map(named_params!{
571 ":actor_id": actor_id,
572 ":db_version": version
573 },|row| Ok((CrsqlSeqRange::new(row.get(0)?, row.get(1)?), row.get(2)?, row.get(3)?)))?.collect::<rusqlite::Result<Vec<(CrsqlSeqRange, CrsqlSeq, Timestamp)>>>()?;
574
575 for (range_needed, last_seq, ts) in seqs {
576 let mut prepped = tx.prepare_cached(
577 r#"
578 SELECT "table", pk, cid, val, col_version, db_version, seq, site_id, cl
579 FROM __corro_buffered_changes
580 WHERE site_id = :actor_id
581 AND db_version = :db_version
582 AND seq BETWEEN :start_seq AND :end_seq
583 ORDER BY seq ASC
584 "#,
585 )?;
586
587 let start_seq = range_needed.start();
588 let end_seq = range_needed.end();
589
590 let rows = prepped.query_map(
591 named_params! {
592 ":actor_id": actor_id,
593 ":db_version": version,
594 ":start_seq": start_seq,
595 ":end_seq": end_seq
596 },
597 row_to_change,
598 )?;
599
600 send_change_chunks(
601 sender,
602 ChunkedChanges::new(
603 rows,
604 start_seq,
605 end_seq,
606 MAX_CHANGES_BYTES_PER_MESSAGE,
607 ),
608 actor_id,
609 version,
610 last_seq,
611 ts,
612 )?;
613 }
614 }
615 }
616 }
617 SyncNeedV1::Partial { version, seqs } => {
618 let mut rows = prepped.query(named_params! {
619 ":actor_id": actor_id,
620 ":start": version,
621 ":end": version,
622 })?;
623
624 trace!(%version, "looking up from crsql_changes!");
625
626 match rows.next()? {
627 Some(row) => {
628 let version: CrsqlDbVersion = row.get(0)?;
629 let last_seq: CrsqlSeq = row.get(1)?;
630 let ts: Timestamp = row.get(2)?;
631 trace!(%version, %last_seq, "got a row from crsql_change!");
632
633 for range_needed in seqs {
634 let mut prepped = tx.prepare_cached(
635 r#"
636 SELECT "table", pk, cid, val, col_version, db_version, seq, site_id, cl
637 FROM crsql_changes
638 WHERE site_id = :actor_id
639 AND db_version = :version
640 AND seq BETWEEN :start AND :end
641 ORDER BY seq ASC
642 "#,
643 )?;
644
645 let rows = prepped.query_map(
646 named_params! {
647 ":actor_id": actor_id,
648 ":version": version,
649 ":start": range_needed.start(),
650 ":end": range_needed.end(),
651 },
652 row_to_change,
653 )?;
654
655 send_change_chunks(
656 sender,
657 ChunkedChanges::new(
658 rows,
659 range_needed.start(),
660 range_needed.end(),
661 MAX_CHANGES_BYTES_PER_MESSAGE,
662 ),
663 actor_id,
664 version,
665 last_seq,
666 ts,
667 )?;
668 }
669 }
670 None => {
671 trace!(%version, ?seqs, "no rows in crsql_changes, checking partials and gaps...");
672
673 let (in_gaps, buffered): (bool, bool) = tx
674 .prepare_cached(
675 "
676 SELECT
677 EXISTS(
678 SELECT 1
679 FROM __corro_bookkeeping_gaps
680 WHERE actor_id = :actor_id
681 AND :version BETWEEN start AND end
682 ) AS in_gaps,
683 EXISTS(
684 SELECT 1
685 FROM __corro_buffered_changes
686 WHERE site_id = :actor_id
687 AND db_version = :version
688 ) AS buffered",
689 )?
690 .query_row(
691 named_params! {
692 ":actor_id": actor_id,
693 ":version": version
694 },
695 |row| Ok((row.get(0)?, row.get(1)?)),
696 )?;
697
698 if !buffered && !in_gaps {
699 empties.insert(version..=version);
701 }
702
703 if buffered {
704 for seqs_range in seqs {
705 let seqs = tx
706 .prepare_cached(
707 "
708 SELECT start_seq, end_seq, last_seq, ts
709 FROM __corro_seq_bookkeeping
710 WHERE
711 site_id = :actor_id AND
712 db_version = :db_version AND
713 (
714 -- [:start]---[start_seq]---[:end]
715 ( start_seq BETWEEN :start AND :end ) OR
716
717 -- [start_seq]---[:start]---[:end]---[end_seq]
718 ( start_seq <= :start AND end_seq >= :end ) OR
719
720 -- [:start]---[start_seq]---[:end]---[end_seq]
721 ( start_seq <= :end AND end_seq >= :end ) OR
722
723 -- [:start]---[end_seq]---[:end]
724 ( end_seq BETWEEN :start AND :end )
725 )
726 ",
727 )?
728 .query_map(
729 named_params! {
730 ":actor_id": actor_id,
731 ":db_version": version,
732 ":start": seqs_range.start(),
733 ":end": seqs_range.end(),
734 },
735 |row| {
736 Ok((
737 CrsqlSeqRange::new(row.get(0)?, row.get(1)?),
738 row.get(2)?,
739 row.get(3)?,
740 ))
741 },
742 )?
743 .collect::<rusqlite::Result<
744 Vec<(CrsqlSeqRange, CrsqlSeq, Timestamp)>,
745 >>()?;
746
747 trace!(%version, ?seqs, "got some partial seqs!");
748
749 for (range_needed, last_seq, ts) in seqs {
750 let mut prepped = tx.prepare_cached(
751 r#"
752 SELECT "table", pk, cid, val, col_version, db_version, seq, site_id, cl
753 FROM __corro_buffered_changes
754 WHERE site_id = :actor_id
755 AND db_version = :version
756 AND seq BETWEEN :start_seq AND :end_seq
757 ORDER BY seq ASC
758 "#,
759 )?;
760
761 let start_seq = cmp::max(range_needed.start(), seqs_range.start());
763 let end_seq = cmp::min(range_needed.end(), seqs_range.end());
764
765 trace!(%version, %start_seq, %end_seq, "getting seq range from crsql_changes");
766
767 let rows = prepped.query_map(
768 named_params! {
769 ":actor_id": actor_id,
770 ":version": version,
771 ":start_seq": start_seq,
772 ":end_seq": end_seq
773 },
774 row_to_change,
775 )?;
776
777 send_change_chunks(
778 sender,
779 ChunkedChanges::new(
780 rows,
781 start_seq,
782 end_seq,
783 MAX_CHANGES_BYTES_PER_MESSAGE,
784 ),
785 actor_id,
786 version,
787 last_seq,
788 ts,
789 )?;
790 }
791 }
792 }
793 }
794 }
795 }
796 SyncNeedV1::Empty { .. } => {
797 }
799 }
800
801 if !empties.is_empty() {
802 for versions in empties {
803 sender.blocking_send(SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
804 actor_id,
805 changeset: Changeset::Empty {
806 versions: versions.into(),
807 ts: None,
808 },
809 })))?;
810 }
811 }
812
813 Ok(())
814}
815
816fn send_change_chunks<I: Iterator<Item = rusqlite::Result<Change>>>(
817 sender: &Sender<SyncMessage>,
818 mut chunked: ChunkedChanges<I>,
819 actor_id: ActorId,
820 version: CrsqlDbVersion,
821 last_seq: CrsqlSeq,
822 ts: Timestamp,
823) -> eyre::Result<()> {
824 let mut max_buf_size = chunked.max_buf_size();
825 loop {
826 if sender.is_closed() {
827 eyre::bail!("sync message sender channel is closed");
828 }
829 match chunked.next() {
830 Some(Ok((changes, seqs))) => {
831 let start = Instant::now();
832
833 if changes.is_empty() && seqs.start() == CrsqlSeq(0) && seqs.end() == last_seq {
834 warn!(%actor_id, %version, "got an empty changes we should've had");
835 return Ok(());
836 } else {
837 sender.blocking_send(SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
838 actor_id,
839 changeset: Changeset::FullV2 {
840 actor_id,
841 version,
842 changes,
843 seqs,
844 last_seq,
845 ts,
846 },
847 })))?;
848 }
849
850 let elapsed = start.elapsed();
851
852 if elapsed > Duration::from_secs(5) {
853 eyre::bail!("time out: peer is too slow");
854 }
855
856 if elapsed > ADAPT_CHUNK_SIZE_THRESHOLD {
857 if max_buf_size <= MIN_CHANGES_BYTES_PER_MESSAGE {
858 eyre::bail!("time out: peer is too slow even after reducing throughput");
859 }
860
861 max_buf_size /= 2;
862 debug!("adapting max chunk size to {max_buf_size} bytes");
863
864 chunked.set_max_buf_size(max_buf_size);
865 }
866 }
867 Some(Err(e)) => {
868 error!(%actor_id, %version, "could not process changes to send via sync: {e}");
869 break;
870 }
871 None => {
872 break;
873 }
874 }
875 }
876
877 Ok(())
878}
879
880async fn process_sync(
881 pool: SplitPool,
882 bookie: Bookie,
883 sender: Sender<SyncMessage>,
884 recv: mpsc::Receiver<SyncRequestV1>,
885) -> eyre::Result<()> {
886 let chunked_reqs = ReceiverStream::new(recv).chunks_timeout(10, Duration::from_millis(500));
887 tokio::pin!(chunked_reqs);
888
889 let (job_tx, job_rx) = unbounded_channel();
890
891 let mut buf =
892 futures::StreamExt::buffer_unordered(
893 UnboundedReceiverStream::<
894 std::pin::Pin<Box<dyn Future<Output = eyre::Result<()>> + Send>>,
895 >::new(job_rx),
896 6,
897 );
898 loop {
899 enum Branch {
900 Reqs(Vec<Vec<(ActorId, Vec<SyncNeedV1>)>>),
901 }
902
903 let branch = tokio::select! {
904 maybe_reqs = chunked_reqs.next() => match maybe_reqs {
905 Some(reqs) => {
906 Branch::Reqs(reqs)
907 },
908 None => break
909 },
910 Some(res) = buf.next() => {
911 res?;
912 continue;
913 },
914 else => {
915 break;
916 }
917 };
918
919 match branch {
920 Branch::Reqs(reqs) => {
921 let agg = reqs
922 .into_iter()
923 .flatten()
924 .chunk_by(|req| req.0)
925 .into_iter()
926 .map(|(actor_id, reqs)| (actor_id, reqs.flat_map(|(_, reqs)| reqs).collect()))
927 .collect::<Vec<(ActorId, Vec<SyncNeedV1>)>>();
928
929 for (actor_id, needs) in agg {
930 let booked = bookie
931 .read::<&str, _>("process_sync get actor", None)
932 .await
933 .get(&actor_id)
934 .cloned();
935 let booked = match booked {
936 Some(b) => b,
937 None => continue,
938 };
939 let booked_read = booked
940 .read::<&str, _>("process_sync check needs", None)
941 .await;
942
943 for need in needs {
944 match &need {
945 SyncNeedV1::Full { versions } => {
946 if versions.clone().all(|v| {
947 booked_read.needed().contains(&v)
948 || booked_read.last().is_some_and(|max| v > max)
949 }) {
950 continue;
951 }
952 }
953 SyncNeedV1::Partial { version, .. } => {
954 if booked_read.needed().contains(version)
955 || booked_read.last().is_some_and(|max| *version > max)
956 {
957 continue;
958 }
959 }
960 SyncNeedV1::Empty { ts } => {
961 debug!("received empty need with ts: {ts:?}");
962 }
963 }
964
965 let pool = pool.clone();
966 let sender = sender.clone();
967
968 let fut = Box::pin(async move {
969 let mut conn = pool.read().await?;
970
971 block_in_place(|| handle_need(&mut conn, actor_id, need, &sender))?;
972
973 Ok(())
974 });
975
976 if job_tx.send(fut).is_err() {
977 eyre::bail!("could not send into job channel");
978 }
979 }
980 }
981 }
982 }
983 }
984 debug!("done w/ sync server loop");
985
986 drop(job_tx);
987
988 let _: () = buf.try_collect().await?;
989
990 debug!("done processing sync state");
991
992 Ok(())
993}
994
995fn encode_sync_msg(
996 codec: &mut LengthDelimitedCodec,
997 encode_buf: &mut BytesMut,
998 send_buf: &mut BytesMut,
999 msg: SyncMessage,
1000) -> Result<(), SyncSendError> {
1001 msg.write_to_stream(encode_buf.writer())
1002 .map_err(SyncMessageEncodeError::from)?;
1003
1004 let data = encode_buf.split().freeze();
1005 trace!("encoded sync message, len: {}", data.len());
1006 codec.encode(data, send_buf)?;
1007 Ok(())
1008}
1009
1010pub async fn encode_write_bipayload_msg(
1011 codec: &mut LengthDelimitedCodec,
1012 encode_buf: &mut BytesMut,
1013 send_buf: &mut BytesMut,
1014 msg: BiPayload,
1015 write: &mut SendStream,
1016) -> Result<(), BiPayloadSendError> {
1017 encode_bipayload_msg(codec, encode_buf, send_buf, msg)?;
1018
1019 write_buf(send_buf, write)
1020 .await
1021 .map_err(BiPayloadSendError::from)
1022}
1023
1024fn encode_bipayload_msg(
1025 codec: &mut LengthDelimitedCodec,
1026 encode_buf: &mut BytesMut,
1027 send_buf: &mut BytesMut,
1028 msg: BiPayload,
1029) -> Result<(), BiPayloadEncodeError> {
1030 msg.write_to_stream(encode_buf.writer())
1031 .map_err(BiPayloadEncodeError::from)?;
1032
1033 codec.encode(encode_buf.split().freeze(), send_buf)?;
1034 Ok(())
1035}
1036
1037async fn encode_write_sync_msg(
1038 codec: &mut LengthDelimitedCodec,
1039 encode_buf: &mut BytesMut,
1040 send_buf: &mut BytesMut,
1041 msg: SyncMessage,
1042 write: &mut SendStream,
1043) -> Result<(), SyncSendError> {
1044 encode_sync_msg(codec, encode_buf, send_buf, msg)?;
1045
1046 write_buf(send_buf, write)
1047 .await
1048 .map_err(SyncSendError::from)
1049}
1050
1051#[tracing::instrument(skip_all, fields(buf_size = send_buf.len()), err)]
1052async fn write_buf(send_buf: &mut BytesMut, write: &mut SendStream) -> Result<(), WriteError> {
1053 let len = send_buf.len();
1054 write.write_chunk(send_buf.split().freeze()).await?;
1055 counter!("corro.sync.chunk.sent.bytes").increment(len as u64);
1056
1057 Ok(())
1058}
1059
1060#[tracing::instrument(skip(read), fields(buf_size = tracing::field::Empty), err)]
1061pub async fn read_sync_msg<R: Stream<Item = std::io::Result<BytesMut>> + Unpin>(
1062 read: &mut R,
1063) -> Result<Option<SyncMessage>, SyncRecvError> {
1064 match read.next().await {
1065 Some(buf_res) => match buf_res {
1066 Ok(mut buf) => {
1067 counter!("corro.sync.chunk.recv.bytes").increment(buf.len() as u64);
1068 tracing::Span::current().record("buf_size", buf.len());
1069 match SyncMessage::from_buf(&mut buf) {
1070 Ok(msg) => Ok(Some(msg)),
1071 Err(e) => Err(SyncRecvError::from(e)),
1072 }
1073 }
1074 Err(e) => Err(SyncRecvError::from(e)),
1075 },
1076 None => Ok(None),
1077 }
1078}
1079
1080#[tracing::instrument(skip_all, err)]
1081pub async fn parallel_sync(
1082 agent: &Agent,
1083 transport: &Transport,
1084 members: Vec<(ActorId, SocketAddr)>,
1085 our_sync_state: SyncStateV1,
1086) -> Result<usize, SyncError> {
1087 trace!(
1088 self_actor_id = %agent.actor_id(),
1089 "parallel syncing w/ {}",
1090 members
1091 .iter()
1092 .map(|(actor_id, _)| actor_id.to_string())
1093 .collect::<Vec<_>>()
1094 .join(", ")
1095 );
1096
1097 let mut trace_ctx = SyncTraceContextV1::default();
1098 opentelemetry::global::get_text_map_propagator(|prop| {
1099 prop.inject_context(&tracing::Span::current().context(), &mut trace_ctx)
1100 });
1101
1102 let results = FuturesUnordered::from_iter(members.iter().map(|(actor_id, addr)| {
1103 let trace_ctx = trace_ctx.clone();
1104 async {
1105 (
1106 *actor_id,
1107 *addr,
1108 async {
1109 let mut codec = LengthDelimitedCodec::builder().max_frame_length(100 * 1_024 * 1_024).new_codec();
1110 let mut send_buf = BytesMut::new();
1111 let mut encode_buf = BytesMut::new();
1112
1113 let actor_id = *actor_id;
1114 let (mut tx, rx) = transport.open_bi(*addr).await?;
1115 let mut read = FramedRead::new(rx, LengthDelimitedCodec::builder().max_frame_length(100 * 1_024 * 1_024).new_codec());
1116
1117 encode_write_bipayload_msg(
1118 &mut codec,
1119 &mut encode_buf,
1120 &mut send_buf,
1121 BiPayload::V1 {data: BiPayloadV1::SyncStart {actor_id: agent.actor_id(), trace_ctx}, cluster_id: agent.cluster_id()},
1122 &mut tx,
1123 ).instrument(info_span!("write_sync_start"))
1124 .await?;
1125
1126 trace!(%actor_id, self_actor_id = %agent.actor_id(), "sent start payload");
1127
1128 encode_write_sync_msg(
1129 &mut codec,
1130 &mut encode_buf,
1131 &mut send_buf,
1132 SyncMessage::V1(SyncMessageV1::Clock(agent.clock().new_timestamp().into())),
1133 &mut tx,
1134 ).instrument(info_span!("write_sync_clock"))
1135 .await?;
1136
1137 trace!(%actor_id, self_actor_id = %agent.actor_id(), "sent clock payload");
1138 tx.flush().instrument(info_span!("quic_flush")).await.map_err(SyncSendError::from)?;
1139
1140 trace!(%actor_id, self_actor_id = %agent.actor_id(), "flushed sync payloads");
1141
1142 let their_sync_state = match timeout(Duration::from_secs(2), read_sync_msg(&mut read)).instrument(info_span!("read_sync_state")).await.map_err(SyncRecvError::from)?? {
1143 Some(SyncMessage::V1(SyncMessageV1::State(state))) => state,
1144 Some(SyncMessage::V1(SyncMessageV1::Rejection(rejection))) => {
1145 return Err(rejection.into())
1146 }
1147 Some(_) => return Err(SyncRecvError::ExpectedSyncState.into()),
1148 None => return Err(SyncRecvError::UnexpectedEndOfStream.into()),
1149 };
1150 trace!(%actor_id, self_actor_id = %agent.actor_id(), "read state payload: {their_sync_state:?}");
1151
1152 match timeout(Duration::from_secs(2), read_sync_msg(&mut read)).instrument(info_span!("read_sync_clock")).await.map_err(SyncRecvError::from)?? {
1153 Some(SyncMessage::V1(SyncMessageV1::Clock(ts))) => {
1154 match agent.update_clock_with_timestamp(actor_id, ts) {
1155 Ok(()) => (),
1156 Err(e) => {
1157 warn!("could not update clock from actor {actor_id}: {e}");
1158 }
1159 }
1160 },
1161 Some(_) => return Err(SyncRecvError::ExpectedClockMessage.into()),
1162 None => return Err(SyncRecvError::UnexpectedEndOfStream.into()),
1163 }
1164 trace!(%actor_id, self_actor_id = %agent.actor_id(), "read clock payload");
1165
1166 counter!("corro.sync.client.member", "id" => actor_id.to_string(), "addr" => addr.to_string()).increment(1);
1167
1168 let needs = our_sync_state.compute_available_needs(&their_sync_state);
1169
1170 debug!(%actor_id, self_actor_id = %agent.actor_id(), "computed needs: {:?}, their_sync_state: {:?}", needs, their_sync_state);
1171
1172 Ok::<_, SyncError>((needs, tx, read))
1173 }.await
1174 )
1175 }.instrument(info_span!("sync_client_handshake", %actor_id, %addr))
1176 }))
1177 .collect::<Vec<(ActorId, SocketAddr, Result<_, SyncError>)>>()
1178 .await;
1179
1180 debug!("collected member needs and such!");
1181
1182 assert_sometimes!(true, "Corrosion initializes sync with other nodes");
1183 #[allow(clippy::manual_try_fold)]
1184 let syncers = results
1185 .into_iter()
1186 .fold(Ok(vec![]), |agg, (actor_id, addr, res)| match res {
1187 Ok((needs, tx, read)) => {
1188 let mut v = agg.unwrap_or_default();
1189 v.push((actor_id, addr, needs, tx, read));
1190 Ok(v)
1191 }
1192 Err(e) => {
1193 counter!(
1194 "corro.sync.client.handshake.errors",
1195 "actor_id" => actor_id.to_string(),
1196 "addr" => addr.to_string(),
1197 "error" => e.to_string()
1198 )
1199 .increment(1);
1200 match agg {
1201 Ok(v) if !v.is_empty() => Ok(v),
1202 _ => Err(e),
1203 }
1204 }
1205 })?;
1206
1207 let len = syncers.len();
1208
1209 let (readers, mut servers) = {
1210 syncers.into_iter().fold(
1211 (Vec::with_capacity(len), Vec::with_capacity(len)),
1212 |(mut readers, mut servers), (actor_id, addr, needs, tx, read)| {
1213 if needs.is_empty() {
1214 trace!(%actor_id, "no needs!");
1215 return (readers, servers);
1216 }
1217 readers.push((actor_id, read));
1218
1219 trace!(%actor_id, "needs: {needs:?}");
1220
1221
1222 debug!(%actor_id, %addr, "needs len: {}", needs.values().map(|needs| needs.iter().map(|need| match need {
1223 SyncNeedV1::Full {versions} => versions.len(),
1224 SyncNeedV1::Partial {..} => 0,
1225 SyncNeedV1::Empty {..} => 0,
1226 }).sum::<usize>()).sum::<usize>());
1227
1228 let actor_needs = needs
1229 .into_iter()
1230 .flat_map(|(actor_id, needs)| {
1231 let needs: Vec<_> = needs
1232 .into_iter()
1233 .flat_map(|need| match need {
1234 SyncNeedV1::Full { versions } => CrsqlDbVersion::chunked_iter(versions, 10)
1236 .map(|versions| SyncNeedV1::Full { versions })
1237 .collect(),
1238
1239 need => vec![need],
1240 })
1241 .collect();
1242
1243 needs
1244 .into_iter()
1245 .map(|need| (actor_id, need))
1246 .collect::<Vec<_>>()
1247 })
1248 .collect::<VecDeque<_>>();
1249
1250 servers.push((
1251 actor_id,
1252 addr,
1253 actor_needs,
1254 tx,
1255 ));
1256
1257 (readers, servers)
1258 },
1259 )
1260 };
1261
1262 if readers.is_empty() && servers.is_empty() {
1263 return Ok(0);
1264 }
1265
1266 tokio::spawn(async move {
1267 let mut codec = LengthDelimitedCodec::builder().max_frame_length(100 * 1_024 * 1_024).new_codec();
1269 let mut send_buf = BytesMut::new();
1270 let mut encode_buf = BytesMut::new();
1271
1272 let mut req_full: HashMap<ActorId, RangeInclusiveSet<CrsqlDbVersion>> = HashMap::new();
1274
1275 let mut req_partials: HashMap<(ActorId, CrsqlDbVersion), RangeInclusiveSet<CrsqlSeq>> = HashMap::new();
1277
1278 let start = Instant::now();
1279
1280 loop {
1281 if servers.is_empty() {
1282 break;
1283 }
1284 let mut next_servers = Vec::with_capacity(servers.len());
1285 'servers: for (server_actor_id, addr, mut needs, mut tx) in servers {
1286 if needs.is_empty() {
1287 continue;
1288 }
1289
1290 let mut drained = 0;
1291
1292 while drained < 10 {
1293 let (actor_id, need) = match needs.pop_back() {
1294 Some(popped) => popped,
1295 None => {
1296 break;
1297 }
1298 };
1299
1300 drained += 1;
1301
1302 let actual_needs = match need {
1303 SyncNeedV1::Full { versions } => {
1304 let range = req_full.entry(actor_id).or_default();
1305
1306 let mut new_versions =
1307 RangeInclusiveSet::from_iter(std::iter::once(versions.into()));
1308
1309 for overlap in range.overlapping(&versions.into()) {
1311 new_versions.remove(overlap.clone());
1312 }
1313
1314 if new_versions.is_empty() {
1315 continue;
1316 }
1317
1318 new_versions
1319 .into_iter()
1320 .map(|versions| {
1321 range.insert(versions.clone());
1322 SyncNeedV1::Full { versions: versions.into() }
1323 })
1324 .collect()
1325 }
1326 SyncNeedV1::Partial { version, seqs } => {
1327 let range = req_partials.entry((actor_id, version)).or_default();
1328 let mut new_seqs =
1329 RangeInclusiveSet::from_iter(seqs.iter().map(|s| s.into()));
1330
1331 for seqs in seqs {
1332 for overlap in range.overlapping(&seqs.into()) {
1333 new_seqs.remove(overlap.clone());
1334 }
1335 }
1336
1337 if new_seqs.is_empty() {
1338 continue;
1339 }
1340
1341 vec![SyncNeedV1::Partial {
1342 version,
1343 seqs: new_seqs
1344 .into_iter()
1345 .map(|seqs| {
1346 range.insert(seqs.clone());
1347 CrsqlSeqRange::from(seqs)
1348 })
1349 .collect(),
1350 }]
1351 }
1352 need => {vec![need]},
1353 };
1354
1355 if actual_needs.is_empty() {
1356 warn!(%server_actor_id, %actor_id, %addr, "nothing to send!");
1357 continue;
1358 }
1359
1360 let req_len = actual_needs.len();
1361
1362 if let Err(e) = encode_sync_msg(
1363 &mut codec,
1364 &mut encode_buf,
1365 &mut send_buf,
1366 SyncMessage::V1(SyncMessageV1::Request(vec![(actor_id, actual_needs)])),
1367 ) {
1368 error!(%server_actor_id, %actor_id, %addr, "could not encode sync request: {e} (elapsed: {:?})", start.elapsed());
1369 continue 'servers;
1370 }
1371
1372 counter!("corro.sync.client.req.sent", "actor_id" => server_actor_id.to_string()).increment(req_len as u64);
1373 }
1374
1375 if !send_buf.is_empty() {
1376 if let Err(e) = write_buf(&mut send_buf, &mut tx).await {
1377 error!(%server_actor_id, %addr, "could not write sync requests: {e} (elapsed: {:?})", start.elapsed());
1378 continue;
1379 }
1380 } else {
1381 tokio::task::yield_now().await;
1383 }
1384
1385 if needs.is_empty() {
1386 if let Err(e) = tx.finish() {
1387 warn!("could not finish stream while sending sync requests: {e}");
1388 }
1389 debug!(%server_actor_id, %addr, "done trying to sync w/ actor after {:?}", start.elapsed());
1390 continue;
1391 }
1392
1393 next_servers.push((server_actor_id, addr, needs, tx));
1394 }
1395 servers = next_servers;
1396 }
1397 }.instrument(info_span!("send_sync_requests")));
1398
1399 let counts = FuturesUnordered::from_iter(readers.into_iter().map(|(actor_id, mut read)| {
1401 let tx_changes = agent.tx_changes().clone();
1402
1403 async move {
1404 let mut count = 0;
1405 loop {
1406 match read_sync_msg(&mut read).await {
1407 Ok(None) => {
1408 break;
1409 }
1410 Err(e) => {
1411 error!(%actor_id, "sync recv error: {e}");
1412 break;
1413 }
1414 Ok(Some(msg)) => match msg {
1415 SyncMessage::V1(SyncMessageV1::Changeset(change)) => {
1416 let changes_len = cmp::max(change.len(), 1);
1417 count += changes_len;
1419 counter!("corro.sync.changes.recv", "actor_id" => actor_id.to_string())
1420 .increment(changes_len as u64);
1421
1422 debug!(
1423 "handling versions: {:?}, seqs: {:?}, len: {changes_len} (is_empty: {}) from {actor_id}",
1424 change.versions(),
1425 change.seqs(),
1426 change.is_empty()
1427 );
1428 if change.is_empty_set() {
1430 warn!("received empty set from {actor_id}, ignoring");
1431 continue;
1432 }
1433
1434 tx_changes
1435 .send((change, ChangeSource::Sync))
1436 .await
1437 .map_err(|_| SyncRecvError::ChangesChannelClosed)?;
1438 }
1439 SyncMessage::V1(SyncMessageV1::Request(_)) => {
1440 warn!("received sync request message unexpectedly, ignoring");
1441 continue;
1442 }
1443 SyncMessage::V1(SyncMessageV1::State(_)) => {
1444 warn!("received sync state message unexpectedly, ignoring");
1445 continue;
1446 }
1447 SyncMessage::V1(SyncMessageV1::Clock(_)) => {
1448 warn!("received sync clock message unexpectedly, ignoring");
1449 continue;
1450 }
1451 SyncMessage::V1(SyncMessageV1::Rejection(rejection)) => {
1452 return Err(rejection.into())
1453 }
1454 },
1455 }
1456 }
1457
1458 debug!(%actor_id, %count, "done reading sync messages");
1459
1460 Ok((actor_id, count))
1461 }
1462 .instrument(info_span!("read_sync_requests_responses", %actor_id))
1463 }))
1464 .collect::<Vec<Result<(ActorId, usize), SyncError>>>()
1465 .await;
1466
1467 let mut members = agent.members().write();
1468 let ts = Timestamp::from(agent.clock().new_timestamp());
1469 for res in counts.iter() {
1470 match res {
1471 Err(e) => error!("could not properly recv from peer: {e}"),
1472 Ok((actor_id, _)) => {
1473 members.update_sync_ts(actor_id, ts);
1474 }
1475 };
1476 }
1477
1478 Ok(counts
1479 .into_iter()
1480 .flat_map(|res| res.map(|i| i.1))
1481 .sum::<usize>())
1482}
1483
1484#[tracing::instrument(skip(agent, bookie, their_actor_id, read, write), fields(actor_id = %their_actor_id), err)]
1485pub async fn serve_sync(
1486 agent: &Agent,
1487 bookie: &Bookie,
1488 their_actor_id: ActorId,
1489 trace_ctx: SyncTraceContextV1,
1490 cluster_id: ClusterId,
1491 mut read: FramedRead<RecvStream, LengthDelimitedCodec>,
1492 mut write: SendStream,
1493) -> Result<usize, SyncError> {
1494 let context =
1495 opentelemetry::global::get_text_map_propagator(|propagator| propagator.extract(&trace_ctx));
1496 tracing::Span::current().set_parent(context);
1497
1498 debug!(actor_id = %their_actor_id, self_actor_id = %agent.actor_id(), "received sync request");
1499 let mut codec = LengthDelimitedCodec::builder()
1500 .max_frame_length(100 * 1_024 * 1_024)
1501 .new_codec();
1502 let mut send_buf = BytesMut::new();
1503 let mut encode_buf = BytesMut::new();
1504
1505 if cluster_id != agent.cluster_id() {
1506 encode_write_sync_msg(
1507 &mut codec,
1508 &mut encode_buf,
1509 &mut send_buf,
1510 SyncMessage::V1(SyncMessageV1::Rejection(SyncRejectionV1::DifferentCluster)),
1511 &mut write,
1512 )
1513 .instrument(info_span!("write_rejection_cluster_id"))
1514 .await?;
1515 return Ok(0);
1516 }
1517
1518 match read_sync_msg(&mut read)
1520 .instrument(info_span!("read_peer_clock"))
1521 .await?
1522 {
1523 Some(SyncMessage::V1(SyncMessageV1::Clock(ts))) => match their_actor_id.try_into() {
1524 Ok(id) => {
1525 if let Err(e) = agent
1526 .clock()
1527 .update_with_timestamp(&uhlc::Timestamp::new(ts.to_ntp64(), id))
1528 {
1529 warn!("could not update clock from actor {their_actor_id}: {e}");
1530 }
1531 }
1532 Err(e) => {
1533 error!("could not convert ActorId to uhlc ID: {e}");
1534 }
1535 },
1536 Some(_) => return Err(SyncRecvError::ExpectedClockMessage.into()),
1537 None => return Err(SyncRecvError::UnexpectedEndOfStream.into()),
1538 }
1539
1540 trace!(actor_id = %their_actor_id, self_actor_id = %agent.actor_id(), "read clock");
1541
1542 let _permit = match agent.limits().sync.try_acquire() {
1543 Ok(permit) => permit,
1544 Err(_) => {
1545 encode_write_sync_msg(
1547 &mut codec,
1548 &mut encode_buf,
1549 &mut send_buf,
1550 SyncMessage::V1(SyncMessageV1::Rejection(
1551 SyncRejectionV1::MaxConcurrencyReached,
1552 )),
1553 &mut write,
1554 )
1555 .instrument(info_span!("write_sync_rejection"))
1556 .await?;
1557 return Ok(0);
1558 }
1559 };
1560
1561 let sync_state = generate_sync(bookie, agent.actor_id()).await;
1562
1563 encode_write_sync_msg(
1565 &mut codec,
1566 &mut encode_buf,
1567 &mut send_buf,
1568 SyncMessage::V1(SyncMessageV1::State(sync_state)),
1569 &mut write,
1570 )
1571 .instrument(info_span!("write_sync_state"))
1572 .await?;
1573
1574 trace!(actor_id = %their_actor_id, self_actor_id = %agent.actor_id(), "sent sync state");
1575
1576 encode_write_sync_msg(
1578 &mut codec,
1579 &mut encode_buf,
1580 &mut send_buf,
1581 SyncMessage::V1(SyncMessageV1::Clock(agent.clock().new_timestamp().into())),
1582 &mut write,
1583 )
1584 .instrument(info_span!("write_sync_clock"))
1585 .await?;
1586 trace!(actor_id = %their_actor_id, self_actor_id = %agent.actor_id(), "sent clock");
1587
1588 write
1590 .flush()
1591 .instrument(info_span!("quic_flush"))
1592 .await
1593 .map_err(SyncSendError::from)?;
1594 trace!(actor_id = %their_actor_id, self_actor_id = %agent.actor_id(), "flushed sync messages");
1595
1596 let (tx_need, rx_need) = mpsc::channel(1024);
1597 let (tx, mut rx) = mpsc::channel::<SyncMessage>(256);
1598
1599 tokio::spawn(
1600 process_sync(agent.pool().clone(), bookie.clone(), tx, rx_need)
1601 .instrument(info_span!("process_sync"))
1602 .inspect_err(|e| error!("could not process sync request: {e}")),
1603 );
1604
1605 let (send_res, recv_res) = tokio::join!(
1606 async move {
1607 let mut count = 0;
1608
1609 let mut check_buf = tokio::time::interval(Duration::from_secs(1));
1610
1611 let mut stopped = false;
1612
1613 loop {
1614 tokio::select! {
1615 biased;
1616
1617 stopped_res = write.stopped() => {
1618 match stopped_res {
1619 Ok(code) => {
1620 debug!(actor_id = %their_actor_id, "send stream was stopped by peer, code: {code:?}");
1621 },
1622 Err(e) => {
1623 warn!(actor_id = %their_actor_id, "error waiting for stop from stream: {e}");
1624 }
1625 }
1626 stopped = true;
1627 break;
1628 },
1629
1630 maybe_msg = rx.recv() => match maybe_msg {
1631 Some(msg) => {
1632 if let SyncMessage::V1(SyncMessageV1::Changeset(change)) = &msg {
1633 count += change.len();
1634 }
1635 encode_sync_msg(&mut codec, &mut encode_buf, &mut send_buf, msg)?;
1636
1637 if send_buf.len() >= 16 * 1024 {
1638 write_buf(&mut send_buf, &mut write).await.map_err(SyncSendError::from)?;
1639 }
1640 },
1641 None => {
1642 break;
1643 }
1644 },
1645
1646 _ = check_buf.tick() => {
1647 if !send_buf.is_empty() {
1648 write_buf(&mut send_buf, &mut write).await.map_err(SyncSendError::from)?;
1649 }
1650 }
1651 }
1652 }
1653
1654 if !stopped {
1655 if !send_buf.is_empty() {
1656 write_buf(&mut send_buf, &mut write).await.map_err(SyncSendError::from)?;
1657 }
1658
1659 if let Err(e) = write.finish() {
1660 warn!("could not properly finish QUIC send stream: {e}");
1661 }
1662 }
1663
1664 debug!(actor_id = %agent.actor_id(), "done writing sync messages (count: {count})");
1665
1666 counter!("corro.sync.changes.sent", "actor_id" => their_actor_id.to_string()).increment(count as u64);
1667
1668 Ok::<_, SyncError>(count)
1669 }.instrument(info_span!("process_versions_to_send")),
1670 async move {
1671 let mut count = 0;
1672
1673 loop {
1674 match read_sync_msg(&mut read).await {
1675 Ok(None) => {
1676 break;
1677 }
1678 Err(e) => {
1679 error!("sync recv error: {e}");
1680 break;
1681 }
1682 Ok(Some(msg)) => match msg {
1683 SyncMessage::V1(SyncMessageV1::Request(req)) => {
1684 trace!(actor_id = %their_actor_id, self_actor_id = %agent.actor_id(), "read req: {req:?}");
1685 count += req
1686 .iter()
1687 .map(|(_, needs)| {
1688 needs.iter().map(|need| need.count()).sum::<usize>()
1689 })
1690 .sum::<usize>();
1691 tx_need
1692 .send(req)
1693 .await
1694 .map_err(|_| SyncRecvError::RequestsChannelClosed)?;
1695 }
1696 SyncMessage::V1(SyncMessageV1::Changeset(_)) => {
1697 warn!(actor_id = %their_actor_id, "received sync changeset message unexpectedly, ignoring");
1698 continue;
1699 }
1700 SyncMessage::V1(SyncMessageV1::State(_)) => {
1701 warn!(actor_id = %their_actor_id, "received sync state message unexpectedly, ignoring");
1702 continue;
1703 }
1704 SyncMessage::V1(SyncMessageV1::Clock(_)) => {
1705 warn!(actor_id = %their_actor_id, "received sync clock message more than once, ignoring");
1706 continue;
1707 }
1708 SyncMessage::V1(SyncMessageV1::Rejection(rejection)) => {
1709 return Err(rejection.into())
1710 }
1711 },
1712 }
1713 }
1714
1715 debug!(actor_id = %agent.actor_id(), "done reading sync messages");
1716
1717 counter!("corro.sync.requests.recv", "actor_id" => their_actor_id.to_string()).increment(count as u64);
1718
1719 Ok(count)
1720 }.instrument(info_span!("process_version_requests"))
1721 );
1722
1723 if let Err(e) = send_res {
1724 error!(actor_id = %their_actor_id, "could not complete serving sync due to a send side error: {e}");
1725 }
1726
1727 recv_res
1728}
1729
1730#[cfg(test)]
1731mod tests {
1732 use crate::api::public::api_v1_transactions;
1733 use axum::{Extension, Json};
1734 use camino::Utf8PathBuf;
1735 use hyper::StatusCode;
1736 use klukai_tests::TEST_SCHEMA;
1737 use klukai_tests::launch_test_agent;
1738 use klukai_types::api::Statement;
1739 use klukai_types::base::CrsqlDbVersion;
1740 use klukai_types::tripwire::Tripwire;
1741 use klukai_types::{
1742 api::{ColumnName, TableName},
1743 broadcast::ChangesetPerTable,
1744 config::{Config, DEFAULT_GOSSIP_CLIENT_ADDR, TlsConfig},
1745 dbsr, dbvr,
1746 pubsub::pack_columns,
1747 tls::{generate_ca, generate_client_cert, generate_server_cert},
1748 };
1749 use rand::{Rng, RngCore};
1750 use tempfile::TempDir;
1751
1752 use crate::{
1753 agent::{process_multiple_changes, setup},
1754 api::public::{TimeoutParams, api_v1_db_schema},
1755 };
1756
1757 use super::*;
1758
1759 #[tokio::test(flavor = "multi_thread")]
1760 async fn test_sync_changes_order() -> eyre::Result<()> {
1761 _ = tracing_subscriber::fmt::try_init();
1762
1763 let (tripwire, _tripwire_worker, _tripwire_tx) = Tripwire::new_simple();
1764
1765 let ta1 = launch_test_agent(|conf| conf.build(), tripwire.clone()).await?;
1766
1767 let versions_range = 1..=100;
1769 for i in versions_range.clone() {
1770 let (status_code, body) = api_v1_transactions(
1771 Extension(ta1.agent.clone()),
1772 axum::extract::Query(TimeoutParams { timeout: None }),
1773 axum::Json(vec![Statement::WithParams(
1774 "INSERT OR REPLACE INTO testsblob (id,text) VALUES (?,?)".into(),
1775 vec![format!("service-id-{i}").into(), "service-name".into()],
1776 )]),
1777 )
1778 .await;
1779 assert_eq!(status_code, StatusCode::OK);
1780
1781 let version = body.0.version.unwrap();
1782 assert_eq!(version, i);
1783 }
1784
1785 let dir = tempfile::tempdir()?;
1786
1787 let (ta2_agent, mut ta2_opts) = setup(
1788 Config::builder()
1789 .db_path(dir.path().join("corrosion.db").display().to_string())
1790 .gossip_addr("127.0.0.1:0".parse()?)
1791 .api_addr("127.0.0.1:0".parse()?)
1792 .build()?,
1793 tripwire,
1794 )
1795 .await?;
1796
1797 let members = vec![(ta1.agent.actor_id(), ta1.agent.gossip_addr())];
1798 let _ = parallel_sync(&ta2_agent, &ta2_opts.transport, members, Default::default()).await?;
1799
1800 for i in versions_range.rev() {
1801 let changes = tokio::time::timeout(Duration::from_secs(5), ta2_opts.rx_changes.recv())
1802 .await?
1803 .unwrap();
1804 assert_eq!(
1805 changes.0.versions(),
1806 (CrsqlDbVersion(i)..=CrsqlDbVersion(i)).into()
1807 );
1808 }
1809
1810 Ok(())
1811 }
1812
1813 #[tokio::test(flavor = "multi_thread")]
1814 async fn test_handle_need() -> eyre::Result<()> {
1815 _ = tracing_subscriber::fmt::try_init();
1816
1817 let (tripwire, _tripwire_worker, _tripwire_tx) = Tripwire::new_simple();
1818 let tx_timeout = Duration::from_secs(60);
1819
1820 let dir = tempfile::tempdir()?;
1821
1822 let (agent, _agent_options) = setup(
1823 Config::builder()
1824 .db_path(dir.path().join("corrosion.db").display().to_string())
1825 .gossip_addr("127.0.0.1:0".parse()?)
1826 .api_addr("127.0.0.1:0".parse()?)
1827 .build()?,
1828 tripwire,
1829 )
1830 .await?;
1831
1832 let (status_code, _res) =
1833 api_v1_db_schema(Extension(agent.clone()), Json(vec![TEST_SCHEMA.to_owned()])).await;
1834
1835 assert_eq!(status_code, StatusCode::OK);
1836
1837 let actor_id = ActorId(uuid::Uuid::new_v4());
1838
1839 let ts = agent.clock().new_timestamp().into();
1840
1841 let change1 = Change {
1842 table: TableName("tests".into()),
1843 pk: pack_columns(&[1i64.into()])?,
1844 cid: ColumnName("text".into()),
1845 val: "one".into(),
1846 col_version: 1,
1847 db_version: CrsqlDbVersion(1),
1848 seq: CrsqlSeq(0),
1849 site_id: actor_id.to_bytes(),
1850 cl: 1,
1851 };
1852
1853 let change2 = Change {
1854 table: TableName("tests".into()),
1855 pk: pack_columns(&[2i64.into()])?,
1856 cid: ColumnName("text".into()),
1857 val: "two".into(),
1858 col_version: 1,
1859 db_version: CrsqlDbVersion(2),
1860 seq: CrsqlSeq(0),
1861 site_id: actor_id.to_bytes(),
1862 cl: 1,
1863 };
1864 let changes_two: ChangesetPerTable = changeset_with_changes(vec![change2.clone()]);
1865
1866 let bookie = Bookie::new(Default::default());
1867
1868 process_multiple_changes(
1869 agent.clone(),
1870 bookie.clone(),
1871 vec![
1872 (
1873 ChangeV1 {
1874 actor_id,
1875 changeset: Changeset::Full {
1876 version: CrsqlDbVersion(1),
1877 changes: vec![change1.clone()],
1878 seqs: dbsr!(0, 0),
1879 last_seq: CrsqlSeq(0),
1880 ts,
1881 },
1882 },
1883 ChangeSource::Sync,
1884 Instant::now(),
1885 ),
1886 (
1887 ChangeV1 {
1888 actor_id,
1889 changeset: Changeset::Full {
1890 version: CrsqlDbVersion(2),
1891 changes: vec![change2.clone()],
1892 seqs: dbsr!(0, 0),
1893 last_seq: CrsqlSeq(0),
1894 ts,
1895 },
1896 },
1897 ChangeSource::Sync,
1898 Instant::now(),
1899 ),
1900 ],
1901 tx_timeout,
1902 )
1903 .await?;
1904
1905 let booked = bookie
1906 .read::<&str, _>("test", None)
1907 .await
1908 .get(&actor_id)
1909 .cloned()
1910 .unwrap();
1911
1912 {
1913 let read = booked.read::<&str, _>("test", None).await;
1914
1915 assert!(read.contains_version(&CrsqlDbVersion(1)));
1916 assert!(read.contains_version(&CrsqlDbVersion(2)));
1917 }
1918
1919 {
1920 let (tx, mut rx) = mpsc::channel(5);
1921 let mut conn = agent.pool().read().await?;
1922
1923 {
1924 let mut prepped = conn.prepare("SELECT * FROM crsql_changes;")?;
1925 let mut rows = prepped.query([])?;
1926
1927 loop {
1928 let row = rows.next()?;
1929 if row.is_none() {
1930 break;
1931 }
1932
1933 println!("ROW: {row:?}");
1934 }
1935 }
1936
1937 block_in_place(|| {
1938 handle_need(
1939 &mut conn,
1940 actor_id,
1941 SyncNeedV1::Full {
1942 versions: dbvr!(1, 1),
1943 },
1944 &tx,
1945 )
1946 })?;
1947
1948 let changes_one = changeset_with_changes(vec![change1.clone()]);
1949
1950 let msg = rx.recv().await.unwrap();
1951 assert_eq!(
1952 msg,
1953 SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
1954 actor_id,
1955 changeset: Changeset::FullV2 {
1956 actor_id,
1957 version: CrsqlDbVersion(1),
1958 changes: changes_one,
1959 seqs: dbsr!(0, 0),
1960 last_seq: CrsqlSeq(0),
1961 ts,
1962 }
1963 }))
1964 );
1965
1966 println!("gonna handle a partial need...");
1967 block_in_place(|| {
1968 handle_need(
1969 &mut conn,
1970 actor_id,
1971 SyncNeedV1::Partial {
1972 version: CrsqlDbVersion(2),
1973 seqs: vec![dbsr!(0, 0)],
1974 },
1975 &tx,
1976 )
1977 })?;
1978
1979 let msg = rx.recv().await.unwrap();
1980 assert_eq!(
1981 msg,
1982 SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
1983 actor_id,
1984 changeset: Changeset::FullV2 {
1985 actor_id,
1986 version: CrsqlDbVersion(2),
1987 changes: changes_two.clone(),
1988 seqs: dbsr!(0, 0),
1989 last_seq: CrsqlSeq(0),
1990 ts,
1991 }
1992 }))
1993 );
1994 }
1995
1996 let change3 = Change {
1997 table: TableName("tests".into()),
1998 pk: pack_columns(&[1i64.into()])?,
1999 cid: ColumnName("text".into()),
2000 val: "one override".into(),
2001 col_version: 2,
2002 db_version: CrsqlDbVersion(3),
2003 seq: CrsqlSeq(0),
2004 site_id: actor_id.to_bytes(),
2005 cl: 1,
2006 };
2007 let changes_three = changeset_with_changes(vec![change3.clone()]);
2008
2009 process_multiple_changes(
2010 agent.clone(),
2011 bookie.clone(),
2012 vec![(
2013 ChangeV1 {
2014 actor_id,
2015 changeset: Changeset::Full {
2016 version: CrsqlDbVersion(3),
2017 changes: vec![change3.clone()],
2018 seqs: dbsr!(0, 0),
2019 last_seq: CrsqlSeq(0),
2020 ts,
2021 },
2022 },
2023 ChangeSource::Sync,
2024 Instant::now(),
2025 )],
2026 tx_timeout,
2027 )
2028 .await?;
2029
2030 {
2031 let (tx, mut rx) = mpsc::channel(5);
2032 let mut conn = agent.pool().read().await?;
2033
2034 {
2035 let mut prepped = conn.prepare("SELECT * FROM crsql_changes;")?;
2036 let mut rows = prepped.query([])?;
2037
2038 loop {
2039 let row = rows.next()?;
2040 if row.is_none() {
2041 break;
2042 }
2043
2044 println!("ROW: {row:?}");
2045 }
2046 }
2047
2048 block_in_place(|| {
2049 handle_need(
2050 &mut conn,
2051 actor_id,
2052 SyncNeedV1::Partial {
2053 version: CrsqlDbVersion(1),
2054 seqs: vec![dbsr!(0, 0)],
2055 },
2056 &tx,
2057 )
2058 })?;
2059
2060 let msg = rx.recv().await.unwrap();
2061 if let SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2062 actor_id: actor,
2063 changeset,
2064 ..
2065 })) = msg
2066 {
2067 assert_eq!(actor_id, actor);
2068 assert!(changeset.is_empty());
2069 assert_eq!(changeset.versions(), dbvr!(1, 1));
2070 } else {
2071 panic!("{msg:?} doesn't contain an empty changeset");
2072 }
2073 }
2074
2075 {
2076 let (tx, mut rx) = mpsc::channel(5);
2077 let mut conn = agent.pool().read().await?;
2078
2079 block_in_place(|| {
2080 handle_need(
2081 &mut conn,
2082 actor_id,
2083 SyncNeedV1::Full {
2084 versions: dbvr!(1, 6),
2085 },
2086 &tx,
2087 )
2088 })?;
2089
2090 let changes_three = changeset_with_changes(vec![change3.clone()]);
2091 let msg = rx.recv().await.unwrap();
2092 assert_eq!(
2093 msg,
2094 SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2095 actor_id,
2096 changeset: Changeset::FullV2 {
2097 actor_id,
2098 version: CrsqlDbVersion(3),
2099 changes: changes_three,
2100 seqs: dbsr!(0, 0),
2101 last_seq: CrsqlSeq(0),
2102 ts,
2103 }
2104 }))
2105 );
2106
2107 let msg = rx.recv().await.unwrap();
2108 assert_eq!(
2109 msg,
2110 SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2111 actor_id,
2112 changeset: Changeset::FullV2 {
2113 actor_id,
2114 version: CrsqlDbVersion(2),
2115 changes: changes_two,
2116 seqs: dbsr!(0, 0),
2117 last_seq: CrsqlSeq(0),
2118 ts,
2119 }
2120 }))
2121 );
2122
2123 let msg = rx.recv().await.unwrap();
2124 if let SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2125 actor_id: actor,
2126 changeset,
2127 ..
2128 })) = msg
2129 {
2130 assert_eq!(actor_id, actor);
2131 assert!(changeset.is_empty());
2132 assert_eq!(changeset.versions(), dbvr!(1, 1));
2133 } else {
2134 panic!("{msg:?} doesn't contain an empty changeset");
2135 }
2136 }
2137
2138 let change4 = Change {
2140 table: TableName("tests".into()),
2141 pk: pack_columns(&[2i64.into()])?,
2142 cid: ColumnName("text".into()),
2143 val: "two override".into(),
2144 col_version: 2,
2145 db_version: CrsqlDbVersion(4),
2146 seq: CrsqlSeq(0),
2147 site_id: actor_id.to_bytes(),
2148 cl: 1,
2149 };
2150
2151 process_multiple_changes(
2152 agent.clone(),
2153 bookie.clone(),
2154 vec![(
2155 ChangeV1 {
2156 actor_id,
2157 changeset: Changeset::Full {
2158 version: CrsqlDbVersion(4),
2159 changes: vec![change4.clone()],
2160 seqs: dbsr!(0, 0),
2161 last_seq: CrsqlSeq(0),
2162 ts,
2163 },
2164 },
2165 ChangeSource::Sync,
2166 Instant::now(),
2167 )],
2168 tx_timeout,
2169 )
2170 .await?;
2171
2172 let mut rng = rand::rng();
2173
2174 let len = 10u64;
2175 let mut last_seq = 0u64;
2176
2177 let changes = (0u64..len)
2178 .map(|i| {
2179 let mut b = vec![0u8; 16];
2180 rng.fill_bytes(&mut b);
2181
2182 let int: i64 = rng.sample(rand::distr::StandardUniform);
2183 let float: f64 = rng.sample(rand::distr::StandardUniform);
2184 [
2185 ("int", int.into()),
2186 ("float", float.into()),
2187 ("blob", b.into()),
2188 ]
2189 .into_iter()
2190 .map(|(col, val)| {
2191 let c = Change {
2192 table: TableName("wide".into()),
2193 pk: pack_columns(&[i.to_be_bytes().to_vec().into(), i.to_string().into()])
2194 .unwrap(),
2195 cid: ColumnName(col.into()),
2196 val,
2197 col_version: 1,
2198 db_version: CrsqlDbVersion(5),
2199 seq: CrsqlSeq(last_seq),
2200 site_id: actor_id.to_bytes(),
2201 cl: 1,
2202 };
2203 last_seq += 1;
2204 c
2205 })
2206 .collect::<Vec<_>>()
2207 })
2208 .collect::<Vec<_>>();
2209
2210 last_seq -= 1;
2211 let last_seq = CrsqlSeq(last_seq);
2212
2213 let changes_v1 = changes
2214 .iter()
2215 .map(|changes| {
2216 let seqs = changes.first().unwrap().seq..=changes.last().unwrap().seq;
2217 ChangeV1 {
2218 actor_id,
2219 changeset: Changeset::Full {
2220 version: CrsqlDbVersion(5),
2221 changes: changes.clone(),
2222 seqs: seqs.into(),
2223 last_seq,
2224 ts,
2225 },
2226 }
2227 })
2228 .collect::<Vec<_>>();
2229
2230 process_multiple_changes(
2231 agent.clone(),
2232 bookie.clone(),
2233 changes_v1
2234 .iter()
2235 .map(|change| (change.clone(), ChangeSource::Sync, Instant::now()))
2236 .collect(),
2237 tx_timeout,
2238 )
2239 .await?;
2240
2241 {
2242 let (tx, mut rx) = mpsc::channel(5);
2243 let mut conn = agent.pool().read().await?;
2244
2245 block_in_place(|| {
2246 handle_need(
2247 &mut conn,
2248 actor_id,
2249 SyncNeedV1::Full {
2250 versions: dbvr!(1, 1000),
2251 },
2252 &tx,
2253 )
2254 })?;
2255
2256 let msg = rx.recv().await.unwrap();
2257 let changes_four = changeset_with_changes(vec![change4.clone()]);
2258 assert_eq!(
2259 msg,
2260 SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2261 actor_id,
2262 changeset: Changeset::FullV2 {
2263 actor_id,
2264 version: CrsqlDbVersion(4),
2265 changes: changes_four,
2266 seqs: dbsr!(0, 0),
2267 last_seq: CrsqlSeq(0),
2268 ts,
2269 }
2270 }))
2271 );
2272
2273 let msg = rx.recv().await.unwrap();
2274 assert_eq!(
2275 msg,
2276 SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2277 actor_id,
2278 changeset: Changeset::FullV2 {
2279 actor_id,
2280 version: CrsqlDbVersion(3),
2281 changes: changes_three,
2282 seqs: dbsr!(0, 0),
2283 last_seq: CrsqlSeq(0),
2284 ts,
2285 }
2286 }))
2287 );
2288
2289 let changes_five = changeset_with_changes(changes.iter().flatten().cloned().collect());
2290 let msg = rx.recv().await.unwrap();
2291 assert_eq!(
2292 msg,
2293 SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2294 actor_id,
2295 changeset: Changeset::FullV2 {
2296 actor_id,
2297 version: CrsqlDbVersion(5),
2298 changes: changes_five,
2299 seqs: (CrsqlSeq(0)..=last_seq).into(),
2300 last_seq,
2301 ts,
2302 }
2303 }))
2304 );
2305
2306 let msg = rx.recv().await.unwrap();
2307 if let SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2308 actor_id: actor,
2309 changeset,
2310 ..
2311 })) = msg
2312 {
2313 assert_eq!(actor_id, actor);
2314 assert!(changeset.is_empty());
2315 assert_eq!(changeset.versions(), dbvr!(1, 2));
2316 } else {
2317 panic!("{msg:?} doesn't contain an empty changeset");
2318 }
2319 }
2320
2321 {
2322 let (tx, mut rx) = mpsc::channel(5);
2323 let mut conn = agent.pool().read().await?;
2324
2325 block_in_place(|| {
2326 handle_need(
2327 &mut conn,
2328 actor_id,
2329 SyncNeedV1::Partial {
2330 version: CrsqlDbVersion(5),
2331 seqs: vec![dbsr!(4, 7)],
2332 },
2333 &tx,
2334 )
2335 })?;
2336
2337 let filtered_changes = changes
2338 .iter()
2339 .flatten()
2340 .enumerate()
2341 .filter_map(|(i, c)| {
2342 if (4..=7).contains(&i) {
2343 Some(c.clone())
2344 } else {
2345 None
2346 }
2347 })
2348 .collect();
2349 let changes_five_partial = changeset_with_changes(filtered_changes);
2350 let msg = rx.recv().await.unwrap();
2351 assert_eq!(
2352 msg,
2353 SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2354 actor_id,
2355 changeset: Changeset::FullV2 {
2356 actor_id,
2357 version: CrsqlDbVersion(5),
2358 changes: changes_five_partial,
2359 seqs: dbsr!(4, 7),
2360 last_seq,
2361 ts,
2362 }
2363 }))
2364 );
2365
2366 block_in_place(|| {
2367 handle_need(
2368 &mut conn,
2369 actor_id,
2370 SyncNeedV1::Partial {
2371 version: CrsqlDbVersion(5),
2372 seqs: vec![dbsr!(2, 2), dbsr!(15, 24)],
2373 },
2374 &tx,
2375 )
2376 })?;
2377
2378 let filtered_changes = changes
2379 .iter()
2380 .flatten()
2381 .enumerate()
2382 .filter_map(|(i, c)| if i == 2 { Some(c.clone()) } else { None })
2383 .collect();
2384 let changes_five_partial2 = changeset_with_changes(filtered_changes);
2385 let msg = rx.recv().await.unwrap();
2386 assert_eq!(
2387 msg,
2388 SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2389 actor_id,
2390 changeset: Changeset::FullV2 {
2391 actor_id,
2392 version: CrsqlDbVersion(5),
2393 changes: changes_five_partial2,
2394 seqs: dbsr!(2, 2),
2395 last_seq,
2396 ts,
2397 }
2398 }))
2399 );
2400
2401 let filtered_changes = changes
2402 .iter()
2403 .flatten()
2404 .enumerate()
2405 .filter_map(|(i, c)| {
2406 if (15..=24).contains(&i) {
2407 Some(c.clone())
2408 } else {
2409 None
2410 }
2411 })
2412 .collect();
2413 let changes_five_partial3 = changeset_with_changes(filtered_changes);
2414 let msg = rx.recv().await.unwrap();
2415 assert_eq!(
2416 msg,
2417 SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2418 actor_id,
2419 changeset: Changeset::FullV2 {
2420 actor_id,
2421 version: CrsqlDbVersion(5),
2422 changes: changes_five_partial3,
2423 seqs: dbsr!(15, 24),
2424 last_seq,
2425 ts,
2426 }
2427 }))
2428 );
2429 }
2430
2431 Ok(())
2432 }
2433
2434 fn changeset_with_changes(changes: Vec<Change>) -> ChangesetPerTable {
2435 let mut changeset = ChangesetPerTable::default();
2436 for change in changes {
2437 changeset.insert(change);
2438 }
2439 changeset
2440 }
2441
2442 #[tokio::test]
2443 async fn test_mutual_tls() -> eyre::Result<()> {
2444 let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
2445
2446 let (ca_cert, ca_key_pair) = generate_ca()?;
2447 let (_server_cert, server_cert_signed, server_key_pair) = generate_server_cert(
2448 &ca_cert.pem(),
2449 &ca_key_pair.serialize_pem(),
2450 "127.0.0.1".parse()?,
2451 )?;
2452
2453 let (_client_cert, client_cert_signed, client_key_pair) =
2454 generate_client_cert(&ca_cert.pem(), &ca_key_pair.serialize_pem())?;
2455
2456 let tmpdir = TempDir::new()?;
2457 let base_path = Utf8PathBuf::from(tmpdir.path().display().to_string());
2458
2459 let cert_file = base_path.join("cert.pem");
2460 let key_file = base_path.join("cert.key");
2461 let ca_file = base_path.join("ca.pem");
2462
2463 let client_cert_file = base_path.join("client-cert.pem");
2464 let client_key_file = base_path.join("client-cert.key");
2465
2466 tokio::fs::write(&cert_file, &server_cert_signed).await?;
2467 tokio::fs::write(&key_file, server_key_pair.serialize_pem()).await?;
2468
2469 tokio::fs::write(&ca_file, ca_cert.pem()).await?;
2470
2471 tokio::fs::write(&client_cert_file, &client_cert_signed).await?;
2472 tokio::fs::write(&client_key_file, client_key_pair.serialize_pem()).await?;
2473
2474 let gossip_config = GossipConfig {
2475 bind_addr: "127.0.0.1:0".parse()?,
2476 client_addr: DEFAULT_GOSSIP_CLIENT_ADDR,
2477 external_addr: None,
2478 bootstrap: vec![],
2479 tls: Some(TlsConfig {
2480 cert_file,
2481 key_file,
2482 ca_file: Some(ca_file),
2483 client: Some(TlsClientConfig {
2484 cert_file: client_cert_file,
2485 key_file: client_key_file,
2486 }),
2487 insecure: false,
2488 }),
2489 idle_timeout_secs: 30,
2490 plaintext: false,
2491 max_mtu: None,
2492 disable_gso: false,
2493 };
2494
2495 let server = gossip_server_endpoint(&gossip_config).await?;
2496 let addr = server.local_addr()?;
2497
2498 let client = gossip_client_endpoint(&gossip_config).await?;
2499
2500 let res = tokio::try_join!(
2501 async {
2502 Ok(client
2503 .connect(addr, &addr.ip().to_string())
2504 .unwrap()
2505 .await
2506 .unwrap())
2507 },
2508 async {
2509 let conn = match server.accept().await {
2510 None => eyre::bail!("None accept!"),
2511 Some(connecting) => connecting.await.unwrap(),
2512 };
2513 Ok(conn)
2514 }
2515 )?;
2516
2517 let client_conn = res.0;
2518
2519 let client_conn_peer_id = client_conn
2520 .peer_identity()
2521 .unwrap()
2522 .downcast::<Vec<rustls_pki_types::CertificateDer>>()
2523 .unwrap();
2524
2525 let mut server_cert_signed_buf =
2526 std::io::Cursor::new(server_cert_signed.as_bytes().to_vec());
2527
2528 assert_eq!(
2529 client_conn_peer_id[0].as_ref(),
2530 {
2531 let result: Result<Vec<_>, _> =
2532 rustls_pemfile::certs(&mut server_cert_signed_buf).collect();
2533 result?
2534 }[0]
2535 .as_ref()
2536 );
2537
2538 let server_conn = res.1;
2539
2540 let server_conn_peer_id = server_conn
2541 .peer_identity()
2542 .unwrap()
2543 .downcast::<Vec<rustls_pki_types::CertificateDer>>()
2544 .unwrap();
2545
2546 let mut client_cert_signed_buf =
2547 std::io::Cursor::new(client_cert_signed.as_bytes().to_vec());
2548
2549 assert_eq!(
2550 server_conn_peer_id[0].as_ref(),
2551 {
2552 let result: Result<Vec<_>, _> =
2553 rustls_pemfile::certs(&mut client_cert_signed_buf).collect();
2554 result?
2555 }[0]
2556 .as_ref()
2557 );
2558
2559 Ok(())
2560 }
2561}