コード例 #1
0
 public function getValidatorInstance()
 {
     $validator = parent::getValidatorInstance();
     $validator->after(function () use($validator) {
         // validate destinations
         $destinations = $this->json('destinations');
         if ($destinations) {
             if (is_array($destinations)) {
                 $offset = 0;
                 foreach ($destinations as $destination) {
                     if (!isset($destination['address']) or !isset($destination['amount'])) {
                         $validator->errors()->add('destinations', 'Missing address or amount for destination ' . ($offset + 1) . '.');
                         continue;
                     }
                     if (!AddressValidator::isValid($destination['address'])) {
                         $validator->errors()->add('destinations', 'The address for destination ' . ($offset + 1) . ' was invalid.');
                     }
                     if (!is_numeric($destination['amount']) or CurrencyUtil::valueToSatoshis($destination['amount']) <= 0) {
                         $validator->errors()->add('destinations', 'The amount for destination ' . ($offset + 1) . ' was invalid.');
                     }
                     ++$offset;
                 }
             } else {
                 $validator->errors()->add('destinations', 'The destinations were invalid.');
             }
         }
     });
     return $validator;
 }
コード例 #2
0
ファイル: ClientGUIDTest.php プロジェクト: CryptArc/xchain
 public function testMultiplePaymentAddressSendsWithSameClientGUID()
 {
     // mock the xcp sender
     $mock_calls = app('CounterpartySenderMockBuilder')->installMockCounterpartySenderDependencies($this->app, $this);
     $user = app('\\UserHelper')->createSampleUser();
     $payment_address = app('\\PaymentAddressHelper')->createSamplePaymentAddress($user);
     // create a send with a client guid
     $api_tester = $this->getAPITester();
     $posted_vars = $this->sendHelper()->samplePostVars();
     $posted_vars['requestId'] = 'request001';
     $expected_created_resource = ['id' => '{{response.id}}', 'destination' => '{{response.destination}}', 'destinations' => '', 'asset' => 'TOKENLY', 'sweep' => '{{response.sweep}}', 'quantity' => '{{response.quantity}}', 'txid' => '{{response.txid}}', 'requestId' => 'request001'];
     $loaded_resource_model = $api_tester->testAddResource($posted_vars, $expected_created_resource, $payment_address['uuid']);
     // get_asset_info followed by the send
     PHPUnit::assertCount(1, $mock_calls['btcd']);
     // validate that a mock send was triggered
     $send_details = app('TransactionComposerHelper')->parseCounterpartyTransaction($mock_calls['btcd'][0]['args'][0]);
     PHPUnit::assertEquals('1JztLWos5K7LsqW5E78EASgiVBaCe6f7cD', $send_details['destination']);
     PHPUnit::assertEquals(CurrencyUtil::valueToSatoshis(100), $send_details['quantity']);
     PHPUnit::assertEquals('TOKENLY', $send_details['asset']);
     // try the send again with the same request_id
     $expected_resource = $loaded_resource_model;
     $posted_vars = $this->sendHelper()->samplePostVars();
     $posted_vars['requestId'] = 'request001';
     $loaded_resource_model_2 = $api_tester->testAddResource($posted_vars, $expected_created_resource, $payment_address['uuid']);
     // does not send again
     PHPUnit::assertCount(1, $mock_calls['btcd']);
     // second send resource is the same as the first
     PHPUnit::assertEquals($loaded_resource_model, $loaded_resource_model_2);
 }
コード例 #3
0
 protected function buildTransaction($send_amount, $source, $destinations, $sample_txid_offset = 100, $filename = 'sample_btc_parsed_01.json')
 {
     $tx_helper = app('SampleTransactionsHelper');
     $parsed_tx = $tx_helper->loadSampleTransaction($filename, ['txid' => str_repeat('4', 60) . sprintf('%04d', $sample_txid_offset)]);
     // fix vin
     $parsed_tx['bitcoinTx']['vin'][0]['addr'] = $source;
     $parsed_tx['bitcoinTx']['vin'][0]['value'] = $send_amount;
     $parsed_tx['bitcoinTx']['vin'][0]['valueSat'] = CurrencyUtil::valueToSatoshis($send_amount);
     $parsed_tx['sources'] = [$destinations[0][0]];
     $tx_destinations = [];
     $values = [];
     $total_sent = 0;
     foreach ($destinations as $destination_pair) {
         $dest_address = $destination_pair[0];
         $dest_amount = $destination_pair[1];
         $total_sent += $dest_amount;
         if ($dest_address == $source) {
             continue;
         }
         if (!isset($values[$dest_address])) {
             $values[$dest_address] = 0;
         }
         $values[$dest_address] += $dest_amount;
     }
     $parsed_tx['destinations'] = $tx_destinations;
     $parsed_tx['values'] = $values;
     $parsed_tx['fees'] = $send_amount - $total_sent;
     $parsed_tx['bitcoinTx']['fees'] = $send_amount - $total_sent;
     return $parsed_tx;
 }
コード例 #4
0
 public function addUTXOToPaymentAddress($float_btc_balance, $payment_address, $account_name = 'default', $txid = 'SAMPLE01')
 {
     // add UTXOs for BTC balance
     $sample_txos = [];
     if ($float_btc_balance) {
         $txid = $this->txo_helper->nextTXID();
         $sample_txos[] = $this->txo_helper->createSampleTXO($payment_address, ['txid' => $txid, 'amount' => CurrencyUtil::valueToSatoshis($float_btc_balance), 'n' => 0]);
     }
     return $sample_txos;
 }
コード例 #5
0
ファイル: Quote.php プロジェクト: tokenly/crypto-quote-client
 protected function buildValues($name, $base, $target, $ask, $bid, $last, $timestamp = null)
 {
     $out = [];
     $out['name'] = $name;
     $out['base'] = $base;
     $out['target'] = $target;
     $out['ask'] = floatval($ask);
     $out['askSat'] = CurrencyUtil::valueToSatoshis($ask);
     $out['bid'] = floatval($bid);
     $out['bidSat'] = CurrencyUtil::valueToSatoshis($bid);
     $out['last'] = floatval($last);
     $out['lastSat'] = CurrencyUtil::valueToSatoshis($last);
     $out['timestamp'] = $timestamp === null ? time() : intval($timestamp);
     return $out;
 }
コード例 #6
0
ファイル: SampleSendsHelper.php プロジェクト: CryptArc/xchain
 public function sampleVars($override_vars = [])
 {
     // apply sample post vars
     $override_vars = $this->samplePostVars($override_vars);
     $vars = array_merge(['txid' => 'SAMPLETXID000000000000000000000000000000000000000000000000000001'], $override_vars);
     if (isset($vars['quantity'])) {
         $vars['quantity_sat'] = CurrencyUtil::valueToSatoshis($vars['quantity']);
         unset($vars['quantity']);
     }
     if (isset($vars['requestId'])) {
         $vars['request_id'] = CurrencyUtil::valueToSatoshis($vars['requestId']);
         unset($vars['requestId']);
     }
     return $vars;
 }
コード例 #7
0
 protected function enhanceVin($vin, $n)
 {
     if (!isset($vin['scriptSig']) or !isset($vin['scriptSig']['hex'])) {
         return $vin;
     }
     // add n
     $vin['n'] = $n;
     // extract the address
     $address = $this->addressFromScriptHex($vin['scriptSig']['hex']);
     $vin['addr'] = $address;
     // build value
     $value = $this->buildValueFromTXO($vin['txid'], $vin['vout']);
     $vin['value'] = $value;
     $vin['valueSat'] = CurrencyUtil::valueToSatoshis($value);
     return $vin;
 }
コード例 #8
0
 public function testCurrentUtilsConversions()
 {
     // float value to satoshis
     PHPUnit::assertEquals(12300000000, CurrencyUtil::valueToSatoshis(123));
     PHPUnit::assertEquals(2100000000000000, CurrencyUtil::valueToSatoshis(21000000));
     PHPUnit::assertEquals(12311111119, CurrencyUtil::valueToSatoshis(123.111111189));
     // satoshis to float value
     PHPUnit::assertEquals(123, CurrencyUtil::satoshisToValue(12300000000));
     PHPUnit::assertEquals(21000000, CurrencyUtil::satoshisToValue(2100000000000000));
     PHPUnit::assertEquals(123.11111119, CurrencyUtil::satoshisToValue(12311111119));
     // satoshisToFormattedString
     PHPUnit::assertEquals('123', CurrencyUtil::satoshisToFormattedString(12300000000));
     PHPUnit::assertEquals('123.4', CurrencyUtil::satoshisToFormattedString(12340000000));
     PHPUnit::assertEquals('1,235.6', CurrencyUtil::satoshisToFormattedString(123560000000));
     // valueToFormattedString
     PHPUnit::assertEquals('1,234.5', CurrencyUtil::valueToFormattedString(1234.5));
 }
コード例 #9
0
 /**
  * Get all balances for an address
  *
  * @param  int  $id
  * @return Response
  */
 public function show(Client $xcpd_client, BitcoinPayer $bitcoin_payer, Cache $asset_info_cache, $address)
 {
     if (!AddressValidator::isValid($address)) {
         $message = "The address {$address} was not valid";
         EventLog::logError('error.getBalance', ['address' => $address, 'message' => $message]);
         return new JsonResponse(['message' => $message], 500);
     }
     $balances = $xcpd_client->get_balances(['filters' => ['field' => 'address', 'op' => '==', 'value' => $address]]);
     // and get BTC balance too
     $btc_float_balance = $bitcoin_payer->getBalance($address);
     $balances = array_merge([['asset' => 'BTC', 'quantity' => $btc_float_balance]], $balances);
     $out = ['balances' => [], 'balancesSat' => []];
     foreach ($balances as $balance) {
         $asset_name = $balance['asset'];
         if ($asset_name == 'BTC') {
             // BTC quantity is a float
             $quantity_float = floatval($balance['quantity']);
             $quantity_sat = CurrencyUtil::valueToSatoshis($balance['quantity']);
         } else {
             // determine quantity based on asset info
             $is_divisible = $asset_info_cache->isDivisible($asset_name);
             if ($is_divisible) {
                 $quantity_float = CurrencyUtil::satoshisToValue($balance['quantity']);
                 $quantity_sat = intval($balance['quantity']);
             } else {
                 // non-divisible assets don't use satoshis
                 $quantity_float = floatval($balance['quantity']);
                 $quantity_sat = CurrencyUtil::valueToSatoshis($balance['quantity']);
             }
         }
         $out['balances'][$asset_name] = $quantity_float;
         $out['balancesSat'][$asset_name] = $quantity_sat;
     }
     ksort($out['balances']);
     ksort($out['balancesSat']);
     return json_encode($out);
 }
コード例 #10
0
ファイル: SendAPITest.php プロジェクト: CryptArc/xchain
 public function testAPIAddMultisend()
 {
     // mock the xcp sender
     $mock_calls = $this->app->make('CounterpartySenderMockBuilder')->installMockCounterpartySenderDependencies($this->app, $this);
     $user = $this->app->make('\\UserHelper')->createSampleUser();
     $payment_address = $this->app->make('\\PaymentAddressHelper')->createSamplePaymentAddress($user);
     $api_tester = $this->getAPITester('/api/v1/multisends');
     $posted_vars = $this->sendHelper()->sampleMultisendPostVars();
     $expected_created_resource = ['id' => '{{response.id}}', 'destination' => '', 'destinations' => '{{response.destinations}}', 'asset' => 'BTC', 'txid' => '{{response.txid}}', 'requestId' => '{{response.requestId}}', 'sweep' => '{{response.sweep}}', 'quantity' => 0.006];
     $api_response = $api_tester->testAddResource($posted_vars, $expected_created_resource, $payment_address['uuid']);
     // validate the send details
     $transaction_composer_helper = app('TransactionComposerHelper');
     PHPUnit::assertCount(1, $mock_calls['btcd']);
     $send_details = $transaction_composer_helper->parseBTCTransaction($mock_calls['btcd'][0]['args'][0]);
     // primary send
     PHPUnit::assertEquals('1ATEST111XXXXXXXXXXXXXXXXXXXXwLHDB', $send_details['destination']);
     PHPUnit::assertEquals(CurrencyUtil::valueToSatoshis(0.001), $send_details['btc_amount']);
     // other change...
     PHPUnit::assertEquals('1ATEST222XXXXXXXXXXXXXXXXXXXYzLVeV', $send_details['change'][0][0]);
     PHPUnit::assertEquals(CurrencyUtil::valueToSatoshis(0.002), $send_details['change'][0][1]);
     PHPUnit::assertEquals('1ATEST333XXXXXXXXXXXXXXXXXXXatH8WE', $send_details['change'][1][0]);
     PHPUnit::assertEquals(CurrencyUtil::valueToSatoshis(0.003), $send_details['change'][1][1]);
     PHPUnit::assertEquals($payment_address['address'], $send_details['change'][2][0]);
     PHPUnit::assertEquals(CurrencyUtil::valueToSatoshis(1 - 0.006 - 0.0001), $send_details['change'][2][1]);
 }
コード例 #11
0
 protected function debugDumpUTXOs($utxos)
 {
     $out = '';
     $out .= 'total utxos: ' . count($utxos) . "\n";
     foreach ($utxos as $utxo) {
         $out .= '  ' . $utxo['txid'] . ':' . $utxo['n'] . ' (' . CurrencyUtil::satoshisToValue($utxo['amount']) . ')' . "\n";
     }
     return rtrim($out);
 }
コード例 #12
0
 protected function assembleAccountBalancesWithTXID($results, $in_satoshis = false)
 {
     $sums = array_fill_keys(LedgerEntry::allTypeStrings(), []);
     foreach ($results as $result) {
         $txid = $result['txid'];
         if (!$txid) {
             $txid = 'none';
         }
         if ($in_satoshis) {
             $sums[LedgerEntry::typeIntegerToString($result['type'])][$txid][$result['asset']] = $result['total_amount'];
         } else {
             $sums[LedgerEntry::typeIntegerToString($result['type'])][$txid][$result['asset']] = CurrencyUtil::satoshisToValue($result['total_amount']);
         }
     }
     return $sums;
 }
コード例 #13
0
 protected function buildDifferences($xchain_map, $daemon_map)
 {
     // $items_to_add = [];
     // $items_to_delete = [];
     $differences = [];
     $any_differences = false;
     foreach ($daemon_map as $daemon_map_key => $dum) {
         if (!isset($xchain_map[$daemon_map_key]) and $daemon_map[$daemon_map_key] != 0) {
             // $items_to_add[] = [$daemon_map_key => $daemon_map[$daemon_map_key]];
             $differences[$daemon_map_key] = ['xchain' => '[NULL]', 'daemon' => $daemon_map[$daemon_map_key]];
             $any_differences = true;
         }
     }
     foreach ($xchain_map as $xchain_map_key => $dum) {
         if (!isset($daemon_map[$xchain_map_key])) {
             // $items_to_delete[] = $xchain_map_key;
             $differences[$xchain_map_key] = ['xchain' => $xchain_map[$xchain_map_key], 'daemon' => '[NULL]'];
             $any_differences = true;
         } else {
             if (CurrencyUtil::valueToFormattedString($daemon_map[$xchain_map_key]) != CurrencyUtil::valueToFormattedString($xchain_map[$xchain_map_key])) {
                 $differences[$xchain_map_key] = ['xchain' => $xchain_map[$xchain_map_key], 'daemon' => $daemon_map[$xchain_map_key]];
                 $any_differences = true;
             }
         }
     }
     // return ['any' => $any_differences, 'add' => $items_to_add, 'updates' => $differences, 'delete' => $items_to_delete];
     return ['any' => $any_differences, 'differences' => $differences];
 }
コード例 #14
0
 protected function showAccount(Account $account, $show_ledger = false)
 {
     $ledger = app('App\\Repositories\\LedgerEntryRepository');
     $all_account_balances = $ledger->accountBalancesByAsset($account, null);
     $sep = str_repeat('-', 60) . "\n";
     $out = '';
     $out .= "{$sep}{$account['name']} ({$account['id']}, {$account['uuid']})\n{$sep}";
     if ($show_ledger) {
         $out .= "\n";
         $rows = [];
         $all_entries = $ledger->findByAccount($account);
         foreach ($all_entries as $entry) {
             $row = [];
             $row['date'] = $entry['created_at']->setTimezone('America/Chicago')->format('Y-m-d H:i:s T');
             $row['amount'] = CurrencyUtil::satoshisToFormattedString($entry['amount']);
             $row['asset'] = $entry['asset'];
             $row['type'] = LedgerEntry::typeIntegerToString($entry['type']);
             $row['txid'] = $entry['txid'];
             $rows[] = $row;
         }
         $renderer = new ArrayToTextTable($rows);
         $renderer->showHeaders(true);
         $out .= $renderer->render(true) . "\n";
     }
     // $out .= "BALANCES\n";
     $out .= "\n";
     foreach (LedgerEntry::allTypeStrings() as $type_string) {
         $out .= "{$type_string}:\n";
         if (isset($all_account_balances[$type_string]) and $all_account_balances[$type_string]) {
             foreach ($all_account_balances[$type_string] as $asset => $balance) {
                 $out .= "  {$asset}: " . CurrencyUtil::valueToFormattedString($balance) . "\n";
             }
         } else {
             $out .= "  [empty]\n";
         }
     }
     $out .= "\n{$sep}\n";
     return $out;
 }
コード例 #15
0
ファイル: SendController.php プロジェクト: CryptArc/xchain
 protected function executeSend(APIControllerHelper $helper, Request $request, PaymentAddressRepository $payment_address_respository, SendRepository $send_respository, PaymentAddressSender $address_sender, Guard $auth, APICallRepository $api_call_repository, $id)
 {
     $user = $auth->getUser();
     if (!$user) {
         throw new Exception("User not found", 1);
     }
     // get the address
     $payment_address = $payment_address_respository->findByUuid($id);
     if (!$payment_address) {
         return new JsonResponse(['message' => 'address not found'], 404);
     }
     // make sure this address belongs to this user
     if ($payment_address['user_id'] != $user['id']) {
         return new JsonResponse(['message' => 'Not authorized to send from this address'], 403);
     }
     // attributes
     $request_attributes = $request->only(array_keys($request->rules()));
     // determine if this is a multisend
     $is_multisend = (isset($request_attributes['destinations']) and $request_attributes['destinations']);
     $is_regular_send = !$is_multisend;
     // normalize destinations
     $destinations = $is_multisend ? $this->normalizeDestinations($request_attributes['destinations']) : '';
     $destination = $is_regular_send ? $request_attributes['destination'] : '';
     // determine variables
     $quantity_sat = CurrencyUtil::valueToSatoshis($is_multisend ? $this->sumMultisendQuantity($destinations) : $request_attributes['quantity']);
     $asset = $is_regular_send ? $request_attributes['asset'] : 'BTC';
     $is_sweep = isset($request_attributes['sweep']) ? !!$request_attributes['sweep'] : false;
     $float_fee = isset($request_attributes['fee']) ? $request_attributes['fee'] : PaymentAddressSender::DEFAULT_FEE;
     $dust_size = isset($request_attributes['dust_size']) ? $request_attributes['dust_size'] : PaymentAddressSender::DEFAULT_REGULAR_DUST_SIZE;
     $request_id = isset($request_attributes['requestId']) ? $request_attributes['requestId'] : Uuid::uuid4()->toString();
     // create attibutes
     $create_attributes = [];
     $create_attributes['user_id'] = $user['id'];
     $create_attributes['payment_address_id'] = $payment_address['id'];
     $create_attributes['destination'] = $destination;
     $create_attributes['quantity_sat'] = $quantity_sat;
     $create_attributes['asset'] = $asset;
     $create_attributes['is_sweep'] = $is_sweep;
     $create_attributes['fee'] = $float_fee;
     $create_attributes['dust_size'] = $dust_size;
     // for multisends
     $create_attributes['destinations'] = $destinations;
     // the transaction must be committed before the lock is release and not after
     //   therefore we must release the lock after this closure completes
     $lock_must_be_released = false;
     $lock_must_be_released_with_delay = false;
     // create a send and lock it immediately
     $send_result = $send_respository->executeWithNewLockedSendByRequestID($request_id, $create_attributes, function ($locked_send) use($request_attributes, $create_attributes, $payment_address, $user, $helper, $send_respository, $address_sender, $api_call_repository, $request_id, $is_multisend, $is_regular_send, $quantity_sat, $asset, $destination, $destinations, $is_sweep, $float_fee, $dust_size, &$lock_must_be_released, &$lock_must_be_released_with_delay) {
         $api_call = $api_call_repository->create(['user_id' => $user['id'], 'details' => ['method' => 'api/v1/sends/' . $payment_address['uuid'], 'args' => $request_attributes]]);
         // if a send already exists by this request_id, just return it
         if (isset($locked_send['txid']) && strlen($locked_send['txid'])) {
             EventLog::log('send.alreadyFound', $locked_send);
             return $helper->transformResourceForOutput($locked_send);
         }
         $float_quantity = CurrencyUtil::satoshisToValue($quantity_sat);
         // send
         EventLog::log('send.requested', array_merge($request_attributes, $create_attributes));
         if ($is_sweep) {
             try {
                 // get lock
                 $lock_acquired = AccountHandler::acquirePaymentAddressLock($payment_address);
                 if ($lock_acquired) {
                     $lock_must_be_released = true;
                 }
                 list($txid, $float_balance_sent) = $address_sender->sweepAllAssets($payment_address, $request_attributes['destination'], $float_fee);
                 $quantity_sat_sent = CurrencyUtil::valueToSatoshis($float_balance_sent);
                 // clear all balances from all accounts
                 AccountHandler::zeroAllBalances($payment_address, $api_call);
                 // release the account lock with a slight delay
                 if ($lock_acquired) {
                     $lock_must_be_released_with_delay = true;
                 }
             } catch (PaymentException $e) {
                 EventLog::logError('error.sweep', $e);
                 return new JsonResponse(['message' => $e->getMessage()], 500);
             } catch (Exception $e) {
                 EventLog::logError('error.sweep', $e);
                 return new JsonResponse(['message' => 'Unable to complete this request'], 500);
             }
         } else {
             try {
                 // get the account
                 $account_name = (isset($request_attributes['account']) and strlen($request_attributes['account'])) ? $request_attributes['account'] : 'default';
                 $account = AccountHandler::getAccount($payment_address, $account_name);
                 if (!$account) {
                     EventLog::logError('error.send.accountMissing', ['address_id' => $payment_address['id'], 'account' => $account_name]);
                     return new JsonResponse(['message' => "This account did not exist."], 404);
                 }
                 // Log::debug("\$account=".json_encode($account, 192));
                 // get lock
                 $lock_acquired = AccountHandler::acquirePaymentAddressLock($payment_address);
                 if ($lock_acquired) {
                     $lock_must_be_released = true;
                 }
                 // whether to spend unconfirmed balances
                 $allow_unconfirmed = isset($request_attributes['unconfirmed']) ? $request_attributes['unconfirmed'] : false;
                 // Log::debug("\$allow_unconfirmed=".json_encode($allow_unconfirmed, 192));
                 // validate that the funds are available
                 if ($allow_unconfirmed) {
                     $has_enough_funds = AccountHandler::accountHasSufficientFunds($account, $float_quantity, $asset, $float_fee, $dust_size);
                 } else {
                     $has_enough_funds = AccountHandler::accountHasSufficientConfirmedFunds($account, $float_quantity, $asset, $float_fee, $dust_size);
                 }
                 if (!$has_enough_funds) {
                     EventLog::logError('error.send.insufficient', ['address_id' => $payment_address['id'], 'account' => $account_name, 'quantity' => $float_quantity, 'asset' => $asset]);
                     return new JsonResponse(['message' => "This account does not have sufficient" . ($allow_unconfirmed ? '' : ' confirmed') . " funds available."], 400);
                 }
                 // send the funds
                 EventLog::log('send.begin', ['request_id' => $request_id, 'address_id' => $payment_address['id'], 'account' => $account_name, 'quantity' => $float_quantity, 'asset' => $asset, 'destination' => $is_multisend ? $destinations : $destination]);
                 $txid = $address_sender->sendByRequestID($request_id, $payment_address, $is_multisend ? $destinations : $destination, $float_quantity, $asset, $float_fee, $dust_size);
                 EventLog::log('send.complete', ['txid' => $txid, 'request_id' => $request_id, 'address_id' => $payment_address['id'], 'account' => $account_name, 'quantity' => $float_quantity, 'asset' => $asset, 'destination' => $is_multisend ? $destinations : $destination]);
                 // tag funds as sent with the txid
                 if ($allow_unconfirmed) {
                     AccountHandler::markAccountFundsAsSending($account, $float_quantity, $asset, $float_fee, $dust_size, $txid);
                 } else {
                     AccountHandler::markConfirmedAccountFundsAsSending($account, $float_quantity, $asset, $float_fee, $dust_size, $txid);
                     // Log::debug("After marking confirmed funds as sent, all accounts for ${account['name']}: ".json_encode(app('App\Repositories\LedgerEntryRepository')->accountBalancesByAsset($account, null), 192));
                     // Log::debug("After marking confirmed funds as sent, all accounts for default: ".json_encode(app('App\Repositories\LedgerEntryRepository')->accountBalancesByAsset(AccountHandler::getAccount($payment_address), null), 192));
                 }
                 // release the account lock
                 if ($lock_acquired) {
                     $lock_must_be_released_with_delay = true;
                 }
             } catch (AccountException $e) {
                 EventLog::logError('error.pay', $e);
                 return new JsonResponse(['message' => $e->getMessage(), 'errorName' => $e->getErrorName()], $e->getStatusCode());
             } catch (PaymentException $e) {
                 EventLog::logError('error.pay', $e);
                 return new JsonResponse(['message' => $e->getMessage()], 500);
             } catch (Exception $e) {
                 EventLog::logError('error.pay', $e);
                 return new JsonResponse(['message' => 'Unable to complete this request'], 500);
             }
         }
         $attributes = [];
         $attributes['sent'] = time();
         $attributes['txid'] = $txid;
         EventLog::log('send.complete', $attributes);
         // update and send response
         $send_respository->update($locked_send, $attributes);
         return $helper->buildJSONResponse($locked_send->serializeForAPI());
     }, self::SEND_LOCK_TIMEOUT);
     // make sure to release the lock
     if ($lock_must_be_released_with_delay) {
         $this->releasePaymentAddressLockWithDelay($payment_address);
     } else {
         if ($lock_must_be_released) {
             AccountHandler::releasePaymentAddressLock($payment_address);
         }
     }
     return $send_result;
 }
コード例 #16
0
ファイル: PrimeController.php プロジェクト: CryptArc/xchain
 /**
  * Prime the address with UTXOs of a certain size
  *   only if needed
  * 
  *
  * @return Response
  */
 public function primeAddress($address_uuid, Guard $auth, Request $request, APIControllerHelper $helper, PaymentAddressRepository $payment_address_repository, TXORepository $txo_repository, TXOChooser $txo_chooser, PaymentAddressSender $payment_address_sender)
 {
     try {
         $user = $auth->getUser();
         if (!$user) {
             throw new Exception("User not found", 1);
         }
         $payment_address = $helper->requireResourceOwnedByUser($address_uuid, $user, $payment_address_repository);
         $size = floatval($request->input('size'));
         if ($size <= 0) {
             throw new Exception("Invalid size", 400);
         }
         $size_satoshis = CurrencyUtil::valueToSatoshis($size);
         $desired_prime_count = floatval($request->input('count'));
         if ($desired_prime_count <= 0) {
             throw new Exception("Invalid count", 400);
         }
         $fee = $request->input('fee');
         if ($fee !== null) {
             $fee = floatval($fee);
             if ($fee <= 0) {
                 throw new Exception("Invalid fee", 400);
             }
         }
         // get the UTXOs
         //   [TXO::UNCONFIRMED, TXO::CONFIRMED]
         $txos = $this->filterGreenOrConfirmedUTXOs($txo_repository->findByPaymentAddress($payment_address, null, true));
         // count the number that match the size
         $current_primed_count = 0;
         foreach ($txos as $txo) {
             if ($txo['amount'] == $size_satoshis) {
                 ++$current_primed_count;
             }
         }
         $txid = null;
         $new_primed_count = $current_primed_count;
         if ($desired_prime_count > $current_primed_count) {
             // create a new priming transaction...
             $desired_new_primes_count_to_create = $desired_prime_count - $current_primed_count;
             $actual_new_primes_count_to_create = $this->findMaximumNewPrimeCountTXOs($txo_chooser, $payment_address, $desired_new_primes_count_to_create, $size, $fee);
             if ($actual_new_primes_count_to_create > 0) {
                 $txid = $this->sendPrimingTransaction($payment_address_sender, $payment_address, $size, $actual_new_primes_count_to_create, $fee);
                 $new_primed_count = $current_primed_count + $actual_new_primes_count_to_create;
             }
         }
         $output = ['oldPrimedCount' => $current_primed_count, 'newPrimedCount' => $new_primed_count, 'txid' => $txid, 'primed' => $txid ? true : false];
         return $helper->buildJSONResponse($output);
     } catch (Exception $e) {
         if ($e->getCode() >= 400 and $e->getCode() < 500) {
             throw new HttpResponseException(new JsonResponse(['errors' => [$e->getMessage()]], 400));
         }
         throw $e;
     }
 }
コード例 #17
0
ファイル: PrimeAPITest.php プロジェクト: CryptArc/xchain
 public function testAPICreatePrimes_2()
 {
     $this->app->make('CounterpartySenderMockBuilder')->installMockCounterpartySenderDependencies($this->app, $this);
     // setup
     list($payment_address, $sample_txos) = $this->setupPrimes();
     // api tester
     $api_tester = $this->getAPITester();
     $create_primes_vars = ['size' => CurrencyUtil::satoshisToValue(5430), 'count' => 2];
     $response = $api_tester->callAPIWithAuthentication('POST', '/api/v1/primes/' . $payment_address['uuid'], $create_primes_vars);
     $api_data = json_decode($response->getContent(), true);
     PHPUnit::assertEquals(0, $api_data['oldPrimedCount']);
     PHPUnit::assertEquals(2, $api_data['newPrimedCount']);
     PHPUnit::assertEquals(true, $api_data['primed']);
     // check the composed transaction
     $composed_transaction_raw = DB::connection('untransacted')->table('composed_transactions')->first();
     $send_data = app('TransactionComposerHelper')->parseBTCTransaction($composed_transaction_raw->transaction);
     $bitcoin_address = $payment_address['address'];
     PHPUnit::assertCount(2, $send_data['change']);
     PHPUnit::assertEquals($bitcoin_address, $send_data['destination']);
     PHPUnit::assertEquals(5430, $send_data['btc_amount']);
     PHPUnit::assertEquals($bitcoin_address, $send_data['change'][0][0]);
     PHPUnit::assertEquals(5430, $send_data['change'][0][1]);
 }
コード例 #18
0
ファイル: MockBuilder.php プロジェクト: tokenly/xchain-client
 public function sampleData_get_balances_1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx($data, $address)
 {
     if (isset($this->balances_by_address) and isset($this->balances_by_address[$address])) {
         $float_balances = $this->balances_by_address[$address];
     } else {
         $float_balances = ['BTC' => 0.01, 'LTBCOIN' => 1000, 'SOUP' => 5000];
     }
     $satoshi_balances = [];
     foreach ($float_balances as $token => $float_balance) {
         $satoshi_balances[$token] = CurrencyUtil::valueToSatoshis($float_balance);
     }
     return ['balances' => $float_balances, 'balancesSat' => $satoshi_balances];
 }
コード例 #19
0
 protected function assertFound($expected_offsets, $sample_entries, $chosen_entries)
 {
     $expected_entry_arrays = [];
     foreach ($expected_offsets as $expected_offset) {
         $expected_entry_arrays[] = $sample_entries[$expected_offset]->toArray();
     }
     $actual_amounts = [];
     $chosen_entry_arrays = [];
     foreach ($chosen_entries as $chosen_entry) {
         $chosen_entry_arrays[] = $chosen_entry ? $chosen_entry->toArray() : null;
         $actual_amounts[] = CurrencyUtil::satoshisToFormattedString($chosen_entry['amount']) . ' ' . $chosen_entry['asset'];
     }
     PHPUnit::assertEquals($expected_entry_arrays, $chosen_entry_arrays, "Did not find the expected offsets of " . json_encode($expected_offsets) . '. Actual amounts were ' . json_encode($actual_amounts));
 }
コード例 #20
0
 protected function normalizeBitcoindUTXORecord($unspent_txo_record)
 {
     return ['address' => $unspent_txo_record->destination_address, 'txid' => $unspent_txo_record->txid, 'vout' => $unspent_txo_record->n, 'script' => $unspent_txo_record->script, 'amount' => CurrencyUtil::satoshisToValue($unspent_txo_record->destination_value), 'amount_sat' => $unspent_txo_record->destination_value, 'confirmations' => $unspent_txo_record->confirmations, 'confirmed' => $unspent_txo_record->confirmations > 0];
 }
コード例 #21
0
ファイル: ScenarioRunner.php プロジェクト: CryptArc/xchain
 protected function normalizeTransactionEvent($raw_transaction_event)
 {
     $meta = isset($raw_transaction_event['meta']) ? $raw_transaction_event['meta'] : [];
     if (isset($meta['baseFilename'])) {
         $sample_filename = $meta['baseFilename'];
     } else {
         $sample_filename = 'default_xcp_parsed_01.json';
     }
     $default = json_decode(file_get_contents(base_path() . '/tests/fixtures/transactions/' . $sample_filename), true);
     $normalized_transaction_event = $default;
     if (isset($raw_transaction_event['txid'])) {
         $normalized_transaction_event['txid'] = $raw_transaction_event['txid'];
         $normalized_transaction_event['bitcoinTx']['txid'] = $raw_transaction_event['txid'];
     }
     if (isset($raw_transaction_event['sender'])) {
         $normalized_transaction_event['sources'] = [$raw_transaction_event['sender']];
     }
     if (isset($raw_transaction_event['recipient'])) {
         $normalized_transaction_event['destinations'] = [$raw_transaction_event['recipient']];
         // vouts
         if (isset($normalized_transaction_event['bitcoinTx']['vout'])) {
             $total_vouts = count($normalized_transaction_event['bitcoinTx']['vout']);
             foreach ($normalized_transaction_event['bitcoinTx']['vout'] as $offset => $vout) {
                 if ($offset < $total_vouts - 1) {
                     $new_recipient = $raw_transaction_event['recipient'];
                     $vout['scriptPubKey']['addresses'] = [$new_recipient];
                 }
                 $normalized_transaction_event['bitcoinTx']['vout'][$offset] = $vout;
             }
         }
     }
     // if (isset($raw_transaction_event['isCounterpartyTx'])) {
     //     $normalized_transaction_event['isCounterpartyTx'] = $raw_transaction_event['isCounterpartyTx'];
     //     if (!$raw_transaction_event['isCounterpartyTx']) {
     //         $normalized_transaction_event['counterPartyTxType'] = false;
     //         $normalized_transaction_event['counterpartyTx'] = [];
     //     }
     // }
     if (isset($raw_transaction_event['asset'])) {
         $normalized_transaction_event['asset'] = $raw_transaction_event['asset'];
     }
     if (isset($raw_transaction_event['quantity'])) {
         // $normalized_transaction_event['quantity'] = $raw_transaction_event['quantity'];
         // $normalized_transaction_event['quantitySat'] = CurrencyUtil::valueToSatoshis($raw_transaction_event['quantity']);
         $normalized_transaction_event['values'] = [$normalized_transaction_event['destinations'][0] => $raw_transaction_event['quantity']];
         if (isset($normalized_transaction_event['counterpartyTx']) and $normalized_transaction_event['counterpartyTx']) {
             $normalized_transaction_event['counterpartyTx']['quantity'] = $raw_transaction_event['quantity'];
             $normalized_transaction_event['counterpartyTx']['quantitySat'] = CurrencyUtil::valueToSatoshis($raw_transaction_event['quantity']);
         } else {
             // adjust utxos
             foreach ($normalized_transaction_event['bitcoinTx']['vout'] as $offset => $vout) {
                 if ($offset < $total_vouts - 1) {
                     $new_recipient = $raw_transaction_event['recipient'];
                     $vout['value'] = $raw_transaction_event['quantity'];
                 }
                 $normalized_transaction_event['bitcoinTx']['vout'][$offset] = $vout;
             }
         }
     }
     // confirmations and blockhash
     if (isset($raw_transaction_event['confirmations'])) {
         $normalized_transaction_event['bitcoinTx']['confirmations'] = $raw_transaction_event['confirmations'];
     }
     if (isset($raw_transaction_event['blockhash'])) {
         $normalized_transaction_event['bitcoinTx']['blockhash'] = $raw_transaction_event['blockhash'];
     }
     if (isset($raw_transaction_event['blockId'])) {
         $normalized_transaction_event['bitcoinTx']['blockheight'] = $raw_transaction_event['blockId'];
     }
     // timestamp
     if (isset($raw_transaction_event['timestamp'])) {
         $normalized_transaction_event['timestamp'] = $raw_transaction_event['timestamp'];
         $normalized_transaction_event['bitcoinTx']['timestamp'] = $raw_transaction_event['timestamp'];
     }
     // vins
     if (isset($raw_transaction_event['vins'])) {
         foreach ($raw_transaction_event['vins'] as $offset => $vin) {
             $normalized_transaction_event['bitcoinTx']['vin'][$offset] = array_replace_recursive($normalized_transaction_event['bitcoinTx']['vin'][$offset], $vin);
         }
     }
     // timing
     if (isset($raw_transaction_event['mempool'])) {
     }
     if (isset($raw_transaction_event['blockId'])) {
     }
     // apply special transaction
     $normalized_transaction_event['txid'] = str_replace('%%last_send_txid%%', $this->last_send_txid, $normalized_transaction_event['txid']);
     return $normalized_transaction_event;
 }
コード例 #22
0
 /**
  * Execute the console command.
  *
  * @return mixed
  */
 public function fire()
 {
     $txo_repository = app('App\\Repositories\\TXORepository');
     $payment_address_repo = app('App\\Repositories\\PaymentAddressRepository');
     $bitcoin_payer = app('Tokenly\\BitcoinPayer\\BitcoinPayer');
     $payment_address_uuid = $this->input->getArgument('payment-address-uuid');
     $should_reconcile = $this->input->getOption('reconcile');
     if ($payment_address_uuid) {
         $payment_address = $payment_address_repo->findByUuid($payment_address_uuid);
         if (!$payment_address) {
             throw new Exception("Payment address not found", 1);
         }
         $payment_addresses = [$payment_address];
     } else {
         $payment_addresses = $payment_address_repo->findAll();
     }
     foreach ($payment_addresses as $payment_address) {
         Log::debug("reconciling TXOs for {$payment_address['address']} ({$payment_address['uuid']})");
         // get xchain utxos
         $xchain_utxos_map = [];
         $db_txos = $txo_repository->findByPaymentAddress($payment_address, null, true);
         // unspent only
         foreach ($db_txos as $db_txo) {
             if ($db_txo['type'] != TXO::CONFIRMED) {
                 continue;
             }
             $filtered_utxo = ['txid' => $db_txo['txid'], 'n' => $db_txo['n'], 'script' => $db_txo['script'], 'amount' => $db_txo['amount']];
             $xchain_utxos_map[$filtered_utxo['txid'] . ':' . $filtered_utxo['n']] = $filtered_utxo;
         }
         // get daemon utxos
         $daemon_utxos_map = [];
         $all_utxos = $bitcoin_payer->getAllUTXOs($payment_address['address']);
         if ($all_utxos) {
             foreach ($all_utxos as $utxo) {
                 if (!isset($utxo['confirmations']) or $utxo['confirmations'] == 0) {
                     continue;
                 }
                 $filtered_utxo = ['txid' => $utxo['txid'], 'n' => $utxo['vout'], 'script' => $utxo['script'], 'amount' => CurrencyUtil::valueToSatoshis($utxo['amount'])];
                 $daemon_utxos_map[$utxo['txid'] . ':' . $utxo['vout']] = $filtered_utxo;
             }
         }
         // compare
         $differences = $this->buildDifferences($xchain_utxos_map, $daemon_utxos_map);
         if ($differences['any']) {
             $this->comment("Differences found for {$payment_address['address']} ({$payment_address['uuid']})");
             $this->line(json_encode($differences, 192));
             $this->line('');
             if ($should_reconcile) {
                 $account = AccountHandler::getAccount($payment_address);
                 foreach ($differences['differences'] as $difference) {
                     // delete the xchain txo if it is different in any way
                     if (isset($difference['xchain']) and $difference['xchain']) {
                         $utxo = $difference['xchain'];
                         $this->line('Removing xchain UTXO: ' . json_encode($utxo, 192));
                         // delete
                         $txo_model = $txo_repository->findByTXIDAndOffset($utxo['txid'], $utxo['n']);
                         if ($txo_model['payment_address_id'] != $payment_address['id']) {
                             throw new Exception("Mismatched payment address id.  Expected {$payment_address['id']}.  Found {$txo_model['payment_address_id']}", 1);
                         }
                     }
                     if (isset($difference['daemon'])) {
                         $utxo = $difference['daemon'];
                         $this->line('Adding daemon UTXO: ' . json_encode($utxo, 192));
                         // add the daemon's utxo
                         $txo_repository->create($payment_address, $account, ['txid' => $utxo['txid'], 'n' => $utxo['n'], 'script' => $utxo['script'], 'amount' => $utxo['amount']]);
                     }
                 }
             }
         } else {
             $this->comment("No differences found for {$payment_address['address']} ({$payment_address['uuid']})");
         }
     }
     $this->info('done');
 }
コード例 #23
0
 public function buildParsedTransactionData($bitcoin_transaction_data, $ts)
 {
     try {
         $parsed_transaction_data = [];
         $parsed_transaction_data = ['txid' => $bitcoin_transaction_data['txid'], 'network' => 'bitcoin', 'timestamp' => round($ts / 1000), 'counterPartyTxType' => false, 'sources' => [], 'destinations' => [], 'values' => [], 'asset' => false, 'bitcoinTx' => $bitcoin_transaction_data, 'counterpartyTx' => []];
         $xcp_data = $this->parser->parseBitcoinTransaction($bitcoin_transaction_data);
         if ($xcp_data) {
             // ensure 1 source and 1 desination
             if (!isset($xcp_data['sources'][0])) {
                 $xcp_data['sources'] = ['unknown'];
                 Log::error("No source found in transaction: " . ((isset($bitcoin_transaction_data) and isset($bitcoin_transaction_data['txid'])) ? $bitcoin_transaction_data['txid'] : "unknown"));
             }
             if (!isset($xcp_data['destinations'][0])) {
                 $xcp_data['destinations'] = ['unknown'];
                 Log::error("No destination found in transaction: " . ((isset($bitcoin_transaction_data) and isset($bitcoin_transaction_data['txid'])) ? $bitcoin_transaction_data['txid'] : "unknown"));
             }
             // this is a counterparty transaction
             $parsed_transaction_data['network'] = 'counterparty';
             $parsed_transaction_data['counterPartyTxType'] = $xcp_data['type'];
             $parsed_transaction_data['sources'] = $xcp_data['sources'];
             $parsed_transaction_data['destinations'] = $xcp_data['destinations'];
             if ($xcp_data['type'] === 'send') {
                 $is_divisible = $this->asset_cache->isDivisible($xcp_data['asset']);
                 // if the asset info doesn't exist, assume it is divisible
                 if ($is_divisible === null) {
                     $is_divisible = true;
                 }
                 if ($is_divisible) {
                     $quantity_sat = $xcp_data['quantity'];
                     $quantity_float = CurrencyUtil::satoshisToValue($xcp_data['quantity']);
                 } else {
                     $quantity_sat = CurrencyUtil::valueToSatoshis($xcp_data['quantity']);
                     $quantity_float = intval($xcp_data['quantity']);
                 }
                 $xcp_data['quantity'] = $quantity_float;
                 $xcp_data['quantitySat'] = $quantity_sat;
                 $destination = $xcp_data['destinations'][0];
                 $parsed_transaction_data['values'] = [$destination => $quantity_float];
                 $parsed_transaction_data['asset'] = $xcp_data['asset'];
                 // dustSize
                 // dustSizeSat
                 list($sources, $quantity_by_destination) = $this->extractSourcesAndDestinations($bitcoin_transaction_data);
                 $dust_size_float = isset($quantity_by_destination[$destination]) ? $quantity_by_destination[$destination] : 0;
                 $xcp_data['dustSize'] = $dust_size_float;
                 $xcp_data['dustSizeSat'] = CurrencyUtil::valueToSatoshis($dust_size_float);
             }
             // Log::debug("\$xcp_data=".json_encode($xcp_data, 192));
             $parsed_transaction_data['counterpartyTx'] = $xcp_data;
         } else {
             // this is just a bitcoin transaction
             list($sources, $quantity_by_destination) = $this->extractSourcesAndDestinations($bitcoin_transaction_data);
             $parsed_transaction_data['network'] = 'bitcoin';
             $parsed_transaction_data['sources'] = $sources;
             $parsed_transaction_data['destinations'] = array_keys($quantity_by_destination);
             $parsed_transaction_data['values'] = $quantity_by_destination;
             $parsed_transaction_data['asset'] = 'BTC';
         }
         // add a blockheight
         if (isset($parsed_transaction_data['bitcoinTx']['blockhash']) and $hash = $parsed_transaction_data['bitcoinTx']['blockhash']) {
             $block = $this->blockchain_store->findByHash($hash);
             $parsed_transaction_data['bitcoinTx']['blockheight'] = $block ? $block['height'] : null;
         }
         // add a transaction fingerprint
         $parsed_transaction_data['transactionFingerprint'] = $this->buildFingerprint($parsed_transaction_data['bitcoinTx']);
         return $parsed_transaction_data;
     } catch (Exception $e) {
         Log::warning("Failed to parse transaction: " . ((isset($bitcoin_transaction_data) and isset($bitcoin_transaction_data['txid'])) ? $bitcoin_transaction_data['txid'] : "unknown") . "\n" . $e->getMessage());
         // print "ERROR: ".$e->getMessage()."\n";
         // echo "\$parsed_transaction_data:\n".json_encode($parsed_transaction_data, 192)."\n";
         throw $e;
     }
 }
コード例 #24
0
 public function testPaymentAddressSenderForBTCMultiSend()
 {
     $mock_calls = app('CounterpartySenderMockBuilder')->installMockCounterpartySenderDependencies($this->app, $this);
     $user = $this->app->make('\\UserHelper')->createSampleUser();
     list($payment_address, $input_utxos) = $this->makeAddressAndSampleTXOs($user);
     $sender = app('App\\Blockchain\\Sender\\PaymentAddressSender');
     $destinations = [['1ATEST111XXXXXXXXXXXXXXXXXXXXwLHDB', 0.0001], ['1ATEST222XXXXXXXXXXXXXXXXXXXYzLVeV', 0.0001], ['1ATEST333XXXXXXXXXXXXXXXXXXXatH8WE', 0.1]];
     $sender->send($payment_address, $destinations, '0.1002', 'BTC', $float_fee = null, $dust_size = null, $is_sweep = false);
     // check the first sent call to bitcoind
     $btcd_send_call = $mock_calls['btcd'][0];
     PHPUnit::assertEquals('sendrawtransaction', $btcd_send_call['method']);
     // sendrawtransaction
     $send_details = app('TransactionComposerHelper')->parseBTCTransaction($btcd_send_call['args'][0]);
     PHPUnit::assertEquals('1ATEST111XXXXXXXXXXXXXXXXXXXXwLHDB', $send_details['destination']);
     // check output amounts
     PHPUnit::assertEquals(CurrencyUtil::valueToSatoshis(0.0001), $send_details['btc_amount']);
     $change_amount_satoshis = 50000000 - 10000 - CurrencyUtil::valueToSatoshis(0.123);
     // 3 change addresses
     PHPUnit::assertCount(3, $send_details['change']);
     PHPUnit::assertEquals('1ATEST222XXXXXXXXXXXXXXXXXXXYzLVeV', $send_details['change'][0][0]);
     PHPUnit::assertEquals(CurrencyUtil::valueToSatoshis(0.0001), $send_details['change'][0][1]);
     PHPUnit::assertEquals('1ATEST333XXXXXXXXXXXXXXXXXXXatH8WE', $send_details['change'][1][0]);
     PHPUnit::assertEquals(CurrencyUtil::valueToSatoshis(0.1), $send_details['change'][1][1]);
     // check that the change utxo exists and is green
     $txo_repository = app('App\\Repositories\\TXORepository');
     $txo = $txo_repository->findByTXIDAndOffset($send_details['txid'], 3);
     PHPUnit::assertTrue($txo['green']);
 }
コード例 #25
0
ファイル: TXOChooser.php プロジェクト: CryptArc/xchain
 protected function filterTXOsNotMatchingSize($raw_txos, $float_amount)
 {
     if ($float_amount === null) {
         $float_amount = 0;
     }
     $amount_satoshis = CurrencyUtil::valueToSatoshis($float_amount);
     $filtered_txos = [];
     foreach ($raw_txos as $raw_txo) {
         if ($raw_txo['amount'] == $amount_satoshis) {
             continue;
         }
         $filtered_txos[] = $raw_txo;
     }
     return $filtered_txos;
 }
コード例 #26
0
 public function fire($job, $data)
 {
     Log::debug("ValidateConfirmedCounterpartydTxJob called.\ntxid=" . json_encode($data['tx']['txid'], 192));
     // $data = [
     //     'tx'            => $parsed_tx,
     //     'confirmations' => $confirmations,
     //     'block_seq'     => $block_seq,
     //     'block_id'      => $block_id,
     // ];
     // {
     //     "destination": "1H42mKvwutzE4DAip57tkAc9KEKMGBD2bB",
     //     "source": "1MFHQCPGtcSfNPXAS6NryWja3TbUN9239Y",
     //     "quantity": 1050000000000,
     //     "block_index": 355675,
     //     "tx_hash": "5cbaf7995e7a8337861a30a65f5d751550127f63fccdb8a9b307efc26e6aa28b",
     //     "tx_index": 234609,
     //     "status": "valid",
     //     "asset": "LTBCOIN"
     // }
     // validate from counterpartyd
     // xcp_command -c get_sends -p '{"filters": {"field": "tx_hash", "op": "==", "value": "address"}}'
     $parsed_tx = $data['tx'];
     $tx_hash = $parsed_tx['txid'];
     try {
         $sends = $this->xcpd_client->get_sends(['filters' => ['field' => 'tx_hash', 'op' => '==', 'value' => $tx_hash]]);
         // not valid by default
         $is_valid = false;
         // not found by default
         $was_found = false;
     } catch (Exception $e) {
         EventLog::logError('error.counterparty', $e);
         // received no result from counterparty
         $sends = null;
         $is_valid = null;
     }
     if ($sends) {
         $send = $sends[0];
         if ($send) {
             $is_valid = true;
             $was_found = true;
             try {
                 if ($send['destination'] != $parsed_tx['destinations'][0]) {
                     throw new Exception("mismatched destination: {$send['destination']} (xcpd) != {$parsed_tx['destinations'][0]} (parsed)", 1);
                 }
                 $xcpd_quantity_sat = $send['quantity'];
                 // if token is not divisible, adjust to satoshis
                 $is_divisible = $this->asset_cache->isDivisible($send['asset']);
                 if (!$is_divisible) {
                     $xcpd_quantity_sat = CurrencyUtil::valueToSatoshis($xcpd_quantity_sat);
                 }
                 // compare send quantity
                 $parsed_quantity_sat = CurrencyUtil::valueToSatoshis($parsed_tx['values'][$send['destination']]);
                 if ($xcpd_quantity_sat != $parsed_quantity_sat) {
                     throw new Exception("mismatched quantity: {$xcpd_quantity_sat} (xcpd) != {$parsed_quantity_sat} (parsed)", 1);
                 }
                 // check asset
                 if ($send['asset'] != $parsed_tx['asset']) {
                     throw new Exception("mismatched asset: {$send['asset']} (xcpd) != {$parsed_tx['asset']} (parsed)", 1);
                 }
                 Log::debug("Send {$tx_hash} was confirmed by counterpartyd.  {$xcpd_quantity_sat} {$send['asset']} to {$send['destination']}");
             } catch (Exception $e) {
                 EventLog::logError('error.counterpartyConfirm', $e, ['txid' => $tx_hash]);
                 $is_valid = false;
             }
         }
     }
     if ($is_valid === null) {
         // no response from conterpartyd
         if ($job->attempts() > 240) {
             // permanent failure
             EventLog::logError('job.failed.permanent', ['txid' => $tx_hash]);
             $job->delete();
         } else {
             // no response - bury this task and try again
             $release_time = null;
             $attempts = $job->attempts();
             if ($job->attempts() > 60) {
                 $release_time = 60;
             } else {
                 if ($job->attempts() > 30) {
                     $release_time = 30;
                 } else {
                     if ($job->attempts() > 20) {
                         $release_time = 20;
                     } else {
                         if ($job->attempts() > 10) {
                             $release_time = 5;
                         } else {
                             $release_time = 2;
                         }
                     }
                 }
             }
             // put it back in the queue
             Log::debug("Send {$tx_hash} was not confirmed by counterpartyd yet.  putting it back in the queue for {$release_time} seconds.  \$sends=" . json_encode($sends, 192));
             $job->release($release_time);
         }
     } else {
         if ($is_valid === true) {
             // valid send - return it
             $data['tx']['counterpartyTx']['validated'] = true;
             // handle the parsed tx now
             $block = $this->block_repository->findByID($data['block_id']);
             if (!$block) {
                 throw new Exception("Block not found: {$data['block_id']}", 1);
             }
             try {
                 $this->events->fire('xchain.tx.confirmed', [$data['tx'], $data['confirmations'], $data['block_seq'], $block]);
             } catch (Exception $e) {
                 EventLog::logError('error.confirmingTx', $e);
                 usleep(500000);
                 // sleep 0.5 seconds to prevent runaway errors
                 throw $e;
             }
             // if all went well, delete the job
             $job->delete();
         } else {
             if ($is_valid === false) {
                 if ($was_found) {
                     // this send was found, but it was not a valid send
                     //   delete it
                     $job->delete();
                 } else {
                     // this send wasn't found by counterpartyd at all
                     //   but it might be found if we try a few more times
                     $attempts = $job->attempts();
                     if ($attempts >= 4) {
                         // we've already tried 4 times - give up
                         Log::debug("Send {$tx_hash} was not found by counterpartyd after attempt " . $attempts . ". Giving up.");
                         $job->delete();
                     } else {
                         $release_time = $attempts > 2 ? 10 : 2;
                         Log::debug("Send {$tx_hash} was not found by counterpartyd after attempt " . $attempts . ". Trying again in {$release_time} seconds.");
                         $job->release($release_time);
                     }
                 }
             }
         }
     }
 }
コード例 #27
0
ファイル: TXOChooserTest.php プロジェクト: CryptArc/xchain
 public function testChooseTXOsFour()
 {
     // receiving a transaction adds TXOs
     $txo_repository = app('App\\Repositories\\TXORepository');
     $txo_chooser = app('App\\Blockchain\\Sender\\TXOChooser');
     $float = function ($i) {
         return CurrencyUtil::satoshisToValue($i);
     };
     list($payment_address, $sample_txos) = $this->makeAddressAndSampleTXOs_4();
     // choose exact change from a bunch
     $chosen_txos = $txo_chooser->chooseUTXOs($payment_address, 0.0005, 0.0001, $float(5430));
     // $this->assertFound([2,1,3,4], $sample_txos, $chosen_txos);
 }
コード例 #28
0
 protected function buildNotification($event_type, $parsed_tx, $quantity, $sources, $destinations, $confirmations, $block, $block_seq, $monitored_address)
 {
     $confirmation_timestamp = $block ? $block['parsed_block']['time'] : null;
     if ($event_type === null) {
         $notification = ['network' => $parsed_tx['network'], 'asset' => $parsed_tx['asset'], 'sources' => $sources, 'destinations' => $destinations, 'notificationId' => null, 'txid' => $parsed_tx['txid'], 'transactionTime' => DateTimeUtil::ISO8601Date($parsed_tx['timestamp']), 'confirmed' => $confirmations > 0 ? true : false, 'confirmations' => $confirmations, 'confirmationTime' => $confirmation_timestamp ? DateTimeUtil::ISO8601Date($confirmation_timestamp) : '', 'blockSeq' => $block_seq, 'bitcoinTx' => $parsed_tx['bitcoinTx'], 'transactionFingerprint' => isset($parsed_tx['transactionFingerprint']) ? $parsed_tx['transactionFingerprint'] : null];
     } else {
         $notification = ['event' => $event_type, 'network' => $parsed_tx['network'], 'asset' => $parsed_tx['asset'], 'quantity' => $quantity, 'quantitySat' => CurrencyUtil::valueToSatoshis($quantity), 'sources' => $sources, 'destinations' => $destinations, 'notificationId' => null, 'txid' => $parsed_tx['txid'], 'transactionTime' => DateTimeUtil::ISO8601Date($parsed_tx['timestamp']), 'confirmed' => $confirmations > 0 ? true : false, 'confirmations' => $confirmations, 'confirmationTime' => $confirmation_timestamp ? DateTimeUtil::ISO8601Date($confirmation_timestamp) : '', 'blockSeq' => $block_seq, 'notifiedAddress' => $monitored_address['address'], 'notifiedAddressId' => $monitored_address['uuid'], 'bitcoinTx' => $parsed_tx['bitcoinTx'], 'transactionFingerprint' => isset($parsed_tx['transactionFingerprint']) ? $parsed_tx['transactionFingerprint'] : null];
     }
     if ($block_seq === null) {
         unset($notification['blockSeq']);
     }
     return $notification;
 }
コード例 #29
0
ファイル: TXOHandler.php プロジェクト: CryptArc/xchain
 public function invalidate($parsed_tx)
 {
     DB::transaction(function () use($parsed_tx) {
         $txid = $parsed_tx['txid'];
         $existing_txos = $this->txo_repository->findByTXID($txid);
         if (count($existing_txos) == 0) {
             return;
         }
         // delete each txo
         foreach ($existing_txos as $existing_txo) {
             Log::debug("invalidate TXO: {$existing_txo['txid']}:{$existing_txo['n']}/" . CurrencyUtil::valueToSatoshis($existing_txo['amount']) . " " . TXO::typeIntegerToString($existing_txo['type']) . " from " . $existing_txo['payment_address_id']);
             $this->txo_repository->delete($existing_txo);
         }
     });
 }
コード例 #30
0
ファイル: AccountHandler.php プロジェクト: CryptArc/xchain
 protected function hasSufficientFunds($actual_balances, $quantity, $asset, $float_fee, $dust_size)
 {
     // build required balances with fees
     $balances_required = $this->buildSendBalances($quantity, $asset, $float_fee, $dust_size);
     // check actual balances
     $has_sufficient_funds = true;
     foreach ($balances_required as $asset_required => $quantity_required) {
         if (!isset($actual_balances[$asset_required]) or CurrencyUtil::valueToSatoshis($actual_balances[$asset_required]) < CurrencyUtil::valueToSatoshis($quantity_required)) {
             $has_sufficient_funds = false;
         }
     }
     return $has_sufficient_funds;
 }