Pointer
Pointers are native data types in the Autonomi Network that provide an indirection mechanism for referencing any data on the network.
Overview
Key-addressed: Network address is derived from a BLS public key
Generic target: Can reference any native data type including pointers
Pay-once, free updates: Unlimited updates for free after initial creation
Versioned & signed: Includes a version counter and cryptographically verifiable signatures
Owner-controlled: Only the owner can update the pointer
Data Structure
pub struct Pointer {
owner: PublicKey, // BLS public key that owns this pointer
counter: u64, // Version number that increases with each update
target: PointerTarget, // The address being pointed to
signature: Signature, // Cryptographic signature ensuring authenticity
}
pub enum PointerTarget {
ChunkAddress(ChunkAddress),
GraphEntryAddress(GraphEntryAddress),
PointerAddress(PointerAddress),
ScratchpadAddress(ScratchpadAddress),
}
Client Methods
pointer_get(address: &PointerAddress) -> Result<Pointer, PointerError>
Retrieves a pointer from the network by its address.
pointer_create(owner: &SecretKey, target: PointerTarget, payment_option: PaymentOption) -> Result<(AttoTokens, PointerAddress), PointerError>
Creates and uploads a new pointer for a payment. Returns the total cost and the pointer's address.
Note: Each public key can only have one pointer. If a pointer already exists at the address, this will fail.
pointer_update(owner: &SecretKey, target: PointerTarget) -> Result<(), PointerError>
Updates an existing pointer with a new target. This operation is free as the pointer was already paid for at creation.
Note: The pointer must exist before it can be updated. Only the latest version (highest counter) is kept on the network.
pointer_cost(key: &PublicKey) -> Result<AttoTokens, CostError>
Estimates the storage cost for creating a pointer.
pointer_put(pointer: Pointer, payment_option: PaymentOption) -> Result<(AttoTokens, PointerAddress), PointerError>
Manually uploads a pointer to the network for a payment. Returns the total cost and the pointer's address.
Warning: Only use this when you know what you are doing. For most use cases, prefer pointer_create
and pointer_update
.
pointer_check_existence(address: &PointerAddress) -> Result<bool, PointerError>
Checks if a pointer exists on the network. This is much faster than pointer_get
but may fail if called immediately after creating a pointer.
Error Types
pub enum PointerError {
PutError(PutError), // Failed to put pointer
GetError(GetError), // Failed to get pointer
Serialization, // Serialization error
Corrupt(String), // Pointer record corrupt
BadSignature, // Pointer signature is invalid
Pay(PayError), // Payment failure
Wallet(EvmWalletError), // Failed to retrieve wallet payment
InvalidQuote, // Received invalid quote from node
PointerAlreadyExists(PointerAddress), // Pointer already exists at address
CannotUpdateNewPointer, // Cannot update non-existent pointer
}
Code Examples
Basic Pointer Creation and Update
use autonomi::{Client, SecretKey, PublicKey};
use autonomi::client::payment::PaymentOption;
use autonomi::client::pointer::{Pointer, PointerTarget};
use autonomi::chunk::ChunkAddress;
use autonomi::AttoTokens;
use eyre::Result;
async fn basic_pointer_example() -> Result<()> {
// Initialize client and wallet
let client = Client::init_local().await?;
let wallet = get_funded_wallet();
let payment_option = PaymentOption::from(&wallet);
// Create a secret key for the pointer owner
let owner_key = SecretKey::random();
let public_key = owner_key.public_key();
// Create a target (e.g., a chunk address)
let chunk_addr = ChunkAddress::new(XorName::random(&mut rand::thread_rng()));
let target = PointerTarget::ChunkAddress(chunk_addr);
// Estimate the cost
let estimated_cost = client.pointer_cost(&public_key).await?;
println!("Estimated pointer cost: {estimated_cost}");
// Create the pointer
let (actual_cost, pointer_addr) = client
.pointer_create(&owner_key, target.clone(), payment_option)
.await?;
println!("Pointer created at {pointer_addr:?} with cost: {actual_cost}");
// Wait for replication
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
// Retrieve the pointer
let retrieved_pointer = client.pointer_get(&pointer_addr).await?;
println!("Retrieved pointer: {retrieved_pointer:?}");
// Verify the pointer signature
if retrieved_pointer.verify_signature() {
println!("Pointer signature is valid!");
}
// Update the pointer to point to itself (creating a self-reference)
let new_target = PointerTarget::PointerAddress(pointer_addr);
client.pointer_update(&owner_key, new_target.clone()).await?;
// Wait for replication
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
// Verify the update
let updated_pointer = client.pointer_get(&pointer_addr).await?;
assert_eq!(updated_pointer.counter(), 1); // Version should be incremented
assert_eq!(updated_pointer.target(), &new_target);
Ok(())
}
Pointer Update
async fn pointer_update_example() -> Result<()> {
let client = Client::init_local().await?;
let wallet = get_funded_wallet();
let payment_option = PaymentOption::from(&wallet);
// Create owner key
let key = SecretKey::random();
// Upload first target data
let data1 = b"First target data";
let (_, addr1) = client.data_put_public(data1, payment_option.clone()).await?;
let target1 = PointerTarget::ChunkAddress(ChunkAddress::from_content(data1));
// Create pointer pointing to first data
let (_, pointer_addr) = client
.pointer_create(&key, target1, payment_option.clone())
.await?;
println!("Created pointer at: {pointer_addr:?}");
// Wait for replication
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
// Upload second target data
let data2 = b"Second target data";
let target2 = PointerTarget::ChunkAddress(ChunkAddress::from_content(data2));
// Update pointer to point to second data
client.pointer_update(&key, target2).await?;
println!("Pointer updated successfully");
// Wait for replication
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
// Retrieve and verify the updated pointer
let final_pointer = client.pointer_get(&pointer_addr).await?;
println!("Updated pointer counter: {}", final_pointer.counter());
Ok(())
}
Pointer Chain
async fn pointer_chain_example() -> Result<()> {
let client = Client::init_local().await?;
let wallet = get_funded_wallet();
let payment_option = PaymentOption::from(&wallet);
// Create multiple keys for different pointers
let key1 = SecretKey::random();
let key2 = SecretKey::random();
let key3 = SecretKey::random();
// Create a chunk as the final target
let chunk_addr = ChunkAddress::new(XorName::random(&mut rand::thread_rng()));
// Create pointer3 pointing to the chunk
let (_, addr3) = client
.pointer_create(&key3, PointerTarget::ChunkAddress(chunk_addr), payment_option.clone())
.await?;
// Create pointer2 pointing to pointer3
let (_, addr2) = client
.pointer_create(&key2, PointerTarget::PointerAddress(addr3), payment_option.clone())
.await?;
// Create pointer1 pointing to pointer2
let (_, addr1) = client
.pointer_create(&key1, PointerTarget::PointerAddress(addr2), payment_option)
.await?;
// Wait for replication
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
// Follow the chain: pointer1 -> pointer2 -> pointer3 -> chunk
let ptr1 = client.pointer_get(&addr1).await?;
let ptr2 = client.pointer_get(&ptr1.target().as_pointer_address().unwrap()).await?;
let ptr3 = client.pointer_get(&ptr2.target().as_pointer_address().unwrap()).await?;
// Verify we reach the original chunk
assert!(matches!(ptr3.target(), PointerTarget::ChunkAddress(_)));
Ok(())
}
Error Handling
async fn error_handling_example() -> Result<()> {
let client = Client::init_local().await?;
let wallet = get_funded_wallet();
let payment_option = PaymentOption::from(&wallet);
let key = SecretKey::random();
let target = PointerTarget::ChunkAddress(ChunkAddress::new(XorName::random(&mut rand::thread_rng())));
// Create a pointer
let (_, addr) = client
.pointer_create(&key, target.clone(), payment_option.clone())
.await?;
// Try to create another pointer with the same key (should fail)
match client.pointer_create(&key, target, payment_option).await {
Ok(_) => panic!("Should have failed - pointer already exists"),
Err(PointerError::PointerAlreadyExists(existing_addr)) => {
println!("Pointer already exists at: {existing_addr:?}");
}
Err(e) => panic!("Unexpected error: {e}"),
}
// Try to update a non-existent pointer
let non_existent_key = SecretKey::random();
let new_target = PointerTarget::ChunkAddress(ChunkAddress::new(XorName::random(&mut rand::thread_rng())));
match client.pointer_update(&non_existent_key, new_target).await {
Ok(_) => panic!("Should have failed - pointer doesn't exist"),
Err(PointerError::CannotUpdateNewPointer) => {
println!("Cannot update non-existent pointer");
}
Err(e) => panic!("Unexpected error: {e}"),
}
Ok(())
}
Best Practices
Use
pointer_create
andpointer_update
instead ofpointer_put
for most use casesHandle errors appropriately - especially
PointerAlreadyExists
andCannotUpdateNewPointer
Wait for replication after creating or updating pointers before trying to retrieve them
Use
pointer_check_existence
for fast existence checks when you don't need the full pointer dataConsider pointer chains for complex data structures, but be aware of the traversal cost
Verify signatures when working with pointers from untrusted sources using
pointer.verify_signature()
Performance Considerations
Creation cost: Initial pointer creation requires payment
Update cost: Updates are free after creation
Retrieval cost: Reading pointers is free
Replication delay: Allow 3-5 seconds for network replication after writes
Existence checks: Use
pointer_check_existence
for faster existence verification
Last updated