private async Task <bool> EnsureBalance(decimal requiredAmount, CryptonoteCoinTemplate coin, CancellationToken ct)
        {
            var response = await walletDaemon.ExecuteCmdSingleAsync <GetBalanceResponse>(logger, CryptonoteWalletCommands.GetBalance, ct);

            if (response.Error != null)
            {
                logger.Error(() => $"[{LogCategory}] Daemon command '{CryptonoteWalletCommands.GetBalance}' returned error: {response.Error.Message} code {response.Error.Code}");
                return(false);
            }

            var unlockedBalance = Math.Floor(response.Response.UnlockedBalance / coin.SmallestUnit);
            var balance         = Math.Floor(response.Response.Balance / coin.SmallestUnit);

            if (unlockedBalance < requiredAmount)
            {
                logger.Info(() => $"[{LogCategory}] {FormatAmount(requiredAmount)} unlocked balance required for payment, but only have {FormatAmount(unlockedBalance)} of {FormatAmount(balance)} available yet. Will try again.");
                return(false);
            }

            logger.Info(() => $"[{LogCategory}] Current balance is {FormatAmount(unlockedBalance)}");
            return(true);
        }
        public virtual async Task PayoutAsync(Balance[] balances)
        {
            Contract.RequiresNonNull(balances, nameof(balances));

            // build args
            var amounts = balances
                          .Where(x => x.Amount > 0)
                          .ToDictionary(x => x.Address, x => Math.Round(x.Amount, 8));

            if (amounts.Count == 0)
            {
                return;
            }

            logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses");

            var args = new object[]
            {
                string.Empty,          // default account
                amounts                // addresses and associated amounts
            };

            // send command
            var result = await daemon.ExecuteCmdSingleAsync <string>(BitcoinCommands.SendMany, args, new JsonSerializerSettings());

            if (result.Error == null)
            {
                var txId = result.Response;

                // check result
                if (string.IsNullOrEmpty(txId))
                {
                    logger.Error(() => $"[{LogCategory}] {BitcoinCommands.SendMany} did not return a transaction id!");
                }
                else
                {
                    logger.Info(() => $"[{LogCategory}] Payout transaction id: {txId}");
                }

                PersistPayments(balances, txId);

                NotifyPayoutSuccess(balances, new[] { txId }, null);
            }

            else
            {
                logger.Error(() => $"[{LogCategory}] {BitcoinCommands.SendMany} returned error: {result.Error.Message} code {result.Error.Code}");

                NotifyPayoutFailure(balances, $"{BitcoinCommands.SendMany} returned error: {result.Error.Message} code {result.Error.Code}", null);
            }
        }
        public async Task PayoutAsync(Balance[] balances)
        {
            // ensure we have peers
            var infoResponse = await daemon.ExecuteCmdSingleAsync <string>(logger, EC.GetPeerCount);

            if (networkType == EthereumNetworkType.Mainnet &&
                (infoResponse.Error != null || string.IsNullOrEmpty(infoResponse.Response) ||
                 infoResponse.Response.IntegralFromHex <int>() < EthereumConstants.MinPayoutPeerCount))
            {
                logger.Warn(() => $"[{LogCategory}] Payout aborted. Not enough peers (4 required)");
                return;
            }

            var txHashes = new List <string>();

            foreach (var balance in balances)
            {
                try
                {
                    var txHash = await PayoutAsync(balance);

                    txHashes.Add(txHash);
                }

                catch (Exception ex)
                {
                    logger.Error(ex);

                    NotifyPayoutFailure(poolConfig.Id, new[] { balance }, ex.Message, null);
                }
            }

            if (txHashes.Any())
            {
                NotifyPayoutSuccess(poolConfig.Id, balances, txHashes.ToArray(), null);
            }
        }
        public async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolConfig)
        {
            Contract.RequiresNonNull(poolConfig, nameof(poolConfig));

            this.poolConfig = poolConfig;
            this.clusterConfig = clusterConfig;
            extraConfig = poolConfig.PaymentProcessing.Extra.SafeExtensionDataAs<CryptonotePoolPaymentProcessingConfigExtra>();

            logger = LogUtil.GetPoolScopedLogger(typeof(CryptonotePayoutHandler), poolConfig);

            // configure standard daemon
            var jsonSerializerSettings = ctx.Resolve<JsonSerializerSettings>();

            var daemonEndpoints = poolConfig.Daemons
                .Where(x => string.IsNullOrEmpty(x.Category))
                .Select(x =>
                {
                    if (string.IsNullOrEmpty(x.HttpPath))
                        x.HttpPath = CryptonoteConstants.DaemonRpcLocation;

                    return x;
                })
                .ToArray();

            daemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id);
            daemon.Configure(daemonEndpoints);

            // configure wallet daemon
            var walletDaemonEndpoints = poolConfig.Daemons
                .Where(x => x.Category?.ToLower() == CryptonoteConstants.WalletDaemonCategory)
                .Select(x =>
                {
                    if (string.IsNullOrEmpty(x.HttpPath))
                        x.HttpPath = CryptonoteConstants.DaemonRpcLocation;

                    return x;
                })
                .ToArray();

            walletDaemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id);
            walletDaemon.Configure(walletDaemonEndpoints);

            // detect network
            await GetNetworkTypeAsync();

            // detect transfer_split support
            var response = await walletDaemon.ExecuteCmdSingleAsync<TransferResponse>(logger, CryptonoteWalletCommands.TransferSplit);
            walletSupportsTransferSplit = response.Error.Code != CryptonoteConstants.MoneroRpcMethodNotFound;
        }
        private async Task<bool> EnsureBalance(decimal requiredAmount, CryptonoteCoinTemplate coin)
        {
            var response = await walletDaemon.ExecuteCmdSingleAsync<GetBalanceResponse>(logger, CryptonoteWalletCommands.GetBalance);

            if (response.Error != null)
            {
                logger.Error(() => $"[{LogCategory}] Daemon command '{CryptonoteWalletCommands.GetBalance}' returned error: {response.Error.Message} code {response.Error.Code}");
                return false;
            }

            var unlockedBalance = Math.Floor(response.Response.UnlockedBalance / coin.SmallestUnit);
            var balance = Math.Floor(response.Response.Balance / coin.SmallestUnit);

            if (response.Response.UnlockedBalance < requiredAmount)
            {
                logger.Error(() => $"[{LogCategory}] Need {FormatAmount(requiredAmount)} unlocked balance, but only have {FormatAmount(unlockedBalance)} ({FormatAmount(balance)})");
                return false;
            }

            logger.Error(() => $"[{LogCategory}] Current balance is {FormatAmount(unlockedBalance)}");
            return true;
        }
        public virtual async Task <Block[]> ClassifyBlocksAsync(Block[] blocks)
        {
            Contract.RequiresNonNull(poolConfig, nameof(poolConfig));
            Contract.RequiresNonNull(blocks, nameof(blocks));

            var pageSize  = 100;
            var pageCount = (int)Math.Ceiling(blocks.Length / (double)pageSize);
            var result    = new List <Block>();

            for (var i = 0; i < pageCount; i++)
            {
                // get a page full of blocks
                var page = blocks
                           .Skip(i * pageSize)
                           .Take(pageSize)
                           .ToArray();

                // build command batch (block.TransactionConfirmationData is the hash of the blocks coinbase transaction)
                var batch = page.Select(block => new[] { block.TransactionConfirmationData }).ToArray();

                for (var j = 0; j < batch.Length; j++)
                {
                    var cmdResult = await daemon.ExecuteCmdSingleAsync <JToken>(BitcoinCommands.GetTransaction, batch[j]);

                    var transactionInfo = cmdResult.Response?.ToObject <Transaction>();
                    var block           = page[j];

                    // check error
                    if (cmdResult.Error != null)
                    {
                        // Code -5 interpreted as "orphaned"
                        if (cmdResult.Error.Code == -5)
                        {
                            block.Status = BlockStatus.Orphaned;
                            result.Add(block);
                        }

                        else
                        {
                            logger.Warn(() => $"[{LogCategory}] Daemon reports error '{cmdResult.Error.Message}' (Code {cmdResult.Error.Code}) for transaction {page[j].TransactionConfirmationData}");
                        }
                    }

                    // missing transaction details are interpreted as "orphaned"
                    else if (transactionInfo?.Details == null || transactionInfo.Details.Length == 0)
                    {
                        block.Status = BlockStatus.Orphaned;
                        result.Add(block);
                    }

                    else
                    {
                        switch (transactionInfo.Details[0].Category)
                        {
                        case "immature":
                            // update progress
                            var minConfirmations = extraPoolConfig?.MinimumConfirmations ?? BitcoinConstants.CoinbaseMinConfimations;
                            block.ConfirmationProgress = Math.Min(1.0d, (double)transactionInfo.Confirmations / minConfirmations);
                            block.Reward = transactionInfo.Details[0].Amount;
                            result.Add(block);
                            break;

                        case "generate":
                            // matured and spendable coinbase transaction
                            block.Status = BlockStatus.Confirmed;
                            block.ConfirmationProgress = 1;
                            block.Reward = transactionInfo.Details[0].Amount;
                            result.Add(block);

                            logger.Info(() => $"[{LogCategory}] Unlocked block {block.BlockHeight} worth {FormatAmount(block.Reward)}");
                            break;

                        default:
                            logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} classified as orphaned. Category: {transactionInfo.Details[0].Category}");

                            block.Status = BlockStatus.Orphaned;
                            block.Reward = 0;
                            result.Add(block);
                            break;
                        }
                    }
                }
            }

            return(result.ToArray());
        }
        public virtual async Task PayoutAsync(Balance[] balances)
        {
            Assertion.RequiresNonNull(balances, nameof(balances));

            var amounts = balances
                          .Where(x => x.Amount > 0)
                          .ToDictionary(x => x.Address, x => Math.Round(x.Amount, 8));

            if (amounts.Count == 0)
            {
                return;
            }

            logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses");

            object[] args;

            if (extraPoolPaymentProcessingConfig?.MinersPayTxFees == true)
            {
                var comment          = (poolConfig.PoolName ?? clusterConfig.ClusterName ?? "MiningCore").Trim() + " Payment";
                var subtractFeesFrom = amounts.Keys.ToArray();

                args = new object[]
                {
                    string.Empty, amounts, 1, comment, subtractFeesFrom
                };
            }

            else
            {
                args = new object[]
                {
                    string.Empty, amounts,
                };
            }

            var didUnlockWallet = false;

tryTransfer:
            var result = await daemon.ExecuteCmdSingleAsync <string>(BitcoinCommands.SendMany, args, new JsonSerializerSettings());

            if (result.Error == null)
            {
                if (didUnlockWallet)
                {
                    logger.Info(() => $"[{LogCategory}] Locking wallet");
                    await daemon.ExecuteCmdSingleAsync <JToken>(BitcoinCommands.WalletLock);
                }

                var txId = result.Response;

                if (string.IsNullOrEmpty(txId))
                {
                    logger.Error(() => $"[{LogCategory}] {BitcoinCommands.SendMany} did not return a transaction id!");
                }
                else
                {
                    logger.Info(() => $"[{LogCategory}] Payout transaction id: {txId}");
                }

                PersistPayments(balances, txId);

                NotifyPayoutSuccess(poolConfig.Id, balances, new[] { txId }, null);
            }

            else
            {
                if (result.Error.Code == (int)BitcoinRPCErrorCode.RPC_WALLET_UNLOCK_NEEDED && !didUnlockWallet)
                {
                    if (!string.IsNullOrEmpty(extraPoolPaymentProcessingConfig?.WalletPassword))
                    {
                        logger.Info(() => $"[{LogCategory}] Unlocking wallet");

                        var unlockResult = await daemon.ExecuteCmdSingleAsync <JToken>(BitcoinCommands.WalletPassphrase, new[]
                        {
                            (object)extraPoolPaymentProcessingConfig.WalletPassword,
                            (object)5
                        });

                        if (unlockResult.Error == null)
                        {
                            didUnlockWallet = true;
                            goto tryTransfer;
                        }

                        else
                        {
                            logger.Error(() => $"[{LogCategory}] {BitcoinCommands.WalletPassphrase} returned error: {result.Error.Message} code {result.Error.Code}");
                        }
                    }

                    else
                    {
                        logger.Error(() => $"[{LogCategory}] Wallet is locked but walletPassword was not configured. Unable to send funds.");
                    }
                }

                else
                {
                    logger.Error(() => $"[{LogCategory}] {BitcoinCommands.SendMany} returned error: {result.Error.Message} code {result.Error.Code}");

                    NotifyPayoutFailure(poolConfig.Id, balances, $"{BitcoinCommands.SendMany} returned error: {result.Error.Message} code {result.Error.Code}", null);
                }
            }
        }
Esempio n. 8
0
        private async Task PayoutBatch(Balance[] balances)
        {
            // build request
            var request = new TransferRequest
            {
                Destinations = balances
                               .Where(x => x.Amount > 0)
                               .Select(x => new TransferDestination
                {
                    Address = x.Address,
                    Amount  = (ulong)Math.Floor(x.Amount * MoneroConstants.SmallestUnit[poolConfig.Coin.Type])
                }).ToArray(),

                GetTxKey = true
            };

            if (request.Destinations.Length == 0)
            {
                return;
            }

            logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses");

            // send command
            var transferResponse = await walletDaemon.ExecuteCmdSingleAsync <TransferResponse>(MWC.Transfer, request);

            // gracefully handle error -4 (transaction would be too large. try /transfer_split)
            if (transferResponse.Error?.Code == -4)
            {
                if (walletSupportsTransferSplit)
                {
                    logger.Info(() => $"[{LogCategory}] Retrying transfer using {MWC.TransferSplit}");

                    var transferSplitResponse = await walletDaemon.ExecuteCmdSingleAsync <TransferSplitResponse>(MWC.TransferSplit, request);

                    // gracefully handle error -4 (transaction would be too large. try /transfer_split)
                    if (transferResponse.Error?.Code != -4)
                    {
                        HandleTransferResponse(transferSplitResponse, balances);
                        return;
                    }
                }

                // retry paged
                logger.Info(() => $"[{LogCategory}] Retrying paged");

                var validBalances = balances.Where(x => x.Amount > 0).ToArray();
                var pageSize      = 10;
                var pageCount     = (int)Math.Ceiling((double)validBalances.Length / pageSize);

                for (var i = 0; i < pageCount; i++)
                {
                    var page = validBalances
                               .Skip(i * pageSize)
                               .Take(pageSize)
                               .ToArray();

                    // update request
                    request.Destinations = page
                                           .Where(x => x.Amount > 0)
                                           .Select(x => new TransferDestination
                    {
                        Address = x.Address,
                        Amount  = (ulong)Math.Floor(x.Amount * MoneroConstants.SmallestUnit[poolConfig.Coin.Type])
                    }).ToArray();

                    logger.Info(() => $"[{LogCategory}] Page {i + 1}: Paying out {FormatAmount(page.Sum(x => x.Amount))} to {page.Length} addresses");

                    transferResponse = await walletDaemon.ExecuteCmdSingleAsync <TransferResponse>(MWC.Transfer, request);

                    HandleTransferResponse(transferResponse, page);

                    if (transferResponse.Error != null)
                    {
                        break;
                    }
                }
            }

            else
            {
                HandleTransferResponse(transferResponse, balances);
            }
        }
Esempio n. 9
0
        public virtual async Task PayoutAsync(Balance[] balances)
        {
            Contract.RequiresNonNull(balances, nameof(balances));

            // build args
            var amounts = balances
                          .Where(x => x.Amount > 0)
                          .ToDictionary(x => x.Address, x => Math.Round(x.Amount, 8));

            if (amounts.Count == 0)
            {
                return;
            }

            logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses");

            object[] args;

            if (extraPoolPaymentProcessingConfig?.MinersPayTxFees == true)
            {
                var comment          = (poolConfig.PoolName ?? clusterConfig.ClusterName ?? "MiningCore").Trim() + " Payment";
                var subtractFeesFrom = amounts.Keys.ToArray();

                if (!poolConfig.Template.As <BitcoinTemplate>().HasMasterNodes)
                {
                    args = new object[]
                    {
                        string.Empty,    // default account
                        amounts,         // addresses and associated amounts
                        1,               // only spend funds covered by this many confirmations
                        comment,         // tx comment
                        subtractFeesFrom // distribute transaction fee equally over all recipients
                    };
                }

                else
                {
                    args = new object[]
                    {
                        string.Empty,     // default account
                        amounts,          // addresses and associated amounts
                        1,                // only spend funds covered by this many confirmations
                        false,            // Whether to add confirmations to transactions locked via InstantSend
                        comment,          // tx comment
                        subtractFeesFrom, // distribute transaction fee equally over all recipients
                        false,            // use_is: Send this transaction as InstantSend
                        false,            // Use anonymized funds only
                    };
                }
            }

            else
            {
                args = new object[]
                {
                    string.Empty, // default account
                    amounts,      // addresses and associated amounts
                };
            }

            var didUnlockWallet = false;

            // send command
tryTransfer:
            var result = await daemon.ExecuteCmdSingleAsync <string>(logger, BitcoinCommands.SendMany, args, new JsonSerializerSettings());

            if (result.Error == null)
            {
                if (didUnlockWallet)
                {
                    // lock wallet
                    logger.Info(() => $"[{LogCategory}] Locking wallet");
                    await daemon.ExecuteCmdSingleAsync <JToken>(logger, BitcoinCommands.WalletLock);
                }

                // check result
                var txId = result.Response;

                if (string.IsNullOrEmpty(txId))
                {
                    logger.Error(() => $"[{LogCategory}] {BitcoinCommands.SendMany} did not return a transaction id!");
                }
                else
                {
                    logger.Info(() => $"[{LogCategory}] Payout transaction id: {txId}");
                }

                await PersistPaymentsAsync(balances, txId);

                NotifyPayoutSuccess(poolConfig.Id, balances, new[] { txId }, null);
            }

            else
            {
                if (result.Error.Code == (int)BitcoinRPCErrorCode.RPC_WALLET_UNLOCK_NEEDED && !didUnlockWallet)
                {
                    if (!string.IsNullOrEmpty(extraPoolPaymentProcessingConfig?.WalletPassword))
                    {
                        logger.Info(() => $"[{LogCategory}] Unlocking wallet");

                        var unlockResult = await daemon.ExecuteCmdSingleAsync <JToken>(logger, BitcoinCommands.WalletPassphrase, new[]
                        {
                            (object)extraPoolPaymentProcessingConfig.WalletPassword,
                            (object)5  // unlock for N seconds
                        });

                        if (unlockResult.Error == null)
                        {
                            didUnlockWallet = true;
                            goto tryTransfer;
                        }

                        else
                        {
                            logger.Error(() => $"[{LogCategory}] {BitcoinCommands.WalletPassphrase} returned error: {result.Error.Message} code {result.Error.Code}");
                        }
                    }

                    else
                    {
                        logger.Error(() => $"[{LogCategory}] Wallet is locked but walletPassword was not configured. Unable to send funds.");
                    }
                }

                else
                {
                    logger.Error(() => $"[{LogCategory}] {BitcoinCommands.SendMany} returned error: {result.Error.Message} code {result.Error.Code}");

                    NotifyPayoutFailure(poolConfig.Id, balances, $"{BitcoinCommands.SendMany} returned error: {result.Error.Message} code {result.Error.Code}", null);
                }
            }
        }
        public virtual async Task <Share> SubmitShareAsync(StratumClient worker, object submission)
        {
            Contract.RequiresNonNull(worker, nameof(worker));
            Contract.RequiresNonNull(submission, nameof(submission));

            logger.LogInvoke(LogCat, new object[] { worker.ConnectionId });

            if (!(submission is object[] submitParams))
            {
                throw new StratumException(StratumError.Other, "invalid params");
            }

            var context = worker.ContextAs <BitcoinWorkerContext>();

            // extract params
            var workerValue = (submitParams[0] as string)?.Trim();
            var jobId       = submitParams[1] as string;
            var extraNonce2 = submitParams[2] as string;
            var nTime       = submitParams[3] as string;
            var nonce       = submitParams[4] as string;
            var solution    = submitParams[5] as string;

            if (string.IsNullOrEmpty(workerValue))
            {
                throw new StratumException(StratumError.Other, "missing or invalid workername");
            }

            if (string.IsNullOrEmpty(solution))
            {
                throw new StratumException(StratumError.Other, "missing or invalid solution");
            }

            CommerciumJob job;

            lock (jobLock)
            {
                job = _validJobs.FirstOrDefault(x => x.JobId == jobId);
            }

            if (job == null)
            {
                throw new StratumException(StratumError.JobNotFound, "job not found");
            }

            // extract worker/miner/payoutid
            var split      = workerValue.Split('.');
            var minerName  = split[0];
            var workerName = split.Length > 1 ? split[1] : null;

            // validate & process
            var(share, blockHex) = job.ProcessShare(worker, extraNonce2, nTime, nonce, solution);

            // if block candidate, submit & check if accepted by network
            if (share.IsBlockCandidate)
            {
                logger.Info(() => $"[{LogCat}] Submitting block {share.BlockHeight} [{share.BlockHash}]");

                var acceptResponse = await SubmitBlockAsync(share, blockHex);

                // is it still a block candidate?
                share.IsBlockCandidate = acceptResponse.Accepted;

                if (share.IsBlockCandidate)
                {
                    logger.Info(() => $"[{LogCat}] Daemon accepted block {share.BlockHeight} [{share.BlockHash}] submitted by {minerName}");

                    blockSubmissionSubject.OnNext(Unit.Default);

                    // persist the coinbase transaction-hash to allow the payment processor
                    // to verify later on that the pool has received the reward for the block
                    share.TransactionConfirmationData = acceptResponse.CoinbaseTransaction;

                    // check what is a value of CoinbaseTransaction and assign it to block reward
                    var result = await _wallet.ExecuteCmdSingleAsync <JToken>(BitcoinCommands.GetTransaction, new [] { acceptResponse.CoinbaseTransaction });

                    Transaction transactionInfo = result.Response?.ToObject <Transaction>();
                    if (transactionInfo != null)
                    {
                        share.BlockReward = (decimal)transactionInfo.Details?[0]?.Amount;
                    }
                }

                else
                {
                    // clear fields that no longer apply
                    share.TransactionConfirmationData = null;
                }
            }

            // enrich share with common data
            share.PoolId            = poolConfig.Id;
            share.IpAddress         = worker.RemoteEndpoint.Address.ToString();
            share.Miner             = minerName;
            share.Worker            = workerName;
            share.UserAgent         = context.UserAgent;
            share.Source            = clusterConfig.ClusterName;
            share.NetworkDifficulty = job.Difficulty;
            share.Difficulty        = share.Difficulty / ShareMultiplier;
            share.Created           = _clock.Now;

            return(share);
        }
Esempio n. 11
0
        private string PayoutToAddress(string address, decimal amount, DaemonClient daemonClient)
        {
            string txId = string.Empty;

            object[] args = new object[]
            {
                address,        // address
                amount,         // amount
                "CX Trade",     // comment
                "Payout",       // comment_to
                false           // subtractfeefromamount
            };

            bool didUnlockWallet = false;

            // send command
tryTransfer:
            var result = daemonClient.ExecuteCmdSingleAsync <string>(BlockchainConstants.BitcoinCommands.SendToAddress, args, _jsonSerializerSettings).Result;

            if (result.Error == null)
            {
                SafeLockWallet(daemonClient, didUnlockWallet);

                // check result
                txId = result.Response;

                if (string.IsNullOrEmpty(txId))
                {
                    _logger.Error($"{BlockchainConstants.BitcoinCommands.SendToAddress} did not return a transaction id!");
                }
            }
            else
            {
                if (result.Error.Code == (int)BlockchainConstants.BitcoinRPCErrorCode.RPC_WALLET_UNLOCK_NEEDED && !didUnlockWallet)
                {
                    if (!string.IsNullOrEmpty(daemonClient.WalletPassword))
                    {
                        _logger.Info("Unlocking wallet");

                        var unlockResult = daemonClient.ExecuteCmdSingleAsync <JToken>(BlockchainConstants.BitcoinCommands.WalletPassphrase, new[]
                        {
                            (object)daemonClient.WalletPassword,
                            (object)5   // unlock for N seconds
                        }).Result;

                        if (unlockResult.Error == null)
                        {
                            didUnlockWallet = true;
                            goto tryTransfer;
                        }
                        else
                        {
                            _logger.Error($"{BlockchainConstants.BitcoinCommands.WalletPassphrase} returned error: {result.Error.Message} code {result.Error.Code}");
                        }
                    }

                    else
                    {
                        _logger.Error($"Wallet is locked but walletPassword was not configured. Unable to send funds.");
                    }
                }
                else if (result.Error.Code == (int)BlockchainConstants.BitcoinRPCErrorCode.RPC_WALLET_INSUFFICIENT_FUNDS)
                {
                    SafeLockWallet(daemonClient, didUnlockWallet);
                    _notificationService.NotifyPaymentFailure(address, amount, "Not enough funds in wallet or account.");
                }
                else
                {
                    _logger.Error($"{BlockchainConstants.BitcoinCommands.SendMany} returned error: {result.Error.Message} code {result.Error.Code}");
                }
            }

            return(txId);
        }
Esempio n. 12
0
        public virtual async Task PayoutAsync(Balance[] balances)
        {
            Contract.RequiresNonNull(balances, nameof(balances));

            // build args
            var amounts = balances
                          .Where(x => x.Amount > 0)
                          .ToDictionary(x => x.Address, x => Math.Round(x.Amount, 8));

            if (amounts.Count == 0)
            {
                return;
            }

            logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses");

            object[] args;

            if (extraPoolPaymentProcessingConfig?.MinersPayTxFees == true)
            {
                var comment          = (poolConfig.PoolName ?? clusterConfig.ClusterName ?? "MiningCore").Trim() + " Payment";
                var subtractFeesFrom = amounts.Keys.ToArray();

                args = new object[]
                {
                    string.Empty,           // default account
                    amounts,                // addresses and associated amounts
                    1,                      // only spend funds covered by this many confirmations
                    comment,                // tx comment
                    subtractFeesFrom        // distribute transaction fee equally over all recipients
                };
            }

            else
            {
                args = new object[]
                {
                    string.Empty,           // default account
                    amounts,                // addresses and associated amounts
                };
            }

            // send command
            var result = await daemon.ExecuteCmdSingleAsync <string>(BitcoinCommands.SendMany, args, new JsonSerializerSettings());

            if (result.Error == null)
            {
                var txId = result.Response;

                // check result
                if (string.IsNullOrEmpty(txId))
                {
                    logger.Error(() => $"[{LogCategory}] {BitcoinCommands.SendMany} did not return a transaction id!");
                }
                else
                {
                    logger.Info(() => $"[{LogCategory}] Payout transaction id: {txId}");
                }

                PersistPayments(balances, txId);

                NotifyPayoutSuccess(poolConfig.Id, balances, new[] { txId }, null);
            }

            else
            {
                logger.Error(() => $"[{LogCategory}] {BitcoinCommands.SendMany} returned error: {result.Error.Message} code {result.Error.Code}");

                NotifyPayoutFailure(poolConfig.Id, balances, $"{BitcoinCommands.SendMany} returned error: {result.Error.Message} code {result.Error.Code}", null);
            }
        }
Esempio n. 13
0
        public virtual async Task PayoutAsync(Balance[] balances)
        {
            Contract.RequiresNonNull(balances, nameof(balances));

            // build args
            var amounts = balances
                          .Where(x => x.Amount > 0)
                          .ToDictionary(x => x.Address, x => Math.Round(x.Amount, 8));

            if (amounts.Count == 0)
            {
                return;
            }

            logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses");

            object[] args;
            //Remarks: unfortunately 0.15 & 0.16 seem to be broken, sendmany not working anymore, instead using sendtoaddress
            var didUnlockWallet = false;

            foreach (string key in amounts.Keys)
            {
                args = new object[]
                {
                    key,            // address
                    amounts[key],   // amount
                    "reward",       // comment
                    "pool payment", // comment_to
                    true            // subtractfeefromamount
                };
                // send command
tryTransfer:
                var result = await daemon.ExecuteCmdSingleAsync <string>(BitcoinCommands.SendToAddress, args, new JsonSerializerSettings());

                Balance[] newBalances = balances.Where(b => b.Address == key).ToArray();
                if (result.Error == null)
                {
                    if (didUnlockWallet)
                    {
                        // lock wallet
                        logger.Info(() => $"[{LogCategory}] Locking wallet");
                        await daemon.ExecuteCmdSingleAsync <JToken>(BitcoinCommands.WalletLock);
                    }

                    // check result
                    var txId = result.Response;

                    if (string.IsNullOrEmpty(txId))
                    {
                        logger.Error(() => $"[{LogCategory}] {BitcoinCommands.SendToAddress} did not return a transaction id!");
                    }
                    else
                    {
                        logger.Info(() => $"[{LogCategory}] Payout transaction id: {txId}");
                    }

                    PersistPayments(newBalances, txId);

                    NotifyPayoutSuccess(poolConfig.Id, newBalances, new[] { txId }, null);
                }

                else
                {
                    if (result.Error.Code == (int)BitcoinRPCErrorCode.RPC_WALLET_UNLOCK_NEEDED && !didUnlockWallet)
                    {
                        if (!string.IsNullOrEmpty(extraPoolPaymentProcessingConfig?.WalletPassword))
                        {
                            logger.Info(() => $"[{LogCategory}] Unlocking wallet");

                            var unlockResult = await daemon.ExecuteCmdSingleAsync <JToken>(BitcoinCommands.WalletPassphrase, new[]
                            {
                                (object)extraPoolPaymentProcessingConfig.WalletPassword,
                                (object)5 // unlock for N seconds
                            });

                            if (unlockResult.Error == null)
                            {
                                didUnlockWallet = true;
                                goto tryTransfer;
                            }

                            else
                            {
                                logger.Error(() => $"[{LogCategory}] {BitcoinCommands.WalletPassphrase} returned error: {result.Error.Message} code {result.Error.Code}");
                            }
                        }

                        else
                        {
                            logger.Error(() => $"[{LogCategory}] Wallet is locked but walletPassword was not configured. Unable to send funds.");
                        }
                    }

                    else
                    {
                        logger.Error(() => $"[{LogCategory}] {BitcoinCommands.SendToAddress} returned error: {result.Error.Message} code {result.Error.Code}");

                        NotifyPayoutFailure(poolConfig.Id, newBalances, $"{BitcoinCommands.SendMany} returned error: {result.Error.Message} code {result.Error.Code}", null);
                    }
                }
            }
        }