This guide documents the implementation of reproducible and traceable anchor interaction sessions in AnchorKit. The system ensures all contract operations are logged, auditable, and can be replayed deterministically.
- Purpose: Group related operations into logical units
- Uniqueness: Auto-incremented session IDs
- Traceability: Tracks initiator, creation time, and operation count
- Replay Protection: Includes nonce based on ledger sequence
pub struct InteractionSession {
pub session_id: u64, // Unique identifier
pub initiator: Address, // Who created the session
pub created_at: u64, // Unix timestamp
pub operation_count: u64, // Total operations in session
pub nonce: u64, // Ledger sequence for replay protection
}- Purpose: Record detailed information about each operation
- Sequencing: Operation index ensures deterministic ordering
- Status Tracking: Records success/failure with result data
- Timestamp: Precise operation execution time
pub struct OperationContext {
pub session_id: u64, // Which session
pub operation_index: u64, // Sequential position (0-based)
pub operation_type: String, // "init", "register", "attest", etc.
pub timestamp: u64, // Execution time
pub status: String, // "success" or error code
pub result_data: u64, // Result (e.g., attestation ID)
}- Purpose: Immutable record of all operations
- Completeness: Combines session, operation, and actor info
- Persistence: Stored in persistent storage for long-term audit
pub struct AuditLog {
pub log_id: u64, // Unique log entry ID
pub session_id: u64, // Associated session
pub operation: OperationContext,
pub actor: Address, // Who performed the operation
}New storage keys added to support session management:
enum StorageKey {
// ... existing keys ...
SessionCounter, // Global session ID counter
Session(u64), // Session data by ID
SessionNonce(u64), // Session nonce for replay protection
AuditLogCounter, // Global audit log counter
AuditLog(u64), // Audit log entries by ID
SessionOperationCount(u64), // Operation count per session
}Storage Lifetime:
- Instance storage (30 days): Session counter, audit log counter
- Persistent storage (90 days): Session data, audit logs, nonces
Emitted when a new session is created.
pub struct SessionCreated {
pub session_id: u64,
pub initiator: Address,
pub timestamp: u64,
}Topic: ("session", "created", session_id)
Emitted when an operation is recorded in a session.
pub struct OperationLogged {
pub log_id: u64,
pub session_id: u64,
pub operation_index: u64,
pub operation_type: String,
pub status: String,
}Topic: ("audit", "logged", log_id)
1. User calls create_session(initiator)
↓
2. Verify contract is initialized
↓
3. Generate unique session_id (increment counter)
↓
4. Create nonce from current ledger sequence
↓
5. Store InteractionSession in persistent storage
↓
6. Store nonce for replay protection
↓
7. Emit SessionCreated event
↓
8. Return session_id
1. User calls session-aware operation (e.g., submit_attestation_with_session)
↓
2. Verify session exists
↓
3. Execute operation logic
↓
4. On success/failure:
- Increment session operation count
- Create OperationContext with details
- Store AuditLog entry
- Emit OperationLogged event
↓
5. Return result
Sessions are reproducible through:
- Deterministic Ordering: Operation indices ensure exact replay order
- Complete Context: All operation details stored (timestamp, actor, result)
- Immutable Logs: Audit logs cannot be modified after creation
- Nonce Protection: Session nonce prevents unauthorized replays
- Sequential IDs: Both session and operation IDs are sequential
Two-level replay protection:
- Session Level: Nonce based on ledger sequence prevents session reuse
- Operation Level: Payload hash tracking prevents duplicate attestations
Creates a new interaction session.
Implementation:
pub fn create_session(env: Env, initiator: Address) -> Result<u64, Error> {
initiator.require_auth();
Storage::get_admin(&env)?; // Verify initialized
let session_id = Storage::create_session(&env, &initiator);
let timestamp = env.ledger().timestamp();
SessionCreated::publish(&env, session_id, &initiator, timestamp);
Ok(session_id)
}Storage Operations:
- Increment session counter
- Create and store InteractionSession
- Store session nonce
- Initialize operation count to 0
Retrieves session details.
Implementation:
pub fn get_session(env: Env, session_id: u64) -> Result<InteractionSession, Error> {
Storage::get_session(&env, session_id)
}Gets total operations in a session.
Implementation:
pub fn get_session_operation_count(env: Env, session_id: u64) -> Result<u64, Error> {
Storage::get_session(&env, session_id)?;
Ok(Storage::get_session_operation_count(&env, session_id))
}Retrieves a specific audit log entry.
Implementation:
pub fn get_audit_log(env: Env, log_id: u64) -> Result<AuditLog, Error> {
Storage::get_audit_log(&env, log_id)
}Submits attestation with full session logging.
Key Differences from Standard Method:
- Takes
session_idparameter - Logs operation on success and failure
- Returns attestation ID
- Emits both AttestationRecorded and OperationLogged events
Implementation Flow:
1. Verify issuer auth
2. Validate timestamp
3. Check attestor registration
4. Check replay attack
5. Verify signature
6. Create attestation
7. Store attestation
8. Mark hash as used
9. Emit AttestationRecorded event
10. Log operation with result_data = attestation_id
11. Emit OperationLogged event
12. Return attestation_idRegisters attestor with session logging.
Implementation Flow:
1. Verify admin auth
2. Check attestor not already registered
3. Set attestor as registered
4. Emit AttestorAdded event
5. Log operation with status = "success"
6. Emit OperationLogged eventRevokes attestor with session logging.
Implementation Flow:
1. Verify admin auth
2. Check attestor is registered
3. Set attestor as unregistered
4. Emit AttestorRemoved event
5. Log operation with status = "success"
6. Emit OperationLogged eventInternal method to log operations within sessions.
Parameters:
session_id: Session to log inactor: Address performing operationoperation_type: Type of operationstatus: "success" or error coderesult_data: Operation result
Implementation:
fn log_session_operation(
env: &Env,
session_id: u64,
actor: &Address,
operation_type: &str,
status: &str,
result_data: u64,
) -> Result<u64, Error> {
// Verify session exists
Storage::get_session(env, session_id)?;
// Get next operation index
let operation_index = Storage::increment_session_operation_count(env, session_id);
let timestamp = env.ledger().timestamp();
// Create operation context
let operation = OperationContext {
session_id,
operation_index,
operation_type: String::from_str(env, operation_type),
timestamp,
status: String::from_str(env, status),
result_data,
};
// Store audit log
let log_id = Storage::log_operation(env, session_id, actor, &operation);
// Emit event
OperationLogged::publish(
env,
log_id,
session_id,
operation_index,
&operation.operation_type,
&operation.status,
);
Ok(log_id)
}pub fn create_session(env: &Env, initiator: &Address) -> u64 {
let session_id = Self::get_and_increment_session_counter(env);
let nonce = env.ledger().sequence() as u64;
let session = InteractionSession {
session_id,
initiator: initiator.clone(),
created_at: env.ledger().timestamp(),
operation_count: 0,
nonce,
};
// Store session
let key = StorageKey::Session(session_id).to_storage_key(env);
env.storage().persistent().set(&key, &session);
env.storage()
.persistent()
.extend_ttl(&key, Self::PERSISTENT_LIFETIME, Self::PERSISTENT_LIFETIME);
// Store nonce
let nonce_key = StorageKey::SessionNonce(session_id).to_storage_key(env);
env.storage().persistent().set(&nonce_key, &nonce);
env.storage()
.persistent()
.extend_ttl(&nonce_key, Self::PERSISTENT_LIFETIME, Self::PERSISTENT_LIFETIME);
session_id
}pub fn log_operation(
env: &Env,
session_id: u64,
actor: &Address,
operation: &OperationContext,
) -> u64 {
let log_id = Self::get_and_increment_audit_counter(env);
let audit_log = AuditLog {
log_id,
session_id,
operation: operation.clone(),
actor: actor.clone(),
};
// Store audit log
let key = StorageKey::AuditLog(log_id).to_storage_key(env);
env.storage().persistent().set(&key, &audit_log);
env.storage()
.persistent()
.extend_ttl(&key, Self::PERSISTENT_LIFETIME, Self::PERSISTENT_LIFETIME);
log_id
}| Code | Name | Cause |
|---|---|---|
| 13 | SessionNotFound |
Session ID doesn't exist |
| 14 | InvalidSessionId |
Session ID is invalid |
| 15 | SessionReplayAttack |
Nonce mismatch detected |
SessionNotFound:
- Calling operation with non-existent session_id
- Retrieving session that was never created
InvalidSessionId:
- Reserved for future use with session validation
SessionReplayAttack:
- Attempting to use session with different nonce
- Indicates potential unauthorized replay attempt
-
Session Creation
- Create session successfully
- Verify session ID increments
- Verify nonce is set correctly
-
Operation Logging
- Log operation in session
- Verify operation index increments
- Verify audit log is stored
-
Reproducibility
- Create session with multiple operations
- Retrieve all operations in order
- Verify operation indices are sequential
-
Error Cases
- Session not found
- Invalid session ID
- Replay attack detection
-
Multi-Operation Session
- Create session
- Register attestor
- Submit attestation
- Verify all operations logged
-
Session Verification
- Create session
- Perform operations
- Retrieve session details
- Verify operation count matches
-
Audit Trail
- Create session
- Perform operations
- Retrieve audit logs
- Verify complete history
- Session Counter: Single instance storage entry (minimal)
- Session Data: One persistent entry per session
- Audit Logs: One persistent entry per operation
- Nonce Storage: One persistent entry per session
- Minimal event data (only essential fields)
- Efficient storage key generation
- TTL management prevents storage bloat
- Sequential IDs avoid hash lookups
- Session IDs are u64 (supports 18 quintillion sessions)
- Operation indices are u64 (supports 18 quintillion operations per session)
- Audit log IDs are u64 (supports 18 quintillion total operations)
- Session Nonce: Based on ledger sequence, unique per session
- Payload Hash Tracking: Prevents duplicate attestations
- Operation Sequencing: Ensures deterministic ordering
- Immutable Logs: Audit logs cannot be modified
- Complete Context: All operation details recorded
- Actor Tracking: Records who performed each operation
- Session Creation: Requires initiator auth
- Operation Logging: Automatic, no additional auth needed
- Audit Retrieval: Public read access (no auth required)
For existing contracts:
- Backward Compatibility: Old methods still work unchanged
- Gradual Adoption: Use session-aware methods for new operations
- Audit Trail: Historical operations not logged (only new ones)
- No Breaking Changes: Existing functionality unaffected
The session and traceability system provides:
- Reproducibility: Sessions can be replayed deterministically
- Traceability: Complete audit trail of all operations
- Security: Replay protection at multiple levels
- Scalability: Efficient storage and gas usage
- Compliance: Full audit trail for regulatory requirements
This implementation ensures AnchorKit can be used in regulated environments where complete auditability and reproducibility are critical requirements.