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