A BlockChain holds a series of Block objects, links them together, and knows how to verify that the chain follows the rules of the NetworkParameters for this chain.
A BlockChain requires a Wallet to receive transactions that it finds during the initial download. However, if you don't care about this, you can just pass in an empty wallet and nothing bad will happen.

A newly constructed BlockChain is empty. To fill it up, use a Peer object to download the chain from the network.

Notes

The 'chain' can actually be a tree although in normal operation it can be thought of as a simple list. In such a situation there are multiple stories of the economy competing to become the one true consensus. This can happen naturally when two miners solve a block within a few seconds of each other, or it can happen when the chain is under attack.

A reference to the head block of every chain is stored. If you can reach the genesis block by repeatedly walking through the prevBlock pointers, then we say this is a full chain. If you cannot reach the genesis block we say it is an orphan chain.

Orphan chains can occur when blocks are solved and received during the initial block chain download, or if we connect to a peer that doesn't send us blocks in order.

Esempio n. 1
0
        /// <exception cref="VerificationException"/>
        /// <exception cref="ScriptException"/>
        private void Receive(Transaction tx, StoredBlock block, BlockChain.NewBlockType blockType, bool reorg)
        {
            lock (this)
            {
                // Runs in a peer thread.
                var prevBalance = GetBalance();

                var txHash = tx.Hash;

                var bestChain = blockType == BlockChain.NewBlockType.BestChain;
                var sideChain = blockType == BlockChain.NewBlockType.SideChain;

                var valueSentFromMe = tx.GetValueSentFromMe(this);
                var valueSentToMe = tx.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), tx.HashAsString);
                }

                // If this transaction is already in the wallet we may need to move it into a different pool. At the very
                // least we need to ensure we're manipulating the canonical object rather than a duplicate.
                Transaction wtx;
                if (Pending.TryGetValue(txHash, out wtx))
                {
                    Pending.Remove(txHash);
                    _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.
                    wtx.AddBlockAppearance(block);
                    if (bestChain)
                    {
                        if (valueSentToMe.Equals(0))
                        {
                            // There were no change transactions so this tx is fully spent.
                            _log.Info("  ->spent");
                            Debug.Assert(!Spent.ContainsKey(wtx.Hash), "TX in both pending and spent pools");
                            Spent[wtx.Hash] = wtx;
                        }
                        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(wtx.Hash), "TX in both pending and unspent pools");
                            Unspent[wtx.Hash] = wtx;
                        }
                    }
                    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 pool 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(wtx.Hash))
                            _log.Info("Saw a transaction be incorporated into multiple independent side chains");
                        _inactive[wtx.Hash] = wtx;
                        // Put it back into the pending pool, because 'pending' means 'waiting to be included in best chain'.
                        Pending[wtx.Hash] = wtx;
                    }
                }
                else
                {
                    if (!reorg)
                    {
                        // Mark the tx as appearing in this block so we can find it later after a re-org.
                        tx.AddBlockAppearance(block);
                    }
                    // 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[tx.Hash] = tx;
                    }
                    else if (bestChain)
                    {
                        ProcessTxFromBestChain(tx);
                    }
                }

                _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(tx, prevBalance, GetBalance()));
                    }
                }
            }
        }
Esempio n. 2
0
 /// <summary>
 /// Called by the <see cref="BlockChain"/> when we receive a new block that sends coins to one of our addresses or
 /// spends coins from one of our addresses (note that a single transaction can do both).
 /// </summary>
 /// <remarks>
 /// This is necessary for the internal book-keeping Wallet does. When a transaction is received that sends us
 /// coins it is added to a pool so we can use it later to create spends. When a transaction is received that
 /// consumes outputs they are marked as spent so they won't be used in future.<p/>
 /// A transaction that spends our own coins can be received either because a spend we created was accepted by the
 /// network and thus made it into a block, or because our keys are being shared between multiple instances and
 /// some other node spent the coins instead. We still have to know about that to avoid accidentally trying to
 /// double spend.<p/>
 /// A transaction may be received multiple times if is included into blocks in parallel chains. The blockType
 /// parameter describes whether the containing block is on the main/best chain or whether it's on a presently
 /// inactive side chain. We must still record these transactions and the blocks they appear in because a future
 /// block might change which chain is best causing a reorganize. A re-org can totally change our balance!
 /// </remarks>
 /// <exception cref="VerificationException"/>
 /// <exception cref="ScriptException"/>
 internal void Receive(Transaction tx, StoredBlock block, BlockChain.NewBlockType blockType)
 {
     lock (this)
     {
         Receive(tx, block, blockType, false);
     }
 }
        /// <summary>
        /// Creates a PeerGroup with the given parameters. The connectionDelayMillis parameter controls how long the
        /// PeerGroup will wait between attempts to connect to nodes or read from any added peer discovery sources.
        /// </summary>
        public PeerGroup(IBlockStore blockStore, NetworkParameters @params, BlockChain chain, int connectionDelayMillis)
        {
            _blockStore = blockStore;
            _params = @params;
            _chain = chain;
            _connectionDelayMillis = connectionDelayMillis;

            _inactives = new LinkedBlockingQueue<PeerAddress>();
            _peers = new SynchronizedHashSet<Peer>();
            _peerDiscoverers = new SynchronizedHashSet<IPeerDiscovery>();
            _peerPool = new ThreadPoolExecutor(_coreThreads, _defaultConnections,
                                               TimeSpan.FromSeconds(_threadKeepAliveSeconds),
                                               new LinkedBlockingQueue<IRunnable>(1),
                                               new PeerGroupThreadFactory());
        }
 /// <summary>
 /// Creates a PeerGroup with the given parameters and a default 5 second connection timeout.
 /// </summary>
 public PeerGroup(IBlockStore blockStore, NetworkParameters @params, BlockChain chain)
     : this(blockStore, @params, chain, DefaultConnectionDelayMillis)
 {
 }
Esempio n. 5
0
 /// <summary>
 /// Construct a peer that handles the given network connection and reads/writes from the given block chain. Note that
 /// communication won't occur until you call connect().
 /// </summary>
 public Peer(NetworkParameters @params, PeerAddress address, BlockChain blockChain)
     : this(@params, address, 0, blockChain)
 {
 }
Esempio n. 6
0
 /// <summary>
 /// Creates a PeerGroup with the given parameters and a default 5 second connection timeout.
 /// </summary>
 public PeerGroup(IBlockStore blockStore, NetworkParameters @params, BlockChain chain)
     : this(blockStore, @params, chain, DefaultConnectionDelayMillis)
 {
 }