public void Transactions() { // This test covers a _bug in which Transaction.getValueSentFromMe was calculating incorrectly. var tx = TestUtils.CreateFakeTx(Params, Utils.ToNanoCoins(1, 0), _myAddress); // Now add another output (ie, change) that goes to some other address. var someOtherGuy = new EcKey().ToAddress(Params); var output = new TransactionOutput(Params, tx, Utils.ToNanoCoins(0, 5), someOtherGuy); tx.AddOutput(output); // Note that tx is no longer valid: it spends more than it imports. However checking transactions balance // correctly isn't possible in SPV mode because value is a property of outputs not inputs. Without all // transactions you can't check they add up. _defaultWallet.Receive(tx, null, BlockChain.NewBlockType.BestChain); // Now the other guy creates a transaction which spends that change. var tx2 = new Transaction(Params); tx2.AddInput(output); tx2.AddOutput(new TransactionOutput(Params, tx2, Utils.ToNanoCoins(0, 5), _myAddress)); // tx2 doesn't send any coins from us, even though the output is in the wallet. Assert.AreEqual(Utils.ToNanoCoins(0, 0), tx2.GetValueSentFromMe(_defaultWallet)); }
public void Balances() { var nanos = Utils.ToNanoCoins(1, 0); var tx1 = TestUtils.CreateFakeTx(Params, nanos, _myAddress); _defaultWallet.Receive(tx1, null, BlockChain.NewBlockType.BestChain); Assert.AreEqual(nanos, tx1.GetValueSentToMe(_defaultWallet, true)); // Send 0.10 to somebody else. var send1 = _defaultWallet.CreateSend(new EcKey().ToAddress(Params), Utils.ToNanoCoins(0, 10), _myAddress); // Re-serialize. var send2 = new Transaction(Params, send1.BitcoinSerialize()); Assert.AreEqual(nanos, send2.GetValueSentFromMe(_defaultWallet)); }
/// <exception cref="BitcoinSharp.Core.Exceptions.VerificationException"/> /// <exception cref="BitcoinSharp.Core.Exceptions.ScriptException"/> private void Receive(Transaction transaction, StoredBlock storedBlock, BlockChain.NewBlockType blockType, bool reorg) { lock (this) { // Runs in a peer thread. var previousBalance = GetBalance(); var transactionHash = transaction.Hash; var bestChain = blockType == BlockChain.NewBlockType.BestChain; var sideChain = blockType == BlockChain.NewBlockType.SideChain; var valueSentFromMe = transaction.GetValueSentFromMe(this); var valueSentToMe = transaction.GetValueSentToMe(this); var valueDifference = (long) (valueSentToMe - valueSentFromMe); if (!reorg) { Log.InfoFormat("Received tx{0} for {1} BTC: {2}", sideChain ? " on a side chain" : "", Utils.BitcoinValueToFriendlyString(valueDifference), transaction.HashAsString); } // If this transaction is already in the wallet we may need to move it into a different WalletPool. At the very // least we need to ensure we're manipulating the canonical object rather than a duplicate. Transaction walletTransaction; if (Pending.TryGetValue(transactionHash, out walletTransaction)) { Pending.Remove(transactionHash); Log.Info(" <-pending"); // A transaction we created appeared in a block. Probably this is a spend we broadcast that has been // accepted by the network. // // Mark the tx as appearing in this block so we can find it later after a re-org. walletTransaction.AddBlockAppearance(storedBlock); if (bestChain) { if (valueSentToMe.Equals(0)) { // There were no change transactions so this tx is fully spent. Log.Info(" ->spent"); Debug.Assert(!Spent.ContainsKey(walletTransaction.Hash), "TX in both pending and spent pools"); Spent[walletTransaction.Hash] = walletTransaction; } else { // There was change back to us, or this tx was purely a spend back to ourselves (perhaps for // anonymization purposes). Log.Info(" ->unspent"); Debug.Assert(!Unspent.ContainsKey(walletTransaction.Hash), "TX in both pending and unspent pools"); Unspent[walletTransaction.Hash] = walletTransaction; } } else if (sideChain) { // The transaction was accepted on an inactive side chain, but not yet by the best chain. Log.Info(" ->inactive"); // It's OK for this to already be in the inactive WalletPool because there can be multiple independent side // chains in which it appears: // // b1 --> b2 // \-> b3 // \-> b4 (at this point it's already present in 'inactive' if (_inactive.ContainsKey(walletTransaction.Hash)) Log.Info("Saw a transaction be incorporated into multiple independent side chains"); _inactive[walletTransaction.Hash] = walletTransaction; // Put it back into the pending WalletPool, because 'pending' means 'waiting to be included in best chain'. Pending[walletTransaction.Hash] = walletTransaction; } } else { if (!reorg) { // Mark the tx as appearing in this block so we can find it later after a re-org. transaction.AddBlockAppearance(storedBlock); } // This TX didn't originate with us. It could be sending us coins and also spending our own coins if keys // are being shared between different wallets. if (sideChain) { Log.Info(" ->inactive"); _inactive[transaction.Hash] = transaction; } else if (bestChain) { ProcessTransactionFromBestChain(transaction); } } Log.InfoFormat("Balance is now: {0}", Utils.BitcoinValueToFriendlyString(GetBalance())); // Inform anyone interested that we have new coins. Note: we may be re-entered by the event listener, // so we must not make assumptions about our state after this loop returns! For example, // the balance we just received might already be spent! if (!reorg && bestChain && valueDifference > 0 && CoinsReceived != null) { lock (CoinsReceived) { CoinsReceived(this, new WalletCoinsReceivedEventArgs(transaction, previousBalance, GetBalance())); } } } }