public Block MineNextBlock(string minerAddress, int difficulty) { // Prepare the next block for mining var oldDifficulty = this.CurrentDifficulty; this.CurrentDifficulty = difficulty; var nextBlock = this.GetMiningJob(minerAddress); this.CurrentDifficulty = oldDifficulty; // Mine the next block nextBlock.DateCreated = GeneralUtils.NowInISO8601(); nextBlock.Nonce = 0; do { nextBlock.Nonce++; nextBlock.BlockHash = nextBlock.CalculateBlockHash(); } while (!ValidationUtils.IsValidDifficulty(CryptoUtils.BytesToHex(nextBlock.BlockHash), difficulty)); // Submit the mined block var newBlock = this.SubmitMinedBlock(nextBlock.BlockDataHash, // ?? thing if I have to recalculate that nextBlock.DateCreated, nextBlock.Nonce, nextBlock.BlockHash); return(newBlock); }
public Block ExtendChain(Block newBlock) { if (newBlock.Index != this.Blocks.Count) { throw new ArgumentException("The submitted block was already mined by someone else"); } var prevBlock = this.Blocks[this.Blocks.Count - 1]; if (CryptoUtils.BytesToHex(prevBlock.BlockHash) != CryptoUtils.BytesToHex(newBlock.PrevBlockHash)) { throw new ArgumentException("Incorrect prevBlockHash"); } // The block is correct --> accept it this.Blocks.Add(newBlock); this.MiningJobs = new Dictionary <string, Block>(); // Invalidate all mining jobs this.RemovePendingTransactions(newBlock.Transactions); this.AdjustDifficulty(); return(newBlock); }
public Transaction FindTransactionByDataHash(string transactionDataHash) { var allTransactions = this.GetAllTransactions(); var matchingTransaction = allTransactions.FirstOrDefault( t => CryptoUtils.BytesToHex(t.TransactionDataHash) == transactionDataHash); return(matchingTransaction); }
private Node(string serverHost, string serverPort, Blockchain blockchain) { this.NodeId = Guid.NewGuid().ToString(); this.Host = serverHost; this.Port = serverPort; this.SelfUrl = string.Format("http://{0}:{1}", serverHost, serverPort); this.Peers = new Dictionary <string, string>(); this.Chain = blockchain; this.ChainId = CryptoUtils.BytesToHex(blockchain.Blocks[0].BlockHash); }
public void Test_CryptoUtils_HexToBytesAndViseVersa() { var expResult = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"; var bytes = CryptoUtils.HexToBytes(expResult); Assert.True(bytes.Length == 32); var resultString = CryptoUtils.BytesToHex(bytes); Assert.True(expResult == resultString); Assert.True(expResult.Length == 64); }
public void RemovePendingTransactions(IEnumerable <Transaction> transactionsToRemove) { var tranHashesToRemove = new HashSet <string>(); foreach (var item in transactionsToRemove) { tranHashesToRemove.Add(CryptoUtils.BytesToHex(item.TransactionDataHash)); } foreach (var dataHash in tranHashesToRemove) { this.PendingTransactions.RemoveAll(s => CryptoUtils.BytesToHex(s.TransactionDataHash) == dataHash); } }
public IActionResult GetMiningJob(string address) { var blockCandidate = this.GetNodeSingleton().Chain.GetMiningJob(address); //retun candidate block return(Ok(new { index = blockCandidate.Index, transactionsIncluded = blockCandidate.Transactions.Count, difficulty = blockCandidate.Difficulty, expectedReward = blockCandidate.Transactions[0].Value, rewardAddress = blockCandidate.Transactions[0].To, blockDataHash = CryptoUtils.BytesToHex(blockCandidate.BlockDataHash) })); }
} // the unique chain ID (hash of the genesis block) public void BroadcastTransactionToAllPeers(Transaction tran) { foreach (var nodeId in this.Peers) { var peerUrl = this.Peers[nodeId.Key]; Console.WriteLine("Broadcasting a transaction {0} to peer {1}" , CryptoUtils.BytesToHex(tran.TransactionDataHash), peerUrl); try { var result = WebRequester .Post(peerUrl + "/api/Node/transactions/send", tran); } catch (Exception ex) { Console .WriteLine("Broadcasting a transaction to {0} failed, due to {1}" , peerUrl, ex.Message); } } }
public Block SubmitMinedBlock(byte[] blockDataHash, string dateCreated, ulong nonce, byte[] blockHash) { // Find the block candidate by its data hash var newBlock = this.MiningJobs[CryptoUtils.BytesToHex(blockDataHash)]; if (newBlock == null) { throw new ArgumentException("Block not found or already mined"); } // Build the new block newBlock.DateCreated = dateCreated; newBlock.Nonce = nonce; newBlock.BlockHash = newBlock.CalculateBlockHash(); // We can not compare block hash because the one we have in newBlock is not ok // Validate the block hash + the proof of work //if (CryptoUtils.BytesToHex(newBlock.BlockHash) != CryptoUtils.BytesToHex(blockHash)) // throw new ArgumentException("Block hash is incorrectly calculated"); if (!ValidationUtils.IsValidDifficulty( CryptoUtils.BytesToHex(newBlock.BlockHash), newBlock.Difficulty)) { throw new ArgumentException("The calculated block hash does not match the block difficulty"); } newBlock = this.ExtendChain(newBlock); //if (!newBlock.errorMsg) // logger.debug("Mined a new block: " + JSON.stringify(newBlock)); return(newBlock); }
public byte[] CalculateBlockHash() { var data = string.Format("{0}|{1}|{2}", CryptoUtils.BytesToHex(this.BlockDataHash), this.DateCreated, this.Nonce); return(CryptoUtils.CalcSHA256(data)); }
public Block GetMiningJob(string minerAddress) { var nextBlockIndex = this.Blocks.Count; // Deep clone all pending transactions & sort them by fee var clonedTransactions = (SerializeUtils.CloneList <Transaction>(this.PendingTransactions)) .OrderByDescending(s => s.Fee).ToList(); // sort descending by fee var dateCreated = GeneralUtils.NowInISO8601(); var key = nextBlockIndex + "_" + minerAddress; if (MinerAddressBlockIndexDateTimeMap.ContainsKey(key)) { dateCreated = MinerAddressBlockIndexDateTimeMap[key]; } else { MinerAddressBlockIndexDateTimeMap[key] = dateCreated; } // Prepare the coinbase transaction -> it will collect all tx fees var coinbaseTransaction = new Transaction( Config.NullAddress, // from (address) minerAddress, // to (address) Config.BlockReward, // value (of transfer) 0, // fee (for mining) dateCreated, // dateCreated "coinbase tx", // data (payload / comments) Config.NullPubKey, // senderPubKey null, // transactionDataHash Config.GetNullSignatureHex(), // senderSignature nextBlockIndex, // minedInBlockIndex true ); // Execute all pending transactions (after paying their fees) // Transfer the requested values if the balance is sufficient var balances = this.CalcAllConfirmedBalances(); foreach (var tran in clonedTransactions) { if (!balances.ContainsKey(tran.From)) { balances[tran.From] = 0; } if (!balances.ContainsKey(tran.To)) { balances[tran.To] = 0; } if (balances[tran.From] >= tran.Fee) { tran.MinedInBlockIndex = nextBlockIndex; // The transaction sender pays the processing fee balances[tran.From] -= tran.Fee; coinbaseTransaction.Value += tran.Fee; // Transfer the requested value: sender -> recipient if (balances[tran.From] >= tran.Value) { balances[tran.From] -= tran.Value; balances[tran.To] += tran.Value; tran.TransferSuccessful = true; } else { tran.TransferSuccessful = false; } } else { // The transaction cannot be mined due to insufficient // balance to pay the transaction fee -> drop it this.RemovePendingTransactions(new List <Transaction>() { tran }); clonedTransactions.Remove(tran); } } // Insert the coinbase transaction, holding the block reward + tx fees coinbaseTransaction.CalculateDataHash(); clonedTransactions.Insert(0, coinbaseTransaction); // Prepare the next block candidate (block template) var previousBlock = this.Blocks[this.Blocks.Count - 1]; //TODO: test that with Date null?? var nextBlockCandidate = new Block( nextBlockIndex, clonedTransactions.ToList(), this.CurrentDifficulty, previousBlock.BlockHash, previousBlock.Index, minerAddress ); this.MiningJobs[CryptoUtils.BytesToHex(nextBlockCandidate.BlockDataHash)] = nextBlockCandidate; return(nextBlockCandidate); }
public Transaction AddNewTransaction(Transaction tranData) { // Validate the transaction & add it to the pending transactions if (!ValidationUtils.IsValidAddress(tranData.From)) { throw new ArgumentException("Invalid sender address:" + tranData.From); } if (!ValidationUtils.IsValidAddress(tranData.To)) { throw new ArgumentException("Invalid recipient address:" + tranData.To); } if (!ValidationUtils.IsValidPublicKey(tranData.SenderPubKey)) { throw new ArgumentException("Invalid public key:" + tranData.SenderPubKey); } var senderAddr = CryptoUtils.GetAddressFromPublicKey(tranData.SenderPubKey); if (senderAddr != tranData.From) { throw new ArgumentException("The public key should match the sender address:" + tranData.SenderPubKey); } if (!ValidationUtils.IsValidTransferValue(tranData.Value)) { throw new ArgumentException("Invalid transfer value: " + tranData.Value); } if (!ValidationUtils.IsValidFee(tranData.Fee)) { throw new ArgumentException("Invalid transaction fee: " + tranData.Fee); } if (!ValidationUtils.IsValidDate(tranData.DateCreated)) { throw new ArgumentException("Invalid date: " + tranData.DateCreated); } if (!ValidationUtils.IsValidSignatureFormat(CryptoUtils.SignatureByHex(tranData.SenderSignature))) { throw new ArgumentException("Invalid or missing signature. Expected signature format: BigInteger[2] " + tranData.SenderSignature); } var tran = new Transaction( tranData.From, tranData.To, tranData.Value, tranData.Fee, tranData.DateCreated, tranData.Data, tranData.SenderPubKey, null, // the transactionDataHash is auto-calculated tranData.SenderSignature ); //tran.IsSignatureValid = tranData.IsSignatureValid; // Check for duplicated transactions (to avoid "replay attack") var tranDataHex = CryptoUtils.BytesToHex(tran.TransactionDataHash); if (this.FindTransactionByDataHash(tranDataHex) != null) { throw new ArgumentException("Duplicated transaction: " + tranDataHex); } if (!CryptoUtils.VerifySignature(CryptoUtils.SignatureByHex(tran.SenderSignature), tran.TransactionDataHash)) { throw new ArgumentException("Invalid signature: " + tranData.SenderSignature); } var balances = this.GetAccountBalance(tran.From); if (balances.ConfirmedBalance < tran.Fee) { throw new ArgumentException("Unsufficient sender balance at address: " + tran.From); } //if (this.PendingTransactions.Any(s => CryptoUtils.BytesToHex(s.TransactionDataHash) == CryptoUtils.BytesToHex(tran.TransactionDataHash))) // throw new ArgumentException("Trying to add duplicate transaction: " + CryptoUtils.BytesToHex(tran.TransactionDataHash)); this.PendingTransactions.Add(tran); Console.WriteLine("Added pending transaction: " + JsonConvert.SerializeObject(tran)); return(tran); }