/// <summary> /// Called as part of connecting a block when the new block results in a different chain having higher total work. /// </summary> /// <exception cref="BlockStoreException"/> /// <exception cref="VerificationException"/> private void HandleNewBestChain(StoredBlock newChainHead) { // This chain has overtaken the one we currently believe is best. Reorganize is required. // // Firstly, calculate the block at which the chain diverged. We only need to examine the // chain from beyond this block to find differences. var splitPoint = FindSplit(newChainHead, _chainHead); _log.InfoFormat("Re-organize after split at height {0}", splitPoint.Height); _log.InfoFormat("Old chain head: {0}", _chainHead.Header.HashAsString); _log.InfoFormat("New chain head: {0}", newChainHead.Header.HashAsString); _log.InfoFormat("Split at block: {0}", splitPoint.Header.HashAsString); // Then build a list of all blocks in the old part of the chain and the new part. var oldBlocks = GetPartialChain(_chainHead, splitPoint); var newBlocks = GetPartialChain(newChainHead, splitPoint); // Now inform the wallet. This is necessary so the set of currently active transactions (that we can spend) // can be updated to take into account the re-organize. We might also have received new coins we didn't have // before and our previous spends might have been undone. foreach (var wallet in _wallets) { wallet.Reorganize(oldBlocks, newBlocks); } // Update the pointer to the best known block. ChainHead = newChainHead; }
/// <exception cref="BitCoinSharp.BlockStoreException" /> private void CreateNewStore(NetworkParameters @params, FileInfo file) { // Create a new block store if the file wasn't found or anything went wrong whilst reading. _blockCache.Clear(); try { if (_channel != null) { _channel.Dispose(); _channel = null; } file.Delete(); _channel = file.Create(); // Create fresh. _channel.Write(_fileFormatVersion); } catch (IOException e1) { // We could not load a block store nor could we create a new one! throw new BlockStoreException(e1); } try { // Set up the genesis block. When we start out fresh, it is by definition the top of the chain. var genesis = @params.GenesisBlock.CloneAsHeader(); var storedGenesis = new StoredBlock(genesis, genesis.GetWork(), 0); _chainHead = new Sha256Hash(storedGenesis.Header.Hash); _channel.Write(_chainHead.Hash); Put(storedGenesis); } catch (IOException e) { throw new BlockStoreException(e); } }
/// <summary> /// Locates the point in the chain at which newStoredBlock and chainHead diverge. Returns null if no split point was /// found (ie they are part of the same chain). /// </summary> /// <exception cref="BitCoinSharp.BlockStoreException" /> private StoredBlock FindSplit(StoredBlock newChainHead, StoredBlock chainHead) { var currentChainCursor = chainHead; var newChainCursor = newChainHead; // Loop until we find the block both chains have in common. Example: // // A -> B -> C -> D // \--> E -> F -> G // // findSplit will return block B. chainHead = D and newChainHead = G. while (!currentChainCursor.Equals(newChainCursor)) { if (currentChainCursor.Height > newChainCursor.Height) { currentChainCursor = currentChainCursor.GetPrev(_blockStore); Debug.Assert(newChainCursor != null, "Attempt to follow an orphan chain"); } else { newChainCursor = newChainCursor.GetPrev(_blockStore); Debug.Assert(currentChainCursor != null, "Attempt to follow an orphan chain"); } } return(currentChainCursor); }
/// <summary> /// Constructs a BlockChain connected to the given wallet and store. To obtain a <see cref="Wallet">Wallet</see> you can construct /// one from scratch, or you can deserialize a saved wallet from disk using <see cref="Wallet.LoadFromFile(System.IO.FileInfo)">Wallet.LoadFromFile(System.IO.FileInfo)</see><p /> /// </summary> /// <remarks> /// For the store you can use a <see cref="MemoryBlockStore">MemoryBlockStore</see> if you don't care about saving the downloaded data, or a /// <see cref="BoundedOverheadBlockStore">BoundedOverheadBlockStore</see> if you'd like to ensure fast start-up the next time you run the program. /// </remarks> public BlockChain(NetworkParameters @params, Wallet wallet, IBlockStore blockStore) { _blockStore = blockStore; _chainHead = blockStore.GetChainHead(); _log.InfoFormat("chain head is:{0}{1}", Environment.NewLine, _chainHead.Header); _params = @params; _wallet = wallet; }
/// <summary> /// Constructs a BlockChain connected to the given list of wallets and a store. /// </summary> /// <exception cref="BlockStoreException"/> public BlockChain(NetworkParameters @params, IEnumerable <Wallet> wallets, IBlockStore blockStore) { _blockStore = blockStore; _chainHead = blockStore.GetChainHead(); _log.InfoFormat("chain head is:{0}{1}", Environment.NewLine, _chainHead.Header); _params = @params; _wallets = new List <Wallet>(wallets); }
/// <summary> /// Adds the given block to the internal serializable set of blocks in which this transaction appears. This is /// used by the wallet to ensure transactions that appear on side chains are recorded properly even though the /// block stores do not save the transaction data at all. /// </summary> internal void AddBlockAppearance(StoredBlock block) { if (AppearsIn == null) { AppearsIn = new HashSet <StoredBlock>(); } AppearsIn.Add(block); }
public MemoryBlockStore(NetworkParameters @params) { _blockMap = new Dictionary<ByteBuffer, byte[]>(); // Insert the genesis block. var genesisHeader = @params.GenesisBlock.CloneAsHeader(); var storedGenesis = new StoredBlock(genesisHeader, genesisHeader.GetWork(), 0); Put(storedGenesis); SetChainHead(storedGenesis); }
public MemoryBlockStore(NetworkParameters @params) { _blockMap = new Dictionary <ByteBuffer, byte[]>(); // Insert the genesis block. var genesisHeader = @params.GenesisBlock.CloneAsHeader(); var storedGenesis = new StoredBlock(genesisHeader, genesisHeader.GetWork(), 0); Put(storedGenesis); SetChainHead(storedGenesis); }
/// <exception cref="BitCoinSharp.BlockStoreException" /> public void Put(StoredBlock block) { lock (this) { var hash = block.Header.Hash; using (var bos = new MemoryStream()) { var oos = new BinaryFormatter(); oos.Serialize(bos, block); _blockMap[ByteBuffer.Wrap(hash)] = bos.ToArray(); } } }
/// <exception cref="BitCoinSharp.BlockStoreException" /> public BoundedOverheadBlockStore(NetworkParameters @params, FileInfo file) { _params = @params; _notFoundMarker = new StoredBlock(null, null, uint.MaxValue); try { Load(file); } catch (IOException e) { _log.Error("failed to load block store from file", e); CreateNewStore(@params, file); } }
/// <exception cref="BitCoinSharp.BlockStoreException" /> public StoredBlock Get(byte[] hashBytes) { lock (this) { // Check the memory cache first. var hash = new Sha256Hash(hashBytes); StoredBlock fromMem; if (_blockCache.TryGetValue(hash, out fromMem)) { return(fromMem); } if (_notFoundCache.TryGetValue(hash, out fromMem) && fromMem == _notFoundMarker) { return(null); } try { var fromDisk = GetRecord(hash); StoredBlock block = null; if (fromDisk == null) { _notFoundCache[hash] = _notFoundMarker; while (_notFoundCache.Count > 100) { _notFoundCache.RemoveAt(0); } } else { block = fromDisk.ToStoredBlock(_params); _blockCache[hash] = block; while (_blockCache.Count > 100) { _blockCache.RemoveAt(0); } } return(block); } catch (IOException e) { throw new BlockStoreException(e); } catch (ProtocolException e) { throw new BlockStoreException(e); } } }
/// <exception cref="BitCoinSharp.VerificationException" /> private void SendTransactionsToWallet(StoredBlock block, NewBlockType blockType, IEnumerable <Transaction> newTransactions) { // Scan the transactions to find out if any mention addresses we own. foreach (var tx in newTransactions) { try { ScanTransaction(block, tx, blockType); } catch (ScriptException e) { // We don't want scripts we don't understand to break the block chain, // so just note that this tx was not scanned here and continue. _log.WarnFormat("Failed to parse a script: {0}", e); } } }
/// <summary> /// Returns the set of contiguous blocks between 'higher' and 'lower'. Higher is included, lower is not. /// </summary> /// <exception cref="BitCoinSharp.BlockStoreException" /> private IList <StoredBlock> GetPartialChain(StoredBlock higher, StoredBlock lower) { Debug.Assert(higher.Height > lower.Height); var results = new LinkedList <StoredBlock>(); var cursor = higher; while (true) { results.AddLast(cursor); cursor = cursor.GetPrev(_blockStore); Debug.Assert(cursor != null, "Ran off the end of the chain"); if (cursor.Equals(lower)) { break; } } return(results.ToList()); }
/// <exception cref="BitCoinSharp.BlockStoreException" /> public void SetChainHead(StoredBlock chainHead) { lock (this) { try { var hash = chainHead.Header.Hash; _chainHead = new Sha256Hash(hash); // Write out new hash to the first 32 bytes of the file past one (first byte is version number). _stream.Seek(1, SeekOrigin.Begin); _stream.Write(hash, 0, hash.Length); } catch (IOException e) { throw new BlockStoreException(e); } } }
/// <exception cref="BitCoinSharp.BlockStoreException" /> /// <exception cref="BitCoinSharp.VerificationException" /> private void ConnectBlock(StoredBlock newStoredBlock, StoredBlock storedPrev, IEnumerable <Transaction> newTransactions) { if (storedPrev.Equals(_chainHead)) { // This block connects to the best known block, it is a normal continuation of the system. ChainHead = newStoredBlock; _log.DebugFormat("Chain is now {0} blocks high", _chainHead.Height); if (newTransactions != null) { SendTransactionsToWallet(newStoredBlock, NewBlockType.BestChain, newTransactions); } } else { // This block connects to somewhere other than the top of the best known chain. We treat these differently. // // Note that we send the transactions to the wallet FIRST, even if we're about to re-organize this block // to become the new best chain head. This simplifies handling of the re-org in the Wallet class. var haveNewBestChain = newStoredBlock.MoreWorkThan(_chainHead); if (haveNewBestChain) { _log.Info("Block is causing a re-organize"); } else { var splitPoint = FindSplit(newStoredBlock, _chainHead); var splitPointHash = splitPoint != null ? splitPoint.Header.HashAsString : "?"; _log.InfoFormat("Block forks the chain at {0}, but it did not cause a reorganize:{1}{2}", splitPointHash, Environment.NewLine, newStoredBlock); } // We may not have any transactions if we received only a header. That never happens today but will in // future when GetHeaders is used as an optimization. if (newTransactions != null) { SendTransactionsToWallet(newStoredBlock, NewBlockType.SideChain, newTransactions); } if (haveNewBestChain) { HandleNewBestChain(newStoredBlock); } } }
/// <exception cref="VerificationException"/> private static void SendTransactionsToWallet(StoredBlock block, NewBlockType blockType, IEnumerable <KeyValuePair <Wallet, List <Transaction> > > newTransactions) { foreach (var item in newTransactions) { try { foreach (var tx in item.Value) { item.Key.Receive(tx, block, blockType); } } catch (ScriptException e) { // We don't want scripts we don't understand to break the block chain so just note that this tx was // not scanned here and continue. _log.WarnFormat("Failed to parse a script: {0}", e); } } }
/// <exception cref="BitCoinSharp.BlockStoreException" /> public void Put(StoredBlock block) { lock (this) { try { var hash = new Sha256Hash(block.Header.Hash); Debug.Assert(!_blockMap.ContainsKey(hash), "Attempt to insert duplicate"); // Append to the end of the file. The other fields in StoredBlock will be recalculated when it's reloaded. var bytes = block.Header.BitcoinSerialize(); _stream.Write(bytes); _stream.Flush(); _blockMap[hash] = block; } catch (IOException e) { throw new BlockStoreException(e); } } }
// This should be static but the language does not allow for it. /// <exception cref="System.IO.IOException" /> public void Write(Stream channel, StoredBlock block) { using (var buf = ByteBuffer.Allocate(Size)) { buf.PutInt((int)block.Height); var chainWorkBytes = block.ChainWork.ToByteArray(); Debug.Assert(chainWorkBytes.Length <= _chainWorkBytes, "Ran out of space to store chain work!"); if (chainWorkBytes.Length < _chainWorkBytes) { // Pad to the right size. buf.Put(_emptyBytes, 0, _chainWorkBytes - chainWorkBytes.Length); } buf.Put(chainWorkBytes); buf.Put(block.Header.BitcoinSerialize()); buf.Position = 0; channel.Position = channel.Length; channel.Write(buf.ToArray()); channel.Position = channel.Length - Size; } }
/// <exception cref="BitCoinSharp.BlockStoreException" /> public void SetChainHead(StoredBlock chainHead) { lock (this) { try { var hash = chainHead.Header.Hash; _chainHead = new Sha256Hash(hash); // Write out new hash to the first 32 bytes of the file past one (first byte is version number). var originalPos = _channel.Position; _channel.Position = 1; _channel.Write(hash, 0, hash.Length); _channel.Position = originalPos; } catch (IOException e) { throw new BlockStoreException(e); } } }
/// <exception cref="BitCoinSharp.BlockStoreException" /> public void Put(StoredBlock block) { lock (this) { try { var hash = new Sha256Hash(block.Header.Hash); // Append to the end of the file. _dummyRecord.Write(_channel, block); _blockCache[hash] = block; while (_blockCache.Count > 100) { _blockCache.RemoveAt(0); } } catch (IOException e) { throw new BlockStoreException(e); } } }
/// <exception cref="BitCoinSharp.ScriptException" /> /// <exception cref="BitCoinSharp.VerificationException" /> private void ScanTransaction(StoredBlock block, Transaction tx, NewBlockType blockType) { var shouldReceive = false; foreach (var output in tx.Outputs) { // TODO: Handle more types of outputs, not just regular to address outputs. if (output.ScriptPubKey.IsSentToIp) { return; } // This is not thread safe as a key could be removed between the call to isMine and receive. if (output.IsMine(_wallet)) { shouldReceive = true; } } // Coinbase transactions don't have anything useful in their inputs (as they create coins out of thin air). if (!tx.IsCoinBase) { foreach (var i in tx.Inputs) { var pubkey = i.ScriptSig.PubKey; // This is not thread safe as a key could be removed between the call to isPubKeyMine and receive. if (_wallet.IsPubKeyMine(pubkey)) { shouldReceive = true; } } } if (shouldReceive) { _wallet.Receive(tx, block, blockType); } }
/// <exception cref="BitCoinSharp.BlockStoreException" /> public void SetChainHead(StoredBlock chainHead) { _chainHead = chainHead; }
/// <summary> /// Constructs a BlockChain connected to the given list of wallets and a store. /// </summary> /// <exception cref="BlockStoreException"/> public BlockChain(NetworkParameters @params, IEnumerable<Wallet> wallets, IBlockStore blockStore) { _blockStore = blockStore; _chainHead = blockStore.GetChainHead(); _log.InfoFormat("chain head is:{0}{1}", Environment.NewLine, _chainHead.Header); _params = @params; _wallets = new List<Wallet>(wallets); }
/// <exception cref="System.IO.IOException" /> /// <exception cref="BitCoinSharp.BlockStoreException" /> private void Load(FileInfo file) { _log.InfoFormat("Reading block store from {0}", file); using (var input = file.OpenRead()) { // Read a version byte. var version = input.Read(); if (version == -1) { // No such file or the file was empty. throw new FileNotFoundException(file.Name + " does not exist or is empty"); } if (version != 1) { throw new BlockStoreException("Bad version number: " + version); } // Chain head pointer is the first thing in the file. var chainHeadHash = new byte[32]; input.Read(chainHeadHash); _chainHead = new Sha256Hash(chainHeadHash); _log.InfoFormat("Read chain head from disk: {0}", _chainHead); var now = Environment.TickCount; // Rest of file is raw block headers. var headerBytes = new byte[Block.HeaderSize]; try { while (true) { // Read a block from disk. if (input.Read(headerBytes) < 80) { // End of file. break; } // Parse it. var b = new Block(_params, headerBytes); // Look up the previous block it connects to. var prev = Get(b.PrevBlockHash); StoredBlock s; if (prev == null) { // First block in the stored chain has to be treated specially. if (b.Equals(_params.GenesisBlock)) { s = new StoredBlock(_params.GenesisBlock.CloneAsHeader(), _params.GenesisBlock.GetWork(), 0); } else { throw new BlockStoreException("Could not connect " + Utils.BytesToHexString(b.Hash) + " to " + Utils.BytesToHexString(b.PrevBlockHash)); } } else { // Don't try to verify the genesis block to avoid upsetting the unit tests. b.Verify(); // Calculate its height and total chain work. s = prev.Build(b); } // Save in memory. _blockMap[new Sha256Hash(b.Hash)] = s; } } catch (ProtocolException e) { // Corrupted file. throw new BlockStoreException(e); } catch (VerificationException e) { // Should not be able to happen unless the file contains bad blocks. throw new BlockStoreException(e); } var elapsed = Environment.TickCount - now; _log.InfoFormat("Block chain read complete in {0}ms", elapsed); } }
/// <exception cref="BitCoinSharp.ScriptException" /> /// <exception cref="BitCoinSharp.VerificationException" /> private void ScanTransaction(StoredBlock block, Transaction tx, NewBlockType blockType) { var shouldReceive = false; foreach (var output in tx.Outputs) { // TODO: Handle more types of outputs, not just regular to address outputs. if (output.ScriptPubKey.IsSentToIp) return; // This is not thread safe as a key could be removed between the call to isMine and receive. if (output.IsMine(_wallet)) { shouldReceive = true; } } // Coinbase transactions don't have anything useful in their inputs (as they create coins out of thin air). if (!tx.IsCoinBase) { foreach (var i in tx.Inputs) { var pubkey = i.ScriptSig.PubKey; // This is not thread safe as a key could be removed between the call to isPubKeyMine and receive. if (_wallet.IsPubKeyMine(pubkey)) { shouldReceive = true; } } } if (shouldReceive) _wallet.Receive(tx, block, blockType); }
/// <summary> /// Throws an exception if the blocks difficulty is not correct. /// </summary> /// <exception cref="BitCoinSharp.BlockStoreException" /> /// <exception cref="BitCoinSharp.VerificationException" /> private void CheckDifficultyTransitions(StoredBlock storedPrev, StoredBlock storedNext) { var prev = storedPrev.Header; var next = storedNext.Header; // Is this supposed to be a difficulty transition point? if ((storedPrev.Height + 1) % _params.Interval != 0) { // No ... so check the difficulty didn't actually change. if (next.DifficultyTarget != prev.DifficultyTarget) { throw new VerificationException("Unexpected change in difficulty at height " + storedPrev.Height + ": " + next.DifficultyTarget.ToString("x") + " vs " + prev.DifficultyTarget.ToString("x")); } return; } // We need to find a block far back in the chain. It's OK that this is expensive because it only occurs every // two weeks after the initial block chain download. var now = Environment.TickCount; var cursor = _blockStore.Get(prev.Hash); for (var i = 0; i < _params.Interval - 1; i++) { if (cursor == null) { // This should never happen. If it does, it means we are following an incorrect or busted chain. throw new VerificationException( "Difficulty transition point but we did not find a way back to the genesis block."); } cursor = _blockStore.Get(cursor.Header.PrevBlockHash); } _log.InfoFormat("Difficulty transition traversal took {0}ms", Environment.TickCount - now); var blockIntervalAgo = cursor.Header; var timespan = (int)(prev.Time - blockIntervalAgo.Time); // Limit the adjustment step. if (timespan < _params.TargetTimespan / 4) { timespan = _params.TargetTimespan / 4; } if (timespan > _params.TargetTimespan * 4) { timespan = _params.TargetTimespan * 4; } var newDifficulty = Utils.DecodeCompactBits(blockIntervalAgo.DifficultyTarget); newDifficulty = newDifficulty.Multiply(BigInteger.ValueOf(timespan)); newDifficulty = newDifficulty.Divide(BigInteger.ValueOf(_params.TargetTimespan)); if (newDifficulty.CompareTo(_params.ProofOfWorkLimit) > 0) { _log.WarnFormat("Difficulty hit proof of work limit: {0}", newDifficulty.ToString(16)); newDifficulty = _params.ProofOfWorkLimit; } var accuracyBytes = (int)(next.DifficultyTarget >> 24) - 3; var receivedDifficulty = next.GetDifficultyTargetAsInteger(); // The calculated difficulty is to a higher precision than received, so reduce here. var mask = BigInteger.ValueOf(0xFFFFFF).ShiftLeft(accuracyBytes * 8); newDifficulty = newDifficulty.And(mask); if (newDifficulty.CompareTo(receivedDifficulty) != 0) { throw new VerificationException("Network provided difficulty bits do not match what was calculated: " + receivedDifficulty.ToString(16) + " vs " + newDifficulty.ToString(16)); } }
/// <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); } }
/// <exception cref="BitCoinSharp.VerificationException" /> private void SendTransactionsToWallet(StoredBlock block, NewBlockType blockType, IEnumerable<Transaction> newTransactions) { // Scan the transactions to find out if any mention addresses we own. foreach (var tx in newTransactions) { try { ScanTransaction(block, tx, blockType); } catch (ScriptException e) { // We don't want scripts we don't understand to break the block chain, // so just note that this tx was not scanned here and continue. _log.WarnFormat("Failed to parse a script: {0}", e); } } }
// This should be static but the language does not allow for it. /// <exception cref="System.IO.IOException" /> public void Write(Stream channel, StoredBlock block) { using (var buf = ByteBuffer.Allocate(Size)) { buf.PutInt((int) block.Height); var chainWorkBytes = block.ChainWork.ToByteArray(); Debug.Assert(chainWorkBytes.Length <= _chainWorkBytes, "Ran out of space to store chain work!"); if (chainWorkBytes.Length < _chainWorkBytes) { // Pad to the right size. buf.Put(_emptyBytes, 0, _chainWorkBytes - chainWorkBytes.Length); } buf.Put(chainWorkBytes); buf.Put(block.Header.BitcoinSerialize()); buf.Position = 0; channel.Position = channel.Length; channel.Write(buf.ToArray()); channel.Position = channel.Length - Size; } }
/// <summary> /// Locates the point in the chain at which newStoredBlock and chainHead diverge. Returns null if no split point was /// found (ie they are part of the same chain). /// </summary> /// <exception cref="BlockStoreException"/> private StoredBlock FindSplit(StoredBlock newChainHead, StoredBlock chainHead) { var currentChainCursor = chainHead; var newChainCursor = newChainHead; // Loop until we find the block both chains have in common. Example: // // A -> B -> C -> D // \--> E -> F -> G // // findSplit will return block B. chainHead = D and newChainHead = G. while (!currentChainCursor.Equals(newChainCursor)) { if (currentChainCursor.Height > newChainCursor.Height) { currentChainCursor = currentChainCursor.GetPrev(_blockStore); Debug.Assert(newChainCursor != null, "Attempt to follow an orphan chain"); } else { newChainCursor = newChainCursor.GetPrev(_blockStore); Debug.Assert(currentChainCursor != null, "Attempt to follow an orphan chain"); } } return currentChainCursor; }
/// <summary> /// Throws an exception if the blocks difficulty is not correct. /// </summary> /// <exception cref="BlockStoreException"/> /// <exception cref="VerificationException"/> private void CheckDifficultyTransitions(StoredBlock storedPrev, StoredBlock storedNext) { var prev = storedPrev.Header; var next = storedNext.Header; // Is this supposed to be a difficulty transition point? if ((storedPrev.Height + 1)%_params.Interval != 0) { // No ... so check the difficulty didn't actually change. if (next.DifficultyTarget != prev.DifficultyTarget) throw new VerificationException("Unexpected change in difficulty at height " + storedPrev.Height + ": " + next.DifficultyTarget.ToString("x") + " vs " + prev.DifficultyTarget.ToString("x")); return; } // We need to find a block far back in the chain. It's OK that this is expensive because it only occurs every // two weeks after the initial block chain download. var now = Environment.TickCount; var cursor = _blockStore.Get(prev.Hash); for (var i = 0; i < _params.Interval - 1; i++) { if (cursor == null) { // This should never happen. If it does, it means we are following an incorrect or busted chain. throw new VerificationException( "Difficulty transition point but we did not find a way back to the genesis block."); } cursor = _blockStore.Get(cursor.Header.PrevBlockHash); } _log.DebugFormat("Difficulty transition traversal took {0}ms", Environment.TickCount - now); var blockIntervalAgo = cursor.Header; var timespan = (int) (prev.TimeSeconds - blockIntervalAgo.TimeSeconds); // Limit the adjustment step. if (timespan < _params.TargetTimespan/4) timespan = _params.TargetTimespan/4; if (timespan > _params.TargetTimespan*4) timespan = _params.TargetTimespan*4; var newDifficulty = Utils.DecodeCompactBits(blockIntervalAgo.DifficultyTarget); newDifficulty = newDifficulty.Multiply(BigInteger.ValueOf(timespan)); newDifficulty = newDifficulty.Divide(BigInteger.ValueOf(_params.TargetTimespan)); if (newDifficulty.CompareTo(_params.ProofOfWorkLimit) > 0) { _log.DebugFormat("Difficulty hit proof of work limit: {0}", newDifficulty.ToString(16)); newDifficulty = _params.ProofOfWorkLimit; } var accuracyBytes = (int) (next.DifficultyTarget >> 24) - 3; var receivedDifficulty = next.GetDifficultyTargetAsInteger(); // The calculated difficulty is to a higher precision than received, so reduce here. var mask = BigInteger.ValueOf(0xFFFFFF).ShiftLeft(accuracyBytes*8); newDifficulty = newDifficulty.And(mask); if (newDifficulty.CompareTo(receivedDifficulty) != 0) throw new VerificationException("Network provided difficulty bits do not match what was calculated: " + receivedDifficulty.ToString(16) + " vs " + newDifficulty.ToString(16)); }
/// <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())); } } } }
/// <summary> /// Adds the given block to the internal serializable set of blocks in which this transaction appears. This is /// used by the wallet to ensure transactions that appear on side chains are recorded properly even though the /// block stores do not save the transaction data at all. /// </summary> internal void AddBlockAppearance(StoredBlock block) { if (AppearsIn == null) { AppearsIn = new HashSet<StoredBlock>(); } AppearsIn.Add(block); }
/// <summary> /// Returns true if this objects chainWork is higher than the others. /// </summary> public bool MoreWorkThan(StoredBlock other) { return(_chainWork.CompareTo(other._chainWork) > 0); }
/// <exception cref="VerificationException"/> private static void SendTransactionsToWallet(StoredBlock block, NewBlockType blockType, IDictionary<Wallet, List<Transaction>> newTransactions) { foreach (var item in newTransactions) { try { foreach (var tx in item.Value) { item.Key.Receive(tx, block, blockType); } } catch (ScriptException e) { // We don't want scripts we don't understand to break the block chain so just note that this tx was // not scanned here and continue. _log.WarnFormat("Failed to parse a script: {0}", e); } } }
/// <summary> /// Returns true if this objects chainWork is higher than the others. /// </summary> public bool MoreWorkThan(StoredBlock other) { return _chainWork.CompareTo(other._chainWork) > 0; }
/// <exception cref="BlockStoreException"/> /// <exception cref="VerificationException"/> private void ConnectBlock(StoredBlock newStoredBlock, StoredBlock storedPrev, IDictionary<Wallet, List<Transaction>> newTransactions) { if (storedPrev.Equals(_chainHead)) { // This block connects to the best known block, it is a normal continuation of the system. ChainHead = newStoredBlock; _log.DebugFormat("Chain is now {0} blocks high", _chainHead.Height); if (newTransactions != null) SendTransactionsToWallet(newStoredBlock, NewBlockType.BestChain, newTransactions); } else { // This block connects to somewhere other than the top of the best known chain. We treat these differently. // // Note that we send the transactions to the wallet FIRST, even if we're about to re-organize this block // to become the new best chain head. This simplifies handling of the re-org in the Wallet class. var haveNewBestChain = newStoredBlock.MoreWorkThan(_chainHead); if (haveNewBestChain) { _log.Info("Block is causing a re-organize"); } else { var splitPoint = FindSplit(newStoredBlock, _chainHead); var splitPointHash = splitPoint != null ? splitPoint.Header.HashAsString : "?"; _log.InfoFormat("Block forks the chain at {0}, but it did not cause a reorganize:{1}{2}", splitPointHash, Environment.NewLine, newStoredBlock); } // We may not have any transactions if we received only a header. That never happens today but will in // future when GetHeaders is used as an optimization. if (newTransactions != null) { SendTransactionsToWallet(newStoredBlock, NewBlockType.SideChain, newTransactions); } if (haveNewBestChain) HandleNewBestChain(newStoredBlock); } }
/// <summary> /// Returns the set of contiguous blocks between 'higher' and 'lower'. Higher is included, lower is not. /// </summary> /// <exception cref="BlockStoreException"/> private IList<StoredBlock> GetPartialChain(StoredBlock higher, StoredBlock lower) { Debug.Assert(higher.Height > lower.Height); var results = new LinkedList<StoredBlock>(); var cursor = higher; while (true) { results.AddLast(cursor); cursor = cursor.GetPrev(_blockStore); Debug.Assert(cursor != null, "Ran off the end of the chain"); if (cursor.Equals(lower)) break; } return results.ToList(); }