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 Block CreateEmptyBlock(UInt256 previousBlockHash) { var coinbaseTx = new Transaction ( version: 0, inputs: ImmutableArray.Create ( new TxInput ( previousTxOutputKey: new TxOutputKey ( txHash: 0, txOutputIndex: 0 ), scriptSignature: ImmutableArray.Create(random.NextBytes(100)), sequence: 0 ) ), outputs: ImmutableArray.Create ( new TxOutput ( value: 50 * SATOSHI_PER_BTC, scriptPublicKey: ImmutableArray.Create(TransactionManager.CreatePublicKeyScript(_coinbasePublicKey)) ) ), lockTime: 0 ); //Debug.WriteLine("Coinbase Tx Created: {0}".Format2(coinbaseTx.Hash.ToHexNumberString())); var transactions = ImmutableArray.Create(coinbaseTx); var merkleRoot = DataCalculator.CalculateMerkleRoot(transactions); var block = new Block ( header: new BlockHeader ( version: 0, previousBlock: previousBlockHash, merkleRoot: merkleRoot, time: 0, bits: DataCalculator.TargetToBits(this._rules.HighestTarget), nonce: 0 ), transactions: transactions ); return block; }
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); }
public static byte[] EncodeBlock(Block block) { var stream = new MemoryStream(); EncodeBlock(stream, block); return stream.ToArray(); }
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 ImmutableDictionary<UInt256, UnspentTx> CalculateUtxo(long blockHeight, Block block, ImmutableDictionary<UInt256, UnspentTx> currentUtxo, out ImmutableDictionary<UInt256, ImmutableHashSet<int>> newTransactions, out long txCount, out long inputCount) { txCount = 0; inputCount = 0; // create builder for new utxo var newUtxoBuilder = currentUtxo.ToBuilder(); var newTransactionsBuilder = ImmutableDictionary.CreateBuilder<UInt256, ImmutableHashSet<int>>(); // don't include genesis block coinbase in utxo if (blockHeight > 0) { //TODO apply real coinbase rule // https://github.com/bitcoin/bitcoin/blob/481d89979457d69da07edd99fba451fd42a47f5c/src/core.h#L219 var coinbaseTx = block.Transactions[0]; // add the coinbase outputs to the utxo var coinbaseUnspentTx = new UnspentTx(block.Hash, 0, coinbaseTx.Hash, new ImmutableBitArray(coinbaseTx.Outputs.Length, true)); // add transaction output to to the utxo if (newUtxoBuilder.ContainsKey(coinbaseTx.Hash)) { // duplicate transaction output Debug.WriteLine("Duplicate transaction at block {0:#,##0}, {1}, coinbase".Format2(blockHeight, block.Hash.ToHexNumberString())); if ((blockHeight == 91842 && coinbaseTx.Hash == UInt256.Parse("d5d27987d2a3dfc724e359870c6644b40e497bdc0589a033220fe15429d88599", NumberStyles.HexNumber)) || (blockHeight == 91880 && coinbaseTx.Hash == UInt256.Parse("e3bf3d07d4b0375638d5f1db5255fe07ba2c4cb067cd81b84ee974b6585fb468", NumberStyles.HexNumber))) { newUtxoBuilder.Remove(coinbaseTx.Hash); } else { throw new ValidationException(); } } newTransactionsBuilder.Add(coinbaseTx.Hash, ImmutableHashSet.Create(0)); newUtxoBuilder.Add(coinbaseTx.Hash, coinbaseUnspentTx); } // check for double spends for (var txIndex = 1; txIndex < block.Transactions.Length; txIndex++) { var tx = block.Transactions[txIndex]; txCount++; for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++) { var input = tx.Inputs[inputIndex]; inputCount++; if (!newUtxoBuilder.ContainsKey(input.PreviousTxOutputKey.TxHash)) { // output wasn't present in utxo, invalid block throw new ValidationException(); } var prevUnspentTx = newUtxoBuilder[input.PreviousTxOutputKey.TxHash]; if (input.PreviousTxOutputKey.TxOutputIndex >= prevUnspentTx.UnspentOutputs.Length) { // output was out of bounds throw new ValidationException(); } if (!prevUnspentTx.UnspentOutputs[input.PreviousTxOutputKey.TxOutputIndex.ToIntChecked()]) { // output was already spent throw new ValidationException(); } // remove the output from the utxo newUtxoBuilder[input.PreviousTxOutputKey.TxHash] = new UnspentTx(prevUnspentTx.BlockHash, prevUnspentTx.TxIndex, prevUnspentTx.TxHash, prevUnspentTx.UnspentOutputs.Set(input.PreviousTxOutputKey.TxOutputIndex.ToIntChecked(), false)); // remove fully spent transaction from the utxo if (newUtxoBuilder[input.PreviousTxOutputKey.TxHash].UnspentOutputs.All(x => !x)) newUtxoBuilder.Remove(input.PreviousTxOutputKey.TxHash); } // add the output to the list to be added to the utxo var unspentTx = new UnspentTx(block.Hash, (UInt32)txIndex, tx.Hash, new ImmutableBitArray(tx.Outputs.Length, true)); // add transaction output to to the utxo if (newUtxoBuilder.ContainsKey(tx.Hash)) { // duplicate transaction output Debug.WriteLine("Duplicate transaction at block {0:#,##0}, {1}, tx {2}".Format2(blockHeight, block.Hash.ToHexNumberString(), txIndex)); //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 } if (!newTransactionsBuilder.ContainsKey(tx.Hash)) newTransactionsBuilder.Add(tx.Hash, ImmutableHashSet.Create(txIndex)); else newTransactionsBuilder[tx.Hash] = newTransactionsBuilder[tx.Hash].Add(txIndex); newUtxoBuilder.Add(tx.Hash, unspentTx); } // validation successful, return the new utxo newTransactions = newTransactionsBuilder.ToImmutable(); return newUtxoBuilder.ToImmutable(); }
public Data.Blockchain RollbackBlockchain(Data.Blockchain blockchain, Block block) { List<TxOutputKey> spendOutputs, receiveOutputs; return RollbackBlockchain(blockchain, block, out spendOutputs, out receiveOutputs); }
public Block MineEmptyBlock(Block previousBlock) { return MineEmptyBlock(previousBlock.Hash); }
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 }
private void OnBlockModification(UInt256 blockHash, Block block) { OnBlockAddition(blockHash); }
public bool TryGetBlock(UInt256 blockHash, out Block block, bool saveInCache = true) { if (this.CacheContext.BlockCache.TryGetValue(blockHash, out block, saveInCache)) { this.missingBlocks.Remove(blockHash); return true; } else { this.missingBlocks.Add(blockHash); block = default(Block); 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 void GetTransaction(BlockProvider blockProvider, int blockIndex, int txIndex, out Block block, out Transaction tx) { block = blockProvider.GetBlock(blockIndex); tx = block.Transactions[txIndex]; }
public static void GetFirstTransaction(BlockProvider blockProvider, out Block block, out Transaction tx, out IDictionary<UInt256, Transaction> txLookup) { txLookup = new Dictionary<UInt256, Transaction>(); // prior outputs for first transaction GetTransaction(blockProvider, 9, 0, out block, out tx); txLookup.Add(tx.Hash, tx); // first transaction // do this last so its output is what is returned GetTransaction(blockProvider, 170, 1, out block, out tx); txLookup.Add(tx.Hash, tx); }
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 MineBlock(Block block) { var minedHeader = Miner.MineBlockHeader(block.Header, this._rules.HighestTarget); if (minedHeader == null) Assert.Fail("No block could be mined for test data header."); block = block.With(Header: minedHeader); return block; }
//TODO utxo needs to be as-at transaction, with regards to a transaction being fully spent and added back in in the same block public virtual void ValidateTransaction(long blockHeight, Block block, Transaction tx, int txIndex, ImmutableDictionary<UInt256, UnspentTx> utxo, ImmutableDictionary<UInt256, ImmutableHashSet<int>> newTransactions, out long unspentValue /*, ImmutableDictionary<UInt256, Transaction> transactions*/) { unspentValue = -1; // lookup all previous outputs var prevOutputMissing = false; var previousOutputs = new Dictionary<TxOutputKey, Tuple<TxInput, int, TxOutput>>(); for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++) { var input = tx.Inputs[inputIndex]; // find previous transaction var prevTx = GetPreviousTransaction(block, txIndex, input.PreviousTxOutputKey, utxo, newTransactions); // find previous transaction output if (input.PreviousTxOutputKey.TxOutputIndex >= prevTx.Outputs.Length) throw new ValidationException(); var prevOutput = prevTx.Outputs[input.PreviousTxOutputKey.TxOutputIndex.ToIntChecked()]; previousOutputs.Add(input.PreviousTxOutputKey, Tuple.Create(input, inputIndex, prevOutput)); } if (prevOutputMissing) { throw new ValidationException(); } // verify spend amounts var txInputValue = (UInt64)0; var txOutputValue = (UInt64)0; for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++) { var input = tx.Inputs[inputIndex]; // add transactions previous value to unspent amount (used to calculate allowed coinbase reward) var prevOutput = previousOutputs[input.PreviousTxOutputKey].Item3; txInputValue += prevOutput.Value; } for (var outputIndex = 0; outputIndex < tx.Outputs.Length; outputIndex++) { // remove transactions spend value from unspent amount (used to calculate allowed coinbase reward) var output = tx.Outputs[outputIndex]; txOutputValue += output.Value; } // ensure that amount being output from transaction isn't greater than amount being input if (txOutputValue > txInputValue) { throw new ValidationException("Failing tx {0}: Transaction output value is greater than input value".Format2(tx.Hash.ToHexNumberString())); } // calculate unspent value unspentValue = (long)(txInputValue - txOutputValue); // sanity check if (unspentValue < 0) { throw new ValidationException(); } // all validation has passed }
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 ); }
//TODO utxo needs to be as-at transaction, with regards to a transaction being fully spent and added back in in the same block public virtual void ValidateTransactionScripts(Block block, ImmutableDictionary<UInt256, UnspentTx> utxo, ImmutableDictionary<UInt256, ImmutableHashSet<int>> newTransactions) { if (BypassExecuteScript) return; // lookup all previous outputs var prevOutputMissing = false; var previousOutputs = new Dictionary<TxOutputKey, Tuple<Transaction, TxInput, int, TxOutput>>(); for (var txIndex = 1; txIndex < block.Transactions.Length; txIndex++) { var tx = block.Transactions[txIndex]; for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++) { var input = tx.Inputs[inputIndex]; // find previous transaction var prevTx = GetPreviousTransaction(block, txIndex, input.PreviousTxOutputKey, utxo, newTransactions); // find previous transaction output if (input.PreviousTxOutputKey.TxOutputIndex >= prevTx.Outputs.Length) throw new ValidationException(); var prevOutput = prevTx.Outputs[input.PreviousTxOutputKey.TxOutputIndex.ToIntChecked()]; previousOutputs.Add(input.PreviousTxOutputKey, Tuple.Create(tx, input, inputIndex, prevOutput)); } } if (prevOutputMissing) { throw new ValidationException(); } var exceptions = new ConcurrentBag<Exception>(); var scriptEngine = new ScriptEngine(); Parallel.ForEach(previousOutputs.Values, (tuple, loopState) => { try { var tx = tuple.Item1; var input = tuple.Item2; var inputIndex = tuple.Item3; var prevOutput = tuple.Item4; // create the transaction script from the input and output var script = input.ScriptSignature.AddRange(prevOutput.ScriptPublicKey); if (!scriptEngine.VerifyScript(0 /*TODO blockHash*/, 0 /*TODO txIndex*/, prevOutput.ScriptPublicKey.ToArray(), tx, inputIndex, script.ToArray())) { exceptions.Add(new ValidationException()); loopState.Break(); } } catch (Exception e) { exceptions.Add(e); loopState.Break(); } }); if (exceptions.Count > 0) throw new AggregateException(exceptions.ToArray()); }
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 Transaction GetPreviousTransaction(Block block, int txIndex, TxOutputKey prevTxOutputKey, ImmutableDictionary<UInt256, UnspentTx> utxo, ImmutableDictionary<UInt256, ImmutableHashSet<int>> newTransactions) { if (newTransactions.ContainsKey(prevTxOutputKey.TxHash)) { var eligible = newTransactions[prevTxOutputKey.TxHash].Where(x => x < txIndex).ToList(); if (eligible.Count > 0) { var max = eligible.Max(); if (max >= block.Transactions.Length) throw new Exception(); var prevTx1 = block.Transactions[max]; if (prevTx1.Hash != prevTxOutputKey.TxHash) throw new Exception(); return prevTx1; } } // find previous transaction if (!utxo.ContainsKey(prevTxOutputKey.TxHash)) throw new MissingDataException(DataType.Transaction, prevTxOutputKey.TxHash); var prevTxKey = utxo[prevTxOutputKey.TxHash].ToTxKey(); var prevTx2 = this.CacheContext.GetTransaction(prevTxKey); if (prevTx2.Hash != prevTxOutputKey.TxHash) throw new Exception(); return prevTx2; }
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 static long SizeEstimator(Block block) { return block.SizeEstimate; }
private void OnBlock(Block block) { //Debug.WriteLine("Received block {0}".Format2(block.Hash)); this.requestedBlocks.TryRemove(block.Hash); this.blockchainDaemon.CacheContext.BlockCache.CreateValue(block.Hash, block); Tuple<DateTime, DateTime?> receiveTime; if (this.requestedBlockTimes.TryGetValue(block.Hash, out receiveTime)) { if (receiveTime.Item2 == null) this.requestedBlockTimes[block.Hash] = Tuple.Create(receiveTime.Item1, (DateTime?)DateTime.UtcNow); this.requestBlocksWorker.NotifyWork(); } }
public static void EncodeBlock(Stream stream, Block block) { using (var writer = new BinaryWriter(stream, Encoding.ASCII, leaveOpen: true)) { EncodeBlockHeader(stream, block.Header); writer.EncodeList(block.Transactions, tx => EncodeTransaction(stream, tx)); } }