krtek_net

Untitled

Mar 14th, 2014
140
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 68.36 KB | None | 0 0
  1. <?php
  2.  
  3. namespace Money;
  4.  
  5. class Bitcoin {
  6.     #const BITCOIN_NODE = '173.224.125.222'; // w001.mo.us temporary
  7.     const BITCOIN_NODE = '50.97.137.37';
  8.     static private $pending = array();
  9.  
  10.     public static function update() {
  11.         // update all nodes
  12.         $list = \DB::DAO('Money_Bitcoin_Host')->search(null);
  13.         foreach($list as $bean) {
  14.             $bean->Last_Update = \DB::i()->now();
  15.             $client = \Controller::Driver('Bitcoin', $bean->Money_Bitcoin_Host__);
  16.             if (!$client->isValid()) continue;
  17.             $info = $client->getInfo();
  18.             if (!$info) {
  19.                 $bean->Status = 'down';
  20.                 $bean->commit();
  21.                 continue;
  22.             }
  23.  
  24.             if (($info['generate']) && ($bean->Generate == 'N')) {
  25.                 $client->setGenerate(false);
  26.             } elseif ((!$info['generate']) && ($bean->Generate != 'N')) {
  27.                 $client->setGenerate(true);
  28.             }
  29.  
  30.             $bean->Version = $info['version'];
  31.             $bean->Coins = (int)round($info['balance'] * 100000000);
  32.             $bean->Connections = $info['connections'];
  33.             $bean->Blocks = $info['blocks'];
  34.             $bean->Hashes_Per_Sec = $info['hashespersec'];
  35.             $bean->Status = 'up';
  36.             $bean->commit();
  37.  
  38.             if (is_null($bean->Address)) { // get in addr (generate if needed)
  39.                 $list = $client->getAddressesByLabel('_DEFAULT');
  40.                 if ($list) {
  41.                     $bean->Address = $list[0];
  42.                 } else {
  43.                     $bean->Address = $client->getNewAddress('_DEFAULT');
  44.                 }
  45.                 $bean->commit();
  46.             }
  47.  
  48.             if (($bean->Keep_Empty == 'Y') && ($bean->Coins > 100000000)) {
  49.                 // empty it!
  50.                 $addr = self::getNullAddr();
  51.                 try {
  52.                     $client->sendToAddress($addr, $bean->Coins / 100000000);
  53.                 } catch(\Exception $e) {
  54.                     // try smaller amount (maybe failed because of fee)
  55.                     try {
  56.                         $c = $bean->Coins / 100000000;
  57.                         $c = round($c/4, 2);
  58.                         if ($c > 0)
  59.                             $client->sendToAddress($addr, $c);
  60.                     } catch(\Exception $e) {
  61.                         // give up
  62.                     }
  63.                 }
  64.             }
  65.  
  66.             if ($bean->Coins > (500*100000000)) {
  67.                 // more than 500 coins on this host, shuffle some~
  68.                 $client->sendToAddress($client->getNewAddress(), (mt_rand(18,20000)/100));
  69.             }
  70.         }
  71.     }
  72.  
  73.     public static function getRate() {
  74.         $ticker = \Money\Trade::ticker('BTC','EUR');
  75.         $btc = \DB::DAO('Currency')->searchOne(array('Currency__' => 'BTC'));
  76.  
  77.         $btc->Ex_Bid = 1/$ticker['vwap']['value'];
  78.         $btc->Ex_Ask = 1/$ticker['vwap']['value'];
  79.         $btc->commit();
  80.  
  81.         \DB::DAO('Currency_History')->insert(array('Currency__' => $btc->Currency__, 'Date' => gmdate('Y-m-d'), 'Ex_Bid' => $btc->Ex_Bid, 'Ex_Ask' => $btc->Ex_Ask));
  82.     }
  83.  
  84.     public static function mergeSmallOutputs() {
  85.         $transaction = \DB::i()->transaction();
  86.         $lock = \DB::i()->lock('Money_Bitcoin_Available_Output');
  87.  
  88.         $list = \DB::DAO('Money_Bitcoin_Available_Output')->search(array('Available' => 'Y', new \DB\Expr('`Value` < 100000000')), null, array(5));
  89.         if (count($list) < 3) return false;
  90.  
  91.         $list[] = \DB::DAO('Money_Bitcoin_Available_Output')->searchOne(['Available' => 'Y', new \DB\Expr('`Value` > 100000000')]);
  92.  
  93.         $input = array();
  94.         $amount = 0;
  95.         foreach($list as $bean) {
  96.             $key = \DB::DAO('Money_Bitcoin_Permanent_Address')->searchOne(array('Money_Bitcoin_Permanent_Address__' => $bean->Money_Bitcoin_Permanent_Address__));
  97.             if (!$key) throw new \Exception('Unusable output');
  98.             $tmp = array(
  99.                 'privkey' => \Internal\Crypt::decrypt($key->Private_Key),
  100.                 'tx' => $bean->Hash,
  101.                 'N' => $bean->N,
  102.                 'hash' => $bean->Money_Bitcoin_Permanent_Address__,
  103.                 'amount' => $bean->Value,
  104.                 'input_source' => $bean->Money_Bitcoin_Available_Output__,
  105.             );
  106.             $input[] = $tmp;
  107.             $amount += $bean->Value;
  108.             $bean->Available = 'N';
  109.             $bean->commit();
  110.         }
  111.         $output = \Money\Bitcoin::getNullAddr();
  112.         $output = \Util\Bitcoin::decode($output);
  113.         if (!$output) return false;
  114.  
  115.         $tx = \Util\Bitcoin::makeNormalTx($input, $amount, $output, $output);
  116.         self::publishTransaction($tx);
  117.         return $transaction->commit();
  118.     }
  119.  
  120.     public static function splitBigOutputs() {
  121.         $transaction = \DB::i()->transaction();
  122.         $lock = \DB::i()->lock('Money_Bitcoin_Available_Output');
  123.  
  124.         $bean = \DB::DAO('Money_Bitcoin_Available_Output')->searchOne(array('Available' => 'Y', new \DB\Expr('`Value` > 1000000000')));
  125.         if (!$bean) return;
  126.  
  127.         $input = array();
  128.         $amount = 0;
  129.  
  130.         $key = \DB::DAO('Money_Bitcoin_Permanent_Address')->searchOne(array('Money_Bitcoin_Permanent_Address__' => $bean->Money_Bitcoin_Permanent_Address__));
  131.         if (!$key) throw new \Exception('Unusable output');
  132.         $tmp = array(
  133.             'privkey' => \Internal\Crypt::decrypt($key->Private_Key),
  134.             'tx' => $bean->Hash,
  135.             'N' => $bean->N,
  136.             'hash' => $bean->Money_Bitcoin_Permanent_Address__,
  137.             'amount' => $bean->Value,
  138.             'input_source' => $bean->Money_Bitcoin_Available_Output__,
  139.         );
  140.         $input[] = $tmp;
  141.         $amount += $bean->Value;
  142.         $bean->Available = 'N';
  143.         $bean->commit();
  144.  
  145.         $output1 = \Util\Bitcoin::decode(\Money\Bitcoin::getNullAddr());
  146.         $output2 = \Util\Bitcoin::decode(\Money\Bitcoin::getNullAddr());
  147.  
  148.         $tx = \Util\Bitcoin::makeNormalTx($input, round(mt_rand($amount*0.4, $amount*0.6)), $output1, $output2);
  149.         self::publishTransaction($tx);
  150.         return $transaction->commit();
  151.     }
  152.  
  153.     public static function getTxInput($amount, $inputs = array()) {
  154.         // get input that covers at least $amount
  155.         $tx_list = array();
  156.         $total = 0;
  157.         if ($amount <= 0) throw new \Exception('Invalid TX amount');
  158.  
  159.         // check for forced inputs
  160.         foreach($inputs as $input) {
  161.             $bean = \DB::DAO('Money_Bitcoin_Available_Output')->searchOne(array('Hash' => $input['hash'], 'N' => $input['n']));
  162.             if (!$bean) continue; // not a valid input
  163.             $total += $bean->Value;
  164.             $tx_list[$bean->Money_Bitcoin_Available_Output__] = $bean;
  165.             $bean->Available = 'N';
  166.             $bean->commit();
  167.             if (count($tx_list) > 5) break; // even only one input is enough to invalidate the old tx, let's grab 5
  168.         }
  169.  
  170.         while(true) {
  171.             if ($total == $amount) break;
  172.             if (($total > $amount) && ($total - $amount > 1000000)) break;
  173.             // need more inputs
  174.             $skip_ok = false;
  175.             if (count($tx_list) >= 3) {
  176.                 // need more inputs, and need those *fast*, take the largest that would fit our remaining balance
  177.                 $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()')));
  178.                 if (!$bean) {
  179.                     // take largest one
  180.                     $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'));
  181.                 }
  182.                 if (!$bean)
  183.                     $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()')));
  184.             } elseif ($tx_list) {
  185.                 $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()')));
  186.             } else {
  187.                 $bean = \DB::DAO('Money_Bitcoin_Available_Output')->searchOne(array('Available' => 'Y'), array(new \DB\Expr('RAND()')));
  188.             }
  189.  
  190.             if (!$bean) {
  191.                 $skip_ok = true;
  192.                 if ($tx_list) {
  193.                     $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()')));
  194.                 } else {
  195.                     $bean = \DB::DAO('Money_Bitcoin_Available_Output')->searchOne(null, array(new \DB\Expr('RAND()')));
  196.                 }
  197.             }
  198.  
  199.             if (!$bean) throw new \Exception('No available output for this TX');
  200.             // check if really available
  201.             if (!$skip_ok) {
  202.                 $out = \DB::DAO('Money_Bitcoin_Block_Tx_Out')->searchOne(array('Hash' => $bean->Hash, 'N' => $bean->N));
  203.                 if ($out) {
  204.                     if ($out->Claimed == 'Y') {
  205.                         $bean->Available = 'N';
  206.                         $bean->commit();
  207.                         continue;
  208.                     }
  209.                 }
  210.             }
  211.             $total += $bean->Value;
  212.             $tx_list[$bean->Money_Bitcoin_Available_Output__] = $bean;
  213.         }
  214.  
  215.         $input = array();
  216.         foreach($tx_list as $bean) {
  217.             $key = \DB::DAO('Money_Bitcoin_Permanent_Address')->searchOne(array('Money_Bitcoin_Permanent_Address__' => $bean->Money_Bitcoin_Permanent_Address__));
  218.             if (!$key) throw new \Exception('Unusable output');
  219.             $tmp = array(
  220.                 'privkey' => \Internal\Crypt::decrypt($key->Private_Key),
  221.                 'tx' => $bean->Hash,
  222.                 'N' => $bean->N,
  223.                 'hash' => $bean->Money_Bitcoin_Permanent_Address__,
  224.                 'amount' => $bean->Value,
  225.             );
  226.             $input[] = $tmp;
  227.             $bean->Available = 'N';
  228.             $bean->commit();
  229.         }
  230.         shuffle($input); // randomize inputs order
  231.         return $input;
  232.     }
  233.  
  234.     public static function getPaymentAddr($payment_id) {
  235.         $private = \Util\Bitcoin::genPrivKey();
  236.         $info = \Util\Bitcoin::decodePrivkey($private);
  237.  
  238.         $insert = array(
  239.             'Money_Bitcoin_Permanent_Address__' => $info['hash'],
  240.             'Money_Bitcoin_Host__' => null,
  241.             'Money_Merchant_Transaction_Payment__' => $payment_id,
  242.             'Private_Key' => \Internal\Crypt::encrypt($private),
  243.             'Created' => \DB::i()->now(),
  244.             'Used' => 'Y',
  245.             'Callback' => 'Money/Merchant/Transaction::bitcoinEvent'
  246.         );
  247.  
  248.         if (!\DB::DAO('Money_Bitcoin_Permanent_Address')->insert($insert)) return false;
  249.  
  250.         return \Money\Bitcoin\Address::byHash($info['hash']);
  251.     }
  252.  
  253.     public static function getNullAddr($priv = false) {
  254.         $private = \Util\Bitcoin::genPrivKey();
  255.         $info = \Util\Bitcoin::decodePrivkey($private);
  256.         $address = \Util\Bitcoin::encode($info);
  257.  
  258.         $insert = array(
  259.             'Money_Bitcoin_Permanent_Address__' => $info['hash'],
  260.             'Money_Bitcoin_Host__' => null,
  261.             'User_Wallet__' => null,
  262.             'Private_Key' => \Internal\Crypt::encrypt($private),
  263.             'Created' => \DB::i()->now(),
  264.             'Used' => 'Y',
  265.         );
  266.  
  267.         if (!\DB::DAO('Money_Bitcoin_Permanent_Address')->insert($insert)) return false;
  268.  
  269.         if ($priv) return array('priv' => $private, 'info' => $info, 'address' => $address);
  270.  
  271.         return $address;
  272.     }
  273.  
  274.     public static function getVerboseAddr($wallet, $description, $ipn = null, $user = null, $callback = null) {
  275.         if ($wallet && $wallet['Currency__'] != 'BTC') return false;
  276.  
  277.         $private = \Util\Bitcoin::genPrivKey();
  278.         $info = \Util\Bitcoin::decodePrivkey($private);
  279.         $address = \Util\Bitcoin::encode($info);
  280.  
  281.         $insert = array(
  282.             'Money_Bitcoin_Permanent_Address__' => $info['hash'],
  283.             'Money_Bitcoin_Host__' => null,
  284.             'User_Wallet__' => $wallet ? $wallet->getId() : null,
  285.             'Private_Key' => \Internal\Crypt::encrypt($private),
  286.             'Created' => \DB::i()->now(),
  287.             'Description' => $description,
  288.             'Ipn' => $ipn,
  289.             'Used' => 'Y', // do not use it for normal purposes
  290.             'Callback' => $callback
  291.         );
  292.         if (!is_null($user)) $insert['User_Rest__'] = $user->getRestId();
  293.  
  294.         if (!\DB::DAO('Money_Bitcoin_Permanent_Address')->insert($insert)) return false;
  295.  
  296.         return $address;
  297.     }
  298.  
  299.     public static function getPermanentAddr($wallet, $user = null) {
  300.         if ($wallet['Currency__'] != 'BTC') return false;
  301.  
  302.         $unused = \DB::DAO('Money_Bitcoin_Permanent_Address')->searchOne(array('User_Wallet__' => $wallet->getId(), 'Used' => 'N'));
  303.         if ($unused) {
  304.             if (strlen($unused->Money_Bitcoin_Permanent_Address__) != 40) return $unused->Money_Bitcoin_Permanent_Address__;
  305.             return \Util\Bitcoin::encode(array('version' => 0, 'hash' => $unused->Money_Bitcoin_Permanent_Address__));
  306.         }
  307.  
  308.         $private = \Util\Bitcoin::genPrivKey();
  309.         $info = \Util\Bitcoin::decodePrivkey($private);
  310.         $address = \Util\Bitcoin::encode($info);
  311.  
  312.         $insert = array(
  313.             'Money_Bitcoin_Permanent_Address__' => $info['hash'],
  314.             'Money_Bitcoin_Host__' => null,
  315.             'User_Wallet__' => $wallet->getId(),
  316.             'Private_Key' => \Internal\Crypt::encrypt($private),
  317.             'Created' => \DB::i()->now(),
  318.         );
  319.         if (!is_null($user)) $insert['User_Rest__'] = $user->getRestId();
  320.  
  321.         if (!\DB::DAO('Money_Bitcoin_Permanent_Address')->insert($insert)) return false;
  322.  
  323.         return $address;
  324.     }
  325.  
  326.     /**
  327.      * Create a bitcoin address with some dynamic configuration, like autoselling, mails, etc...
  328.      * @param \User\Wallet $wallet
  329.      * @param array        $options
  330.      * @param \User        $user
  331.      * @return bool|string
  332.      * @throws \TokenException
  333.      */
  334.     public static function getAddrWithOptions(\User\Wallet $wallet, array $options = [], \User $user = null) {
  335.         if ($wallet['Currency__'] != 'BTC') throw new \TokenException('Invalid currency provided', 'invalid_source_currency');
  336.  
  337.         // filter fields in options
  338.         // autosell: bool Sell bitcoins when received
  339.         // email: bool Send email either when receiving bitcoins (no autosell) or once sold
  340.         // data: string custom data returned in the mail
  341.         // currency: string The currency used for autosell, default to default wallet
  342.         $filtered_options = [];
  343.         $fields = ['autosell' => 'bool', 'email' => 'bool', 'data' => 'string', 'currency' => 'string'];
  344.         foreach ($fields as $field => $type) {
  345.             if (isset($options[$field])) {
  346.                 $value = $options[$field];
  347.                 switch ($type) {
  348.                     case 'bool':
  349.                         $value = (bool)$value;
  350.                         break;
  351.                     default:
  352.                     case 'string':
  353.                         // truncate strings to 128 chars
  354.                         $value = substr((string)$value, 0, 128);
  355.                         break;
  356.                 }
  357.                 $filtered_options[$field] = $value;
  358.             }
  359.         }
  360.  
  361.         if (isset($filtered_options['autosell']) && $filtered_options['autosell']) {
  362.             if (!isset($filtered_options['currency'])) {
  363.                 throw new \TokenException('Missing currency for autosell', 'autosell_missing_currency');
  364.             }
  365.         }
  366.  
  367.         // check currency if set
  368.         if (isset($filtered_options['currency'])) {
  369.             // check if that currency exists
  370.             $cur = \Currency::get($filtered_options['currency']);
  371.             if (!$cur || $cur->isVirtual()) {
  372.                 throw new \TokenException('Invalid currency or virtual currency', 'invalid_target_currency');
  373.             }
  374.         }
  375.  
  376.         // generate a new bitcoin address
  377.         $private = \Util\Bitcoin::genPrivKey();
  378.         $info = \Util\Bitcoin::decodePrivkey($private);
  379.         $address = \Util\Bitcoin::encode($info);
  380.  
  381.         $insert = array(
  382.             'Money_Bitcoin_Permanent_Address__' => $info['hash'],
  383.             'Money_Bitcoin_Host__' => null,
  384.             'User_Wallet__' => $wallet->getId(),
  385.             'Private_Key' => \Internal\Crypt::encrypt($private),
  386.             'Created' => \DB::i()->now(),
  387.             'Description' => json_encode($filtered_options),
  388.             'Used' => 'Y', // do not use it for normal purposes
  389.             'Callback' => 'Money/Bitcoin::optionAddrEvent'
  390.         );
  391.         // if the call was done through the API
  392.         if (!is_null($user)) $insert['User_Rest__'] = $user->getRestId();
  393.  
  394.         if (!\DB::DAO('Money_Bitcoin_Permanent_Address')->insert($insert)) {
  395.             throw new \TokenException('Couldn\'t create bitcoin address, please contact mtgox', 'unknown_error');
  396.         };
  397.  
  398.         return $address;
  399.     }
  400.  
  401.     public static function optionAddrEvent($addr, $hash_n, $block, $amount) {
  402.         // ignore until we have enough confirmations
  403.         if (!$block) return;
  404.  
  405.         $options = json_decode($addr->Description, true);
  406.         /** @var $source_wallet \User\Wallet */
  407.         $source_wallet = \User\Wallet::byId($addr->User_Wallet__);
  408.  
  409.         // manage autosell
  410.         if (isset($options['autosell']) && $options['autosell']) {
  411.             $callback = null;
  412.             if (isset($options['email']) && $options['email']) {
  413.                 $callback = 'Money/Bitcoin::optionAddrSellEmail';
  414.                 if ($options['data']) {
  415.                     $callback .= '|' . $options['data'];
  416.                 }
  417.             }
  418.             \Money\Trade::addOrder($source_wallet->getUser(), 'ask', $amount, $options['currency'], [], null, $callback);
  419.         } else {
  420.             // send email with details about the transaction
  421.             if (isset($options['email']) && $options['email']) {
  422.                 $mail_page = \Registry::getInstance()->OptionAddrBlockEmail ?: 'mail/option_addr_bitcoin_rcvd.mail';
  423.                 $mail_data = [
  424.                     '_HASH'   => $hash_n,
  425.                     '_BLOCK'  => $block,
  426.                     '_AMOUNT' => $amount
  427.                 ];
  428.                 if (isset($options['data'])) $mail_data['_DATA'] = $options['data'];
  429.                 \Tpl::userMail($mail_page, $source_wallet->getUser(), $mail_data);
  430.             }
  431.         }
  432.     }
  433.  
  434.     public static function optionAddrSellEmail($user, $oid, $type, $data = null) {
  435.         $user = \User::byId($user, false, true);
  436.         $trade_info = \Money\Trade::getOrderExecutionResult($user, $oid, $type == 'bid');
  437.         $mail_page = \Registry::getInstance()->OptionAddrOrderEmail ?: 'mail/option_addr_bitcoin_sold.mail';
  438.         $mail_data = [
  439.             '_TRADE_INFO' => $trade_info,
  440.         ];
  441.         if ($data) $mail_data['_DATA'] = $data;
  442.         return \Tpl::userMail($mail_page, $user, $mail_data);
  443.     }
  444.  
  445.     public static function checkOrders() {
  446.         // check data in Money_Bitcoin_Order to see if any order is completed
  447.         $db = \DB::i();
  448.         $list = $db['Money_Bitcoin_Order']->search(array('Status' => 'pending'));
  449.         $clients = array();
  450.  
  451.         foreach($list as $bean) {
  452.             if (!isset($clients[$bean->Money_Bitcoin_Host__])) $clients[$bean->Money_Bitcoin_Host__] = \Controller::Driver('Bitcoin', $bean->Money_Bitcoin_Host__);
  453.             $client = $clients[$bean->Money_Bitcoin_Host__];
  454.             $total = (int)round($client->getReceivedByAddress($bean->Address, 3) * 100000000); // 3 confirmations
  455.  
  456.             if ($bean->Coins == $total) { // nothing moved
  457.                 if ($db->dateRead($bean->Expires) < time()) {
  458.                     $bean->Status = 'expired';
  459.                     $bean->commit();
  460.                     continue;
  461.                 }
  462.             }
  463.             $bean->Coins = $total;
  464.             $total += $bean->Coins_Extra;
  465.             if ($bean->Total <= $total) {
  466.                 // payment complete!
  467.                 $bean->Status = 'ok';
  468.                 $bean->commit();
  469.  
  470.                 // mark order paid
  471.                 $order = \Order::byId($bean->Order__);
  472.                 if ($order->isPaid()) continue; // ?!
  473.                 $info = array(
  474.                     'method' => 'BITCOIN',
  475.                     'class' => 'Bitcoin',
  476.                     'stamp' => time(),
  477.                 );
  478.                 $order->paid($info);
  479.                 continue;
  480.             }
  481.  
  482.             $total_nc = (int)round($client->getReceivedByAddress($bean->Address, 0) * 100000000);
  483.             $bean->Coins_NC = $total_nc;
  484.             $bean->commit();
  485.         }
  486.     }
  487.  
  488.     public static function getAddressForOrder($order) {
  489.         $total = $order->getTotal();
  490.         if ($total->getCurrency()->Currency__ != 'BTC') return false;
  491.         $btc = $total['value'];
  492.  
  493.         $bean = \DB::DAO('Money_Bitcoin_Order')->searchOne(array('Order__' => $order->getId()));
  494.         if ($bean) {
  495.             if ($bean->Status != 'pending') return false;
  496.             $bean->Total = ((int)round($btc * 100))*1000000;
  497.             if ($bean->Address != '') {
  498.                 $bean->commit();
  499.                 return $bean;
  500.             } elseif ($bean->Coins == $bean->Coins_NC) {
  501.                 $bean->Coins_Extra = $bean->Coins;
  502.                 $bean->Coins = 0;
  503.                 $bean->Coins_NC = 0;
  504.                 // find a (new) random host
  505.                 $host = \DB::DAO('Money_Bitcoin_Host')->searchOne(array('Status' => 'up', 'Allow_Order' => 'Y'), array(new \DB\Expr('RAND()')));
  506.                 if (!$host) return false; // no available host right now
  507.                 $client = \Controller::Driver('Bitcoin', $host->Money_Bitcoin_Host__);
  508.                 $addr = $client->getNewAddress('ORDER:'.$order->getId());
  509.                 // update
  510.                 $bean->Address = $addr;
  511.                 $bean->commit();
  512.                 return $bean;
  513.             }
  514.         }
  515.  
  516.         // find a random host
  517.         $host = \DB::DAO('Money_Bitcoin_Host')->searchOne(array('Status' => 'up', 'Allow_Order' => 'Y'), array(new \DB\Expr('RAND()')));
  518.         if (!$host) return false; // no available host right now
  519.  
  520.         $client = \Controller::Driver('Bitcoin', $host->Money_Bitcoin_Host__);
  521.         $addr = $client->getNewAddress('ORDER:'.$order->getId());
  522.  
  523.         // new entry
  524.         $db = \DB::i();
  525.         $uuid = \System::uuid();
  526.         $insert = array(
  527.             'Money_Bitcoin_Order__' => $uuid,
  528.             'Order__' => $order->getId(),
  529.             'Money_Bitcoin_Host__' => $host->Money_Bitcoin_Host__,
  530.             'Address' => $addr,
  531.             'Coins' => 0,
  532.             'Total' => ((int)round($btc * 100)) * 1000000,
  533.             'Created' => $db->now(),
  534.             'Expires' => $db->dateWrite(time()+(86400*10)),
  535.         );
  536.         $db['Money_Bitcoin_Order']->insert($insert);
  537.         $bean = $db['Money_Bitcoin_Order'][$uuid];
  538.         if (!$bean) return false;
  539.  
  540.         return $bean;
  541.     }
  542.  
  543.     public static function sendAmount($address, $amount, $green = null, $inputs = array(), $fee = 0) {
  544.         if ($amount instanceof \Internal\Price) $amount = $amount->convert('BTC', null, \Currency::DIRECTION_OUT)->getIntValue();
  545.         if ($fee instanceof \Internal\Price) $fee = $fee->convert('BTC', null, \Currency::DIRECTION_OUT)->getIntValue();
  546.  
  547.         $transaction = \DB::i()->transaction();
  548.         $lock = \DB::i()->lock('Money_Bitcoin_Available_Output');
  549.  
  550.         $address = \Util\Bitcoin::decode($address);
  551.         if (!$address) throw new \Exception('Invalid bitcoin address');
  552.         $remainder = \Util\Bitcoin::decode(self::getNullAddr());
  553.         if (!$remainder) throw new \Exception('Failed to create output TX');
  554.  
  555.         $input = self::getTxInput($amount+$fee, $inputs);
  556.  
  557.         if (!is_null($green)) {
  558.             // green send
  559.             // default=d47c1c9afc2a18319e7b78762dc8814727473e90
  560.             $tmp_total = 0;
  561.             foreach($input as $tmp) $tmp_total += $tmp['amount'];
  562.             $key = \DB::DAO('Money_Bitcoin_Permanent_Address')->searchOne(array('Money_Bitcoin_Permanent_Address__' => $green));
  563.             if (!$key) throw new \Exception('Invalid green address for transaction');
  564.             // intermediate tx
  565.             $tx = \Util\Bitcoin::makeNormalTx($input, $tmp_total, array('hash' => $green), array('hash' => $green));
  566.             $txid = self::publishTransaction($tx);
  567.             \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'));
  568.             // final tx
  569.             $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);
  570.             $txid = self::publishTransaction($tx);
  571.         } else {
  572.             $tx = \Util\Bitcoin::makeNormalTx($input, $amount, $address, $remainder, $fee);
  573.             $txid = self::publishTransaction($tx);
  574.         }
  575.  
  576.         if (!$transaction->commit()) return false;
  577.  
  578.         return $txid;
  579.  
  580.         // find a node with enough coins
  581.         $node = \DB::DAO('Money_Bitcoin_Host')->searchOne(array('Status' => 'up', new \DB\Expr('`Coins` >= '.\DB::i()->quote($amount))), array(new \DB\Expr('RAND()')));
  582.         if (!$node) return false;
  583.         $client = \Controller::Driver('Bitcoin', $node->Money_Bitcoin_Host__);
  584.         return $client->sendToAddress($address, $amount/100000000);
  585.     }
  586.  
  587.     public function getWalletHost() {
  588.         throw new \Exception('Method is deprecated');
  589.     }
  590.  
  591.     public static function parseVersion($v) {
  592.         if ($v == 0) return '[unknown]';
  593.         if ($v > 10000) {
  594.             // [22:06:18] <ArtForz> new is major * 10000 + minor * 100 + revision
  595.             $rem = floor($v / 100);
  596.             $proto = $v - ($rem*100);
  597.             $v = $rem;
  598.         } else {
  599.             // [22:06:05] <ArtForz> old was major * 100 + minor
  600.             $proto = 0;
  601.         }
  602.         foreach(array('revision','minor','major') as $type) {
  603.             $rem = floor($v / 100);
  604.             $$type = $v - ($rem * 100);
  605.             $v = $rem;
  606.         }
  607.         // build string
  608.         return $major . '.' . $minor . '.' . $revision . ($proto?('[.'.$proto.']'):'');
  609.     }
  610.  
  611.     public static function _Route_getStats($path) {
  612.         switch($path) {
  613.             case 'version':
  614.                 $req = 'SELECT `Version`, COUNT(1) AS `Count` FROM `Money_Bitcoin_Node` WHERE `Status` != \'down\' GROUP BY `Version`';
  615.                 $sqlres = \DB::i()->query($req);
  616.                 $res = array();
  617.                 while($row = $sqlres->fetch_assoc()) {
  618.                     $res[self::parseVersion($row['Version'])] += $row['Count'];
  619.                 }
  620.                 break;
  621.             case 'ua':
  622.                 $req = 'SELECT `User_Agent`, COUNT(1) AS `Count` FROM `Money_Bitcoin_Node` WHERE `Status` != \'down\' GROUP BY `User_Agent`';
  623.                 $sqlres = \DB::i()->query($req);
  624.                 $res = array();
  625.                 while($row = $sqlres->fetch_assoc()) {
  626.                     $res[$row['User_Agent']] += $row['Count'];
  627.                 }
  628.                 break;
  629.             case 'nodes':
  630.                 $req = 'SELECT COUNT(1) AS `Count` FROM `Money_Bitcoin_Node` WHERE `Last_Seen` > DATE_SUB(NOW(), INTERVAL 6 HOUR)';
  631.                 $sqlres = \DB::i()->query($req);
  632.                 $row = $sqlres->fetch_assoc();
  633.                 header('Content-Type: text/plain');
  634.                 echo $row['Count'];
  635.                 exit;
  636.             case 'accepting':
  637.                 $req = 'SELECT `Status`, COUNT(1) AS `Count` FROM `Money_Bitcoin_Node` WHERE `Last_Seen` > DATE_SUB(NOW(), INTERVAL 6 HOUR) GROUP BY  `Status`';
  638.                 $sqlres = \DB::i()->query($req);
  639.                 $res = array();
  640.                 while($row = $sqlres->fetch_assoc()) {
  641.                     $res[$row['Status']] = $row['Count'];
  642.                 }
  643.                 $res['total_known'] = $res['up'] + $res['down'];
  644.                 $res['total'] = $res['total_known'] + $res['unknown'];
  645.                 $res['rate_accepting'] = $res['up'] / $res['total_known'];
  646.                 break;
  647.             case 'bootstrap':
  648.                 // select a set of peers appropriate as seed
  649.                 $limit = 50;
  650.                 if (isset($_GET['limit'])) {
  651.                     $limit = (int)$_GET['limit'];
  652.                     if ($limit < 1) $limit = 1;
  653.                     if ($limit > 10000) $limit = 10000;
  654.                 }
  655.                 $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;
  656.                 $sqlres = \DB::i()->query($req);
  657.                 if ($sqlres->num_rows == 0) {
  658.                     $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;
  659.                     $sqlres = \DB::i()->query($req);
  660.                 }
  661.                 $res = array();
  662.                 while($row = $sqlres->fetch_assoc()) {
  663.                     $res[] = array(
  664.                         'ipv4' => $row['IP'],
  665.                         'port' => $row['Port'],
  666.                         'version' => $row['Version'],
  667.                         'version_str' => self::parseVersion($row['Version']),
  668.                         'user_agent' => $row['User_Agent'],
  669.                         'timestamp' => \DB::i()->dateRead($row['Last_Checked']),
  670.                     );
  671.                 }
  672.                 break;
  673.             case 'geomap':
  674.                 // select all nodes
  675.                 $req = 'SELECT `IP`, `Status`, `Version` FROM `Money_Bitcoin_Node` WHERE `Last_Seen` > DATE_SUB(NOW(), INTERVAL 3 HOUR)';
  676.                 $sqlres = \DB::i()->query($req);
  677.                 header('Content-Type: application/json');
  678.                 echo '[';
  679.                 $first = true;
  680.                 $geoip = \ThirdParty\Geoip::getInstance();
  681.                 while($row = $sqlres->fetch_assoc()) {
  682.                     $res = array('ipv4' => $row['IP'], 'version' => $row['Version'], 'status' => $row['Status']);
  683.                     $record = $geoip->lookup($row['IP'], false);
  684.                     if (!$record) continue;
  685.                     if (!isset($record['latitude'])) continue;
  686.                     $res['latitude'] = $record['latitude'];
  687.                     $res['longitude'] = $record['longitude'];
  688.                     if ($first) {
  689.                         $first = false;
  690.                     } else {
  691.                         echo ',';
  692.                     }
  693.                     echo json_encode($res);
  694.                 }
  695.                 echo ']';
  696.                 exit;
  697.             case 'full':
  698.                 // select all nodes
  699.                 $req = 'SELECT * FROM `Money_Bitcoin_Node`';
  700.                 $sqlres = \DB::i()->query($req);
  701.                 header('Content-Type: application/json');
  702.                 echo '[';
  703.                 $first = true;
  704.                 while($row = $sqlres->fetch_assoc()) {
  705.                     if ($first) {
  706.                         $first = false;
  707.                     } else {
  708.                         echo ',';
  709.                     }
  710.                     echo json_encode($row);
  711.                 }
  712.                 echo ']';
  713.                 exit;
  714.             case 'bitcoin.kml':
  715.                 header('Content-Type: application/vnd.google-earth.kml+xml');
  716.                 // check cache
  717.                 $cache = \Cache::getInstance();
  718.                 $data = $cache->get('bitcoin.kml_full');
  719.                 if ($data) {
  720.                     echo $data;
  721.                     exit;
  722.                 }
  723.                 // select all nodes
  724.                 $out = fopen('php://temp', 'w');
  725.                 fwrite($out, "<?xml version=\"1.0\" encoding=\"UTF-8\"?".">\n");
  726.                 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");
  727.                 fwrite($out, "<Document>\n<name>Bitcoin nodes in the world</name>\n");
  728.                 // styles
  729.                 fwrite($out, "<Style id=\"up\"><IconStyle><Icon><href>http://maps.google.com/mapfiles/kml/paddle/grn-blank.png</href></Icon></IconStyle></Style>\n");
  730.                 fwrite($out, "<Style id=\"down\"><IconStyle><Icon><href>http://maps.google.com/mapfiles/kml/paddle/red-blank.png</href></Icon></IconStyle></Style>\n");
  731.                 fwrite($out, "<Style id=\"unknown\"><IconStyle><Icon><href>http://maps.google.com/mapfiles/kml/paddle/wht-blank.png</href></Icon></IconStyle></Style>\n");
  732.                 $req = 'SELECT `IP`, `Status`, `Version` FROM `Money_Bitcoin_Node` WHERE `Last_Seen` > DATE_SUB(NOW(), INTERVAL 3 HOUR) ORDER BY `Status`';
  733.                 $geoip = \ThirdParty\Geoip::getInstance();
  734.                 $folder = '';
  735.                 $sqlres = \DB::i()->query($req);
  736.                 while($row = $sqlres->fetch_assoc()) {
  737.                     // lookup
  738.                     $record = $geoip->lookup($row['IP'], false);
  739.                     if (!$record) continue;
  740.                     if (!isset($record['latitude'])) continue;
  741.  
  742.                     if ($folder != $row['Status']) {
  743.                         if ($folder) fwrite($out, "</Folder>\n");
  744.                         $folder = $row['Status'];
  745.                         fwrite($out, "<Folder><name>Bitcoin Nodes in status ".$folder."</name>\n");
  746.                     }
  747.                     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>");
  748.                     fwrite($out, "<Point><coordinates>".$record['longitude'].",".$record['latitude']."</coordinates></Point></Placemark>\n");
  749.                 }
  750.                 fwrite($out, "</Folder>\n</Document>\n</kml>\n");
  751.                 rewind($out);
  752.                 $data = stream_get_contents($out);
  753.                 fclose($out);
  754.                 $cache->set('bitcoin.kml_full', $data, 1800);
  755.                 echo $data;
  756.                 exit;
  757.             default:
  758.                 header('HTTP/1.0 404 Not Found');
  759.                 die('Not available');
  760.         }
  761.         header('Content-Type: application/json');
  762.         echo json_encode($res);
  763.         exit;
  764.     }
  765.  
  766.     public static function checkNodes($sched) {
  767.         // get nodes to check
  768.         $db = \DB::i();
  769.         $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));
  770.         if (count($list) == 701) {
  771.             $sched->busy();
  772.             array_pop($list);
  773.         }
  774.  
  775.         $end_time = (floor(time()/60)*60)+50;
  776.  
  777.         $nodes = new Bitcoin\Nodes();
  778.         $info = array();
  779.         $up = array();
  780.  
  781.         $nodes->on(null, 'ready', function($key) use (&$info, $nodes, $db, &$up) {
  782.             $node = $info[$key];
  783.             $node->Version = $nodes->getVersion($key);
  784.             $node->User_Agent = $nodes->getUserAgent($key);
  785.             $node->Status = 'up';
  786.             $node->Last_Seen = $db->now();
  787.             $node->Last_Checked = $db->now();
  788.             $node->Next_Check = $db->dateWrite(time()+(1800));
  789.             $node->commit();
  790.             $up[$key] = true;
  791.             $nodes->getAddr($key); // initiate loading of addrs
  792.         });
  793.  
  794.         $nodes->on(null, 'error', function($key, $error) use (&$info, $db, &$up) {
  795.             if ($up[$key]) return; // probably getaddr failed
  796.             $node = $info[$key];
  797.             $node->Status = 'down';
  798.             $node->Last_Checked = $db->now();
  799.             $node->Next_Check = $db->dateWrite(time()+(3600*24));
  800.             $node->Last_Down = $db->now();
  801.             $node->Last_Error = $error;
  802.             if ($db->dateRead($node->Last_Seen) < (time() - (3600*24))) { // no news for 24 hours, drop it
  803.                 $node->delete();
  804.                 return;
  805.             }
  806.             $node->commit();
  807.         });
  808.  
  809.         $nodes->on(null, 'addr', function($key, $addr_list) use (&$info, $nodes, $db) {
  810.             $node = $info[$key];
  811.             if (count($addr_list) > 1000) {
  812.                 $node->Addresses = 0;
  813.                 $node->commit();
  814.                 return;
  815.             }
  816.             $node->Addresses = count($addr_list);
  817.             $node->commit();
  818.             foreach($addr_list as $addr) {
  819.                 $bean = $db['Money_Bitcoin_Node']->searchOne(array('IP' => $addr['ipv4'], 'Port' => $addr['port']));
  820.                 if ($bean) {
  821.                     $bean->Last_Seen = $db->now();
  822.                     $bean->commit();
  823.                     continue;
  824.                 }
  825.  
  826.                 $db['Money_Bitcoin_Node']->insert(array(
  827.                     'IP' => $addr['ipv4'],
  828.                     'Port' => $addr['port'],
  829.                     'Next_Check' => $db->now(),
  830.                     'First_Seen' => $db->now(),
  831.                     'Last_Seen' => $db->now(),
  832.                 ));
  833.             }
  834.  
  835.             $nodes->close($key);
  836.         });
  837.  
  838.         foreach($list as $node) {
  839.             if ($node->Port < 1024) {
  840.                 $node->Status = 'down';
  841.                 $node->Last_Checked = $db->now();
  842.                 $node->Next_Check = $db->dateWrite(time()+(3600*24));
  843.                 $node->Last_Down = $db->now();
  844.                 if ($db->dateRead($node->Last_Seen) < (time() - (3600*24))) { // no news for 24 hours, drop it
  845.                     $node->delete();
  846.                     return;
  847.                 }
  848.                 $node->Last_Error = 'invalid_port';
  849.                 $node->commit();
  850.                 continue;
  851.             }
  852.             $key = 'node_'.$node->Money_Bitcoin_Node__;
  853.             $info[$key] = $node;
  854.             if (!$nodes->connect($key, $node->IP, $node->Port)) {
  855.                 $node->Status = 'down';
  856.                 $node->Last_Checked = $db->now();
  857.                 $node->Next_Check = $db->dateWrite(time()+(3600*24));
  858.                 $node->Last_Down = $db->now();
  859.                 if ($db->dateRead($node->Last_Seen) < (time() - (3600*24))) { // no news for 24 hours, drop it
  860.                     $node->delete();
  861.                     return;
  862.                 }
  863.                 $node->Last_Error = 'invalid_address';
  864.                 $node->commit();
  865.             }
  866.         }
  867.  
  868.         while($nodes->wait());
  869.     }
  870.  
  871.     public static function importBlockClaim($hash, $n, $tx) {
  872.         $trx = \DB::DAO('Money_Bitcoin_Block_Tx_Out')->searchOne(array('Hash' => $hash, 'N' => $n));
  873.         if (!$trx) throw new \Exception('Claim from unknown trx: '.$hash.':'.$n);
  874.         $trx->Claimed = 'Y';
  875.         $trx->commit();
  876.         \DB::DAO('Money_Bitcoin_Available_Output')->delete(array('Hash' => $hash, 'N' => $n));
  877.         return true;
  878.     }
  879.  
  880.     public static function parseScriptPubKey($pubkey) {
  881.         if (preg_match('/^([0-9a-f]{1,130}) OP_CHECKSIG$/', $pubkey, $matches)) {
  882.             return array('hash' => \Util\Bitcoin::decodePubkey($matches[1]), 'pubkey' => $matches[1]);
  883.         }
  884.         if (preg_match('/^OP_DUP OP_HASH160 ([0-9a-f]{40}) OP_EQUALVERIFY OP_CHECKSIG.*$/', $pubkey, $matches)) {
  885.             return array('hash' => array('hash' => $matches[1], 'version' => 0));
  886.         }
  887.         \Debug::exception(new \Exception('WEIRD scriptPubKey - dropping it: '.$pubkey));
  888.         return array('hash' => ['hash' => '0000000000000000000000000000000000000000', 'version' => 0]);
  889.     }
  890.  
  891.     public static function importBlock($id) {
  892.         $peer = \Controller::Driver('Bitcoin', 'b54f4d35-dd1c-43aa-9096-88e37a83bda3');
  893.         $block = $peer->getBlock($id);
  894.  
  895.         $transaction = \DB::i()->transaction();
  896.  
  897.         // insert block
  898.         $data = array(
  899.             'Money_Bitcoin_Block__' => $block['hash'],
  900.             'Parent_Money_Bitcoin_Block__' => $block['prev_block'],
  901.             'Depth' => $id,
  902.             'Version' => $block['version'],
  903.             'Mrkl_Root' => $block['mrkl_root'],
  904.             'Time' => \DB::i()->dateWrite($block['time']),
  905.             'Bits' => $block['bits'],
  906.             'Nonce' => $block['nonce'],
  907.             'Size' => $block['size'],
  908.         );
  909.         \DB::DAO('Money_Bitcoin_Block')->insert($data);
  910.  
  911.         $retry = 0;
  912.         while($block['tx']) {
  913.             $tx = array_shift($block['tx']);
  914.             $tmp = \DB::DAO('Money_Bitcoin_Block_Tx')->search(array('Hash' => $tx['hash']));
  915.             if ($tmp) continue; // skip duplicate TXs
  916.             $tx['block'] = $id;
  917.             $data = array(
  918.                 'Hash' => $tx['hash'],
  919.                 'Block' => $block['hash'],
  920.                 'Version' => $tx['version'],
  921.                 'Lock_Time' => $tx['lock_time'],
  922.                 'size' => $tx['size'],
  923.             );
  924.             \DB::DAO('Money_Bitcoin_Block_Tx')->insert($data);
  925.             \DB::DAO('Money_Bitcoin_Tx')->delete(array('Money_Bitcoin_Tx__' => $data['Hash']));
  926.             \DB::DAO('Money_Bitcoin_Tx_In')->delete(array('Hash' => $data['Hash']));
  927.             \DB::DAO('Money_Bitcoin_Tx_Out')->delete(array('Hash' => $data['Hash']));
  928.  
  929.             $watch = null;
  930.             $taint = null;
  931.             $taint_c = 0;
  932.  
  933.             try {
  934.                 foreach($tx['in'] as $n => $in) {
  935.                     $data = array(
  936.                         'Hash' => $tx['hash'],
  937.                         'N' => $n,
  938.                         'Prev_Out_Hash' => $in['prev_out']['hash'],
  939.                         'Prev_Out_N' => $in['prev_out']['n'],
  940.                     );
  941.                     if ($in['coinbase']) {
  942.                         $data['CoinBase'] = $in['coinbase'];
  943.                     } else {
  944.                         $data['scriptSig'] = $in['scriptSig'];
  945.                         self::importBlockClaim($in['prev_out']['hash'], $in['prev_out']['n'], $tx);
  946.                     }
  947. //                  \DB::DAO('Money_Bitcoin_Block_Tx_In')->insert($data);
  948.                 }
  949.             } catch(\Exception $e) {
  950.                 // retry later
  951.                 if ($retry++ > 10) throw $e;
  952.                 $block['tx'][] = $tx;
  953.                 continue;
  954.             }
  955.  
  956.             if (!is_null($taint)) $taint = (int)floor($taint/$taint_c);
  957.  
  958.             foreach($tx['out'] as $n => $out) {
  959.                 $data = array(
  960.                     'Hash' => $tx['hash'],
  961.                     'N' => $n,
  962.                     'Value' => round($out['value']*100000000),
  963.                 );
  964.                 $addr = self::parseScriptPubKey($out['scriptPubKey']);
  965.                 $data['Addr'] = $addr['hash']['hash'];
  966.                 \DB::DAO('Money_Bitcoin_Block_Tx_Out')->insert($data);
  967.                 if (isset(\DB::DAO('Money_Bitcoin_Permanent_Address')[$data['Addr']])) {
  968.                     $data['Money_Bitcoin_Process_Tx_Out__'] = \System::uuid();
  969.                     \DB::DAO('Money_Bitcoin_Process_Tx_Out')->insert($data, true);
  970.                 }
  971.             }
  972.         }
  973.  
  974.         $transaction->commit();
  975.     }
  976.  
  977.     public static function importBlocks($scheduler) {
  978.         // determine last imported block
  979.         $block = \DB::DAO('Money_Bitcoin_Block')->searchOne(null, array('Depth' => 'DESC'));
  980.         if ($block) {
  981.             $block_id = $block->Depth + 1;
  982.         } else {
  983.             $block_id = 0;
  984.         }
  985.         // read blocks from b54f4d35-dd1c-43aa-9096-88e37a83bda3
  986.         $peer = \Controller::Driver('Bitcoin', 'b54f4d35-dd1c-43aa-9096-88e37a83bda3');
  987.  
  988.         $info = $peer->getInfo();
  989.         if ($info['errors']) {
  990.             // reschedule for in one hour
  991.             $scheduler->busy(3600);
  992.             throw new \Exception('Can\'t import blocks: '.$info['errors']);
  993.         }
  994.  
  995.         $last_block = $peer->getCurrentBlock()-5; // 5 confirmations
  996.         if ($last_block < $block_id) {
  997.             // nothing new here
  998. //          self::runAddrTriggers();
  999.             return;
  1000.         }
  1001.  
  1002.         $deadline = time()+50;
  1003.         $c = 0;
  1004.  
  1005.         while($block_id <= $last_block) {
  1006.             try {
  1007.                 self::importBlock($block_id);
  1008.             } catch(\Exception $e) {
  1009.                 mail('[email protected]', 'BLOCK IMPORT ERROR', $e->getMessage()."\n\n".$e);
  1010.                 $scheduler->busy(600);
  1011.                 return;
  1012.                 // empty all!
  1013.                 $db = \DB::i();
  1014.                 $db->query('TRUNCATE `Money_Bitcoin_Block`');
  1015. //              $db->query('TRUNCATE `Money_Bitcoin_Block_Addr`');
  1016.                 $db->query('TRUNCATE `Money_Bitcoin_Block_Tx`');
  1017. //              $db->query('TRUNCATE `Money_Bitcoin_Block_Tx_In`');
  1018.                 $db->query('TRUNCATE `Money_Bitcoin_Block_Tx_Out`');
  1019.             }
  1020.             $block_id++;
  1021.             if ((time() > $deadline) || ($c++>49)) {
  1022.                 $scheduler->busy(0);
  1023.                 break;
  1024.             }
  1025.         }
  1026.  
  1027.         // run addr triggers
  1028. //      self::runAddrTriggers();
  1029.     }
  1030.  
  1031.     public static function insertMisingAvailableOutputs($addr) {
  1032.         // search all unclaimed on this addr
  1033.         $list = \DB::DAO('Money_Bitcoin_Process_Tx_Out')->search(array('Addr' => $addr, 'Claimed' => 'N'));
  1034.         foreach($list as $bean) {
  1035.             $insert = array(
  1036.                 'Money_Bitcoin_Available_Output__' => \System::uuid(),
  1037.                 'Money_Bitcoin_Permanent_Address__' => $bean->Addr,
  1038.                 'Value' => $bean->Value,
  1039.                 'Hash' => $bean->Hash,
  1040.                 'N' => $bean->N,
  1041.             );
  1042.             \DB::DAO('Money_Bitcoin_Available_Output')->insert($insert, true);
  1043.         }
  1044.     }
  1045.  
  1046.     public static function runAddrTriggers() {
  1047.         // lookup tx out with Trigger = new
  1048.         $list = \DB::DAO('Money_Bitcoin_Process_Tx_Out')->search(array('Trigger' => 'new'), null, array(500)); // limit to 500
  1049. //      $main_transaction = \DB::i()->transaction();
  1050.  
  1051.         foreach($list as $bean) {
  1052.             $transaction = \DB::i()->transaction();
  1053.             $bean->reloadForUpdate();
  1054.             if ($bean->Trigger != 'new') {
  1055.                 // rollback, exit
  1056.                 unset($transaction);
  1057.                 continue;
  1058.             }
  1059.             $bean->Trigger = 'executed';
  1060.             $bean->commit();
  1061.  
  1062.             $tx = $bean->Hash.':'.$bean->N;
  1063.  
  1064.             $addr_str = \Util\Bitcoin::encode(array('version' => 0, 'hash' => $bean->Addr));
  1065.             $wallet_info = \DB::DAO('Money_Bitcoin_Permanent_Address')->searchOne(array('Money_Bitcoin_Permanent_Address__' => $bean->Addr));
  1066.  
  1067.             $redirect_value = null;
  1068.             if ($wallet_info) $redirect_value = $wallet_info->Redirect;
  1069.  
  1070.             $base_tx_data = \DB::DAO('Money_Bitcoin_Block_Tx')->searchOne(array('Hash' => $bean->Hash));
  1071.             $base_block_data = \DB::DAO('Money_Bitcoin_Block')->searchOne(['Money_Bitcoin_Block__' => $base_tx_data->Block]);
  1072.  
  1073.             if (($wallet_info) && (!is_null($wallet_info->Private_Key)) && ($redirect_value == 'none')) {
  1074.                 $insert = array(
  1075.                     'Money_Bitcoin_Available_Output__' => \System::uuid(),
  1076.                     'Money_Bitcoin_Permanent_Address__' => $bean->Addr,
  1077.                     'Value' => $bean->Value,
  1078.                     'Hash' => $bean->Hash,
  1079.                     'N' => $bean->N,
  1080.                     'Block' => $base_block_data->Depth,
  1081.                 );
  1082.                 \DB::DAO('Money_Bitcoin_Available_Output')->insert($insert, true);
  1083.             }
  1084.  
  1085.             if ($redirect_value == 'fixed') {
  1086.                 // redirect funds
  1087.                 $target = $wallet_info->Redirect_Value;
  1088.                 $pub = \Util\Bitcoin::decode($target);
  1089.                 $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);
  1090.                 self::publishTransaction($tx);
  1091.                 $transaction->commit();
  1092.                 continue;
  1093.             }
  1094.  
  1095.             if (($wallet_info) && (!is_null($wallet_info->Callback))) {
  1096.                 try {
  1097.                     $cb = explode('::', str_replace('/', '\\', $wallet_info->Callback));
  1098.                     call_user_func($cb, $wallet_info, $tx, $base_tx_data->Block, \Internal\Price::spawnInt($bean->Value,'BTC'));
  1099.                 } catch(\Exception $e) {
  1100.                     \Debug::exception($e);
  1101.                     unset($transaction);
  1102.                     continue;
  1103.                 }
  1104.             }
  1105.  
  1106.             if (($wallet_info) && (!is_null($wallet_info->Ipn))) {
  1107.                 $base_tx_data = \DB::DAO('Money_Bitcoin_Block_Tx')->searchOne(array('Hash' => $bean->Hash));
  1108.                 $post = array(
  1109.                     'description' => $wallet_info->Description,
  1110.                     'tx' => $tx,
  1111.                     'block' => $base_tx_data->Block,
  1112.                     'status' => 'confirmed',
  1113.                     'amount_int' => $bean->Value,
  1114.                     'item' => 'BTC',
  1115.                     'addr' => \Util\Bitcoin::encode(array('version' => 0, 'hash' => $wallet_info->Money_Bitcoin_Permanent_Address__)),
  1116.                 );
  1117.                 \Scheduler::oneshotUrl($wallet_info->Ipn, $post, null, null, null, $wallet_info->User_Rest__);
  1118.             }
  1119.  
  1120.             if (($wallet_info) && (!is_null($wallet_info->User_Wallet__))) {
  1121.                 $wallet_info->Used = 'Y';
  1122.                 $wallet_info->commit();
  1123.                 $wallet = \User\Wallet::byId($wallet_info->User_Wallet__);
  1124.                 if (($wallet) && ($wallet['Currency__'] == 'BTC')) {
  1125.                     // WALLET REDIRECT CODE 1
  1126.                     if ((!is_null($wallet_info->Private_Key)) && ($wallet_info->Redirect == 'wallet') && ($bean->Value > 100000)) {
  1127.                         // redirect funds
  1128.                         $target = self::getVerboseAddr($wallet, $wallet_info->Description);
  1129.                         $pub = \Util\Bitcoin::decode($target);
  1130.                         try {
  1131.                             $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);
  1132.                         } catch(\Exception $e) {
  1133.                             mail('[email protected]', 'FAILED TO GENERATE REDIRECT TX', 'Error '.$e->getMessage().' on: '.$wallet_info->Money_Bitcoin_Permanent_Address__."\n".print_r($bean->getProperties(), true));
  1134.                             throw $e;
  1135.                         }
  1136.                         self::publishTransaction($tx);
  1137.                         $transaction->commit();
  1138.                         continue;
  1139.                     }
  1140.                     // search for already add
  1141.                     $nfo = \DB::DAO('User_Wallet_History')->searchOne(array('Reference_Type' => 'Money_Bitcoin_Block_Tx_Out', 'Reference' => $tx));
  1142.                     if (!$nfo) {
  1143.                         $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);
  1144.                         if ($wallet['Balance']['value'] > 10000) $wallet->getUser()->aml('Balance in bitcoin is over 10000', 2); // force AML
  1145.                         \Money\Trade::updateUserOrders($wallet->getUser());
  1146.                     }
  1147.                 }
  1148.             }
  1149.  
  1150.             $transaction->commit();
  1151.         }
  1152.  
  1153. //      $main_transaction->commit();
  1154.  
  1155.         return count($list);
  1156.     }
  1157.  
  1158.     public static function getAddressBalance($addr) {
  1159.         $res = \Internal\Price::spawn(0,'BTC');
  1160.         $list = \DB::DAO('Money_Bitcoin_Block_Tx_Out')->search(['Addr'=>$addr['hash']]);
  1161.         foreach($list as $bean)
  1162.             $res->add(\Internal\Price::spawnInt($bean->Value, 'BTC'));
  1163.         return $res;
  1164.     }
  1165.  
  1166.     public static function getAddressOutputs($addr) {
  1167.         // get all unclaimed outputs for that addr
  1168.         $list = \DB::DAO('Money_Bitcoin_Block_Tx_Out')->search(array('Addr' => $addr['hash'], 'Claimed' => 'N'));
  1169.         $final = array();
  1170.         foreach($list as $bean) $final[] = $bean->getProperties();
  1171.         return $final;
  1172.     }
  1173.  
  1174.     public static function claimPrivateSha256($wallet, $priv, $desc = null) {
  1175.         return self::claimPrivate($wallet, \Util\Bitcoin::hash_sha256($priv), $desc);
  1176.     }
  1177.  
  1178.     public static function claimWalletFile($wallet, $data, $desc = null) {
  1179.         $keys = \Util\Bitcoin::scanWalletFile($data);
  1180.         if (!$keys) return array();
  1181.  
  1182.         $res = array();
  1183.  
  1184.         foreach($keys as $key) {
  1185.             $tmp = self::claimPrivate($wallet, $key, $desc);
  1186.             if (!$tmp) continue;
  1187.             $res[] = $tmp;
  1188.         }
  1189.         return $res;
  1190.     }
  1191.  
  1192.     public static function claimPrivate($wallet, $priv, $desc = null) {
  1193.         // get all the funds sent to that private addr and record it for future deposits
  1194.         if (strlen($priv) != 32) throw new \Exception('The private key must be 32 bytes');
  1195.  
  1196.         // check if privkey is within range
  1197.         $pk_num = gmp_init(bin2hex($priv), 16);
  1198.         if (gmp_cmp($pk_num, '0') <= 0) return false;
  1199.         if (gmp_cmp($pk_num, gmp_init('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16)) >= 0) return false;
  1200.  
  1201.         $pub = \Util\Bitcoin::decodePrivkey($priv);
  1202.         $addr = \Util\Bitcoin::encode($pub);
  1203.         $outs = \Money\Bitcoin::getAddressOutputs($pub);
  1204.  
  1205.         $find = \DB::DAO('Money_Bitcoin_Permanent_Address')->searchOne(array('Money_Bitcoin_Permanent_Address__' => $pub['hash']));
  1206.         if ($find) {
  1207.             if (!is_null($find->Private_Key)) return false; // already got this one
  1208.             $find->Private_Key = \Internal\Crypt::encrypt($priv);
  1209.             $find->Redirect = 'wallet';
  1210.             $find->Used = 'Y';
  1211.             $find->commit();
  1212.             $wallet = \User\Wallet::byId($find->User_Wallet__);
  1213.         } else {
  1214.             $insert = array(
  1215.                 'Money_Bitcoin_Permanent_Address__' => $pub['hash'],
  1216.                 'Money_Bitcoin_Host__' => null,
  1217.                 'Private_Key' => \Internal\Crypt::encrypt($priv),
  1218.                 'Description' => $desc,
  1219.                 'Redirect' => 'nulladdr',
  1220.                 'Used' => 'Y',
  1221.             );
  1222.             if (!is_null($wallet)) {
  1223.                 $insert['User_Wallet__'] = $wallet->getId();
  1224.                 $insert['Redirect'] = 'wallet';
  1225.             }
  1226.             \DB::DAO('Money_Bitcoin_Permanent_Address')->insert($insert);
  1227.         }
  1228.  
  1229.         $total = 0;
  1230.         if ($outs) {
  1231.             if (is_null($wallet)) {
  1232.                 $out = self::getNullAddr();
  1233.             } else {
  1234.                 $out = self::getVerboseAddr($wallet, $desc);
  1235.             }
  1236.             $outpub = \Util\Bitcoin::decode($out);
  1237.             $input = array();
  1238.             foreach($outs as $t) {
  1239.                 $input[] = array('amount' => $t['Value'], 'tx' => $t['Hash'], 'N' => $t['N'], 'privkey' => $priv, 'hash' => $pub['hash']);
  1240.                 $total += $t['Value'];
  1241.             }
  1242.  
  1243.             $tx = \Util\Bitcoin::makeNormalTx($input, $total, $outpub, $outpub);
  1244.             self::publishTransaction($tx);
  1245.         }
  1246.         return array('amount' => \Internal\Price::spawnInt($total, 'BTC'), 'address' => $addr);
  1247.     }
  1248.  
  1249.     public static function makeNormalTx($input, $amount, $final_output, $remainder, $fee = 0) {
  1250.         // make a normal tx, merge inputs if preferable
  1251.         $res = array();
  1252.         while(count($input) > 5) {
  1253.             // merge some inputs
  1254.             $xinput = array();
  1255.             $output = self::getNullAddr(true);
  1256.  
  1257.             // merge as many inputs as we can in a single tx
  1258.             while(true) {
  1259.                 $extra = array_shift($input);
  1260.                 if (is_null($extra)) break;
  1261.                 $tinput = $xinput;
  1262.                 $tinput[] = $extra;
  1263.                 $total = 0;
  1264.                 foreach($tinput as $t) $total+=$t['amount'];
  1265.                 $ttx = \Util\Bitcoin::makeNormalTx($tinput, $total, $output['info'], $output['info']);
  1266.                 if (strlen($ttx) >= 1000) break;
  1267.                 $xinput[] = $extra;
  1268.             }
  1269.             if (!is_null($extra))
  1270.                 array_unshift($input, $extra);
  1271.             $total = 0;
  1272.             foreach($xinput as $t) $total += $t['amount'];
  1273.             $ttx = \Util\Bitcoin::makeNormalTx($xinput, $total, $output['info'], $output['info']);
  1274.             $res[] = $ttx;
  1275.             $thash = bin2hex(strrev(\Util\Bitcoin::hash_sha256(\Util\Bitcoin::hash_sha256($ttx))));
  1276.             $input[] = array(
  1277.                 'amount' => $total,
  1278.                 'tx' => $thash,
  1279.                 'N' => 0,
  1280.                 'privkey' => $output['priv'],
  1281.                 'hash' => $output['info']['hash'],
  1282.             );
  1283.             \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'));
  1284.         }
  1285.         // do the final tx
  1286.         $res[] = \Util\Bitcoin::makeNormalTx($input, $amount, $final_output, $remainder, $fee);
  1287.         return $res;
  1288.     }
  1289.  
  1290.     public static function publishTransaction($txs) {
  1291.         // generate tx id
  1292.         if (!is_array($txs)) $txs = array($txs);
  1293.         foreach($txs as $tx) {
  1294.             $txid = bin2hex(strrev(\Util\Bitcoin::hash_sha256(\Util\Bitcoin::hash_sha256($tx))));
  1295.             $insert = array(
  1296.                 'Hash' => $txid,
  1297.                 'Blob' => base64_encode($tx),
  1298.                 'Created' => \DB::i()->now(),
  1299.             );
  1300.             \DB::DAO('Money_Bitcoin_Pending_Tx')->insert($insert);
  1301.             self::$pending[$txid] = $tx;
  1302.         }
  1303.         return $txid;
  1304.     }
  1305.  
  1306.     public static function broadcastPublished() {
  1307.         if (!self::$pending) return;
  1308.         \Controller::MQ('RabbitMQ')->invoke('Money/Bitcoin::broadcastPublished', ['txs' => self::$pending]);
  1309.         self::$pending = [];
  1310.     }
  1311.  
  1312.     public static function _MQ_broadcastPublished($info) {
  1313.         $list = $info['txs'];
  1314.         $node = new \Money\Bitcoin\Node(self::BITCOIN_NODE);
  1315.         foreach($list as $tx) {
  1316.             $node->pushTx($tx);
  1317.         }
  1318.         $node->getAddr(); // force sync
  1319.     }
  1320.  
  1321.     public static function broadcastTransactions() {
  1322.         $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));
  1323.         if (!$list) return;
  1324.  
  1325. //      $ip = gethostbyname('relay.eligius.st');
  1326.         $ip = gethostbyname('mtgox.relay.eligius.st');
  1327.         $node = new \Money\Bitcoin\Node(self::BITCOIN_NODE);
  1328.         $peer = \Controller::Driver('Bitcoin', 'b54f4d35-dd1c-43aa-9096-88e37a83bda3');
  1329.         $el_todo = array();
  1330.  
  1331.         foreach($list as $bean) {
  1332.             // check if successful
  1333.             $success = \DB::DAO('Money_Bitcoin_Block_Tx')->searchOne(array('Hash' => $bean->Hash));
  1334.             if ($success) {
  1335.                 $bean->delete();
  1336.                 continue;
  1337.             }
  1338.             $bean->Last_Broadcast = \DB::i()->now();
  1339.             if ((\DB::i()->dateRead($bean->Created) < (time()-7000)) && ($bean->Eligius == 'N')) {
  1340.                 try {
  1341.                     if (!$el_node) $el_node = new \Money\Bitcoin\Node($ip);
  1342.                     $el_node->pushTx(base64_decode($bean->Blob));
  1343.                     $bean->Eligius = 'P';
  1344.                 } catch(\Exception $e) {
  1345.                     // too bad
  1346.                 }
  1347.             } elseif ($bean->Eligius == 'P') {
  1348.                 $bean->Eligius = 'Y';
  1349.                 $el_todo[] = $bean->Hash;
  1350.             }
  1351.             try {
  1352.                 $bean->Last_Result = $peer->sendRawTransaction(bin2hex(base64_decode($bean->Blob)));
  1353.             } catch(\Exception $e) {
  1354.                 $bean->Last_Result = $e->getMessage();
  1355.             }
  1356.             $bean->commit();
  1357.             $node->pushTx(base64_decode($bean->Blob));
  1358.         }
  1359.  
  1360.         $node->getAddr(); // force sync reply from bitcoin daemon so we know the stuff went through
  1361.         if ($el_node) $el_node->getAddr();
  1362.         if ($el_todo) {
  1363.             $ssh = new \Network\SSH($ip);
  1364.             if (!$ssh->authKeyUuid('freetxn', '14a70b11-5f36-4890-82ca-5de820882c7f')) {
  1365.                 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));
  1366.                 return; // failed
  1367.             }
  1368.             foreach($el_todo as $tx) {
  1369.                 $channel = $ssh->channel();
  1370.                 $channel->exec($tx);
  1371.                 $channel->wait();
  1372.             }
  1373.         }
  1374.     }
  1375.  
  1376.     /**
  1377.      * Returns the total amount of bitcoins in the world based on that last block generated
  1378.      *
  1379.      * @return int The total amount of bitcoins
  1380.      */
  1381.     public static function getTotalCount() {
  1382.         // get total count of BTC in the world based on latest block #
  1383.         $last_block = \DB::DAO('Money_Bitcoin_Block')->searchOne(null, ['Depth'=>'DESC']);
  1384.         $current = $last_block->Depth;
  1385.  
  1386.         // this is a chunk of blocks, bitcoins generated per chunk start at 50 and halve every chunks
  1387.         $block_size = 210000;
  1388.  
  1389.         // first compute the total amount of bitcoins for the chunks that are fully done
  1390.         $full_block_count = floor($current / $block_size);
  1391.         $full_block_coeff = (1 - pow(0.5, $full_block_count)) * 100;
  1392.  
  1393.         // those are the bitcoins on the full block chunks
  1394.         $total_bitcoins = $full_block_coeff * $block_size;
  1395.  
  1396.         // then for the last chunk
  1397.         $last_block_coeff = pow(0.5, $full_block_count + 1) * 100;
  1398.         $total_bitcoins += $last_block_coeff * ($current - ($full_block_count * $block_size));
  1399.  
  1400.         return $total_bitcoins;
  1401.     }
  1402.  
  1403.     public static function _Route_bitcoind($path) {
  1404.         $post = file_get_contents('php://input');
  1405.         $post = json_decode($post, true);
  1406.         if (!$post) return;
  1407.         $method = $post['method'];
  1408.         $params = $post['params'];
  1409.         $id = $post['id']?:\System::uuid();
  1410.         try {
  1411.             throw new \Exception('Meh: '.$method);
  1412.             die(json_encode(array('result' => $res, 'id' => $id)));
  1413.         } catch(\Exception $e) {
  1414.             die(json_encode(array('error' => $e->getMessage(), 'id' => $id)));
  1415.         }
  1416.     }
  1417.  
  1418.     public static function _Route_handleTx() {
  1419.         // posted by halfnode with a TX
  1420.         $tx_bin = pack('H*', $_POST['tx']);
  1421.         $tx = \Util\Bitcoin::parseTx($tx_bin);
  1422.         if (!$tx) die('BAD TX');
  1423.         $hash = $tx['hash'];
  1424.         $dao = \DB::DAO('Money_Bitcoin_Tx');
  1425.         if (isset($dao[$hash])) die('DUP');
  1426.         if (\DB::DAO('Money_Bitcoin_Block_Tx')->countByField(array('Hash' => $hash))) die('DUP(blockchain)');
  1427.  
  1428.         $insert = array(
  1429.             'Money_Bitcoin_Tx__' => $hash,
  1430.             'Data' => base64_encode($tx_bin),
  1431.             'Size' => strlen($tx_bin),
  1432.         );
  1433.         $dao->insert($insert);
  1434.  
  1435.         foreach($tx['in'] as $i => $txin) {
  1436.             \DB::DAO('Money_Bitcoin_Tx_In')->insert(array(
  1437.                 'Hash' => $hash,
  1438.                 'N' => $i,
  1439.                 'Prev_Out_Hash' => $txin['prev_out']['hash'],
  1440.                 'Prev_Out_N' => $txin['prev_out']['n'],
  1441.                 'scriptSig' => $txin['scriptSig'],
  1442.                 'Addr' => $txin['addr'],
  1443.             ));
  1444.         }
  1445.  
  1446.         foreach($tx['out'] as $i => $txout) {
  1447.             \DB::DAO('Money_Bitcoin_Tx_Out')->insert(array(
  1448.                 'Hash' => $hash,
  1449.                 'N' => $i,
  1450.                 'Value' => $txout['value_int'],
  1451.                 'scriptPubKey' => $txout['scriptPubKey'],
  1452.                 'Addr' => $txout['addr'],
  1453.             ));
  1454.  
  1455.             // check if one of our addrs
  1456.             $info = \DB::DAO('Money_Bitcoin_Permanent_Address')->searchOne(array('Money_Bitcoin_Permanent_Address__' => $txout['addr']));
  1457.             if (($info) && (!is_null($info->Callback))) {
  1458.                 $cb = explode('::', str_replace('/', '\\', $info->Callback));
  1459.                 call_user_func($cb, $info, $hash.':'.$i, null, \Internal\Price::spawnInt($txout['value_int'],'BTC'));
  1460.             }
  1461.             if (($info) && (!is_null($info->Ipn))) {
  1462.                 $post = array(
  1463.                     'description' => $info->Description,
  1464.                     'tx' => $hash.':'.$i,
  1465.                     'status' => 'published',
  1466.                     'amount_int' => $txout['value_int'],
  1467.                     'item' => 'BTC',
  1468.                     'addr' => \Util\Bitcoin::encode(array('version' => 0, 'hash' => $info->Money_Bitcoin_Permanent_Address__)),
  1469.                 );
  1470.                 \Scheduler::oneshotUrl($info->Ipn, $post, null, null, null, $info->User_Rest__);
  1471.             }
  1472.  
  1473.             // REDIRECT CODE 2
  1474.             if (($info) && (!is_null($info->Private_Key)) && ($info->Redirect != 'none') && ($txout['value_int'] > 10000)) {
  1475.                 // issue redirect now!
  1476.                 switch($info->Redirect) {
  1477.                     case 'wallet':
  1478.                         $wallet = \User\Wallet::byId($info->User_Wallet__);
  1479.                         $target = self::getVerboseAddr($wallet, $info->Description);
  1480.                         break;
  1481.                     case 'fixed':
  1482.                         $target = $info->Redirect_Value;
  1483.                         break;
  1484.                     case 'nulladdr':
  1485.                         $target = self::getNullAddr();
  1486.                         break;
  1487.                 }
  1488.                 $pub = \Util\Bitcoin::decode($target);
  1489.                 $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);
  1490.                 self::publishTransaction($tx);
  1491. //              self::broadcastPublished();
  1492.             }
  1493.         }
  1494.         die('OK');
  1495.     }
  1496.  
  1497.     public static function getTablesStruct() {
  1498.         return array(
  1499.             'Money_Bitcoin_Host' => array(
  1500.                 'Money_Bitcoin_Host__' => 'UUID',
  1501.                 'Name' => array('type' => 'VARCHAR', 'size' => 16, 'null' => false),
  1502.                 'IP' => array('type' => 'VARCHAR', 'size' => 39, 'null' => false, 'key' => 'UNIQUE:IP'),
  1503.                 'Address' => array('type' => 'VARCHAR', 'size' => 35, 'null' => true),
  1504.                 'Version' => array('type' => 'INT', 'size' => 10, 'unsigned' => true),
  1505.                 'Coins' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true), /* stored in smallest unit of coin */
  1506.                 'Connections' => array('type' => 'INT', 'size' => 10, 'unsigned' => true),
  1507.                 'Blocks' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true),
  1508.                 'Hashes_Per_Sec' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true),
  1509.                 'Status' => array('type' => 'ENUM', 'values' => array('up','down'), 'default' => 'down'),
  1510.                 'Last_Update' => array('type' => 'DATETIME', 'null' => true),
  1511.                 '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 */
  1512.                 'Allow_Order' => array('type' => 'ENUM', 'values' => array('Y','N'), 'default' => 'Y'), /* should we use this node for incoming payments? */
  1513.                 'Generate' => array('type' => 'ENUM', 'values' => array('Y','N'), 'default' => 'Y'),
  1514.                 'Stamp' => array('type' => 'TIMESTAMP', 'null' => false),
  1515.             ),
  1516.             'Money_Bitcoin_Tx' => array(
  1517.                 'Money_Bitcoin_Tx__' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'PRIMARY'),
  1518.                 'Data' => array('type' => 'LONGTEXT', 'null' => false),
  1519.                 'Network' => array('type' => 'VARCHAR', 'size' => 32, 'default' => 'bitcoin', 'key' => 'Network'),
  1520.                 'Size' => array('type' => 'INT', 'unsigned' => true, 'size' => 10, 'null' => false),
  1521.                 'Stamp' => array('type' => 'TIMESTAMP', 'null' => false),
  1522.             ),
  1523.             'Money_Bitcoin_Tx_In' => array(
  1524.                 'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'UNIQUE:Key'),
  1525.                 'N' => array('type' => 'INT', 'size' => 10, 'unsigned' => true, 'key' => 'UNIQUE:Key'),
  1526.                 'Prev_Out_Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false),
  1527.                 'Prev_Out_N' => array('type' => 'INT', 'size' => 10, 'unsigned' => true),
  1528.                 'CoinBase' => array('type' => 'TEXT', 'null' => true),
  1529.                 'scriptSig' => array('type' => 'TEXT', 'null' => true),
  1530.                 'Addr' => array('type' => 'CHAR', 'size' => 40, 'null' => true, 'key' => 'Addr'),
  1531.                 '_keys' => array(
  1532.                     'Prev_Out' => array('Prev_Out_Hash','Prev_Out_N'),
  1533.                 ),
  1534.             ),
  1535.             'Money_Bitcoin_Tx_Out' => array(
  1536.                 'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'UNIQUE:Key'),
  1537.                 'N' => array('type' => 'INT', 'size' => 10, 'unsigned' => true, 'key' => 'UNIQUE:Key'),
  1538.                 'Value' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true, 'null' => false),
  1539.                 'scriptPubKey' => array('type' => 'TEXT'),
  1540.                 'Addr' => array('type' => 'CHAR', 'size' => 40, 'null' => true, 'key' => 'Addr'),
  1541.             ),
  1542.             'Money_Bitcoin_Permanent_Address' => array(
  1543.                 'Money_Bitcoin_Permanent_Address__' => array('type' => 'CHAR', 'size' => 40, 'key' => 'PRIMARY'),
  1544.                 'Money_Bitcoin_Host__' => 'UUID/N',
  1545.                 'User_Wallet__' => 'UUID/N',
  1546.                 'User_Rest__' => 'UUID/N',
  1547.                 'Money_Merchant_Transaction_Payment__' => 'UUID/N',
  1548.                 'Private_Key' => array('type' => 'VARCHAR', 'size' => 255, 'null' => true),
  1549.                 'Redirect' => array('type' => 'ENUM', 'values' => array('wallet','fixed','nulladdr','none'), 'default' => 'none'), // wallet => redirect to new addr on same wallet
  1550.                 'Redirect_Value' => array('type' => 'VARCHAR', 'size' => 35, 'null' => true),
  1551.                 'Description' => array('type' => 'VARCHAR', 'size' => 255, 'null' => true),
  1552.                 'Ipn' => array('type' => 'VARCHAR', 'size' => 255, 'null' => true),
  1553.                 'Callback' => array('type' => 'VARCHAR', 'size' => 255, 'null' => true),
  1554.                 'Used' => array('type' => 'ENUM', 'values' => array('Y','N'), 'default' => 'N'),
  1555.                 'Created' => array('type' => 'DATETIME', 'null' => false),
  1556.                 'Stamp' => array('type' => 'TIMESTAMP', 'null' => false),
  1557.                 '_keys' => array(
  1558.                     'Unused_Addr_Key' => array('User_Wallet__','Used'),
  1559.                     'User_Wallet__' => ['User_Wallet__'],
  1560.                 ),
  1561.             ),
  1562.             'Money_Bitcoin_Available_Output' => array( // list available funds
  1563.                 'Money_Bitcoin_Available_Output__' => 'UUID',
  1564.                 'Money_Bitcoin_Permanent_Address__' => array('type' => 'CHAR', 'size' => 40, 'key' => 'Money_Bitcoin_Permanent_Address__'),
  1565.                 'Network' => array('type' => 'VARCHAR', 'size' => 32, 'default' => 'bitcoin', 'key' => 'Network'),
  1566.                 'Value' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true, 'null' => false),
  1567.                 'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'UNIQUE:Key'),
  1568.                 'N' => array('type' => 'INT', 'size' => 10, 'unsigned' => true, 'key' => 'UNIQUE:Key'),
  1569.                 'Block' => array('type' => 'INT', 'size' => 10, 'unsigned' => true, 'key' => 'Block'),
  1570.                 'Available' => array('type' => 'ENUM', 'values' => array('Y','N'), 'default' => 'Y'),
  1571.                 'Stamp' => array('type' => 'TIMESTAMP', 'null' => false),
  1572.             ),
  1573.             'Money_Bitcoin_Order' => array(
  1574.                 'Money_Bitcoin_Order__' => 'UUID',
  1575.                 'Order__' => 'UUID',
  1576.                 'Money_Bitcoin_Host__' => 'UUID',
  1577.                 'Address' => array('type' => 'VARCHAR', 'size' => 35, 'null' => true), /* generated only for this order */
  1578.                 'Coins' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true),
  1579.                 'Coins_NC' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true),
  1580.                 'Coins_Extra' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true),
  1581.                 'Total' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true),
  1582.                 'Created' => array('type' => 'DATETIME', 'null' => false),
  1583.                 'Expires' => array('type' => 'DATETIME', 'null' => false),
  1584.                 'Status' => array('type' => 'ENUM', 'values' => array('pending','expired','ok'), 'default' => 'pending'),
  1585.                 'Stamp' => array('type' => 'TIMESTAMP', 'null' => false),
  1586.                 '_keys' => array(
  1587.                     '@Order__' => array('Order__'),
  1588.                     '@Address' => array('Address'),
  1589.                 ),
  1590.             ),
  1591.             'Money_Bitcoin_Wallet' => array(
  1592.                 'Money_Bitcoin_Wallet__' => 'UUID',
  1593.                 'User__' => 'UUID',
  1594.                 'Money_Bitcoin_Host__' => 'UUID',
  1595.                 'Address' => array('type' => 'VARCHAR', 'size' => 35, 'null' => true),
  1596.                 'Coins' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true),
  1597.                 'Coins_NC' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true),
  1598.                 'Withdrawn_Coins' => array('type' => 'BIGINT', 'size' => 21, 'unsigned' => false),
  1599.                 'Refresh' => array('type' => 'DATETIME', 'null' => false),
  1600.                 'Stamp' => array('type' => 'TIMESTAMP', 'null' => false),
  1601.                 '_keys' => array(
  1602.                     '@User__' => array('User__'),
  1603.                     '@Address' => array('Address'),
  1604.                 ),
  1605.             ),
  1606.             'Money_Bitcoin_Node' => array(
  1607.                 'Money_Bitcoin_Node__' => NULL,
  1608.                 'IP' => array('type' => 'VARCHAR', 'size' => 15, 'null' => false, 'key' => 'UNIQUE:Unique_Host'),
  1609.                 'Port' => array('type' => 'INT', 'size' => 5, 'unsigned' => true, 'key' => 'UNIQUE:Unique_Host'),
  1610.                 'Version' => array('type' => 'INT', 'unsigned' => true, 'size' => 10),
  1611.                 'User_Agent' => array('type' => 'VARCHAR', 'size' => 256, 'null' => true),
  1612.                 'Status' => array('type' => 'ENUM', 'values' => array('up','down','unknown'), 'default' => 'unknown'),
  1613.                 'Addresses' => array('type' => 'INT', 'unsigned' => true, 'size' => 10, 'default' => 0),
  1614.                 'Last_Checked' => array('type' => 'DATETIME'),
  1615.                 'Next_Check' => array('type' => 'DATETIME'),
  1616.                 'First_Seen' => array('type' => 'DATETIME'),
  1617.                 'Last_Seen' => array('type' => 'DATETIME'),
  1618.                 'Last_Down' => array('type' => 'DATETIME', 'null' => true, 'default' => NULL),
  1619.                 'Last_Error' => array('type' => 'VARCHAR', 'size' => 32, 'null' => true),
  1620.                 'Stamp' => array('type' => 'TIMESTAMP', 'null' => false),
  1621.                 '_keys' => [
  1622.                     'Next_Check' => ['Next_Check'],
  1623.                     'Status' => ['Status'],
  1624.                 ],
  1625.             ),
  1626.             'Money_Bitcoin_Pending_Tx' => array(
  1627.                 'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'PRIMARY'),
  1628.                 'Network' => array('type' => 'VARCHAR', 'size' => 32, 'default' => 'bitcoin', 'key' => 'Network'),
  1629.                 'Blob' => array('type' => 'LONGTEXT'),
  1630.                 'Eligius' => array('type' => 'ENUM', 'values' => array('Y','P','N'), 'default' => 'N'),
  1631.                 'Created' => array('type' => 'DATETIME'),
  1632.                 'Input_Total' => ['type' => 'BIGINT', 'size' => 20, 'unsigned' => true, 'null' => true],
  1633.                 'Last_Broadcast' => array('type' => 'DATETIME'),
  1634.                 'Last_Result' => ['type' => 'VARCHAR', 'size' => 128, 'null' => true],
  1635.                 'Stamp' => array('type' => 'TIMESTAMP', 'null' => false),
  1636.             ),
  1637.             'Money_Bitcoin_Block' => array(
  1638.                 'Money_Bitcoin_Block__' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'PRIMARY'),
  1639.                 'Parent_Money_Bitcoin_Block__' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'Parent_Money_Bitcoin_Block__'),
  1640.                 'Depth' => array('type' => 'BIGINT', 'size' => 20, 'null' => false, 'key' => 'Depth'),
  1641.                 'Network' => array('type' => 'VARCHAR', 'size' => 32, 'default' => 'bitcoin', 'key' => 'Network'),
  1642.                 'Version' => array('type' => 'INT', 'size' => 10, 'unsigned' => true),
  1643.                 'Mrkl_Root' => array('type' => 'CHAR', 'size' => 64, 'null' => false),
  1644.                 'Time' => array('type' => 'DATETIME'),
  1645.                 'Bits' => array('type' => 'INT', 'size' => 10, 'unsigned' => true),
  1646.                 'Nonce' => array('type' => 'INT', 'size' => 10, 'unsigned' => true),
  1647.                 'Size' => array('type' => 'INT', 'size' => 10, 'unsigned' => true),
  1648.                 'Status' => array('type' => 'ENUM', 'values' => array('pending','confirmed','dropped'), 'default' => 'confirmed', 'null' => false),
  1649.             ),
  1650.             'Money_Bitcoin_Process_Tx_Out' => [
  1651.                 'Money_Bitcoin_Process_Tx_Out__' => 'UUID',
  1652.                 'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'UNIQUE:Key'),
  1653.                 'N' => array('type' => 'INT', 'size' => 10, 'unsigned' => true, 'key' => 'UNIQUE:Key'),
  1654.                 'Value' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true, 'null' => false),
  1655.                 'scriptPubKey' => array('type' => 'TEXT'),
  1656.                 'Addr' => array('type' => 'CHAR', 'size' => 40, 'null' => false, 'key' => 'Addr'),
  1657.                 'Trigger' => array('type' => 'ENUM', 'values' => array('new','executed','nil'), 'default' => 'new'),
  1658.                 '_keys' => array(
  1659.                     'Trigger' => array('Trigger'),
  1660.                 ),
  1661.             ],
  1662.             'Money_Bitcoin_Block_Tx' => array(
  1663.                 'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'UNIQUE:Hash'),
  1664.                 'Block' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'Block'),
  1665.                 'Version' => array('type' => 'INT', 'size' => 10, 'unsigned' => true),
  1666.                 'Lock_Time' => array('type' => 'INT', 'size' => 10, 'unsigned' => true),
  1667.                 'Size' => array('type' => 'INT', 'size' => 10, 'unsigned' => true),
  1668.             ),
  1669. /*          'Money_Bitcoin_Block_Tx_In' => array(
  1670.                 'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'UNIQUE:Key'),
  1671.                 'N' => array('type' => 'INT', 'size' => 10, 'unsigned' => true, 'key' => 'UNIQUE:Key'),
  1672.                 'Prev_Out_Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false),
  1673.                 'Prev_Out_N' => array('type' => 'INT', 'size' => 10, 'unsigned' => true),
  1674.                 'CoinBase' => array('type' => 'TEXT', 'null' => true),
  1675.                 'scriptSig' => array('type' => 'TEXT', 'null' => true),
  1676.                 'Addr' => array('type' => 'CHAR', 'size' => 40, 'null' => true, 'key' => 'Addr'),
  1677.                 '_keys' => array(
  1678.                     'Prev_Out' => array('Prev_Out_Hash','Prev_Out_N'),
  1679.                 ),
  1680.             ),*/
  1681.             'Money_Bitcoin_Block_Tx_Out' => array(
  1682.                 'Hash' => array('type' => 'CHAR', 'size' => 64, 'null' => false, 'key' => 'UNIQUE:Key'),
  1683.                 'N' => array('type' => 'INT', 'size' => 10, 'unsigned' => true, 'key' => 'UNIQUE:Key'),
  1684.                 'Value' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true, 'null' => false),
  1685.                 'scriptPubKey' => array('type' => 'TEXT'),
  1686.                 'Addr' => array('type' => 'CHAR', 'size' => 40, 'null' => false, 'key' => 'Addr'),
  1687.                 'Claimed' => array('type' => 'ENUM', 'values' => array('Y','N'), 'default' => 'N'),
  1688.                 'Trigger' => array('type' => 'ENUM', 'values' => array('new','executed','nil'), 'default' => 'new'),
  1689.                 '_keys' => array(
  1690.                     'Trigger' => array('Trigger'),
  1691.                 ),
  1692.             ),
  1693. /*          'Money_Bitcoin_Block_Addr' => array(
  1694.                 'Addr' => array('type' => 'CHAR', 'size' => 40, 'null' => false, 'key' => 'PRIMARY'),
  1695.                 'Network' => array('type' => 'VARCHAR', 'size' => 32, 'default' => 'bitcoin', 'key' => 'Network'),
  1696.                 'Pubkey' => array('type' => 'CHAR', 'size' => 130, 'null' => true),
  1697.                 'Balance' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true, 'null' => false),
  1698.                 'Watch' => array('type' => 'VARCHAR', 'size' => 128, 'null' => true, 'default' => NULL, 'key' => 'Watch'),
  1699.                 'Taint' => array('type' => 'BIGINT', 'size' => 20, 'unsigned' => true, 'null' => true, 'default' => NULL),
  1700.                 'Clean' => array('type' => 'VARCHAR', 'size' => 64, 'null' => true, 'default' => NULL, 'key' => 'Clean'),
  1701.             ), */
  1702.             'Money_Bitcoin_Vanity' => array(
  1703.                 'Money_Bitcoin_Vanity__' => array('type' => 'VARCHAR', 'size' => 35, 'null' => false, 'key' => 'PRIMARY'),
  1704.                 'Private_Key' => array('type' => 'VARCHAR', 'size' => 255, 'null' => false),
  1705.             ),
  1706.         );
  1707.     }
  1708. }
  1709.  
  1710. \Scheduler::schedule('MoneyBitcoinUpdate', '10min', 'Money/Bitcoin::update');
  1711. \Scheduler::schedule('MoneyBitcoinCheckOrders', '5min', 'Money/Bitcoin::checkOrders');
  1712. \Scheduler::schedule('MoneyBitcoinGetRate', array('daily', '5i'), 'Money/Bitcoin::getRate');
  1713. \Scheduler::schedule('MoneyBitcoinCheckNodes', '10min', 'Money/Bitcoin::checkNodes');
  1714. \Scheduler::schedule('MoneyBitcoinImportBlocks', '1min', 'Money/Bitcoin::importBlocks');
  1715. \Scheduler::schedule('MoneyBitcoinAddrTriggers', '1min', 'Money/Bitcoin::runAddrTriggers');
  1716. \Scheduler::schedule('MoneyBitcoinBroadcastTxs', '1min', 'Money/Bitcoin::broadcastTransactions');
  1717. \Scheduler::schedule('MoneyBitcoinMergeSmallOutputs', '10min', 'Money/Bitcoin::mergeSmallOutputs');
  1718. \Scheduler::schedule('MoneyBitcoinSplitBigOutputs', '10min', 'Money/Bitcoin::splitBigOutputs');
  1719. \DB::i()->validateStruct(Bitcoin::getTablesStruct());
Add Comment
Please, Sign In to add comment