diff --git a/README.md b/README.md index 7442c97..be6bbbe 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,9 @@ Name | Title | Aut | ACTION | Description | | ------------------------------------------ | ------------------------------------------------------------------------------------------------- | +| [`ADDRESS`](./docs/actions/ADDRESS.md) | Configure address specific options | | [`AIRDROP`](./docs/actions/AIRDROP.md) | Transfer/Distribute `token` supply to a `LIST` | | [`BATCH`](./docs/actions/BATCH.md) | Execute multiple BTNS `ACTION` commands in a single transaction | -| [`BET`](./docs/actions/BET.md) | Bet `token` on `broadcast` oracle feed outcomes | | [`CALLBACK`](./docs/actions/CALLBACK.md) | Return all `token` supply to owner address after a set block, in exchange for a different `token` | | [`DESTROY`](./docs/actions/DESTROY.md) | Destroy `token` supply forever | | [`DISPENSER`](./docs/actions/DISPENSER.md) | Create a dispenser (vending machine) to dispense a `token` when triggered | diff --git a/docs/BTNS-420.md b/docs/BTNS-420.md index 4942046..a7b2a0e 100644 --- a/docs/BTNS-420.md +++ b/docs/BTNS-420.md @@ -62,9 +62,9 @@ Below is a list of the defined BTNS `ACTION` commands and the function of each: | ACTION | Description | | ------------------------------------- | ------------------------------------------------------------------------------------------------- | +| [`ADDRESS`](./actions/ADDRESS.md) | Configures address specific options | | [`AIRDROP`](./actions/AIRDROP.md) | Transfer/Distribute `token` supply to a `LIST` | | [`BATCH`](./actions/BATCH.md) | Execute multiple BTNS `ACTION` commands in a single transaction | -| [`BET`](./actions/BET.md) | Bet `token` on `broadcast` oracle feed outcomes | | [`CALLBACK`](./actions/CALLBACK.md) | Return all `token` supply to owner address after a set block, in exchange for a different `token` | | [`DESTROY`](./actions/DESTROY.md) | Destroy `token` supply forever | | [`DISPENSER`](./actions/DISPENSER.md) | Create a dispenser (vending machine) to dispense a `token` when triggered | diff --git a/docs/actions/ADDRESS.md b/docs/actions/ADDRESS.md new file mode 100644 index 0000000..4e9fdff --- /dev/null +++ b/docs/actions/ADDRESS.md @@ -0,0 +1,39 @@ +# ADDRESS command +This command configures address specific options. + +## PARAMS +| Name | Type | Description | +| ---------------- | ------ | ----------------------------------------| +| `VERSION` | String | Broadcast Format Version | +| `FEE_PREFERENCE` | String | Set preference for how `FEE` is used | +| `REQUIRE_MEMO` | String | Require a `MEMO` on any received `SEND` | + +## Formats + +### Version `0` +- `VERSION|FEE_PREFERENCE|REQUIRE_MEMO` + +## Examples +``` +bt:ADDRESS|0|1|0 +This example sets the address to DESTROY fees +``` + +``` +bt:ADDRESS|0|2|0 +This example sets the address to DONATE fees +``` + +``` +bt:ADDRESS|0|0|1 +This example sets the address to require a `MEMO` on any received `SEND` +``` + +## `FEE_PREFERENCE` Options +- `1` = Destroy `FEE`, provably lowering supply +- `2` = Donate `FEE` to protocol development (default) +- `3` = Donate `FEE` to community development + +## Notes +- `ADDR` `ACTION` can be used for shorter reference to `ADDRESS` `ACTION` + diff --git a/docs/actions/AIRDROP.md b/docs/actions/AIRDROP.md index 068bdef..a9069d5 100644 --- a/docs/actions/AIRDROP.md +++ b/docs/actions/AIRDROP.md @@ -39,6 +39,7 @@ This example airdops 1 GAS to every holder on a list and 2 BRRR to every holder ## Rules ## Notes +- `DROP` `ACTION` can be used for shorter reference to `AIRDROP` `ACTION` - `AIRDROP` to `address` `LIST` sends `AMOUNT` of `token` to each address on the list - `AIRDROP` to `token` `LIST` sends `AMOUNT` of `token` to holders of each `token` on the list - `AIRDROP` to `ASSET` `LIST` sends `AMOUNT` of `token` to holders of each `ASSET` on the list diff --git a/docs/actions/BATCH.md b/docs/actions/BATCH.md index 2727687..b338a4e 100644 --- a/docs/actions/BATCH.md +++ b/docs/actions/BATCH.md @@ -14,12 +14,15 @@ This command batch executes multiple `BTNS` `ACTION` commands in a single transa ## Examples ``` -bt:BATCH|0|MINT|0|GAS|1000;ISSUE|0|JDOG -This example mints 1000 GAS tokens and reserves the JDOG token +bt:BATCH|0|MINT|0|GAS|60;ISSUE|0|JDOG +This example mints 60 GAS tokens and reserves the JDOG token ``` ## Rules - Can only use one `MINT` command in a `BATCH` command - Can only use one `ISSUE` command in a `BATCH` command +- Can only use one `LIST` command in a `BATCH` command +- Can not use `BATCH` as a command in a `BATCH` command ## Notes +- `COMMANDS` are separated by a semi-colon `;` diff --git a/docs/actions/ISSUE.md b/docs/actions/ISSUE.md index 244722a..f73d8e8 100644 --- a/docs/actions/ISSUE.md +++ b/docs/actions/ISSUE.md @@ -13,8 +13,10 @@ This command creates or issues a `BTNS` `token` | `MINT_SUPPLY` | String | Amount of token supply to mint in immediately (default:0) | | `TRANSFER` | String | Address to transfer ownership of the `token` to (owner can perform future actions on token) | | `TRANSFER_SUPPLY` | String | Address to transfer `MINT_SUPPLY` to (mint initial supply and transfer to address) | -| `LOCK_SUPPLY` | String | Lock `MAX_SUPPLY` permanently (cannot increase `MAX_SUPPLY`) | -| `LOCK_MINT` | String | Lock `MAX_MINT` permanently (cannot edit `MAX_MINT`) | +| `LOCK_MAX_SUPPLY` | String | Lock `MAX_SUPPLY` permanently (cannot increase `MAX_SUPPLY`) | +| `LOCK_MAX_MINT` | String | Lock `MAX_MINT` permanently (cannot edit `MAX_MINT`) | +| `LOCK_MINT` | String | Lock `token` against `MINT` command | +| `LOCK_MINT_SUPPLY` | String | Lock `token` against issuing additional supply via `MINT_SUPPLY` | | `LOCK_DESCRIPTION` | String | Lock `token` against `DESCRIPTION` changes | | `LOCK_RUG` | String | Lock `token` against `RUG` command | | `LOCK_SLEEP` | String | Lock `token` against `SLEEP` command | @@ -32,7 +34,7 @@ This command creates or issues a `BTNS` `token` ## Formats ### Version `0` -- `VERSION|TICK|MAX_SUPPLY|MAX_MINT|DECIMALS|DESCRIPTION|MINT_SUPPLY|TRANSFER|TRANSFER_SUPPLY|LOCK_SUPPLY|LOCK_MINT|LOCK_DESCRIPTION|LOCK_RUG|LOCK_SLEEP|LOCK_CALLBACK|CALLBACK_BLOCK|CALLBACK_TICK|CALLBACK_AMOUNT|ALLOW_LIST|BLOCK_LIST|MINT_ADDRESS_MAX|MINT_START_BLOCK|MINT_STOP_BLOCK` +- `VERSION|TICK|MAX_SUPPLY|MAX_MINT|DECIMALS|DESCRIPTION|MINT_SUPPLY|TRANSFER|TRANSFER_SUPPLY|LOCK_MAX_SUPPLY|LOCK_MAX_MINT|LOCK_DESCRIPTION|LOCK_RUG|LOCK_SLEEP|LOCK_CALLBACK|CALLBACK_BLOCK|CALLBACK_TICK|CALLBACK_AMOUNT|ALLOW_LIST|BLOCK_LIST|MINT_ADDRESS_MAX|MINT_START_BLOCK|MINT_STOP_BLOCK|LOCK_MINT|LOCK_MINT_SUPPLY` ### Version `1` - Edit `DESCRIPTION` - `VERSION|TICK|DESCRIPTION` @@ -41,7 +43,7 @@ This command creates or issues a `BTNS` `token` - `VERSION|TICK|MAX_MINT|MINT_SUPPLY|TRANSFER_SUPPLY|MINT_ADDRESS_MAX|MINT_START_BLOCK|MINT_STOP_BLOCK` ### Version `3` - Edit `LOCK` `PARAMS` -- `VERSION|TICK|LOCK_SUPPLY|LOCK_MINT|LOCK_DESCRIPTION|LOCK_RUG|LOCK_SLEEP|LOCK_CALLBACK` +- `VERSION|TICK|LOCK_MAX_SUPPLY|LOCK_MAX_MINT|LOCK_DESCRIPTION|LOCK_RUG|LOCK_SLEEP|LOCK_CALLBACK|LOCK_MINT|LOCK_MINT_SUPPLY` ### Version `4` - Edit `CALLBACK` `PARAMS` - `VERSION|TICK|LOCK_CALLBACK|CALLBACK_BLOCK|CALLBACK_TICK` @@ -57,7 +59,7 @@ This example issues a JDOG token ``` bt:ISSUE|0|JDOG|1||||1|||1 -This example issues a JDOG token with MAX_SUPPLY set to 1, Mints 1 token via MINT_SUPPLY, and has LOCK_SUPPLY set to 1 to permanently lock the MAX_SUPPLY +This example issues a JDOG token with MAX_SUPPLY set to 1, Mints 1 token via MINT_SUPPLY, and has LOCK_MAX_SUPPLY set to 1 to permanently lock the MAX_SUPPLY ``` ``` @@ -107,13 +109,14 @@ This example issues a TEST token with a max supply of 100, and a maximum mint of - `DECIMALS` can not be changed after `token` supply is issued and/or minted - `MAX_SUPPLY` max value is 1,000,000,000,000,000,000,000 (1 Sextillion) - `MAX_SUPPLY` can not be set below existing supply -- `LOCK_SUPPLY` can not be set to `1` and permanently locked until `MIN_TOKEN_SUPPLY` supply exists. +- `LOCK_MAX_SUPPLY` can not be set to `1` and permanently locked until `MIN_TOKEN_SUPPLY` supply exists. ## Notes - `ISSUE` `TICK` with `MAX_SUPPLY` and `MINT_SUPPLY` set to any non `0` value, to mint supply until `MAX_SUPPLY` is reached (owner can mint beyond `MAX_MINT`) - `ISSUE` `TICK` with `MAX_SUPPLY` and `MAX_MINT` set to any non `0` value, to enable user minting (fair minting) -- `ISSUE` `TICK` with `LOCK_SUPPLY` set to `1` to permanently lock `MAX_SUPPLY` -- `ISSUE` `TICK` with `LOCK_MINT` set to `1` to permanently lock `MAX_MINT` +- `ISSUE` `TICK` with `LOCK_MAX_SUPPLY` set to `1` to permanently lock `MAX_SUPPLY` +- `ISSUE` `TICK` with `LOCK_MAX_MINT` set to `1` to permanently lock `MAX_MINT` +- `ISSUE` `TICK` with `LOCK_MINT` set to `1` to permanently prevent use of the `MINT` command - `ISSUE` `TICK` with `LOCK_RUG` set to `1` to permanently prevent use of the `RUG` command - `ISSUE` `TICK` with `LOCK_SLEEP` set to `1` to permanently prevent use of the `SLEEP` command - `ISSUE` `TICK` with `LOCK_CALLBACK` set to `1` to permanently lock `CALLBACK_BLOCK`, `CALLBACK_TICK`, and `CALLBACK_AMOUNT` @@ -126,5 +129,4 @@ This example issues a TEST token with a max supply of 100, and a maximum mint of - If `TICK` contains any unicode characters, then `TICK` should be `base64` encoded - `counterparty` `ASSET` and `SUBASSET` names are reserved within the BTNS for use by the `counterparty` owner - `MINT_ADDRESS_MAX` can be used to limit the maximum `TICK` `AMOUNT` that a single address can `MINT` -- `MINT_START_BLOCK` and `MINT_STOP_BLOCK` can be used to determine period(s) when `MINT` transactions are allowed - +- `MINT_START_BLOCK` and `MINT_STOP_BLOCK` can be used to determine period(s) when `MINT` transactions are allowed \ No newline at end of file diff --git a/docs/actions/SEND.md b/docs/actions/SEND.md index a047e71..75c5a0b 100644 --- a/docs/actions/SEND.md +++ b/docs/actions/SEND.md @@ -13,16 +13,16 @@ This command sends/transfers one or more `token`s between addresses ## Formats -### Version `0` +### Version `0` - Single Send - `VERSION|TICK|AMOUNT|DESTINATION|MEMO` -### Version `1` +### Version `1` - Multi-Send (Brief) - `VERSION|TICK|AMOUNT|DESTINATION|AMOUNT|DESTINATION|MEMO` -### Version `2` +### Version `2` - Multi-Send (Full) - `VERSION|TICK|AMOUNT|DESTINATION|TICK|AMOUNT|DESTINATION|MEMO` -### Version `3` +### Version `3` - Multi-Send (Full) with Multiple Memos - `VERSION|TICK|AMOUNT|DESTINATION|MEMO|TICK|AMOUNT|DESTINATION|MEMO` diff --git a/indexer/CHANGELOG.md b/indexer/CHANGELOG.md index 62e2467..b1cc352 100644 --- a/indexer/CHANGELOG.md +++ b/indexer/CHANGELOG.md @@ -1,5 +1,16 @@ CHANGELOG --- +0.12.0 +- Added support for `--reparse` +- Optimized ledger hashing +- `ADDRESS` support +- Renamed `LOCK_MINT` param to `LOCK_MAX_MINT` +- Renamed `LOCK_SUPPLY` param to `LOCK_MAX_SUPPLY` +- Added `LOCK_MINT` param to lock against `MINT` command +- Added `LOCK_MINT_SUPPLY` param +- `BATCH` support +- `AIRDROP` support + 0.11.1 - Added support for `MINT_START_BLOCK` - Added support for `MINT_STOP_BLOCK` diff --git a/indexer/includes/actions/address.php b/indexer/includes/actions/address.php new file mode 100644 index 0000000..4becd67 --- /dev/null +++ b/indexer/includes/actions/address.php @@ -0,0 +1,72 @@ + 'VERSION|FEE_PREFERENCE|REQUIRE_MEMO' + ); + + /***************************************************************** + * DEBUGGING - Force params + ****************************************************************/ + // $str = "0|1|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]); + + /***************************************************************** + * FORMAT Validations + ****************************************************************/ + + // Verify FEE_PREFERENCE is numeric + if(!$error && isset($data->FEE_PREFERENCE) && !is_numeric($data->FEE_PREFERENCE)) + $error = "invalid: FEE_PREFERENCE (format)"; + + // Verify REQUIRE_MEMO is numeric + if(!$error && isset($data->REQUIRE_MEMO) && !is_numeric($data->REQUIRE_MEMO)) + $error = "invalid: REQUIRE_MEMO (format)"; + + /***************************************************************** + * General Validations + ****************************************************************/ + + // Verify FEE_PREFERENCE value is valid + if(!$error && isset($data->FEE_PREFERENCE) && !in_array($data->FEE_PREFERENCE,array(0,1,2))) + $error = 'invalid: FEE_PREFERENCE'; + + // Verify REQUIRE_MEMO value is valid + if(!$error && isset($data->REQUIRE_MEMO) && !in_array($data->REQUIRE_MEMO,array(0,1))) + $error = 'invalid: REQUIRE_MEMO'; + + // Determine final status + $data->STATUS = $status = ($error) ? $error : 'valid'; + + // Print status message + print "\n\t ADDRESS : {$data->SOURCE} : {$data->STATUS}"; + + // Create record in addresses table + createAddressOption($data); + +} + +?> \ No newline at end of file diff --git a/indexer/includes/actions/airdrop.php b/indexer/includes/actions/airdrop.php index 61968fd..0de28c2 100644 --- a/indexer/includes/actions/airdrop.php +++ b/indexer/includes/actions/airdrop.php @@ -1,20 +1,302 @@ 'VERSION|TICK|AMOUNT|LIST|MEMO', + 1 => 'VERSION|LIST|TICK|AMOUNT|TICK|AMOUNT|MEMO', + 2 => 'VERSION|TICK|AMOUNT|LIST|TICK|AMOUNT|LIST|MEMO', + 3 => 'VERSION|TICK|AMOUNT|LIST|MEMO|TICK|AMOUNT|LIST|MEMO' + ); + + /***************************************************************** + * DEBUGGING - Force params + ****************************************************************/ + // Single Airdrop + // $str = '0|AIRDROPTEST1|1|fbe2a4946dfefb232571d56ed1c84dd85299736ba356dc300296d65d59991362|test'; // ADDRESS LIST + // $str = '0|AIRDROPTEST2|1|55cd98493c0fe46aed95225d909a82793a9ba7b480dccdb3170a9cd1ce081093|test'; // TICK LIST + // $str = '0|AIRDROPTEST3|1|afd33c2042cd43b229a44c406f03bcc940702f9736f5a222dfa53295b641a00d|test'; // ASSET LIST + // Multi-Airdrop (brief) + // $str = '1|fbe2a4946dfefb232571d56ed1c84dd85299736ba356dc300296d65d59991362|AIRDROPTEST1|1|AIRDROPTEST2|2|test brief'; + // Multi-Airdrop (Full) + // $str = '2|AIRDROPTEST1|1|fbe2a4946dfefb232571d56ed1c84dd85299736ba356dc300296d65d59991362|AIRDROPTEST2|2|55cd98493c0fe46aed95225d909a82793a9ba7b480dccdb3170a9cd1ce081093|test full'; + // Multi-Airdrop (Full) w multiple memos + // $str = '3|AIRDROPTEST1|1|fbe2a4946dfefb232571d56ed1c84dd85299736ba356dc300296d65d59991362|memo1|AIRDROPTEST2|2|55cd98493c0fe46aed95225d909a82793a9ba7b480dccdb3170a9cd1ce081093|memo2|AIRDROPTEST3|3|afd33c2042cd43b229a44c406f03bcc940702f9736f5a222dfa53295b641a00d|memo3'; + // $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)'; + + // Array of airdrops [TICK, AMOUNT, LIST, MEMO] + $airdrops = array(); + + // Extract memo + $memo = NULL; + $last = count($params) - 1; + foreach($params as $idx => $param) + if($idx==$last && (($format==0 && $idx==4) || ($format==1 && $idx%2==0) || ($format==2 && $idx%3==1))) + $memo = $param; + + // Build out array of airdrops + $lastIdx = count($params) - 1; + foreach($params as $idx => $param){ + + // Single Airdrop + if($format==0 && $idx==0) + array_push($airdrops,[$params[1], $params[2], $params[3], $memo]); + + // Multi-Airdrop (brief) + if($format==1 && $idx>1 && $idx%2==1) + array_push($airdrops,[$params[$idx-1], $params[$idx], $params[1], $memo]); + + // Multi-Airdrop (Full) + if($format==2 && $idx>0 && $idx%3==1 && $idx < $lastIdx) + array_push($airdrops,[$params[$idx], $params[$idx+1], $params[$idx+2], $memo]); + + // Multi-Airdrop (Full) with Multiple Memos + if($format==3 && $idx>0 && $idx%4==1 && $idx < $lastIdx) + array_push($airdrops,[$params[$idx], $params[$idx+1], $params[$idx+2], $params[$idx+3]]); + } + + // Get token data for every TICK (reduces duplicated sql queries) + $ticks = []; + foreach($airdrops as $airdrop){ + $tick = $airdrop[0]; + if(!$ticks[$tick]) + $ticks[$tick] = getTokenInfo($tick, null, $data->BLOCK_INDEX, $data->TX_INDEX); + } + + // Get source address balances + $balances = getAddressBalances($data->SOURCE, null, $data->BLOCK_INDEX, $data->TX_INDEX); + + // Get SOURCE address preferences + $preferences = getAddressPreferences($data->SOURCE, $data->BLOCK_INDEX, $data->TX_INDEX); + + // Store original error value + $origError = $error; + + // Array of credits and debits + $credits = []; + $debits = []; + + // 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 + + // Loop through airdrops and process each + foreach($airdrops as $info){ + + // Reset $error to the original value + $error = $origError; + + // Copy base BTNS Transacation data object + $airdrop = clone($data); + + // Array of addresses that will receive this AIRDROP + $recipients = []; + + // Update BTNS transaction data object with send values + $airdrop->TICK = $info[0]; + $airdrop->AMOUNT = $info[1]; + $airdrop->LIST = $info[2]; + $airdrop->MEMO = $info[3]; + + // Get information on BTNS token + $btInfo = $ticks[$airdrop->TICK]; + + // Set divisible flag + $divisible = ($btInfo->DECIMALS==0) ? 0 : 1; + + // Validate TICK exists + if(!$error && !$btInfo) + $error = 'invalid: TICK (unknown)'; + + /************************************************************* + * FORMAT Validations + ************************************************************/ + + // Verify AMOUNT format + if(!$error && isset($airdrop->AMOUNT) && !isValidAmountFormat($divisible, $airdrop->AMOUNT)) + $error = "invalid: AMOUNT (format)"; + + // Verify LIST format + if(!$error && isset($airdrop->LIST) && !isValidTransactionHash($airdrop->LIST)) + $error = "invalid: LIST (format)"; + + /************************************************************* + * General Validations + ************************************************************/ + + // Verify no pipe in MEMO (BTNS uses pipe as field delimiter) + if(!$error && strpos($airdrop->MEMO,'|')!==false) + $error = 'invalid: MEMO (pipe)'; + + // Verify no semicolon in MEMO (BTNS uses semicolon as action delimiter) + if(!$error && strpos($airdrop->MEMO,';')!==false) + $error = 'invalid: MEMO (semicolon)'; + + // Lookup list information + if(!$error){ + $type = getListType($airdrop->LIST); + $list = getList($airdrop->LIST); + } + + // Verify LIST exist + if(!$error && $type===false) + $error = 'invalid: LIST (unknown)'; + + // Verify LIST type is supported + if(!$error && !in_array($type,array(1,2,3))) + $error = 'invalid: LIST TYPE (unsupported)'; + + // Handle ASSET / TICK LIST by looking up holders and adding to recipients list + if(!$error && in_array($type,array(1,2))){ + foreach($list as $tick){ + if($type==1) + $holders = getHolders($tick, $data->BLOCK_INDEX, $data->TX_INDEX); + if($type==2) + $holders = getAssetHolders($tick, $data->BLOCK_INDEX); + foreach($holders as $address => $amount){ + if(!in_array($address, $recipients)) + array_push($recipients, $address); + } + } + } + + // Handle ADDRESS LIST by passing forward addresses to recipients list + if(!$error && $type==3) + $recipients = $list; + + // Determine total DEBIT + $airdrop->DEBIT = bcmul(count($recipients),$airdrop->AMOUNT,$btInfo->DECIMALS); + + // Calculate total number of database hits for this AIRDROP + $db_hits = count($recipients) * 2; // 1 credits, 1 balances + $db_hits += 4; // 1 debits, 1 balances, 1 airdrops + + // Determine total transaction FEE based on database hits + $airdrop->FEE_TICK = 'GAS'; + $airdrop->FEE_AMOUNT = getTransactionFee($db_hits, $airdrop->FEE_TICK); + + // Verify SOURCE has enough balances to cover TICK total DEBIT amount + if(!$error && !hasBalance($balances, $airdrop->TICK, $airdrop->DEBIT)) + $error = 'invalid: insufficient funds (TICK)'; + + // Adjust balances to reduce by DEBIT amount + if(!$error) + $balances = debitBalances($balances, $airdrop->TICK, $airdrop->DEBIT); + + // Verify SOURCE has enough balances to cover FEE AMOUNT + if(!$error && !hasBalance($balances, $airdrop->FEE_TICK, $airdrop->FEE_AMOUNT)) + $error = 'invalid: insufficient funds (FEE)'; + + // Adjust balances to reduce by FEE amount + if(!$error) + $balances = debitBalances($balances, $airdrop->TICK, $airdrop->DEBIT); + + // Determine final status + $airdrop->STATUS = $status = ($error) ? $error : 'valid'; + + // Print status message + print "\n\t AIRDROP : {$airdrop->TICK} : {$airdrop->AMOUNT} : {$airdrop->LIST} : {$airdrop->STATUS}"; + + // Create record in airdrops table + createAirdrop($airdrop); + + // If this was a valid transaction, then add records to the credits and debits array + if($status=='valid'){ + + // Store the SOURCE and TICK in addresses list + addAddressTicker($airdrop->SOURCE, [$airdrop->TICK, $airdrop->FEE_TICK]); + + // Debit SOURCE with total DEBIT and FEE_AMOUNT + array_push($debits, array($airdrop->TICK, $airdrop->DEBIT)); + array_push($debits, array($airdrop->FEE_TICK, $airdrop->FEE_AMOUNT)); + + // Update FEES object with to AMOUNT + $fees->AMOUNT = bcadd($fees->AMOUNT, $airdrop->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, $airdrop->FEE_TICK); + + // Credit donation address with FEE_AMOUNT + array_push($credits, array($airdrop->FEE_TICK, $airdrop->FEE_AMOUNT, $address)); + } + + // Create record of FEE in `fees` table + createFeeRecord($fees); + + // Loop through recipient addresses + foreach($recipients as $address){ + + // Store the recipient ADDRESS and TICK in addresses list + addAddressTicker($address, $airdrop->TICK); + + // Credit address with TICK AMOUNT + array_push($credits, array($airdrop->TICK, $airdrop->AMOUNT, $address)); + } + } + } + + // Consolidate the credit and debit records to write as few records as possible + $debits = consolidateCreditDebitRecords('debits', $debits); + $credits = consolidateCreditDebitRecords('credits', $credits); + + // Create records in debits table + foreach($debits as $debit){ + [$tick, $amount] = $debit; + createDebit('AIRDROP', $data->BLOCK_INDEX, $data->TX_HASH, $tick, $amount, $data->SOURCE); + } + + // Create records in credits table + foreach($credits as $credit){ + [$tick, $amount, $destination] = $credit; + createCredit('AIRDROP', $data->BLOCK_INDEX, $data->TX_HASH, $tick, $amount, $destination); + } + + // If this is a reparse, bail out before updating balances + if($reparse) + return; + + // Store the SOURCE and TICKERS in addresses list + addAddressTicker($data->SOURCE, $tickers); + + // Update address balances + updateBalances(array_keys($addresses)); + + // Update supply for tokens + updateTokens($tickers); } ?> \ No newline at end of file diff --git a/indexer/includes/actions/batch.php b/indexer/includes/actions/batch.php index 61a2684..9d82d23 100644 --- a/indexer/includes/actions/batch.php +++ b/indexer/includes/actions/batch.php @@ -10,8 +10,85 @@ * 0 = VERSION|COMMAND;COMMAND ********************************************************************/ function btnsBatch($params=null, $data=null, $error=null){ - global $mysqli; - // Coming soon + global $mysqli, $reparse; + + // Define list of known FORMATS + $formats = array( + 0 => 'VERSION|COMMAND' + ); + + // Define list of ACTIONS and usage limits + $limits = array( + 'BATCH' => 0, + 'MINT' => 1, + 'ISSUE' => 1 + ); + + // Define list of ACTIONS and count of usage within BATCH + $actions = array(); + + // Clone the raw data for storage in batches table + $batch = clone($data); + + /***************************************************************** + * DEBUGGING - Force params + ****************************************************************/ + // $data->TX_RAW = "BATCH|0|MINT|0|GAS|60;ISSUE|0|JDOGTEST"; + + // Validate that format is known + $format = getFormatVersion($params[0]); + if(!$error && ($format===NULL || !in_array($format,array_keys($formats)))) + $error = 'invalid: VERSION (unknown)'; + + // Get list of commands + $commands = explode(';',$data->TX_RAW); + if(!$error && count($commands)==0){ + $error = 'invalid: COMMAND (unknown)'; + } else { + // Trim BATCH/VERSION from first command + $commands[0] = str_replace("BATCH|{$format}|",'',$commands[0]); + } + + // Build out array of ACTIONs and count of times used in BATCH + foreach($commands as $command){ + $action = explode('|',$command)[0]; + if(!$actions[$action]) + $actions[$action] = 0; + $actions[$action]++; + } + + /***************************************************************** + * General Validations + ****************************************************************/ + + // Verify all command ACTIONs are valid + foreach($commands as $command) + if(!$error && !array_key_exists(explode('|',$command)[0],PROTOCOL_CHANGES)) + $error = 'invalid: ACTION (unknown)'; + + // Verify command ACTION limits + foreach($actions as $action => $limit) + if(!$error && array_key_exists($action,$limits) && $actions[$action]>$limits[$action]) + $error = "invalid: {$action} (limit)"; + + // Determine final status + $batch->STATUS = $status = ($error) ? $error : 'valid'; + + // Print status message + print "\n\t BATCH : {$data->SOURCE} : {$batch->STATUS}"; + + // Create record in batches table + createBatch($batch); + + // Handle processing the specific BTNS ACTION commands + if($status=='valid'){ + foreach($commands as $command){ + $params = explode('|',$command); + $action = strtoupper(array_shift($params)); + btnsAction($action, $params, $data, $error); + } + } + } -?> \ No newline at end of file +?> diff --git a/indexer/includes/actions/bet.php b/indexer/includes/actions/bet.php index 68b495d..2d8ec01 100644 --- a/indexer/includes/actions/bet.php +++ b/indexer/includes/actions/bet.php @@ -19,7 +19,7 @@ * 0 = VERSION|FEED_ADDRESS|BET_TYPE|DEADLINE|WAGER_TICK|WAGER_AMOUNT|COUNTERWAGER_TICK|COUNTERWAGER_AMOUNT|EXPIRATION|LEVERAGE|TARGET_VALUE ********************************************************************/ function btnsBet($params=null, $data=null, $error=null){ - global $mysqli; + global $mysqli, $reparse; // Coming soon } diff --git a/indexer/includes/actions/callback.php b/indexer/includes/actions/callback.php index ae63d50..37e6248 100644 --- a/indexer/includes/actions/callback.php +++ b/indexer/includes/actions/callback.php @@ -10,7 +10,7 @@ * 0 = VERSION|TICK ********************************************************************/ function btnsCallback($params=null, $data=null, $error=null){ - global $mysqli; + global $mysqli, $reparse; // Coming soon } diff --git a/indexer/includes/actions/destroy.php b/indexer/includes/actions/destroy.php index 41ea966..ec7802c 100644 --- a/indexer/includes/actions/destroy.php +++ b/indexer/includes/actions/destroy.php @@ -15,7 +15,7 @@ * ********************************************************************/ function btnsDestroy($params=null, $data=null, $error=null){ - global $mysqli, $tickers, $addresses; + global $mysqli, $reparse, $addresses, $tickers; // Define list of known FORMATS $formats = array( @@ -69,18 +69,15 @@ function btnsDestroy($params=null, $data=null, $error=null){ foreach($destroys as $destroy){ $tick = $destroy[0]; if(!$ticks[$tick]) - $ticks[$tick] = getTokenInfo($tick); + $ticks[$tick] = getTokenInfo($tick, null, $data->BLOCK_INDEX, $data->TX_INDEX); } - // Get source address balances - $balances = getAddressBalances($data->SOURCE); + // Get source address balances at the time of the tx + $balances = getAddressBalances($data->SOURCE, null, $data->BLOCK_INDEX, $data->TX_INDEX); // Store original error value $origError = $error; - // Add SOURCE address to the addresses array - $addresses[$data->SOURCE] = 1; - // Array of debits $debits = []; @@ -148,8 +145,8 @@ function btnsDestroy($params=null, $data=null, $error=null){ // If this was a valid transaction, then create debit record if($status=='valid'){ - // Add ticker to tickers array - $tickers[$destroy->TICK] = 1; + // Store the SOURCE and TICK in addresses list + addAddressTicker($destroy->SOURCE, $destroy->TICK); // Add ticker and amount to debits array array_push($debits, array($destroy->TICK, $destroy->AMOUNT)); @@ -165,11 +162,15 @@ function btnsDestroy($params=null, $data=null, $error=null){ createDebit('DESTROY', $data->BLOCK_INDEX, $data->TX_HASH, $tick, $amount, $data->SOURCE); } + // If this is a reparse, bail out before updating balances + if($reparse) + return; + // Update address balances updateBalances(array_keys($addresses)); // Update supply for tokens - updateTokens(array_keys($ticks)); + updateTokens($tickers); } ?> \ No newline at end of file diff --git a/indexer/includes/actions/dispenser.php b/indexer/includes/actions/dispenser.php index 95f75b3..0d7f60b 100644 --- a/indexer/includes/actions/dispenser.php +++ b/indexer/includes/actions/dispenser.php @@ -19,7 +19,7 @@ * 0 = VERSION|GIVE_TICK|GIVE_AMOUNT|ESCROW_AMOUNT|TRIGGER_TICK|TRIGGER_AMOUNT|STATUS|ADDRESS|ORACLE_ADDRESS|ALLOW_LIST|BLOCK_LIST ********************************************************************/ function btnsDispenser($params=null, $data=null, $error=null){ - global $mysqli; + global $mysqli, $reparse; // Coming soon } diff --git a/indexer/includes/actions/dividend.php b/indexer/includes/actions/dividend.php index 94ee133..fd41113 100644 --- a/indexer/includes/actions/dividend.php +++ b/indexer/includes/actions/dividend.php @@ -12,7 +12,7 @@ * 0 = VERSION|TICK|DIVIDEND_TICK|AMOUNT ********************************************************************/ function btnsDividend($params=null, $data=null, $error=null){ - global $mysqli; + global $mysqli, $reparse; // Coming soon } diff --git a/indexer/includes/actions/issue.php b/indexer/includes/actions/issue.php index ed062aa..92d01b1 100644 --- a/indexer/includes/actions/issue.php +++ b/indexer/includes/actions/issue.php @@ -12,8 +12,9 @@ * - MINT_SUPPLY - Amount of token supply to mint in immediately (default:0) * - TRANSFER - Address to transfer ownership of the `token` to (owner can perform future actions on token) * - TRANSFER_SUPPLY - Address to transfer `MINT_SUPPLY` to (mint initial supply and transfer to address) - * - LOCK_SUPPLY - Lock `MAX_SUPPLY` permanently (cannot increase `MAX_SUPPLY`) - * - LOCK_MINT - Lock `MAX_MINT` permanently (cannot edit `MAX_MINT`) + * - LOCK_MAX_SUPPLY - Lock `MAX_SUPPLY` permanently (cannot increase `MAX_SUPPLY`) + * - LOCK_MINT - Lock `token` against `MINT` command + * - LOCK_MAX_MINT - Lock `MAX_MINT` permanently (cannot edit `MAX_MINT`) * - LOCK_DESCRIPTION - Lock `token` against `DESCRIPTION` changes * - LOCK_RUG - Lock `token` against `RUG` command * - LOCK_SLEEP - Lock `token` against `SLEEP` command @@ -36,21 +37,21 @@ * ********************************************************************/ function btnsIssue( $params=null, $data=null, $error=null){ - global $mysqli, $tickers, $addresses; + global $mysqli, $reparse, $addresses, $tickers; // Define list of known FORMATS $formats = array( - 0 => 'VERSION|TICK|MAX_SUPPLY|MAX_MINT|DECIMALS|DESCRIPTION|MINT_SUPPLY|TRANSFER|TRANSFER_SUPPLY|LOCK_SUPPLY|LOCK_MINT|LOCK_DESCRIPTION|LOCK_RUG|LOCK_SLEEP|LOCK_CALLBACK|CALLBACK_BLOCK|CALLBACK_TICK|CALLBACK_AMOUNT|ALLOW_LIST|BLOCK_LIST|MINT_ADDRESS_MAX|MINT_START_BLOCK|MINT_STOP_BLOCK', + 0 => 'VERSION|TICK|MAX_SUPPLY|MAX_MINT|DECIMALS|DESCRIPTION|MINT_SUPPLY|TRANSFER|TRANSFER_SUPPLY|LOCK_MAX_SUPPLY|LOCK_MAX_MINT|LOCK_DESCRIPTION|LOCK_RUG|LOCK_SLEEP|LOCK_CALLBACK|CALLBACK_BLOCK|CALLBACK_TICK|CALLBACK_AMOUNT|ALLOW_LIST|BLOCK_LIST|MINT_ADDRESS_MAX|MINT_START_BLOCK|MINT_STOP_BLOCK|LOCK_MINT|LOCK_MINT_SUPPLY', 1 => 'VERSION|TICK|DESCRIPTION', 2 => 'VERSION|TICK|MAX_MINT|MINT_SUPPLY|TRANSFER_SUPPLY|MINT_ADDRESS_MAX|MINT_START_BLOCK|MINT_STOP_BLOCK', - 3 => 'VERSION|TICK|LOCK_SUPPLY|LOCK_MINT|LOCK_DESCRIPTION|LOCK_RUG|LOCK_SLEEP|LOCK_CALLBACK', + 3 => 'VERSION|TICK|LOCK_MAX_SUPPLY|LOCK_MAX_MINT|LOCK_DESCRIPTION|LOCK_RUG|LOCK_SLEEP|LOCK_CALLBACK|LOCK_MINT|LOCK_MINT_SUPPLY', 4 => 'VERSION|TICK|LOCK_CALLBACK|CALLBACK_BLOCK|CALLBACK_TICK' ); // Define list of AMOUNT and LOCK fields (used in validations) $fieldList = array( 'AMOUNT' => array('MAX_SUPPLY', 'MAX_MINT', 'MINT_SUPPLY', 'CALLBACK_AMOUNT', 'MINT_ADDRESS_MAX', 'MINT_START_BLOCK', 'MINT_STOP_BLOCK'), - 'LOCK' => array('LOCK_SUPPLY', 'LOCK_MINT', 'LOCK_DESCRIPTION', 'LOCK_RUG', 'LOCK_SLEEP', 'LOCK_CALLBACK') + 'LOCK' => array('LOCK_MAX_SUPPLY', 'LOCK_MINT', 'LOCK_MINT_SUPPLY', 'LOCK_MAX_MINT', 'LOCK_DESCRIPTION', 'LOCK_RUG', 'LOCK_SLEEP', 'LOCK_CALLBACK') ); /***************************************************************** @@ -98,15 +99,18 @@ function btnsIssue( $params=null, $data=null, $error=null){ $error = 'invalid: GAS_ADDRESS'; // Get information on BTNS token - $btInfo = getTokenInfo($data->TICK); - $isDistributed = isDistributed($data->TICK); + $btInfo = getTokenInfo($data->TICK, null, $data->BLOCK_INDEX, $data->TX_INDEX); + $isDistributed = isDistributed($data->TICK, $data->BLOCK_INDEX, $data->TX_INDEX); + + // Clone the raw data for storage in issues table + $issue = clone($data); // Populate empty PARAMS with current setting if($btInfo){ foreach($btInfo as $key => $value) if(!$data->{$key}) $data->{$key} = $value; - } + } // If BTNS Token does not exist yet, do some additional validations if(!$btInfo){ @@ -136,14 +140,14 @@ function btnsIssue( $params=null, $data=null, $error=null){ // Verify AMOUNT field formats foreach($fieldList['AMOUNT'] as $name){ - $value = $data->{$name}; + $value = $issue->{$name}; if(!$error && isset($value) && !isValidAmountFormat($divisible, $value)) $error = "invalid: {$name} (format)"; } // Verify LOCK field formats foreach($fieldList['LOCK'] as $name){ - $value = $data->{$name}; + $value = $issue->{$name}; if(!$error && isset($value) && !isValidLockValue($value)) $error = "invalid: {$name} (format)"; } @@ -158,22 +162,22 @@ function btnsIssue( $params=null, $data=null, $error=null){ // Verify LOCK fields cannot be changed once enabled/locked foreach($fieldList['LOCK'] as $name){ - $value = $data->{$name}; - if(!$error && isset($value) && !isValidLock($btInfo, $data, $name)) + $value = $issue->{$name}; + if(!$error && isset($value) && !isValidLock($btInfo, $issue, $name)) $error = "invalid: {$name} (locked)"; } // Verify MAX_SUPPLY min/max - if(!$error && isset($data->MAX_SUPPLY) && ($data->MAX_SUPPLY < MIN_TOKEN_SUPPLY || $data->MAX_SUPPLY > MAX_TOKEN_SUPPLY)) + if(!$error && isset($data->MAX_SUPPLY) && $data->MAX_SUPPLY > 0 && ($data->MAX_SUPPLY < MIN_TOKEN_SUPPLY || $data->MAX_SUPPLY > MAX_TOKEN_SUPPLY)) $error = 'invalid: MAX_SUPPLY (min/max)'; // Verify MAX_SUPPLY is not set below current SUPPLY - if(!$error && isset($data->MAX_SUPPLY) && $data->MAX_SUPPLY < $data->SUPPLY) + if(!$error && isset($data->MAX_SUPPLY) && $data->MAX_SUPPLY > 0 && $data->MAX_SUPPLY < getTokenSupply($data->TICK, null, $data->BLOCK_INDEX, $data->TX_INDEX)) $error = 'invalid: MAX_SUPPLY < SUPPLY'; - // Verify SUPPLY is at least MIN_TOKEN_SUPPLY before allowing LOCK_SUPPLY - if(!$error && $data->LOCK_SUPPLY && (($btInfo && $btInfo->SUPPLY < MIN_TOKEN_SUPPLY) || (!$btInfo && $data->MINT_SUPPLY < MIN_TOKEN_SUPPLY))) - $error = 'invalid: LOCK_SUPPLY (no supply)'; + // Verify SUPPLY is at least MIN_TOKEN_SUPPLY before allowing LOCK_MAX_SUPPLY + if(!$error && $data->LOCK_MAX_SUPPLY && (($btInfo && $btInfo->SUPPLY < MIN_TOKEN_SUPPLY) || (!$btInfo && $data->MINT_SUPPLY < MIN_TOKEN_SUPPLY))) + $error = 'invalid: LOCK_MAX_SUPPLY (no supply)'; // Verify DECIMAL min/max if(!$error && isset($data->DECIMALS) && ($data->DECIMALS < MIN_TOKEN_DECIMALS || $data->DECIMALS > MAX_TOKEN_DECIMALS)) @@ -195,6 +199,10 @@ function btnsIssue( $params=null, $data=null, $error=null){ if(!$error && isset($data->TRANSFER_SUPPLY) && !isCryptoAddress($data->TRANSFER_SUPPLY)) $error = 'invalid: TRANSFER_SUPPLY (bad address)'; + // Verify MINT_SUPPLY is allowed and LOCK_MINT_SUPPLY is not set + if(!$error && isset($data->MINT_SUPPLY) && $btInfo && $btInfo->LOCK_MINT_SUPPLY==1) + $error = 'invalid: MINT_SUPPLY (locked)'; + // Verify MINT_SUPPLY is less than MAX_SUPPLY if(!$error && isset($data->MINT_SUPPLY) && $data->MINT_SUPPLY > $data->MAX_SUPPLY) $error = 'invalid: MINT_SUPPLY > MAX_SUPPLY'; @@ -207,12 +215,12 @@ function btnsIssue( $params=null, $data=null, $error=null){ if(!$error && isset($data->MINT_ADDRESS_MAX) && $data->MINT_ADDRESS_MAX > 0 && $data->MINT_ADDRESS_MAX < $data->MAX_MINT) $error = 'invalid: MINT_ADDRESS_MAX < MAX_MINT'; - // Verify MAX_SUPPLY can not be changed if LOCK_SUPPLY is enabled - if(!$error && $btInfo && $btnInfo->LOCK_SUPPLY && isset($data->MAX_SUPPLY) && $data->MAX_SUPPLY!=$btnInfo->MAX_SUPPLY) + // Verify MAX_SUPPLY can not be changed if LOCK_MAX_SUPPLY is enabled + if(!$error && $btInfo && $btnInfo->LOCK_MAX_SUPPLY && isset($data->MAX_SUPPLY) && $data->MAX_SUPPLY!=$btnInfo->MAX_SUPPLY) $error = 'invalid: MAX_SUPPLY (locked)'; - // Verify MAX_MINT can not be changed if LOCK_MINT is enabled - if(!$error && $btInfo && $btnInfo->LOCK_MINT && isset($data->MAX_MINT) && $data->MAX_MINT!=$btnInfo->MAX_MINT) + // Verify MAX_MINT can not be changed if LOCK_MAX_MINT is enabled + if(!$error && $btInfo && $btnInfo->LOCK_MAX_MINT && isset($data->MAX_MINT) && $data->MAX_MINT!=$btnInfo->MAX_MINT) $error = 'invalid: MAX_MINT (locked)'; // Verify DESCRIPTION is less than or equal to MAX_TOKEN_DESCRIPTION @@ -272,27 +280,17 @@ function btnsIssue( $params=null, $data=null, $error=null){ $error = 'invalid: MINT_STOP_BLOCK < MINT_START_BLOCK'; // Determine final status - $data->STATUS = $status = ($error) ? $error : 'valid'; + $data->STATUS = $issue->STATUS = $status = ($error) ? $error : 'valid'; // Print status message print "\n\t ISSUE : {$data->TICK} : {$data->STATUS}"; // Create record in issues table - createIssue($data); + createIssue($issue); // If this was a valid transaction, then create the token record, and perform any additional actions if($status=='valid'){ - // Add the ticker to the tickers array - $tickers[$data->TICK] = 1; - - // Add any addresses to the addresses array - $addresses[$data->SOURCE] = 1; - if($data->TRANSFER) - $addresses[$data->TRANSFER] = 1; - if($data->TRANSFER_SUPPLY) - $addresses[$data->TRANSFER_SUPPLY] = 1; - // Support token ownership transfers $data->OWNER = (isset($data->TRANSFER)) ? $data->TRANSFER : $data->SOURCE; @@ -309,6 +307,10 @@ function btnsIssue( $params=null, $data=null, $error=null){ createCredit('ISSUE', $data->BLOCK_INDEX, $data->TX_HASH, $data->TICK, $data->MINT_SUPPLY, $data->TRANSFER_SUPPLY); } + // If this is a reparse, bail out before updating balances and token information + if($reparse) + return; + // Update balances for addresses updateBalances([$data->SOURCE, $data->TRANSFER_SUPPLY]); diff --git a/indexer/includes/actions/list.php b/indexer/includes/actions/list.php index fa95f92..fd1d172 100644 --- a/indexer/includes/actions/list.php +++ b/indexer/includes/actions/list.php @@ -15,7 +15,7 @@ * ********************************************************************/ function btnsList( $params=null, $data=null, $error=null){ - global $mysqli, $tickers, $addresses; + global $mysqli, $reparse, $addresses, $tickers; // Define list of known FORMATS $formats = array( diff --git a/indexer/includes/actions/mint.php b/indexer/includes/actions/mint.php index 746fe1f..106d865 100644 --- a/indexer/includes/actions/mint.php +++ b/indexer/includes/actions/mint.php @@ -13,7 +13,7 @@ * ********************************************************************/ function btnsMint($params=null, $data=null, $error=null){ - global $mysqli, $tickers, $addresses; + global $mysqli, $reparse, $addresses, $tickers; // Define list of known FORMATS $formats = array( @@ -36,7 +36,14 @@ function btnsMint($params=null, $data=null, $error=null){ $data = setActionParams($data, $params, $formats[$format]); // Get information on BTNS token - $btInfo = getTokenInfo($data->TICK); + $btInfo = getTokenInfo($data->TICK, null, $data->BLOCK_INDEX, $data->TX_INDEX); + + // Clone the raw data for storage in mints table + $mint = clone($data); + + // Verify TICK is valid before MINT + if($btInfo->BLOCK_INDEX==$data->BLOCK_INDEX && !validTickerBeforeTxIndex($data->TICK, $data->TX_INDEX)) + unset($btInfo); // Set divisible flag $divisible = ($btInfo->DECIMALS==0) ? 0 : 1; @@ -60,6 +67,14 @@ function btnsMint($params=null, $data=null, $error=null){ $data->MINT_STOP_BLOCK = ($btInfo) ? $btInfo->MINT_STOP_BLOCK : 0; } + /***************************************************************** + * ACTION Validations + ****************************************************************/ + + // Verify MINT is allowed + if(!$error && isset($btInfo->LOCK_MINT) && $btInfo->LOCK_MINT==1) + $error = "invalid: LOCK_MINT"; + /***************************************************************** * FORMAT Validations ****************************************************************/ @@ -93,7 +108,7 @@ function btnsMint($params=null, $data=null, $error=null){ $error = 'invalid: DESTINATION (not authorized)'; // Verify minting AMOUNT will not exceed MINT_ADDRESS_MAX - if(!$error && isset($data->MINT_ADDRESS_MAX) && $data->MINT_ADDRESS_MAX > 0 && (bcadd(getActionCreditDebitAmount('credits', 'MINT', $data->TICK, $data->SOURCE),$data->AMOUNT,$data->DECIMALS) > $data->MINT_ADDRESS_MAX)) + if(!$error && isset($data->MINT_ADDRESS_MAX) && $data->MINT_ADDRESS_MAX > 0 && (bcadd(getActionCreditDebitAmount('credits', 'MINT', $data->TICK, $data->SOURCE, $data->TX_INDEX),$data->AMOUNT,$data->DECIMALS) > $data->MINT_ADDRESS_MAX)) $error = 'invalid: mint exceeds MINT_ADDRESS_MAX'; // Verify minting begins at MINT_START_BLOCK @@ -105,27 +120,17 @@ function btnsMint($params=null, $data=null, $error=null){ $error = 'invalid: MINT_STOP_BLOCK'; // Determine final status - $data->STATUS = $status = ($error) ? $error : 'valid'; + $data->STATUS = $mint->STATUS = $status = ($error) ? $error : 'valid'; // Print status message print "\n\t MINT : {$data->TICK} : {$data->AMOUNT} : {$data->STATUS}"; // Create record in mints table - createMint($data); + createMint($mint); // If this was a valid transaction, then mint any actual supply if($status=='valid'){ - // Add the ticker to the tickers array - $tickers[$data->TICK] = 1; - - // Add SOURCE address to the addresses array - $addresses[$data->SOURCE] = 1; - - // Add DESTINATION address to the addresses array - if($data->DESTINATION) - $addresses[$data->DESTINATION] = 1; - // Credit MINT_SUPPLY to source address if($data->AMOUNT){ createCredit('MINT', $data->BLOCK_INDEX, $data->TX_HASH, $data->TICK, $data->AMOUNT, $data->SOURCE); @@ -137,6 +142,10 @@ function btnsMint($params=null, $data=null, $error=null){ } } + // If this is a reparse, bail out before updating balances and token information + if($reparse) + return; + // Update balances for addresses updateBalances([$data->SOURCE, $data->DESTINATION]); diff --git a/indexer/includes/actions/rug.php b/indexer/includes/actions/rug.php index 4b36ea6..3e11274 100644 --- a/indexer/includes/actions/rug.php +++ b/indexer/includes/actions/rug.php @@ -10,7 +10,7 @@ * 0 = VERSION|TICK ********************************************************************/ function btnsRug($params=null, $data=null, $error=null){ - global $mysqli; + global $mysqli, $reparse; // Coming soon } diff --git a/indexer/includes/actions/send.php b/indexer/includes/actions/send.php index 615c7c6..5ba304e 100644 --- a/indexer/includes/actions/send.php +++ b/indexer/includes/actions/send.php @@ -17,7 +17,7 @@ * ********************************************************************/ function btnsSend($params=null, $data=null, $error=null){ - global $mysqli, $tickers, $addresses; + global $mysqli, $reparse, $addresses, $tickers; // Define list of known FORMATS $formats = array( @@ -84,18 +84,15 @@ function btnsSend($params=null, $data=null, $error=null){ foreach($sends as $send){ $tick = $send[0]; if(!$ticks[$tick]) - $ticks[$tick] = getTokenInfo($tick); + $ticks[$tick] = getTokenInfo($tick, null, $data->BLOCK_INDEX, $data->TX_INDEX); } - // Get source address balances - $balances = getAddressBalances($data->SOURCE); + // Get source address balances + $balances = getAddressBalances($data->SOURCE, null, $data->BLOCK_INDEX, $data->TX_INDEX); // Store original error value $origError = $error; - // Add SOURCE address to the addresses array - $addresses[$data->SOURCE] = 1; - // Array of credits and debits $credits = []; $debits = []; @@ -177,11 +174,8 @@ function btnsSend($params=null, $data=null, $error=null){ // If this was a valid transaction, then add records to the credits and debits array if($status=='valid'){ - // Add ticker to tickers array - $tickers[$send->TICK] = 1; - - // Add destination address to addresses array - $addresses[$send->DESTINATION] = 1; + // Store the DESTINATION and TICK in addresses list + addAddressTicker($send->DESTINATION, $send->TICK); // Add ticker and amount to debits array array_push($debits, array($send->TICK, $send->AMOUNT)); @@ -207,6 +201,13 @@ function btnsSend($params=null, $data=null, $error=null){ createCredit('SEND', $data->BLOCK_INDEX, $data->TX_HASH, $tick, $amount, $destination); } + // If this is a reparse, bail out before updating balances + if($reparse) + return; + + // Store the SOURCE and TICKERS in addresses list + addAddressTicker($send->SOURCE, $tickers); + // Update address balances updateBalances(array_keys($addresses)); } diff --git a/indexer/includes/actions/sleep.php b/indexer/includes/actions/sleep.php index 515efdf..96c173a 100644 --- a/indexer/includes/actions/sleep.php +++ b/indexer/includes/actions/sleep.php @@ -11,7 +11,7 @@ * 0 = VERSION|TICK|RESUME_BLOCK ********************************************************************/ function btnsSleep($params=null, $data=null, $error=null){ - global $mysqli; + global $mysqli, $reparse; // Coming soon } diff --git a/indexer/includes/actions/sweep.php b/indexer/includes/actions/sweep.php index 4008d28..959aa15 100644 --- a/indexer/includes/actions/sweep.php +++ b/indexer/includes/actions/sweep.php @@ -13,7 +13,7 @@ * 0 = VERSION|DESTINATION|SWEEP_BALANCES|SWEEP_OWNERSHIP|MEMO ********************************************************************/ function btnsSweep($params=null, $data=null, $error=null){ - global $mysqli; + global $mysqli, $reparse; // Coming soon } diff --git a/indexer/includes/config.php b/indexer/includes/config.php index 0ed44e7..104393e 100644 --- a/indexer/includes/config.php +++ b/indexer/includes/config.php @@ -9,8 +9,8 @@ // BTNS Indexer Version define("VERSION_MAJOR", 0); -define("VERSION_MINOR", 11); -define("VERSION_REVISION",1); +define("VERSION_MINOR", 12); +define("VERSION_REVISION",0); define("VERSION_STRING", VERSION_MAJOR . '.' . VERSION_MINOR . '.' . VERSION_REVISION); // TICK constants @@ -37,9 +37,13 @@ // First block with BTNS transaction define("FIRST_BLOCK",789742); - // BTNS Address + // BTNS Addresses define('BURN_ADDRESS', "1Muhahahahhahahahahahhahahauxh9QX"); define('GAS_ADDRESS', "1BTNSGASK5En7rFurDJ79LQ8CVYo2ecLC8"); + + // Donation Addresses + define('DONATE_ADDRESS_1', "1BTNSGASK5En7rFurDJ79LQ8CVYo2ecLC8"); // Protocol Development + define('DONATE_ADDRESS_2', "1BTNSGASK5En7rFurDJ79LQ8CVYo2ecLC8"); // Community Develoment } // Testnet config @@ -50,6 +54,10 @@ // BTNS Address define('BURN_ADDRESS', "mvCounterpartyXXXXXXXXXXXXXXW24Hef"); define('GAS_ADDRESS', "mvThcDEbeqog2aJ7JNj1FefUPaNdYYGqHt"); + + // Donation Addresses + define('DONATE_ADDRESS_1', "mvThcDEbeqog2aJ7JNj1FefUPaNdYYGqHt"); // Protocol Development + define('DONATE_ADDRESS_2', "mvThcDEbeqog2aJ7JNj1FefUPaNdYYGqHt"); // Community Develoment } // Database Credentials @@ -64,10 +72,14 @@ // Rollback code require_once('rollback.php'); +// Reparse code +require_once('reparse.php'); + // Protocol Changes / Activation blocks require_once('protocol_changes.php'); // BTNS Actions +require_once('actions/address.php'); require_once('actions/airdrop.php'); require_once('actions/batch.php'); require_once('actions/bet.php'); diff --git a/indexer/includes/functions.php b/indexer/includes/functions.php index 6d2b135..acc4452 100644 --- a/indexer/includes/functions.php +++ b/indexer/includes/functions.php @@ -26,7 +26,6 @@ function createLockFile($file=null){ } } - // Handle removing a lockfile function removeLockFile($file=null){ $lockFile = ($file!='') ? $file : LOCKFILE; @@ -145,7 +144,6 @@ function createTicker( $tick=null ){ } } - // Create records in the 'index_statuses' table and return record id function createStatus( $status=null ){ global $mysqli; @@ -215,46 +213,46 @@ function createAction( $action=null ){ // Create record in `issues` table function createIssue( $data=null ){ global $mysqli; - // Convert supply amounts to integers - $max_supply = (isset($data->MAX_SUPPLY) && is_numeric($data->MAX_SUPPLY)) ? $data->MAX_SUPPLY : 0; - $max_mint = (isset($data->MAX_MINT) && is_numeric($data->MAX_MINT)) ? $data->MAX_MINT : 0; - $mint_supply = (isset($data->MINT_SUPPLY) && is_numeric($data->MINT_SUPPLY)) ? $data->MINT_SUPPLY : 0; - $mint_address_max = (isset($data->MINT_ADDRESS_MAX) && is_numeric($data->MINT_ADDRESS_MAX)) ? $data->MINT_ADDRESS_MAX : 0; - $mint_start_block = (isset($data->MINT_START_BLOCK) && is_numeric($data->MINT_START_BLOCK)) ? $data->MINT_START_BLOCK : 0; - $mint_stop_block = (isset($data->MINT_STOP_BLOCK) && is_numeric($data->MINT_STOP_BLOCK)) ? $data->MINT_STOP_BLOCK : 0; - $callback_amount = (isset($data->CALLBACK_AMOUNT) && is_numeric($data->CALLBACK_AMOUNT)) ? $data->CALLBACK_AMOUNT : 0; - $decimals = (isset($data->DECIMALS)) ? $data->DECIMALS : 0; - // Truncate description to 250 chars - $description = substr($data->DESCRIPTION,0,250); - // Force any amount values to the correct decimal precision - if(is_numeric($decimals) && $decimals>=0 && $decimals<=18){ - $max_supply = bcmul($max_supply,1,$decimals); - $max_mint = bcmul($max_mint,1,$decimals); - $mint_supply = bcmul($mint_supply,1,$decimals); - $mint_address_max = bcmul($mint_address_max,1,$decimals); - $callback_amount = bcmul($callback_amount,1,$decimals); - } - $max_supply = $mysqli->real_escape_string($max_supply); - $max_mint = $mysqli->real_escape_string($max_mint); - $mint_supply = $mysqli->real_escape_string($mint_supply); - $mint_address_max = $mysqli->real_escape_string($mint_address_max); - $mint_start_block = $mysqli->real_escape_string($mint_start_block); - $mint_stop_block = $mysqli->real_escape_string($mint_stop_block); - $decimals = $mysqli->real_escape_string($decimals); - $description = $mysqli->real_escape_string($description); + // Define list of LOCK fields + $locks = array( + 'LOCK_MAX_SUPPLY', + 'LOCK_MINT', + 'LOCK_MINT_SUPPLY', + 'LOCK_MAX_MINT', + 'LOCK_DESCRIPTION', + 'LOCK_RUG', + 'LOCK_SLEEP', + 'LOCK_CALLBACK' + ); + // Unset any LOCK fields with invalid values + foreach($locks as $lock) + if(!in_array($data->{$lock},array(0,1))) + unset($data->{$lock}); + // Unset DECIMALS if it is outside of the acceptable range + if(isset($data->DECIMALS) && ($data->DECIMALS < MIN_TOKEN_DECIMALS || $data->DECIMALS > MAX_TOKEN_DECIMALS)) + unset($data->DECIMALS); + // Make data safe for use in SQL queries + $description = $mysqli->real_escape_string(substr($data->DESCRIPTION,0,250)); // Truncate description to 250 chars + $max_supply = $mysqli->real_escape_string($data->MAX_SUPPLY); + $max_mint = $mysqli->real_escape_string($data->MAX_MINT); + $mint_supply = $mysqli->real_escape_string($data->MINT_SUPPLY); + $mint_address_max = $mysqli->real_escape_string($data->MINT_ADDRESS_MAX); + $mint_start_block = $mysqli->real_escape_string($data->MINT_START_BLOCK); + $mint_stop_block = $mysqli->real_escape_string($data->MINT_STOP_BLOCK); + $decimals = $mysqli->real_escape_string($data->DECIMALS); $block_index = $mysqli->real_escape_string($data->BLOCK_INDEX); $tx_index = $mysqli->real_escape_string($data->TX_INDEX); $status = $mysqli->real_escape_string($data->STATUS); - // Force lock fields to integer values - $lock_supply = ($data->LOCK_SUPPLY==1) ? 1 : 0; - $lock_supply = ($data->LOCK_SUPPLY==1) ? 1 : 0; - $lock_mint = ($data->LOCK_MINT==1) ? 1 : 0; - $lock_description = ($data->LOCK_DESCRIPTION==1) ? 1 : 0; - $lock_rug = ($data->LOCK_RUG==1) ? 1 : 0; - $lock_sleep = ($data->LOCK_SLEEP==1) ? 1 : 0; - $lock_callback = ($data->LOCK_CALLBACK==1) ? 1 : 0; - $callback_block = ($data->CALLBACK_BLOCK>0) ? $data->CALLBACK_BLOCK : 0; - $callback_amount = $mysqli->real_escape_string($callback_amount); + $lock_max_supply = $mysqli->real_escape_string($data->LOCK_MAX_SUPPLY); + $lock_mint = $mysqli->real_escape_string($data->LOCK_MINT); + $lock_mint_supply = $mysqli->real_escape_string($data->LOCK_MINT_SUPPLY); + $lock_max_mint = $mysqli->real_escape_string($data->LOCK_MAX_MINT); + $lock_description = $mysqli->real_escape_string($data->LOCK_DESCRIPTION); + $lock_rug = $mysqli->real_escape_string($data->LOCK_RUG); + $lock_sleep = $mysqli->real_escape_string($data->LOCK_SLEEP); + $lock_callback = $mysqli->real_escape_string($data->LOCK_CALLBACK); + $callback_block = $mysqli->real_escape_string($data->CALLBACK_BLOCK); + $callback_amount = $mysqli->real_escape_string($data->CALLBACK_AMOUNT); $callback_tick_id = createTicker($data->CALLBACK_TICK); $tick_id = createTicker($data->TICK); $source_id = createAddress($data->SOURCE); @@ -281,8 +279,10 @@ function createIssue( $data=null ){ mint_supply='{$mint_supply}', transfer_id='{$transfer_id}', transfer_supply_id='{$transfer_supply_id}', - lock_supply='{$lock_supply}', + lock_max_supply='{$lock_max_supply}', lock_mint='{$lock_mint}', + lock_mint_supply='{$lock_mint_supply}', + lock_max_mint='{$lock_max_mint}', lock_description='{$lock_description}', lock_rug='{$lock_rug}', lock_sleep='{$lock_sleep}', @@ -303,7 +303,7 @@ function createIssue( $data=null ){ tx_hash_id='{$tx_hash_id}'"; } else { // INSERT record - $sql = "INSERT INTO issues (tick_id, max_supply, max_mint, decimals, description, mint_supply, transfer_id, transfer_supply_id, lock_supply, lock_mint, lock_description, lock_rug, lock_sleep, lock_callback, callback_block, callback_tick_id, callback_amount, allow_list_id, block_list_id, mint_address_max, mint_start_block, mint_stop_block, source_id, tx_hash_id, block_index, tx_index, status_id) values ('{$tick_id}', '{$max_supply}', '{$max_mint}', '{$decimals}', '{$description}', '{$mint_supply}', '{$transfer_id}', '{$transfer_supply_id}', '{$lock_supply}', '{$lock_mint}', '{$lock_description}', '{$lock_rug}', '{$lock_sleep}', '{$lock_callback}', '{$callback_block}', '{$callback_tick_id}', '{$callback_amount}', '{$allow_list_id}', '{$block_list_id}', '{$mint_address_max}', '{$mint_start_block}', '{$mint_stop_block}', '{$source_id}', '{$tx_hash_id}', '{$block_index}', '{$tx_index}', '{$status_id}')"; + $sql = "INSERT INTO issues (tick_id, max_supply, max_mint, decimals, description, mint_supply, transfer_id, transfer_supply_id, lock_max_supply, lock_mint, lock_mint_supply, lock_max_mint, lock_description, lock_rug, lock_sleep, lock_callback, callback_block, callback_tick_id, callback_amount, allow_list_id, block_list_id, mint_address_max, mint_start_block, mint_stop_block, source_id, tx_hash_id, block_index, tx_index, status_id) values ('{$tick_id}', '{$max_supply}', '{$max_mint}', '{$decimals}', '{$description}', '{$mint_supply}', '{$transfer_id}', '{$transfer_supply_id}', '{$lock_max_supply}', '{$lock_mint}', '{$lock_mint_supply}', '{$lock_max_mint}', '{$lock_description}', '{$lock_rug}', '{$lock_sleep}', '{$lock_callback}', '{$callback_block}', '{$callback_tick_id}', '{$callback_amount}', '{$allow_list_id}', '{$block_list_id}', '{$mint_address_max}', '{$mint_start_block}', '{$mint_stop_block}', '{$source_id}', '{$tx_hash_id}', '{$block_index}', '{$tx_index}', '{$status_id}')"; } // print $sql; $results = $mysqli->query($sql); @@ -314,7 +314,6 @@ function createIssue( $data=null ){ } } - // Create record in `mints` table function createMint( $data=null ){ global $mysqli; @@ -471,7 +470,7 @@ function createToken( $data=null ){ $mint_start_block = (isset($data->MINT_START_BLOCK) && is_numeric($data->MINT_START_BLOCK)) ? $data->MINT_START_BLOCK : 0; $mint_stop_block = (isset($data->MINT_STOP_BLOCK) && is_numeric($data->MINT_STOP_BLOCK)) ? $data->MINT_STOP_BLOCK : 0; $callback_amount = (isset($data->CALLBACK_AMOUNT) && is_numeric($data->CALLBACK_AMOUNT)) ? $data->CALLBACK_AMOUNT : 0; - $decimals = (isset($data->DECIMALS)) ? $data->DECIMALS : 0; + $decimals = (isset($data->DECIMALS) && is_numeric($data->DECIMALS)) ? intval($data->DECIMALS) : 0; // Force any amount values to the correct decimal precision if(is_numeric($decimals) && $decimals>=0 && $decimals<=18){ $max_supply = bcmul($max_supply,1,$decimals); @@ -490,9 +489,9 @@ function createToken( $data=null ){ $description = $mysqli->real_escape_string($data->DESCRIPTION); $block_index = $mysqli->real_escape_string($data->BLOCK_INDEX); // Force lock fields to integer values - $lock_supply = ($data->LOCK_SUPPLY==1) ? 1 : 0; - $lock_supply = ($data->LOCK_SUPPLY==1) ? 1 : 0; + $lock_max_supply = ($data->LOCK_MAX_SUPPLY==1) ? 1 : 0; $lock_mint = ($data->LOCK_MINT==1) ? 1 : 0; + $lock_max_mint = ($data->LOCK_MAX_MINT==1) ? 1 : 0; $lock_description = ($data->LOCK_DESCRIPTION==1) ? 1 : 0; $lock_rug = ($data->LOCK_RUG==1) ? 1 : 0; $lock_sleep = ($data->LOCK_SLEEP==1) ? 1 : 0; @@ -516,8 +515,9 @@ function createToken( $data=null ){ max_mint='{$max_mint}', decimals='{$decimals}', description='{$description}', - lock_supply='{$lock_supply}', + lock_max_supply='{$lock_max_supply}', lock_mint='{$lock_mint}', + lock_max_mint='{$lock_max_mint}', lock_description='{$lock_description}', lock_rug='{$lock_rug}', lock_sleep='{$lock_sleep}', @@ -530,14 +530,13 @@ function createToken( $data=null ){ mint_address_max='{$mint_address_max}', mint_start_block='{$mint_start_block}', mint_stop_block='{$mint_stop_block}', - block_index='{$block_index}', supply='{$supply}', owner_id='{$owner_id}' WHERE tick_id='{$tick_id}'"; } else { // INSERT record - $sql = "INSERT INTO tokens (tick_id, max_supply, max_mint, decimals, description, lock_supply, lock_mint, lock_description, lock_rug, lock_sleep, lock_callback, callback_block, callback_tick_id, callback_amount, allow_list_id, block_list_id, mint_address_max, mint_start_block, mint_stop_block, owner_id, supply, block_index) values ('{$tick_id}', '{$max_supply}', '{$max_mint}', '{$decimals}', '{$description}', '{$lock_supply}', '{$lock_mint}', '{$lock_description}', '{$lock_rug}', '{$lock_sleep}', '{$lock_callback}', '{$callback_block}', '{$callback_tick_id}', '{$callback_amount}', '{$allow_list_id}', '{$block_list_id}', '{$mint_address_max}', '{$mint_start_block}', '{$mint_stop_block}', '{$owner_id}','{$supply}', '{$block_index}')"; + $sql = "INSERT INTO tokens (tick_id, max_supply, max_mint, decimals, description, lock_max_supply, lock_mint, lock_max_mint, lock_description, lock_rug, lock_sleep, lock_callback, callback_block, callback_tick_id, callback_amount, allow_list_id, block_list_id, mint_address_max, mint_start_block, mint_stop_block, owner_id, supply, block_index) values ('{$tick_id}', '{$max_supply}', '{$max_mint}', '{$decimals}', '{$description}', '{$lock_max_supply}', '{$lock_mint}', '{$lock_max_mint}', '{$lock_description}', '{$lock_rug}', '{$lock_sleep}', '{$lock_callback}', '{$callback_block}', '{$callback_tick_id}', '{$callback_amount}', '{$allow_list_id}', '{$block_list_id}', '{$mint_address_max}', '{$mint_start_block}', '{$mint_stop_block}', '{$owner_id}','{$supply}', '{$block_index}')"; } // print $sql; $results = $mysqli->query($sql); @@ -548,9 +547,6 @@ function createToken( $data=null ){ } } - - - // Create record in `credits` table function createCredit( $action=null, $block_index=null, $event=null, $tick=null, $amount=null, $address=null ){ global $mysqli; @@ -648,10 +644,6 @@ function createDebit( $action=null, $block_index=null, $event=null, $tick=null, // Create record in `blocks` table function createBlock( $block=null ){ global $mysqli, $dbase; - $credits = array(); - $debits = array(); - $balances = array(); - $transactions = array(); $block_time = 0; // Get timestamp of Block from main database $results = $mysqli->query("SELECT block_time FROM {$dbase}.blocks WHERE block_index='{$block}' LIMIT 1"); @@ -663,62 +655,14 @@ function createBlock( $block=null ){ } else { byeLog('Error while trying to lookup records in credits table'); } - - // Get all data from credits table - $results = $mysqli->query("SELECT * FROM credits WHERE block_index<='{$block}' ORDER BY block_index ASC, tick_id ASC, address_id ASC, amount DESC"); - if($results){ - if($results->num_rows){ - while($row = $results->fetch_assoc()) - array_push($credits, (object) $row); - } - } else { - byeLog('Error while trying to lookup records in credits table'); - } - // Get all data from debits table - $results = $mysqli->query("SELECT * FROM debits WHERE block_index<='{$block}' ORDER BY block_index ASC, tick_id ASC, address_id ASC, amount DESC"); - if($results){ - if($results->num_rows){ - while($row = $results->fetch_assoc()) - array_push($debits, (object) $row); - } - } else { - byeLog('Error while trying to lookup records in debits table'); - } - // Get all data from balances table - $results = $mysqli->query("SELECT * FROM balances WHERE id IS NOT NULL ORDER BY tick_id ASC, address_id ASC, amount DESC"); - if($results){ - if($results->num_rows){ - while($row = $results->fetch_assoc()) - array_push($balances, (object) $row); - } - } else { - byeLog('Error while trying to lookup records in balances table'); - } - // Get all data from transactions table - $results = $mysqli->query("SELECT * FROM transactions WHERE tx_index IS NOT NULL ORDER BY tx_index ASC"); - if($results){ - if($results->num_rows){ - while($row = $results->fetch_assoc()) - array_push($transactions, (object) $row); - } - } else { - byeLog('Error while trying to lookup records in balances table'); - } - // Generate SHA256 hashes based on the json object - // This is a rough/dirty way to get some sha256 hashes qucikly... def should revisit when not in a rush - $credits_hash = hash('sha256', json_encode($credits)); - $debits_hash = hash('sha256', json_encode($debits)); - $balances_hash = hash('sha256', json_encode($balances)); - $transactions_hash = hash('sha256', json_encode($transactions)); - $credits_hash_short = substr($credits_hash,0,5); - $debits_hash_short = substr($debits_hash,0,5); - $balances_hash_short = substr($balances_hash,0,5); - $transactions_hash_short = substr($transactions_hash,0,5); - $credits_hash_id = createTransaction($credits_hash); - $debits_hash_id = createTransaction($debits_hash); - $balances_hash_id = createTransaction($balances_hash); - $txlist_hash_id = createTransaction($transactions_hash); - print "\n\t [credits:{$credits_hash_short} debits:{$debits_hash_short} balances:{$balances_hash_short} txlist:{$transactions_hash_short}]"; + // Get a list of hashes for this block + $info = getBlockHashes($block); + $credits = $info['credits']['hash']; + $debits = $info['debits']['hash']; + $txlist = $info['txlist']['hash']; + $credits_hash_id = createTransaction($credits); + $debits_hash_id = createTransaction($debits); + $txlist_hash_id = createTransaction($txlist); // Check if record already exists $results = $mysqli->query("SELECT id FROM blocks WHERE block_index='{$block}'"); if($results){ @@ -730,13 +674,12 @@ function createBlock( $block=null ){ block_time='{$block_time}', credits_hash_id='{$credits_hash_id}', debits_hash_id='{$debits_hash_id}', - balances_hash_id='{$balances_hash_id}', txlist_hash_id='{$txlist_hash_id}' WHERE block_index='{$block}'"; } else { // INSERT record - $sql = "INSERT INTO blocks (block_index, block_time, credits_hash_id, debits_hash_id, balances_hash_id, txlist_hash_id) values ('{$block}', '{$block_time}', '{$credits_hash_id}', '{$debits_hash_id}', '{$balances_hash_id}', '{$txlist_hash_id}')"; + $sql = "INSERT INTO blocks (block_index, block_time, credits_hash_id, debits_hash_id, txlist_hash_id) values ('{$block}', '{$block_time}', '{$credits_hash_id}', '{$debits_hash_id}', '{$txlist_hash_id}')"; } $results = $mysqli->query($sql); if(!$results) @@ -744,6 +687,12 @@ function createBlock( $block=null ){ } else { byeLog('Error while trying to lookup record in blocks table'); } + // Print out a status update + $credits = substr($credits,0,5); + $debits = substr($debits,0,5); + $txlist = substr($txlist,0,5); + print "\n\t [credits:{$credits} debits:{$debits} txlist:{$txlist}]"; + } // Create record in `lists` table @@ -856,7 +805,6 @@ function createListEdit($data=null, $item=null, $status=null ){ } } - // Create record in `list_items` table function createListItem($data=null, $item=null){ global $mysqli; @@ -882,7 +830,6 @@ function createListItem($data=null, $item=null){ } } - // Delete records in lists, list_items, and list_edits tables function deleteLists($list=null, $rollback=null){ global $mysqli; @@ -913,14 +860,20 @@ function deleteLists($list=null, $rollback=null){ } } - -// Handle getting token information for a given tick -function getTokenInfo($tick=null, $tick_id=null){ +// Handle getting token information using issues table +function getTokenInfo($tick=null, $tick_id=null, $block_index=null, $tx_index=null){ global $mysqli; - $data = false; + $data = false; + $whereSql = ""; + // Get the tick_id for the given ticker if(!is_null($tick) && is_null($tick_id)) $tick_id = createTicker($tick); - // Get data from tokens table + // If a block index was given, only lookup tokens created before or in given block + if(isset($block_index) && is_numeric($block_index)) + $whereSql .= " AND t1.block_index <= {$block_index}"; + if(isset($tx_index) && is_numeric($tx_index)) + $whereSql .= " AND t1.tx_index < '{$tx_index}'"; + // Get data from issues table $sql = "SELECT t2.tick, t1.max_supply, @@ -928,9 +881,10 @@ function getTokenInfo($tick=null, $tick_id=null){ t1.decimals, t1.description, t1.block_index, - t1.supply, - t1.lock_supply, + t1.lock_max_supply, + t1.lock_mint_supply, t1.lock_mint, + t1.lock_max_mint, t1.lock_description, t1.lock_rug, t1.lock_sleep, @@ -943,51 +897,78 @@ function getTokenInfo($tick=null, $tick_id=null){ t1.mint_address_max, t1.mint_start_block, t1.mint_stop_block, - a.address as owner + a1.address as owner, + a2.address as transfer FROM - tokens t1 + issues t1 + LEFT JOIN index_addresses a2 on (a2.id=t1.transfer_id) LEFT JOIN index_tickers t3 on (t3.id=t1.callback_tick_id) LEFT JOIN index_transactions t4 on (t4.id=t1.allow_list_id) LEFT JOIN index_transactions t5 on (t5.id=t1.block_list_id), index_tickers t2, - index_addresses a + index_addresses a1, + index_statuses s1 WHERE t2.id=t1.tick_id AND - a.id=t1.owner_id AND - t1.tick_id='{$tick_id}'"; - // print $sql; + a1.id=t1.source_id AND + s1.id=t1.status_id AND + s1.status='valid' AND + t1.tick_id='{$tick_id}' + {$whereSql} + ORDER BY tx_index ASC"; $results = $mysqli->query($sql); if($results){ if($results->num_rows){ - $row = (object) $results->fetch_assoc(); - $data = (object) array( - 'TICK' => $row->tick, - 'BLOCK_INDEX' => $row->block_index, - 'MAX_SUPPLY' => $row->max_supply, - 'MAX_MINT' => $row->max_mint, - 'DECIMALS' => $row->decimals, - 'DESCRIPTION' => $row->description, - 'SUPPLY' => $row->supply, - 'OWNER' => $row->owner, - 'LOCK_SUPPLY' => $row->lock_supply, - 'LOCK_MINT' => $row->lock_mint, - 'LOCK_DESCRIPTION' => $row->lock_description, - 'LOCK_RUG' => $row->lock_rug, - 'LOCK_SLEEP' => $row->lock_sleep, - 'LOCK_CALLBACK' => $row->lock_callback, - 'CALLBACK_TICK' => $row->callback_tick, - 'CALLBACK_BLOCK' => $row->callback_block, - 'CALLBACK_AMOUNT' => $row->callback_amount, - 'ALLOW_LIST' => $row->allow_list, - 'BLOCK_LIST' => $row->block_list, - 'MINT_ADDRESS_MAX' => $row->mint_address_max, - 'MINT_START_BLOCK' => $row->mint_start_block, - 'MINT_STOP_BLOCK' => $row->mint_stop_block - ); + // Loop through issues + while($row = $results->fetch_assoc()){ + $row = (object) $row; + $arr = array( + 'TICK' => $row->tick, + 'OWNER' => ($row->transfer) ? $row->transfer : $row->owner, + 'MAX_SUPPLY' => $row->max_supply, + 'MAX_MINT' => $row->max_mint, + 'DECIMALS' => (isset($row->decimals)) ? intval($row->decimals) : 0, + 'DESCRIPTION' => $row->description, + 'OWNER' => $row->owner, + 'LOCK_MAX_SUPPLY' => $row->lock_max_supply, + 'LOCK_MINT_SUPPLY' => $row->lock_mint_supply, + 'LOCK_MINT' => $row->lock_mint, + 'LOCK_MAX_MINT' => $row->lock_max_mint, + 'LOCK_DESCRIPTION' => $row->lock_description, + 'LOCK_RUG' => $row->lock_rug, + 'LOCK_SLEEP' => $row->lock_sleep, + 'LOCK_CALLBACK' => $row->lock_callback, + 'CALLBACK_TICK' => $row->callback_tick, + 'CALLBACK_BLOCK' => $row->callback_block, + 'CALLBACK_AMOUNT' => $row->callback_amount, + 'ALLOW_LIST' => $row->allow_list, + 'BLOCK_LIST' => $row->block_list, + 'MINT_ADDRESS_MAX' => $row->mint_address_max, + 'MINT_START_BLOCK' => $row->mint_start_block, + 'MINT_STOP_BLOCK' => $row->mint_stop_block + ); + // build out token state before tx_index + // TODO: will need to massage the data a bit more to build out accurate token state... this is quick and dirty + foreach($arr as $key => $value){ + // Disallow unsetting of LOCK flags + if(substr($key,0,5)=='LOCK_') + if($data[$key]==1) + continue; + // Skip setting value if value is null + if(in_array($key,array('MAX_SUPPLY','MAX_MINT')) && !isset($value)) + continue; + $data[$key] = $value; + } + } } } else { byeLog("Error while trying to lookup token info for : {$tick}"); } + if($data){ + // Get token supply at the given tx_index + $data['SUPPLY'] = getTokenSupply($tick, $tick_id, null, $tx_index); + $data = (object) $data; + } return $data; } @@ -1076,13 +1057,38 @@ function getAssetInfo($asset=null, $block_index=null){ // Handle getting decimal precision for a given tick_id function getTokenDecimalPrecision($tick_id=null){ - $info = getTokenInfo(null, $tick_id); - $decimals = ($info) ? $info->DECIMALS : 0; + global $mysqli; + // print "getTokenDecimalPrecision tick_id={$tick_id}\n"; + // Lookup decimal precision using the issues table + // DO NOT lookup precision using getTokenInfo() (avoid recursive queries) + $decimals = 0; + $sql = "SELECT + i.decimals + FROM + issues i, + index_statuses s + WHERE + i.status_id=s.id AND + i.tick_id='{$tick_id}' AND + s.status='valid'"; + // print $sql; + $results = $mysqli->query($sql); + if($results){ + if($results->num_rows){ + while($row = $results->fetch_assoc()){ + $row = (object) $row; + if(isset($row->decimals) && $row->decimals > $decimals) + $decimals = $row->decimals; + } + } + } else { + byeLog("Error while trying to lookup decimal precision from the issues table for tick: {$tick_id}"); + } return $decimals; } // Handle getting credits or debits records for a given address -function getAddressCreditDebit($table=null, $address=null, $action=null){ +function getAddressCreditDebit($table=null, $address=null, $action=null, $block=null, $tx_index=null){ global $mysqli; $data = array(); // Assoc array to store tick/credits $type = gettype($address); @@ -1092,6 +1098,14 @@ function getAddressCreditDebit($table=null, $address=null, $action=null){ $address_id = createAddress($address); if(isset($action)) $action_id = createAction($action); + // Build out custom WHERE sql + $whereSql = ""; + if(isset($action)) + $whereSql .= " AND t1.action_id={$action_id}"; + if(isset($block) && is_numeric($block)) + $whereSql .= " AND t1.block_index < {$block}"; + if(isset($tx_index) && is_numeric($tx_index)) + $whereSql .= " AND t3.tx_index < {$tx_index}"; if(in_array($table,array('credits','debits'))){ // Get data from the table $sql = "SELECT @@ -1100,12 +1114,13 @@ function getAddressCreditDebit($table=null, $address=null, $action=null){ t2.decimals FROM {$table} t1, - tokens t2 + tokens t2, + transactions t3 WHERE t2.tick_id=t1.tick_id AND - t1.address_id='{$address_id}'"; - if(isset($action)) - $sql .= " AND t1.action_id={$action_id}"; + t3.tx_hash_id=t1.event_id AND + t1.address_id='{$address_id}' + {$whereSql}"; $results = $mysqli->query($sql); if($results){ if($results->num_rows){ @@ -1125,15 +1140,15 @@ function getAddressCreditDebit($table=null, $address=null, $action=null){ } // Get address balances using credits/debits table data -function getAddressBalances($address=null){ +function getAddressBalances($address=null, $tick=null, $block=null, $tx_index=null){ global $mysqli; $type = gettype($address); if($type==='integer' || is_numeric($address)) $address_id = $address; if($type==='string' && !is_numeric($address)) $address_id = createAddress($address); - $credits = getAddressCreditDebit('credits', $address_id); - $debits = getAddressCreditDebit('debits', $address_id); + $credits = getAddressCreditDebit('credits', $address_id, null, $block, $tx_index); + $debits = getAddressCreditDebit('debits', $address_id, null, $block, $tx_index); $decimals = array(); // Assoc array to store tick/decimals $balances = array(); // Assoc array to store tick/balance foreach($credits as $tick_id => $amount) @@ -1179,11 +1194,10 @@ function getAddressTableBalances($address=null){ return $balances; } - // Create/Update/Delete records in the 'balances' table function updateAddressBalance( $address=null, $rollback=false){ global $mysqli; - // print "updateAddressBalance address={$address}\n"; + // print "updateAddressBalance address={$address} rollback={$rollback}\n"; $type = gettype($address); if($type==='integer' || is_numeric($address)) $address_id = $address; @@ -1233,8 +1247,6 @@ function updateAddressBalance( $address=null, $rollback=false){ } } - - // Handle updating address balances (credits-debits=balance) // @param {address} boolean Full update // @param {address} string Address string @@ -1244,10 +1256,11 @@ function updateBalances( $address=null, $rollback=false ){ global $mysqli; $addrs = []; $type = gettype($address); - if($type==='array') + if($type==='array'){ foreach($address as $addr) if(!is_null($addr) && $addr!='') array_push($addrs, $addr); + } if($type==='string') array_push($addrs, $address); // Dump full list of addresses @@ -1266,7 +1279,6 @@ function updateBalances( $address=null, $rollback=false ){ updateAddressBalance($address, $rollback); } - // Handle updating token information (supply, price, etc) // @param {tickers} boolean Full update // @param {tickers} string Ticker @@ -1304,25 +1316,43 @@ function updateTokenInfo( $tick=null ){ $tick_id = createTicker($tick); // Lookup current token information $data = getTokenInfo($tick); - if($data){ - // Get current token supply (current token supply) - $data->SUPPLY = getTokenSupply($tick); - // Update the record in `tokens` table + // Update the record in `tokens` table + if($data) createToken($data); - } } // Get token supply from credits/debits table (credits - debits = supply) -function getTokenSupply( $tick=null ){ +// @param {tick} string Ticker name +// @param {block_index} integer Block Index +// @param {tx_index} integer tx_index of transaction +function getTokenSupply( $tick=null, $tick_id=null, $block_index=null, $tx_index=null ){ global $mysqli; $credits = 0; $debits = 0; $supply = 0; - $tick_id = createTicker($tick); + $block = (is_numeric($block_index)) ? $block_index : 99999999999999; + // Get the tick_id for the given ticker + if(!is_null($tick) && is_null($tick_id)) + $tick_id = createTicker($tick); // Get info on decimal precision $decimals = getTokenDecimalPrecision($tick_id); + $whereSql = ""; + // Filter by block_index + if(is_numeric($block)) + $whereSql .= " AND m.block_index <= '{$block}'"; + // Filter by tx_index + if(is_numeric($tx_index)) + $whereSql .= " AND t.tx_index < {$tx_index}"; // Get Credits - $sql = "SELECT CAST(SUM(amount) AS DECIMAL(60,$decimals)) as credits FROM credits WHERE tick_id='{$tick_id}'"; + $sql = "SELECT + CAST(SUM(m.amount) AS DECIMAL(60,$decimals)) as credits + FROM + credits m, + transactions t + WHERE + m.event_id=t.tx_hash_id AND + m.tick_id='{$tick_id}' + {$whereSql}"; $results = $mysqli->query($sql); if($results){ if($results->num_rows){ @@ -1333,7 +1363,15 @@ function getTokenSupply( $tick=null ){ byeLog('Error while trying to get list of credits'); } // Get Debits - $sql = "SELECT CAST(SUM(amount) AS DECIMAL(60,$decimals)) as debits FROM debits WHERE tick_id='{$tick_id}'"; + $sql = "SELECT + CAST(SUM(m.amount) AS DECIMAL(60,$decimals)) as debits + FROM + debits m, + transactions t + WHERE + m.event_id=t.tx_hash_id AND + m.tick_id='{$tick_id}' + {$whereSql}"; $results = $mysqli->query($sql); if($results){ if($results->num_rows){ @@ -1368,7 +1406,6 @@ function getTokenSupplyBalance( $tick=null ){ return $supply; } - // Handle doing VERY lose validation on an address function isCryptoAddress( $address=null ){ $len = strlen($address); @@ -1381,9 +1418,9 @@ function isCryptoAddress( $address=null ){ return false; } - // Generalized function to invoke BTNS action commands function btnsAction($action=null, $params=null, $data=null, $error=null){ + if($action=='ADDRESS') btnsAddress($params, $data, $error); if($action=='AIRDROP') btnsAirdrop($params, $data, $error); if($action=='BATCH') btnsBatch($params, $data, $error); if($action=='BET') btnsBet($params, $data, $error); @@ -1400,7 +1437,6 @@ function btnsAction($action=null, $params=null, $data=null, $error=null){ if($action=='SWEEP') btnsSweep($params, $data, $error); } - // Create records in the 'tx_index_types' table and return record id function createTxType( $type=null ){ global $mysqli; @@ -1451,7 +1487,6 @@ function getTxIndex($tx_hash=null){ } } - // Create records in the 'transactions' table function createTxIndex( $data=null ){ global $mysqli; @@ -1513,13 +1548,10 @@ function isEnabled($name=null, $network=null, $block_index=null){ $mainnet_block_index = $info[3]; $testnet_block_index = $info[4]; $enable_block_index = ${$network . '_block_index'}; - // if(VERSION_MAJOR < $version_major) return false; - // if(VERSION_MINOR < $version_minor) return false; - // if(VERSION_REVISION < $version_revision) return false; - if($block_index >= $enable_block_index) - return true; + if(VERSION_MAJOR >= $version_major && VERSION_MINOR >= $version_minor && VERSION_REVISION >= $version_revision && $block_index >= $enable_block_index) + return 1; } - return false; + return 0; } // Handle returning integer format version @@ -1606,50 +1638,151 @@ function setActionParams($data=null, $params=null, $format=null){ return $data; } -// Handle getting a list of holders -// @param {tick} string TICK or ASSET name -// @param {type} integer Holder Type (1=TICK, 2=ASSET) -function getHolders( $tick=null, $type=null){ +// Handle getting a list of TICK holders and amounts +// @param {tick} string Ticker name +// @param {block_index} integer Block Index +// @param {tx_index} integer tx_index of transaction +function getHolders( $tick=null, $block_index=null, $tx_index=null ){ global $mysqli, $dbase; $holders = []; - $type = ($type>1) ? $type : 1; - $tick = $mysqli->real_escape_string($tick); - // Query TICK Holders - if($type==1){ - $sql = "SELECT - a.address, - b.amount - FROM - balances b, - index_tickers t, - index_addresses a - WHERE - t.id=b.tick_id AND - a.id=b.address_id AND - t.tick='{$tick}'"; - } - // Query ASSET holders - if($type==2){ - // Handle CP ASSETS here... coming soon + $block = (is_numeric($block_index)) ? $block_index : 99999999999999; + $tick_id = createTicker($tick); + // Get info on decimal precision + $decimals = getTokenDecimalPrecision($tick_id); + $whereSql = ""; + // Filter by block_index + if(is_numeric($block)) + $whereSql .= " AND m.block_index <= '{$block}'"; + // Filter by tx_index + if(is_numeric($tx_index)) + $whereSql .= " AND t.tx_index < {$tx_index}"; + // Get Credits + $sql = "SELECT + CAST(SUM(m.amount) AS DECIMAL(60,$decimals)) as credits, + a.address + FROM + credits m, + transactions t, + index_addresses a + WHERE + m.event_id=t.tx_hash_id AND + m.address_id=a.id AND + m.tick_id='{$tick_id}' + {$whereSql} + GROUP BY a.address"; + $results = $mysqli->query($sql); + if($results){ + if($results->num_rows){ + while($row = $results->fetch_assoc()){ + $row = (object) $row; + $holders[$row->address] = $row->credits; + } + } + } else { + byeLog('Error while trying to get list of credits'); } + // Get Debits + $sql = "SELECT + CAST(SUM(m.amount) AS DECIMAL(60,$decimals)) as debits, + a.address + FROM + debits m, + transactions t, + index_addresses a + WHERE + m.event_id=t.tx_hash_id AND + m.address_id=a.id AND + m.tick_id='{$tick_id}' + {$whereSql} + GROUP BY a.address"; $results = $mysqli->query($sql); if($results){ if($results->num_rows){ while($row = $results->fetch_assoc()){ $row = (object) $row; - $holders[$row->address] = $row->amount; + $balance = bcsub($holders[$row->address], $row->debits, $decimals); + if($balance > 0) + $holders[$row->address] = $balance; + else + unset($holders[$row->address]); } } } else { - byeLog("Error while trying to lookup holders of : {$tick}"); + byeLog('Error while trying to get list of debits'); } return $holders; } +// Handle getting a list of ASSET holders and amounts +// @param {asset} string Asset name +// @param {block_index} integer Block Index +function getAssetHolders( $asset=null, $block_index=null ){ + global $mysqli, $dbase; + $holders = []; + $block = (is_numeric($block_index)) ? $block_index : 99999999999999; + $asset_id = getAssetId($asset); + // Filter by block_index + if(is_numeric($block)) + $whereSql .= " AND m.block_index <= '{$block}'"; + // Get Credits + $sql = "SELECT + CAST(SUM(m.quantity) AS DECIMAL(60,0)) as credits, + a.address + FROM + {$dbase}.credits m, + {$dbase}.index_addresses a + WHERE + m.address_id=a.id AND + m.asset_id='{$asset_id}' + {$whereSql} + GROUP BY a.address"; + $results = $mysqli->query($sql); + if($results){ + if($results->num_rows){ + while($row = $results->fetch_assoc()){ + $row = (object) $row; + if(isCryptoAddress($row->address)) + $holders[$row->address] = $row->credits; + } + } + } else { + byeLog('Error while trying to get list of credits'); + } + // Get Debits + $sql = "SELECT + CAST(SUM(m.quantity) AS DECIMAL(60,0)) as debits, + a.address + FROM + {$dbase}.debits m, + {$dbase}.index_addresses a + WHERE + m.address_id=a.id AND + m.asset_id='{$asset_id}' + {$whereSql} + GROUP BY a.address"; + $results = $mysqli->query($sql); + if($results){ + if($results->num_rows){ + while($row = $results->fetch_assoc()){ + $row = (object) $row; + $balance = bcsub($holders[$row->address], $row->debits, 0); + if($balance > 0) + $holders[$row->address] = $balance; + else + unset($holders[$row->address]); + } + } + } else { + byeLog('Error while trying to get list of debits'); + } + return $holders; +} + + // Determine if an ticker is distributed to users (held by more than owner) -function isDistributed($tick=null){ - $info = getTokenInfo($tick); - $holders = ($info) ? getHolders($data->TICK) : []; +function isDistributed($tick=null, $block_index=null, $tx_index=null){ + $info = getTokenInfo($tick, null, $block_index, $tx_index); + $holders = ($info) ? getHolders($tick, $block_index, $tx_index) : []; // More than one holder if(count($holders)>1) return true; @@ -1700,58 +1833,38 @@ function isActionAllowed($tick=null, $address=null){ // Validate that token supplys match credits/debits/balances information function sanityCheck( $block=null ){ - global $mysqli; + global $mysqli, $network; $tickers = []; // Assoc array of tickers $supply = []; // Assoc array of supplys $block_index = $mysqli->real_escape_string($block); - // Get list of tables to check for transactions / tickers - $sql = "SELECT - distinct(t2.type) as type - FROM - transactions t1, - index_tx_types t2 + // Get list of tickers and supply from credits/debits/tokens tables using block_index + $sql = "SELECT + DISTINCT(x.tick_id), + t2.tick, + t1.supply + FROM + ( + SELECT tick_id FROM credits WHERE block_index='{$block_index}' UNION + SELECT tick_id FROM debits WHERE block_index='{$block_index}' + ) as x, + tokens t1, + index_tickers t2 WHERE - t2.id=t1.type_id AND - t2.type!='UNKNOWN' AND - t1.block_index='{$block_index}'"; + t1.tick_id=x.tick_id AND + t2.id=x.tick_id + ORDER BY t2.tick ASC"; $results = $mysqli->query($sql); if($results){ if($results->num_rows){ while($row = $results->fetch_assoc()){ $row = (object) $row; - // Ignore certain tx types - if(in_array($row->type,array('LIST'))) - continue; - // Loop through tables and get ticker and supply - $table = strtolower($row->type) . 's'; - $sql = "SELECT - t2.id, - t2.tick, - t1.supply - FROM - {$table} m LEFT JOIN tokens t1 on (t1.tick_id=m.tick_id), - index_tickers t2 - WHERE - t2.id=m.tick_id AND - m.block_index='{$block_index}'"; - // print $sql; - $results2 = $mysqli->query($sql); - if($results2){ - if($results2->num_rows){ - while($row2 = $results2->fetch_assoc()){ - $row2 = (object) $row2; - // Add ticker and supply info to assoc arrays - $tickers[$row2->tick] = $row2->id; - $supply[$row2->tick] = (!is_null($row2->supply)) ? $row2->supply : "0"; - } - } - } else { - byeLog("Error while trying to lookup tickers in block : {$block}"); - } + // Add ticker and supply info to assoc arrays + $tickers[$row->tick] = $row->tick_id; + $supply[$row->tick] = (!is_null($row->supply)) ? $row->supply : "0"; } } } else { - byeLog("Error while trying to lookup transactions in block : {$block}"); + byeLog("Error while trying to lookup credits/debits in block : {$block}"); } // Loop through the tickers and validate token supply match credits/debits/balances info foreach($tickers as $tick => $tick_id){ @@ -1787,14 +1900,550 @@ function consolidateCreditDebitRecords($type=null, $records=null){ } // Get total amount of credit or debit records for a given address, ticker, and action -function getActionCreditDebitAmount($table=null, $action=null, $tick=null, $address=null){ +function getActionCreditDebitAmount($table=null, $action=null, $tick=null, $address=null, $tx_index=null){ global $mysqli; $total = 0; $tick_id = createTicker($tick); $addr_id = createAddress($address); - $data = getAddressCreditDebit($table, $addr_id, $action); + $data = getAddressCreditDebit($table, $addr_id, $action, null, $tx_index); if($data[$tick_id]) $total = $data[$tick_id]; return $total; } -?> \ No newline at end of file + + + +// Get all data from a given table for a given block +function getBlockTableData($table=null, $block=null){ + global $mysqli; + $data = []; + // Get all block data from table + $results = $mysqli->query("SELECT tx_index, status_id FROM {$table} WHERE block_index='{$block}' ORDER BY tx_index ASC"); + if($results){ + if($results->num_rows){ + while($row = $results->fetch_assoc()) + array_push($data, (object) $row); + } else { + array_push($data, (object) []); + } + } else { + byeLog("Error while trying to lookup records in {$table} table"); + } + return $data; +} + +// Function to get a SHA256 hash of a given data object +function getDataHash($data=null){ + $hash = hash('sha256', json_encode($data)); + return $hash; +} + +// Get block hashes using data from the ACTION tables +function getBlockDataHashes($block=null){ + global $mysqli; + // Define a list of tables + $tables = [ + 'destroys', + 'issues', + 'lists', + 'mints', + 'sends', + ]; + // Define response info object + $info = []; + // Loop through the data tables and dump a quick list of tx_index and status + foreach($tables as $table){ + $data = getBlockTableData($table, $block); + $hash = getDataHash($data); + $info[$table] = [ + 'hash' => $hash, + 'data' => $data + ]; + } + // Get hashes for data in the credits / debits / transactions tables + $info = array_merge($info, getBlockHashes($block)); + return $info; +} + +// Get block hashes using credits/debits/transactions table data and previous hash +function getBlockHashes($block=null){ + global $mysqli; + $credits = array(); + $debits = array(); + $txlist = array(); + $info = array(); + $hashes = array(); + // Get data from credits table + $results = $mysqli->query("SELECT * FROM credits WHERE block_index='{$block}' ORDER BY block_index ASC, tick_id ASC, address_id ASC, amount DESC"); + if($results){ + if($results->num_rows){ + while($row = $results->fetch_assoc()) + array_push($credits, $row); + } + } else { + byeLog('Error while trying to lookup records in credits table'); + } + // Get data from debits table + $results = $mysqli->query("SELECT * FROM debits WHERE block_index='{$block}' ORDER BY block_index ASC, tick_id ASC, address_id ASC, amount DESC"); + if($results){ + if($results->num_rows){ + while($row = $results->fetch_assoc()) + array_push($debits, $row); + } + } else { + byeLog('Error while trying to lookup records in debits table'); + } + // Get all block data from transactions table + $results = $mysqli->query("SELECT * FROM transactions WHERE block_index='{$block}' ORDER BY tx_index ASC"); + if($results){ + if($results->num_rows){ + while($row = $results->fetch_assoc()) + array_push($txlist, $row); + } + } else { + byeLog('Error while trying to lookup records in transactions table'); + } + // Subtract one block from current block + $block--; + // Get hashes from the last block to include in this blocks hash + $sql = "SELECT + t1.hash as credits, + t2.hash as debits, + t3.hash as txlist + FROM + blocks b, + index_transactions t1, + index_transactions t2, + index_transactions t3 + WHERE + t1.id=b.credits_hash_id AND + t2.id=b.debits_hash_id AND + t3.id=b.txlist_hash_id AND + b.block_index='{$block}'"; + $results = $mysqli->query($sql); + if($results){ + if($results->num_rows) + $hashes = $results->fetch_assoc(); + } else { + byeLog('Error while trying to lookup records in transactions table'); + } + $tables = ['credits','debits','txlist']; + // Loop through the tables, add previous hash to data, then create new block hash + foreach($tables as $table){ + $data = ${$table}; + // Include the block_index and previous block hash in the hash calculation for this block hash + $data['block_index'] = $block; + $data['previous_hash'] = $hashes[$table]; + $info[$table] = [ + 'hash' => getDataHash($data), + // 'data' => $data, + ]; + } + return $info; +} + +// Generalized function to handle processing a broadcast transaction +// @param {tx} object Transaction object +// @param {tx->source} string Source address +// @param {$tx->text} string Broadcast `text` +// @param {$tx->version} integer Broadcast `value` +// @param {$tx->tx_hash} string Transaction hash +// @param {$tx->block_index} string Block index of tx +function processTransaction($tx=null){ + global $network; + $error = false; + $tx = (object) $tx; + $prefixes = array('/^bt:/','/^btns:/'); + $tx->raw = preg_replace($prefixes,'',$tx->text); + $params = explode('|',$tx->raw); + $version = $tx->version; // Project Version + $source = $tx->source; // Source address + + // Create database records and get ids for tx_hash and source address + $source_id = createAddress($tx->source); + $tx_hash_id = createTransaction($tx->tx_hash); + + // Trim whitespace from any PARAMS + foreach($params as $idx => $value) + $params[$idx] = trim($value); + + // Extract ACTION from PARAMS + $action = strtoupper(array_shift($params)); + + // Define ACTION aliases + $aliases = array( + // Old BRC20/SRC20 actions + 'TRANSFER' => 'SEND', + 'DEPLOY' => 'ISSUE', + // Short action aliases + 'ADDR' => 'ADDRESS' + ); + + // Set ACTION for any aliases + foreach($aliases as $alias => $act) + if($action==$alias) + $action = $act; + + // Support legacy BTNS format with no VERSION (default to VERSION 0) + if(in_array($action,array('ISSUE','MINT','SEND')) && isLegacyBTNSFormat($params)) + array_splice($params, 0, 0, 0); + + // Define basic BTNS transaction data object + $data = (object) array( + 'ACTION' => $action, // Action (ISSUE, MINT, SEND, etc) + 'BLOCK_INDEX' => $tx->block_index, // Block index + 'SOURCE' => $tx->source, // Source/Broadcasting address + 'TX_HASH' => $tx->tx_hash, // Transaction Hash + 'TX_RAW' => $tx->raw // Raw TX string + ); + + // Validate Action + if(!array_key_exists($action,PROTOCOL_CHANGES)){ + $error = 'invalid: Unknown ACTION'; + $data->ACTION = $action = 'UNKNOWN'; + } + + // Verify action is activated (past ACTIVATION_BLOCK) + if(!$error && !isEnabled($action, $network, $tx->block_index)) + $error = 'invalid: ACTIVATION_BLOCK'; + + // Create a record of this transaction in the transactions table + createTxIndex($data); + + // Get tx_index of record using tx_hash + $data->TX_INDEX = getTxIndex($data->TX_HASH); + + // Handle processing the specific BTNS ACTION commands + btnsAction($action, $params, $data, $error); +} + +// Get broadcast transactions for a given block +function getBroadcastTransactions($block){ + global $mysqli, $dbase; + $data = array(); + // Lookup any BTNS action broadcasts in this block (anything with bt: or btns: prefix) + $sql = "SELECT + b.text, + b.value as version, + t.hash as tx_hash, + a.address as source, + b.block_index as block_index + FROM + {$dbase}.broadcasts b, + {$dbase}.index_transactions t, + {$dbase}.index_addresses a + WHERE + t.id=b.tx_hash_id AND + a.id=b.source_id AND + b.block_index='{$block}' AND + b.status='valid' AND + (b.text LIKE 'bt:%' OR b.text LIKE 'btns:%') + ORDER BY b.tx_index ASC"; + $results = $mysqli->query($sql); + if($results){ + if($results->num_rows) + while($row = $results->fetch_assoc()) + array_push($data, $row); + } else { + byeLog("Error while trying to lookup BTNS broadcasts"); + } + return $data; +} + +// Get tx_index of the first valid ISSUE action for a given ticker +function getFirstIssuanceTxIndex($tick=null){ + global $mysqli; + $tick_id = createTicker($tick); + $sql = "SELECT + tx_index + FROM + issues i, + index_statuses s + WHERE + i.tick_id={$tick_id} AND + s.id=i.status_id AND + s.status='valid' + ORDER BY tx_index ASC LIMIT 1"; + $results = $mysqli->query($sql); + if($results){ + if($results->num_rows){ + $row = (object) $results->fetch_assoc(); + return $row->tx_index; + } + } else { + byeLog("Error while trying to look up tx_index of first valid issuance"); + } + return false; +} + +// Handles validating if a ticker is valid before a given tx_index +function validTickerBeforeTxIndex($tick=null, $txIndex=null){ + $issueIndex = getFirstIssuanceTxIndex($tick); + if($issueIndex < $txIndex) + return true; + return false; +} + +// Handle adding a ticker to the $addresses assoc array and $tickers array +function addAddressTicker($address=null, $tick=null){ + global $addresses, $tickers; + $type = gettype($tick); + $list = (isset($addresses[$address])) ? $addresses[$address] : []; + // If $tick is an array, use the array + if($type=="array"){ + foreach($tick as $t){ + // Add TICK to $addresses + if(!in_array($t, $list)) + array_push($list, $t); + // Add TICK to $tickers + if(!in_array($t, $tickers)) + array_push($tickers, $t); + } + } else { + // Add TICK to $addresses + if(!in_array($tick, $list)) + array_push($list, $tick); + // Add TICK to $tickers + if(!in_array($tick, $tickers)) + array_push($tickers, $tick); + } + $addresses[$address] = $list; +} + +// Handle displaying runtime information in a nice format +function printRuntime($seconds){ + $msg = ""; + $hours = floor($seconds / 3600); + $mins = floor(($seconds / 60) % 60); + $secs = $seconds % 60; + $ms = explode(".",$seconds)[1]; + if($hours>0) $msg .= "{$hours} hours "; + if($mins>0) $msg .= "{$mins} minutes "; + if($secs>0||$ms>0) $msg .= "{$secs}.{$ms} seconds"; + print "Total Execution time: {$msg}\n"; +} + +// Create record in `addresses` table +function createAddressOption( $data=null ){ + global $mysqli; + $source_id = createAddress($data->SOURCE); + $tx_hash_id = createTransaction($data->TX_HASH); + $block_index = $mysqli->real_escape_string($data->BLOCK_INDEX); + $status_id = createStatus($data->STATUS); + $tx_index = $mysqli->real_escape_string($data->TX_INDEX); + $fee_preference = $mysqli->real_escape_string($data->FEE_PREFERENCE); + $require_memo = $mysqli->real_escape_string($data->REQUIRE_MEMO); + // Check if record already exists + $results = $mysqli->query("SELECT tx_index FROM addresses WHERE tx_hash_id='{$tx_hash_id}'"); + if($results){ + if($results->num_rows){ + // UPDATE record + $sql = "UPDATE + addresses + SET + fee_preference='{$fee_preference}', + require_memo='{$require_memo}', + source_id='{$source_id}', + block_index='{$block_index}', + tx_index='{$tx_index}', + status_id='{$status_id}' + WHERE + tx_hash_id='{$tx_hash_id}'"; + } else { + // INSERT record + $sql = "INSERT INTO addresses (tx_index, source_id, tx_hash_id, block_index, fee_preference, require_memo, status_id) values ('{$tx_index}', '{$source_id}', '{$tx_hash_id}', '{$block_index}', '{$fee_preference}', '{$require_memo}', '{$status_id}')"; + } + $results = $mysqli->query($sql); + if(!$results) + byeLog('Error while trying to create / update a record in the addresses table'); + } else { + byeLog('Error while trying to lookup record in addresses table'); + } +} + +// Create record in `batches` table +function createBatch( $data=null ){ + global $mysqli; + $source_id = createAddress($data->SOURCE); + $tx_hash_id = createTransaction($data->TX_HASH); + $block_index = $mysqli->real_escape_string($data->BLOCK_INDEX); + $status_id = createStatus($data->STATUS); + $tx_index = $mysqli->real_escape_string($data->TX_INDEX); + // Check if record already exists + $results = $mysqli->query("SELECT tx_index FROM batches WHERE tx_hash_id='{$tx_hash_id}'"); + if($results){ + if($results->num_rows){ + // UPDATE record + $sql = "UPDATE + batches + SET + source_id='{$source_id}', + block_index='{$block_index}', + tx_index='{$tx_index}', + status_id='{$status_id}' + WHERE + tx_hash_id='{$tx_hash_id}'"; + } else { + // INSERT record + $sql = "INSERT INTO batches (tx_index, source_id, tx_hash_id, block_index, status_id) values ('{$tx_index}', '{$source_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 batches table'); + } else { + byeLog('Error while trying to lookup record in batches table'); + } +} + +// Determine if a tx hash is valid or not +function isValidTransactionHash($hash=null){ + if(strlen($hash)==64) + return 1; + return 0; +} + +// Create record in `airdrops` table +function createAirdrop( $data=null ){ + global $mysqli; + $tick_id = createTicker($data->TICK); + $source_id = createAddress($data->SOURCE); + $tx_hash_id = createTransaction($data->TX_HASH); + $list_id = createTransaction($data->LIST); + $memo_id = createMemo($data->MEMO); + $status_id = createStatus($data->STATUS); + $tx_index = $mysqli->real_escape_string($data->TX_INDEX); + $amount = $mysqli->real_escape_string($data->AMOUNT); + $block_index = $mysqli->real_escape_string($data->BLOCK_INDEX); + // Check if record already exists + $sql = "SELECT + tx_index + FROM + airdrops + WHERE + tick_id='{$tick_id}' AND + source_id='{$source_id}' AND + list_id='{$list_id}' AND + amount='{$amount}' AND + tx_hash_id='{$tx_hash_id}'"; + $results = $mysqli->query($sql); + if($results){ + if($results->num_rows){ + // UPDATE record + $sql = "UPDATE + airdrops + SET + tx_index='{$tx_index}', + block_index='{$block_index}', + memo_id='{$memo_id}', + status_id='{$status_id}' + WHERE + tick_id='{$tick_id}' AND + source_id='{$source_id}' AND + list_id='{$list_id}' AND + amount='{$amount}' AND + tx_hash_id='{$tx_hash_id}'"; + } else { + // INSERT record + $sql = "INSERT INTO airdrops (tx_index, tick_id, source_id, list_id, amount, memo_id, tx_hash_id, block_index, status_id) values ('{$tx_index}','{$tick_id}', '{$source_id}', '{$list_id}', '{$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 airdrops table'); + } else { + byeLog('Error while trying to lookup record in airdrops table'); + } +} + + +// Get address preferences for a given address +function getAddressPreferences($address=null, $block_index=null, $tx_index=null){ + global $mysqli; + $address_id = createAddress($address); + // Set default address preferences + $data = (object)[ + 'FEE_PREFERENCE' => 2, // 2=Donate FEES to development + 'REQUIRE_MEMO' => 0 // Require memo on SENDs to this address + ]; + // Get users ADDRESS preferences right before this tx + $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}'"; + $sql = "SELECT + fee_preference, + require_memo + FROM + addresses a + WHERE + source_id='{$address_id}' + {$whereSql} + ORDER BY tx_index DESC + LIMIT 1"; + $results = $mysqli->query($sql); + if($results){ + if($results->num_rows){ + $row = (object) $results->fetch_assoc(); + $data->FEE_PREFERENCE = $row->fee_preference; + $data->REQUIRE_MEMO = $row->require_memo; + } + } else { + byeLog('Error while trying to lookup record in addresses table'); + } + return $data; +} + +// Calculate Transaction fee based on number of database hits +// TODO: Make this code modular, so we can configure fees on actions on a per-chain basis +function getTransactionFee($db_hits=0){ + $cost = 1000; // Cost in sats per DB hit + $sats = bcmul($db_hits, $cost , 0); // FEE in sats (integer) + $fee = bcmul($sats, '0.00000001', 8); // FEE in decimal (divisible) + return $fee; +} + +// Create record in `fees` table +function createFeeRecord( $data=null ){ + global $mysqli; + $tick_id = createTicker($data->TICK); + $source_id = createAddress($data->SOURCE); + $destination_id = createAddress($data->DESTINATION); + $tx_index = $mysqli->real_escape_string($data->TX_INDEX); + $amount = $mysqli->real_escape_string($data->AMOUNT); + $method = $mysqli->real_escape_string($data->METHOD); + $block_index = $mysqli->real_escape_string($data->BLOCK_INDEX); + // Check if record already exists + $sql = "SELECT + tx_index + FROM + fees + WHERE + tx_index='{$tx_index}'"; + $results = $mysqli->query($sql); + if($results){ + if($results->num_rows){ + // UPDATE record + $sql = "UPDATE + fees + SET + tick_id='{$tick_id}', + source_id='{$source_id}', + destination_id='{$destination_id}', + amount='{$amount}', + method='{$method}', + block_index='{$block_index}' + WHERE + tx_index='{$tx_index}'"; + } else { + // INSERT record + $sql = "INSERT INTO fees (tx_index, block_index, source_id, tick_id, amount, method, destination_id) values ('{$tx_index}', '{$block_index}', '{$source_id}', '{$tick_id}', '{$amount}', '{$method}', '{$destination_id}')"; + } + $results = $mysqli->query($sql); + if(!$results) + byeLog('Error while trying to create / update a record in the fees table'); + } else { + byeLog('Error while trying to lookup record in fees table'); + } +} + +?> diff --git a/indexer/includes/protocol_changes.php b/indexer/includes/protocol_changes.php index 935a426..6c74ce7 100644 --- a/indexer/includes/protocol_changes.php +++ b/indexer/includes/protocol_changes.php @@ -11,8 +11,9 @@ // Define `ACTION` commands and `ACTIVATION_BLOCK` for each (ALL UPPER case) // BTNS-420 SPEC defines when things are ACTUALLY activated // active here just means active for testing / debugging - 'AIRDROP' => array(0, 10, 0, 9999999, 999999999), - 'BATCH' => array(0, 10, 0, 9999999, 999999999), + 'ADDRESS' => array(0, 10, 0, 789742, 2580955), + '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), 'DESTROY' => array(0, 10, 0, 789742, 2473585), diff --git a/indexer/includes/reparse.php b/indexer/includes/reparse.php new file mode 100644 index 0000000..9f8d8e5 --- /dev/null +++ b/indexer/includes/reparse.php @@ -0,0 +1,84 @@ + $info){ + if($ledgerA[$table]['hash']!=$ledgerB[$table]['hash']){ + $error .= " in the {$table} table"; + if(isset($ledgerA[$table]['data'])){ + foreach($ledgerA[$table]['data'] as $idx => $info){ + if($ledgerA[$table]['data'][$idx]!=$ledgerB[$table]['data'][$idx]){ + $tx_index = $ledgerA[$table]['data'][$idx]->tx_index; + if($tx_index) + $error .= " for tx_index {$tx_index}"; + } + } + } + } + } + } + + // Display any errors and exit + if($error){ + print "\n"; + byeLog($error); + } + + // Print out a status update + $credits = substr($ledgerA['credits']['hash'],0,5); + $debits = substr($ledgerA['debits']['hash'],0,5); + $txlist = substr($ledgerA['txlist']['hash'],0,5); + print "\n\t [credits:{$credits} debits:{$debits} txlist:{$txlist}]"; + + // Report time to process block + $time = $timer->finish(); + print " Done [{$time}sec]\n"; + + // Increase block before next loop + $block++; + } + + // Print out information on the total runtime + printRuntime($runtime->finish()); + + // Notify user reparse is complete + byeLog("Reparse complete."); +} + +?> diff --git a/indexer/includes/rollback.php b/indexer/includes/rollback.php index c1eff16..5245e3f 100644 --- a/indexer/includes/rollback.php +++ b/indexer/includes/rollback.php @@ -3,15 +3,19 @@ * rollback.php - Handles rolling back database updates safely ********************************************************************/ function btnsRollback($block_index=null){ - global $mysqli; + global $mysqli, $addresses, $tickers; $block_index = (int) $block_index; $tables = [ + 'airdrops', + 'addresses', + 'batches', 'blocks', 'credits', 'debits', 'destroys', + 'fees', 'issues', 'lists', 'mints', @@ -20,9 +24,7 @@ function btnsRollback($block_index=null){ 'transactions' ]; - // Arrays to track address/tick/transaction ids - $addresses = array(); - $tickers = array(); + // Array to track transaction ids $transactions = array(); $timer = new Profiler(); @@ -32,7 +34,10 @@ function btnsRollback($block_index=null){ // Loop through all database tables foreach($tables as $table){ - // Get list of any addresses or tickers associated with the rollback blocks + // Build out the correct SQL to pull data from the various tables + $sql = false; + + // Credits / Debits if(in_array($table, array('credits','debits'))){ $sql = "SELECT a.address, @@ -45,33 +50,90 @@ function btnsRollback($block_index=null){ t2.id=t1.tick_id AND a.id=t1.address_id AND t1.block_index>{$block_index}"; - $results = $mysqli->query($sql); - if($results){ - if($results->num_rows){ - while($row = $results->fetch_assoc()){ - $row = (object) $row; - if(!in_array($row->address, $addresses)) - array_push($addresses, $row->address); - if(!in_array($row->tick, $tickers)) - array_push($tickers, $row->tick); - } - } - } else { - byeLog("Error while trying to lookup rollback data in the {$table} table"); - } + } + + // AIRDROP / DESTROY + if(in_array($table, array('airdrops','destroys'))){ + $sql = "SELECT + t2.tick, + a.address + FROM + {$table} t1, + index_tickers t2, + index_addresses a + WHERE + t2.id=t1.tick_id AND + a.id=t1.source_id AND + t1.block_index>{$block_index}"; + } + + // MINT / SEND / FEE + if(in_array($table, array('mints','sends','fees'))){ + $sql = "SELECT + t2.tick, + a.address, + a2.address as address2 + FROM + {$table} t1 + LEFT JOIN index_addresses a2 on (t1.destination_id=a2.id), + index_tickers t2, + index_addresses a + WHERE + t2.id=t1.tick_id AND + a.id=t1.source_id AND + t1.block_index>{$block_index}"; + } + + // ISSUE + if($table=='issues'){ + $sql = "SELECT + t2.tick, + a.address, + a2.address as address2, + a3.address as address3 + FROM + {$table} t1 + LEFT JOIN index_addresses a2 on (t1.transfer_id=a2.id) + LEFT JOIN index_addresses a3 on (t1.transfer_supply_id=a3.id), + index_tickers t2, + index_addresses a + WHERE + t2.id=t1.tick_id AND + a.id=t1.source_id AND + t1.block_index>={$block_index}"; } // Get list of transactions associated with the rollback blocks if($table=='transactions'){ - $results = $mysqli->query("SELECT tx_hash_id FROM transactions WHERE block_index>{$block_index}"); + $sql = "SELECT + tx_hash_id + FROM + transactions + WHERE + block_index>{$block_index}"; + } + + // Run the SQL query and populate the addresses, tickers, and transactions arrays + if($sql){ + $results = $mysqli->query($sql); if($results){ if($results->num_rows){ while($row = $results->fetch_assoc()){ $row = (object) $row; - if(!in_array($row->tx_hash_id, $transactions)) - array_push($transactions, $row->tx_hash_id); + if($table=='transactions'){ + if(!in_array($row->tx_hash_id, $transactions)) + array_push($transactions, $row->tx_hash_id); + } else { + addAddressTicker($row->address, $row->tick); + if(!is_null($row->address2)) + addAddressTicker($row->address2, $row->tick); + if(!is_null($row->address3)) + addAddressTicker($row->address3, $row->tick); + } } } + } else { + byeLog("Error while trying to lookup rollback data in the {$table} table"); } } @@ -82,7 +144,7 @@ function btnsRollback($block_index=null){ } // Update address balances to get back to sane balances based on credits/debits - updateBalances($addresses, true); + updateBalances(array_keys($addresses), true); // Update token information updateTokens($tickers, true); diff --git a/indexer/indexer.php b/indexer/indexer.php index b9b1ba4..7217cc8 100755 --- a/indexer/indexer.php +++ b/indexer/indexer.php @@ -24,6 +24,8 @@ * --testnet Load data from testnet * --block=# Load data for given block * --rollback=# Rollback data to a given block + * --reparse Reparse ALL data + * --reparse=# Reparse data from a given block * --single Load single block ********************************************************************/ @@ -31,9 +33,10 @@ error_reporting(E_ERROR|E_PARSE); // Parse in any command line args and set basic runtime flags -$args = getopt("", array("testnet::", "block::", "single::", "rollback::",)); +$args = getopt("", array("testnet::", "block::", "single::", "rollback::", "reparse::")); $testnet = (isset($args['testnet'])) ? true : false; $single = (isset($args['single'])) ? true : false; +$reparse = (isset($args['reparse'])) ? ((is_numeric($args['reparse'])) ? $args['reparse'] : true) : false; $block = (is_numeric($args['block'])) ? intval($args['block']) : false; $network = ($testnet) ? 'testnet' : 'mainnet'; $rollback = (is_numeric($args['rollback'])) ? intval($args['rollback']) : false; @@ -59,6 +62,10 @@ // Create a lock file, and bail if we detect an instance is already running createLockFile(); +// Define global assoc arrays to track address/ticker changes +$addresses = []; +$tickers = []; + // Handle rollbacks if($rollback) btnsRollback($rollback); @@ -85,96 +92,21 @@ if(file_exists($lockfile)) byeLog("found {$service} parsing a block... exiting"); +// Handle reparses +if($reparse) + btnsReparse($current, $reparse); + // Loop through the blocks until we are current while($block <= $current){ $timer = new Profiler(); print "processing block {$block}..."; - // Lookup any BTNS action broadcasts in this block (anything with bt: or btns: prefix) - $sql = "SELECT - b.text, - b.value as version, - t.hash as tx_hash, - a.address as source - FROM - {$dbase}.broadcasts b, - {$dbase}.index_transactions t, - {$dbase}.index_addresses a - WHERE - t.id=b.tx_hash_id AND - a.id=b.source_id AND - b.block_index='{$block}' AND - b.status='valid' AND - (b.text LIKE 'bt:%' OR b.text LIKE 'btns:%') - ORDER BY b.tx_index ASC"; - $results = $mysqli->query($sql); - if($results){ - if($results->num_rows){ - while($row = $results->fetch_assoc()){ - // Assoc arrays to track address/ticker changes - $addresses = array(); - $tickers = array(); - $error = false; - $row = (object) $row; - $prefixes = array('/^bt:/','/^btns:/'); - $params = explode('|',preg_replace($prefixes,'',$row->text)); - $version = $row->version; // Project Version - $source = $row->source; // Source address - - // Create database records and get ids for tx_hash and source address - $source_id = createAddress($row->source); - $tx_hash_id = createTransaction($row->tx_hash); - - // Trim whitespace from any PARAMS - foreach($params as $idx => $value) - $params[$idx] = trim($value); - - // Extract ACTION from PARAMS - $action = strtoupper(array_shift($params)); - - // Support legacy BTNS format with no VERSION on DEPLOY/MINT/TRANSFER actions (default to VERSION 0) - if(in_array($action,array('DEPLOY','MINT','TRANSFER')) && isLegacyBTNSFormat($params)) - array_splice($params, 0, 0, 0); - - // Support old BRC20/SRC20 actions - if($action=='TRANSFER') $action = 'SEND'; - if($action=='DEPLOY') $action = 'ISSUE'; - - // Define basic BTNS transaction data object - $data = (object) array( - 'ACTION' => $action, // Action (ISSUE, MINT, SEND, etc) - 'BLOCK_INDEX' => $block, // Block index - 'SOURCE' => $row->source, // Source/Broadcasting address - 'TX_HASH' => $row->tx_hash // Transaction Hash - ); - - // Validate Action - if(!array_key_exists($action,PROTOCOL_CHANGES)) - $error = 'invalid: Unknown ACTION'; - - // Verify action is activated (past ACTIVATION_BLOCK) - if(!$error && !isEnabled($action, $network, $block)) - $error = 'invalid: ACTIVATION_BLOCK'; - - // Set action to UNKNOWN if we detect error - if($error) - $data->ACTION = $action = 'UNKNOWN'; - - // Create a record of this transaction in the transactions table - createTxIndex($data); - - // Get tx_index of record using tx_hash - $data->TX_INDEX = getTxIndex($data->TX_HASH); - - // Handle processing the specific BTNS ACTION commands - btnsAction($action, $params, $data, $error); - } - } - } else { - byeLog("Error while trying to lookup BTNS broadcasts"); - } + // Get any broadcast transactions for this block and process them + $txs = getBroadcastTransactions($block); + foreach($txs as $tx) + processTransaction($tx); - // Create hash of the credits/debits/balances table and create record in `blocks` table + // Create record in `blocks` table with hashes of the credits/debits/transactions tables createBlock($block); // Do a sanity check to verify that token supplys match data in credits/debits/balances tables @@ -201,4 +133,7 @@ // Remove the lockfile now that we are done running removeLockFile(); -print "Total Execution time: " . $runtime->finish() ." seconds\n"; +// Print out information on the total runtime +printRuntime($runtime->finish()); + +?> \ No newline at end of file diff --git a/indexer/sql/addresses.sql b/indexer/sql/addresses.sql new file mode 100644 index 0000000..ed8b349 --- /dev/null +++ b/indexer/sql/addresses.sql @@ -0,0 +1,16 @@ +DROP TABLE IF EXISTS addresses; +CREATE TABLE addresses ( + tx_index INTEGER UNSIGNED, -- Unique transaction index + source_id INTEGER UNSIGNED, -- id of record in index_addresses table + tx_hash_id INTEGER UNSIGNED, -- id of record in index_transactions + block_index INTEGER UNSIGNED, -- block index of ADDRESS transaction + fee_preference INTEGER UNSIGNED, + require_memo INTEGER UNSIGNED, + status_id INTEGER UNSIGNED -- id of record in index_statuses table +) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +CREATE UNIQUE INDEX tx_index ON addresses (tx_index); +CREATE INDEX source_id ON addresses (source_id); +CREATE INDEX tx_hash_id ON addresses (tx_hash_id); +CREATE INDEX block_index ON addresses (block_index); +CREATE INDEX status_id ON addresses (status_id); \ No newline at end of file diff --git a/indexer/sql/airdrops.sql b/indexer/sql/airdrops.sql new file mode 100644 index 0000000..82d40e1 --- /dev/null +++ b/indexer/sql/airdrops.sql @@ -0,0 +1,22 @@ +DROP TABLE IF EXISTS airdrops; +CREATE TABLE airdrops ( + 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 AIRDROP transaction + tick_id INTEGER UNSIGNED, -- id of record in index_ticks + source_id INTEGER UNSIGNED, -- id of record in index_addresses table + list_id INTEGER UNSIGNED, -- id of record in index_transactions + amount VARCHAR(250), -- Amount of token in airdrop + 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 airdrops (tx_index); +CREATE INDEX source_id ON airdrops (source_id); +CREATE INDEX tx_hash_id ON airdrops (tx_hash_id); +CREATE INDEX block_index ON airdrops (block_index); +CREATE INDEX list_id ON airdrops (list_id); +CREATE INDEX tick_id ON airdrops (tick_id); +CREATE INDEX memo_id ON airdrops (memo_id); +CREATE INDEX status_id ON airdrops (status_id); + diff --git a/indexer/sql/batches.sql b/indexer/sql/batches.sql new file mode 100644 index 0000000..b79b4d1 --- /dev/null +++ b/indexer/sql/batches.sql @@ -0,0 +1,14 @@ +DROP TABLE IF EXISTS batches; +CREATE TABLE batches ( + tx_index INTEGER UNSIGNED, -- Unique transaction index + source_id INTEGER UNSIGNED, -- id of record in index_addresses table + tx_hash_id INTEGER UNSIGNED, -- id of record in index_transactions + block_index INTEGER UNSIGNED, -- block index of BATCH transaction + status_id INTEGER UNSIGNED -- id of record in index_statuses table +) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +CREATE UNIQUE INDEX tx_index ON batches (tx_index); +CREATE INDEX source_id ON batches (source_id); +CREATE INDEX tx_hash_id ON batches (tx_hash_id); +CREATE INDEX block_index ON batches (block_index); +CREATE INDEX status_id ON batches (status_id); \ No newline at end of file diff --git a/indexer/sql/blocks.sql b/indexer/sql/blocks.sql index 07655b0..4cf6ffb 100644 --- a/indexer/sql/blocks.sql +++ b/indexer/sql/blocks.sql @@ -5,11 +5,9 @@ CREATE TABLE blocks ( block_time INTEGER UNSIGNED, credits_hash_id INTEGER UNSIGNED, -- id of record in index_transactions table (sha256 hash of credits data) debits_hash_id INTEGER UNSIGNED, -- id of record in index_transactions table (sha256 hash of debits data) - balances_hash_id INTEGER UNSIGNED, -- id of record in index_transactions table (sha256 hash of balances data) txlist_hash_id INTEGER UNSIGNED -- id of record in index_transactions table (sha256 hash of index_tx data) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; CREATE INDEX block_index ON blocks (block_index); CREATE INDEX credits_hash_id ON blocks (credits_hash_id); CREATE INDEX debits_hash_id ON blocks (debits_hash_id); -CREATE INDEX balances_hash_id ON blocks (balances_hash_id); diff --git a/indexer/sql/fees.sql b/indexer/sql/fees.sql new file mode 100644 index 0000000..91ae260 --- /dev/null +++ b/indexer/sql/fees.sql @@ -0,0 +1,17 @@ +DROP TABLE IF EXISTS fees; +CREATE TABLE fees ( + tx_index INTEGER UNSIGNED NOT NULL, -- Unique transaction index + block_index INTEGER UNSIGNED, -- block index of transaction + source_id INTEGER UNSIGNED, -- id of record in index_addresses table + tick_id INTEGER UNSIGNED, -- id of record in index_tickers (default = GAS) + amount VARCHAR(250), -- Amount of TICK + method INTEGER UNSIGNED NOT NULL, -- FEE Payment Method (1=Destroy, 2=Donate) + destination_id INTEGER UNSIGNED -- id of record in index_addresses table +) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +CREATE INDEX tx_index ON fees (tx_index); +CREATE INDEX block_index ON fees (block_index); +CREATE INDEX source_id ON fees (source_id); +CREATE INDEX tick_id ON fees (tick_id); +CREATE INDEX destination_id ON fees (destination_id); + diff --git a/indexer/sql/issues.sql b/indexer/sql/issues.sql index 742005c..bda14fa 100644 --- a/indexer/sql/issues.sql +++ b/indexer/sql/issues.sql @@ -1,32 +1,34 @@ DROP TABLE IF EXISTS issues; CREATE TABLE issues ( - tx_index INTEGER UNSIGNED NOT NULL, -- Unique transaction index - tick_id INTEGER UNSIGNED, -- id of record in index_tickers table - max_supply VARCHAR(250), -- Maximum token supply (1000000000000000000000.000000000000000000 = 40 Characters) - max_mint VARCHAR(250), -- Maximum amount of supply a MINT transaction can issue - decimals INTEGER(2) UNSIGNED, -- Number of decimal places token should have (max: 18, default: 0) - description VARCHAR(250), -- URL to a an icon to use for this token (48x48 standard size) - mint_supply VARCHAR(250), -- Maximum amount of supply a MINT transaction can issue - transfer_id INTEGER UNSIGNED, -- id of record in index_addresses table - transfer_supply_id INTEGER UNSIGNED, -- id of record in index_addresses table - lock_supply TINYINT(1) NOT NULL DEFAULT 0, -- Locks MAX_SUPPLY - lock_mint TINYINT(1) NOT NULL DEFAULT 0, -- Locks MAX_MINT - lock_description TINYINT(1) NOT NULL DEFAULT 0, -- Locks DESCRIPTION - lock_rug TINYINT(1) NOT NULL DEFAULT 0, -- Locks RUG - lock_sleep TINYINT(1) NOT NULL DEFAULT 0, -- Locks SLEEP - lock_callback TINYINT(1) NOT NULL DEFAULT 0, -- Locks CALLBACK_BLOCK/TICK/AMOUNT - callback_block INTEGER UNSIGNED, -- block_index after which CALLBACK cand be used - callback_tick_id INTEGER UNSIGNED, -- id of record in index_tickers table - callback_amount VARCHAR(250), -- AMOUNT users get if CALLBACK - allow_list_id INTEGER UNSIGNED NOT NULL default 0, -- id of record in index_transactions table - block_list_id INTEGER UNSIGNED NOT NULL default 0, -- id of record in index_transactions table - mint_address_max VARCHAR(250), -- Maximum amount of supply an address can MINT - mint_start_block INTEGER UNSIGNED, -- block_index when MINT transactions are allowed (begin mint) - mint_stop_block INTEGER UNSIGNED, -- BLOCK_INDEX when MINT transactions are NOT allowed (end mint) - source_id INTEGER UNSIGNED, -- id of record in index_addresses table (address that did DEPLOY) - tx_hash_id INTEGER UNSIGNED, -- id of record in index_transactions - block_index INTEGER UNSIGNED, -- block index of DEPLOY transaction - status_id INTEGER UNSIGNED -- id of record in index_statuses table + tx_index INTEGER UNSIGNED NOT NULL, -- Unique transaction index + tick_id INTEGER UNSIGNED, -- id of record in index_tickers table + max_supply VARCHAR(250), -- Maximum token supply (1000000000000000000000.000000000000000000 = 40 Characters) + max_mint VARCHAR(250), -- Maximum amount of supply a MINT transaction can issue + decimals VARCHAR(2), -- Number of decimal places token should have (max: 18, default: 0) + description VARCHAR(250), -- URL to a an icon to use for this token (48x48 standard size) + mint_supply VARCHAR(250), -- Maximum amount of supply a MINT transaction can issue + transfer_id INTEGER UNSIGNED, -- id of record in index_addresses table + transfer_supply_id INTEGER UNSIGNED, -- id of record in index_addresses table + lock_max_supply VARCHAR(1), -- Locks MAX_SUPPLY + lock_mint VARCHAR(1), -- Locks MINT + lock_mint_supply VARCHAR(1), -- Locks MINT_SUPPLY + lock_max_mint VARCHAR(1), -- Locks MAX_MINT + lock_description VARCHAR(1), -- Locks DESCRIPTION + lock_rug VARCHAR(1), -- Locks RUG + lock_sleep VARCHAR(1), -- Locks SLEEP + lock_callback VARCHAR(1), -- Locks CALLBACK_BLOCK/TICK/AMOUNT + callback_block VARCHAR(15), -- block_index after which CALLBACK cand be used + callback_tick_id INTEGER UNSIGNED, -- id of record in index_tickers table + callback_amount VARCHAR(250), -- AMOUNT users get if CALLBACK + allow_list_id INTEGER UNSIGNED, -- id of record in index_transactions table + block_list_id INTEGER UNSIGNED, -- id of record in index_transactions table + mint_address_max VARCHAR(250), -- Maximum amount of supply an address can MINT + mint_start_block VARCHAR(15), -- block_index when MINT transactions are allowed (begin mint) + mint_stop_block VARCHAR(15), -- BLOCK_INDEX when MINT transactions are NOT allowed (end mint) + source_id INTEGER UNSIGNED, -- id of record in index_addresses table (address that did DEPLOY) + tx_hash_id INTEGER UNSIGNED, -- id of record in index_transactions + block_index INTEGER UNSIGNED, -- block index of DEPLOY transaction + status_id INTEGER UNSIGNED -- id of record in index_statuses table ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; CREATE UNIQUE INDEX tx_index ON issues (tx_index); @@ -34,8 +36,11 @@ CREATE INDEX tick_id ON issues (tick_id); CREATE INDEX source_id ON issues (source_id); CREATE INDEX transfer_id ON issues (transfer_id); CREATE INDEX transfer_supply_id ON issues (transfer_supply_id); +CREATE INDEX block_index ON issues (block_index); CREATE INDEX status_id ON issues (status_id); CREATE INDEX tx_hash_id ON issues (tx_hash_id); CREATE INDEX callback_tick_id ON issues (callback_tick_id); CREATE INDEX allow_list_id ON issues (allow_list_id); CREATE INDEX block_list_id ON issues (block_list_id); + + diff --git a/indexer/sql/lists.sql b/indexer/sql/lists.sql index d8cf673..44e836f 100644 --- a/indexer/sql/lists.sql +++ b/indexer/sql/lists.sql @@ -10,8 +10,14 @@ CREATE TABLE lists ( status_id INTEGER UNSIGNED -- id of record in index_statuses table ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - - +CREATE UNIQUE INDEX tx_index ON lists (tx_index); +CREATE INDEX block_index ON lists (block_index); +CREATE INDEX type ON lists (type); +CREATE INDEX edit ON lists (edit); +CREATE INDEX list_tx_hash_id ON lists (list_tx_hash_id); +CREATE INDEX tx_hash_id ON lists (tx_hash_id); +CREATE INDEX source_id ON lists (source_id); +CREATE INDEX status_id ON lists (status_id); diff --git a/indexer/sql/tokens.sql b/indexer/sql/tokens.sql index 74fd91e..9ee27ae 100644 --- a/indexer/sql/tokens.sql +++ b/indexer/sql/tokens.sql @@ -8,8 +8,10 @@ CREATE TABLE tokens ( max_mint VARCHAR(250), -- Supply minted decimals TINYINT(2), -- 0=non-divisible, 1-18=divisible description VARCHAR(250), -- URL to icon - lock_supply TINYINT(1) NOT NULL DEFAULT 0, -- Locks MAX_SUPPLY - lock_mint TINYINT(1) NOT NULL DEFAULT 0, -- Locks MAX_MINT + lock_max_supply TINYINT(1) NOT NULL DEFAULT 0, -- Locks MAX_SUPPLY + lock_mint TINYINT(1) NOT NULL DEFAULT 0, -- Locks MINT + lock_mint_supply TINYINT(1) NOT NULL DEFAULT 0, -- Locks MINT_SUPPLY + lock_max_mint TINYINT(1) NOT NULL DEFAULT 0, -- Locks MAX_MINT lock_description TINYINT(1) NOT NULL DEFAULT 0, -- Locks DESCRIPTION lock_rug TINYINT(1) NOT NULL DEFAULT 0, -- Locks RUG lock_sleep TINYINT(1) NOT NULL DEFAULT 0, -- Locks SLEEP @@ -28,8 +30,10 @@ CREATE TABLE tokens ( CREATE INDEX tick_id ON tokens (tick_id); CREATE INDEX owner_id ON tokens (owner_id); -CREATE INDEX lock_supply ON tokens (lock_supply); +CREATE INDEX lock_max_supply ON tokens (lock_max_supply); CREATE INDEX lock_mint ON tokens (lock_mint); +CREATE INDEX lock_max_mint ON tokens (lock_max_mint); +CREATE INDEX lock_mint_supply ON tokens (lock_mint_supply); CREATE INDEX lock_description ON tokens (lock_description); CREATE INDEX lock_rug ON tokens (lock_rug); CREATE INDEX lock_sleep ON tokens (lock_sleep);