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

base_variables

@property
def base_variables(self) -> TagSet

Get the original verifiable variable tag set (stack variables at time index 0).

Returns: TagSet object with the initial variable set

attestations

@property
def attestations(self) -> List[Attestation]

Get the attestations representing the sequence of consensus steps.

Returns: List of Attestation objects

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

data_type()

def data_type(self) -> DataType

Get the data type.

Returns: DataType.TRANSACTION

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.

get_consensus_participants()

def get_consensus_participants(self) -> List[str]

Get the list of consensus participants from the quorum.

Returns: List of participant identifiers

Comparison Methods

eq(other)

def __eq__(self, other: object) -> bool

Compare transactions for equality.

Parameters:

  • other (Transaction) - The other transaction to compare

Returns: True if transactions are equal

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