public void ValidateSignature(AbstractTransaction tx) { var stateTx = (StateTransaction)tx; var pubKey = stateTx.Action == TransactionAction.ClaimCoinbase.ToString() ? stateTx.ToPubKey : stateTx.FromPubKey; // todo use custom 'ValidationException' if (String.IsNullOrWhiteSpace(pubKey)) { throw new TransactionRejectedException("Tried to validate a signature with an empty public key", tx); } if (!_signer.SignatureIsValid(stateTx.Signature, stateTx.Hash, pubKey)) { throw new TransactionRejectedException("Transaction signature is invalid", tx); } }
//! Chain of Responsibility pattern could be possible here. Only check for PoW things, then call the next one for more generic checks public virtual void ValidateBlock(Block block, BigDecimal currentTarget, Blockchain blockchain, bool checkTimestamp, bool writeToBlockchain) { BigInteger.TryParse(block.Header.Hash, NumberStyles.HexNumber, new CultureInfo("en-US"), out var hashValue); ValidateBlockHeader(block, hashValue, currentTarget, checkTimestamp, blockchain.NetIdentifier); // Transaction list may not be empty if (block.Transactions.Count() == 0) { throw new BlockRejectedException("Transaction list cannot be empty", block); } // Check merkleroot var calculatedMerkleRoot = _transactionValidator.CalculateMerkleRoot(block.Transactions.ToList()); if (block.Header.MerkleRoot != calculatedMerkleRoot) { throw new BlockRejectedException("Incorrect merkleroot", block); } // First transaction must be coinbase var firstTransaction = (StateTransaction)block.Transactions.First(); if (firstTransaction.Action != TransactionAction.ClaimCoinbase.ToString()) { throw new BlockRejectedException("First transaction is not coinbase", block); } // Only one coinbase transaction may exist if (block.Transactions.Where(tx => tx.Action == TransactionAction.ClaimCoinbase.ToString()).Count() > 1) { throw new BlockRejectedException("Multiple coinbase transactions found", block); } // Check the signature to make sure the block wasn't altered if (!_signer.SignatureIsValid(block.Header.Signature, block.Header.Hash, firstTransaction.ToPubKey)) { throw new BlockRejectedException("Block's signature is invalid", block); } // Check if the previous hash exists in our blockchain // Todo if the previous hash is unknown, let Networking retrieve the entire blockchain (don't do that here, but the caller must catch the exception and do it there) lock (blockchain) { if (blockchain.Blocks.Where(b => b.Header.Hash == block.Header.PreviousHash).Count() == 0 && blockchain.CurrentHeight > -1) { throw new BlockRejectedException("Previous blockhash does not exist in our chain", block); } } // Check all other transactions foreach (var tx in block.Transactions) { _transactionValidator.ValidateTransaction(tx); } // Check if the previous hash isn't used by another block // If it is used by another block, and that block is the latest, // then check which block has the best difficulty. lock (blockchain) { if (blockchain.CurrentHeight > -1) { var existingBlocks = blockchain.Blocks.Where(b => b.Header.PreviousHash == block.Header.PreviousHash); for (int i = 0; i < existingBlocks.Count(); i++) { var existingBlock = existingBlocks.ElementAt(i); int heightInChain = blockchain.GetHeightForBlock(existingBlock.Header.Hash); if (blockchain.CurrentHeight == heightInChain) { // This is the latest block so we might replace it. Determine the difficulty. BigDecimal existingBlockHashValue = BigInteger.Parse(existingBlock.Header.Hash, NumberStyles.HexNumber); if (hashValue < existingBlockHashValue) { if (writeToBlockchain) { blockchain.Blocks.Remove(existingBlock); } } else { throw new BlockRejectedException("Another block with higher difficulty points to the same PreviousHash", block); } } else { throw new BlockRejectedException("Chain splitting is not supported", block); } } } // Finished if (writeToBlockchain) { blockchain.Blocks.Add(block); } } }