Created
May 8, 2012 03:52
-
-
Save rezigned/2632421 to your computer and use it in GitHub Desktop.
CSS Hacks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Money; | |
class Bitcoin { | |
#const BITCOIN_NODE = '173.224.125.222'; // w001.mo.us temporary | |
const BITCOIN_NODE = '50.97.137.37'; | |
static private $pending = array(); | |
public static function update() { | |
// update all nodes | |
$list = \DB::DAO('Money_Bitcoin_Host')->search(null); | |
foreach($list as $bean) { | |
$bean->Last_Update = \DB::i()->now(); | |
$client = \Controller::Driver('Bitcoin', $bean->Money_Bitcoin_Host__); | |
if (!$client->isValid()) continue; | |
$info = $client->getInfo(); | |
if (!$info) { | |
$bean->Status = 'down'; | |
$bean->commit(); | |
continue; | |
} | |
if (($info['generate']) && ($bean->Generate == 'N')) { | |
$client->setGenerate(false); | |
} elseif ((!$info['generate']) && ($bean->Generate != 'N')) { | |
$client->setGenerate(true); | |
} | |
$bean->Version = $info['version']; | |
$bean->Coins = (int)round($info['balance'] * 100000000); | |
$bean->Connections = $info['connections']; | |
$bean->Blocks = $info['blocks']; | |
$bean->Hashes_Per_Sec = $info['hashespersec']; | |
$bean->Status = 'up'; | |
$bean->commit(); | |
if (is_null($bean->Address)) { // get in addr (generate if needed) | |
$list = $client->getAddressesByLabel('_DEFAULT'); | |
if ($list) { | |
$bean->Address = $list[0]; | |
} else { | |
$bean->Address = $client->getNewAddress('_DEFAULT'); | |
} | |
$bean->commit(); | |
} | |
if (($bean->Keep_Empty == 'Y') && ($bean->Coins > 100000000)) { | |
// empty it! | |
$addr = self::getNullAddr(); | |
try { | |
$client->sendToAddress($addr, $bean->Coins / 100000000); | |
} catch(\Exception $e) { | |
// try smaller amount (maybe failed because of fee) | |
try { | |
$c = $bean->Coins / 100000000; | |
$c = round($c/4, 2); | |
if ($c > 0) | |
$client->sendToAddress($addr, $c); | |
} catch(\Exception $e) { | |
// give up | |
} | |
} | |
} | |
if ($bean->Coins > (500*100000000)) { | |
// more than 500 coins on this host, shuffle some~ | |
$client->sendToAddress($client->getNewAddress(), (mt_rand(18,20000)/100)); | |
} | |
} | |
} | |
public static function getRate() { | |
$ticker = \Money\Trade::ticker('BTC','EUR'); | |
$btc = \DB::DAO('Currency')->searchOne(array('Currency__' => 'BTC')); | |
$btc->Ex_Bid = 1/$ticker['vwap']['value']; | |
$btc->Ex_Ask = 1/$ticker['vwap']['value']; | |
$btc->commit(); | |
\DB::DAO('Currency_History')->insert(array('Currency__' => $btc->Currency__, 'Date' => gmdate('Y-m-d'), 'Ex_Bid' => $btc->Ex_Bid, 'Ex_Ask' => $btc->Ex_Ask)); | |
} | |
public static function mergeSmallOutputs() { | |
$transaction = \DB::i()->transaction(); | |
$lock = \DB::i()->lock('Money_Bitcoin_Available_Output'); | |
$list = \DB::DAO('Money_Bitcoin_Available_Output')->search(array('Available' => 'Y', new \DB\Expr('`Value` < 100000000')), null, array(5)); | |
if (count($list) < 3) return false; | |
$list[] = \DB::DAO('Money_Bitcoin_Available_Output')->searchOne(['Available' => 'Y', new \DB\Expr('`Value` > 100000000')]); | |
$input = array(); | |
$amount = 0; | |
foreach($list as $bean) { | |
$key = \DB::DAO('Money_Bitcoin_Permanent_Address')->searchOne(array('Money_Bitcoin_Permanent_Address__' => $bean->Money_Bitcoin_Permanent_Address__)); | |
if (!$key) throw new \Exception('Unusable output'); | |
$tmp = array( | |
'privkey' => \Internal\Crypt::decrypt($key->Private_Key), | |
'tx' => $bean->Hash, | |
'N' => $bean->N, | |
'hash' => $bean->Money_Bitcoin_Permanent_Address__, | |
'amount' => $bean->Value, | |
'input_source' => $bean->Money_Bitcoin_Available_Output__, | |
); | |
$input[] = $tmp; | |
$amount += $bean->Value; | |
$bean->Available = 'N'; | |
$bean->commit(); | |
} | |
$output = \Money\Bitcoin::getNullAddr(); | |
$output = \Util\Bitcoin::decode($output); | |
if (!$output) return false; | |
$tx = \Util\Bitcoin::makeNormalTx($input, $amount, $output, $output); | |
self::publishTransaction($tx); | |
return $transaction->commit(); | |
} | |
public static function splitBigOutputs() { | |
$transaction = \DB::i()->transaction(); | |
$lock = \DB::i()->lock('Money_Bitcoin_Available_Output'); | |
$bean = \DB::DAO('Money_Bitcoin_Available_Output')->searchOne(array('Available' => 'Y', new \DB\Expr('`Value` > 1000000000'))); | |
if (!$bean) return; | |
$input = array(); | |
$amount = 0; | |
$key = \DB::DAO('Money_Bitcoin_Permanent_Address')->searchOne(array('Money_Bitcoin_Permanent_Address__' => $bean->Money_Bitcoin_Permanent_Address__)); | |
if (!$key) throw new \Exception('Unusable output'); | |
$tmp = array( | |
'privkey' => \Internal\Crypt::decrypt($key->Private_Key), | |
'tx' => $bean->Hash, | |
'N' => $bean->N, | |
'hash' => $bean->Money_Bitcoin_Permanent_Address__, | |
'amount' => $bean->Value, | |
'input_source' => $bean->Money_Bitcoin_Available_Output__, | |
); | |
$input[] = $tmp; | |
$amount += $bean->Value; | |
$bean->Available = 'N'; | |
$bean->commit(); | |
$output1 = \Util\Bitcoin::decode(\Money\Bitcoin::getNullAddr()); | |
$output2 = \Util\Bitcoin::decode(\Money\Bitcoin::getNullAddr()); | |
$tx = \Util\Bitcoin::makeNormalTx($input, round(mt_rand($amount*0.4, $amount*0.6)), $output1, $output2); | |
self::publishTransaction($tx); | |
return $transaction->commit(); | |
} | |
public static function getTxInput($amount, $inputs = array()) { | |
// get input that covers at least $amount | |
$tx_list = array(); | |
$total = 0; | |
if ($amount <= 0) throw new \Exception('Invalid TX amount'); | |
// check for forced inputs | |
foreach($inputs as $input) { | |
$bean = \DB::DAO('Money_Bitcoin_Available_Output')->searchOne(array('Hash' => $input['hash'], 'N' => $input['n'])); | |
if (!$bean) continue; // not a valid input | |
$total += $bean->Value; | |
$tx_list[$bean->Money_Bitcoin_Available_Output__] = $bean; | |
$bean->Available = 'N'; | |
$bean->commit(); | |
if (count($tx_list) > 5) break; // even only one input is enough to invalidate the old tx, let's grab 5 | |
} | |
while(true) { | |
if ($total == $amount) break; | |
if (($total > $amount) && ($total - $amount > 1000000)) break; | |
// need more inputs | |
$skip_ok = false; | |
if (count($tx_list) >= 3) { | |
// need more inputs, and need those *fast*, take the largest that would fit our remaining balance | |
$bean = \DB::DAO('Money_Bitcoin_Available_Output')->searchOne(array('Available' => 'Y', new \DB\Expr('`Money_Bitcoin_Available_Output__` NOT IN ('.\DB::i()->quote(array_keys($tx_list), \DB::QUOTE_LIST).')'), new \DB\Expr('`Value` > '.($amount - $total))), array(new \DB\Expr('RAND()'))); | |
if (!$bean) { | |
// take largest one | |
$bean = \DB::DAO('Money_Bitcoin_Available_Output')->searchOne(array('Available' => 'Y', new \DB\Expr('`Money_Bitcoin_Available_Output__` NOT IN ('.\DB::i()->quote(array_keys($tx_list), \DB::QUOTE_LIST).')')), array('Value' => 'DESC')); | |
} | |
if (!$bean) | |
$bean = \DB::DAO('Money_Bitcoin_Available_Output')->searchOne(array(new \DB\Expr('`Money_Bitcoin_Available_Output__` NOT IN ('.\DB::i()->quote(array_keys($tx_list), \DB::QUOTE_LIST).')')), array(new \DB\Expr('RAND()'))); | |
} elseif ($tx_list) { | |
$bean = \DB::DAO('Money_Bitcoin_Available_Output')->searchOne(array('Available' => 'Y', new \DB\Expr('`Money_Bitcoin_Available_Output__` NOT IN ('.\DB::i()->quote(array_keys($tx_list), \DB::QUOTE_LIST).')')), array(new \DB\Expr('RAND()'))); | |
} else { | |
$bean = \DB::DAO('Money_Bitcoin_Available_Output')->searchOne(array('Available' => 'Y'), array(new \DB\Expr('RAND()'))); | |
} | |
if (!$bean) { | |
$skip_ok = true; | |
if ($tx_list) { | |
$bean = \DB::DAO('Money_Bitcoin_Available_Output')->searchOne(array(new \DB\Expr('`Money_Bitcoin_Available_Output__` NOT IN ('.\DB::i()->quote(array_keys($tx_list), \DB::QUOTE_LIST).')')), array(new \DB\Expr('RAND()'))); | |
} else { | |
$bean = \DB::DAO('Money_Bitcoin_Available_Output')->searchOne(null, array(new \DB\Expr('RAND()'))); | |
} | |
} | |
if (!$bean) throw new \Exception('No available output for this TX'); | |
// check if really available | |
if (!$skip_ok) { | |
$out = \DB::DAO('Money_Bitcoin_Block_Tx_Out')->searchOne(array('Hash' => $bean->Hash, 'N' => $bean->N)); | |
if ($out) { | |
if ($out->Claimed == 'Y') { | |
$bean->Available = 'N'; | |
$bean->commit(); | |
continue; | |
} | |
} | |
} | |
$total += $bean->Value; | |
$tx_list[$bean->Money_Bitcoin_Available_Output__] = $bean; | |
} | |
$input = array(); | |
foreach($tx_list as $bean) { | |
$key = \DB::DAO('Money_Bitcoin_Permanent_Address')->searchOne(array('Money_Bitcoin_Permanent_Address__' => $bean->Money_Bitcoin_Permanent_Address__)); | |
if (!$key) throw new \Exception('Unusable output'); | |
$tmp = array( | |
'privkey' => \Internal\Crypt::decrypt($key->Private_Key), | |
'tx' => $bean->Hash, | |
'N' => $bean->N, | |
'hash' => $bean->Money_Bitcoin_Permanent_Address__, | |
'amount' => $bean->Value, | |
); | |
$input[] = $tmp; | |
$bean->Available = 'N'; | |
$bean->commit(); | |
} | |
shuffle($input); // randomize inputs order | |
return $input; | |
} | |
public static function getPaymentAddr($payment_id) { | |
$private = \Util\Bitcoin::genPrivKey(); | |
$info = \Util\Bitcoin::decodePrivkey($private); | |
$insert = array( | |
'Money_Bitcoin_Permanent_Address__' => $info['hash'], | |
'Money_Bitcoin_Host__' => null, | |
'Money_Merchant_Transaction_Payment__' => $payment_id, | |
'Private_Key' => \Internal\Crypt::encrypt($private), | |
'Created' => \DB::i()->now(), | |
'Used' => 'Y', | |
'Callback' => 'Money/Merchant/Transaction::bitcoinEvent' | |
); | |
if (!\DB::DAO('Money_Bitcoin_Permanent_Address')->insert($insert)) return false; | |
return \Money\Bitcoin\Address::byHash($info['hash']); | |
} | |
public static function getNullAddr($priv = false) { | |
$private = \Util\Bitcoin::genPrivKey(); | |
$info = \Util\Bitcoin::decodePrivkey($private); | |
$address = \Util\Bitcoin::encode($info); | |
$insert = array( | |
'Money_Bitcoin_Permanent_Address__' => $info['hash'], | |
'Money_Bitcoin_Host__' => null, | |
'User_Wallet__' => null, | |
'Private_Key' => \Internal\Crypt::encrypt($private), | |
'Created' => \DB::i()->now(), | |
'Used' => 'Y', | |
); | |
if (!\DB::DAO('Money_Bitcoin_Permanent_Address')->insert($insert)) return false; | |
if ($priv) return array('priv' => $private, 'info' => $info, 'address' => $address); | |
return $address; | |
} | |
public static function getVerboseAddr($wallet, $description, $ipn = null, $user = null, $callback = null) { | |
if ($wallet && $wallet['Currency__'] != 'BTC') return false; | |
$private = \Util\Bitcoin::genPrivKey(); | |
$info = \Util\Bitcoin::decodePrivkey($private); | |
$address = \Util\Bitcoin::encode($info); | |
$insert = array( | |
'Money_Bitcoin_Permanent_Address__' => $info['hash'], | |
'Money_Bitcoin_Host__' => null, | |
'User_Wallet__' => $wallet ? $wallet->getId() : null, | |
'Private_Key' => \Internal\Crypt::encrypt($private), | |
'Created' => \DB::i()->now(), | |
'Description' => $description, | |
'Ipn' => $ipn, | |
'Used' => 'Y', // do not use it for normal purposes | |
'Callback' => $callback | |
); | |
if (!is_null($user)) $insert['User_Rest__'] = $user->getRestId(); | |
if (!\DB::DAO('Money_Bitcoin_Permanent_Address')->insert($insert)) return false; | |
return $address; | |
} | |
public static function getPermanentAddr($wallet, $user = null) { | |
if ($wallet['Currency__'] != 'BTC') return false; | |
$unused = \DB::DAO('Money_Bitcoin_Permanent_Address')->searchOne(array('User_Wallet__' => $wallet->getId(), 'Used' => 'N')); | |
if ($unused) { | |
if (strlen($unused->Money_Bitcoin_Permanent_Address__) != 40) return $unused->Money_Bitcoin_Permanent_Address__; | |
return \Util\Bitcoin::encode(array('version' => 0, 'hash' => $unused->Money_Bitcoin_Permanent_Address__)); | |
} | |
$private = \Util\Bitcoin::genPrivKey(); | |
$info = \Util\Bitcoin::decodePrivkey($private); | |
$address = \Util\Bitcoin::encode($info); | |
$insert = array( | |
'Money_Bitcoin_Permanent_Address__' => $info['hash'], | |
'Money_Bitcoin_Host__' => null, | |
'User_Wallet__' => $wallet->getId(), | |
'Private_Key' => \Internal\Crypt::encrypt($private), | |
'Created' => \DB::i()->now(), | |
); | |
if (!is_null($user)) $insert['User_Rest__'] = $user->getRestId(); | |
if (!\DB::DAO('Money_Bitcoin_Permanent_Address')->insert($insert)) return false; | |
return $address; | |
} | |
/** | |
* Create a bitcoin address with some dynamic configuration, like autoselling, mails, etc... | |
* @param \User\Wallet $wallet | |
* @param array $options | |
* @param \User $user | |
* @return bool|string | |
* @throws \TokenException | |
*/ | |
public static function getAddrWithOptions(\User\Wallet $wallet, array $options = [], \User $user = null) { | |
if ($wallet['Currency__'] != 'BTC') throw new \TokenException('Invalid currency provided', 'invalid_source_currency'); | |
// filter fields in options | |
// autosell: bool Sell bitcoins when received | |
// email: bool Send email either when receiving bitcoins (no autosell) or once sold | |
// data: string custom data returned in the mail | |
// currency: string The currency used for autosell, default to default wallet | |
$filtered_options = []; | |
$fields = ['autosell' => 'bool', 'email' => 'bool', 'data' => 'string', 'currency' => 'string']; | |
foreach ($fields as $field => $type) { | |
if (isset($options[$field])) { | |
$value = $options[$field]; | |
switch ($type) { | |
case 'bool': | |
$value = (bool)$value; | |
break; | |
default: | |
case 'string': | |
// truncate strings to 128 chars | |
$value = substr((string)$value, 0, 128); | |
break; | |
} | |
$filtered_options[$field] = $value; | |
} | |
} | |
if (isset($filtered_options['autosell']) && $filtered_options['autosell']) { | |
if (!isset($filtered_options['currency'])) { | |
throw new \TokenException('Missing currency for autosell', 'autosell_missing_currency'); | |
} | |
} | |
// check currency if set | |
if (isset($filtered_options['currency'])) { | |
// check if that currency exists | |
$cur = \Currency::get($filtered_options['currency']); | |
if (!$cur || $cur->isVirtual()) { | |
throw new \TokenException('Invalid currency or virtual currency', 'invalid_target_currency'); | |
} | |
} | |
// generate a new bitcoin address | |
$private = \Util\Bitcoin::genPrivKey(); | |
$info = \Util\Bitcoin::decodePrivkey($private); | |
$address = \Util\Bitcoin::encode($info); | |
$insert = array( | |
'Money_Bitcoin_Permanent_Address__' => $info['hash'], | |
'Money_Bitcoin_Host__' => null, | |
'User_Wallet__' => $wallet->getId(), | |
'Private_Key' => \Internal\Crypt::encrypt($private), | |
'Created' => \DB::i()->now(), | |
'Description' => json_encode($filtered_options), | |
'Used' => 'Y', // do not use it for normal purposes | |
'Callback' => 'Money/Bitcoin::optionAddrEvent' | |
); | |
// if the call was done through the API | |
if (!is_null($user)) $insert['User_Rest__'] = $user->getRestId(); | |
if (!\DB::DAO('Money_Bitcoin_Permanent_Address')->insert($insert)) { | |
throw new \TokenException('Couldn\'t create bitcoin address, please contact mtgox', 'unknown_error'); | |
}; | |
return $address; | |
} | |
public static function optionAddrEvent($addr, $hash_n, $block, $amount) { | |
// ignore until we have enough confirmations | |
if (!$block) return; | |
$options = json_decode($addr->Description, true); | |
/** @var $source_wallet \User\Wallet */ | |
$source_wallet = \User\Wallet::byId($addr->User_Wallet__); | |
// manage autosell | |
if (isset($options['autosell']) && $options['autosell']) { | |
$callback = null; | |
if (isset($options['email']) && $options['email']) { | |
$callback = 'Money/Bitcoin::optionAddrSellEmail'; | |
if ($options['data']) { | |
$callback .= '|' . $options['data']; | |
} | |
} | |
\Money\Trade::addOrder($source_wallet->getUser(), 'ask', $amount, $options['currency'], [], null, $callback); | |
} else { | |
// send email with details about the transaction | |
if (isset($options['email']) && $options['email']) { | |
$mail_page = \Registry::getInstance()->OptionAddrBlockEmail ?: 'mail/option_addr_bitcoin_rcvd.mail'; | |
$mail_data = [ | |
'_HASH' => $hash_n, | |
'_BLOCK' => $block, | |
'_AMOUNT' => $amount | |
]; | |
if (isset($options['data'])) $mail_data['_DATA'] = $options['data']; | |
\Tpl::userMail($mail_page, $source_wallet->getUser(), $mail_data); | |
} | |
} | |
} | |
public static function optionAddrSellEmail($user, $oid, $type, $data = null) { | |
$user = \User::byId($user, false, true); | |
$trade_info = \Money\Trade::getOrderExecutionResult($user, $oid, $type == 'bid'); | |
$mail_page = \Registry::getInstance()->OptionAddrOrderEmail ?: 'mail/option_addr_bitcoin_sold.mail'; | |
$mail_data = [ | |
'_TRADE_INFO' => $trade_info, | |
]; | |
if ($data) $mail_data['_DATA'] = $data; | |
return \Tpl::userMail($mail_page, $user, $mail_data); | |
} | |
public static function checkOrders() { | |
// check data in Money_Bitcoin_Order to see if any order is completed | |
$db = \DB::i(); | |
$list = $db['Money_Bitcoin_Order']->search(array('Status' => 'pending')); | |
$clients = array(); | |
foreach($list as $bean) { | |
if (!isset($clients[$bean->Money_Bitcoin_Host__])) $clients[$bean->Money_Bitcoin_Host__] = \Controller::Driver('Bitcoin', $bean->Money_Bitcoin_Host__); | |
$client = $clients[$bean->Money_Bitcoin_Host__]; | |
$total = (int)round($client->getReceivedByAddress($bean->Address, 3) * 100000000); // 3 confirmations | |
if ($bean->Coins == $total) { // nothing moved | |
if ($db->dateRead($bean->Expires) < time()) { | |
$bean->Status = 'expired'; | |
$bean->commit(); | |
continue; | |
} | |
} | |
$bean->Coins = $total; | |
$total += $bean->Coins_Extra; | |
if ($bean->Total <= $total) { | |
// payment complete! | |
$bean->Status = 'ok'; | |
$bean->commit(); | |
// mark order paid | |
$order = \Order::byId($bean->Order__); | |
if ($order->isPaid()) continue; // ?! | |
$info = array( | |
'method' => 'BITCOIN', | |
'class' => 'Bitcoin', | |
'stamp' => time(), | |
); | |
$order->paid($info); | |
continue; | |
} | |
$total_nc = (int)round($client->getReceivedByAddress($bean->Address, 0) * 100000000); | |
$bean->Coins_NC = $total_nc; | |
$bean->commit(); | |
} | |
} | |
public static function getAddressForOrder($order) { | |
$total = $order->getTotal(); | |
if ($total->getCurrency()->Currency__ != 'BTC') return false; | |
$btc = $total['value']; | |
$bean = \DB::DAO('Money_Bitcoin_Order')->searchOne(array('Order__' => $order->getId())); | |
if ($bean) { | |
if ($bean->Status != 'pending') return false; | |
$bean->Total = ((int)round($btc * 100))*1000000; | |
if ($bean->Address != '') { | |
$bean->commit(); | |
return $bean; | |
} elseif ($bean->Coins == $bean->Coins_NC) { | |
$bean->Coins_Extra = $bean->Coins; | |
$bean->Coins = 0; | |
$bean->Coins_NC = 0; | |
// find a (new) random host | |
$host = \DB::DAO('Money_Bitcoin_Host')->searchOne(array('Status' => 'up', 'Allow_Order' => 'Y'), array(new \DB\Expr('RAND()'))); | |
if (!$host) return false; // no available host right now | |
$client = \Controller::Driver('Bitcoin', $host->Money_Bitcoin_Host__); | |
$addr = $client->getNewAddress('ORDER:'.$order->getId()); | |
// update | |
$bean->Address = $addr; | |
$bean->commit(); | |
return $bean; | |
} | |
} | |
// find a random host | |
$host = \DB::DAO('Money_Bitcoin_Host')->searchOne(array('Status' => 'up', 'Allow_Order' => 'Y'), array(new \DB\Expr('RAND()'))); | |
if (!$host) return false; // no available host right now | |
$client = \Controller::Driver('Bitcoin', $host->Money_Bitcoin_Host__); | |
$addr = $client->getNewAddress('ORDER:'.$order->getId()); | |
// new entry | |
$db = \DB::i(); | |
$uuid = \System::uuid(); | |
$insert = array( | |
'Money_Bitcoin_Order__' => $uuid, | |
'Order__' => $order->getId(), | |
'Money_Bitcoin_Host__' => $host->Money_Bitcoin_Host__, | |
'Address' => $addr, | |
'Coins' => 0, | |
'Total' => ((int)round($btc * 100)) * 1000000, | |
'Created' => $db->now(), | |
'Expires' => $db->dateWrite(time()+(86400*10)), | |
); | |
$db['Money_Bitcoin_Order']->insert($insert); | |
$bean = $db['Money_Bitcoin_Order'][$uuid]; | |
if (!$bean) return false; | |
return $bean; | |
} | |
public static function sendAmount($address, $amount, $green = null, $inputs = array(), $fee = 0) { | |
if ($amount instanceof \Internal\Price) $amount = $amount->convert('BTC', null, \Currency::DIRECTION_OUT)->getIntValue(); | |
if ($fee instanceof \Internal\Price) $fee = $fee->convert('BTC', null, \Currency::DIRECTION_OUT)->getIntValue(); | |
$transaction = \DB::i()->transaction(); | |
$lock = \DB::i()->lock('Money_Bitcoin_Available_Output'); | |
$address = \Util\Bitcoin::decode($address); | |
if (!$address) throw new \Exception('Invalid bitcoin address'); | |
$remainder = \Util\Bitcoin::decode(self::getNullAddr()); | |
if (!$remainder) throw new \Exception('Failed to create output TX'); | |
$input = self::getTxInput($amount+$fee, $inputs); | |
if (!is_null($green)) { | |
// green send | |
// default=d47c1c9afc2a18319e7b78762dc8814727473e90 | |
$tmp_total = 0; | |
foreach($input as $tmp) $tmp_total += $tmp['amount']; | |
$key = \DB::DAO('Money_Bitcoin_Permanent_Address')->searchOne(array('Money_Bitcoin_Permanent_Address__' => $green)); | |
if (!$key) throw new \Exception('Invalid green address for transaction'); | |
// intermediate tx | |
$tx = \Util\Bitcoin::makeNormalTx($input, $tmp_total, array('hash' => $green), array('hash' => $green)); | |
$txid = self::publishTransaction($tx); | |
\DB::DAO('Money_Bitcoin_Available_Output')->insert(array('Money_Bitcoin_Available_Output__' => \System::uuid(), 'Money_Bitcoin_Permanent_Address__' => $green, 'Value' => $tmp_total, 'Hash' => $txid, 'N' => 0, 'Available' => 'N')); | |
// final tx | |
$tx = \Util\Bitcoin::makeNormalTx(array(array('amount' => $tmp_total, 'tx' => $txid, 'N' => 0, 'privkey' => \Internal\Crypt::decrypt($key->Private_Key), 'hash' => $green)), $amount, $address, $remainder); | |
$txid = self::publishTransaction($tx); | |
} else { | |
$tx = \Util\Bitcoin::makeNormalTx($input, $amount, $address, $remainder, $fee); | |
$txid = self::publishTransaction($tx); | |
} | |
if (!$transaction->commit()) return false; | |
return $txid; | |
// find a node with enough coins | |
$node = \DB::DAO('Money_Bitcoin_Host')->searchOne(array('Status' => 'up', new \DB\Expr('`Coins` >= '.\DB::i()->quote($amount))), array(new \DB\Expr('RAND()'))); | |
if (!$node) return false; | |
$client = \Controller::Driver('Bitcoin', $node->Money_Bitcoin_Host__); | |
return $client->sendToAddress($address, $amount/100000000); | |
} | |
public function getWalletHost() { | |
throw new \Exception('Method is deprecated'); | |
} | |
public static function parseVersion($v) { | |
if ($v == 0) return '[unknown]'; | |
if ($v > 10000) { | |
// [22:06:18] <ArtForz> new is major * 10000 + minor * 100 + revision | |
$rem = floor($v / 100); | |
$proto = $v - ($rem*100); | |
$v = $rem; | |
} else { | |
// [22:06:05] <ArtForz> old was major * 100 + minor | |
$proto = 0; | |
} | |
foreach(array('revision','minor','major') as $type) { | |
$rem = floor($v / 100); | |
$$type = $v - ($rem * 100); | |
$v = $rem; | |
} | |
// build string | |
return $major . '.' . $minor . '.' . $revision . ($proto?('[.'.$proto.']'):''); | |
} | |
public static function _Route_getStats($path) { | |
switch($path) { | |
case 'version': | |
$req = 'SELECT `Version`, COUNT(1) AS `Count` FROM `Money_Bitcoin_Node` WHERE `Status` != \'down\' GROUP BY `Version`'; | |
$sqlres = \DB::i()->query($req); | |
$res = array(); | |
while($row = $sqlres->fetch_assoc()) { | |
$res[self::parseVersion($row['Version'])] += $row['Count']; | |
} | |
break; | |
case 'ua': | |
$req = 'SELECT `User_Agent`, COUNT(1) AS `Count` FROM `Money_Bitcoin_Node` WHERE `Status` != \'down\' GROUP BY `User_Agent`'; | |
$sqlres = \DB::i()->query($req); | |
$res = array(); | |
while($row = $sqlres->fetch_assoc()) { | |
$res[$row['User_Agent']] += $row['Count']; | |
} | |
break; | |
case 'nodes': | |
$req = 'SELECT COUNT(1) AS `Count` FROM `Money_Bitcoin_Node` WHERE `Last_Seen` > DATE_SUB(NOW(), INTERVAL 6 HOUR)'; | |
$sqlres = \DB::i()->query($req); | |
$row = $sqlres->fetch_assoc(); | |
header('Content-Type: text/plain'); | |
echo $row['Count']; | |
exit; | |
case 'accepting': | |
$req = 'SELECT `Status`, COUNT(1) AS `Count` FROM `Money_Bitcoin_Node` WHERE `Last_Seen` > DATE_SUB(NOW(), INTERVAL 6 HOUR) GROUP BY `Status`'; | |
$sqlres = \DB::i()->query($req); | |
$res = array(); | |
while($row = $sqlres->fetch_assoc()) { | |
$res[$row['Status']] = $row['Count']; | |
} | |
$res['total_known'] = $res['up'] + $res['down']; | |
$res['total'] = $res['total_known'] + $res['unknown']; | |
$res['rate_accepting'] = $res['up'] / $res['total_known']; | |
break; | |
case 'bootstrap': | |
// select a set of peers appropriate as seed | |
$limit = 50; | |
if (isset($_GET['limit'])) { | |
$limit = (int)$_GET['limit']; | |
if ($limit < 1) $limit = 1; | |
if ($limit > 10000) $limit = 10000; | |
} | |
$req = 'SELECT * FROM `Money_Bitcoin_Node` WHERE `Status` = \'up\' AND `Last_Checked` > DATE_SUB(NOW(), INTERVAL 6 HOUR) AND `Version` >= 31500 AND (`Last_Down` IS NULL OR `Last_Down` < DATE_SUB(NOW(), INTERVAL 2 WEEK)) AND `First_Seen` < DATE_SUB(NOW(), INTERVAL 2 WEEK) ORDER BY RAND() LIMIT '.$limit; | |
$sqlres = \DB::i()->query($req); | |
if ($sqlres->num_rows == 0) { | |
$req = 'SELECT * FROM `Money_Bitcoin_Node` WHERE `Status` = \'up\' AND `Last_Checked` > DATE_SUB(NOW(), INTERVAL 6 HOUR) AND `Version` >= 31500 ORDER BY RAND() LIMIT '.$limit; | |
$sqlres = \DB::i()->query($req); | |
} | |
$res = array(); | |
while($row = $sqlres->fetch_assoc()) { | |
$res[] = array( | |
'ipv4' => $row['IP'], | |
'port' => $row['Port'], | |
'version' => $row['Version'], | |
'version_str' => self::parseVersion($row['Version']), | |
'user_agent' => $row['User_Agent'], | |
'timestamp' => \DB::i()->dateRead($row['Last_Checked']), | |
); | |
} | |
break; | |
case 'geomap': | |
// select all nodes | |
$req = 'SELECT `IP`, `Status`, `Version` FROM `Money_Bitcoin_Node` WHERE `Last_Seen` > DATE_SUB(NOW(), INTERVAL 3 HOUR)'; | |
$sqlres = \DB::i()->query($req); | |
header('Content-Type: application/json'); | |
echo '['; | |
$first = true; | |
$geoip = \ThirdParty\Geoip::getInstance(); | |
while($row = $sqlres->fetch_assoc()) { | |
$res = array('ipv4' => $row['IP'], 'version' => $row['Version'], 'status' => $row['Status']); | |
$record = $geoip->lookup($row['IP'], false); | |
if (!$record) continue; | |
if (!isset($record['latitude'])) continue; | |
$res['latitude'] = $record['latitude']; | |
$res['longitude'] = $record['longitude']; | |
if ($first) { | |
$first = false; | |
} else { | |
echo ','; | |
} | |
echo json_encode($res); | |
} | |
echo ']'; | |
exit; | |
case 'full': | |
// select all nodes | |
$req = 'SELECT * FROM `Money_Bitcoin_Node`'; | |
$sqlres = \DB::i()->query($req); | |
header('Content-Type: application/json'); | |
echo '['; | |
$first = true; | |
while($row = $sqlres->fetch_assoc()) { | |
if ($first) { | |
$first = false; | |
} else { | |
echo ','; | |
} | |
echo json_encode($row); | |
} | |
echo ']'; | |
exit; | |
case 'bitcoin.kml': | |
header('Content-Type: application/vnd.google-earth.kml+xml'); | |
// check cache | |
$cache = \Cache::getInstance(); | |
$data = $cache->get('bitcoin.kml_full'); | |
if ($data) { | |
echo $data; | |
exit; | |
} | |
// select all nodes | |
$out = fopen('php://temp', 'w'); | |
fwrite($out, "<?xml version=\"1.0\" encoding=\"UTF-8\"?".">\n"); | |
fwrite($out, '<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">'."\n"); | |
fwrite($out, "<Document>\n<name>Bitcoin nodes in the world</name>\n"); | |
// styles | |
fwrite($out, "<Style id=\"up\"><IconStyle><Icon><href>http://maps.google.com/mapfiles/kml/paddle/grn-blank.png</href></Icon></IconStyle></Style>\n"); | |
fwrite($out, "<Style id=\"down\"><IconStyle><Icon><href>http://maps.google.com/mapfiles/kml/paddle/red-blank.png</href></Icon></IconStyle></Style>\n"); | |
fwrite($out, "<Style id=\"unknown\"><IconStyle><Icon><href>http://maps.google.com/mapfiles/kml/paddle/wht-blank.png</href></Icon></IconStyle></Style>\n"); | |
$req = 'SELECT `IP`, `Status`, `Version` FROM `Money_Bitcoin_Node` WHERE `Last_Seen` > DATE_SUB(NOW(), INTERVAL 3 HOUR) ORDER BY `Status`'; | |
$geoip = \ThirdParty\Geoip::getInstance(); | |
$folder = ''; | |
$sqlres = \DB::i()->query($req); | |
while($row = $sqlres->fetch_assoc()) { | |
// lookup | |
$record = $geoip->lookup($row['IP'], false); | |
if (!$record) continue; | |
if (!isset($record['latitude'])) continue; | |
if ($folder != $row['Status']) { | |
if ($folder) fwrite($out, "</Folder>\n"); | |
$folder = $row['Status']; | |
fwrite($out, "<Folder><name>Bitcoin Nodes in status ".$folder."</name>\n"); | |
} | |
fwrite($out, "<Placemark><name>".$row['IP']."</name><description><![CDATA[<p>IP: ".$row['IP']."</p><p>Version: ".self::parseVersion($row['Version'])."</p>]]></description><styleUrl>#".$folder."</styleUrl>"); | |
fwrite($out, "<Point><coordinates>".$record['longitude'].",".$record['latitude']."</coordinates></Point></Placemark>\n"); | |
} | |
fwrite($out, "</Folder>\n</Document>\n</kml>\n"); | |
rewind($out); | |
$data = stream_get_contents($out); | |
fclose($out); | |
$cache->set('bitcoin.kml_full', $data, 1800); | |
echo $data; | |
exit; | |
default: | |
header('HTTP/1.0 404 Not Found'); | |
die('Not available'); | |
} | |
header('Content-Type: application/json'); | |
echo json_encode($res); | |
exit; | |
} | |
public static function checkNodes($sched) { | |
// get nodes to check | |
$db = \DB::i(); | |
$list = $db['Money_Bitcoin_Node']->search(array(new \DB\Expr('`Next_Check` < NOW()')), array(new \DB\Expr('`Status` IN (\'up\', \'unknown\') DESC'), 'Last_Checked' => 'ASC'), array(701)); | |
if (count($list) == 701) { | |
$sched->busy(); | |
array_pop($list); | |
} | |
$end_time = (floor(time()/60)*60)+50; | |
$nodes = new Bitcoin\Nodes(); | |
$info = array(); | |
$up = array(); | |
$nodes->on(null, 'ready', function($key) use (&$info, $nodes, $db, &$up) { | |
$node = $info[$key]; | |
$node->Version = $nodes->getVersion($key); | |
$node->User_Agent = $nodes->getUserAgent($key); | |
$node->Status = 'up'; | |
$node->Last_Seen = $db->now(); | |
$node->Last_Checked = $db->now(); | |
$node->Next_Check = $db->dateWrite(time()+(1800)); | |
$node->commit(); | |
$up[$key] = true; | |
$nodes->getAddr($key); // initiate loading of addrs | |
}); | |
$nodes->on(null, 'error', function($key, $error) use (&$info, $db, &$up) { | |
if ($up[$key]) return; // probably getaddr failed | |
$node = $info[$key]; | |
$node->Status = 'down'; | |
$node->Last_Checked = $db->now(); | |
$node->Next_Check = $db->dateWrite(time()+(3600*24)); | |
$node->Last_Down = $db->now(); | |
$node->Last_Error = $error; | |
if ($db->dateRead($node->Last_Seen) < (time() - (3600*24))) { // no news for 24 hours, drop it | |
$node->delete(); | |
return; | |
} | |
$node->commit(); | |
}); | |
$nodes->on(null, 'addr', function($key, $addr_list) use (&$info, $nodes, $db) { | |
$node = $info[$key]; | |
if (count($addr_list) > 1000) { | |
$node->Addresses = 0; | |
$node->commit(); | |
return; | |
} | |
$node->Addresses = count($addr_list); | |
$node->commit(); | |
foreach($addr_list as $addr) { | |
$bean = $db['Money_Bitcoin_Node']->searchOne(array('IP' => $addr['ipv4'], 'Port' => $addr['port'])); | |
if ($bean) { | |
$bean->Last_Seen = $db->now(); | |
$bean->commit(); | |
continue; | |
} | |
$db['Money_Bitcoin_Node']->insert(array( | |
'IP' => $addr['ipv4'], | |
'Port' => $addr['port'], | |
'Next_Check' => $db->now(), | |
'First_Seen' => $db->now(), | |
'Last_Seen' => $db->now(), | |
)); | |
} | |
$nodes->close($key); | |
}); | |
foreach($list as $node) { | |
if ($node->Port < 1024) { | |
$node->Status = 'down'; | |
$node->Last_Checked = $db->now(); | |
$node->Next_Check = $db->dateWrite(time()+(3600*24)); | |
$node->Last_Down = $db->now(); | |
if ($db->dateRead($node->Last_Seen) < (time() - (3600*24))) { // no news for 24 hours, drop it | |
$node->delete(); | |
return; | |
} | |
$node->Last_Error = 'invalid_port'; | |
$node->commit(); | |
continue; | |
} | |
$key = 'node_'.$node->Money_Bitcoin_Node__; | |
$info[$key] = $node; | |
if (!$nodes->connect($key, $node->IP, $node->Port)) { | |
$node->Status = 'down'; | |
$node->Last_Checked = $db->now(); | |
$node->Next_Check = $db->dateWrite(time()+(3600*24)); | |
$node->Last_Down = $db->now(); | |
if ($db->dateRead($node->Last_Seen) < (time() - (3600*24))) { // no news for 24 hours, drop it | |
$node->delete(); | |
return; | |
} | |
$node->Last_Error = 'invalid_address'; | |
$node->commit(); | |
} | |
} | |
while($nodes->wait()); | |
} | |
public static function importBlockClaim($hash, $n, $tx) { | |
$trx = \DB::DAO('Money_Bitcoin_Block_Tx_Out')->searchOne(array('Hash' => $hash, 'N' => $n)); | |
if (!$trx) throw new \Exception('Claim from unknown trx: '.$hash.':'.$n); | |
$trx->Claimed = 'Y'; | |
$trx->commit(); | |
\DB::DAO('Money_Bitcoin_Available_Output')->delete(array('Hash' => $hash, 'N' => $n)); | |
return true; | |
} | |
public static function parseScriptPubKey($pubkey) { | |
if (preg_match('/^([0-9a-f]{1,130}) OP_CHECKSIG$/', $pubkey, $matches)) { | |
return array('hash' => \Util\Bitcoin::decodePubkey($matches[1]), 'pubkey' => $matches[1]); | |
} | |
if (preg_match('/^OP_DUP OP_HASH160 ([0-9a-f]{40}) OP_EQUALVERIFY OP_CHECKSIG.*$/', $pubkey, $matches)) { | |
return array('hash' => array('hash' => $matches[1], 'version' => 0)); | |
} | |
\Debug::exception(new \Exception('WEIRD scriptPubKey - dropping it: '.$pubkey)); | |
return array('hash' => ['hash' => '0000000000000000000000000000000000000000', 'version' => 0]); | |
} | |
public static function importBlock($id) { | |
$peer = \Controller::Driver('Bitcoin', 'b54f4d35-dd1c-43aa-9096-88e37a83bda3'); | |
$block = $peer->getBlock($id); | |
$transaction = \DB::i()->transaction(); | |
// insert block | |
$data = array( | |
'Money_Bitcoin_Block__' => $block['hash'], | |
'Parent_Money_Bitcoin_Block__' => $block['prev_block'], | |
'Depth' => $id, | |
'Version' => $block['version'], | |
'Mrkl_Root' => $block['mrkl_root'], | |
'Time' => \DB::i()->dateWrite($block['time']), | |
'Bits' => $block['bits'], | |
'Nonce' => $block['nonce'], | |
'Size' => $block['size'], | |
); | |
\DB::DAO('Money_Bitcoin_Block')->insert($data); | |
$retry = 0; | |
while($block['tx']) { | |
$tx = array_shift($block['tx']); | |
$tmp = \DB::DAO('Money_Bitcoin_Block_Tx')->search(array('Hash' => $tx['hash'])); | |
if ($tmp) continue; // skip duplicate TXs | |
$tx['block'] = $id; | |
$data = array( | |
'Hash' => $tx['hash'], | |
'Block' => $block['hash'], | |
'Version' => $tx['version'], | |
'Lock_Time' => $tx['lock_time'], | |
'size' => $tx['size'], | |
); | |
\DB::DAO('Money_Bitcoin_Block_Tx')->insert($data); | |
\DB::DAO('Money_Bitcoin_Tx')->delete(array('Money_Bitcoin_Tx__' => $data['Hash'])); | |
\DB::DAO('Money_Bitcoin_Tx_In')->delete(array('Hash' => $data['Hash'])); | |
\DB::DAO('Money_Bitcoin_Tx_Out')->delete(array('Hash' => $data['Hash'])); | |
$watch = null; | |
$taint = null; | |
$taint_c = 0; | |
try { | |
foreach($tx['in'] as $n => $in) { | |
$data = array( | |
'Hash' => $tx['hash'], | |
'N' => $n, | |
'Prev_Out_Hash' => $in['prev_out']['hash'], | |
'Prev_Out_N' => $in['prev_out']['n'], | |
); | |
if ($in['coinbase']) { | |
$data['CoinBase'] = $in['coinbase']; | |
} else { | |
$data['scriptSig'] = $in['scriptSig']; | |
self::importBlockClaim($in['prev_out']['hash'], $in['prev_out']['n'], $tx); | |
} | |
// \DB::DAO('Money_Bitcoin_Block_Tx_In')->insert($data); | |
} | |
} catch(\Exception $e) { | |
// retry later | |
if ($retry++ > 10) throw $e; | |
$block['tx'][] = $tx; | |
continue; | |
} | |
if (!is_null($taint)) $taint = (int)floor($taint/$taint_c); | |
foreach($tx['out'] as $n => $out) { | |
$data = array( | |
'Hash' => $tx['hash'], | |
'N' => $n, | |
'Value' => round($out['value']*100000000), | |
); | |
$addr = self::parseScriptPubKey($out['scriptPubKey']); | |
$data['Addr'] = $addr['hash']['hash']; | |
\DB::DAO('Money_Bitcoin_Block_Tx_Out')->insert($data); | |
if (isset(\DB::DAO('Money_Bitcoin_Permanent_Address')[$data['Addr']])) { | |
$data['Money_Bitcoin_Process_Tx_Out__'] = \System::uuid(); | |
\DB::DAO('Money_Bitcoin_Process_Tx_Out')->insert($data, true); | |
} | |
} | |
} | |
$transaction->commit(); | |
} | |
public static function importBlocks($scheduler) { | |
// determine last imported block | |
$block = \DB::DAO('Money_Bitcoin_Block')->searchOne(null, array('Depth' => 'DESC')); | |
if ($block) { | |
$block_id = $block->Depth + 1; | |
} else { | |
$block_id = 0; | |
} | |
// read blocks from b54f4d35-dd1c-43aa-9096-88e37a83bda3 | |
$peer = \Controller::Driver('Bitcoin', 'b54f4d35-dd1c-43aa-9096-88e37a83bda3'); | |
$info = $peer->getInfo(); | |
if ($info['errors']) { | |
// reschedule for in one hour | |
$scheduler->busy(3600); | |
throw new \Exception('Can\'t import blocks: '.$info['errors']); | |
} | |
$last_block = $peer->getCurrentBlock()-5; // 5 confirmations | |
if ($last_block < $block_id) { | |
// nothing new here | |
// self::runAddrTriggers(); | |
return; | |
} | |
$deadline = time()+50; | |
$c = 0; | |
while($block_id <= $last_block) { | |
try { | |
self::importBlock($block_id); | |
} catch(\Exception $e) { | |
mail('[email protected]', 'BLOCK IMPORT ERROR', $e->getMessage()."\n\n".$e); | |
$scheduler->busy(600); | |
return; | |
// empty all! | |
$db = \DB::i(); | |
$db->query('TRUNCATE `Money_Bitcoin_Block`'); | |
// $db->query('TRUNCATE `Money_Bitcoin_Block_Addr`'); | |
$db->query('TRUNCATE `Money_Bitcoin_Block_Tx`'); | |
// $db->query('TRUNCATE `Money_Bitcoin_Block_Tx_In`'); | |
$db->query('TRUNCATE `Money_Bitcoin_Block_Tx_Out`'); | |
} | |
$block_id++; | |
if ((time() > $deadline) || ($c++>49)) { | |
$scheduler->busy(0); | |
break; | |
} | |
} | |
// run addr triggers | |
// self::runAddrTriggers(); | |
} | |
public static function insertMisingAvailableOutputs($addr) { | |
// search all unclaimed on this addr | |
$list = \DB::DAO('Money_Bitcoin_Process_Tx_Out')->search(array('Addr' => $addr, 'Claimed' => 'N')); | |
foreach($list as $bean) { | |
$insert = array( | |
'Money_Bitcoin_Available_Output__' => \System::uuid(), | |
'Money_Bitcoin_Permanent_Address__' => $bean->Addr, | |
'Value' => $bean->Value, | |
'Hash' => $bean->Hash, | |
'N' => $bean->N, | |
); | |
\DB::DAO('Money_Bitcoin_Available_Output')->insert($insert, true); | |
} | |
} | |
public static function runAddrTriggers() { | |
// lookup tx out with Trigger = new | |
$list = \DB::DAO('Money_Bitcoin_Process_Tx_Out')->search(array('Trigger' => 'new'), null, array(500)); // limit to 500 | |
// $main_transaction = \DB::i()->transaction(); | |
foreach($list as $bean) { | |
$transaction = \DB::i()->transaction(); | |
$bean->reloadForUpdate(); | |
if ($bean->Trigger != 'new') { | |
// rollback, exit | |
unset($transaction); | |
continue; | |
} | |
$bean->Trigger = 'executed'; | |
$bean->commit(); | |
$tx = $bean->Hash.':'.$bean->N; | |
$addr_str = \Util\Bitcoin::encode(array('version' => 0, 'hash' => $bean->Addr)); | |
$wallet_info = \DB::DAO('Money_Bitcoin_Permanent_Address')->searchOne(array('Money_Bitcoin_Permanent_Address__' => $bean->Addr)); | |
$redirect_value = null; | |
if ($wallet_info) $redirect_value = $wallet_info->Redirect; | |
$base_tx_data = \DB::DAO('Money_Bitcoin_Block_Tx')->searchOne(array('Hash' => $bean->Hash)); | |
$base_block_data = \DB::DAO('Money_Bitcoin_Block')->searchOne(['Money_Bitcoin_Block__' => $base_tx_data->Block]); | |
if (($wallet_info) && (!is_null($wallet_info->Private_Key)) && ($redirect_value == 'none')) { | |
$insert = array( | |
'Money_Bitcoin_Available_Output__' => \System::uuid(), | |
'Money_Bitcoin_Permanent_Address__' => $bean->Addr, | |
'Value' => $bean->Value, | |
'Hash' => $bean->Hash, | |
'N' => $bean->N, | |
'Block' => $base_block_data->Depth, | |
); | |
\DB::DAO('Money_Bitcoin_Available_Output')->insert($insert, true); | |
} | |
if ($redirect_value == 'fixed') { | |
// redirect funds | |
$target = $wallet_info->Redirect_Value; | |
$pub = \Util\Bitcoin::decode($target); | |
$tx = \Util\Bitcoin::makeNormalTx(array(array('amount' => $bean->Value, 'tx' => $bean->Hash, 'N' => $bean->N, 'privkey' => \Internal\Crypt::decrypt($wallet_info->Private_Key), 'hash' => $bean->Addr)), $bean->Value, $pub, $pub); | |
self::publishTransaction($tx); | |
$transaction->commit(); | |
continue; | |
} | |
if (($wallet_info) && (!is_null($wallet_info->Callback))) { | |
try { | |
$cb = explode('::', str_replace('/', '\\', $wallet_info->Callback)); | |
call_user_func($cb, $wallet_info, $tx, $base_tx_data->Block, \Internal\Price::spawnInt($bean->Value,'BTC')); | |
} catch(\Exception $e) { | |
\Debug::exception($e); | |
unset($transaction); | |
continue; | |
} | |
} | |
if (($wallet_info) && (!is_null($wallet_info->Ipn))) { | |
$base_tx_data = \DB::DAO('Money_Bitcoin_Block_Tx')->searchOne(array('Hash' => $bean->Hash)); | |
$post = array( | |
'description' => $wallet_info->Description, | |
'tx' => $tx, | |
'block' => $base_tx_data->Block, | |
'status' => 'confirmed', | |
'amount_int' => $bean->Value, | |
'item' => 'BTC', | |
'addr' => \Util\Bitcoin::encode(array('version' => 0, 'hash' => $wallet_info->Money_Bitcoin_Permanent_Address__)), | |
); | |
\Scheduler::oneshotUrl($wallet_info->Ipn, $post, null, null, null, $wallet_info->User_Rest__); | |
} | |
if (($wallet_info) && (!is_null($wallet_info->User_Wallet__))) { | |
$wallet_info->Used = 'Y'; | |
$wallet_info->commit(); | |
$wallet = \User\Wallet::byId($wallet_info->User_Wallet__); | |
if (($wallet) && ($wallet['Currency__'] == 'BTC')) { | |
// WALLET REDIRECT CODE 1 | |
if ((!is_null($wallet_info->Private_Key)) && ($wallet_info->Redirect == 'wallet') && ($bean->Value > 100000)) { | |
// redirect funds | |
$target = self::getVerboseAddr($wallet, $wallet_info->Description); | |
$pub = \Util\Bitcoin::decode($target); | |
try { | |
$tx = \Util\Bitcoin::makeNormalTx(array(array('amount' => $bean->Value, 'tx' => $bean->Hash, 'N' => $bean->N, 'privkey' => \Internal\Crypt::decrypt($wallet_info->Private_Key), 'hash' => $bean->Addr)), $bean->Value, $pub, $pub); | |
} catch(\Exception $e) { | |
mail('[email protected]', 'FAILED TO GENERATE REDIRECT TX', 'Error '.$e->getMessage().' on: '.$wallet_info->Money_Bitcoin_Permanent_Address__."\n".print_r($bean->getProperties(), true)); | |
throw $e; | |
} | |
self::publishTransaction($tx); | |
$transaction->commit(); | |
continue; | |
} | |
// search for already add | |
$nfo = \DB::DAO('User_Wallet_History')->searchOne(array('Reference_Type' => 'Money_Bitcoin_Block_Tx_Out', 'Reference' => $tx)); | |
if (!$nfo) { | |
$wallet->deposit(\Internal\Price::spawnInt($bean->Value, 'BTC'), $addr_str.(is_null($wallet_info->Description)?'':"\n".$wallet_info->Description), 'deposit', 'Money_Bitcoin_Block_Tx_Out', $tx); | |
if ($wallet['Balance']['value'] > 10000) $wallet->getUser()->aml('Balance in bitcoin is over 10000', 2); // force AML | |
\Money\Trade::updateUserOrders($wallet->getUser()); | |
} | |
} | |
} | |
$transaction->commit(); | |
} | |
// $main_transaction->commit(); | |
return count($list); | |
} | |
public static function getAddressBalance($addr) { | |
$res = \Internal\Price::spawn(0,'BTC'); | |
$list = \DB::DAO('Money_Bitcoin_Block_Tx_Out')->search(['Addr'=>$addr['hash']]); | |
foreach($list as $bean) | |
$res->add(\Internal\Price::spawnInt($bean->Value, 'BTC')); | |
return $res; | |
} | |
public static function getAddressOutputs($addr) { | |
// get all unclaimed outputs for that addr | |
$list = \DB::DAO('Money_Bitcoin_Block_Tx_Out')->search(array('Addr' => $addr['hash'], 'Claimed' => 'N')); | |
$final = array(); | |
foreach($list as $bean) $final[] = $bean->getProperties(); | |
return $final; | |
} | |
public static function claimPrivateSha256($wallet, $priv, $desc = null) { | |
return self::claimPrivate($wallet, \Util\Bitcoin::hash_sha256($priv), $desc); | |
} | |
public static function claimWalletFile($wallet, $data, $desc = null) { | |
$keys = \Util\Bitcoin::scanWalletFile($data); | |
if (!$keys) return array(); | |
$res = array(); | |
foreach($keys as $key) { | |
$tmp = self::claimPrivate($wallet, $key, $desc); | |
if (!$tmp) continue; | |
$res[] = $tmp; | |
} | |
return $res; | |
} | |
public static function claimPrivate($wallet, $priv, $desc = null) { | |
// get all the funds sent to that private addr and record it for future deposits | |
if (strlen($priv) != 32) throw new \Exception('The private key must be 32 bytes'); | |
// check if privkey is within range | |
$pk_num = gmp_init(bin2hex($priv), 16); | |
if (gmp_cmp($pk_num, '0') <= 0) return false; | |
if (gmp_cmp($pk_num, gmp_init('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16)) >= 0) return false; | |
$pub = \Util\Bitcoin::decodePrivkey($priv); | |
$addr = \Util\Bitcoin::encode($pub); | |
$outs = \Money\Bitcoin::getAddressOutputs($pub); | |
$find = \DB::DAO('Money_Bitcoin_Permanent_Address')->searchOne(array('Money_Bitcoin_Permanent_Address__' => $pub['hash'])); | |
if ($find) { | |
if (!is_null($find->Private_Key)) return false; // already got this one | |
$find->Private_Key = \Internal\Crypt::encrypt($priv); | |
$find->Redirect = 'wallet'; | |
$find->Used = 'Y'; | |
$find->commit(); | |
$wallet = \User\Wallet::byId($find->User_Wallet__); | |
} else { | |
$insert = array( | |
'Money_Bitcoin_Permanent_Address__' => $pub['hash'], | |
'Money_Bitcoin_Host__' => null, | |
'Private_Key' => \Internal\Crypt::encrypt($priv), | |
'Description' => $desc, | |
'Redirect' => 'nulladdr', | |
'Used' => 'Y', | |
); | |
if (!is_null($wallet)) { | |
$insert['User_Wallet__'] = $wallet->getId(); | |
$insert['Redirect'] = 'wallet'; | |
} | |
\DB::DAO('Money_Bitcoin_Permanent_Address')->insert($insert); | |
} | |
$total = 0; | |
if ($outs) { | |
if (is_null($wallet)) { | |
$out = self::getNullAddr(); | |
} else { | |
$out = self::getVerboseAddr($wallet, $desc); | |
} | |
$outpub = \Util\Bitcoin::decode($out); | |
$input = array(); | |
foreach($outs as $t) { | |
$input[] = array('amount' => $t['Value'], 'tx' => $t['Hash'], 'N' => $t['N'], 'privkey' => $priv, 'hash' => $pub['hash']); | |
$total += $t['Value']; | |
} | |
$tx = \Util\Bitcoin::makeNormalTx($input, $total, $outpub, $outpub); | |
self::publishTransaction($tx); | |
} | |
return array('amount' => \Internal\Price::spawnInt($total, 'BTC'), 'address' => $addr); | |
} | |
public static function makeNormalTx($input, $amount, $final_output, $remainder, $fee = 0) { | |
// make a normal tx, merge inputs if preferable | |
$res = array(); | |
while(count($input) > 5) { | |
// merge some inputs | |
$xinput = array(); | |
$output = self::getNullAddr(true); | |
// merge as many inputs as we can in a single tx | |
while(true) { | |
$extra = array_shift($input); | |
if (is_null($extra)) break; | |
$tinput = $xinput; | |
$tinput[] = $extra; | |
$total = 0; | |
foreach($tinput as $t) $total+=$t['amount']; | |
$ttx = \Util\Bitcoin::makeNormalTx($tinput, $total, $output['info'], $output['info']); | |
if (strlen($ttx) >= 1000) break; | |
$xinput[] = $extra; | |
} | |
if (!is_null($extra)) | |
array_unshift($input, $extra); | |
$total = 0; | |
foreach($xinput as $t) $total += $t['amount']; | |
$ttx = \Util\Bitcoin::makeNormalTx($xinput, $total, $output['info'], $output['info']); | |
$res[] = $ttx; | |
$thash = bin2hex(strrev(\Util\Bitcoin::hash_sha256(\Util\Bitcoin::hash_sha256($ttx)))); | |
$input[] = array( | |
'amount' => $total, | |
'tx' => $thash, | |
'N' => 0, | |
'privkey' => $output['priv'], | |
'hash' => $output['info']['hash'], | |
); | |
\DB::DAO('Money_Bitcoin_Available_Output')->insert(array('Money_Bitcoin_Available_Output__' => \System::uuid(), 'Money_Bitcoin_Permanent_Address__' => $output['info']['hash'], 'Value' => $total, 'Hash' => $thash, 'N' => 0, 'Available' => 'N')); | |
} | |
// do the final tx | |
$res[] = \Util\Bitcoin::makeNormalTx($input, $amount, $final_output, $remainder, $fee); | |
return $res; | |
} | |
public static function publishTransaction($txs) { | |
// generate tx id | |
if (!is_array($txs)) $txs = array($txs); | |
foreach($txs as $tx) { | |
$txid = bin2hex(strrev(\Util\Bitcoin::hash_sha256(\Util\Bitcoin::hash_sha256($tx)))); | |
$insert = array( | |
'Hash' => $txid, | |
'Blob' => base64_encode($tx), | |
'Created' => \DB::i()->now(), | |
); | |
\DB::DAO('Money_Bitcoin_Pending_Tx')->insert($insert); | |
self::$pending[$txid] = $tx; | |
} | |
return $txid; | |
} | |
public static function broadcastPublished() { | |
if (!self::$pending) return; | |
\Controller::MQ('RabbitMQ')->invoke('Money/Bitcoin::broadcastPublished', ['txs' => self::$pending]); | |
self::$pending = []; | |
} | |
public static function _MQ_broadcastPublished($info) { | |
$list = $info['txs']; | |
$node = new \Money\Bitcoin\Node(self::BITCOIN_NODE); | |
foreach($list as $tx) { | |
$node->pushTx($tx); | |
} | |
$node->getAddr(); // force sync | |
} | |
public static function broadcastTransactions() { | |
$list = \DB::DAO('Money_Bitcoin_Pending_Tx')->search(array(new \DB\Expr('`Last_Broadcast` < DATE_SUB(NOW(), INTERVAL 30 MINUTE)')), ['Last_Broadcast' => 'ASC'], array(100)); | |
if (!$list) return; | |
// $ip = gethostbyname('relay.eligius.st'); | |
$ip = gethostbyname('mtgox.relay.eligius.st'); | |
$node = new \Money\Bitcoin\Node(self::BITCOIN_NODE); | |
$peer = \Controller::Driver('Bitcoin', 'b54f4d35-dd1c-43aa-9096-88e37a83bda3'); | |
$el_todo = array(); | |
foreach($list as $bean) { | |
// check if successful | |
$success = \DB::DAO('Money_Bitcoin_Block_Tx')->searchOne(array('Hash' => $bean->Hash)); | |
if ($success) { | |
$bean->delete(); | |
continue; | |
} | |
$bean->Last_Broadcast = \DB::i()->now(); | |
if ((\DB::i()->dateRead($bean->Created) < (time()-7000)) && ($bean->Eligius == 'N')) { | |
try { | |
if (!$el_node) $el_node = new \Money\Bitcoin\Node($ip); | |
$el_node->pushTx(base64_decode($bean->Blob)); | |
$bean->Eligius = 'P'; | |
} catch(\Exception $e) { | |
// too bad | |
} | |
} elseif ($bean->Eligius == 'P') { | |
$bean->Eligius = 'Y'; | |
$el_todo[] = $bean->Hash; | |
} | |
try { | |
$bean->Last_Result = $peer->sendRawTransaction(bin2hex(base64_decode($bean->Blob))); | |
} catch(\Exception $e) { | |
$bean->Last_Result = $e->getMessage(); | |
} | |
$bean->commit(); | |
$node->pushTx(base64_decode($bean->Blob)); | |
} | |
$node->getAddr(); // force sync reply from bitcoin daemon so we know the stuff went through | |
if ($el_node) $el_node->getAddr(); | |
if ($el_todo) { | |
$ssh = new \Network\SSH($ip); | |
if (!$ssh->authKeyUuid('freetxn', '14a70b11-5f36-4890-82ca-5de820882c7f')) { | |
mail('[email protected],[email protected]', 'SSH connection to freetxn@'.$ip.' failed', 'Used ssh key 14a70b11-5f36-4890-82ca-5de820882c7f, but couldn\'t login to push those txs:'."\n".implode("\n", $el_todo)); | |
return; // failed | |
} | |
foreach($el_todo as $tx) { | |
$channel = $ssh->channel(); | |
$channel->exec($tx); | |
$channel->wait(); | |
} | |
} | |
} | |
/** | |
* Returns the total amount of bitcoins in the world based on that last block generated | |
* | |
* @return int The total amount of bitcoins | |
*/ | |
public static function getTotalCount() { | |
// get total count of BTC in the world based on latest block # | |
$last_block = \DB::DAO('Money_Bitcoin_Block')->searchOne(null, ['Depth'=>'DESC']); | |
$current = $last_block->Depth; | |
// this is a chunk of blocks, bitcoins generated per chunk start at 50 and halve every chunks | |
$block_size = 210000; | |
// first compute the total amount of bitcoins for the chunks that are fully done | |
$full_block_count = floor($current / $block_size); | |
$full_block_coeff = (1 - pow(0.5, $full_block_count)) * 100; | |
// those are the bitcoins on the full block chunks | |
$total_bitcoins = $full_block_coeff * $block_size; | |
// then for the last chunk | |
$last_block_coeff = pow(0.5, $full_block_count + 1) * 100; | |
$total_bitcoins += $last_block_coeff * ($current - ($full_block_count * $block_size)); | |
return $total_bitcoins; | |
} | |
public static function _Route_bitcoind($path) { | |
$post = file_get_contents('php://input'); | |
$post = json_decode($post, true); | |
if (!$post) return; | |
$method = $post['method']; | |
$params = $post['params']; | |
$id = $post['id']?:\System::uuid(); | |
try { | |
throw new \Exception('Meh: '.$method); | |
die(json_encode(array('result' => $res, 'id' => $id))); | |
} catch(\Exception $e) { | |
die(json_encode(array('error' => $e->getMessage(), 'id' => $id))); | |
} | |
} | |
public static function _Route_handleTx() { | |
// posted by halfnode with a TX | |
$tx_bin = pack('H*', $_POST['tx']); | |
$tx = \Util\Bitcoin::parseTx($tx_bin); | |
if (!$tx) die('BAD TX'); | |
$hash = $tx['hash']; | |
$dao = \DB::DAO('Money_Bitcoin_Tx'); | |
if (isset($dao[$hash])) die('DUP'); | |
if (\DB::DAO('Money_Bitcoin_Block_Tx')->countByField(array('Hash' => $hash))) die('DUP(blockchain)'); | |
$insert = array( | |
'Money_Bitcoin_Tx__' => $hash, | |
'Data' => base64_encode($tx_bin), | |
'Size' => strlen($tx_bin), | |
); | |
$dao->insert($insert); | |
foreach($tx['in'] as $i => $txin) { | |
\DB::DAO('Money_Bitcoin_Tx_In')->insert(array( | |
'Hash' => $hash, | |
'N' => $i, | |
'Prev_Out_Hash' => $txin['prev_out']['hash'], | |
'Prev_Out_N' => $txin['prev_out']['n'], | |
'scriptSig' => $txin['scriptSig'], | |
'Addr' => $txin['addr'], | |
)); | |
} | |
foreach($tx['out'] as $i => $txout) { | |
\DB::DAO('Money_Bitcoin_Tx_Out')->insert(array( | |
'Hash' => $hash, | |
'N' => $i, | |
'Value' => $txout['value_int'], | |
'scriptPubKey' => $txout['scriptPubKey'], | |
'Addr' => $txout['addr'], | |
)); | |
// check if one of our addrs | |
$info = \DB::DAO('Money_Bitcoin_Permanent_Address')->searchOne(array('Money_Bitcoin_Permanent_Address__' => $txout['addr'])); | |
if (($info) && (!is_null($info->Callback))) { | |
$cb = explode('::', str_replace('/', '\\', $info->Callback)); | |
call_user_func($cb, $info, $hash.':'.$i, null, \Internal\Price::spawnInt($txout['value_int'],'BTC')); | |
} | |
if (($info) && (!is_null($info->Ipn))) { | |
$post = array( | |
'description' => $info->Description, | |
'tx' => $hash.':'.$i, | |
'status' => 'published', | |
'amount_int' => $txout['value_int'], | |
'item' => 'BTC', | |
'addr' => \Util\Bitcoin::encode(array('version' => 0, 'hash' => $info->Money_Bitcoin_Permanent_Address__)), | |
); | |
\Scheduler::oneshotUrl($info->Ipn, $post, null, null, null, $info->User_Rest__); | |
} | |
// REDIRECT CODE 2 | |
if (($info) && (!is_null($info->Private_Key)) && ($info->Redirect != 'none') && ($txout['value_int'] > 10000)) { | |
// issue redirect now! | |
switch($info->Redirect) { | |
case 'wallet': | |
$wallet = \User\Wallet::byId($info->User_Wallet__); | |
$target = self::getVerboseAddr($wallet, $info->Description); | |
break; | |
case 'fixed': | |
$target = $info->Redirect_Value; | |
break; | |
case 'nulladdr': | |
$target = self::getNullAddr(); | |
break; | |
} | |
$pub = \Util\Bitcoin::decode($target); | |
$tx = \Util\Bitcoin::makeNormalTx(array(array('amount' => $txout['value_int'], 'tx' => $hash, 'N' => $i, 'privkey' => \Internal\Crypt::decrypt($info->Private_Key), 'hash' => $txout['addr'])), $txout['value_int'], $pub, $pub); | |
self::publishTransaction($tx); | |
// self::broadcastPublished(); | |
} | |
} | |
die('OK'); | |
} | |
public static function getTablesStruct() { | |
return array( | |
'Money_Bitcoin_Host' => array( | |
'Money_Bitcoin_Host__' => 'UUID', | |
'Name' => array('type' => 'VARCHAR', 'size' => 16, 'null' => false), | |
'IP' => array('type' => 'VARCHAR', 'size' => 39, 'null' => false, 'key' => 'UNIQUE:IP'), | |
'Address' => array('type' => 'VARCHAR', 'size' => 35, 'null' => true), | |
'Version' => array('type' => 'INT', 'size' => 10, 'unsigned' => true), | |
'Coins' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true), /* stored in smallest unit of coin */ | |
'Connections' => array('type' => 'INT', 'size' => 10, 'unsigned' => true), | |
'Blocks' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true), | |
'Hashes_Per_Sec' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true), | |
'Status' => array('type' => 'ENUM', 'values' => array('up','down'), 'default' => 'down'), | |
'Last_Update' => array('type' => 'DATETIME', 'null' => true), | |
'Keep_Empty' => array('type' => 'ENUM', 'values' => array('Y','N','E'), 'default' => 'N'), /* if set, any money on there will be sent somewhere else. E=exclude */ | |
'Allow_Order' => array('type' => 'ENUM', 'values' => array('Y','N'), 'default' => 'Y'), /* should we use this node for incoming payments? */ | |
'Generate' => array('type' => 'ENUM', 'values' => array('Y','N'), 'default' => 'Y'), | |
'Stamp' => array('type' => 'TIMESTAMP', 'null' => false), | |
), | |
'Money_Bitcoin_Tx' => array( | |
'Money_Bitcoin_Tx__' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'PRIMARY'), | |
'Data' => array('type' => 'LONGTEXT', 'null' => false), | |
'Network' => array('type' => 'VARCHAR', 'size' => 32, 'default' => 'bitcoin', 'key' => 'Network'), | |
'Size' => array('type' => 'INT', 'unsigned' => true, 'size' => 10, 'null' => false), | |
'Stamp' => array('type' => 'TIMESTAMP', 'null' => false), | |
), | |
'Money_Bitcoin_Tx_In' => array( | |
'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'UNIQUE:Key'), | |
'N' => array('type' => 'INT', 'size' => 10, 'unsigned' => true, 'key' => 'UNIQUE:Key'), | |
'Prev_Out_Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false), | |
'Prev_Out_N' => array('type' => 'INT', 'size' => 10, 'unsigned' => true), | |
'CoinBase' => array('type' => 'TEXT', 'null' => true), | |
'scriptSig' => array('type' => 'TEXT', 'null' => true), | |
'Addr' => array('type' => 'CHAR', 'size' => 40, 'null' => true, 'key' => 'Addr'), | |
'_keys' => array( | |
'Prev_Out' => array('Prev_Out_Hash','Prev_Out_N'), | |
), | |
), | |
'Money_Bitcoin_Tx_Out' => array( | |
'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'UNIQUE:Key'), | |
'N' => array('type' => 'INT', 'size' => 10, 'unsigned' => true, 'key' => 'UNIQUE:Key'), | |
'Value' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true, 'null' => false), | |
'scriptPubKey' => array('type' => 'TEXT'), | |
'Addr' => array('type' => 'CHAR', 'size' => 40, 'null' => true, 'key' => 'Addr'), | |
), | |
'Money_Bitcoin_Permanent_Address' => array( | |
'Money_Bitcoin_Permanent_Address__' => array('type' => 'CHAR', 'size' => 40, 'key' => 'PRIMARY'), | |
'Money_Bitcoin_Host__' => 'UUID/N', | |
'User_Wallet__' => 'UUID/N', | |
'User_Rest__' => 'UUID/N', | |
'Money_Merchant_Transaction_Payment__' => 'UUID/N', | |
'Private_Key' => array('type' => 'VARCHAR', 'size' => 255, 'null' => true), | |
'Redirect' => array('type' => 'ENUM', 'values' => array('wallet','fixed','nulladdr','none'), 'default' => 'none'), // wallet => redirect to new addr on same wallet | |
'Redirect_Value' => array('type' => 'VARCHAR', 'size' => 35, 'null' => true), | |
'Description' => array('type' => 'VARCHAR', 'size' => 255, 'null' => true), | |
'Ipn' => array('type' => 'VARCHAR', 'size' => 255, 'null' => true), | |
'Callback' => array('type' => 'VARCHAR', 'size' => 255, 'null' => true), | |
'Used' => array('type' => 'ENUM', 'values' => array('Y','N'), 'default' => 'N'), | |
'Created' => array('type' => 'DATETIME', 'null' => false), | |
'Stamp' => array('type' => 'TIMESTAMP', 'null' => false), | |
'_keys' => array( | |
'Unused_Addr_Key' => array('User_Wallet__','Used'), | |
'User_Wallet__' => ['User_Wallet__'], | |
), | |
), | |
'Money_Bitcoin_Available_Output' => array( // list available funds | |
'Money_Bitcoin_Available_Output__' => 'UUID', | |
'Money_Bitcoin_Permanent_Address__' => array('type' => 'CHAR', 'size' => 40, 'key' => 'Money_Bitcoin_Permanent_Address__'), | |
'Network' => array('type' => 'VARCHAR', 'size' => 32, 'default' => 'bitcoin', 'key' => 'Network'), | |
'Value' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true, 'null' => false), | |
'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'UNIQUE:Key'), | |
'N' => array('type' => 'INT', 'size' => 10, 'unsigned' => true, 'key' => 'UNIQUE:Key'), | |
'Block' => array('type' => 'INT', 'size' => 10, 'unsigned' => true, 'key' => 'Block'), | |
'Available' => array('type' => 'ENUM', 'values' => array('Y','N'), 'default' => 'Y'), | |
'Stamp' => array('type' => 'TIMESTAMP', 'null' => false), | |
), | |
'Money_Bitcoin_Order' => array( | |
'Money_Bitcoin_Order__' => 'UUID', | |
'Order__' => 'UUID', | |
'Money_Bitcoin_Host__' => 'UUID', | |
'Address' => array('type' => 'VARCHAR', 'size' => 35, 'null' => true), /* generated only for this order */ | |
'Coins' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true), | |
'Coins_NC' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true), | |
'Coins_Extra' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true), | |
'Total' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true), | |
'Created' => array('type' => 'DATETIME', 'null' => false), | |
'Expires' => array('type' => 'DATETIME', 'null' => false), | |
'Status' => array('type' => 'ENUM', 'values' => array('pending','expired','ok'), 'default' => 'pending'), | |
'Stamp' => array('type' => 'TIMESTAMP', 'null' => false), | |
'_keys' => array( | |
'@Order__' => array('Order__'), | |
'@Address' => array('Address'), | |
), | |
), | |
'Money_Bitcoin_Wallet' => array( | |
'Money_Bitcoin_Wallet__' => 'UUID', | |
'User__' => 'UUID', | |
'Money_Bitcoin_Host__' => 'UUID', | |
'Address' => array('type' => 'VARCHAR', 'size' => 35, 'null' => true), | |
'Coins' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true), | |
'Coins_NC' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true), | |
'Withdrawn_Coins' => array('type' => 'BIGINT', 'size' => 21, 'unsigned' => false), | |
'Refresh' => array('type' => 'DATETIME', 'null' => false), | |
'Stamp' => array('type' => 'TIMESTAMP', 'null' => false), | |
'_keys' => array( | |
'@User__' => array('User__'), | |
'@Address' => array('Address'), | |
), | |
), | |
'Money_Bitcoin_Node' => array( | |
'Money_Bitcoin_Node__' => NULL, | |
'IP' => array('type' => 'VARCHAR', 'size' => 15, 'null' => false, 'key' => 'UNIQUE:Unique_Host'), | |
'Port' => array('type' => 'INT', 'size' => 5, 'unsigned' => true, 'key' => 'UNIQUE:Unique_Host'), | |
'Version' => array('type' => 'INT', 'unsigned' => true, 'size' => 10), | |
'User_Agent' => array('type' => 'VARCHAR', 'size' => 256, 'null' => true), | |
'Status' => array('type' => 'ENUM', 'values' => array('up','down','unknown'), 'default' => 'unknown'), | |
'Addresses' => array('type' => 'INT', 'unsigned' => true, 'size' => 10, 'default' => 0), | |
'Last_Checked' => array('type' => 'DATETIME'), | |
'Next_Check' => array('type' => 'DATETIME'), | |
'First_Seen' => array('type' => 'DATETIME'), | |
'Last_Seen' => array('type' => 'DATETIME'), | |
'Last_Down' => array('type' => 'DATETIME', 'null' => true, 'default' => NULL), | |
'Last_Error' => array('type' => 'VARCHAR', 'size' => 32, 'null' => true), | |
'Stamp' => array('type' => 'TIMESTAMP', 'null' => false), | |
'_keys' => [ | |
'Next_Check' => ['Next_Check'], | |
'Status' => ['Status'], | |
], | |
), | |
'Money_Bitcoin_Pending_Tx' => array( | |
'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'PRIMARY'), | |
'Network' => array('type' => 'VARCHAR', 'size' => 32, 'default' => 'bitcoin', 'key' => 'Network'), | |
'Blob' => array('type' => 'LONGTEXT'), | |
'Eligius' => array('type' => 'ENUM', 'values' => array('Y','P','N'), 'default' => 'N'), | |
'Created' => array('type' => 'DATETIME'), | |
'Input_Total' => ['type' => 'BIGINT', 'size' => 20, 'unsigned' => true, 'null' => true], | |
'Last_Broadcast' => array('type' => 'DATETIME'), | |
'Last_Result' => ['type' => 'VARCHAR', 'size' => 128, 'null' => true], | |
'Stamp' => array('type' => 'TIMESTAMP', 'null' => false), | |
), | |
'Money_Bitcoin_Block' => array( | |
'Money_Bitcoin_Block__' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'PRIMARY'), | |
'Parent_Money_Bitcoin_Block__' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'Parent_Money_Bitcoin_Block__'), | |
'Depth' => array('type' => 'BIGINT', 'size' => 20, 'null' => false, 'key' => 'Depth'), | |
'Network' => array('type' => 'VARCHAR', 'size' => 32, 'default' => 'bitcoin', 'key' => 'Network'), | |
'Version' => array('type' => 'INT', 'size' => 10, 'unsigned' => true), | |
'Mrkl_Root' => array('type' => 'CHAR', 'size' => 64, 'null' => false), | |
'Time' => array('type' => 'DATETIME'), | |
'Bits' => array('type' => 'INT', 'size' => 10, 'unsigned' => true), | |
'Nonce' => array('type' => 'INT', 'size' => 10, 'unsigned' => true), | |
'Size' => array('type' => 'INT', 'size' => 10, 'unsigned' => true), | |
'Status' => array('type' => 'ENUM', 'values' => array('pending','confirmed','dropped'), 'default' => 'confirmed', 'null' => false), | |
), | |
'Money_Bitcoin_Process_Tx_Out' => [ | |
'Money_Bitcoin_Process_Tx_Out__' => 'UUID', | |
'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'UNIQUE:Key'), | |
'N' => array('type' => 'INT', 'size' => 10, 'unsigned' => true, 'key' => 'UNIQUE:Key'), | |
'Value' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true, 'null' => false), | |
'scriptPubKey' => array('type' => 'TEXT'), | |
'Addr' => array('type' => 'CHAR', 'size' => 40, 'null' => false, 'key' => 'Addr'), | |
'Trigger' => array('type' => 'ENUM', 'values' => array('new','executed','nil'), 'default' => 'new'), | |
'_keys' => array( | |
'Trigger' => array('Trigger'), | |
), | |
], | |
'Money_Bitcoin_Block_Tx' => array( | |
'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'UNIQUE:Hash'), | |
'Block' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'Block'), | |
'Version' => array('type' => 'INT', 'size' => 10, 'unsigned' => true), | |
'Lock_Time' => array('type' => 'INT', 'size' => 10, 'unsigned' => true), | |
'Size' => array('type' => 'INT', 'size' => 10, 'unsigned' => true), | |
), | |
/* 'Money_Bitcoin_Block_Tx_In' => array( | |
'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'UNIQUE:Key'), | |
'N' => array('type' => 'INT', 'size' => 10, 'unsigned' => true, 'key' => 'UNIQUE:Key'), | |
'Prev_Out_Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false), | |
'Prev_Out_N' => array('type' => 'INT', 'size' => 10, 'unsigned' => true), | |
'CoinBase' => array('type' => 'TEXT', 'null' => true), | |
'scriptSig' => array('type' => 'TEXT', 'null' => true), | |
'Addr' => array('type' => 'CHAR', 'size' => 40, 'null' => true, 'key' => 'Addr'), | |
'_keys' => array( | |
'Prev_Out' => array('Prev_Out_Hash','Prev_Out_N'), | |
), | |
),*/ | |
'Money_Bitcoin_Block_Tx_Out' => array( | |
'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'UNIQUE:Key'), | |
'N' => array('type' => 'INT', 'size' => 10, 'unsigned' => true, 'key' => 'UNIQUE:Key'), | |
'Value' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true, 'null' => false), | |
'scriptPubKey' => array('type' => 'TEXT'), | |
'Addr' => array('type' => 'CHAR', 'size' => 40, 'null' => false, 'key' => 'Addr'), | |
'Claimed' => array('type' => 'ENUM', 'values' => array('Y','N'), 'default' => 'N'), | |
'Trigger' => array('type' => 'ENUM', 'values' => array('new','executed','nil'), 'default' => 'new'), | |
'_keys' => array( | |
'Trigger' => array('Trigger'), | |
), | |
), | |
/* 'Money_Bitcoin_Block_Addr' => array( | |
'Addr' => array('type' => 'CHAR', 'size' => 40, 'null' => false, 'key' => 'PRIMARY'), | |
'Network' => array('type' => 'VARCHAR', 'size' => 32, 'default' => 'bitcoin', 'key' => 'Network'), | |
'Pubkey' => array('type' => 'CHAR', 'size' => 130, 'null' => true), | |
'Balance' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true, 'null' => false), | |
'Watch' => array('type' => 'VARCHAR', 'size' => 128, 'null' => true, 'default' => NULL, 'key' => 'Watch'), | |
'Taint' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true, 'null' => true, 'default' => NULL), | |
'Clean' => array('type' => 'VARCHAR', 'size' => 64, 'null' => true, 'default' => NULL, 'key' => 'Clean'), | |
), */ | |
'Money_Bitcoin_Vanity' => array( | |
'Money_Bitcoin_Vanity__' => array('type' => 'VARCHAR', 'size' => 35, 'null' => false, 'key' => 'PRIMARY'), | |
'Private_Key' => array('type' => 'VARCHAR', 'size' => 255, 'null' => false), | |
), | |
); | |
} | |
} | |
\Scheduler::schedule('MoneyBitcoinUpdate', '10min', 'Money/Bitcoin::update'); | |
\Scheduler::schedule('MoneyBitcoinCheckOrders', '5min', 'Money/Bitcoin::checkOrders'); | |
\Scheduler::schedule('MoneyBitcoinGetRate', array('daily', '5i'), 'Money/Bitcoin::getRate'); | |
\Scheduler::schedule('MoneyBitcoinCheckNodes', '10min', 'Money/Bitcoin::checkNodes'); | |
\Scheduler::schedule('MoneyBitcoinImportBlocks', '1min', 'Money/Bitcoin::importBlocks'); | |
\Scheduler::schedule('MoneyBitcoinAddrTriggers', '1min', 'Money/Bitcoin::runAddrTriggers'); | |
\Scheduler::schedule('MoneyBitcoinBroadcastTxs', '1min', 'Money/Bitcoin::broadcastTransactions'); | |
\Scheduler::schedule('MoneyBitcoinMergeSmallOutputs', '10min', 'Money/Bitcoin::mergeSmallOutputs'); | |
\Scheduler::schedule('MoneyBitcoinSplitBigOutputs', '10min', 'Money/Bitcoin::splitBigOutputs'); | |
\DB::i()->validateStruct(Bitcoin::getTablesStruct()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* IE 6 and below */ | |
* html .box { | |
color: red; | |
} | |
/* IE 7 */ | |
*:first-child+html .box { | |
color: pink; | |
} | |
*+html .box { | |
color: pink; | |
} | |
/* Refs http://paulirish.com/2009/browser-specific-css-hacks/ */ | |
/* Min-height hack */ | |
.box { | |
height: auto !important; /* Standard browsers will apply this */ | |
height: 30px; /* IE6 will ignore prior !important rules (IE6 always has default */ | |
min-height: 30px; /* Standard browsers will apply this */ | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How to use this