Transaction Class
Overview
The Transaction
class represents a consensus-based ledger transaction. It encapsulates data that tracks the state of one branch of an application-level consensus process, comprising consensus terms, participants, metadata, and a sequence of attestations representing consensus progress.
Package: keychain.core.transaction
from keychain.core.transaction import Transaction
Class Definition
class Transaction(SerializableObject):
"""Represents a consensus-based ledger transaction."""
Constructor
def __init__(
self,
serialized_string: Optional[bytes] = None,
serialization_format: SerializationFormat = SerializationFormat.PROTOBUF,
serialized_data: Optional[SerializedData] = None,
c_pointer: Optional[ctypes.c_void_p] = None,
) -> None
Initialize a Transaction object. Transactions are typically created through Gateway.create_transaction()
or Persona.create_transaction()
methods.
Parameters:
-
serialized_string
(bytes
, optional) - Serialized transaction bytes to deserialize -
serialization_format
(SerializationFormat
) - Format of the serialized string (default: PROTOBUF) -
serialized_data
(SerializedData
, optional) - SerializedData object to construct from -
c_pointer
(ctypes.c_void_p
, optional) - Existing C pointer to wrap
Example:
# Create from serialized bytes
transaction = Transaction(serialized_string=tx_bytes)
# Create from existing C pointer (typically from library functions)
transaction = Transaction(c_pointer=existing_pointer)
Class Methods
from_copy(other)
@classmethod
def from_copy(cls, other: "Transaction") -> "Transaction"
Create a deep copy of an existing transaction.
Parameters:
-
other
(Transaction
) - The transaction to copy
Returns: New Transaction
instance that is a deep copy
Example:
original_tx = gateway.create_transaction(...)
copied_tx = Transaction.from_copy(original_tx)
Properties
Basic Properties
consensus_algorithm
@property
def consensus_algorithm(self) -> ConsensusAlgorithm
Get the consensus algorithm.
Returns: ConsensusAlgorithm
enumeration value
version
@property
def version(self) -> Version
Get the serialized data format version.
Returns: Version
enumeration value
timestamp
@property
def timestamp(self) -> int
Get the create timestamp as milliseconds since the Epoch (Jan 1, 1970 00:00:00 UTC).
Returns: The timestamp in milliseconds
quorum
@property
def quorum(self) -> TagSet
Get the quorum, which is the set of participants and roles necessary to participate in the consensus.
Returns: TagSet
object defining consensus participants
tags
@property
def tags(self) -> TagSet
Get the tags tag set containing metadata.
Returns: TagSet
object with metadata tags
Methods
stack_variables(time_index)
def stack_variables(self, time_index: int) -> TagSet
Get the current (merged) stack variables at the specified time index.
Parameters:
-
time_index
(int
) - Time index (0 = initial variables, 1 = after first attestation, etc.)
Returns: TagSet
object with merged variables at the specified time
Example:
# Get initial variables
initial_vars = transaction.stack_variables(0)
# Get variables after first attestation
updated_vars = transaction.stack_variables(1)
copy()
def copy(self) -> "Transaction"
Create a deep copy of this transaction.
Returns: New Transaction
instance that is a deep copy
reattach_attachable_attestation(attestation)
def reattach_attachable_attestation(self, attestation: Attestation) -> None
Attach an attachable attestation to the transaction.
Parameters:
-
attestation
(Attestation
) - The attestation to attach
Raises: KeychainError
if the attestation cannot be attached
serialize()
def serialize(self) -> bytes
Serialize the transaction to bytes.
Returns: Bytes serialization in protobuf format
is_consensus_complete()
def is_consensus_complete(self) -> bool
Check if the consensus process appears complete based on attestations.
Returns: True
if consensus appears complete based on available information
Note: This is a heuristic check. For definitive answers, the consensus algorithm logic should be consulted.
Example: Creating and Using Transactions
from keychain.gateway import Gateway
from keychain.core.tag_set import TagSet
from keychain.core.serialized_data import SerializedData
from keychain.constants import SecurityLevel, ConsensusAlgorithm, DataType
import time
# Initialize gateway
settings = Gateway.init("keychain.config")
gateway = Gateway(settings)
try:
# Create participants
alice = gateway.create_persona("alice", "company.com", SecurityLevel.HIGH, True)
bob = gateway.create_persona("bob", "company.com", SecurityLevel.HIGH, True)
charlie = gateway.create_persona("charlie", "company.com", SecurityLevel.HIGH, True)
# Wait for personas to mature
for persona in [alice, bob, charlie]:
if not persona.is_mature():
persona.await_maturity()
# Set up contacts
alice.add_contact("bob", "company.com", bob.did)
alice.add_contact("charlie", "company.com", charlie.did)
bob.add_contact("alice", "company.com", alice.did)
bob.add_contact("charlie", "company.com", charlie.did)
charlie.add_contact("alice", "company.com", alice.did)
charlie.add_contact("bob", "company.com", bob.did)
# Define consensus quorum
quorum = TagSet()
quorum.set_tag_value("participants", str(alice.did),
SerializedData.from_string("approver", DataType.UTF8_STRING))
quorum.set_tag_value("participants", str(bob.did),
SerializedData.from_string("reviewer", DataType.UTF8_STRING))
quorum.set_tag_value("participants", str(charlie.did),
SerializedData.from_string("auditor", DataType.UTF8_STRING))
quorum.set_tag_value("", "threshold",
SerializedData.from_string("2", DataType.UTF8_STRING))
# Set initial variables
base_variables = TagSet()
base_variables.set_tag_value("", "proposal_id",
SerializedData.from_string("PROP-2024-001", DataType.UTF8_STRING))
base_variables.set_tag_value("", "status",
SerializedData.from_string("pending", DataType.UTF8_STRING))
base_variables.set_tag_value("", "amount",
SerializedData.from_string("50000", DataType.UTF8_STRING))
# Create transaction
transaction = alice.create_transaction(
consensus_algorithm=ConsensusAlgorithm.MAJORITY,
quorum=quorum,
base_variables=base_variables
)
print("Consensus transaction created successfully")
# Display transaction information
print(f"Consensus Algorithm: {transaction.consensus_algorithm}")
print(f"Timestamp: {transaction.timestamp}")
print(f"Version: {transaction.version}")
print(f"Data Type: {transaction.data_type()}")
# Access quorum information
tx_quorum = transaction.quorum
threshold_data = tx_quorum.get_tag_value("", "threshold")
print(f"Consensus Threshold: {threshold_data.content}")
# Access base variables
variables = transaction.base_variables
proposal_id = variables.get_tag_value("", "proposal_id")
status = variables.get_tag_value("", "status")
amount = variables.get_tag_value("", "amount")
print(f"\nTransaction Details:")
print(f" Proposal ID: {proposal_id.content}")
print(f" Status: {status.content}")
print(f" Amount: {amount.content}")
# Bob adds his signature (review approval)
bob_signed_tx = bob.add_signature_to_transaction(
transaction,
is_approval=True
)
print(f"\nAttestations after Bob's signature: {len(bob_signed_tx.attestations)}")
# Charlie adds his signature (audit approval)
final_tx = charlie.add_signature_to_transaction(
bob_signed_tx,
is_approval=True
)
print(f"Attestations after Charlie's signature: {len(final_tx.attestations)}")
# Check if consensus is complete
if final_tx.is_consensus_complete():
print("✓ Consensus process appears complete")
else:
print("⚠ Consensus process not yet complete")
# Check current variables after all signatures
current_vars = final_tx.stack_variables(len(final_tx.attestations))
current_status = current_vars.get_tag_value("", "status")
print(f"Current status: {current_status.content}")
finally:
Gateway.close()
Example: Multi-Step Consensus Workflow
from keychain.gateway import Gateway
from keychain.core.tag_set import TagSet
from keychain.core.serialized_data import SerializedData
from keychain.constants import SecurityLevel, ConsensusAlgorithm, DataType
def demonstrate_consensus_workflow():
"""Demonstrate a complete consensus workflow with multiple participants."""
settings = Gateway.init("keychain.config")
gateway = Gateway(settings)
try:
# Create participants with different roles
proposer = gateway.create_persona("proposer", "company.com", SecurityLevel.HIGH, True)
reviewer1 = gateway.create_persona("reviewer1", "company.com", SecurityLevel.HIGH, True)
reviewer2 = gateway.create_persona("reviewer2", "company.com", SecurityLevel.HIGH, True)
approver = gateway.create_persona("approver", "company.com", SecurityLevel.HIGH, True)
# Wait for maturity
participants = [proposer, reviewer1, reviewer2, approver]
for participant in participants:
if not participant.is_mature():
participant.await_maturity()
# Set up contact relationships
for i, participant in enumerate(participants):
for j, other in enumerate(participants):
if i != j:
participant.add_contact(f"participant{j}", "company.com", other.did)
# Define consensus quorum with roles
quorum = TagSet()
quorum.set_tag_value("participants", str(proposer.did),
SerializedData.from_string("proposer", DataType.UTF8_STRING))
quorum.set_tag_value("participants", str(reviewer1.did),
SerializedData.from_string("reviewer", DataType.UTF8_STRING))
quorum.set_tag_value("participants", str(reviewer2.did),
SerializedData.from_string("reviewer", DataType.UTF8_STRING))
quorum.set_tag_value("participants", str(approver.did),
SerializedData.from_string("approver", DataType.UTF8_STRING))
# Set consensus parameters
quorum.set_tag_value("", "algorithm",
SerializedData.from_string("majority", DataType.UTF8_STRING))
quorum.set_tag_value("", "min_reviewers",
SerializedData.from_string("2", DataType.UTF8_STRING))
quorum.set_tag_value("", "requires_approver",
SerializedData.from_string("true", DataType.UTF8_STRING))
# Create initial transaction variables
variables = TagSet()
variables.set_tag_value("", "proposal_type",
SerializedData.from_string("budget_allocation", DataType.UTF8_STRING))
variables.set_tag_value("", "requested_amount",
SerializedData.from_string("75000", DataType.UTF8_STRING))
variables.set_tag_value("", "department",
SerializedData.from_string("engineering", DataType.UTF8_STRING))
variables.set_tag_value("", "quarter",
SerializedData.from_string("Q2-2024", DataType.UTF8_STRING))
variables.set_tag_value("", "status",
SerializedData.from_string("proposed", DataType.UTF8_STRING))
# Proposer creates the transaction
transaction = proposer.create_transaction(
consensus_algorithm=ConsensusAlgorithm.MAJORITY,
quorum=quorum,
base_variables=variables
)
print("=== Budget Allocation Consensus Process ===")
print(f"Initial proposal created by: {proposer.name}.{proposer.subname}")
print(f"Consensus algorithm: {transaction.consensus_algorithm.name}")
# Display initial state
initial_vars = transaction.base_variables
amount = initial_vars.get_tag_value("", "requested_amount")
dept = initial_vars.get_tag_value("", "department")
quarter = initial_vars.get_tag_value("", "quarter")
print(f"Amount: ${amount.content}")
print(f"Department: {dept.content}")
print(f"Quarter: {quarter.content}")
# Consensus step 1: First reviewer
print(f"\n--- Step 1: {reviewer1.name} Review ---")
# Reviewer 1 adds approval with updated variables
review1_vars = TagSet()
review1_vars.set_tag_value("", "reviewer1_status",
SerializedData.from_string("approved", DataType.UTF8_STRING))
review1_vars.set_tag_value("", "reviewer1_comments",
SerializedData.from_string("Budget justified", DataType.UTF8_STRING))
tx_after_review1 = reviewer1.add_signature_to_transaction(
transaction,
is_approval=True,
variables=review1_vars
)
print(f"✓ Reviewer 1 approved")
print(f"Attestations: {len(tx_after_review1.attestations)}")
# Consensus step 2: Second reviewer
print(f"\n--- Step 2: {reviewer2.name} Review ---")
review2_vars = TagSet()
review2_vars.set_tag_value("", "reviewer2_status",
SerializedData.from_string("approved_with_conditions", DataType.UTF8_STRING))
review2_vars.set_tag_value("", "reviewer2_comments",
SerializedData.from_string("Approved with 10% reduction", DataType.UTF8_STRING))
review2_vars.set_tag_value("", "adjusted_amount",
SerializedData.from_string("67500", DataType.UTF8_STRING))
tx_after_review2 = reviewer2.add_signature_to_transaction(
tx_after_review1,
is_approval=True,
variables=review2_vars
)
print(f"✓ Reviewer 2 approved with conditions")
print(f"Attestations: {len(tx_after_review2.attestations)}")
# Consensus step 3: Final approval
print(f"\n--- Step 3: {approver.name} Final Approval ---")
approval_vars = TagSet()
approval_vars.set_tag_value("", "final_status",
SerializedData.from_string("approved", DataType.UTF8_STRING))
approval_vars.set_tag_value("", "final_amount",
SerializedData.from_string("67500", DataType.UTF8_STRING))
approval_vars.set_tag_value("", "approval_date",
SerializedData.from_string("2024-04-15", DataType.UTF8_STRING))
final_transaction = approver.add_signature_to_transaction(
tx_after_review2,
is_approval=True,
variables=approval_vars
)
print(f"✓ Final approval granted")
print(f"Total attestations: {len(final_transaction.attestations)}")
# Analyze final consensus state
print(f"\n=== Consensus Results ===")
final_vars = final_transaction.stack_variables(len(final_transaction.attestations))
try:
final_status = final_vars.get_tag_value("", "final_status")
final_amount = final_vars.get_tag_value("", "final_amount")
approval_date = final_vars.get_tag_value("", "approval_date")
print(f"Status: {final_status.content}")
print(f"Approved Amount: ${final_amount.content}")
print(f"Approval Date: {approval_date.content}")
except Exception as e:
print(f"Error accessing final variables: {e}")
# Check consensus completion
if final_transaction.is_consensus_complete():
print("✅ Consensus process complete")
else:
print("⚠️ Consensus process incomplete")
# Demonstrate verification
print(f"\n=== Verification ===")
verification_result = reviewer1.verify_transaction(final_transaction)
if verification_result.is_verified():
print("✓ Transaction cryptographically verified")
if verification_result.signer_is_known():
signer = verification_result.signer()
if signer:
print(f"Last signer: {signer.name}.{signer.subname}")
else:
print("✗ Transaction verification failed")
return final_transaction
finally:
Gateway.close()
if __name__ == "__main__":
transaction = demonstrate_consensus_workflow()
Example: Transaction Analysis Utilities
from keychain.core.transaction import Transaction
from keychain.core.tag_set import TagSet
import json
class TransactionAnalyzer:
"""Utility class for analyzing consensus transactions."""
@staticmethod
def get_transaction_summary(transaction: Transaction) -> dict:
"""Create a summary of transaction information."""
return {
"consensus_algorithm": transaction.consensus_algorithm.name,
"timestamp": transaction.timestamp,
"version": transaction.version.name if hasattr(transaction.version, 'name') else str(transaction.version),
"attestation_count": len(transaction.attestations),
"is_complete": transaction.is_consensus_complete(),
"participants": transaction.get_consensus_participants()
}
@staticmethod
def trace_consensus_progression(transaction: Transaction) -> list:
"""Trace the progression of consensus through attestations."""
progression = []
# Start with base variables
base_vars = transaction.base_variables
progression.append({
"step": 0,
"description": "Initial state",
"variables": TransactionAnalyzer._extract_variables(base_vars)
})
# Add each attestation step
for i in range(len(transaction.attestations)):
step_vars = transaction.stack_variables(i + 1)
progression.append({
"step": i + 1,
"description": f"After attestation {i + 1}",
"variables": TransactionAnalyzer._extract_variables(step_vars)
})
return progression
@staticmethod
def _extract_variables(tag_set: TagSet) -> dict:
"""Extract variables from TagSet to dictionary."""
variables = {}
# Note: In a real implementation, you would iterate over all tags
# This is a simplified example showing how you would extract known variables
known_keys = [
"status", "amount", "final_amount", "proposal_id", "approval_date",
"reviewer1_status", "reviewer2_status", "final_status"
]
for key in known_keys:
try:
value_data = tag_set.get_tag_value("", key)
variables[key] = value_data.content
except:
# Variable not present
pass
return variables
@staticmethod
def validate_consensus_rules(transaction: Transaction) -> dict:
"""Validate transaction against consensus rules."""
results = {
"valid": True,
"errors": [],
"warnings": []
}
# Check minimum attestations
if len(transaction.attestations) == 0:
results["valid"] = False
results["errors"].append("No attestations present")
# Check quorum requirements
quorum = transaction.quorum
try:
threshold_data = quorum.get_tag_value("", "threshold")
threshold = int(threshold_data.content)
if len(transaction.attestations) < threshold:
results["valid"] = False
results["errors"].append(f"Insufficient attestations: {len(transaction.attestations)} < {threshold}")
except:
results["warnings"].append("Could not determine consensus threshold")
# Check consensus completion
if not transaction.is_consensus_complete():
results["warnings"].append("Consensus process appears incomplete")
return results
# Usage example
def analyze_transaction_example():
"""Demonstrate transaction analysis utilities."""
# In a real scenario, you would load an existing transaction
print("Transaction analysis utilities ready for use")
print("Create a transaction using Gateway/Persona methods, then analyze it")
if __name__ == "__main__":
analyze_transaction_example()
Error Handling
Transaction operations can raise various exceptions:
Example:
from keychain.exceptions import KeychainValidationError, KeychainSecurityError
try:
# Create transaction
transaction = persona.create_transaction(...)
# Access properties
consensus_algo = transaction.consensus_algorithm
quorum = transaction.quorum
# Add signatures
signed_tx = persona.add_signature_to_transaction(transaction, is_approval=True)
# Serialize transaction
serialized = transaction.serialize()
except KeychainValidationError as e:
print(f"Validation error: {e}")
except KeychainSecurityError as e:
print(f"Security error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
See Also
-
Persona Class - Creating transactions
-
Gateway Class - Transaction operations
-
TagSet Class - Quorum and variable storage
-
Attestation Class - Consensus signatures
-
VerificationResult Class - Verification results
-
Exception Classes - Error handling