Skip to content

Commit

Permalink
feat: register history
Browse files Browse the repository at this point in the history
  • Loading branch information
grumbach committed Jan 30, 2025
1 parent a602280 commit 2b85e25
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 44 deletions.
18 changes: 17 additions & 1 deletion autonomi/src/client/high_level/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,21 @@

pub mod data;
pub mod files;
pub mod register;
pub mod vault;

/// Registers are a mutable piece of data on the Network.
/// They can be read by anyone and updated only by the register owner.
/// Each entry is signed by the owner and all value history is kept on the Network.
/// They can be accessed on the Network using the RegisterAddress which is effectively the hash of the owner's [`crate::PublicKey`].
/// This means there can only be one Register per key.
///
/// The underlying structure of registers is a graph, where each version is a new [`crate::GraphEntry`]
/// Each entry is linked to the previous entry and to the next entry, like a doubly linked list
/// For fast access to the current register value, a [`crate::Pointer`] to the last entry always keeps track of the latest version
/// ```no_run
/// chain of GraphEntry: [register root] <-> [value2] <-> [value3] <-> [latest value]
/// ^
/// |
/// a Pointer to the latest version: [pointer to head]
/// ```
pub mod register;
74 changes: 74 additions & 0 deletions autonomi/src/client/high_level/register/history.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2025 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use ant_networking::{GetRecordError, NetworkError};

use crate::client::data_types::graph::{GraphEntryAddress, GraphError};
use crate::client::high_level::register::{
PublicKey, RegisterAddress, RegisterError, RegisterValue,
};
use crate::client::key_derivation::MainPubkey;
use crate::client::Client;

/// A handle to the register history
pub struct RegisterHistory {
client: Client,
register_owner: PublicKey,
current: GraphEntryAddress,
}

impl RegisterHistory {
fn new(client: Client, register_owner: PublicKey, root: GraphEntryAddress) -> Self {
Self {
client,
register_owner,
current: root,
}
}

/// Fetch and go to the next register value from the history
/// Returns `Ok(None)` when we reached the end
pub async fn next(&mut self) -> Result<Option<RegisterValue>, RegisterError> {
let (entry, next_derivation) = match self
.client
.register_get_graph_entry_and_next_derivation_index(&self.current)
.await
{
Ok(res) => res,
Err(RegisterError::GraphError(GraphError::Network(NetworkError::GetRecordError(
GetRecordError::RecordNotFound,
)))) => return Ok(None),
Err(e) => return Err(e),
};
let next_entry_pk: PublicKey = MainPubkey::from(self.register_owner)
.derive_key(&next_derivation)
.into();
self.current = GraphEntryAddress::from_owner(next_entry_pk);
Ok(Some(entry.content))
}

/// Get all the register values from the history
pub async fn collect(&mut self) -> Result<Vec<RegisterValue>, RegisterError> {
let mut values = Vec::new();
while let Some(value) = self.next().await? {
values.push(value);
}
Ok(values)
}
}

impl Client {
/// Get the register history, starting from the root to the latest entry
/// This returns a [`RegisterHistory`] that can be use to get the register values from the history
/// [`RegisterHistory::next`] can be used to get the values one by one
/// [`RegisterHistory::collect`] can be used to get all the register values from the history
pub fn register_history(&self, addr: &RegisterAddress) -> RegisterHistory {
let graph_entry_addr = addr.to_underlying_graph_root();
RegisterHistory::new(self.clone(), addr.owner, graph_entry_addr)
}
}
107 changes: 74 additions & 33 deletions autonomi/src/client/high_level/register/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use crate::client::data_types::graph::{GraphContent, GraphEntry, GraphError};
use crate::client::data_types::graph::{GraphContent, GraphEntry, GraphEntryAddress, GraphError};
use crate::client::data_types::pointer::{PointerAddress, PointerError, PointerTarget};
use crate::client::key_derivation::{DerivationIndex, MainPubkey, MainSecretKey};
use crate::client::payment::PaymentOption;
Expand All @@ -19,6 +19,10 @@ use serde::{Deserialize, Serialize};
use thiserror::Error;
use xor_name::XorName;

mod history;

pub use history::RegisterHistory;

/// A Register is addressed at a [`RegisterAddress`] which is in fact the owner's [`PublicKey`].
/// There can only be one register stored at [`PublicKey`].
/// Any data stored in the register is stored as is, without encryption or modifications.
Expand All @@ -41,11 +45,19 @@ impl RegisterAddress {
pub fn owner(&self) -> PublicKey {
self.owner
}

/// To underlying graph representation
pub fn to_underlying_graph_root(&self) -> GraphEntryAddress {
GraphEntryAddress::from_owner(self.owner)
}
}

/// The value of a register: a 32 bytes array (same as [`GraphContent`])
pub type RegisterValue = GraphContent;

/// The size of a register value: 32 bytes
pub const REGISTER_VALUE_SIZE: usize = size_of::<RegisterValue>();

#[derive(Error, Debug)]
pub enum RegisterError {
#[error("Underlying GraphError: {0}")]
Expand All @@ -62,6 +74,10 @@ pub enum RegisterError {
Corrupt(String),
#[error("Register cannot be updated as it does not exist, please create it first or wait for it to be created")]
CannotUpdateNewRegister,
#[error(
"Invalid register value length: {0}, expected something within {REGISTER_VALUE_SIZE} bytes"
)]
InvalidRegisterValueLength(usize),
}

/// Hard coded derivation index for the register head pointer
Expand All @@ -79,7 +95,18 @@ impl Client {
main_key.derive_key(&derivation_index).into()
}

/// Create a new register
/// Create a new [`RegisterValue`] from bytes, make sure the bytes are not longer than [`REGISTER_VALUE_SIZE`]
pub fn register_value_from_bytes(bytes: &[u8]) -> Result<RegisterValue, RegisterError> {
if bytes.len() > REGISTER_VALUE_SIZE {
return Err(RegisterError::InvalidRegisterValueLength(bytes.len()));
}
let mut content: RegisterValue = [0; REGISTER_VALUE_SIZE];
content[..bytes.len()].copy_from_slice(bytes);
Ok(content)
}

/// Create a new register with an initial value
/// Note that two payments are required, one for the underlying [`GraphEntry`] and one for the [`crate::Pointer`]
pub async fn register_create(
&self,
owner: &SecretKey,
Expand Down Expand Up @@ -152,40 +179,13 @@ impl Client {

// get the next derivation index from the current head entry
debug!("Getting register head graph entry at {graph_entry_addr:?}");
let parent_entry = match self.graph_entry_get(*graph_entry_addr).await {
Ok(e) => e,
Err(GraphError::Fork(entries)) => {
warn!("Forked register, multiple entries found: {entries:?}, choosing the one with the smallest derivation index for the next entry");
let (entry_by_smallest_derivation, _) = entries
.into_iter()
.filter_map(|e| {
e.descendants
.iter()
.map(|d| d.1)
.min()
.map(|derivation| (e, derivation))
})
.min_by(|a, b| a.1.cmp(&b.1))
.ok_or(RegisterError::Corrupt(format!(
"No descendants found for FORKED entry at {graph_entry_addr:?}"
)))?;
entry_by_smallest_derivation
}
Err(err) => return Err(err.into()),
};
let new_derivation =
parent_entry
.descendants
.iter()
.map(|d| d.1)
.min()
.ok_or(RegisterError::Corrupt(format!(
"No descendants found for entry at {graph_entry_addr:?}"
)))?;
let (parent_entry, new_derivation) = self
.register_get_graph_entry_and_next_derivation_index(graph_entry_addr)
.await?;

// create a new entry with the new value
let main_key = MainSecretKey::new(owner.clone());
let new_key = main_key.derive_key(&DerivationIndex::from_bytes(new_derivation));
let new_key = main_key.derive_key(&new_derivation);
let parents = vec![parent_entry.owner];
let next_derivation = DerivationIndex::random(&mut rand::thread_rng());
let next_pk = main_key.public_key().derive_key(&next_derivation);
Expand Down Expand Up @@ -268,6 +268,47 @@ impl Client {
pk.derive_key(&DerivationIndex::from_bytes(REGISTER_HEAD_DERIVATION_INDEX));
pointer_pk.into()
}

/// Get underlying register graph entry and next derivation index
/// In normal circumstances, there is only one entry with one descendant, yielding ONE entry and ONE derivation index
/// In the case of a fork or a corrupt register, the smallest derivation index among all the entries descendants is chosen
/// We chose here to deal with the errors instead of erroring out to allow users to solve Fork and Corrupt issues by updating the register
async fn register_get_graph_entry_and_next_derivation_index(
&self,
graph_entry_addr: &GraphEntryAddress,
) -> Result<(GraphEntry, DerivationIndex), RegisterError> {
let entry = match self.graph_entry_get(*graph_entry_addr).await {
Ok(e) => e,
Err(GraphError::Fork(entries)) => {
warn!("Forked register, multiple entries found: {entries:?}, choosing the one with the smallest derivation index for the next entry");
let (entry_by_smallest_derivation, _) = entries
.into_iter()
.filter_map(|e| {
e.descendants
.iter()
.map(|d| d.1)
.min()
.map(|derivation| (e, derivation))
})
.min_by(|a, b| a.1.cmp(&b.1))
.ok_or(RegisterError::Corrupt(format!(
"No descendants found for FORKED entry at {graph_entry_addr:?}"
)))?;
entry_by_smallest_derivation
}
Err(err) => return Err(err.into()),
};
let new_derivation =
entry
.descendants
.iter()
.map(|d| d.1)
.min()
.ok_or(RegisterError::Corrupt(format!(
"No descendants found for entry at {graph_entry_addr:?}"
)))?;
Ok((entry, DerivationIndex::from_bytes(new_derivation)))
}
}

mod tests {
Expand Down
75 changes: 65 additions & 10 deletions autonomi/tests/registers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@

use ant_logging::LogBuilder;
use autonomi::{
client::{
payment::PaymentOption,
register::{RegisterAddress, RegisterValue},
},
client::{payment::PaymentOption, register::RegisterAddress},
graph::GraphError,
register::RegisterError,
Client,
Expand All @@ -29,8 +26,7 @@ async fn registers_usage() -> Result<()> {
let main_key = bls::SecretKey::random();

let register_key = Client::register_key_from_name(&main_key, "register1");
let mut content: RegisterValue = [0; 32];
content[..13].copy_from_slice(b"Hello, World!");
let content = Client::register_value_from_bytes(b"Hello, World!")?;
let cost = client.register_cost(&register_key.public_key()).await?;
println!("register cost: {cost}");

Expand All @@ -54,8 +50,7 @@ async fn registers_usage() -> Result<()> {
assert_eq!(value, content);

// update the register
let mut new_content: RegisterValue = [0; 32];
new_content[..26].copy_from_slice(b"any 32 bytes of fresh data");
let new_content = Client::register_value_from_bytes(b"any 32 bytes of fresh data")?;
let cost = client
.register_update(&register_key, new_content, PaymentOption::from(&wallet))
.await?;
Expand All @@ -80,8 +75,7 @@ async fn registers_errors() -> Result<()> {
let main_key = bls::SecretKey::random();

let register_key = Client::register_key_from_name(&main_key, "register1");
let mut content: RegisterValue = [0; 32];
content[..13].copy_from_slice(b"Hello, World!");
let content = Client::register_value_from_bytes(b"Hello, World!")?;
let cost = client.register_cost(&register_key.public_key()).await?;
println!("register cost: {cost}");

Expand Down Expand Up @@ -127,3 +121,64 @@ async fn registers_errors() -> Result<()> {

Ok(())
}

#[tokio::test]
#[serial]
async fn test_register_history() -> Result<()> {
let client = Client::init_local().await?;
let wallet = get_funded_wallet();
let main_key = bls::SecretKey::random();
let register_key = Client::register_key_from_name(&main_key, "history_test");
let content1 = Client::register_value_from_bytes(b"Massive")?;
let (_cost, addr) = client
.register_create(
&register_key,
content1,
PaymentOption::from(&wallet),
PaymentOption::from(&wallet),
)
.await?;

// let the network replicate the register
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;

let mut history = client.register_history(&addr);
let first = history.next().await?;
assert_eq!(first, Some(content1));
let second = history.next().await?;
assert_eq!(second, None);

let content2 = Client::register_value_from_bytes(b"Array")?;
client
.register_update(&register_key, content2, PaymentOption::from(&wallet))
.await?;

// let the network replicate the updates
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;

let all = client.register_history(&addr).collect().await?;
assert_eq!(all.len(), 2);
assert_eq!(all[0], content1);
assert_eq!(all[1], content2);

let content3 = Client::register_value_from_bytes(b"Internet")?;
client
.register_update(&register_key, content3, PaymentOption::from(&wallet))
.await?;
let content4 = Client::register_value_from_bytes(b"Disk")?;
client
.register_update(&register_key, content4, PaymentOption::from(&wallet))
.await?;

// let the network replicate the updates
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;

let all = client.register_history(&addr).collect().await?;
assert_eq!(all.len(), 4);
assert_eq!(all[0], content1);
assert_eq!(all[1], content2);
assert_eq!(all[2], content3);
assert_eq!(all[3], content4);

Ok(())
}

0 comments on commit 2b85e25

Please sign in to comment.