/// <summary> /// Mark an output as spent, the credit of the output will not be used to calculate the balance. /// The output will remain in the wallet for history (and reorg). /// </summary> /// <param name="transaction">The transaction from which details are added.</param> /// <param name="paidToOutputs">A list of payments made out</param> /// <param name="spendingTransactionId">The id of the transaction containing the output being spent, if this is a spending transaction.</param> /// <param name="spendingTransactionIndex">The index of the output in the transaction being referenced, if this is a spending transaction.</param> /// <param name="blockHeight">Height of the block.</param> /// <param name="block">The block containing the transaction to add.</param> private void AddSpendingTransactionToWallet(Transaction transaction, IEnumerable <TxOut> paidToOutputs, uint256 spendingTransactionId, int?spendingTransactionIndex, int?blockHeight = null, Block block = null) { Guard.NotNull(transaction, nameof(transaction)); Guard.NotNull(paidToOutputs, nameof(paidToOutputs)); this.logger.LogTrace("({0}:'{1}',{2}:'{3}',{4}:{5},{6}:'{7}')", nameof(transaction), transaction.GetHash(), nameof(spendingTransactionId), spendingTransactionId, nameof(spendingTransactionIndex), spendingTransactionIndex, nameof(blockHeight), blockHeight); // Get the transaction being spent. TransactionData spentTransaction = this.Wallet.MultiSigAddress.Transactions.SingleOrDefault(t => (t.Id == spendingTransactionId) && (t.Index == spendingTransactionIndex)); if (spentTransaction == null) { // Strange, why would it be null? this.logger.LogTrace("(-)[TX_NULL]"); return; } // If the details of this spending transaction are seen for the first time. if (spentTransaction.SpendingDetails == null) { this.logger.LogTrace("Spending UTXO '{0}-{1}' is new.", spendingTransactionId, spendingTransactionIndex); List <PaymentDetails> payments = new List <PaymentDetails>(); foreach (TxOut paidToOutput in paidToOutputs) { // Figure out how to retrieve the destination address. string destinationAddress = string.Empty; ScriptTemplate scriptTemplate = paidToOutput.ScriptPubKey.FindTemplate(this.network); switch (scriptTemplate.Type) { // Pay to PubKey can be found in outputs of staking transactions. case TxOutType.TX_PUBKEY: PubKey pubKey = PayToPubkeyTemplate.Instance.ExtractScriptPubKeyParameters(paidToOutput.ScriptPubKey); destinationAddress = pubKey.GetAddress(this.network).ToString(); break; // Pay to PubKey hash is the regular, most common type of output. case TxOutType.TX_PUBKEYHASH: destinationAddress = paidToOutput.ScriptPubKey.GetDestinationAddress(this.network).ToString(); break; case TxOutType.TX_NONSTANDARD: case TxOutType.TX_SCRIPTHASH: destinationAddress = paidToOutput.ScriptPubKey.GetDestinationAddress(this.network).ToString(); break; case TxOutType.TX_MULTISIG: case TxOutType.TX_NULL_DATA: case TxOutType.TX_SEGWIT: break; } payments.Add(new PaymentDetails { DestinationScriptPubKey = paidToOutput.ScriptPubKey, DestinationAddress = destinationAddress, Amount = paidToOutput.Value }); } SpendingDetails spendingDetails = new SpendingDetails { TransactionId = transaction.GetHash(), Payments = payments, CreationTime = DateTimeOffset.FromUnixTimeSeconds(block?.Header.Time ?? transaction.Time), BlockHeight = blockHeight, Hex = transaction.ToHex(), IsCoinStake = transaction.IsCoinStake == false ? (bool?)null : true }; spentTransaction.SpendingDetails = spendingDetails; spentTransaction.MerkleProof = null; } else // If this spending transaction is being confirmed in a block. { this.logger.LogTrace("Spending transaction ID '{0}' is being confirmed, updating.", spendingTransactionId); // Update the block height. if (spentTransaction.SpendingDetails.BlockHeight == null && blockHeight != null) { spentTransaction.SpendingDetails.BlockHeight = blockHeight; } // Update the block time to be that of the block in which the transaction is confirmed. if (block != null) { spentTransaction.SpendingDetails.CreationTime = DateTimeOffset.FromUnixTimeSeconds(block.Header.Time); } } this.logger.LogTrace("(-)"); }
/// <summary> /// Adds a transaction that credits the wallet with new coins. /// This method is can be called many times for the same transaction (idempotent). /// </summary> /// <param name="transaction">The transaction from which details are added.</param> /// <param name="utxo">The unspent output to add to the wallet.</param> /// <param name="blockHeight">Height of the block.</param> /// <param name="block">The block containing the transaction to add.</param> /// <param name="isPropagated">Propagation state of the transaction.</param> private void AddTransactionToWallet(Transaction transaction, TxOut utxo, int?blockHeight = null, Block block = null, bool isPropagated = true) { Guard.NotNull(transaction, nameof(transaction)); Guard.NotNull(utxo, nameof(utxo)); uint256 transactionHash = transaction.GetHash(); this.logger.LogTrace("({0}:'{1}',{2}:{3})", nameof(transaction), transactionHash, nameof(blockHeight), blockHeight); // Get the collection of transactions to add to. Script script = utxo.ScriptPubKey; // Check if a similar UTXO exists or not (same transaction ID and same index). // New UTXOs are added, existing ones are updated. int index = transaction.Outputs.IndexOf(utxo); Money amount = utxo.Value; TransactionData foundTransaction = this.Wallet.MultiSigAddress.Transactions.FirstOrDefault(t => (t.Id == transactionHash) && (t.Index == index)); if (foundTransaction == null) { this.logger.LogTrace("UTXO '{0}-{1}' not found, creating.", transactionHash, index); TransactionData newTransaction = new TransactionData { Amount = amount, BlockHeight = blockHeight, BlockHash = block?.GetHash(), Id = transactionHash, CreationTime = DateTimeOffset.FromUnixTimeSeconds(block?.Header.Time ?? transaction.Time), Index = index, ScriptPubKey = script, Hex = transaction.ToHex(), IsPropagated = isPropagated }; // Add the Merkle proof to the (non-spending) transaction. if (block != null) { newTransaction.MerkleProof = new MerkleBlock(block, new[] { transactionHash }).PartialMerkleTree; } this.Wallet.MultiSigAddress.Transactions.Add(newTransaction); this.AddInputKeysLookupLock(newTransaction); } else { this.logger.LogTrace("Transaction ID '{0}' found, updating.", transactionHash); // Update the block height and block hash. if ((foundTransaction.BlockHeight == null) && (blockHeight != null)) { foundTransaction.BlockHeight = blockHeight; foundTransaction.BlockHash = block?.GetHash(); } // Update the block time. if (block != null) { foundTransaction.CreationTime = DateTimeOffset.FromUnixTimeSeconds(block.Header.Time); } // Add the Merkle proof now that the transaction is confirmed in a block. if ((block != null) && (foundTransaction.MerkleProof == null)) { foundTransaction.MerkleProof = new MerkleBlock(block, new[] { transactionHash }).PartialMerkleTree; } if (isPropagated) { foundTransaction.IsPropagated = true; } } this.TransactionFoundInternal(script); this.logger.LogTrace("(-)"); }