/// <summary> Sell bit asset. </summary> /// /// <remarks> Paul, 16/02/2015. </remarks> /// /// <param name="l"> The BitsharesLedgerEntry to process. </param> /// <param name="s2d"> The 2D. </param> /// <param name="trxId"> Identifier for the trx. </param> protected virtual void SellBitAsset(BitsharesLedgerEntry l, SenderToDepositRow s2d, string trxId) { decimal oldBid = m_market.bid; try { if (m_market.price_discovery) { // // adjust prices based on order // decimal informed = m_asset.GetAmountFromLarimers(l.amount.amount) / m_market.bid_max; m_market.bid = m_prices.GetBidForSell(informed); } string btcAddress = s2d.receiving_address; SendBitcoinsToDepositor(btcAddress, trxId, l.amount.amount, m_asset, s2d.deposit_address, MetaOrderType.sell, m_currency.uia); if (m_market.price_discovery) { // update database with new prices m_isDirty = true; } } catch (Exception e) { // also lets now ignore this transaction so we don't keep failing RefundBitsharesDeposit(l.from_account, l.amount.amount, trxId, e.Message, m_asset, s2d.deposit_address, MetaOrderType.sell); // restore this m_market.bid = oldBid; } }
/// <summary> Handles the command. </summary> /// /// <remarks> Paul, 26/02/2015. </remarks> /// /// <param name="l"> The BitsharesLedgerEntry to process. </param> /// <param name="handler"> The handler. </param> /// <param name="market"> The market. </param> /// /// <returns> true if it succeeds, false if it fails. </returns> public bool HandleCommand(BitsharesLedgerEntry l, MarketBase handler, MarketRow market, string trxid) { if (m_adminUsernames.Contains(l.from_account)) { try { string[] parts = l.memo.Split(' '); if (l.memo.StartsWith(kSetPricesMemoStart)) { HandlePriceSetting(parts, l, handler, market); return(true); } else if (l.memo.StartsWith(kWithdrawMemo)) { // process withdrawal if (parts[0] == kWithdrawMemo) { // make sure we didn't already process this transaction! if (!m_dataAccess.IsWithdrawalProcessed(trxid)) { decimal amount = decimal.Parse(parts[1]); CurrenciesRow type = CurrencyHelpers.FromSymbol(parts[2], m_allCurrencies); string to; string txid; if (!CurrencyHelpers.IsBitsharesAsset(type)) { to = m_dataAccess.GetStats().bitcoin_withdraw_address; Debug.Assert(to != null); txid = m_bitcoin.SendToAddress(to, amount); } else { to = l.from_account; BitsharesTransactionResponse response = m_bitshares.WalletTransfer(amount, CurrencyHelpers.ToBitsharesSymbol(type), m_bitsharesAccount, to); txid = response.record_id; } // log in DB m_dataAccess.InsertWithdrawal(trxid, txid, type.ToString(), amount, to, DateTime.UtcNow); } return(true); } } } catch (Exception e) { LogGeneralException(e.ToString()); } } return(false); }
/// <summary> Handles the bitshares deposit described by kvp. </summary> /// /// <remarks> Paul, 05/02/2015. </remarks> /// /// <param name="kvp"> The kvp. </param> public override void HandleBitsharesDeposit(KeyValuePair <string, BitsharesLedgerEntry> kvp) { // get the btc address BitsharesLedgerEntry l = kvp.Value; string trxId = kvp.Key; SenderToDepositRow s2d = BitsharesTransactionToBitcoinAddress(l); SellBitAsset(l, s2d, trxId); }
/// <summary> Handles the bitshares desposits. </summary> /// /// <remarks> Paul, 16/12/2014. </remarks> /// /// <exception cref="UnsupportedTransactionException"> Thrown when an Unsupported Transaction /// error condition occurs. </exception> protected virtual Dictionary <string, BitsharesLedgerEntry> HandleBitsharesDesposits() { Dictionary <string, BitsharesLedgerEntry> results = new Dictionary <string, BitsharesLedgerEntry>(); // which block do we start from uint lastBlockBitshares = GetLastBitsharesBlock(); // which block do we end on GetInfoResponse info = m_bitshares.GetInfo(); if (lastBlockBitshares == 0) { // default to current block lastBlockBitshares = info.blockchain_head_block_num; } // get all relevant bitshares deposits List <BitsharesWalletTransaction> assetTransactions = m_bitshares.WalletAccountTransactionHistory(m_bitsharesAccount, null, 0, lastBlockBitshares, info.blockchain_head_block_num); IEnumerable <BitsharesWalletTransaction> assetDeposits = assetTransactions.Where(t => t.is_confirmed && t.ledger_entries.Any(l => l.to_account == m_bitsharesAccount && l.from_account != l.to_account && l.memo != kFundingMemo && l.from_account != BitsharesWallet.kNetworkAccount)); foreach (BitsharesWalletTransaction t in assetDeposits) { // make sure we didn't already send bitcoins for this deposit if (!HasDepositBeenCredited(t.trx_id) && !IsTransactionIgnored(t.trx_id)) { IEnumerable <BitsharesLedgerEntry> deposits = t.ledger_entries.Where(l => l.to_account == m_bitsharesAccount); if (deposits.Count() == 1) { BitsharesLedgerEntry l = deposits.First(); results[t.trx_id] = l; } else { // fail with unhandled case throw new UnsupportedTransactionException(t.trx_id); } } } UpdateBitsharesBlock(info.blockchain_head_block_num); return(results); }
/// <summary> This is virtual because implementors might like a different way </summary> /// /// <remarks> Paul, 16/12/2014. </remarks> /// /// <param name="account"> The account. </param> /// /// <returns> A string. </returns> /*protected virtual string BitsharesTransactionToBitcoinAddress(BitsharesLedgerEntry l) * { * // THIS CANNOT WORK DUE TO MEMO SIZE = 19 bytes!!!!!!! * * * // expect the BTC address to be inside the memo somewhere * string[] memo = l.memo.Split(' '); * AddressBase address = null; * foreach (string s in memo) * { * try * { * address = new AddressBase(s); * break; * } * catch (ArgumentException){} * } * * if (address == null) * { * throw new RefundBitsharesException("Unable to find desired bitcoin address in transction memo!"); * } * * return address.AddressBase58; * }*/ /// <summary> Bitshares transaction to bitcoin address. </summary> /// /// <remarks> Paul, 25/01/2015. </remarks> /// /// <exception cref="RefundBitsharesException"> Thrown when a Refund Bitshares error condition /// occurs. </exception> /// /// <param name="l"> The BitsharesLedgerEntry to process. </param> /// <param name="t"> The TransactionSinceBlock to process. </param> /// /// <returns> A string. </returns> protected virtual string BitsharesTransactionToBitcoinAddress(BitsharesLedgerEntry l, BitsharesTransaction t) { try { // get the public key of the sender BitsharesAccount account = GetAccountFromLedger(l.from_account); // turn into into a bitshares address return(BitsharesAccountToBitcoinAddress(account)); } catch (BitsharesRpcException) { throw new RefundBitsharesException("Unregistered acct!"); } }
/// <summary> Bitshares transaction to bitcoin address. </summary> /// /// <remarks> Paul, 31/01/2015. </remarks> /// /// <param name="l"> The BitsharesLedgerEntry to process. </param> /// <param name="t"> The TransactionSinceBlock to process. </param> /// /// <returns> A string. </returns> /*protected override string BitsharesTransactionToBitcoinAddress(BitsharesLedgerEntry l, BitsharesTransaction t) * { * // look up the BTS address this transaction was sent to * BitsharesOperation op = t.trx.operations.First(o => o.type == BitsharesTransactionOp.deposit_op_type); * * string depositAddress = op.data.condition.data.owner; * * // look that address up in our map of sender->deposit address * SenderToDepositRow senderToDeposit = GetSenderDepositFromDeposit(depositAddress); * if (senderToDeposit != null) * { * return senderToDeposit.sender; * } * else * { * return null; * } * }*/ /// <summary> Bitshares transaction to bitcoin address. </summary> /// /// <remarks> Paul, 31/01/2015. </remarks> /// /// <param name="l"> The BitsharesLedgerEntry to process. </param> /// <param name="t"> The TransactionSinceBlock to process. </param> /// /// <returns> A string. </returns> protected override string BitsharesTransactionToBitcoinAddress(BitsharesLedgerEntry l, BitsharesTransaction t) { // look up the BTS address this transaction was sent to // look that address up in our map of sender->deposit address SenderToDepositRow senderToDeposit = GetSenderDepositFromDeposit(l.memo); if (senderToDeposit != null) { return(senderToDeposit.receiving_address); } else { throw new RefundBitsharesException("Missing memo!"); } }
/// <summary> Handles the price setting. </summary> /// /// <remarks> Paul, 14/02/2015. </remarks> /// /// <param name="l"> The BitsharesLedgerEntry to process. </param> /// <param name="handler"> The handler. </param> /// <param name="market"> The market. </param> void HandlePriceSetting(string[] parts, BitsharesLedgerEntry l, MarketBase handler, MarketRow market) { // parse if (parts[0] == kSetPricesMemoStart) { if (parts[1] == market.symbol_pair) { // setting is for this market! decimal basePrice = decimal.Parse(parts[2]); decimal quotePrice = decimal.Parse(parts[3]); // go do it! handler.SetPricesFromSingleUnitQuantities(basePrice, quotePrice, market.flipped, market); } } }
/// <summary> Bitshares transaction to bitcoin address. </summary> /// /// <remarks> Paul, 05/02/2015. </remarks> /// /// <exception cref="RefundBitsharesException"> Thrown when a Refund Bitshares error condition /// occurs. </exception> /// /// <param name="l"> The BitsharesLedgerEntry to process. </param> /// /// <returns> A string. </returns> protected SenderToDepositRow BitsharesTransactionToBitcoinAddress(BitsharesLedgerEntry l) { // look up the BTS address this transaction was sent to // look that address up in our map of sender->deposit address // pull the market uid out of the memo string symbolPair; uint referralUser; MemoExtract(l.memo, out symbolPair, out referralUser); SenderToDepositRow senderToDeposit = m_daemon.GetSenderDepositFromDeposit(l.memo, symbolPair, referralUser); if (senderToDeposit != null) { return(senderToDeposit); } else { throw new RefundBitsharesException("Missing/bad memo!"); } }
/// <summary> Updates this object. </summary> /// /// <remarks> Paul, 05/02/2015. </remarks> async public override void Update() { try { // // don't process transactions if the network is in danger // GetInfoResponse info = m_bitshares.GetInfo(); m_suspended = info.blockchain_average_delegate_participation < kMinDelegateParticipation; if (!m_suspended) { Dictionary <string, MarketRow> allMarkets = GetAllMarkets().ToDictionary(m => m.symbol_pair); m_allCurrencies = m_dataAccess.GetAllCurrencies(); // create any handlers we need for new markets CheckMarketHandlers(allMarkets); // get all markets RecomputeTransactionLimitsAndPrices(allMarkets); // // handle bitshares->bitcoin // Dictionary <string, BitsharesLedgerEntry> bitsharesDeposits = HandleBitsharesDesposits(); // // handle bitcoin->bitshares // List <TransactionSinceBlock> bitcoinDeposits = HandleBitcoinDeposits(); // // process bitshares deposits // uint siteLastTid = m_dataAccess.GetSiteLastTransactionUid(); foreach (KeyValuePair <string, BitsharesLedgerEntry> kvpDeposit in bitsharesDeposits) { // figure out which market each deposit belongs to foreach (KeyValuePair <string, MarketBase> kvpHandler in m_marketHandlers) { BitsharesLedgerEntry l = kvpDeposit.Value; MarketRow m = allMarkets[kvpHandler.Key]; BitsharesAsset depositAsset = m_allBitsharesAssets[l.amount.asset_id]; if (!HandleCommand(l, kvpHandler.Value, m, kvpDeposit.Key)) { if (IsDepositForMarket(l.memo, m.symbol_pair)) { // make sure the deposit is for this market! if (kvpHandler.Value.CanDepositAsset(CurrencyHelpers.FromBitsharesSymbol(depositAsset.symbol, m_allCurrencies, depositAsset.IsUia()))) { kvpHandler.Value.HandleBitsharesDeposit(kvpDeposit); } } } } // this needs to happen for every transaction RecomputeTransactionLimitsAndPrices(allMarkets); } // // process bitcoin deposits // List <TransactionsRow> pendingTransactions = m_dataAccess.GetAllPendingTransactions(); foreach (TransactionSinceBlock deposit in bitcoinDeposits) { // figure out which market each deposit belongs to foreach (KeyValuePair <string, MarketBase> kvpHandler in m_marketHandlers) { if (IsDepositForMarket(deposit.Address, allMarkets[kvpHandler.Key].symbol_pair)) { kvpHandler.Value.HandleBitcoinDeposit(deposit); } } // this needs to happen for every transaction RecomputeTransactionLimitsAndPrices(allMarkets); } // // handle changes in transaction status // List <TransactionsRow> updatedTrans = new List <TransactionsRow>(); foreach (TransactionsRow pending in pendingTransactions) { TransactionsRow updated = m_dataAccess.GetTransaction(pending.received_txid); if (updated.status != MetaOrderStatus.pending) { updatedTrans.Add(updated); } } // // push any new transactions, make sure site acknowledges receipt // uint latestTid = m_dataAccess.GetLastTransactionUid(); if (latestTid > siteLastTid || updatedTrans.Count > 0) { List <TransactionsRow> newTrans = m_dataAccess.GetAllTransactionsSince(siteLastTid); // lump them together newTrans.AddRange(updatedTrans); // send 'em all string result = await ApiPush <List <TransactionsRow> >(Routes.kPushTransactions, newTrans); if (bool.Parse(result)) { m_dataAccess.UpdateSiteLastTransactionUid(latestTid); } else { throw new Exception("API push response unknown! " + result); } } // // push market updates // foreach (KeyValuePair <string, MarketBase> kvpHandler in m_marketHandlers) { if (kvpHandler.Value.m_IsDirty) { m_dataAccess.UpdateMarketInDatabase(kvpHandler.Value.m_Market); #pragma warning disable 4014 ApiPush <MarketRow>(Routes.kPushMarket, kvpHandler.Value.m_Market); #pragma warning restore 4014 kvpHandler.Value.m_IsDirty = false; } } // // push fee collections // if (m_bitcoinFeeAddress != null && m_bitshaaresFeeAccount != null) { uint lastFeeId = m_dataAccess.GetSiteLastFeeUid(); // collect our fees foreach (KeyValuePair <string, MarketBase> kvpHandler in m_marketHandlers) { kvpHandler.Value.CollectFees(m_bitcoinFeeAddress, m_bitshaaresFeeAccount); } // keep the site up to date, make sure it acknowledges receipt uint latestFeeId = m_dataAccess.GetLastFeeCollectionUid(); if (latestFeeId > lastFeeId) { List <FeeCollectionRow> fees = m_dataAccess.GetFeeCollectionsSince(lastFeeId); string result = await ApiPush <List <FeeCollectionRow> >(Routes.kPushFees, fees); if (bool.Parse(result)) { m_dataAccess.UpdateSiteLastFeeUid(latestFeeId); } else { throw new Exception("API push response unknown! " + result); } } } } // // wait for a stop command to exit gracefully // if (m_lastCommand == null) { m_lastCommand = ReadConsoleAsync(); string command = await m_lastCommand; // remember we never get here unless a command was entered Console.WriteLine("got command: " + command); if (command == "stop") { m_scheduler.Dispose(); } m_lastCommand = null; } } catch (UnsupportedTransactionException ute) { // ignore so we can move on! m_dataAccess.IgnoreTransaction(ute.m_trxId); // log it LogGeneralException(ute.ToString()); } catch (Exception e) { LogGeneralException(e.ToString()); } }
/// <summary> Bitshares transaction to bitcoin address. </summary> /// /// <remarks> Paul, 31/01/2015. </remarks> /// /// <param name="l"> The BitsharesLedgerEntry to process. </param> /// <param name="t"> The TransactionSinceBlock to process. </param> /// /// <returns> A string. </returns> /*protected override string BitsharesTransactionToBitcoinAddress(BitsharesLedgerEntry l, BitsharesTransaction t) { // look up the BTS address this transaction was sent to BitsharesOperation op = t.trx.operations.First(o => o.type == BitsharesTransactionOp.deposit_op_type); string depositAddress = op.data.condition.data.owner; // look that address up in our map of sender->deposit address SenderToDepositRow senderToDeposit = GetSenderDepositFromDeposit(depositAddress); if (senderToDeposit != null) { return senderToDeposit.sender; } else { return null; } }*/ /// <summary> Bitshares transaction to bitcoin address. </summary> /// /// <remarks> Paul, 31/01/2015. </remarks> /// /// <param name="l"> The BitsharesLedgerEntry to process. </param> /// <param name="t"> The TransactionSinceBlock to process. </param> /// /// <returns> A string. </returns> protected override string BitsharesTransactionToBitcoinAddress(BitsharesLedgerEntry l, BitsharesTransaction t) { // look up the BTS address this transaction was sent to // look that address up in our map of sender->deposit address SenderToDepositRow senderToDeposit = GetSenderDepositFromDeposit(l.memo); if (senderToDeposit != null) { return senderToDeposit.receiving_address; } else { throw new RefundBitsharesException("Missing memo!"); } }
/// <summary> Handles the command. </summary> /// /// <remarks> Paul, 26/02/2015. </remarks> /// /// <param name="l"> The BitsharesLedgerEntry to process. </param> /// <param name="handler"> The handler. </param> /// <param name="market"> The market. </param> /// /// <returns> true if it succeeds, false if it fails. </returns> public bool HandleCommand(BitsharesLedgerEntry l, MarketBase handler, MarketRow market, string trxid) { if (m_adminUsernames.Contains(l.from_account)) { try { string[] parts = l.memo.Split(' '); if (l.memo.StartsWith(kSetPricesMemoStart)) { HandlePriceSetting(parts, l, handler, market); return true; } else if (l.memo.StartsWith(kWithdrawMemo)) { // process withdrawal if (parts[0] == kWithdrawMemo) { // make sure we didn't already process this transaction! if (!m_dataAccess.IsWithdrawalProcessed(trxid)) { decimal amount = decimal.Parse(parts[1]); CurrenciesRow type = CurrencyHelpers.FromSymbol(parts[2], m_allCurrencies); string to; string txid; if ( !CurrencyHelpers.IsBitsharesAsset(type) ) { to = m_dataAccess.GetStats().bitcoin_withdraw_address; Debug.Assert(to != null); txid = m_bitcoin.SendToAddress(to, amount); } else { to = l.from_account; BitsharesTransactionResponse response = m_bitshares.WalletTransfer(amount, CurrencyHelpers.ToBitsharesSymbol(type), m_bitsharesAccount, to); txid = response.record_id; } // log in DB m_dataAccess.InsertWithdrawal(trxid, txid, type.ToString(), amount, to, DateTime.UtcNow); } return true; } } } catch (Exception e) { LogGeneralException(e.ToString()); } } return false; }
/// <summary> Handles the bitshares desposits. </summary> /// /// <remarks> Paul, 16/12/2014. </remarks> /// /// <exception cref="UnsupportedTransactionException"> Thrown when an Unsupported Transaction /// error condition occurs. </exception> protected virtual void HandleBitsharesDesposits() { // which block do we start from uint lastBlockBitshares = GetLastBitsharesBlock(); // which block do we end on GetInfoResponse info = m_bitshares.GetInfo(); if (lastBlockBitshares == 0) { // default to current block lastBlockBitshares = info.blockchain_head_block_num; } // get all relevant bitshares deposits List <BitsharesWalletTransaction> assetTransactions = m_bitshares.WalletAccountTransactionHistory(m_bitsharesAccount, m_bitsharesAsset, 0, lastBlockBitshares, info.blockchain_head_block_num); IEnumerable <BitsharesWalletTransaction> assetDeposits = assetTransactions.Where(t => t.is_confirmed && t.ledger_entries.Any(l => l.to_account == m_bitsharesAccount && l.from_account != l.to_account && l.memo != kFundingMemo && l.from_account != BitsharesWallet.kNetworkAccount)); foreach (BitsharesWalletTransaction t in assetDeposits) { IEnumerable <BitsharesLedgerEntry> deposits = t.ledger_entries.Where(l => l.to_account == m_bitsharesAccount); if (deposits.Count() == 1) { BitsharesLedgerEntry l = deposits.First(); // make sure we didn't already send bitcoins for this deposit if (!HasBitsharesDepositBeenCredited(t.trx_id) && !IsTransactionIgnored(t.trx_id)) { try { /// /// STILL NEED A WAY TO GET SENDER BITCOIN ADDRESS FOR UNREGISTERED ACCOUNTS /// // look up the transaction proper BitsharesTransaction fullT = m_bitshares.BlockchainGetTransaction(t.trx_id); // get the btc address string btcAddress = BitsharesTransactionToBitcoinAddress(l, fullT); try { SendBitcoinsToDepositor(btcAddress, t.trx_id, l.amount.amount); } catch (Exception e) { // problem sending bitcoins, lets log it LogException(t.trx_id, e.Message, DateTime.UtcNow, DaemonTransactionType.bitsharesDeposit); // also lets now ignore this transaction so we don't keep failing RefundBitsharesDeposit(l.from_account, l.amount.amount, t.trx_id, e.Message); } } catch (RefundBitsharesException r) { // were unable to get a bitcoin address from the bitshares account, so refund the transaction RefundBitsharesDeposit(l.from_account, l.amount.amount, t.trx_id, r.Message); } } } else { // fail with unhandled case throw new UnsupportedTransactionException(t.trx_id); } } UpdateBitsharesBlock(info.blockchain_head_block_num); }
/// <summary> This is virtual because implementors might like a different way </summary> /// /// <remarks> Paul, 16/12/2014. </remarks> /// /// <param name="account"> The account. </param> /// /// <returns> A string. </returns> /*protected virtual string BitsharesTransactionToBitcoinAddress(BitsharesLedgerEntry l) { // THIS CANNOT WORK DUE TO MEMO SIZE = 19 bytes!!!!!!! // expect the BTC address to be inside the memo somewhere string[] memo = l.memo.Split(' '); AddressBase address = null; foreach (string s in memo) { try { address = new AddressBase(s); break; } catch (ArgumentException){} } if (address == null) { throw new RefundBitsharesException("Unable to find desired bitcoin address in transction memo!"); } return address.AddressBase58; }*/ /// <summary> Bitshares transaction to bitcoin address. </summary> /// /// <remarks> Paul, 25/01/2015. </remarks> /// /// <exception cref="RefundBitsharesException"> Thrown when a Refund Bitshares error condition /// occurs. </exception> /// /// <param name="l"> The BitsharesLedgerEntry to process. </param> /// <param name="t"> The TransactionSinceBlock to process. </param> /// /// <returns> A string. </returns> protected virtual string BitsharesTransactionToBitcoinAddress(BitsharesLedgerEntry l, BitsharesTransaction t) { try { // get the public key of the sender BitsharesAccount account = GetAccountFromLedger(l.from_account); // turn into into a bitshares address return BitsharesAccountToBitcoinAddress(account); } catch (BitsharesRpcException) { throw new RefundBitsharesException("Unregistered acct!"); } }
/// <summary> Bitshares transaction to bitcoin address. </summary> /// /// <remarks> Paul, 05/02/2015. </remarks> /// /// <exception cref="RefundBitsharesException"> Thrown when a Refund Bitshares error condition /// occurs. </exception> /// /// <param name="l"> The BitsharesLedgerEntry to process. </param> /// /// <returns> A string. </returns> protected SenderToDepositRow BitsharesTransactionToBitcoinAddress(BitsharesLedgerEntry l) { // look up the BTS address this transaction was sent to // look that address up in our map of sender->deposit address // pull the market uid out of the memo string symbolPair; uint referralUser; MemoExtract(l.memo, out symbolPair, out referralUser); SenderToDepositRow senderToDeposit = m_daemon.GetSenderDepositFromDeposit(l.memo, symbolPair, referralUser); if (senderToDeposit != null) { return senderToDeposit; } else { throw new RefundBitsharesException("Missing/bad memo!"); } }