Processing Transactions in Stable Shards
This tutorial guides developers through a TallyBox Windows desktop application that monitors stable database shards for new transactions and processes them using the `sql_ods_processor` function. The application uses a timer-based mechanism (`timer_engine_Tick`) to check sharded tables (`tbl_order_dispatcher_X`) and move new transactions to the `tbl_order_ods` table for processing. This tutorial explains the logic, algorithm, and data flow, with pseudo-code to aid implementation in a C# Windows Forms environment.
Step 1: Initialize Timer and Application State
The `timer_engine_Tick` function initializes the application state and sets up periodic shard checking. The process involves:
- Retrieve the current Unix timestamp (`local_utc_unix`) and update the UI (`txt_tree_local`).
- On first run (`Program.progress_utc_unix == 0`), set the progress timestamp to the current timestamp minus one and count existing jobs in `tbl_order_ods`.
- Log initialization messages using `AddText`.
Pseudo-Code: Initialize Timer
FUNCTION initialize_timer():
local_utc_unix = get_current_unix_timestamp()
update_ui(txt_tree_local, local_utc_unix)
IF progress_utc_unix == 0 THEN
progress_utc_unix = local_utc_unix - 1
progress_in_ods = sql_count_records("tbl_order_ods", "the_sign")
log_message("Box - ignites.. [" + progress_utc_unix + "]")
log_message("ODS - scanner.. [" + progress_in_ods + "]")
END IF
RETURN local_utc_unix, progress_utc_unix, progress_in_ods
END FUNCTION
Example Process:
Current Timestamp: 1741675600
UI Update: txt_tree_local = 1741675600
Initial State: progress_utc_unix = 1741675599
ODS Jobs: progress_in_ods = 5
Log Output:
Box - ignites.. [1741675599]
ODS - scanner.. [5]
References:
Timer Class (Microsoft Docs)
Unix Time (Wikipedia)
Step 2: Check for Pending Jobs
If jobs exist in `tbl_order_ods` (`progress_in_ods > 0`), the `timer_engine_Tick` function triggers processing. The process involves:
- Log the busy state and number of remaining jobs.
- If `sql_ods_processor` is not running (`in_sql_ods_processor == false`), start it in a new thread.
Pseudo-Code: Check Pending Jobs
FUNCTION check_pending_jobs():
IF progress_in_ods > 0 THEN
log_message("Box - busy.. [" + progress_utc_unix + "] - job(s) remains: " + progress_in_ods)
IF NOT in_sql_ods_processor THEN
start_thread(sql_ods_processor)
END IF
END IF
END FUNCTION
Example Process:
progress_in_ods: 5
in_sql_ods_processor: false
Log Output: Box - busy.. [1741675599] - job(s) remains: 5
Action: Start sql_ods_processor thread
References:
Thread Class (Microsoft Docs)
Multithreading (Wikipedia)
Step 3: Monitor Shards for New Transactions
If no jobs are pending, `timer_engine_Tick` checks sharded tables (`tbl_order_dispatcher_X`) for new transactions. The process involves:
- Increment `progress_utc_unix` and select the shard table based on its last digit (`dispatcher_line`).
- Check for records in the shard using `sql_find_record`.
- If records are found, move them to `tbl_order_ods` using `sql_move_record_extended` with a condition (`local_utc_unix <= progress_utc_unix`).
- Update `progress_in_ods` and log the number of moved jobs.
- Handle timestamp alignment (e.g., reset if ahead of current time).
Pseudo-Code: Monitor Shards
FUNCTION monitor_shards(local_utc_unix, progress_utc_unix):
WHILE true:
progress_utc_unix = progress_utc_unix + 1
IF local_utc_unix - progress_utc_unix == 1 THEN
log_message("Box - listens.. [" + progress_utc_unix + "]")
BREAK
ELSE IF local_utc_unix - progress_utc_unix < 1 THEN
progress_utc_unix = progress_utc_unix - 1
log_message("Box - exception.. [" + progress_utc_unix + "]")
BREAK
ELSE
log_message("Box - jumps.. [" + progress_utc_unix + "]")
END IF
dispatcher_line = last_digit(progress_utc_unix)
job_found = sql_find_record("tbl_order_dispatcher_" + dispatcher_line, "the_sign")
IF job_found != "no_record" THEN
jobs_moved = sql_move_records("tbl_order_dispatcher_" + dispatcher_line,
"tbl_order_ods",
"graph_from,graph_to,wallet_from,wallet_to,order_currency,order_amount,order_utc_unix,order_id,public_key,the_sign,local_utc_unix",
"local_utc_unix<=" + progress_utc_unix)
IF jobs_moved > 0 THEN
log_message("dispatcher[" + dispatcher_line + "][" + jobs_moved + "] moved to ods..")
progress_in_ods = sql_count_records("tbl_order_ods", "the_sign")
log_message("- job(s) found: " + progress_in_ods)
BREAK
END IF
END IF
END WHILE
update_ui(txt_tree_progress, progress_utc_unix)
update_ui(txt_dispatcher_line, dispatcher_line)
update_ui(txt_ods_jobs, progress_in_ods)
END FUNCTION
Example Process:
local_utc_unix: 1741675600
progress_utc_unix: 1741675599
dispatcher_line: 9
Job Found: the_sign = MEYCIQCxzNKhOUXijLr+z2mI9npu/+KZijiEv3//W7Ya3VpvzgIhAI1m7wJLJ9ldP2m5jmYfUreuvoKTjoZmFQmt5e6foakp
Jobs Moved: 3
Log Output:
Box - listens.. [1741675599]
dispatcher[9][3] moved to ods..
- job(s) found: 3
UI Update:
txt_tree_progress = 1741675599
txt_dispatcher_line = 9
txt_ods_jobs = 3
References:
SQL INSERT Statement (Microsoft Docs)
Database Sharding (Wikipedia)
Step 4: Process ODS Records
The `sql_ods_processor` function processes records from `tbl_order_ods`, performing validation and ledger updates. The process involves:
- Query `tbl_order_ods` for records, ordered by `local_utc_unix`.
- For each record, extract fields (e.g., `graph_from`, `wallet_from`, `order_currency`).
- Decrement `progress_in_ods` to track remaining jobs.
- Validate formats, graph domains, currency, and signatures, marking invalid records for deletion.
- Handle treasury (`$`) and group (`#`) transactions with specific checks.
Pseudo-Code: Process ODS Records
FUNCTION sql_ods_processor():
in_sql_ods_processor = true
records = sql_query("SELECT * FROM tbl_order_ods ORDER BY local_utc_unix")
IF records.is_empty THEN
in_sql_ods_processor = false
RETURN
END IF
FOR EACH record IN records:
progress_in_ods = progress_in_ods - 1
order_data = extract_fields(record, ["graph_from", "graph_to", "wallet_from", "wallet_to",
"order_currency", "order_amount", "order_utc_unix",
"order_id", "public_key", "the_sign", "local_utc_unix"])
delete_record = validate_record(order_data)
IF delete_record THEN
sql_delete("tbl_order_ods", "the_sign='" + order_data.the_sign + "'")
sql_delete("tbl_order_ods_multiple", "the_sign_md5='" + hash_md5(order_data.the_sign) + "'")
CONTINUE
END IF
// Proceed to further processing (next steps)
END FOR
END FUNCTION
Example Process:
Query: SELECT * FROM tbl_order_ods ORDER BY local_utc_unix
Record: graph_from=tallybox.mixoftix.net, wallet_from=boxB2bbc15c8c135..., order_currency=2ZR, order_amount=3500.00000000
progress_in_ods: 4 (after decrement)
Validation: All formats valid
Result: Proceed to next steps
References:
SqlDataReader (Microsoft Docs)
SQL (Wikipedia)
Step 5: Validate Transactions
Validate each transaction’s format, database records, treasury/group details, and signature. The process involves:
- Check formats: Ensure `graph_from`/`graph_to` contain dots, wallets are valid, and timestamps/amounts are numeric.
- Verify graph domains and currency in database tables (`tbl_system_graph`, `tbl_system_currency`).
- For treasury transactions (`order_id` starts with `$`): Validate treasury ID, check for double-spending, and match currency/amount/wallet.
- For group transactions (`order_id` starts with `#`): Validate multiple recipients and amounts, check for duplicates, and verify total amount.
- Verify ECDSA signature using `secp256r1` curve.
Pseudo-Code: Validate Transactions
FUNCTION validate_record(order_data):
delete_record = false
IF NOT contains(order_data.graph_from, ".") OR NOT contains(order_data.graph_to, ".") THEN
log_message("check format failed: graph")
delete_record = true
END IF
IF NOT wallet_qc(order_data.wallet_from) OR (NOT order_data.order_id.starts_with("#") AND NOT wallet_qc(order_data.wallet_to)) THEN
log_message("check format failed: wallet")
delete_record = true
END IF
IF NOT isnumeric(order_data.order_utc_unix) OR NOT isnumeric(order_data.local_utc_unix) THEN
log_message("check format failed: timestamp")
delete_record = true
END IF
IF graph_id_from = sql_find_record("tbl_system_graph", "graph_id", "graph_domain", order_data.graph_from) == "no_record" THEN
log_message("error~207~invalid graph~graph_from")
delete_record = true
END IF
IF graph_id_to = sql_find_record("tbl_system_graph", "graph_id", "graph_domain", order_data.graph_to) == "no_record" THEN
log_message("error~207~invalid graph~graph_to")
delete_record = true
END IF
IF currency_id = sql_find_record("tbl_system_currency", "currency_id", "currency_name", order_data.order_currency) == "no_record" THEN
log_message("not found (currency_name): " + order_data.order_currency)
delete_record = true
END IF
the_sign_md5 = hash_md5(order_data.the_sign)
IF order_data.order_id.starts_with("$") THEN
treasury_id = sql_find_record("tbl_system_treasury", "treasury_id", "treasury_id", order_data.order_id)
IF treasury_id == "no_record" OR sql_find_record("tbl_tallybox_sign", "order_id", "order_id", treasury_id) != "no_record" THEN
log_message("error~204 or 205~treasury error")
delete_record = true
ELSE IF treasury details mismatch THEN
log_message("error~206~treasury mismatch error")
delete_record = true
END IF
ELSE IF order_data.order_id.starts_with("#") THEN
num_tnxs = sql_max_field_id("tbl_order_ods_multiple", "row_id", "the_sign_md5", the_sign_md5) OR 1
IF check_duplicate(the_sign_md5) THEN
delete_record = true
END IF
FOR i = 1 TO num_tnxs:
wallet_to = sql_find_record_with_where("tbl_order_ods_multiple", "wallet_to", "row_id='" + i + "' and the_sign_md5='" + the_sign_md5 + "'")
order_amount = sql_find_record_with_where("tbl_order_ods_multiple", "order_amount", "row_id='" + i + "' and the_sign_md5='" + the_sign_md5 + "'")
IF NOT wallet_qc(wallet_to) OR NOT isnumeric(order_amount) THEN
delete_record = true
END IF
END FOR
ELSE IF NOT order_data.order_id.is_empty AND NOT isnumeric(order_data.order_id) THEN
log_message("warning~503~invalid numeric data~order_id")
order_data.order_id = ""
END IF
IF NOT order_data.order_id.starts_with("#") AND NOT isnumeric(order_data.order_amount) THEN
log_message("error~303~invalid numeric data~order_amount")
delete_record = true
END IF
order_string = join([order_data.graph_from, order_data.graph_to, order_data.wallet_from,
order_data.wallet_to, order_data.order_currency, order_data.order_amount,
order_data.order_id, order_data.order_utc_unix], "~")
public_key_decompressed = decompress_b58_in_b58_out(order_data.public_key.replace("*", "~"), "secp256r1")
IF NOT sign_data_b64_check(order_string, order_data.the_sign, public_key_decompressed, "secp256r1") THEN
log_message("error mis-match (ecc_sign)")
delete_record = true
END IF
RETURN delete_record
END FUNCTION
Example Process:
order_id: 778844 (Numeric, single transaction)
graph_from: tallybox.mixoftix.net (Valid)
wallet_from: boxB2bbc15c8c135... (Valid)
order_currency: 2ZR (Valid in tbl_system_currency)
order_amount: 3500.00000000 (Numeric)
Signature: Valid
Result: delete_record = false
References:
ECDSA Algorithm (Wikipedia)
Double-Spending (Wikipedia)
Step 6: Update Ledger and Wallets
For valid transactions, update wallet records and ledger entries. The process involves:
- Generate a unique transaction ID (`tnx_id`) using a tree-branch structure (`tree_id.local_utc_unix.branch_id_reverse`).
- Register new wallets (`wallet_from`, `wallet_to`) in `tbl_tallybox_wallet` and `tbl_tallybox_wallet_pubkey`, with buffer tables.
- Calculate fees (2 * 250.0 IRR + num_tnxs * 250.0 IRR) and update sender’s balance in `tbl_tallybox_book`.
- Update sender and receiver balances for the transaction amount in `tbl_tallybox_book`.
- Record the signature in `tbl_tallybox_sign` with a transaction hash.
- Delete processed records from `tbl_order_ods` and `tbl_order_ods_multiple`.
Pseudo-Code: Update Ledger and Wallets
FUNCTION update_ledger_and_wallets(order_data, num_tnxs, wallet_to_arr, order_amount_arr):
tree_id = order_data.local_utc_unix
row_number = increment_row_number(tree_id)
branch_id = row_number
branch_id_reverse = reverse(branch_id)
tnx_id = tree_id + "." + branch_id_reverse
session_wallet_id = sql_max_field_id("tbl_tallybox_wallet", "wallet_id")
wallet_from_id = register_wallet(order_data.wallet_from, session_wallet_id)
register_public_key(order_data.public_key, wallet_from_id)
FOR j = 0 TO num_tnxs - 1:
wallet_to_id_arr[j] = register_wallet(wallet_to_arr[j], session_wallet_id)
END FOR
currency_id_fee = "1"
currency_amount_fee = (2 * base_fee_amount) + (num_tnxs * base_fee_amount)
left_amount_fee = calculate_balance(order_data.graph_from, wallet_from_id, currency_id_fee, currency_amount_fee)
tally_hash_fee = hash_sha256(previous_tally_hash + components)
sql_insert("tbl_tallybox_book", fee_entry)
sql_insert("tbl_tallybox_book_buffer", fee_entry, archive_id="1")
sql_insert("tbl_tallybox_book_buffer", fee_entry, archive_id="2")
left_amount_from = calculate_balance(order_data.graph_from, wallet_from_id, currency_id, order_data.order_amount)
tally_hash_from = hash_sha256(previous_tally_hash + components)
sql_insert("tbl_tallybox_book", from_entry)
sql_insert("tbl_tallybox_book_buffer", from_entry, archive_id="1")
sql_insert("tbl_tallybox_book_buffer", from_entry, archive_id="2")
FOR j = 0 TO num_tnxs - 1:
left_amount_to = calculate_balance(order_data.graph_to, wallet_to_id_arr[j], currency_id, order_amount_arr[j])
tally_hash_to = hash_sha256(previous_tally_hash + components)
sql_insert("tbl_tallybox_book", to_entry[j])
sql_insert("tbl_tallybox_book_buffer", to_entry[j], archive_id="1")
sql_insert("tbl_tallybox_book_buffer", to_entry[j], archive_id="2")
END FOR
tnx_md5 = hash_md5(hash_sha256(tally_hash_fee + tally_hash_from + tally_hash_to))
sql_insert("tbl_tallybox_sign", signature_entry)
sql_insert("tbl_tallybox_sign_buffer", signature_entry, archive_id="1")
sql_insert("tbl_tallybox_sign_buffer", signature_entry, archive_id="2")
sql_delete("tbl_order_ods", "the_sign='" + order_data.the_sign + "'")
sql_delete("tbl_order_ods_multiple", "the_sign_md5='" + hash_md5(order_data.the_sign) + "'")
END FUNCTION
Example Output:
References:
SHA-256 Algorithm (Wikipedia)
MD5 Algorithm (Wikipedia)
Acknowledgments
Special thanks to Grok, for its invaluable assistance in creating this TallyBox transaction shard processing tutorial.