From f39c4b5c571336d8070cd826ed39dbe83792f3d6 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 22 Sep 2025 22:29:53 -0400 Subject: [PATCH] add limits to schemas. first stab at dht transactions --- veilid-core/proto/veilid.capnp | 43 +++++++++++++++++-- .../src/veilid_api/types/dht/schema/dflt.rs | 4 ++ .../src/veilid_api/types/dht/schema/mod.rs | 3 ++ .../src/veilid_api/types/dht/schema/smpl.rs | 36 +++++++++++++--- 4 files changed, 77 insertions(+), 9 deletions(-) diff --git a/veilid-core/proto/veilid.capnp b/veilid-core/proto/veilid.capnp index 052e0948..7f599c95 100644 --- a/veilid-core/proto/veilid.capnp +++ b/veilid-core/proto/veilid.capnp @@ -328,9 +328,10 @@ struct OperationGetValueA @0xf97edb86a914d093 { struct OperationSetValueQ @0xb315a71cd3f555b3 { key @0 :OpaqueRecordKey; # DHT Key = Hash(ownerKeyKind) of: [ ownerKeyValue, schema ] - subkey @1 :Subkey; # the index of the subkey - value @2 :SignedValueData; # value or subvalue contents (older or equal seq number gets dropped) - descriptor @3 :SignedValueDescriptor; # optional: the descriptor if needed + transactionId @1 :UInt64; # optional: transaction id if inside a transaction + subkey @2 :Subkey; # the index of the subkey + value @3 :SignedValueData; # value or subvalue contents (older or equal seq number gets dropped) + descriptor @4 :SignedValueDescriptor; # optional: the descriptor if needed } struct OperationSetValueA @0xb5ff5b18c0d7b918 { @@ -376,6 +377,42 @@ struct OperationValueChanged @0xbf9d00e88fd96623 { value @4 :SignedValueData; # Optional: first value that changed (the rest can be gotten with getvalue) } + +enum TransactCommand @0xa841a757a9a7f946 { + begin @0; # start a new transaction + commit @1; # commit all operations + rollback @2; # roll back all operations +} + +struct OperationTransactValueQ @0xf8629eff87ac729d { + key @0 :OpaqueRecordKey; # key for record to transact on + command @1 :TransactCommand; # transaction command to execute + transactionId @2 :UInt64; # optional: transaction id if committing or rolling back + writer @3 :PublicKey; # the writer performing the transaction, can be the owner or a schema member + signature @4 :Signature; # signature of the writer, signature covers: key, writer, signature, count, watchId +} + +struct OperationTransactValueA @0xd2b5a46f55268aa4 { + accepted @0 :Bool; # true if the transaction was close enough to be accepted + transactionId @1 :UInt64; # optional: transaction id if successful, missing if operation failed + peers @2 :List(PeerInfo); # returned 'closer peer' information on either success or failure +} + +struct OperationSyncValueQ @0xee28e7a72302fef7 { + key @0 :OpaqueRecordKey; # key for record to sync + transactionId @1 :UInt64; # optional: transaction id if inside a transaction + seqs @2 :List(ValueSeqNum); # the list of subkey value sequence numbers in ascending order for each subkey + subkey @3 :Subkey; # optional: the index of the subkey + value @4 :SignedValueData; # optional: value or subvalue contents (older or equal seq number gets dropped) + descriptor @5 :SignedValueDescriptor; # optional: the descriptor if needed +} + +struct OperationSyncValueA @0xe42e5bb79e2f9009 { + seqs @0 :List(ValueSeqNum); # the list of subkey value sequence numbers in ascending order for each subkey + subkey @1 :Subkey; # optional: the index of the subkey + value @2 :SignedValueData; # optional: value or subvalue contents (older or equal seq number gets dropped) +} + struct OperationSupplyBlockQ @0xe0d00fd8091dd2e0 { blockId @0 :BlockId; # hash of the block we can supply routeId @1 :RouteId; # the private route endpoint for this block supplier diff --git a/veilid-core/src/veilid_api/types/dht/schema/dflt.rs b/veilid-core/src/veilid_api/types/dht/schema/dflt.rs index cc8c3673..bd9bcd85 100644 --- a/veilid-core/src/veilid_api/types/dht/schema/dflt.rs +++ b/veilid-core/src/veilid_api/types/dht/schema/dflt.rs @@ -29,6 +29,10 @@ impl DHTSchemaDFLT { if self.o_cnt == 0 { apibail_invalid_argument!("must have at least one subkey", "o_cnt", self.o_cnt); } + if self.o_cnt > DHTSchema::MAX_SUBKEY_COUNT { + apibail_invalid_argument!("too many subkeys", "o_cnt", self.o_cnt); + } + Ok(()) } diff --git a/veilid-core/src/veilid_api/types/dht/schema/mod.rs b/veilid-core/src/veilid_api/types/dht/schema/mod.rs index b81392d0..abbf8394 100644 --- a/veilid-core/src/veilid_api/types/dht/schema/mod.rs +++ b/veilid-core/src/veilid_api/types/dht/schema/mod.rs @@ -21,6 +21,9 @@ pub enum DHTSchema { } impl DHTSchema { + pub const MAX_WRITER_COUNT: usize = 256; + pub const MAX_SUBKEY_COUNT: usize = 1024; + pub fn dflt(o_cnt: u16) -> VeilidAPIResult { Ok(DHTSchema::DFLT(DHTSchemaDFLT::new(o_cnt)?)) } diff --git a/veilid-core/src/veilid_api/types/dht/schema/smpl.rs b/veilid-core/src/veilid_api/types/dht/schema/smpl.rs index 3cac3bdd..50e473a9 100644 --- a/veilid-core/src/veilid_api/types/dht/schema/smpl.rs +++ b/veilid-core/src/veilid_api/types/dht/schema/smpl.rs @@ -35,6 +35,7 @@ pub struct DHTSchemaSMPL { impl DHTSchemaSMPL { pub const FCC: [u8; 4] = *b"SMPL"; pub const FIXED_SIZE: usize = 6; + pub const MAX_MEMBER_COUNT: usize = 256; /// Make a schema pub fn new(o_cnt: u16, members: Vec) -> VeilidAPIResult { @@ -45,7 +46,13 @@ impl DHTSchemaSMPL { /// Validate the data representation pub fn validate(&self) -> VeilidAPIResult<()> { - let mut keycount = self.o_cnt as usize; + let mut subkey_count = self.o_cnt as usize; + let mut writer_count = 0; + if self.o_cnt > 0 { + writer_count += 1; + } + + let mut writers = HashSet::::new(); for m in &self.members { if m.m_key.len() != MEMBER_ID_LENGTH { apibail_invalid_argument!( @@ -54,14 +61,31 @@ impl DHTSchemaSMPL { m.m_key.len() ); } - keycount += m.m_cnt as usize; + if m.m_cnt > 0 { + writers.insert(m.m_key); + } + subkey_count += m.m_cnt as usize; } - if keycount == 0 { - apibail_invalid_argument!("must have at least one subkey", "keycount", keycount); + let member_count = self.members.len(); + if member_count > MAX_MEMBER_COUNT { + apibail_invalid_argument!("too many members", "member_count", writercount); } - if keycount > 65535 { - apibail_invalid_argument!("too many subkeys", "keycount", keycount); + + writer_count += writers.len(); + if writer_count > DHTSchema::MAX_WRITER_COUNT { + apibail_invalid_argument!("too many writers", "writer_count", writercount); + } + + if subkey_count == 0 { + apibail_invalid_argument!( + "must have at least one subkey", + "subkey_count", + subkey_count + ); + } + if subkey_count > DHTSchema::MAX_SUBKEY_COUNT { + apibail_invalid_argument!("too many subkeys", "subkey_count", subkey_count); } Ok(()) }