public void SetProofBlob(PayoutData data, PayoutTransactionOnChainBlob blob) { var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(blob, _jsonSerializerSettings.GetSerializer(data.GetPaymentMethodId().CryptoCode))); // We only update the property if the bytes actually changed, this prevent from hammering the DB too much if (data.Proof is null || bytes.Length != data.Proof.Length || !bytes.SequenceEqual(data.Proof)) { data.Proof = bytes; } }
private async Task UpdatePayoutsAwaitingForPayment(NewOnChainTransactionEvent newTransaction) { try { var network = _btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(newTransaction.CryptoCode); Dictionary <string, decimal> destinations; if (newTransaction.NewTransactionEvent.TrackedSource is AddressTrackedSource addressTrackedSource) { destinations = new Dictionary <string, decimal>() { { addressTrackedSource.Address.ToString(), newTransaction.NewTransactionEvent.Outputs.Sum(output => output.Value.GetValue(network)) } }; } else { destinations = newTransaction.NewTransactionEvent.TransactionData.Transaction.Outputs .GroupBy(txout => txout.ScriptPubKey) .ToDictionary( txoutSet => txoutSet.Key.GetDestinationAddress(network.NBitcoinNetwork).ToString(), txoutSet => txoutSet.Sum(txout => txout.Value.ToDecimal(MoneyUnit.BTC))); } var paymentMethodId = new PaymentMethodId(newTransaction.CryptoCode, BitcoinPaymentType.Instance); using var ctx = _dbContextFactory.CreateContext(); var payouts = await ctx.Payouts .Include(o => o.PullPaymentData) .Where(p => p.State == PayoutState.AwaitingPayment) .Where(p => p.PaymentMethodId == paymentMethodId.ToString()) .Where(p => destinations.Keys.Contains(p.Destination)) .ToListAsync(); var payoutByDestination = payouts.ToDictionary(p => p.Destination); foreach (var destination in destinations) { if (!payoutByDestination.TryGetValue(destination.Key, out var payout)) { continue; } var payoutBlob = payout.GetBlob(_jsonSerializerSettings); if (payoutBlob.CryptoAmount is null || // The round up here is not strictly necessary, this is temporary to fix existing payout before we // were properly roundup the crypto amount destination.Value != BTCPayServer.Extensions.RoundUp(payoutBlob.CryptoAmount.Value, network.Divisibility)) { continue; } var proof = ParseProof(payout) as PayoutTransactionOnChainBlob; if (proof is null) { proof = new PayoutTransactionOnChainBlob() { Accounted = !(newTransaction.NewTransactionEvent.TrackedSource is AddressTrackedSource), }; } var txId = newTransaction.NewTransactionEvent.TransactionData.TransactionHash; if (proof.Candidates.Add(txId)) { if (proof.Accounted is true) { payout.State = PayoutState.InProgress; var walletId = new WalletId(payout.PullPaymentData.StoreId, newTransaction.CryptoCode); _eventAggregator.Publish(new UpdateTransactionLabel(walletId, newTransaction.NewTransactionEvent.TransactionData.TransactionHash, UpdateTransactionLabel.PayoutTemplate(payout.Id, payout.PullPaymentDataId, walletId.ToString()))); } if (proof.TransactionId is null) { proof.TransactionId = txId; } SetProofBlob(payout, proof); } } await ctx.SaveChangesAsync(); } catch (Exception ex) { Logs.PayServer.LogWarning(ex, "Error while processing a transaction in the pull payment hosted service"); } }