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 MainWindowViewModel(BlockchainDaemon blockchainDaemon) { this.blockchainDaemon = blockchainDaemon; var winningBlockLocal = this.blockchainDaemon.WinningBlock; var currentBlockchainLocal = this.blockchainDaemon.CurrentBlockchain; this.viewBlockchain = currentBlockchainLocal; this.WinningBlockchainHeight = winningBlockLocal.Height; this.CurrentBlockchainHeight = currentBlockchainLocal.Height; this.DownloadedBlockCount = this.blockchainDaemon.CacheContext.BlockCache.Count; this.blockchainDaemon.CacheContext.BlockCache.OnAddition += blockHash => DownloadedBlockCount = this.blockchainDaemon.CacheContext.BlockCache.Count; this.blockchainDaemon.OnWinningBlockChanged += (sender, block) => WinningBlockchainHeight = block.Height; this.blockchainDaemon.OnCurrentBlockchainChanged += (sender, blockchain) => CurrentBlockchainHeight = blockchain.Height; }
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; }
private void LogBlockchainProgress(Data.Blockchain blockchain, Stopwatch totalStopwatch, long totalTxCount, long totalInputCount, Stopwatch currentRateStopwatch, long currentBlockCount, long currentTxCount, long currentInputCount) { var currentBlockRate = (float)currentBlockCount / currentRateStopwatch.ElapsedSecondsFloat(); var currentTxRate = (float)currentTxCount / currentRateStopwatch.ElapsedSecondsFloat(); var currentInputRate = (float)currentInputCount / currentRateStopwatch.ElapsedSecondsFloat(); Debug.WriteLine( string.Join("\n", new string('-', 80), "Height: {0,10} | Date: {1} | Duration: {7} hh:mm:ss | Validation: {8} hh:mm:ss | Blocks/s: {2,7} | Tx/s: {3,7} | Inputs/s: {4,7} | Total Tx: {5,7} | Total Inputs: {6,7} | Utxo Size: {9,7}", "GC Memory: {10,10:#,##0.00} MB", "Process Memory: {11,10:#,##0.00} MB", new string('-', 80) ) .Format2 ( blockchain.Height.ToString("#,##0"), "", currentBlockRate.ToString("#,##0"), currentTxRate.ToString("#,##0"), currentInputRate.ToString("#,##0"), totalTxCount.ToString("#,##0"), totalInputCount.ToString("#,##0"), totalStopwatch.Elapsed.ToString(@"hh\:mm\:ss"), validateStopwatch.Elapsed.ToString(@"hh\:mm\:ss"), blockchain.Utxo.Count.ToString("#,##0"), (float)GC.GetTotalMemory(false) / 1.MILLION(), (float)Process.GetCurrentProcess().PrivateMemorySize64 / 1.MILLION() )); }
public Data.Blockchain RollbackBlockchain(Data.Blockchain blockchain, Block block, out List <TxOutputKey> spendOutputs, out List <TxOutputKey> receiveOutputs) { if (blockchain.BlockCount == 0 || blockchain.RootBlockHash != block.Hash) { throw new ValidationException(); } var newUtxo = RollbackUtxo(blockchain, block, out spendOutputs, out receiveOutputs); return(new Data.Blockchain ( blockList: blockchain.BlockList.RemoveAt(blockchain.BlockCount - 1), blockListHashes: blockchain.BlockListHashes.Remove(blockchain.RootBlockHash), utxo: newUtxo )); }
private void SetViewBlockchain(Data.Blockchain blockchain) { this.viewBlockchain = blockchain; try { if (blockchain.Height > 0) { Block block; if (this.blockchainDaemon.TryGetBlock(this.viewBlockchain.RootBlockHash, out block)) { // TODO this is abusing rollback a bit just to get the transactions that exist in a target block that's already known // TODO make a better api for get the net output of a block List <TxOutputKey> spendOutputs, receiveOutputs; this.blockchainDaemon.Calculator.RollbackBlockchain(this.viewBlockchain, block, out spendOutputs, out receiveOutputs); ViewBlockchainSpendOutputs = spendOutputs; ViewBlockchainReceiveOutputs = receiveOutputs; } } else { ViewBlockchainSpendOutputs = new List <TxOutputKey>(); ViewBlockchainReceiveOutputs = new List <TxOutputKey>(); } } catch (MissingDataException) { // TODO } catch (AggregateException e) { if (!e.IsMissingDataOnly()) { throw; } } var handler = this.PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs("ViewBlockchainHeight")); handler(this, new PropertyChangedEventArgs("ViewBlockchainSpendOutputs")); handler(this, new PropertyChangedEventArgs("ViewBlockchainReceiveOutputs")); } }
private Guid UpdateCurrentBlockchain(Data.Blockchain newBlockchain) { var guid = Guid.NewGuid(); this.currentBlockchainLock.DoWrite(() => { this.lastCurrentBlockchainWrite = guid; this._currentBlockchain = newBlockchain; }); var handler = this.OnCurrentBlockchainChanged; if (handler != null) { handler(this, newBlockchain); } return(guid); }
public Data.Blockchain RollbackBlockchainToHeight(Data.Blockchain blockchain, int targetHeight, out List <Data.Blockchain> rolledBackBlockchains, CancellationToken cancelToken) { if (targetHeight > blockchain.Height || targetHeight < 0) { throw new ArgumentOutOfRangeException("targetHeight"); } List <ChainedBlock> rolledBackChainedBlocks; var targetChainedBlock = RollbackChainedBlockToHeight(blockchain.RootBlock, targetHeight, out rolledBackChainedBlocks, cancelToken); var rollbackCount = blockchain.Height - targetHeight; if (rolledBackChainedBlocks.Count != rollbackCount) { throw new Exception(); } rolledBackBlockchains = new List <Data.Blockchain>(); var targetBlockchain = blockchain; var rollbackIndex = 0; foreach (var tuple in BlockLookAhead(rolledBackChainedBlocks.Select(x => x.BlockHash).ToList())) { // cooperative loop this.shutdownToken.ThrowIfCancellationRequested(); cancelToken.ThrowIfCancellationRequested(); // keep track of rolled back data on the new blockchain rolledBackBlockchains.Add(targetBlockchain); // roll back var block = tuple.Item1; Debug.Assert(targetBlockchain.RootBlockHash == block.Hash); targetBlockchain = RollbackBlockchain(targetBlockchain, block); Debug.WriteLineIf(rollbackIndex % 100 == 0, "Rolling back {0} of {1}".Format2(rollbackIndex + 1, rollbackCount)); rollbackIndex++; } return(targetBlockchain); }
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 ); }
private void ChooseNewWinner() { //TODO if there is a valid blockchain with less work than invalid blockchains, it won't get picked up as this is currently implemented //TODO when there is a tie this method is not deterministic, causing TestSimpleBlockchainSplit to fail var leafChainedBlocks = this.CacheContext.ChainedBlockCache.FindLeafChainedBlocks() .ToDictionary(x => x.BlockHash, x => x); while (true) { var newWinner = this._rules.SelectWinningChainedBlock(leafChainedBlocks.Values.ToList()); if (newWinner == null) { break; } leafChainedBlocks.Remove(newWinner.BlockHash); try { // try to use the blockchain using (var cancelToken = new CancellationTokenSource()) { List <MissingDataException> missingData; this._currentBlockchain = this._calculator.CalculateBlockchainFromExisting(this._currentBlockchain, newWinner, out missingData, cancelToken.Token); } // success, exit return; } catch (ValidationException) { } // failure, try another candidate if present } }
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)); }
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 ); }
private void ChooseNewWinner() { //TODO if there is a valid blockchain with less work than invalid blockchains, it won't get picked up as this is currently implemented //TODO when there is a tie this method is not deterministic, causing TestSimpleBlockchainSplit to fail var leafChainedBlocks = this.CacheContext.ChainedBlockCache.FindLeafChainedBlocks() .ToDictionary(x => x.BlockHash, x => x); while (true) { var newWinner = this._rules.SelectWinningChainedBlock(leafChainedBlocks.Values.ToList()); if (newWinner.IsDefault) break; leafChainedBlocks.Remove(newWinner.BlockHash); try { // try to use the blockchain using (var cancelToken = new CancellationTokenSource()) { List<MissingDataException> missingData; this._currentBlockchain = this._calculator.CalculateBlockchainFromExisting(this._currentBlockchain, newWinner, out missingData, cancelToken.Token); } // success, exit return; } catch (ValidationException) { } // failure, try another candidate if present } }
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 void RevalidateBlockchain(Data.Blockchain blockchain, Block genesisBlock) { var stopwatch = new Stopwatch(); stopwatch.Start(); try { //TODO delete corrupted data? could get stuck in a fail-loop on the winning chain otherwise // verify blockchain has blocks if (blockchain.BlockList.Count == 0) { throw new ValidationException(); } // verify genesis block hash if (blockchain.BlockList[0].BlockHash != genesisBlock.Hash) { throw new ValidationException(); } // get genesis block header var chainGenesisBlockHeader = this.CacheContext.GetBlockHeader(blockchain.BlockList[0].BlockHash); // verify genesis block header if ( genesisBlock.Header.Version != chainGenesisBlockHeader.Version || genesisBlock.Header.PreviousBlock != chainGenesisBlockHeader.PreviousBlock || genesisBlock.Header.MerkleRoot != chainGenesisBlockHeader.MerkleRoot || genesisBlock.Header.Time != chainGenesisBlockHeader.Time || genesisBlock.Header.Bits != chainGenesisBlockHeader.Bits || genesisBlock.Header.Nonce != chainGenesisBlockHeader.Nonce || genesisBlock.Hash != chainGenesisBlockHeader.Hash || genesisBlock.Hash != CalculateHash(chainGenesisBlockHeader)) { throw new ValidationException(); } // setup expected previous block hash value to verify each chain actually does link var expectedPreviousBlockHash = genesisBlock.Header.PreviousBlock; for (var height = 0; height < blockchain.BlockList.Count; height++) { // cooperative loop this.shutdownToken.ThrowIfCancellationRequested(); // get the current link in the chain var chainedBlock = blockchain.BlockList[height]; // verify height if (chainedBlock.Height != height) { throw new ValidationException(); } // verify blockchain linking if (chainedBlock.PreviousBlockHash != expectedPreviousBlockHash) { throw new ValidationException(); } // verify block exists var blockHeader = this.CacheContext.GetBlockHeader(chainedBlock.BlockHash); // verify block metadata matches header values if (blockHeader.PreviousBlock != chainedBlock.PreviousBlockHash) { throw new ValidationException(); } // verify block header hash if (CalculateHash(blockHeader) != chainedBlock.BlockHash) { throw new ValidationException(); } // next block metadata should have the current metadata's hash as its previous hash value expectedPreviousBlockHash = chainedBlock.BlockHash; } // all validation passed } finally { stopwatch.Stop(); Debug.WriteLine("Blockchain revalidation: {0:#,##0.000000}s".Format2(stopwatch.ElapsedSecondsFloat())); } }
private Guid UpdateCurrentBlockchain(Data.Blockchain newBlockchain) { var guid = Guid.NewGuid(); this.currentBlockchainLock.DoWrite(() => { this.lastCurrentBlockchainWrite = guid; this._currentBlockchain = newBlockchain; }); var handler = this.OnCurrentBlockchainChanged; if (handler != null) handler(this, newBlockchain); return guid; }
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 virtual void ValidateBlock(Block block, Data.Blockchain blockchain, ImmutableDictionary <UInt256, UnspentTx> utxo, ImmutableDictionary <UInt256, ImmutableHashSet <int> > newTransactions /*, ImmutableDictionary<UInt256, Transaction> transactions*/) { //TODO if (BypassValidation) { return; } // calculate the next required target var requiredTarget = GetRequiredNextTarget(blockchain); // validate block's target against the required target var blockTarget = block.Header.CalculateTarget(); if (blockTarget > requiredTarget) { throw new ValidationException("Failing block {0} at height {1}: Block target {2} did not match required target of {3}".Format2(block.Hash.ToHexNumberString(), blockchain.Height, blockTarget.ToHexNumberString(), requiredTarget.ToHexNumberString())); } // validate block's proof of work against its stated target if (block.Hash > blockTarget || block.Hash > requiredTarget) { throw new ValidationException("Failing block {0} at height {1}: Block did not match its own target of {2}".Format2(block.Hash.ToHexNumberString(), blockchain.Height, blockTarget.ToHexNumberString())); } // ensure there is at least 1 transaction if (block.Transactions.Length == 0) { throw new ValidationException("Failing block {0} at height {1}: Zero transactions present".Format2(block.Hash.ToHexNumberString(), blockchain.Height)); } //TODO apply real coinbase rule // https://github.com/bitcoin/bitcoin/blob/481d89979457d69da07edd99fba451fd42a47f5c/src/core.h#L219 var coinbaseTx = block.Transactions[0]; // check that coinbase has only one input if (coinbaseTx.Inputs.Length != 1) { throw new ValidationException("Failing block {0} at height {1}: Coinbase transaction does not have exactly one input".Format2(block.Hash.ToHexNumberString(), blockchain.Height)); } // validate transactions in parallel long unspentValue = 0L; try { Parallel.For(1, block.Transactions.Length, (txIndex, loopState) => { var tx = block.Transactions[txIndex]; long unspentValueInner; //TODO utxo will not be correct at transaction if a tx hash is reused within the same block ValidateTransaction(blockchain.Height, block, tx, txIndex, utxo, newTransactions, out unspentValueInner); Interlocked.Add(ref unspentValue, unspentValueInner); }); } catch (AggregateException e) { var validationException = e.InnerExceptions.FirstOrDefault(x => x is ValidationException); var missingDataException = e.InnerExceptions.FirstOrDefault(x => x is MissingDataException); if (validationException != null) { throw validationException; } if (missingDataException != null) { throw missingDataException; } throw; } //TODO utxo will not be correct at transaction if a tx hash is reused within the same block ValidateTransactionScripts(block, utxo, newTransactions); // calculate the expected reward in coinbase var expectedReward = (long)(50 * SATOSHI_PER_BTC); if (blockchain.Height / 210000 <= 32) { expectedReward /= (long)Math.Pow(2, blockchain.Height / 210000); } expectedReward += unspentValue; // calculate the actual reward in coinbase var actualReward = 0L; foreach (var txOutput in coinbaseTx.Outputs) { actualReward += (long)txOutput.Value; } // ensure coinbase has correct reward if (actualReward > expectedReward) { throw new ValidationException("Failing block {0} at height {1}: Coinbase value is greater than reward + fees".Format2(block.Hash.ToHexNumberString(), blockchain.Height)); } // all validation has passed }
public BlockchainKey WriteBlockchain(Data.Blockchain blockchain) { var guid = Guid.NewGuid(); var blockchainKey = new BlockchainKey(guid, blockchain.RootBlockHash); var connString = @"Server=localhost; Database=BitSharp_Blockchains; Trusted_Connection=true;"; using (var conn = new SqlConnection(connString)) { conn.Open(); using (var trans = conn.BeginTransaction()) { // write out the metadata for the blockchain using (var cmd = conn.CreateCommand()) { cmd.Transaction = trans; cmd.CommandText = @" INSERT INTO BlockchainMetadata (Guid, RootBlockHash, TotalWork, IsComplete) VALUES (@guid, @rootBlockHash, @totalWork, 0);"; cmd.Parameters.SetValue("@guid", SqlDbType.Binary, 16).Value = blockchainKey.Guid.ToByteArray(); cmd.Parameters.SetValue("@rootBlockHash", SqlDbType.Binary, 32).Value = blockchainKey.RootBlockHash.ToDbByteArray(); cmd.Parameters.SetValue("@totalWork", SqlDbType.Binary, 64).Value = blockchain.TotalWork.ToDbByteArray(); cmd.ExecuteNonQuery(); } // write out the block metadata comprising the blockchain using (var cmd = conn.CreateCommand()) { cmd.Transaction = trans; cmd.CommandText = @" INSERT INTO ChainedBlocks (Guid, RootBlockHash, BlockHash, PreviousBlockHash, Height, TotalWork) VALUES (@guid, @rootBlockHash, @blockHash, @previousBlockHash, @height, @totalWork)"; cmd.Parameters.SetValue("@guid", SqlDbType.Binary, 16).Value = blockchainKey.Guid.ToByteArray(); cmd.Parameters.SetValue("@rootBlockHash", SqlDbType.Binary, 32).Value = blockchainKey.RootBlockHash.ToDbByteArray(); cmd.Parameters.Add(new SqlParameter { ParameterName = "@blockHash", SqlDbType = SqlDbType.Binary, Size = 32 }); cmd.Parameters.Add(new SqlParameter { ParameterName = "@previousBlockHash", SqlDbType = SqlDbType.Binary, Size = 32 }); cmd.Parameters.Add(new SqlParameter { ParameterName = "@height", SqlDbType = SqlDbType.Int }); cmd.Parameters.Add(new SqlParameter { ParameterName = "@totalWork", SqlDbType = SqlDbType.Binary, Size = 64 }); foreach (var chainedBlock in blockchain.BlockList) { cmd.Parameters["@blockHash"].Value = chainedBlock.BlockHash.ToDbByteArray(); cmd.Parameters["@previousBlockHash"].Value = chainedBlock.PreviousBlockHash.ToDbByteArray(); cmd.Parameters["@height"].Value = chainedBlock.Height; cmd.Parameters["@totalWork"].Value = chainedBlock.TotalWork.ToDbByteArray(); cmd.ExecuteNonQuery(); } } // write out the utxo using (var cmd = conn.CreateCommand()) { cmd.Transaction = trans; cmd.CommandText = @" INSERT INTO UtxoData (Guid, RootBlockhash, UtxoChunkBytes) VALUES (@guid, @rootBlockHash, @utxoChunkBytes)"; cmd.Parameters.SetValue("@guid", SqlDbType.Binary, 16).Value = blockchainKey.Guid.ToByteArray(); cmd.Parameters.SetValue("@rootBlockHash", SqlDbType.Binary, 32).Value = blockchainKey.RootBlockHash.ToDbByteArray(); cmd.Parameters.Add(new SqlParameter { ParameterName = "@utxoChunkBytes", SqlDbType = SqlDbType.VarBinary }); var chunkSize = 100000; var currentOffset = 0; var chunkBytes = new byte[4 + (36 * chunkSize)]; using (var utxoEnumerator = blockchain.Utxo.GetEnumerator()) { // chunk outer loop while (currentOffset < blockchain.Utxo.Count) { var chunkLength = Math.Min(chunkSize, blockchain.Utxo.Count - currentOffset); var chunkStream = new MemoryStream(chunkBytes); using (var chunkWriter = new BinaryWriter(chunkStream)) { chunkWriter.Write4Bytes((UInt32)chunkLength); // chunk inner loop for (var i = 0; i < chunkLength; i++) { // get the next output from the utxo if (!utxoEnumerator.MoveNext()) { throw new Exception(); } var output = utxoEnumerator.Current; //TODO chunkWriter.Write32Bytes(output.TxHash); //TODO chunkWriter.Write4Bytes((UInt32)output.TxOutputIndex); } cmd.Parameters["@utxoChunkBytes"].Size = chunkBytes.Length; cmd.Parameters["@utxoChunkBytes"].Value = chunkBytes; } // write the chunk cmd.ExecuteNonQuery(); currentOffset += chunkLength; } // there should be no items left in utxo at this point if (utxoEnumerator.MoveNext()) { throw new Exception(); } } } // mark write as complete using (var cmd = conn.CreateCommand()) { cmd.Transaction = trans; cmd.CommandText = @" UPDATE BlockchainMetadata SET IsComplete = 1 WHERE Guid = @guid AND RootBlockHash = @rootBlockHash;"; cmd.Parameters.SetValue("@guid", SqlDbType.Binary, 16).Value = blockchainKey.Guid.ToByteArray(); cmd.Parameters.SetValue("@rootBlockHash", SqlDbType.Binary, 32).Value = blockchainKey.RootBlockHash.ToDbByteArray(); cmd.ExecuteNonQuery(); } trans.Commit(); } } return(blockchainKey); }
public Data.Blockchain RollbackBlockchain(Data.Blockchain blockchain, Block block) { List <TxOutputKey> spendOutputs, receiveOutputs; return(RollbackBlockchain(blockchain, block, out spendOutputs, out receiveOutputs)); }
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; })); }
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 ); }
private ImmutableDictionary <UInt256, UnspentTx> RollbackUtxo(Data.Blockchain blockchain, Block block, out List <TxOutputKey> spendOutputs, out List <TxOutputKey> receiveOutputs) { var blockHeight = blockchain.Height; var currentUtxo = blockchain.Utxo; // create builder for prev utxo var prevUtxoBuilder = currentUtxo.ToBuilder(); spendOutputs = new List <TxOutputKey>(); receiveOutputs = new List <TxOutputKey>(); //TODO apply real coinbase rule // https://github.com/bitcoin/bitcoin/blob/481d89979457d69da07edd99fba451fd42a47f5c/src/core.h#L219 var coinbaseTx = block.Transactions[0]; for (var outputIndex = 0; outputIndex < coinbaseTx.Outputs.Length; outputIndex++) { var txOutputKey = new TxOutputKey(coinbaseTx.Hash, (UInt32)outputIndex); if (blockHeight > 0) { // remove new outputs from the rolled back utxo if (prevUtxoBuilder.Remove(coinbaseTx.Hash)) { receiveOutputs.Add(txOutputKey); } else { // missing transaction output Debug.WriteLine("Missing transaction at block {0:#,##0}, {1}, tx {2}, output {3}".Format2(blockHeight, block.Hash.ToHexNumberString(), 0, outputIndex)); Debugger.Break(); //TODO throw new Validation(); //TODO this needs to be tracked so that blocks can be rolled back accurately //TODO track these separately on the blockchain info? gonna be costly to track on every transaction } } } for (var txIndex = block.Transactions.Length - 1; txIndex >= 1; txIndex--) { var tx = block.Transactions[txIndex]; for (var outputIndex = tx.Outputs.Length - 1; outputIndex >= 0; outputIndex--) { var output = tx.Outputs[outputIndex]; var txOutputKey = new TxOutputKey(tx.Hash, (UInt32)outputIndex); //TODO what if a transaction wasn't added to the utxo because it already existed? //TODO the block would still pass without adding the tx to its utxo, but here it would get rolled back //TODO maybe a flag bit to track this? // remove new outputs from the rolled back utxo if (prevUtxoBuilder.Remove(tx.Hash)) { receiveOutputs.Add(txOutputKey); } else { // missing transaction output Debug.WriteLine("Missing transaction at block {0:#,##0}, {1}, tx {2}, output {3}".Format2(blockHeight, block.Hash.ToHexNumberString(), txIndex, outputIndex)); Debugger.Break(); //TODO throw new Validation(); //TODO this needs to be tracked so that blocks can be rolled back accurately //TODO track these separately on the blockchain info? gonna be costly to track on every transaction } } for (var inputIndex = tx.Inputs.Length - 1; inputIndex >= 0; inputIndex--) { var input = tx.Inputs[inputIndex]; // add spent outputs back into the rolled back utxo if (prevUtxoBuilder.ContainsKey(input.PreviousTxOutputKey.TxHash)) { var prevUnspentTx = prevUtxoBuilder[input.PreviousTxOutputKey.TxHash]; // check if output is out of bounds if (input.PreviousTxOutputKey.TxOutputIndex >= prevUnspentTx.UnspentOutputs.Length) { throw new ValidationException(); } // check that output isn't already considered unspent if (prevUnspentTx.UnspentOutputs[input.PreviousTxOutputKey.TxOutputIndex.ToIntChecked()]) { throw new ValidationException(); } // mark output as unspent prevUtxoBuilder[input.PreviousTxOutputKey.TxHash] = new UnspentTx(prevUnspentTx.BlockHash, prevUnspentTx.TxIndex, prevUnspentTx.TxHash, prevUnspentTx.UnspentOutputs.Set(input.PreviousTxOutputKey.TxOutputIndex.ToIntChecked(), true)); } else { // fully spent transaction being added back in during roll back //TODO throw new NotImplementedException(); } //TODO //if (prevUtxoBuilder.Add(input.PreviousTxOutputKey)) //{ // spendOutputs.Add(input.PreviousTxOutputKey); //} //else //{ // // missing transaction output // Debug.WriteLine("Duplicate transaction at block {0:#,##0}, {1}, tx {2}, input {3}".Format2(blockHeight, block.Hash.ToHexNumberString(), txIndex, inputIndex)); // Debugger.Break(); // //TODO throw new Validation(); // //TODO this needs to be tracked so that blocks can be rolled back accurately // //TODO track these separately on the blockchain info? gonna be costly to track on every transaction //} } } return(prevUtxoBuilder.ToImmutable()); }
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)); }
public virtual UInt256 GetRequiredNextTarget(Data.Blockchain blockchain) { try { // lookup genesis block header var genesisBlockHeader = this.CacheContext.GetBlockHeader(blockchain.BlockList[0].BlockHash); // lookup the latest block on the current blockchain var currentBlockHeader = this.CacheContext.GetBlockHeader(blockchain.RootBlockHash); // use genesis block difficulty if first adjusment interval has not yet been reached if (blockchain.Height < DifficultyInternal) { return(genesisBlockHeader.CalculateTarget()); } // not on an adjustment interval, reuse current block's target else if (blockchain.Height % DifficultyInternal != 0) { return(currentBlockHeader.CalculateTarget()); } // on an adjustment interval, calculate the required next target else { // get the block difficultyInterval blocks ago var startChainedBlock = blockchain.BlockList.Reverse().Skip(DifficultyInternal).First(); var startBlockHeader = this.CacheContext.GetBlockHeader(startChainedBlock.BlockHash); Debug.Assert(startChainedBlock.Height == blockchain.Height - DifficultyInternal); var actualTimespan = (long)currentBlockHeader.Time - (long)startBlockHeader.Time; var targetTimespan = DifficultyTargetTimespan; // limit adjustment to 4x or 1/4x if (actualTimespan < targetTimespan / 4) { actualTimespan = targetTimespan / 4; } else if (actualTimespan > targetTimespan * 4) { actualTimespan = targetTimespan * 4; } // calculate the new target var target = startBlockHeader.CalculateTarget(); target *= actualTimespan; target /= targetTimespan; // make sure target isn't too high (too low difficulty) if (target > HighestTarget) { target = HighestTarget; } return(target); } } catch (ArgumentException) { // invalid bits Debugger.Break(); throw new ValidationException(); } }
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 == null) { // 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 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 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 ); }