753 lines
32 KiB
PHP
753 lines
32 KiB
PHP
<?
|
|
/*
|
|
TODO: abstract DB functions to enable new DB types
|
|
TODO: use daux.io for comments and documentation
|
|
*/
|
|
|
|
/* block parser class
|
|
|
|
|
|
VARIABLES:
|
|
|
|
$dbh - PDO database object
|
|
$control_count - currently, the script updates the control database field every 100 blocks
|
|
|
|
$rpc - a florin RPC object for making calls to florincoind
|
|
$debug - if debug is set, important info will be output to the log
|
|
$action - the action specified from the -a flag when running the program
|
|
|
|
|
|
FUNCTIONS:
|
|
|
|
decho($m) - output the message passed through $m and output it if the $debug flag is set
|
|
|
|
get_lockdb() - returns 0 if the DB is locked, 1 if it is locked
|
|
|
|
set_lockdb($value) - sets lockdb to $value, outputs any warnings regarding the lockstatus
|
|
|
|
testSimple() - test function that is run via the "test" action
|
|
|
|
database_write_block($data) - $data is the block being worked on currently
|
|
this function first uses verifyRawTX() to store transaction data, then stores block data
|
|
|
|
verifyRawTX($hash, $block) - the $hash here refers to the transaction hash found in the block $block (which is the index of the block - not its hash or object)
|
|
verifyRawTX goes through and stores the transaction into the tx table
|
|
it also calls the TXMP process for filtering transaction comments
|
|
verifyRawTX then goes through the inputs and outputs of the transaction and stores them into their respective tables
|
|
|
|
fillVin($vin, $db_id) - $vin is the object containing vin data in an array, $db_id is the index of the transaction within the tx table
|
|
fillVin stores the reference to the previous transaction referenced by this one
|
|
|
|
fillVout($vout, $db_id, $txid) - $vout is the output object, $db_id is the current transaction id within the tx table, $txid is the hash of the transaction we're currently focusing on
|
|
fillVout will store both the vout table values to associate each vout with a txid
|
|
fillVout also stores the address in each vout in a separate table for faster indexing
|
|
|
|
getHeight() - returns an array with all relevant info on block height
|
|
[0] = how many blocks missing
|
|
[1] = current height of blockchain from RPC call
|
|
[2] = index of latest block in database
|
|
|
|
testScanComments($from) - display all comments from block $from and higher
|
|
|
|
rescan($start, $back) - $start is the block to start at, unless $back is set, in which case the program starts at block height - $back
|
|
this function looks for reorganizations within the blockchain using fillInBlanks
|
|
|
|
fillInBlanks($delete, $delete_all, $start) - if $delete isn't set, the function will not delete anything. $delete_all will delete everything from tables (shouldn't use this, it's better to drop tables). $start is where program should start (which block height)
|
|
fillInBlanks first handles deleting all data from the database if it doesn't match the current block structure. it then goes through the missing blocks and calls database_write_block for each block that is missing. the rest is history ...
|
|
|
|
|
|
initial_setup() - temporary function for creating tables for the first time when running aterna with the "setup" command. this will populate MySQL database tables
|
|
*/
|
|
|
|
|
|
class block_parser {
|
|
public $dbh;
|
|
public $control_count;
|
|
public function __construct($rpc, $setup_file, $debug, $action) {
|
|
require($setup_file);
|
|
$this->rpc = $rpc;
|
|
$this->dbh = $dbh;
|
|
$this->debug = $debug;
|
|
$this->action = $action;
|
|
$this->control_count= 0;
|
|
}
|
|
|
|
function decho($m) { if ($this->debug) echo $m . "\r\n"; return $this->debug; }
|
|
|
|
function get_lockdb() {
|
|
$q = $this->dbh->query("select value from control where name = 'lockdb'");
|
|
$v = $q->fetch();
|
|
return (int)$v[0];
|
|
}
|
|
|
|
function set_lockdb($value) {
|
|
// if we're trying to lock the database, and it's already locked, just exit
|
|
if ($value == 1 && $this->get_lockdb() == 1 && $this->action != "unlock") {
|
|
if ($this->action == "lock") echo "&& DB already locked. EXITING PROGRAM\r\n";
|
|
|
|
// if set_lockdb is called outside of the lock/unlock action
|
|
else echo "&& DB locked ... process is currently running.\r\n&& if it isn't, execute \"./floexplorer -a unlock\" to force the DB to unlock itself.\r\n&& only do this if you are certain there is no other process modifying this database now. \r\n&& EXITING PROGRAM\r\n";
|
|
exit();
|
|
}
|
|
|
|
// otherwise, just update to $value
|
|
$this->dbh->query("update control set value = '$value', entry = unix_timestamp(now()) where name = 'lockdb'");
|
|
echo "&& lockdb set to $value\r\n";
|
|
}
|
|
|
|
function testSimple() {
|
|
$count = 0;
|
|
foreach ($this->dbh->query("select address, ((sum(value)*100)-sum(value))/100000000 as v from vout_address as a join (select a.id, a.value from vout as a join (select tx.id from tx where outputs = 100000000 and coinbase = 1) as b where a.tx_db_id = b.id) as b where a.vout_id = b.id group by address order by v") as $txid) {
|
|
echo $txid[0] . "\t" . $txid[1] . "\r\n";
|
|
$count++;
|
|
}
|
|
echo "Total: $count";
|
|
}
|
|
|
|
function database_write_block($data) {
|
|
$hash = $data['hash'];
|
|
$total_coins = 0;
|
|
|
|
// if we're not in the genesis block
|
|
if ($hash != "09c7781c9df90708e278c35d38ea5c9041d7ecfcdd1c56ba67274b7cff3e1cea") {
|
|
$this->decho("block " . $data["height"] . " hash $hash time " . $data["time"] . " nonce " . $data["nonce"] . " size " . $data["size"]);
|
|
|
|
// go through transactions in the block
|
|
foreach ($data['tx'] as $txhash) {
|
|
if ($coins = $this->verifyRawTX($txhash, $data["height"])) {$total_coins += $coins;}
|
|
else { echo "* * * WARNING: could not fully parse block " . $data['height'] . "(failure to read tx $txhash) * * *\r\n"; }
|
|
}
|
|
|
|
// if we've parsed 100 blocks, update the control table
|
|
$this->control_count++;
|
|
if (!$data["nextblockhash"] || $this->control_count % 100 == 0) {
|
|
$this->dbh->query($q = "update control set value = '" . $data["height"] . "', entry = unix_timestamp(now()) where name = 'lastblock'");
|
|
$this->decho("block " . $data["height"] . " UPDATE CONTROL SET VALUE = '" . $data["height"] . "' WHERE NAME = 'lastblock'");
|
|
// echo "No next block, updating control for next time\r\n". $q . "\r\n";
|
|
}
|
|
}
|
|
|
|
// write block
|
|
if ($hash == "09c7781c9df90708e278c35d38ea5c9041d7ecfcdd1c56ba67274b7cff3e1cea") {
|
|
echo "||||| GENESIS BLOCK\r\n";
|
|
echo $q = 'insert into block (id, hash, prev, next, time, diff, txs, total_coins, size, merk, nonce, version, inactive) values (' . $data["height"] . ', "' . $data["hash"] . '", "", "' . $data["nextblockhash"] . '", ' . $data["time"] . ', ' . $data["difficulty"] . ', ' . count($data["tx"]) . ', ' . $total_coins . ', ' . $data["size"] . ', "' . $data["merkleroot"] . '", ' . $data["nonce"] . ', ' . $data["version"] . ', 0)';
|
|
echo "\r\n";
|
|
} else {
|
|
$q = 'insert into block (id, hash, prev, next, time, diff, txs, total_coins, size, merk, nonce, version, inactive) values (' . $data["height"] . ', "' . $data["hash"] . '", "' . $data["previousblockhash"] . '", "' . $data["nextblockhash"] . '", ' . $data["time"] . ', ' . $data["difficulty"] . ', ' . count($data["tx"]) . ', ' . $total_coins . ', ' . $data["size"] . ', "' . $data["merkleroot"] . '", ' . $data["nonce"] . ', ' . $data["version"] . ', 0)';
|
|
}
|
|
if (!$this->dbh->query($q)) {
|
|
echo "***** FATAL ERROR: couldn't write block " . $data['height'] . " into database.\r\n";
|
|
echo $q . "\r\n";
|
|
$this->set_lockdb(0);
|
|
exit();
|
|
}
|
|
}
|
|
|
|
function verifyRawTX($hash, $block) {
|
|
if ($hash == "730f0c8ddc5a592d5512566890e2a73e45feaa6748b24b849d1c29a7ab2b2300") {
|
|
$this->decho("||||| genesis block tx found\r\n");
|
|
} else {
|
|
// decode and get json from this tx hex
|
|
$raw_tx= trim($this->rpc->call('getrawtransaction', $hash));
|
|
$decoded_tx = $this->rpc->call('decoderawtransaction', $raw_tx);
|
|
// add up all inputs/outputs
|
|
$satoshi = 100000000;
|
|
$coinbase = 0;
|
|
foreach ($decoded_tx["vout"] as $out) $outputs += round($out["value"] * $satoshi);
|
|
foreach ($decoded_tx['vin'] as $in) if (isset($in['coinbase'])) $coinbase = 1;
|
|
|
|
//foreach ($raw_tx["vin"] as $in) $inputs += $in["value"] * $satoshi;
|
|
$size = (strlen($raw_tx))/2;
|
|
$this->decho("block $block txid $hash inputs $inputs size $size outputs $outputs");
|
|
|
|
// sometimes the tx-comment is null, false, or empty string, let's store it in the DB as just NULL for any of these cases
|
|
$txc_fix = $decoded_tx['tx-comment'];
|
|
if ($txc_fix === "" || $txc_fix === FALSE) { $txc_fix = NULL; }
|
|
$rtime = (int)(microtime(1) * 10000);
|
|
|
|
$stmt = $this->dbh->prepare('insert into tx (hash, block, message, outputs, inputs, size, version, coinbase, inactive, rtime) values (?, ?, ?, ?, ?, ?, ?, ?, 0, ' . $rtime . ')');
|
|
$stmt->bindParam(1, $hash, PDO::PARAM_STR, strlen($hash));
|
|
$stmt->bindParam(2, $block, PDO::PARAM_INT);
|
|
$stmt->bindParam(3, $txc_fix, PDO::PARAM_STR, strlen($txc_fix));
|
|
$stmt->bindParam(4, $outputs, PDO::PARAM_INT);
|
|
$stmt->bindParam(5, $inputs, PDO::PARAM_INT);
|
|
$stmt->bindParam(6, $size, PDO::PARAM_INT);
|
|
$stmt->bindParam(7, $decoded_tx["version"], PDO::PARAM_INT);
|
|
$stmt->bindParam(8, $coinbase, PDO::PARAM_INT);
|
|
|
|
$stmt->execute();
|
|
|
|
$r = $this->dbh->query("select id from tx where hash = '$hash' and rtime = $rtime");
|
|
$db_id = $r->fetchAll();
|
|
$db_id = $db_id[0];
|
|
|
|
if (!$db_id) {
|
|
$this->decho("***** FATAL ERROR: database returned no index for tx - txid = $hash *****");
|
|
$this->set_lockdb(0);
|
|
exit();
|
|
}
|
|
|
|
// store vout and vin data
|
|
foreach ($decoded_tx['vout'] as $vout) { $this->fillVout($vout, $db_id[0], $hash); }
|
|
foreach ($decoded_tx['vin'] as $vin) { $this->fillVin($vin, $db_id[0], $coinbase); }
|
|
|
|
// return coins
|
|
return $outputs;
|
|
}
|
|
return; // return null if error
|
|
}
|
|
|
|
function fillVin($vin, $db_id, $coinbase) {
|
|
$db_id = (int)($db_id);
|
|
$txid = $vin['txid'];
|
|
|
|
// find the db_id referenced by txid
|
|
$q = $this->dbh->query("select id from tx where hash = '$txid'");
|
|
$r = $q->fetchAll();
|
|
$prev_tx_db_id = $r[0][0];
|
|
|
|
// find the address associated with this vin
|
|
if (isset($prev_tx_db_id) && isset($db_id) && isset($vin['txid']) && isset($vin['vout']) && !$coinbase) {
|
|
$addr = $this->dbh->query($q = 'select address from vout_address as A join (select id from vout where n = ' . (int)$vin['vout'] . ' and tx_db_id = ' . $prev_tx_db_id . ') as B where A.vout_id = B.id');
|
|
$address = $addr->fetchAll();
|
|
$address = $address[0][0];
|
|
}
|
|
$stmt = $this->dbh->prepare('insert into vin (vout, tx_db_id, txid, address, coinbase) values (?, ?, ?, ?, ?)');
|
|
$stmt->bindParam(1, $vin['vout'], PDO::PARAM_INT);
|
|
$stmt->bindParam(2, $db_id, PDO::PARAM_INT);
|
|
$stmt->bindParam(3, $txid, PDO::PARAM_STR, strlen($txid));
|
|
$stmt->bindParam(4, $address, PDO::PARAM_STR, strlen($address));
|
|
$stmt->bindParam(5, $coinbase, PDO::PARAM_INT);
|
|
if (!$stmt->execute()) {
|
|
$this->decho("***** FATAL ERROR: could not write vin into DB (txid: $txid, vout: " . $vin['vout'] . ")\r\n");
|
|
$this->set_lockdb(0);
|
|
exit();
|
|
}
|
|
}
|
|
|
|
function fillVout($vout, $db_id, $txid) {
|
|
$satoshi = 100000000;
|
|
// write each vout into vout table
|
|
$stmt = $this->dbh->prepare('insert into vout (tx_db_id, value, n) values (?, ?, ?)');
|
|
$val = round($vout['value']*$satoshi);
|
|
$stmt->bindParam(1, $db_id, PDO::PARAM_INT);
|
|
$stmt->bindParam(2, $val, PDO::PARAM_INT);
|
|
$stmt->bindParam(3, $vout['n'], PDO::PARAM_INT);
|
|
if (!$stmt->execute()) {
|
|
$this->decho("***** FATAL ERROR: could not write vout into DB (tx_db_id: $db_id, n: " . $vout['n'] . ")\r\n");
|
|
$this->set_lockdb(0);
|
|
exit();
|
|
}
|
|
$db_id = (int)$this->dbh->lastInsertId();
|
|
if (!isset($vout['scriptPubKey']['addresses'])) return;
|
|
|
|
// write each address into vout_address table
|
|
foreach ($vout['scriptPubKey']['addresses'] as $address) {
|
|
$stmt = $this->dbh->prepare('insert into vout_address (vout_id, address) values (?, ?)');
|
|
$stmt->bindParam(1, $db_id, PDO::PARAM_INT);
|
|
$stmt->bindParam(2, $address, PDO::PARAM_STR, strlen($address));
|
|
if (!$stmt->execute()) {
|
|
$this->decho("***** FATAL ERROR: could not write vout_address into DB (vout_id: $db_id)\r\n");
|
|
$this->set_lockdb(0);
|
|
exit();
|
|
}
|
|
}
|
|
}
|
|
|
|
function getHeight() {
|
|
$r = $this->dbh->query("select MAX(id) from block where inactive = 0");
|
|
$v = $r->fetch();
|
|
$max_id = $v[0]; // max block in DB
|
|
$height = $this->rpc->call('getblockcount'); // max block in local blockchain
|
|
$r2 = $this->dbh->query("select value from control where name = 'lastblock'");
|
|
$v2 = $r2->fetch();
|
|
$controlMax = $v2[0]; // max block in control table under value "lastblock"
|
|
if ($controlMax <= $max_id) {
|
|
$max_id = $controlMax;
|
|
}
|
|
else echo "-- Database is up to date [$max_id/$height]\r\n";
|
|
return array($height - $max_id, $height, $max_id, $controlMax);
|
|
}
|
|
|
|
function testScanComments($from) {
|
|
if (!$from) $from = 0;
|
|
else $from = (int)$from;
|
|
$height = $this->getHeight();
|
|
for ($i = $from; $i <= $height[1]; $i++) {
|
|
$block_hash = $this->rpc->call('getblockhash', $i);
|
|
$block = $this->rpc->call('getblock', $block_hash);
|
|
$this->decho("looking through block $i which has " . count($block['tx']) . "tx...");
|
|
foreach ($block['tx'] as $txnum=>$txhash) {
|
|
if ($txc = $this->get_tx_comment($txhash)) {
|
|
$txcs[$block["height"]] = $txc;
|
|
echo "$i/" . $height[1] . ": $txnum: $txc\r\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function rescan_tx($hash, $block) {
|
|
$this->decho("=- rescanning tx $hash in block $block");
|
|
$rawtx = $this->rpc->call('getrawtransaction', $hash);
|
|
$dectx = $this->rpc->call('decoderawtransaction', $rawtx);
|
|
|
|
$blockid = $block['height'];
|
|
$message = $dectx['tx-comment'];
|
|
// find tx data and check if it's the same as database data
|
|
|
|
$q = $this->dbh->query($dbq = "select id, hash, block, message from tx where hash = '$hash' and inactive = 0");
|
|
$r = $q->fetchAll(PDO::FETCH_NUM);
|
|
|
|
|
|
// if any transactions should be inactive, find them and add them to list
|
|
// current criteria: transaction not found within this block any more, transaction message is different (somehow)
|
|
$inactive_tx = null;
|
|
foreach ($r as $result_row) {
|
|
//echo "txid: $hash ? blocks match[" . ($result_row[2] == $blockid) . "] && tx-comments match[" . ($result_row[3] == $message) . "]\r\n";
|
|
if ($result_row[2] != $blockid || $result_row[3] != $message) {
|
|
$inactive_tx[] = $result_row[0];
|
|
}
|
|
}
|
|
|
|
// if there are some inactive tx, make them inactive
|
|
if (count($inactive_tx) > 0) {
|
|
echo "\r\n=-=-=-=-=-= FOUND INACTIVE TRANSACTIONS!\r\n";
|
|
foreach ($inactive_tx as $tx_db_id) {
|
|
// make inactive each transaction
|
|
$this->decho("=- update tx set inactive = 1 where id = $tx_db_id");
|
|
if (!$this->dbh->query("update tx set inactive = 1 where id = $tx_db_id")) {
|
|
$this->decho("***** FATAL ERROR: cannot update tx $tx_db_id to inactive");
|
|
$this->set_lockdb(0);
|
|
exit();
|
|
}
|
|
|
|
// make inactive each vin
|
|
$this->decho("=- update vin set inactive = 1 where tx_db_id = $tx_db_id");
|
|
if (!$this->dbh->query("update vin set inactive = 1 where tx_db_id = $tx_db_id")) {
|
|
$this->decho("***** FATAL ERROR: cannot update vin table to set all vin where tx_db_id = $tx_db_id to inactive");
|
|
$this->set_lockdb(0);
|
|
exit();
|
|
}
|
|
|
|
// get a list of all vout, make them all inactive
|
|
$r3 = $this->dbh->query("select id from vout where tx_db_id = $tx_db_id");
|
|
$v3 = $r3->fetchAll(PDO::FETCH_NUM);
|
|
foreach ($v3 as $vout_db_id) {
|
|
$vout_db_id = (int)$vout_db_id[0];
|
|
$this->decho("=- update vout_address set inactive = 1 where vout_id = $vout_db_id");
|
|
if (!$this->dbh->query("update vout_address set inactive = 1 where vout_id = $vout_db_id")) {
|
|
$this->decho("***** FATAL ERROR: cannot update vout_address table to set all vout_address where vout_id = $vout_db_id inactive");
|
|
$this->set_lockdb(0);
|
|
exit();
|
|
}
|
|
}
|
|
|
|
// make the vouts inactive
|
|
$this->decho("=- update vout set inactive = 1 where tx_db_id = $tx_db_id");
|
|
if (!$this->dbh->query("update vout set inactive = 1 where tx_db_id = $tx_db_id")) {
|
|
$this->decho("***** FATAL ERROR: cannot update vout table to set all vout where tx_db_id = $tx_db_id to inactive");
|
|
$this->set_lockdb(0);
|
|
exit();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function rescan($start, $back) {
|
|
$height = $this->getHeight();
|
|
if ($height[1] > $height[2]) echo "\r\n-- Found incomplete data in database [$height[2]/$height[1]] (control last recorded: $height[3])\r\n";
|
|
$rpc_height = $height[1];
|
|
|
|
// get the highest block in our database according to control field
|
|
$r = $this->dbh->query("select value from control where name = 'lastblock'");
|
|
$v = $r->fetch();
|
|
$db_lastblock = (int)$v[0];
|
|
|
|
// check the highest block in our database according to max(ID) vs control field
|
|
if ($height[2] < $db_lastblock) {
|
|
$this->decho("* * * WARNING: inconsistent data in control table - lastblock = $db_lastblock, max(id) from block = " . $height[2]);
|
|
$db_lastblock = $height[2];
|
|
}
|
|
|
|
// start looking through blocks starting at the minimum block minus our preset value
|
|
if ($back) { $start = $db_lastblock - $back; }
|
|
if ($start < 0) $start = 0;
|
|
$this->decho("-- scanning last $back blocks (from height $start to $rpc_height)...");
|
|
|
|
for ($i = $start; $i <= $rpc_height; $i++) {
|
|
// retrieve block data from DB
|
|
$r = $this->dbh->query("select hash, next, prev from block where id = $i and inactive = 0");
|
|
$v = $r->fetch();
|
|
|
|
// retrieve block data from RPC
|
|
$rpc_block_hash = $this->rpc->call('getblockhash', $i);
|
|
$rpc_block_full = $this->rpc->call('getblock', $rpc_block_hash);
|
|
$rpc_block_next = $rpc_block_full['nextblockhash'];
|
|
$rpc_block_prev = $rpc_block_full['previousblockhash'];
|
|
|
|
|
|
// check next and previous blocks
|
|
if ($v[1] != $rpc_block_next) {
|
|
echo ("@@ found inconsistent data at block $i: db says next block is " . $v[1] . ", RPC says $rpc_block_next\r\n");
|
|
if (!$this->dbh->query("update block set next = '$rpc_block_next' where id = $i and inactive != 1")) {
|
|
echo ("***** FATAL ERROR: could not update block $i with new next value $rpc_block_next, exiting *****\r\n");
|
|
$this->set_lockdb(0);
|
|
exit();
|
|
}
|
|
$this->decho("~~ update block set next = '$rpc_block_next' where id = $i and inactive != 1");
|
|
}
|
|
if ($v[2] != $rpc_block_prev) {
|
|
echo ("@@ found inconsistent data at block $i: db says previous block is " . $v[2] . ", RPC says $rpc_block_prev\r\n");
|
|
if (!$q = $this->dbh->query("update block set prev = '$rpc_block_prev' where id = $i and inactive != 1")) {
|
|
echo ("***** FATAL ERROR: could not update block $i with new prev value $rpc_block_prev, exiting *****\r\n");
|
|
$this->set_lockdb(0);
|
|
exit();
|
|
}
|
|
$this->decho("~~ update block set prev = '$rpc_block_prev' where id = $i and inactive != 1");
|
|
}
|
|
if ($break_after_checking_header == 1) break;
|
|
|
|
// check block hash - this is the big one
|
|
if ($v[0] != $rpc_block_hash) {
|
|
echo ("@@ found reorg or inconsistent data: db says block $i is hash " . $v[0] . ", RPC says $rpc_block_hash\r\n");
|
|
|
|
// find all transactions in the database in this block, and make sure they are all valid
|
|
$q = $this->dbh->query($dbq = "select hash from tx as A join (select id from block where hash = '$rpc_block_hash') as B where B.id = A.block and A.inactive = 0");
|
|
$r = $q->fetchAll(PDO::FETCH_NUM);
|
|
if (count($r) > 0) {
|
|
echo("$i ");
|
|
$break_after_checking_header = 0;
|
|
foreach ($r as $tx) {
|
|
if ($this->rescan_tx($tx[0], $rpc_block_full)) {
|
|
$this->fillInBlanks(1, 0, $i);
|
|
$break_after_checking_header = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// make inactive all following blocks, start at the current position (reorg handling)
|
|
$this->fillInBlanks(1, 0, $i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: separate delete from the rest of the function
|
|
function fillInBlanks($delete, $delete_all, $start) {
|
|
$height = $this->getHeight();
|
|
|
|
/* BEGIN DELETE METHOD */
|
|
if ($delete) {
|
|
if ($start) $delete_start = $start;
|
|
else $delete_start = $height[2];
|
|
$this->dbh->beginTransaction();
|
|
$start = $delete_start;
|
|
echo "@@ making everything inactive from $delete_start to highest block found\r\n";
|
|
$r = $this->dbh->query("select id from block where id >= " . $delete_start . " and inactive != 1");
|
|
$this->decho("@@ select id from block where id >= " . $delete_start . " and inactive != 1");
|
|
$v = $r->fetchAll(PDO::FETCH_NUM);
|
|
if (count($v) < 1) $this->decho("@@ no data in database on this block yet, skipping reorg handling procedure");
|
|
|
|
// reorg handling procedure (make the block, all its transactions, and all related data inactive)
|
|
foreach ($v as $block_db_id) {
|
|
$block_db_id = (int)$block_db_id[0];
|
|
$r2 = $this->dbh->query("select id from tx where block = $block_db_id");
|
|
$v2 = $r2->fetchAll(PDO::FETCH_NUM);
|
|
foreach($v2 as $tx_db_id) {
|
|
$tx_db_id = (int)$tx_db_id[0];
|
|
$r3 = $this->dbh->query("select id from vout where tx_db_id = $tx_db_id");
|
|
$v3 = $r3->fetchAll(PDO::FETCH_NUM);
|
|
foreach ($v3 as $vout_db_id) {
|
|
$vout_db_id = (int)$vout_db_id[0];
|
|
$this->decho("@@ update vout_address set inactive = 1 where vout_id = $vout_db_id");
|
|
$this->dbh->query("update vout_address set inactive = 1 where vout_id = $vout_db_id");
|
|
}
|
|
$this->decho("@@ update vout set inactive = 1 where tx_db_id = $tx_db_id");
|
|
$this->dbh->query("update vout set inactive = 1 where tx_db_id = $tx_db_id");
|
|
$this->dbh->query("update vin set inactive = 1 where tx_db_id = $tx_db_id");
|
|
$this->decho("@@ update vin set inactive = 1 where tx_db_id = $tx_db_id");
|
|
}
|
|
$this->decho("@@ update tx set inactive = 1 where block = $block_db_id");
|
|
$this->dbh->query(" update tx set inactive = 1 where block = $block_db_id");
|
|
$this->decho("@@ update block set inactive = 1 where id = $block_db_id");
|
|
$this->dbh->query(" update block set inactive = 1 where id = $block_db_id");
|
|
}
|
|
$this->dbh->commit();
|
|
}
|
|
else if (!$start) { $start = $height[2]+1; }
|
|
/* END DELETE METHOD */
|
|
// begin reading blockchain
|
|
echo "===== Filling in the last " . ($height[1]-$start+1) . " block" . ((($height[1]-$start+1)>1)?('s'):('')) . ": height $start to " . $height[1] . "\r\n";
|
|
for ($i = $start; $i <= $height[1]; $i++) {
|
|
$block_hash = $this->rpc->call('getblockhash', $i);
|
|
$block = $this->rpc->call('getblock', $block_hash);
|
|
$this->database_write_block($block);
|
|
}
|
|
return $height[0] . ' ' . $height[1];
|
|
}
|
|
|
|
function initial_setup() {
|
|
$this->decho("Creating block table...");
|
|
/* block table...
|
|
recorded in this table is all block info possible
|
|
id = database index
|
|
hash = this block's hash
|
|
prev = previous block hash
|
|
next = next block hash
|
|
time = unixtime of this block as reported by miner
|
|
diff = difficulty at the time of this block
|
|
txs = transactions within this block
|
|
total_coins = total coins sent through this block
|
|
size = size of this block in bytes
|
|
merk = merkle root
|
|
nonce = the nonce found by the miner who solved this block
|
|
version = version no
|
|
*/
|
|
$this->dbh->query("
|
|
CREATE TABLE `block` (
|
|
`id` bigint(20) NOT NULL,
|
|
`hash` char(64) DEFAULT NULL,
|
|
`prev` char(64) DEFAULT NULL,
|
|
`next` char(64) DEFAULT NULL,
|
|
`time` bigint(20) DEFAULT NULL,
|
|
`diff` float DEFAULT NULL,
|
|
`txs` int(11) DEFAULT NULL,
|
|
`total_coins` decimal(20,0) DEFAULT NULL,
|
|
`size` bigint(20) DEFAULT NULL,
|
|
`merk` char(64) DEFAULT NULL,
|
|
`nonce` bigint(20) DEFAULT NULL,
|
|
`version` float DEFAULT NULL,
|
|
`inactive` tinyint(1) DEFAULT 0,
|
|
INDEX `id_index` (`id`),
|
|
INDEX `hash_index` (`hash`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1");
|
|
$this->dbh->query("truncate table block");
|
|
|
|
|
|
/* color_coin_tx table...
|
|
this table tracks all color coin transactions
|
|
id = database index
|
|
tx_db_id = database index of the florin transaction referenced in this row
|
|
cc_db_id = database index of the color coin transaction referenced in this row
|
|
from_address = the address this color coin transaction was sent from
|
|
to_address = the address this color coin was sent to
|
|
amount = the amount of color coins sent in this transaction (satoshis)
|
|
genesis = is this a genesis transaction? (was the color coin created in this tx?)
|
|
valid = **IMPORTANT** valid or invalid transaction (checked from previous chain data)
|
|
*/
|
|
|
|
$this->decho("Creating color_coin_tx table...");
|
|
$this->dbh->query("
|
|
CREATE TABLE `color_coin_tx` (
|
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
|
`tx_db_id` bigint(20) DEFAULT NULL,
|
|
`cc_db_id` bigint(20) DEFAULT NULL,
|
|
`from_address` char(34) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
|
|
`to_address` char(34) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
|
|
`amount` bigint(20) DEFAULT NULL,
|
|
`genesis` bit(1) DEFAULT NULL,
|
|
`valid` bit(1) NOT NULL,
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `tx_db_id` (`tx_db_id`),
|
|
KEY `cc_db_id` (`cc_db_id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1
|
|
");
|
|
$this->dbh->query("truncate table color_coin_tx");
|
|
|
|
/* color_coins table...
|
|
this table contains a list of all color coins, NOT color coin transactions
|
|
id = database index
|
|
tx_db_id = database index of florin transaction referenced in this row
|
|
total_amount = number of total coins in this color coin set (satoshis)
|
|
name = name of this coin
|
|
address = originator's address
|
|
valid = is a valid coin (maybe this column shouldn't exist)
|
|
avatar = url for this avatar
|
|
*/
|
|
|
|
$this->decho("Creating color_coins table...");
|
|
$this->dbh->query("
|
|
CREATE TABLE `color_coins` (
|
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
|
`tx_db_id` bigint(20) DEFAULT NULL,
|
|
`total_amount` bigint(20) DEFAULT NULL,
|
|
`name` varchar(20) DEFAULT NULL,
|
|
`address` char(34) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
|
|
`valid` bit(1) NOT NULL,
|
|
`avatar` varchar(140) DEFAULT NULL,
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `address` (`address`),
|
|
KEY `tx_db_id` (`tx_db_id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1
|
|
");
|
|
$this->dbh->query("truncate table color_coins");
|
|
|
|
/* control table...
|
|
**this is not a usual table. each row is a key value pair essentially.**
|
|
id = database index
|
|
name = name of this row (key)
|
|
value = value of this row
|
|
entry = timestamp of the last time this row was modified
|
|
*/
|
|
$this->decho("Creating control table...");
|
|
$this->dbh->query("
|
|
CREATE TABLE `control` (
|
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
`name` varchar(100) DEFAULT NULL,
|
|
`value` varchar(200) DEFAULT NULL,
|
|
`entry` bigint(20) DEFAULT NULL,
|
|
PRIMARY KEY (`id`)
|
|
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1
|
|
");
|
|
$this->dbh->query("truncate table control");
|
|
|
|
/* filter table...
|
|
** this goes against everything i believe in **
|
|
txid = transaction id to omit the tx-comment from...
|
|
user = address to omit the tx-comments from....
|
|
|
|
thank god i haven't had to use this yet.
|
|
*/
|
|
|
|
$this->decho("Creating filter table...");
|
|
$this->dbh->query("
|
|
CREATE TABLE `filter` (
|
|
`txid` char(64) DEFAULT NULL,
|
|
`user` char(34) DEFAULT NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1
|
|
");
|
|
$this->dbh->query("truncate table filter");
|
|
|
|
|
|
/* tx table...
|
|
this table contains data on all transactions within the FLO blockchain
|
|
id = database index
|
|
hash = the hash of this transaction, otherwise known as "txid"
|
|
block = the numerical block this transaction is found in
|
|
message = the tx-comment in this tx
|
|
outputs = total coins output from this tx (satoshis)
|
|
inputs = total coins input in to this tx (not implemented yet...)
|
|
size = size in bytes
|
|
version = version no
|
|
coinbase = boolean whether or not this transaction is a coinbase tx
|
|
*/
|
|
|
|
$this->decho("Creating tx table...");
|
|
$this->dbh->query("
|
|
CREATE TABLE `tx` (
|
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
|
`hash` char(64) DEFAULT NULL,
|
|
`block` bigint(14) DEFAULT NULL,
|
|
`message` varchar(528) DEFAULT NULL,
|
|
`outputs` bigint(20) DEFAULT NULL,
|
|
`inputs` bigint(20) DEFAULT NULL,
|
|
`size` bigint(20) DEFAULT NULL,
|
|
`version` int(2) DEFAULT NULL,
|
|
`coinbase` tinyint(1) DEFAULT NULL,
|
|
`inactive` tinyint(1) DEFAULT 0,
|
|
`rtime` bigint(14) DEFAULT 0,
|
|
INDEX `id_index` (`id`),
|
|
INDEX `hash_index` (`hash`),
|
|
INDEX `message_index` (`message`)
|
|
) ENGINE=InnoDB CHARSET=latin1
|
|
");
|
|
$this->dbh->query("truncate table tx");
|
|
|
|
|
|
/* txmp table...
|
|
this table simply lists all transactions that are part of txmp (the transaction message protocol)
|
|
id = database index
|
|
tx_db_id = database index of the FLO transaction in question
|
|
*/
|
|
$this->decho("Creating txmp table...");
|
|
$this->dbh->query("
|
|
CREATE TABLE `txmp` (
|
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
|
`tx_db_id` bigint(20) DEFAULT NULL,
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `tx_db_id` (`tx_db_id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1
|
|
");
|
|
$this->dbh->query("truncate table txmp");
|
|
|
|
|
|
/* vin table...
|
|
the vin table holds all values from the input of a transaction
|
|
id = database index
|
|
vout = the vout number this input is assigned within the transaction
|
|
tx_db_id = the database index of the transaction this vin is contained within
|
|
*/
|
|
$this->decho("Creating vin table...");
|
|
$this->dbh->query("
|
|
CREATE TABLE `vin` (
|
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
|
`vout` int(11) DEFAULT NULL,
|
|
`tx_db_id` bigint(20) DEFAULT NULL,
|
|
`txid` char(64) DEFAULT NULL,
|
|
`coinbase` tinyint(1) DEFAULT NULL,
|
|
`address` char(34) CHARACTER SET latin1 COLLATE latin1_bin,
|
|
`multiaddr` tinyint(1),
|
|
`inactive` tinyint(1) DEFAULT 0,
|
|
INDEX `id_index` (`id`),
|
|
INDEX `tx_db_id_index` (`tx_db_id`),
|
|
INDEX `txid_index` (`txid`),
|
|
INDEX `address_index` (`address`),
|
|
INDEX `vout_index` (`vout`)
|
|
) ENGINE=InnoDB CHARSET=latin1
|
|
");
|
|
$this->dbh->query("truncate table vin");
|
|
|
|
/* vout table...
|
|
the vout table holds the data from the outputs of each transaction
|
|
id = database index
|
|
value = coins out (satoshis)
|
|
n = output number (listed sequentially from zero. used to specify an input when this output is spent)
|
|
tx_db_id = database index this output's containing transaction is found in
|
|
*/
|
|
$this->decho("Creating vout table...");
|
|
$this->dbh->query("
|
|
CREATE TABLE `vout` (
|
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
|
`value` decimal(20,0) DEFAULT NULL,
|
|
`n` int(11) DEFAULT NULL,
|
|
`tx_db_id` bigint(20) DEFAULT NULL,
|
|
`inactive` tinyint(1) DEFAULT 0,
|
|
INDEX `id_index` (`id`),
|
|
INDEX `tx_db_id_index` (`tx_db_id`)
|
|
) ENGINE=InnoDB CHARSET=latin1
|
|
");
|
|
$this->dbh->query("truncate table vout");
|
|
|
|
/* vout_address table
|
|
the vout_address table holds all address data for each vout
|
|
id = database index
|
|
address = the address found in this vout
|
|
vout_id = database index of the referenced vout (maybe this should be called vout_db_id...)
|
|
*/
|
|
$this->decho("Creating vout_address table...");
|
|
$this->dbh->query("
|
|
CREATE TABLE `vout_address` (
|
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
|
`address` char(34) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
|
|
`vout_id` bigint(20) DEFAULT NULL,
|
|
`inactive` tinyint(1) DEFAULT 0,
|
|
INDEX `id_index` (`id`),
|
|
INDEX `vout_id_index` (`vout_id`),
|
|
INDEX `address_index` (`address`)
|
|
) ENGINE=InnoDB CHARSET=latin1
|
|
");
|
|
$this->dbh->query("truncate table vout_address");
|
|
|
|
/* control table... as described above. setting up the key/value rows */
|
|
// lastblock = the last block parsed and recorded into DB (use this to check against florincoind blockheight)
|
|
// lockdb = control for whether or not the database is in use... **important** to prevent DB being read/written while in use
|
|
$this->decho("building initial control table...");
|
|
$this->dbh->query("delete from control");
|
|
$this->dbh->query("insert into control (name, value, entry) values ('lastblock', 0, 0)");
|
|
$this->dbh->query("insert into control (name, value, entry) values ('lockdb', 0, 0)");
|
|
$this->decho("done.");
|
|
}
|
|
}
|
|
?>
|