TallyBox Tutorial - P2P Network Flow

by shahiN Noursalehi

You are here: Home / Tutorials / TallyBox Payment Processor - P2P Network Flow

Note: This tutorial is structured to be AI-Friendly. An AI can generate code in any programming language based on the content of this URL, thanks to its clear steps, pseudo-code, and examples.

P2P Networking in TallyBox DAG

This tutorial guides developers through the peer-to-peer (P2P) networking flow in a TallyBox application, which operates on a Directed Acyclic Graph (DAG) structure for scalable and efficient transaction processing. The tutorial emphasizes the sender's role in recomposing transactions from the database into XML messages using the main_recomposer function (with supporting functions transaction_recomposer, group_transaction_recomposer, and minting_transaction_recomposer), followed by the receiver's role in decomposing incoming XML messages using transaction_decomposer.

Unlike most blockchain systems that rely on bidirectional synchronization, where peers mutually exchange and reconcile transaction data to maintain a consistent ledger, TallyBox employs unidirectional synchronization. In this approach, the sender peer pushes transaction data to the receiver, which processes and validates it, returning a SHA256 hash to confirm successful integration into the DAG without sending back its own transaction data. This unidirectional flow enhances efficiency in high-throughput scenarios by reducing network overhead and simplifying conflict resolution.

This tutorial covers data flow, messaging formats, and the hash-based synchronization mechanism where the receiver generates a SHA256 hash that the sender verifies to confirm synchronization before sending subsequent records. The implementation is in Python, using a SQL Server database for ledger storage and ECDSA (secp256r1) for cryptographic validation.

Step 1: Overview of P2P Networking in TallyBox DAG

TallyBox operates as a decentralized P2P network where nodes (peers) exchange transaction records using a DAG structure, allowing transactions to reference multiple previous transactions for scalability. The sender peer uses main_recomposer to construct XML messages in the sync_transaction format from database records and broadcasts them to other peers. The receiver peer processes these messages with transaction_decomposer, validates them, stores them in the database, and generates a final SHA256 hash. This hash is sent back to the sender, who verifies it to ensure successful processing before sending the next record. The system supports three transaction types:

The data flow starts with the sender recomposing and broadcasting transactions, followed by the receiver decomposing, validating, and storing them, with hash-based synchronization ensuring DAG consistency.

Pseudo-Code: P2P Networking Overview

FUNCTION sender_broadcast_and_sync(tnx_id, the_tnx_md5):
    xml_string = main_recomposer(tnx_id, the_tnx_md5)
    IF xml_string IS NOT NULL THEN
        log_message("Broadcasting transaction: " + tnx_id)
        broadcast_to_peers(xml_string)
        RETURN xml_string
    ELSE
        log_message("Failed to recompose transaction")
        RETURN NULL
    END IF

FUNCTION receiver_process(xml_string):
    sha256_hash = transaction_decomposer(xml_string)
    IF sha256_hash IS NOT NULL THEN
        log_message("Processed transaction, hash: " + sha256_hash)
        send_to_sender(sha256_hash)  // Notify sender for synchronization
        broadcast_to_peers(xml_string)
        RETURN sha256_hash
    ELSE
        log_message("Failed to process transaction")
        RETURN NULL
    END IF
                


Example Process:

Sender Peer: Reconstructs XML with main_recomposer, broadcasts to Peer B
Receiver Peer (B): Processes XML with transaction_decomposer, generates SHA256 hash (e.g., '7c4a8d09ca3762af61e59520943dc26494f8941b'), sends to Sender
Sender Peer: Verifies hash, sends next transaction
Receiver Peer (B): Broadcasts XML to Peer C

References:
Directed Acyclic Graph (Wikipedia)
PyODBC (Microsoft Docs)

Step 2: Sender - Transaction Recomposition

The sender peer uses the main_recomposer function to retrieve transaction data from the database and construct XML messages in the sync_transaction format for broadcasting. It delegates to one of three functions based on the order_id prefix:

The process involves querying database tables, reconstructing the order_csv and order_csv_multiple fields, and formatting the XML with precise decimal handling for tnx_id.

Pseudo-Code: Recompose Transaction

FUNCTION main_recomposer(tnx_id, the_tnx_md5):
    order_id = sql_find_record("tbl_tallybox_sign", "order_id", "tnx_id", tnx_id, "the_tnx_md5", the_tnx_md5)
    IF order_id == "no_record" THEN
        log_error("Transaction not found")
        RETURN NULL
    END IF
    IF order_id.starts_with("$") THEN
        RETURN minting_transaction_recomposer(tnx_id, the_tnx_md5)
    ELSE IF order_id.starts_with("#") THEN
        RETURN group_transaction_recomposer(tnx_id, the_tnx_md5)
    ELSE
        RETURN transaction_recomposer(tnx_id, the_tnx_md5)
    END IF

FUNCTION transaction_recomposer(tnx_id, the_tnx_md5):
    sign_data = sql_query("tbl_tallybox_sign", tnx_id, the_tnx_md5)
    sender_data = sql_query("tbl_tallybox_book", tnx_type="1", tnx_id)
    wallet_from = sql_query("tbl_tallybox_wallet", sender_data.wallet_id)
    public_key = sql_query("tbl_tallybox_wallet_pubkey", sender_data.wallet_id)
    order_currency = sql_query("tbl_system_currency", sender_data.currency_id)
    graph_from = sql_query("tbl_system_graph", sender_data.graph_id)
    recipient_data = sql_query("tbl_tallybox_book", tnx_type="2", tnx_id)
    wallet_to = sql_query("tbl_tallybox_wallet", recipient_data.wallet_id)
    graph_to = sql_query("tbl_system_graph", recipient_data.graph_id)
    order_csv = join_fields(["tallybox", "parcel_of_transaction", "graph_from", graph_from, ..., "publicKey_xy_compressed", public_key], "~")
    xml = create_xml(order_csv, "", tnx_id, the_tnx_md5)
    RETURN xml
                


Example Recomposed XML:

References:
MiniDOM XML (Python Docs)
XML (Wikipedia)

Step 3: Sender - Broadcasting Transactions

After recomposing the transaction into an XML message, the sender peer broadcasts it to other peers in the TallyBox network. The process involves:

The sender waits for hash confirmation before sending the next transaction, ensuring synchronization.

Pseudo-Code: Broadcast Transactions

FUNCTION broadcast_transactions():
    transactions = sql_query("tbl_tallybox_sign", "tnx_id, the_tnx_md5")
    FOR tnx_id, the_tnx_md5 IN transactions:
        xml_string = main_recomposer(tnx_id, the_tnx_md5)
        IF xml_string IS NOT NULL THEN
            broadcast_to_peers(xml_string)
            expected_hash = calculate_expected_hash(tnx_id)
            received_hash = await_receiver_hash()
            IF received_hash == expected_hash THEN
                log_message("Synchronization confirmed, sending next record")
                continue
            ELSE
                log_error("Synchronization failed, retrying")
                request_resend(xml_string)
            END IF
        END IF
    END FOR
                


Example Broadcast:

Sender Peer: Queries tnx_id='1741675600.1', the_tnx_md5='b2e3f94169f3d82b9b67d93acbc288a4'
XML Generated: <sync_transaction>...</sync_transaction>
Action: Broadcasts XML to Peer B, awaits SHA256 hash

References:
Peer-to-Peer Networking (Wikipedia)
Socket Programming (Python Docs)

Step 4: Receiver - Parsing XML Messages

The receiver peer processes incoming XML messages using the transaction_decomposer function, extracting fields from the sync_transaction structure. The process involves:

The XML format ensures standardized communication across peers, with fields encoded in a tilde-separated string for efficient parsing.

Pseudo-Code: Parse XML Message

FUNCTION transaction_decomposer(xml_string):
    TRY:
        root = parse_xml(xml_string)
        order_csv = root.find("order_csv").text
        order_csv_multiple = root.find("order_csv_multiple").text OR ""
        enforce_tnx_id = root.find("enforce_tnx_id").text
        checksum = root.find("checksum").text
        csv_parts = split(order_csv, "~")
        is_group = csv_parts[2] == "number_of_transactions"
        offset = 2 IF is_group ELSE 0
        graph_from = csv_parts[3 + offset]
        graph_to = csv_parts[5 + offset]
        wallet_from = csv_parts[7 + offset]
        wallet_to = csv_parts[9 + offset]
        order_currency = csv_parts[11 + offset]
        order_amount = Decimal(csv_parts[13 + offset])
        order_id = csv_parts[15 + offset]
        order_utc_unix = csv_parts[17 + offset]
        the_sign = csv_parts[19 + offset]
        public_key = csv_parts[21 + offset]
        IF is_group THEN
            multiple_fields = split(order_csv_multiple, "~")
            wallet_to_arr, order_amount_arr = parse_multiple_fields(multiple_fields)
        ELSE
            wallet_to_arr = [wallet_to]
            order_amount_arr = [order_amount]
        END IF
        tree_id, branch_id_reverse = split(enforce_tnx_id, ".")
        branch_id = reverse(branch_id_reverse)
        RETURN parsed_data
    CATCH Exception e:
        log_error("XML parsing error: " + e)
        RETURN NULL
END FUNCTION
                


Example XML Input:



Parsed Output:
graph_from: tallybox.mixoftix.net
wallet_from: tjbB2bbc15c8c135...
order_currency: 2ZR
order_amount: 3500.00000000
tree_id: 1741675600
branch_id: 1

References:
ElementTree XML API (Python Docs)
XML (Wikipedia)

Step 5: Receiver - Validation and Hash Generation

The receiver peer validates the parsed transaction and generates a final SHA256 hash for synchronization. The validation and hash generation process includes:

The sender compares the received hash to its expected value to confirm successful processing before sending the next record.

Pseudo-Code: Validate and Generate Hash

FUNCTION validate_and_generate_hash(parsed_data):
    IF NOT validate_formats(parsed_data) THEN
        log_error("Invalid format")
        RETURN NULL
    END IF
    IF NOT validate_database_records(parsed_data) THEN
        log_error("Invalid database records")
        RETURN NULL
    END IF
    IF parsed_data.order_id.starts_with("$") THEN
        IF NOT validate_treasury(parsed_data) THEN
            log_error("Treasury validation failed")
            RETURN NULL
        END IF
    ELSE IF parsed_data.order_id.starts_with("#") THEN
        IF NOT validate_group_transaction(parsed_data) THEN
            log_error("Group transaction validation failed")
            RETURN NULL
        END IF
    END IF
    IF NOT parsed_data.order_id.starts_with("$") THEN
        IF NOT validate_balances(parsed_data) THEN
            log_error("Insufficient balance")
            RETURN NULL
        END IF
    END IF
    order_string = join_fields(parsed_data, ["graph_from", "graph_to", "wallet_from", "wallet_to", "order_currency", "order_amount", "order_id", "order_utc_unix"])
    digest = hash_sha256(order_string)
    IF NOT verify_digest(digest, parsed_data.the_sign, parsed_data.public_key) THEN
        log_error("Signature verification failed")
        RETURN NULL
    END IF
    ledger_entries = generate_ledger_entries(parsed_data)
    IF NOT parsed_data.order_id.starts_with("$") THEN
        total_tally_hash = ledger_entries.tally_hash_fee + "~" + ledger_entries.tally_hash_from + "~" + join(ledger_entries.tally_hash_to_arr, "~")
    ELSE
        total_tally_hash = join(ledger_entries.tally_hash_to_arr, "~")
    END IF
    sha256_output = hash_sha256(total_tally_hash)
    computed_tnx_md5 = hash_md5(sha256_output)
    IF computed_tnx_md5 != parsed_data.checksum THEN
        log_error("Checksum mismatch")
        RETURN NULL
    END IF
    store_ledger_entries(ledger_entries)
    store_signature(parsed_data, computed_tnx_md5)
    RETURN sha256_output
END FUNCTION
                


Example Hash Generation:

Input: XML with checksum='b2e3f94169f3d82b9b67d93acbc288a4'
Ledger Entries: tally_hash_fee, tally_hash_from, tally_hash_to
Total Hash: 'tally_hash_fee~tally_hash_from~tally_hash_to'
SHA256 Output: '7c4a8d09ca3762af61e59520943dc26494f8941b'
MD5 of SHA256: 'b2e3f94169f3d82b9b67d93acbc288a4' (Matches checksum)
Sender Peer: Receives SHA256 hash, confirms match, sends next record

References:
ECDSA (Wikipedia)
SHA-256 (Wikipedia)

Step 6: Receiver - Database Storage

After validation, the receiver peer stores the transaction data in the SQL Server database, maintaining the DAG structure. The process involves:

Pseudo-Code: Store in Database

FUNCTION store_transaction(parsed_data):
    session_wallet_id = sql_max_field_id("tbl_tallybox_wallet", "wallet_id") + 1
    wallet_from_id = register_wallet(parsed_data.wallet_from, session_wallet_id)
    register_public_key(parsed_data.public_key, wallet_from_id)
    wallet_to_id_arr = []
    FOR wallet_to IN parsed_data.wallet_to_arr:
        wallet_to_id = register_wallet(wallet_to, session_wallet_id)
        wallet_to_id_arr.append(wallet_to_id)
    END FOR
    ledger_entries = []
    IF NOT parsed_data.order_id.starts_with("$") THEN
        fee_entry = create_fee_entry(parsed_data, wallet_from_id)
        ledger_entries.append(fee_entry)
    END IF
    from_entry = create_sender_entry(parsed_data, wallet_from_id)
    ledger_entries.append(from_entry)
    FOR i = 0 TO len(parsed_data.wallet_to_arr) - 1:
        to_entry = create_recipient_entry(parsed_data, wallet_to_id_arr[i], i)
        ledger_entries.append(to_entry)
    END FOR
    FOR entry IN ledger_entries:
        sql_insert("tbl_tallybox_book", entry)
    END FOR
    signature_entry = create_signature_entry(parsed_data, computed_tnx_md5)
    sql_insert("tbl_tallybox_sign", signature_entry)
END FUNCTION
                


Example Database Entries:

References:
SQL INSERT Statement (Microsoft Docs)
DAG Structure (Wikipedia)

Step 7: Synchronization via Hash Confirmation

The TallyBox P2P network relies on a hash-based synchronization mechanism to ensure reliable data exchange. After the receiver processes a transaction with transaction_decomposer, it generates a final SHA256 hash from the concatenated tally_hash fields of ledger entries. This hash is sent back to the sender peer, which compares it to its expected hash to confirm successful processing. Only then does the sender proceed to send the next transaction record. The process ensures that the DAG remains consistent across peers, as each transaction’s hash depends on previous transactions in the graph. The data flow is:

Pseudo-Code: Synchronization via Hash

FUNCTION synchronize_transaction(xml_string):
    sha256_hash = transaction_decomposer(xml_string)
    IF sha256_hash IS NOT NULL THEN
        send_to_sender(sha256_hash)
        broadcast_to_peers(xml_string)
        RETURN true
    ELSE
        send_to_sender(NULL)
        RETURN false
    END IF

FUNCTION sender_verify_hash(received_hash, expected_hash):
    IF received_hash == expected_hash THEN
        log_message("Synchronization confirmed, sending next record")
        send_next_record()
    ELSE
        log_error("Synchronization failed, hash mismatch")
        request_resend()
    END IF
                


Example Synchronization Flow:

Sender Peer: Sends XML transaction to Peer B
Peer B: Processes transaction, generates SHA256 hash '7c4a8d09ca3762af61e59520943dc26494f8941b', sends to Sender
Sender Peer: Verifies hash matches, sends next transaction
Peer B: Broadcasts XML to Peer C
Peer C: Repeats process, ensuring DAG consistency

References:
SHA-256 Algorithm (Wikipedia)
Socket Programming (Python Docs)

Acknowledgments

Special thanks to Grok, created by xAI, for its invaluable assistance in creating this TallyBox P2P DAG networking tutorial.