From 6f91d679528f98489ba84664a2c6de9d3c05d067 Mon Sep 17 00:00:00 2001 From: J-Dog Date: Thu, 25 Apr 2024 11:51:41 -0700 Subject: [PATCH] add `SWEEP` support --- docs/actions/SWEEP.md | 22 ++-- indexer/includes/actions/sweep.php | 180 ++++++++++++++++++++++++-- indexer/includes/functions.php | 118 ++++++++++++++++- indexer/includes/protocol_changes.php | 2 +- indexer/sql/sweeps.sql | 21 +++ 5 files changed, 319 insertions(+), 24 deletions(-) create mode 100644 indexer/sql/sweeps.sql diff --git a/docs/actions/SWEEP.md b/docs/actions/SWEEP.md index 3f2cf82..4a45224 100644 --- a/docs/actions/SWEEP.md +++ b/docs/actions/SWEEP.md @@ -2,28 +2,28 @@ This command transfers all `token` balances and/or ownerships to a destination address ## PARAMS -| Name | Type | Description | -| ----------------- | ------ | ----------------------------------------------------------------- | -| `VERSION` | String | Broadcast Format Version | -| `DESTINATION` | String | address where `token` shall be swept | -| `SWEEP_BALANCES` | String | Indicates if address `token` balances should be swept (default=1) | -| `SWEEP_OWNERSHIP` | String | Indicates if address `token` balances should be swept (default=1) | -| `MEMO` | String | Optional memo to include | +| Name | Type | Description | +| ------------- | ------ | ------------------------------------------------------------------ | +| `VERSION` | String | Broadcast Format Version | +| `DESTINATION` | String | address where `token` shall be swept | +| `BALANCES` | String | Indicates if address `token` balances should be swept (default=1) | +| `OWNERSHIPS` | String | Indicates if address `token` ownership should be swept (default=1) | +| `MEMO` | String | Optional memo to include | ## Formats ### Version `0` -- `VERSION|DESTINATION|SWEEP_BALANCES|SWEEP_OWNERSHIP|MEMO` +- `VERSION|DESTINATION|BALANCES|OWNERSHIPS|MEMO` ## Examples ``` bt:SWEEP|0|1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev|1|1 -This example sweeps both token balances and ownership from the broadcasting address to 1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev +This example sweeps both token balances and ownerships from the broadcasting address to 1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev ``` ``` -bt:SWEEP|0|1BoogrfDADPLQpq8LMASmWQUVYDp4t2hF9|0|1` -This example sweeps only token ownership from the broadcasting address to 1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev +bt:SWEEP|0|1BoogrfDADPLQpq8LMASmWQUVYDp4t2hF9|0|1 +This example sweeps only token ownerships from the broadcasting address to 1BoogrfDADPLQpq8LMASmWQUVYDp4t2hF9 ``` ## Rules diff --git a/indexer/includes/actions/sweep.php b/indexer/includes/actions/sweep.php index 959aa15..fb2c1ae 100644 --- a/indexer/includes/actions/sweep.php +++ b/indexer/includes/actions/sweep.php @@ -3,18 +3,182 @@ * sweep.php - SWEEP command * * PARAMS: - * VERSION - Broadcast Format Version - * DESTINATION - address where `token` shall be swept - * SWEEP_BALANCES - Indicates if address `token` balances should be swept (default=1) - * SWEEP_OWNERSHIP - Indicates if address `token` balances should be swept (default=1) - * MEMO - Optional memo to include + * VERSION - Broadcast Format Version + * DESTINATION - address where `token` shall be swept + * BALANCES - Indicates if address `token` balances should be swept (default=1) + * OWNERSHIPS - Indicates if address `token` ownerships should be swept (default=1) + * MEMO - Optional memo to include * * FORMATS: - * 0 = VERSION|DESTINATION|SWEEP_BALANCES|SWEEP_OWNERSHIP|MEMO + * 0 = VERSION|DESTINATION|BALANCES|OWNERSHIP|MEMO ********************************************************************/ function btnsSweep($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|DESTINATION|BALANCES|OWNERSHIPS|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 SOURCE address preferences, balances, and ownership information + $preferences = getAddressPreferences($data->SOURCE, $data->BLOCK_INDEX, $data->TX_INDEX); + $balances = getAddressBalances($data->SOURCE, null, $data->BLOCK_INDEX, $data->TX_INDEX); + $ownerships = getAddressOwnership($data->SOURCE, null, $data->BLOCK_INDEX, $data->TX_INDEX); + + // Copy base BTNS transaction data object into fees object + $fees = clone($data); + $fees->TICK = 'GAS'; + $fees->AMOUNT = 0; + $fees->METHOD = ($preferences->FEE_PREFERENCE==1) ? 1 : 2; // 1=Destroy, 2=Donate + + /***************************************************************** + * FORMAT Validations + ****************************************************************/ + + // Verify DESTINATION address format + if(!$error && isset($data->DESTINATION) && !isCryptoAddress($data->DESTINATION)) + $error = "invalid: DESTINATION (format)"; + + // Verify BALANCES format is valid (0 or 1) + if(!$error && isset($data->BALANCES) && !in_array($data->BALANCES,array(0,1))) + $error = "invalid: BALANCES (format)"; + + // Verify OWNERSHIP format is valid (0 or 1) + if(!$error && isset($data->OWNERSHIP) && !in_array($data->OWNERSHIP,array(0,1))) + $error = "invalid: OWNERSHIP (format)"; + + // Set default values for BALANCES and OWNERSHIP (default = 1) + $data->BALANCES = (isset($data->BALANCES)) ? $data->BALANCES : 1; + $data->OWNERSHIPS = (isset($data->OWNERSHIPS)) ? $data->OWNERSHIPS : 1; + + // Clone the raw data for storage in sweeps table + $sweep = clone($data); + + /***************************************************************** + * General Validations + ****************************************************************/ + + // Verify no pipe in MEMO (BTNS uses pipe as field delimiter) + if(!$error && strpos($data->MEMO,'|')!==false) + $error = 'invalid: MEMO (pipe)'; + + // Verify no semicolon in MEMO (BTNS uses semicolon as action delimiter) + if(!$error && strpos($data->MEMO,';')!==false) + $error = 'invalid: MEMO (semicolon)'; + + // Calculate total number of database hits for this SWEEP + $db_hits = 1; // 1 sweeps + $db_hits += ($data->BALANCES) ? bcmul(count($balances),3,0) : 0; // 1 debits, 1 credits, 1 balances + $db_hits += ($data->OWNERSHIP) ? count($ownerships) : 0; // 1 issues + + // Determine total transaction FEE based on database hits + $data->FEE_TICK = 'GAS'; + $data->FEE_AMOUNT = getTransactionFee($db_hits, $data->FEE_TICK); + + // Verify SOURCE has enough balances to cover FEE AMOUNT + if(!$error && !hasBalance($balances, $data->FEE_TICK, $data->FEE_AMOUNT)) + $error = 'invalid: insufficient funds (FEE)'; + + // Adjust balances to reduce by FEE amount + if(!$error) + $balances = debitBalances($balances, $data->FEE_TICK, $data->FEE_AMOUNT); + + // Determine final status + $data->STATUS = $sweep->STATUS = $status = ($error) ? $error : 'valid'; + + // Print status message + print "\n\t SWEEP : {$data->DESTINATION} : {$data->STATUS}"; + + // Create record in sweeps table + createSweep($sweep); + + // If this was a valid transaction, then handle the actual sweep actions + if($status=='valid'){ + + // Store the SOURCE and FEE_TICK in addresses and tickers arrays + addAddressTicker($data->SOURCE, $data->FEE_TICK); + + // Debit FEE_AMOUNT from SOURCE address + createDebit('SWEEP', $data->BLOCK_INDEX, $data->TX_HASH, $data->FEE_TICK, $data->FEE_AMOUNT, $data->SOURCE); + + // Transfer Balances + if($data->BALANCES==1){ + foreach($balances as $tick_id => $amount){ + $tick = getTicker($tick_id); + + // Debit token amount from SOURCE and credit to DESTINATION + createDebit('SWEEP', $data->BLOCK_INDEX, $data->TX_HASH, $tick, $amount, $data->SOURCE); + createCredit('SWEEP', $data->BLOCK_INDEX, $data->TX_HASH, $tick, $amount, $data->DESTINATION); + + // Store the SOURCE, DESTINATION and TICK in addresses and tickers arrays + addAddressTicker($data->SOURCE, $tick); + addAddressTicker($data->DESTINATION, $tick); + } + } + + // Transfer token ownerships + if($data->OWNERSHIPS==1){ + // Copy base BTNS transaction data object into issue object + $issue = $data; + $issue->TRANSFER = $data->DESTINATION; + foreach($ownerships as $tick){ + $issue->TICK = $tick; + + // Create issue record for transfer of ownership + createIssue($issue); + + // Store the DESTINATION and TICK in addresses and tickers arrays + addAddressTicker($data->DESTINATION, $tick); + } + } + + // Update FEES object with to AMOUNT + $fees->AMOUNT = bcadd($fees->AMOUNT, $data->FEE_AMOUNT, 8); + + // Handle using FEE according the the users ADDRESS preferences + if($preferences->FEE_PREFERENCE>1){ + + // Determine what address to donate to + $address = ($preferences->FEE_PREFERENCE==2) ? DONATE_ADDRESS_1 : DONATE_ADDRESS_2; + + // Update the $fees object with the destination address + $fees->DESTINATION = $address; + + // Store the donation ADDRESS and TICK in addresses list + addAddressTicker($address, $data->FEE_TICK); + + // Credit donation address with FEE_AMOUNT + createCredit('SWEEP', $data->BLOCK_INDEX, $data->TX_HASH, $data->FEE_TICK, $data->FEE_AMOUNT, $fees->DESTINATION); + } + + // Create record of FEE in `fees` table + createFeeRecord($fees); + + // If this is a reparse, bail out before updating balances and token information + if($reparse) + return; + + // Update address balances + updateBalances(array_keys($addresses)); + + // Update supply for tokens + updateTokens($tickers); + } } ?> \ No newline at end of file diff --git a/indexer/includes/functions.php b/indexer/includes/functions.php index 93c2822..6340cb8 100644 --- a/indexer/includes/functions.php +++ b/indexer/includes/functions.php @@ -1795,8 +1795,9 @@ function hasBalance($balances=null, $tick=null, $amount=null){ // Handle deducting TICK AMOUNT from balances and return updated balances array function debitBalances($balances=null, $tick=null, $amount=null){ - $balance = (isset($balances[$tick])) ? $balances[$tick] : 0; - $balances[$tick] = $balance - $amount; + $tick_id = createTicker($tick); + $balance = (isset($balances[$tick_id])) ? $balances[$tick_id] : 0; + $balances[$tick_id] = $balance - $amount; return $balances; } @@ -1853,9 +1854,9 @@ function sanityCheck( $block=null ){ $supplyB = getTokenSupplyBalance($tick); // Supply from balances table $supplyC = getTokenSupply($tick); // Supply from credits/debits tables if($supplyA!=$supplyB) - byeLog("SanityError: balances table supply does not match token supply : {$tick}"); + byeLog("SanityError: balances table supply does not match token supply : {$tick} ({$supplyB} != {$supplyA})"); if($supplyA!=$supplyC) - byeLog("SanityError: credits/debits table supply does not match token supply : {$tick}"); + byeLog("SanityError: credits/debits table supply does not match token supply : {$tick} ({$supplyC} != {$supplyA})"); } } @@ -2479,4 +2480,113 @@ function createDividend( $data=null ){ } } +// Get list of tokens owned by a given address +function getAddressOwnership($address=null, $block_index=null, $tx_index=null){ + global $mysqli; + $address_id = createAddress($address); + $data = []; + $whereSql = ""; + if(isset($block_index) && is_numeric($block_index)) + $whereSql .= " AND block_index <= {$block_index}"; + if(isset($tx_index) && is_numeric($tx_index)) + $whereSql .= " AND tx_index < '{$tx_index}'"; + // get list of issuances where address is SOURCE or TRANSFER (list of tokens) + $sql = "SELECT + t.tick, + i.tick_id + FROM + issues i, + index_tickers t + WHERE + t.id=i.tick_id AND + (source_id='{$address_id}' OR transfer_id='{$address_id}') + {$whereSql} + ORDER BY tick DESC"; + $results = $mysqli->query($sql); + if($results){ + if($results->num_rows){ + while($row = $results->fetch_assoc()){ + $row = (object) $row; + $info = getTokenInfo($row->tick, null, $block_index, $tx_index); + if($info->OWNER==$address) + array_push($data, $row->tick); + } + } + } else { + byeLog('Error while trying to lookup records in issues table'); + } + return $data; +} + + +// Create record in `sweeps` table +function createSweep( $data=null ){ + global $mysqli; + $tick_id = createTicker($data->TICK); + $source_id = createAddress($data->SOURCE); + $destination_id = createAddress($data->DESTINATION); + $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); + $balances = $mysqli->real_escape_string($data->BALANCES); + $ownerships = $mysqli->real_escape_string($data->OWNERSHIPS); + $block_index = $mysqli->real_escape_string($data->BLOCK_INDEX); + // Check if record already exists + $sql = "SELECT + tx_index + FROM + sweeps + 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 + sweeps + SET + tx_index='{$tx_index}', + block_index='{$block_index}', + destination_id='{$destination_id}', + balances='{$balances}', + ownerships='{$ownerships}', + 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 sweeps (tx_index, source_id, destination_id, balances, ownerships, memo_id, tx_hash_id, block_index, status_id) values ('{$tx_index}', '{$source_id}','{$destination_id}', '{$balances}', '{$ownerships}', '{$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 dividends table'); + } else { + byeLog('Error while trying to lookup record in dividends table'); + } +} + +// Get TICK for a given tick_id +function getTicker( $tick_id=null ){ + global $mysqli; + if(!isset($tick_id) || $tick_id=='') + return 0; + // Truncate description to 250 chars + $tick_id = $mysqli->real_escape_string($tick_id); + $results = $mysqli->query("SELECT tick FROM index_tickers WHERE id='{$tick_id}' LIMIT 1"); + if($results){ + if($results->num_rows){ + $row = $results->fetch_assoc(); + return $row['tick']; + } + } else { + byeLog('Error while trying to lookup ticker in index_tickers table'); + } +} + + + ?> diff --git a/indexer/includes/protocol_changes.php b/indexer/includes/protocol_changes.php index e92dac5..c09e1dc 100644 --- a/indexer/includes/protocol_changes.php +++ b/indexer/includes/protocol_changes.php @@ -25,7 +25,7 @@ 'RUG' => array(0, 10, 0, 9999999, 999999999), 'SEND' => array(0, 10, 0, 789742, 2473585), 'SLEEP' => array(0, 10, 0, 9999999, 999999999), - 'SWEEP' => array(0, 10, 0, 9999999, 999999999), + 'SWEEP' => array(0, 10, 0, 789742, 2473585), // Define protocol changes (ALL LOWER Case) // 'name' => array(0, 10, 0, 0, 0), diff --git a/indexer/sql/sweeps.sql b/indexer/sql/sweeps.sql new file mode 100644 index 0000000..fb81ceb --- /dev/null +++ b/indexer/sql/sweeps.sql @@ -0,0 +1,21 @@ +DROP TABLE IF EXISTS sweeps; +CREATE TABLE sweeps ( + 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 SWEEP transaction + balances INTEGER UNSIGNED, -- Indicates if token balances should be swept + ownerships INTEGER UNSIGNED, -- Indicates if token ownerships should be swept + source_id INTEGER UNSIGNED, -- id of record in index_addresses table + destination_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 sweeps (tx_index); +CREATE INDEX source_id ON sweeps (source_id); +CREATE INDEX destination_id ON sweeps (destination_id); +CREATE INDEX tx_hash_id ON sweeps (tx_hash_id); +CREATE INDEX block_index ON sweeps (block_index); +CREATE INDEX memo_id ON sweeps (memo_id); +CREATE INDEX status_id ON sweeps (status_id); +