Skip to content

Commit 2d488c9

Browse files
committed
Fix Spark sync record key collisions
1 parent 43d10b4 commit 2d488c9

1 file changed

Lines changed: 64 additions & 7 deletions

File tree

orange-sdk/src/trusted_wallet/spark/spark_store.rs

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ use breez_sdk_spark::{
1313
Contact, DepositInfo, ListContactsRequest, Payment, PaymentDetails, PaymentMetadata,
1414
SetLnurlMetadataItem, StorageError, StorageListPaymentsRequest, UpdateDepositPayload,
1515
};
16+
use ldk_node::bitcoin::hashes::Hash;
17+
use ldk_node::bitcoin::hashes::sha256::Hash as Sha256;
18+
use ldk_node::bitcoin::hex::DisplayHex;
1619
use ldk_node::lightning::util::persist::KVSTORE_NAMESPACE_KEY_MAX_LEN;
1720
use ldk_node::lightning::util::persist::KVStore;
1821

@@ -43,9 +46,28 @@ fn sanitize_key(key: String) -> String {
4346
}
4447
}
4548

49+
fn sync_record_key(parts: &[&str]) -> String {
50+
let key = parts.iter().fold(String::new(), |mut key, part| {
51+
key.push_str(&part.len().to_string());
52+
key.push('_');
53+
key.push_str(part);
54+
key
55+
});
56+
57+
if key.len() > KVSTORE_NAMESPACE_KEY_MAX_LEN {
58+
format!("h_{}", Sha256::hash(key.as_bytes()).to_byte_array().as_hex())
59+
} else {
60+
key
61+
}
62+
}
63+
4664
/// Create a KV key from a RecordId.
4765
fn record_id_key(id: &RecordId) -> String {
48-
sanitize_key(format!("{}_{}", id.r#type, id.data_id))
66+
sync_record_key(&[&id.r#type, &id.data_id])
67+
}
68+
69+
fn incoming_record_key(record: &Record) -> String {
70+
sync_record_key(&[&record.id.r#type, &record.id.data_id, &record.revision.to_string()])
4971
}
5072

5173
/// Serialize a Record to JSON bytes.
@@ -664,10 +686,7 @@ impl breez_sdk_spark::Storage for SparkStore {
664686

665687
async fn insert_incoming_records(&self, records: Vec<Record>) -> Result<(), StorageError> {
666688
for record in records {
667-
let key = sanitize_key(format!(
668-
"{}_{}_{}",
669-
record.id.r#type, record.id.data_id, record.revision
670-
));
689+
let key = incoming_record_key(&record);
671690
let data = record_to_bytes(&record)?;
672691

673692
KVStore::write(
@@ -684,8 +703,7 @@ impl breez_sdk_spark::Storage for SparkStore {
684703
}
685704

686705
async fn delete_incoming_record(&self, record: Record) -> Result<(), StorageError> {
687-
let key =
688-
sanitize_key(format!("{}_{}_{}", record.id.r#type, record.id.data_id, record.revision));
706+
let key = incoming_record_key(&record);
689707
let _ = KVStore::remove(
690708
self.0.as_ref(),
691709
SPARK_PRIMARY_NAMESPACE,
@@ -779,3 +797,42 @@ impl breez_sdk_spark::Storage for SparkStore {
779797
Ok(())
780798
}
781799
}
800+
801+
#[cfg(test)]
802+
mod tests {
803+
use super::*;
804+
805+
fn record(record_type: &str, data_id: &str, revision: u64) -> Record {
806+
Record {
807+
id: RecordId::new(record_type.to_string(), data_id.to_string()),
808+
schema_version: "1".to_string(),
809+
data: HashMap::new(),
810+
revision,
811+
}
812+
}
813+
814+
#[test]
815+
fn record_id_key_distinguishes_underscore_in_type_vs_data_id() {
816+
let a = RecordId::new("foo".to_string(), "bar_baz".to_string());
817+
let b = RecordId::new("foo_bar".to_string(), "baz".to_string());
818+
819+
assert_ne!(record_id_key(&a), record_id_key(&b));
820+
}
821+
822+
#[test]
823+
fn incoming_record_key_distinguishes_underscore_in_type_vs_data_id() {
824+
let a = record("foo", "bar_baz", 7);
825+
let b = record("foo_bar", "baz", 7);
826+
827+
assert_ne!(incoming_record_key(&a), incoming_record_key(&b));
828+
}
829+
830+
#[test]
831+
fn oversized_keys_use_bounded_hash_key() {
832+
let id = RecordId::new("a".repeat(KVSTORE_NAMESPACE_KEY_MAX_LEN), "b".to_string());
833+
let key = record_id_key(&id);
834+
835+
assert!(key.len() <= KVSTORE_NAMESPACE_KEY_MAX_LEN);
836+
assert!(key.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-'));
837+
}
838+
}

0 commit comments

Comments
 (0)