public MemoryBlockchain(Block? genesisBlock = null) { this.shutdownToken = new CancellationToken(); this.random = new Random(); // create the key pair that block rewards will be sent to var keyPair = TransactionManager.CreateKeyPair(); this._coinbasePrivateKey = keyPair.Item1; this._coinbasePublicKey = keyPair.Item2; // initialize unit test storage this._storageContext = new MemoryStorageContext(); this._cacheContext = new CacheContext(this._storageContext); // initialize unit test rules this._rules = new UnitTestRules(this._cacheContext); // initialize blockchain calculator this._calculator = new BlockchainCalculator(this._rules, this._cacheContext, this.shutdownToken); // create and mine the genesis block this._genesisBlock = genesisBlock ?? MineEmptyBlock(0); // update genesis blockchain and add to storage this._rules.SetGenesisBlock(this._genesisBlock); this._currentBlockchain = this._rules.GenesisBlockchain; this._genesisChainedBlock = AddBlock(this._genesisBlock, null).Item2; }
public void SetGenesisBlock(Block genesisBlock) { this._genesisBlock = genesisBlock; this._genesisChainedBlock = new ChainedBlock ( blockHash: this._genesisBlock.Hash, previousBlockHash: this._genesisBlock.Header.PreviousBlock, height: 0, totalWork: this._genesisBlock.Header.CalculateWork() ); this._genesisBlockchain = new Data.Blockchain ( blockList: ImmutableList.Create(this._genesisChainedBlock), blockListHashes: ImmutableHashSet.Create(this._genesisBlock.Hash), utxo: ImmutableDictionary.Create<UInt256, UnspentTx>() // genesis block coinbase is not included in utxo, it is unspendable ); }
public Tuple<Block, ChainedBlock> MineAndAddEmptyBlock(ChainedBlock prevChainedBlock) { var block = MineEmptyBlock(prevChainedBlock.BlockHash); return AddBlock(block, prevChainedBlock); }
public Tuple<Block, ChainedBlock> MineAndAddBlock(Block block, ChainedBlock? prevChainedBlock) { var minedHeader = Miner.MineBlockHeader(block.Header, this._rules.HighestTarget); if (minedHeader == null) Assert.Fail("No block could be mined for test data header."); var minedBlock = block.With(Header: minedHeader); return AddBlock(minedBlock, prevChainedBlock); }
public Block CreateEmptyBlock(ChainedBlock prevChainedBlock) { return CreateEmptyBlock(prevChainedBlock.BlockHash); }
public IEnumerable<ChainedBlock> PreviousChainedBlocks(ChainedBlock firstBlock) { var prevChainedBlock = firstBlock; //TODO some kind of hard stop while (true) { yield return prevChainedBlock; var prevBlockHash = prevChainedBlock.PreviousBlockHash; if (prevBlockHash == 0) { break; } prevChainedBlock = this.CacheContext.GetChainedBlock(prevBlockHash); } }
public Data.Blockchain CalculateBlockchainFromExisting(Data.Blockchain currentBlockchain, ChainedBlock targetChainedBlock, out List<MissingDataException> missingData, CancellationToken cancelToken, Action<Data.Blockchain> onProgress = null) { Debug.WriteLine("Winning chained block {0} at height {1}, total work: {2}".Format2(targetChainedBlock.BlockHash.ToHexNumberString(), targetChainedBlock.Height, targetChainedBlock.TotalWork.ToString("X"))); // if the target block is at height 0 don't use currentBlockchain as-is, set it to be the genesis chain for the target block if (targetChainedBlock.Height == 0) { currentBlockchain = new Data.Blockchain ( blockList: ImmutableList.Create(targetChainedBlock), blockListHashes: ImmutableHashSet.Create(targetChainedBlock.BlockHash), utxo: ImmutableDictionary.Create<UInt256, UnspentTx>() ); } // if currentBlockchain is not present find the genesis block for the target block and use it as the current chain else if (currentBlockchain.IsDefault) { // find the genesis block for the target block var genesisBlock = targetChainedBlock; foreach (var prevBlock in PreviousChainedBlocks(targetChainedBlock)) { // cooperative loop this.shutdownToken.ThrowIfCancellationRequested(); cancelToken.ThrowIfCancellationRequested(); genesisBlock = prevBlock; } currentBlockchain = new Data.Blockchain ( blockList: ImmutableList.Create(genesisBlock), blockListHashes: ImmutableHashSet.Create(genesisBlock.BlockHash), utxo: ImmutableDictionary.Create<UInt256, UnspentTx>() ); } missingData = new List<MissingDataException>(); Debug.WriteLine("Searching for last common ancestor between current chainblock and winning chainblock"); List<UInt256> newChainBlockList; var lastCommonAncestorChain = RollbackToLastCommonAncestor(currentBlockchain, targetChainedBlock, cancelToken, out newChainBlockList); Debug.WriteLine("Last common ancestor found at block {0}, height {1:#,##0}, begin processing winning blockchain".Format2(currentBlockchain.RootBlockHash.ToHexNumberString(), currentBlockchain.Height)); // setup statistics var totalTxCount = 0L; var totalInputCount = 0L; var totalStopwatch = new Stopwatch(); var currentBlockCount = 0L; var currentTxCount = 0L; var currentInputCount = 0L; var currentRateStopwatch = new Stopwatch(); totalStopwatch.Start(); currentRateStopwatch.Start(); // with last common ancestor found and utxo rolled back to that point, calculate the new blockchain // use ImmutableList for BlockList during modification var newBlockchain = new Data.Blockchain ( blockList: lastCommonAncestorChain.BlockList, blockListHashes: lastCommonAncestorChain.BlockListHashes, utxo: lastCommonAncestorChain.Utxo ); // start calculating new utxo foreach (var tuple in BlockAndTxLookAhead(newChainBlockList)) { // cooperative loop this.shutdownToken.ThrowIfCancellationRequested(); cancelToken.ThrowIfCancellationRequested(); try { // get block and metadata for next link in blockchain var nextBlock = tuple.Item1; var nextChainedBlock = tuple.Item2; // calculate the new block utxo, double spends will be checked for ImmutableDictionary<UInt256, ImmutableHashSet<int>> newTransactions = ImmutableDictionary.Create<UInt256, ImmutableHashSet<int>>(); long txCount = 0, inputCount = 0; var newUtxo = new MethodTimer(false).Time("CalculateUtxo", () => CalculateUtxo(nextChainedBlock.Height, nextBlock, newBlockchain.Utxo, out newTransactions, out txCount, out inputCount)); var nextBlockchain = new MethodTimer(false).Time("nextBlockchain", () => new Data.Blockchain ( blockList: newBlockchain.BlockList.Add(nextChainedBlock), blockListHashes: newBlockchain.BlockListHashes.Add(nextChainedBlock.BlockHash), utxo: newUtxo )); // validate the block // validation utxo includes all transactions added in the same block, any double spends will have failed the block above validateStopwatch.Start(); new MethodTimer(false).Time("ValidateBlock", () => this.Rules.ValidateBlock(nextBlock, nextBlockchain, newBlockchain.Utxo, newTransactions)); validateStopwatch.Stop(); // create the next link in the new blockchain newBlockchain = nextBlockchain; if (onProgress != null) onProgress(newBlockchain); // blockchain processing statistics currentBlockCount++; currentTxCount += txCount; currentInputCount += inputCount; totalTxCount += txCount; totalInputCount += inputCount; var txInterval = 100.THOUSAND(); if ( newBlockchain.Height % 10.THOUSAND() == 0 || (totalTxCount % txInterval < (totalTxCount - txCount) % txInterval || txCount >= txInterval)) { LogBlockchainProgress(newBlockchain, totalStopwatch, totalTxCount, totalInputCount, currentRateStopwatch, currentBlockCount, currentTxCount, currentInputCount); currentBlockCount = 0; currentTxCount = 0; currentInputCount = 0; currentRateStopwatch.Reset(); currentRateStopwatch.Start(); } } catch (MissingDataException e) { // if there is missing data once blockchain processing has started, return the current progress missingData.Add(e); break; } catch (AggregateException e) { if (e.InnerExceptions.Any(x => !(x is MissingDataException))) { throw; } else { missingData.AddRange(e.InnerExceptions.OfType<MissingDataException>()); break; } } } if (onProgress != null) onProgress(newBlockchain); LogBlockchainProgress(newBlockchain, totalStopwatch, totalTxCount, totalInputCount, currentRateStopwatch, currentBlockCount, currentTxCount, currentInputCount); return newBlockchain; }
public List<UInt256> FindBlocksPastLastCommonAncestor(Data.Blockchain currentBlockchain, ChainedBlock targetChainedBlock, CancellationToken cancelToken) { return new MethodTimer().Time(() => { // take snapshots var newChainedBlock = targetChainedBlock; var newChainBlockList = new List<UInt256>(); // check height difference between chains, they will be roll backed before checking for the last common ancestor var heightDelta = targetChainedBlock.Height - currentBlockchain.Height; // if current chain is shorter, roll new chain back to current chain's height ImmutableList<ChainedBlock> currentChainedBlocks; if (heightDelta > 0) { currentChainedBlocks = currentBlockchain.BlockList; List<ChainedBlock> rolledBackChainedBlocks; newChainedBlock = RollbackChainedBlockToHeight(targetChainedBlock, currentBlockchain.Height, out rolledBackChainedBlocks, this.shutdownToken); newChainBlockList.AddRange(rolledBackChainedBlocks.Select(x => x.BlockHash)); } // if current chain is longer, roll it back to new chain's height else if (heightDelta < 0) { currentChainedBlocks = currentBlockchain.BlockList.GetRange(0, targetChainedBlock.Height + 1); } else { currentChainedBlocks = currentBlockchain.BlockList; } if (newChainedBlock.Height != currentChainedBlocks.Last().Height) throw new Exception(); // with both chains at the same height, roll back to last common ancestor if (newChainedBlock.BlockHash != currentChainedBlocks.Last().BlockHash) { foreach (var tuple in PreviousChainedBlocks(newChainedBlock).Zip(currentChainedBlocks.Reverse<ChainedBlock>(), (prevBlock, currentBlock) => Tuple.Create(prevBlock, currentBlock))) { // cooperative loop this.shutdownToken.ThrowIfCancellationRequested(); cancelToken.ThrowIfCancellationRequested(); newChainedBlock = tuple.Item1; var currentBlock = tuple.Item2; // ensure that height is as expected while looking up previous blocks if (newChainedBlock.Height != currentBlock.Height) { throw new ValidationException(); } if (newChainedBlock.BlockHash == currentBlock.BlockHash) { break; } // keep track of rolled back data on the new blockchain newChainBlockList.Add(newChainedBlock.BlockHash); } } // work list will have last items added first, reverse newChainBlockList.Reverse(); return newChainBlockList; }); }
private void OnChainedBlockModification(UInt256 blockHash, ChainedBlock chainedBlock) { OnChainedBlockAddition(blockHash); }
public BlockchainDaemon(IBlockchainRules rules, CacheContext cacheContext) { this.shutdownToken = new CancellationTokenSource(); this._rules = rules; this._cacheContext = cacheContext; this._calculator = new BlockchainCalculator(this._rules, this._cacheContext, this.shutdownToken.Token); this._winningBlock = this._rules.GenesisChainedBlock; this._winningBlockchain = ImmutableArray.Create(this._rules.GenesisChainedBlock); this.winningBlockchainLock = new ReaderWriterLockSlim(); this._currentBlockchain = this._rules.GenesisBlockchain; this.currentBlockchainLock = new ReaderWriterLockSlim(); //TODO this.lastCurrentBlockchainWrite = Guid.NewGuid(); this.missingBlocks = new ConcurrentSetBuilder<UInt256>(); this.unchainedBlocks = new ConcurrentSetBuilder<UInt256>(); this.missingChainedBlocks = new ConcurrentSet<UInt256>(); this.missingTransactions = new ConcurrentSet<UInt256>(); // write genesis block out to storage this._cacheContext.BlockCache.UpdateValue(this._rules.GenesisBlock.Hash, this._rules.GenesisBlock); this._cacheContext.ChainedBlockCache.UpdateValue(this._rules.GenesisChainedBlock.BlockHash, this._rules.GenesisChainedBlock); // wait for genesis block to be flushed this._cacheContext.BlockCache.WaitForStorageFlush(); this._cacheContext.ChainedBlockCache.WaitForStorageFlush(); // pre-fill the chained block and header caches //this._cacheContext.BlockHeaderCache.FillCache(); this._cacheContext.ChainedBlockCache.FillCache(); // wire up cache events this._cacheContext.BlockHeaderCache.OnAddition += OnBlockHeaderAddition; this._cacheContext.BlockHeaderCache.OnModification += OnBlockHeaderModification; this._cacheContext.BlockCache.OnAddition += OnBlockAddition; this._cacheContext.BlockCache.OnModification += OnBlockModification; this._cacheContext.ChainedBlockCache.OnAddition += OnChainedBlockAddition; this._cacheContext.ChainedBlockCache.OnModification += OnChainedBlockModification; this.unchainedBlocks.UnionWith(this.CacheContext.BlockHeaderCache.GetAllKeys()); this.unchainedBlocks.ExceptWith(this.CacheContext.ChainedBlockCache.GetAllKeys()); // create workers this.chainingWorker = new Worker("BlockchainDaemon.ChainingWorker", ChainingWorker, runOnStart: true, waitTime: TimeSpan.FromSeconds(1), maxIdleTime: TimeSpan.FromSeconds(30)); this.winnerWorker = new Worker("BlockchainDaemon.WinnerWorker", WinnerWorker, runOnStart: true, waitTime: TimeSpan.FromSeconds(1), maxIdleTime: TimeSpan.FromSeconds(30)); this.validationWorker = new Worker("BlockchainDaemon.ValidationWorker", ValidationWorker, runOnStart: true, waitTime: TimeSpan.FromSeconds(10), maxIdleTime: TimeSpan.FromMinutes(5)); this.blockchainWorker = new Worker("BlockchainDaemon.BlockchainWorker", BlockchainWorker, runOnStart: true, waitTime: TimeSpan.FromSeconds(5), maxIdleTime: TimeSpan.FromMinutes(5)); this.validateCurrentChainWorker = new Worker("BlockchainDaemon.ValidateCurrentChainWorker", ValidateCurrentChainWorker, runOnStart: true, waitTime: TimeSpan.FromMinutes(30), maxIdleTime: TimeSpan.FromMinutes(30)); this.writeBlockchainWorker = new Worker("BlockchainDaemon.WriteBlockchainWorker", WriteBlockchainWorker, runOnStart: true, waitTime: TimeSpan.FromMinutes(5), maxIdleTime: TimeSpan.FromMinutes(30)); }
private void ChainingWorker() { var stopwatch = new Stopwatch(); stopwatch.Start(); var chainCount = 0; ChainedBlock? lastChainedBlock = null; var chainedBlocks = new List<ChainedBlock>(); var chainedBlocksSet = new HashSet<UInt256>(); var unchainedBlocksLocal = this.unchainedBlocks.ToImmutable(); var unchainedByPrevious = new Dictionary<UInt256, List<BlockHeader>>(); foreach (var unchainedBlock in unchainedBlocksLocal) { // cooperative loop this.shutdownToken.Token.ThrowIfCancellationRequested(); // check that chained block is actually missing if (this.CacheContext.ChainedBlockCache.ContainsKey(unchainedBlock)) { this.unchainedBlocks.Remove(unchainedBlock); continue; } BlockHeader unchainedBlockHeader; if (this.CacheContext.BlockHeaderCache.TryGetValue(unchainedBlock, out unchainedBlockHeader)) { if (!chainedBlocksSet.Contains(unchainedBlockHeader.PreviousBlock)) { ChainedBlock chainedBlock; if (this.CacheContext.ChainedBlockCache.ContainsKey(unchainedBlockHeader.PreviousBlock) && this.CacheContext.ChainedBlockCache.TryGetValue(unchainedBlockHeader.PreviousBlock, out chainedBlock)) { chainedBlocks.Add(chainedBlock); chainedBlocksSet.Add(chainedBlock.BlockHash); } } List<BlockHeader> unchainedGroup; if (!unchainedByPrevious.TryGetValue(unchainedBlockHeader.PreviousBlock, out unchainedGroup)) { unchainedGroup = new List<BlockHeader>(); unchainedByPrevious.Add(unchainedBlockHeader.PreviousBlock, unchainedGroup); } unchainedGroup.Add(unchainedBlockHeader); } else { this.missingBlocks.Add(unchainedBlock); } } // start with chained blocks... for (var i = 0; i < chainedBlocks.Count; i++) { // cooperative loop this.shutdownToken.Token.ThrowIfCancellationRequested(); var chainedBlock = chainedBlocks[i]; // find any unchained blocks whose previous block is the current chained block... IList<BlockHeader> unchainedGroup; if (unchainedByPrevious.ContainsKey(chainedBlock.BlockHash)) { unchainedGroup = unchainedByPrevious[chainedBlock.BlockHash]; unchainedByPrevious.Remove(chainedBlock.BlockHash); } else { unchainedGroup = new List<BlockHeader>(); foreach (var blockHash in this.CacheContext.ChainedBlockCache.FindByPreviousBlockHash(chainedBlock.BlockHash)) { // cooperative loop this.shutdownToken.Token.ThrowIfCancellationRequested(); BlockHeader blockHeader; if (this.CacheContext.BlockHeaderCache.TryGetValue(blockHash, out blockHeader)) unchainedGroup.Add(blockHeader); } } foreach (var unchainedBlock in unchainedGroup) { // cooperative loop this.shutdownToken.Token.ThrowIfCancellationRequested(); // check that block hasn't become chained if (this.CacheContext.ChainedBlockCache.ContainsKey(unchainedBlock.Hash)) break; // update the unchained block to chain off of the current chained block... var newChainedBlock = new ChainedBlock ( unchainedBlock.Hash, unchainedBlock.PreviousBlock, chainedBlock.Height + 1, chainedBlock.TotalWork + unchainedBlock.CalculateWork() ); this.CacheContext.ChainedBlockCache.CreateValue(newChainedBlock.BlockHash, newChainedBlock); // and finally add the newly chained block to the list of chained blocks so that an attempt will be made to chain off of it chainedBlocks.Add(newChainedBlock); // statistics chainCount++; lastChainedBlock = newChainedBlock; Debug.WriteLineIf(chainCount % 1.THOUSAND() == 0, "Chained block {0} at height {1}, total work: {2}".Format2(newChainedBlock.BlockHash.ToHexNumberString(), newChainedBlock.Height, newChainedBlock.TotalWork.ToString("X"))); if (chainCount % 1.THOUSAND() == 0) { // notify winner worker after chaining blocks this.winnerWorker.NotifyWork(); // notify the blockchain worker after chaining blocks this.blockchainWorker.NotifyWork(); } } } if (lastChainedBlock != null) Debug.WriteLine("Chained block {0} at height {1}, total work: {2}".Format2(lastChainedBlock.Value.BlockHash.ToHexNumberString(), lastChainedBlock.Value.Height, lastChainedBlock.Value.TotalWork.ToString("X"))); if (chainCount > 0) { // keep looking for more broken links after each pass this.chainingWorker.NotifyWork(); } // notify winner worker after chaining blocks this.winnerWorker.NotifyWork(); // notify the blockchain worker after chaining blocks this.blockchainWorker.NotifyWork(); stopwatch.Stop(); //Debug.WriteLine("ChainingWorker: Chained {0:#,##0} items in {1:#,##0.000}s".Format2(chainCount, stopwatch.ElapsedSecondsFloat())); }
public bool TryGetChainedBlock(UInt256 blockHash, out ChainedBlock chainedBlock, bool saveInCache = true) { if (this.CacheContext.ChainedBlockCache.TryGetValue(blockHash, out chainedBlock, saveInCache)) { this.missingChainedBlocks.TryRemove(blockHash); return true; } else { this.missingChainedBlocks.TryAdd(blockHash); if (!this.CacheContext.BlockCache.ContainsKey(blockHash)) this.missingBlocks.Add(blockHash); chainedBlock = default(ChainedBlock); return false; } }
public Testnet2Rules(CacheContext cacheContext) : base(cacheContext) { this._genesisBlock = new Block ( header: new BlockHeader ( version: 1, previousBlock: 0, merkleRoot: UInt256.Parse("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", NumberStyles.HexNumber), time: 1296688602, bits: 0x207FFFFF, nonce: 2 ), transactions: ImmutableArray.Create ( new Transaction ( version: 1, inputs: ImmutableArray.Create ( new TxInput ( previousTxOutputKey: new TxOutputKey ( txHash: 0, txOutputIndex: 0xFFFFFFFF ), scriptSignature: ImmutableArray.Create<byte> ( 0x04, 0xFF, 0xFF, 0x00, 0x1D, 0x01, 0x04, 0x45, 0x54, 0x68, 0x65, 0x20, 0x54, 0x69, 0x6D, 0x65, 0x73, 0x20, 0x30, 0x33, 0x2F, 0x4A, 0x61, 0x6E, 0x2F, 0x32, 0x30, 0x30, 0x39, 0x20, 0x43, 0x68, 0x61, 0x6E, 0x63, 0x65, 0x6C, 0x6C, 0x6F, 0x72, 0x20, 0x6F, 0x6E, 0x20, 0x62, 0x72, 0x69, 0x6E, 0x6B, 0x20, 0x6F, 0x66, 0x20, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x20, 0x62, 0x61, 0x69, 0x6C, 0x6F, 0x75, 0x74, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x62, 0x61, 0x6E, 0x6B, 0x73 ), sequence: 0xFFFFFFFF ) ), outputs: ImmutableArray.Create ( new TxOutput ( value: (UInt64)(50L * 100.MILLION()), scriptPublicKey: ImmutableArray.Create<byte> ( 0x41, 0x04, 0x67, 0x8A, 0xFD, 0xB0, 0xFE, 0x55, 0x48, 0x27, 0x19, 0x67, 0xF1, 0xA6, 0x71, 0x30, 0xB7, 0x10, 0x5C, 0xD6, 0xA8, 0x28, 0xE0, 0x39, 0x09, 0xA6, 0x79, 0x62, 0xE0, 0xEA, 0x1F, 0x61, 0xDE, 0xB6, 0x49, 0xF6, 0xBC, 0x3F, 0x4C, 0xEF, 0x38, 0xC4, 0xF3, 0x55, 0x04, 0xE5, 0x1E, 0xC1, 0x12, 0xDE, 0x5C, 0x38, 0x4D, 0xF7, 0xBA, 0x0B, 0x8D, 0x57, 0x8A, 0x4C, 0x70, 0x2B, 0x6B, 0xF1, 0x1D, 0x5F, 0xAC ) ) ), lockTime: 0 ) ) ); Debug.Assert(_genesisBlock.Hash == UInt256.Parse("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206", NumberStyles.HexNumber)); this._genesisChainedBlock = new ChainedBlock ( blockHash: this._genesisBlock.Hash, previousBlockHash: this._genesisBlock.Header.PreviousBlock, height: 0, totalWork: this._genesisBlock.Header.CalculateWork() ); this._genesisBlockchain = new Data.Blockchain ( blockList: ImmutableList.Create(this._genesisChainedBlock), blockListHashes: ImmutableHashSet.Create(this._genesisBlock.Hash), utxo: ImmutableDictionary.Create<UInt256, UnspentTx>() // genesis block coinbase is not included in utxo, it is unspendable ); }
public static long SizeEstimator(ChainedBlock chainedBlock) { return(100); }
private void UpdateWinningBlock(ChainedBlock winningBlock) { this.winningBlockchainLock.EnterWriteLock(); try { this._winningBlockchain = default(ImmutableArray<ChainedBlock>); } finally { this.winningBlockchainLock.ExitWriteLock(); } this._winningBlock = winningBlock; // notify the blockchain worker after updating winning block this.blockchainWorker.NotifyWork(); var handler = this.OnWinningBlockChanged; if (handler != null) handler(this, winningBlock); }
public Data.Blockchain RollbackToLastCommonAncestor(Data.Blockchain currentBlockchain, ChainedBlock targetChainedBlock, CancellationToken cancelToken, out List<UInt256> newChainBlockList) { // take snapshots var newChainedBlock = targetChainedBlock; newChainBlockList = new List<UInt256>(); // check height difference between chains, they will be roll backed before checking for the last common ancestor var heightDelta = targetChainedBlock.Height - currentBlockchain.Height; // if current chain is shorter, roll new chain back to current chain's height if (heightDelta > 0) { List<ChainedBlock> rolledBackChainedBlocks; newChainedBlock = RollbackChainedBlockToHeight(targetChainedBlock, currentBlockchain.Height, out rolledBackChainedBlocks, this.shutdownToken); newChainBlockList.AddRange(rolledBackChainedBlocks.Select(x => x.BlockHash)); } // if current chain is longer, roll it back to new chain's height else if (heightDelta < 0) { List<Data.Blockchain> rolledBackBlockchains; currentBlockchain = RollbackBlockchainToHeight(currentBlockchain, newChainedBlock.Height, out rolledBackBlockchains, this.shutdownToken); } if (newChainedBlock.Height != currentBlockchain.Height) throw new Exception(); //TODO continue looking backwards while processing moves forward to double check //TODO the blockchain history back to genesis? only look at height, work, valid bits in //TODO the metadata, sync and check this task at the end before updating current blockchain, //TODO if any error is ever found, mark everything after it as invalid or unprocessed, the //TODO processor could get stuck otherwise trying what it thinks is the winning chain over and over // with both chains at the same height, roll back to last common ancestor if (newChainedBlock.BlockHash != currentBlockchain.RootBlockHash) { var rollbackList = new List<UInt256>(); var currentBlockchainIndex = currentBlockchain.BlockList.Count - 1; foreach (var prevBlock in PreviousChainedBlocks(newChainedBlock)) { // cooperative loop this.shutdownToken.ThrowIfCancellationRequested(); cancelToken.ThrowIfCancellationRequested(); newChainedBlock = prevBlock; if (newChainedBlock.BlockHash == currentBlockchain.BlockList[currentBlockchainIndex].BlockHash) { break; } // ensure that height is as expected while looking up previous blocks if (newChainedBlock.Height != currentBlockchain.BlockList[currentBlockchainIndex].Height) { throw new ValidationException(); } // keep track of rolled back data on the new blockchain newChainBlockList.Add(newChainedBlock.BlockHash); // queue up current blockchain rollback rollbackList.Add(currentBlockchain.BlockList[currentBlockchainIndex].BlockHash); currentBlockchainIndex--; } // roll back current block chain foreach (var tuple in BlockLookAhead(rollbackList)) { var block = tuple.Item1; currentBlockchain = RollbackBlockchain(currentBlockchain, block); } } // work list will have last items added first, reverse newChainBlockList.Reverse(); return currentBlockchain; }
public MainnetRules(CacheContext cacheContext) { this._cacheContext = cacheContext; this._highestTarget = UInt256.Parse("00000000FFFF0000000000000000000000000000000000000000000000000000", NumberStyles.HexNumber); this._genesisBlock = new Block ( header: new BlockHeader ( version: 1, previousBlock: 0, merkleRoot: UInt256.Parse("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", NumberStyles.HexNumber), time: 1231006505, bits: 486604799, nonce: 2083236893 ), transactions: ImmutableArray.Create ( new Transaction ( version: 1, inputs: ImmutableArray.Create ( new TxInput ( previousTxOutputKey: new TxOutputKey ( txHash: 0, txOutputIndex: 0xFFFFFFFF ), scriptSignature: ImmutableArray.Create<byte> ( 0x04, 0xFF, 0xFF, 0x00, 0x1D, 0x01, 0x04, 0x45, 0x54, 0x68, 0x65, 0x20, 0x54, 0x69, 0x6D, 0x65, 0x73, 0x20, 0x30, 0x33, 0x2F, 0x4A, 0x61, 0x6E, 0x2F, 0x32, 0x30, 0x30, 0x39, 0x20, 0x43, 0x68, 0x61, 0x6E, 0x63, 0x65, 0x6C, 0x6C, 0x6F, 0x72, 0x20, 0x6F, 0x6E, 0x20, 0x62, 0x72, 0x69, 0x6E, 0x6B, 0x20, 0x6F, 0x66, 0x20, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x20, 0x62, 0x61, 0x69, 0x6C, 0x6F, 0x75, 0x74, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x62, 0x61, 0x6E, 0x6B, 0x73 ), sequence: 0xFFFFFFFF ) ), outputs: ImmutableArray.Create ( new TxOutput ( value: 50 * SATOSHI_PER_BTC, scriptPublicKey: ImmutableArray.Create<byte> ( 0x41, 0x04, 0x67, 0x8A, 0xFD, 0xB0, 0xFE, 0x55, 0x48, 0x27, 0x19, 0x67, 0xF1, 0xA6, 0x71, 0x30, 0xB7, 0x10, 0x5C, 0xD6, 0xA8, 0x28, 0xE0, 0x39, 0x09, 0xA6, 0x79, 0x62, 0xE0, 0xEA, 0x1F, 0x61, 0xDE, 0xB6, 0x49, 0xF6, 0xBC, 0x3F, 0x4C, 0xEF, 0x38, 0xC4, 0xF3, 0x55, 0x04, 0xE5, 0x1E, 0xC1, 0x12, 0xDE, 0x5C, 0x38, 0x4D, 0xF7, 0xBA, 0x0B, 0x8D, 0x57, 0x8A, 0x4C, 0x70, 0x2B, 0x6B, 0xF1, 0x1D, 0x5F, 0xAC ) ) ), lockTime: 0 ) ) ); this._genesisChainedBlock = new ChainedBlock ( blockHash: this._genesisBlock.Hash, previousBlockHash: this._genesisBlock.Header.PreviousBlock, height: 0, totalWork: this._genesisBlock.Header.CalculateWork() ); this._genesisBlockchain = new Data.Blockchain ( blockList: ImmutableList.Create(this._genesisChainedBlock), blockListHashes: ImmutableHashSet.Create(this._genesisBlock.Hash), utxo: ImmutableDictionary.Create<UInt256, UnspentTx>() // genesis block coinbase is not included in utxo, it is unspendable ); }
public ChainedBlock RollbackChainedBlockToHeight(ChainedBlock chainedBlock, int targetHeight, out List<ChainedBlock> rolledBackChainedBlocks, CancellationToken cancelToken) { if (targetHeight > chainedBlock.Height || targetHeight < 0) throw new ArgumentOutOfRangeException("targetHeight"); rolledBackChainedBlocks = new List<ChainedBlock>(); var targetChainedBlock = chainedBlock; var expectedHeight = targetChainedBlock.Height; while (targetChainedBlock.Height > targetHeight) { // cooperative loop cancelToken.ThrowIfCancellationRequested(); // keep track of rolled back data on the new blockchain rolledBackChainedBlocks.Add(targetChainedBlock); // roll back targetChainedBlock = this.CacheContext.GetChainedBlock(targetChainedBlock.PreviousBlockHash); // ensure that height is as expected while looking up previous blocks expectedHeight--; if (targetChainedBlock.Height != expectedHeight) throw new ValidationException(); } return targetChainedBlock; }
public static long SizeEstimator(ChainedBlock chainedBlock) { return 100; }
public IEnumerable<Tuple<ChainedBlock, Block>> PreviousBlocksLookAhead(ChainedBlock firstBlock) { using (var cancelToken = new CancellationTokenSource()) { foreach (var tuple in LookAheadMethods.LookAhead(() => PreviousBlocks(firstBlock), cancelToken.Token)) { yield return tuple; } } }
public Tuple<Block, ChainedBlock> AddBlock(Block block, ChainedBlock? prevChainedBlock) { if (prevChainedBlock != null) Assert.AreEqual(block.Header.PreviousBlock, prevChainedBlock.Value.BlockHash); var chainedBlock = new ChainedBlock ( block.Hash, block.Header.PreviousBlock, prevChainedBlock != null ? prevChainedBlock.Value.Height + 1 : 0, prevChainedBlock != null ? prevChainedBlock.Value.TotalWork + block.Header.CalculateWork() : block.Header.CalculateWork() ); this.CacheContext.BlockCache.CreateValue(block.Hash, block); this.CacheContext.ChainedBlockCache.CreateValue(block.Hash, chainedBlock); ChooseNewWinner(); return Tuple.Create(block, chainedBlock); }
private void OnWinningBlockChanged(object sender, ChainedBlock winningChainedBlock) { this.requestHeadersWorker.NotifyWork(); }
public bool TryReadValue(UInt256 blockHash, out ChainedBlock chainedBlock) { using (var conn = this.OpenConnection()) using (var cmd = conn.CreateCommand()) { cmd.CommandText = @" SELECT PreviousBlockHash, Height, TotalWork FROM ChainedBlocks WHERE BlockHash = @blockHash"; cmd.Parameters.SetValue("@blockHash", FbDbType.Char, FbCharset.Octets, 32).Value = blockHash.ToDbByteArray(); using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { var previousBlockHash = reader.GetUInt256(0); var height = reader.GetInt32(1); var totalWork = reader.GetBigInteger(2); chainedBlock = new ChainedBlock ( blockHash, previousBlockHash, height, totalWork ); return true; } else { chainedBlock = default(ChainedBlock); return false; } } } }