From 553beeee861a4ede79882bc980c8dace53922f76 Mon Sep 17 00:00:00 2001 From: J-Dog Date: Mon, 20 May 2024 12:26:13 -0700 Subject: [PATCH] add basic `CALLBACK` support todo: decimal precision still needs some work --- docs/actions/CALLBACK.md | 3 +- indexer/includes/actions/callback.php | 197 +++++++++++++++++++++++++- indexer/includes/functions.php | 54 +++++++ indexer/includes/protocol_changes.php | 2 +- indexer/sql/callbacks.sql | 22 +++ 5 files changed, 273 insertions(+), 5 deletions(-) create mode 100644 indexer/sql/callbacks.sql diff --git a/docs/actions/CALLBACK.md b/docs/actions/CALLBACK.md index acc0a24..bdbc635 100644 --- a/docs/actions/CALLBACK.md +++ b/docs/actions/CALLBACK.md @@ -6,11 +6,12 @@ This command performs a callback on a `token`. | --------- | ------ | ----------------------------- | | `VERSION` | String | Broadcast Format Version | | `TICK` | String | 1 to 250 characters in length | +| `MEMO` | String | An optional memo to include | ## Formats ### Version `0` -- `VERSION|TICK` +- `VERSION|TICK|MEMO` ## Examples ``` diff --git a/indexer/includes/actions/callback.php b/indexer/includes/actions/callback.php index 37e6248..a9a19ad 100644 --- a/indexer/includes/actions/callback.php +++ b/indexer/includes/actions/callback.php @@ -5,13 +5,204 @@ * PARAMS: * - VERSION - Broadcast Format Version * - TICK - 1 to 250 characters in length + * - MEMO - An optional memo to include * * FORMATS: - * 0 = VERSION|TICK + * 0 = VERSION|TICK|MEMO ********************************************************************/ function btnsCallback($params=null, $data=null, $error=null){ - global $mysqli, $reparse; - // Coming soon + global $mysqli, $reparse, $addresses, $tickers; + + // Define list of known FORMATS + $formats = array( + 0 => 'VERSION|TICK|MEMO' + ); + + /***************************************************************** + * DEBUGGING - Force params + ****************************************************************/ + // $str = "0|JDOG|1|"; + // $params = explode('|',$str); + + // Validate that broadcast format is known + $format = getFormatVersion($params[0]); + if(!$error && ($format===NULL || !in_array($format,array_keys($formats)))) + $error = 'invalid: VERSION (unknown)'; + + // Parse PARAMS using given VERSION format and update BTNS transaction data object + if(!$error) + $data = setActionParams($data, $params, $formats[$format]); + + // Get information on BTNS token + $btInfo = getTokenInfo($data->TICK, null, $data->BLOCK_INDEX, $data->TX_INDEX); + + // Clone the raw data for storage in callbacks table + $callback = clone($data); + + // Create the fees object + $fees = createFeesObject($data); + + // Define placeholders for holders and balances + $holder = []; + $balances = []; + + // Get list of TICK holders and SOURCE address balances + if(!$error){ + $holders = getHolders($data->TICK, $data->BLOCK_INDEX, $data->TX_INDEX); + $balances = getAddressBalances($data->SOURCE, null, $data->BLOCK_INDEX, $data->TX_INDEX); + } + + // Validate TICK exists + if(!$error && !$btInfo) + $error = 'invalid: TICK (unknown)'; + + // Verify only token OWNER can do CALLBACK + if(!$error && $data->SOURCE!=$btInfo->OWNER) + $error = 'invalid: SOURCE (not authorized)'; + + // Get information on CALLBACK_TICK token + if(!$error && isset($btInfo->CALLBACK_TICK)) + $btInfo2 = getTokenInfo($btInfo->CALLBACK_TICK, null, $data->BLOCK_INDEX, $data->TX_INDEX); + + // Validate CALLBACK_TICK exists + if(!$error && !$btInfo2) + $error = 'invalid: CALLBACK_TICK (unknown)'; + + // Set divisible flags + $divisible = ($btInfo->DECIMALS==0) ? 0 : 1; + $divisible2 = ($btInfo2->DECIMALS==0) ? 0 : 1; + + // Populate callback object with callback data + if($btInfo){ + $callback->CALLBACK_TICK = $btInfo->CALLBACK_TICK; + $callback->CALLBACK_AMOUNT = $btInfo->CALLBACK_AMOUNT; + } + + /***************************************************************** + * ACTION Validations + ****************************************************************/ + + // Verify CALLBACK is allowed + if(!$error && isset($btInfo->LOCK_CALLBACK) && $btInfo->LOCK_CALLBACK==1) + $error = "invalid: LOCK_CALLBACK"; + + /***************************************************************** + * FORMAT Validations + ****************************************************************/ + + // Verify CALLBACK_BLOCK format + if(!$error && $btInfo && isset($btInfo->CALLBACK_BLOCK) && $btInfo->CALLBACK_BLOCK!=intval($btInfo->CALLBACK_BLOCK)) + $error = 'invalid: CALLBACK_BLOCK (format)'; + + // Verify CALLBACK_AMOUNT format + if(!$error && isset($btInfo->CALLBACK_AMOUNT) && !isValidAmountFormat($divisible2, $btInfo->CALLBACK_AMOUNT)) + $error = "invalid: CALLBACK_AMOUNT (format)"; + + /***************************************************************** + * General Validations + ****************************************************************/ + + // Verify CALLBACK_BLOCK is less than or equal to current block index + if(!$error && $btInfo && isset($btInfo->CALLBACK_BLOCK) && $btInfo->CALLBACK_BLOCK > $data->BLOCK_INDEX) + $error = 'invalid: CALLBACK_BLOCK (block index)'; + + // Loop through holders and determine CALLBACK_TOTAL + $total = (object)[]; + $total->TICK = 0; + $total->CALLBACK_TICK = 0; + foreach($holders as $address => $amount){ + // Skip including SOURCE address in total calculations + if($address==$data->SOURCE) + continue; + $callback_amount = bcmul($amount, $btInfo->CALLBACK_AMOUNT, $btInfo2->DECIMALS); + $total->TICK = bcadd($amount, $total->TICK, $btInfo->DECIMALS); + $total->CALLBACK_TICK = bcadd($callback_amount, $total->CALLBACK_TICK, $btInfo2->DECIMALS); + } + + // Verify SOURCE has enough balances to cover total callback amount + if(!$error && !hasBalance($balances, $btInfo->CALLBACK_TICK, $total->CALLBACK_TICK)) + $error = 'invalid: insufficient funds (CALLBACK_TICK)'; + + // Adjust balances to reduce by callback total + if(!$error) + $balances = debitBalances($balances, $btInfo->CALLBACK_TICK, $data->CALLBACK_TICK); + + // Calculate total number of database hits for this CALLBACK + $db_hits = count($holders) * 3; // 1 debits, 1 credits, 1 balances + $db_hits += 4; // 1 debits, 1 credits, 1 balances, 1 callback + + // Determine total transaction FEE based on database hits + $fees->AMOUNT = getTransactionFee($db_hits, $fees->TICK); + + // Verify SOURCE has enough balances to cover FEE AMOUNT + if(!$error && !hasBalance($balances, $fees->TICK, $fees->AMOUNT)) + $error = 'invalid: insufficient funds (FEE)'; + + // Adjust balances to reduce by FEE amount + if(!$error) + $balances = debitBalances($balances, $fees->TICK, $fees->AMOUNT); + + // Determine final status + $data->STATUS = $callback->STATUS = $status = ($error) ? $error : 'valid'; + + // Print status message + print "\n\t CALLBACK : {$data->TICK} : {$data->STATUS}"; + + // Create record in callbacks table + createCallback($callback); + + // If this was a valid transaction, then add records to the credits and debits array + if($status=='valid'){ + + // TODO: update to support dispensers (close dispenser) + + // Store the SOURCE, TICK, CALLBACK_TICK, and fee TICK in addresses list + addAddressTicker($data->SOURCE, [$data->TICK, $btInfo->CALLBACK_TICK, $fees->TICK]); + + // Debit CALLBACK_TOTAL from SOURCE address + createDebit('CALLBACK', $data->BLOCK_INDEX, $data->TX_HASH, $btInfo->CALLBACK_TICK, $total->CALLBACK_TICK, $data->SOURCE); + + // Credit all TICK SUPPLY to SOURCE address + createCredit('CALLBACK', $data->BLOCK_INDEX, $data->TX_HASH, $btInfo->TICK, $total->TICK, $data->SOURCE); + + // Handle any transaction FEE according the users's ADDRESS preferences + processTransactionFees('CALLBACK', $fees); + + // Loop through TICK holders + // TODO: Decimal precision needs a bit more work... + foreach($holders as $address => $amount){ + $callback_amount = bcmul($amount, $btInfo->CALLBACK_AMOUNT, $btInfo2->DECIMALS); + + // Skip including SOURCE address in credits/debits (already ) + if($address==$data->SOURCE) + continue; + + // Debit TICK from holders address + createDebit('CALLBACK', $data->BLOCK_INDEX, $data->TX_HASH, $data->TICK, $amount, $address); + + // Credit CALLBACK_TICK to holders address except SOURCE + if($callback_amount>0) + createCredit('CALLBACK', $data->BLOCK_INDEX, $data->TX_HASH, $btInfo->CALLBACK_TICK, $callback_amount, $address); + + // Store the recipient ADDRESS, TICK, and CALLBACK_TICK in addresses list + addAddressTicker($address, [$data->TICK, $btInfo->CALLBACK_TICK]); + } + + } + + // If this is a reparse, bail out before updating balances + if($reparse) + return; + + // Update address balances + updateBalances(array_keys($addresses)); + + // Update supply for GAS (no other supply should change) + updateTokens($fees->TICK); + + // TODO: Remove this when decimal precision is perfect better + updateTokens($tickers); + } ?> \ No newline at end of file diff --git a/indexer/includes/functions.php b/indexer/includes/functions.php index 6b7adba..d99a952 100644 --- a/indexer/includes/functions.php +++ b/indexer/includes/functions.php @@ -1386,6 +1386,9 @@ function getTokenSupply( $tick=null, $tick_id=null, $block_index=null, $tx_index byeLog('Error while trying to get list of debits'); } $supply = bcsub($credits, $debits, $decimals); + // print "credits={$credits}\n"; + // print "debits={$debits}\n"; + // print "supply={$supply}\n"; return $supply; } @@ -1858,6 +1861,10 @@ function sanityCheck( $block=null ){ $supplyA = $supply[$tick]; // Supply from tokens table $supplyB = getTokenSupplyBalance($tick); // Supply from balances table $supplyC = getTokenSupply($tick); // Supply from credits/debits tables + // print "\nTick={$tick} tick_id={$tick_id}\n"; + // print "token supply = {$supplyA}\n"; + // print "balances supply = {$supplyB}\n"; + // print "credit/debit supply = {$supplyC}\n"; if($supplyA!=$supplyB) byeLog("SanityError: balances table supply does not match token supply : {$tick} ({$supplyB} != {$supplyA})"); if($supplyA!=$supplyC) @@ -2660,4 +2667,51 @@ function cleanupHolders($holders){ return $holders; } +// Create record in `callbacks` table +function createCallback( $data=null ){ + global $mysqli; + $tick_id = createTicker($data->TICK); + $callback_tick_id = createTicker($data->CALLBACK_TICK); + $source_id = createAddress($data->SOURCE); + $tx_hash_id = createTransaction($data->TX_HASH); + $memo_id = createMemo($data->MEMO); + $status_id = createStatus($data->STATUS); + $tx_index = $mysqli->real_escape_string($data->TX_INDEX); + $block_index = $mysqli->real_escape_string($data->BLOCK_INDEX); + $callback_amount = $mysqli->real_escape_string($data->CALLBACK_AMOUNT); + // Check if record already exists + $sql = "SELECT + tx_index + FROM + callbacks + WHERE + source_id='{$source_id}' AND + tx_hash_id='{$tx_hash_id}'"; + $results = $mysqli->query($sql); + if($results){ + if($results->num_rows){ + // UPDATE record + $sql = "UPDATE + callbacks + SET + tx_index='{$tx_index}', + block_index='{$block_index}', + tick_id='{$tick_id}', + memo_id='{$memo_id}', + status_id='{$status_id}' + WHERE + source_id='{$source_id}' AND + tx_hash_id='{$tx_hash_id}'"; + } else { + // INSERT record + $sql = "INSERT INTO callbacks (tx_index, source_id, tick_id, callback_tick_id, callback_amount, memo_id, tx_hash_id, block_index, status_id) values ('{$tx_index}', '{$source_id}', '{$tick_id}', '{$callback_tick_id}', '{$callback_amount}', '{$memo_id}', '{$tx_hash_id}', '{$block_index}', '{$status_id}')"; + } + $results = $mysqli->query($sql); + if(!$results) + byeLog('Error while trying to create / update a record in the callbacks table'); + } else { + byeLog('Error while trying to lookup record in callbacks table'); + } +} + ?> diff --git a/indexer/includes/protocol_changes.php b/indexer/includes/protocol_changes.php index c09e1dc..63dcb27 100644 --- a/indexer/includes/protocol_changes.php +++ b/indexer/includes/protocol_changes.php @@ -15,7 +15,7 @@ 'AIRDROP' => array(0, 10, 0, 789742, 2581842), 'BATCH' => array(0, 10, 0, 789742, 2581531), 'BET' => array(0, 10, 0, 9999999, 999999999), - 'CALLBACK' => array(0, 10, 0, 9999999, 999999999), + 'CALLBACK' => array(0, 10, 0, 9999999, 2473585), 'DESTROY' => array(0, 10, 0, 789742, 2473585), 'DISPENSER' => array(0, 10, 0, 9999999, 999999999), 'DIVIDEND' => array(0, 10, 0, 789742, 2473585), diff --git a/indexer/sql/callbacks.sql b/indexer/sql/callbacks.sql new file mode 100644 index 0000000..20bdcb7 --- /dev/null +++ b/indexer/sql/callbacks.sql @@ -0,0 +1,22 @@ +DROP TABLE IF EXISTS callbacks; +CREATE TABLE callbacks ( + tx_index INTEGER UNSIGNED, -- Unique transaction index + tx_hash_id INTEGER UNSIGNED, -- id of record in index_transactions + block_index INTEGER UNSIGNED, -- block index of CALLBACKS transaction + tick_id INTEGER UNSIGNED, -- id of record in index_tickers + callback_tick_id INTEGER UNSIGNED, -- id of record in index_tickers + callback_amount VARCHAR(250), -- Amount of token per unit + source_id INTEGER UNSIGNED, -- id of record in index_addresses table + memo_id INTEGER UNSIGNED, -- id of record in index_memos table + status_id INTEGER UNSIGNED -- id of record in index_statuses table +) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +CREATE INDEX tx_index ON callbacks (tx_index); +CREATE INDEX source_id ON callbacks (source_id); +CREATE INDEX tick_id ON callbacks (tick_id); +CREATE INDEX callback_tick_id ON callbacks (callback_tick_id); +CREATE INDEX tx_hash_id ON callbacks (tx_hash_id); +CREATE INDEX block_index ON callbacks (block_index); +CREATE INDEX memo_id ON callbacks (memo_id); +CREATE INDEX status_id ON callbacks (status_id); +