bool TryAddTransactionToMemoryPool(ByteString id, byte[] data) { // Data should not exceed the maximum size. if (data.Length > MaximumTransactionSize) { throw new ArgumentException(); } // Integrity check. var computedId = Hash.ComputeDoubleSHA256(data); if (!ByteString.CopyFrom(computedId).Equals(id)) { throw new ArgumentException(); } if (MemoryPool.ContainsKey(id)) { return(false); } var tx = BlockchainUtil.DeserializeTransaction(data); // Ignore the coinbase transactions. if (tx.InEntries.Count == 0) { return(false); } lock (MemoryPool) { if (MemoryPool.ContainsKey(id)) { return(false); } MemoryPool.Add(id, tx); } return(true); }
void Run(Block block) { logger.LogDebug($@"Attempt to run Block:{ block.Id.ToString().Substring(0, 7)}"); Debug.Assert(Latest.Id.Equals(block.PreviousHash)); if (!block.ParsedTransactions.IsNull()) { return; } var blockTime = block.Timestamp; var txCount = block.Transactions.Count; var rootTxHash = BlockchainUtil.RootHashTransactionIds( block.TransactionIds); var difficulty = BlockParameter.GetNextDifficulty( block.Ancestors(Blocks).Skip(1)); // Block header validity check. // The block timestamp must be the past time after previous block. // The block must contain at least one transaction: coinbase. // The number of transaction IDs and actual data must match. // Transaction Root must be the hash of all TX IDs concatenated. // The block must have a pointer to the previous block. // The block's difficulty must be within the computed range. // The Block ID has greater difficulty than the computed difficulty. if (blockTime > DateTime.UtcNow || blockTime < Latest.Timestamp || txCount == 0 || txCount != block.TransactionIds.Count || !rootTxHash.SequenceEqual(block.TransactionRootHash) || !Latest.Id.Equals(block.PreviousHash) || block.Difficulty >= difficulty * (1 + 1e-15) || block.Difficulty <= difficulty * (1 - 1e-15) || Hash.Difficulty(block.Id.ToByteArray()) < block.Difficulty) { throw new ArgumentException(); } // Deserialize transactions and check IDs. var transactions = new Transaction[txCount]; for (var i = 0; i < txCount; i++) { var tx = BlockchainUtil.DeserializeTransaction( block.Transactions[i]); if (!tx.Id.Equals(block.TransactionIds[i])) { throw new ArgumentException(); } transactions[i] = tx; } // Run normal transactions. ulong coinbase = BlockParameter.GetCoinbase(Latest.Height + 1); var spent = new List <TransactionOutput>(); for (var i = 1; i < txCount; i++) { Run(transactions[i], blockTime, spentTxo: spent); // Collect all transaction fees to pay to miner. Accumulate // spent transaction outputs. var exec = transactions[i].ExecInfo; coinbase += exec.TransactionFee; spent.AddRange(exec.RedeemedOutputs); } // Run the coinbase transaction. Run(transactions[0], blockTime, coinbase); block.Height = Latest.Height + 1; block.ParsedTransactions = transactions; block.TotalDifficulty = Latest.TotalDifficulty + block.Difficulty; }
public Transaction SendTo( HashSet <TransactionOutput> utxos, ByteString recipient, ulong amount) { // TODO: You should consider transaction fee. // Extract my spendable UTXOs. ulong sum = 0; var inEntries = new List <InEntry>(); foreach (var utxo in utxos) { if (!utxo.Recipient.Equals(Address)) { continue; } inEntries.Add(new InEntry { TransactionId = utxo.TransactionId, OutEntryIndex = utxo.OutIndex, }); sum += utxo.Amount; if (sum >= amount) { goto CreateOutEntries; } } throw new ArgumentException( "Insufficient fund.", nameof(amount)); CreateOutEntries: // Create list of out entries. It should contain fund transfer and // change if necessary. Also the sum of outputs must be less than // that of inputs. The difference will be collected as transaction // fee. var outEntries = new List <OutEntry> { new OutEntry { RecipientHash = recipient, Amount = amount, }, }; var change = sum - amount; if (change != 0) { outEntries.Add(new OutEntry { RecipientHash = Address, Amount = change, }); } // Construct to-be-signed transaction. var transaction = new Transaction { Timestamp = DateTime.UtcNow, InEntries = inEntries, OutEntries = outEntries, }; // Take a transaction signing hash and sign against it. Since // wallet contains a single key pair, single signing is sufficient. var signHash = BlockchainUtil.GetTransactionSignHash( Serialize(transaction)); var signature = EccService.Sign( signHash, keyPair.PrivateKey, keyPair.PublicKey); foreach (var inEntry in inEntries) { inEntry.PublicKey = keyPair.PublicKey; inEntry.Signature = signature; } var bytes = Serialize(transaction); return(BlockchainUtil.DeserializeTransaction(bytes)); }
async Task HandleBody(InventoryMessage message, int peerId) { // Data should not exceed the maximum size. var data = message.Data; if (data.Length > MaximumBlockSize) { throw new ArgumentException(); } // ハッシュ値正しい? var id = message.IsBlock ? BlockchainUtil.ComputeBlockId(data) : Hash.ComputeDoubleSHA256(data); if (!ByteString.CopyFrom(id).Equals(message.ObjectId)) { return; } if (message.IsBlock) { // ミューテックス lock (Blocks) { if (Blocks.ContainsKey(message.ObjectId)) { return; } // ハッシュテーブルに追加 Blocks.Add(message.ObjectId, data); } var block = BlockchainUtil.DeserializeBlock(data); // 前のブロックも知らなかったら前のももらう var prevId = block.PreviousHash; if (!Blocks.ContainsKey(prevId)) { await ConnectionManager.SendAsync(new InventoryMessage { Type = Request, IsBlock = true, ObjectId = prevId, }, peerId); } Executor.ProcessBlock(block); } else { if (MemoryPool.ContainsKey(message.ObjectId)) { return; } var tx = BlockchainUtil.DeserializeTransaction(data); // Ignore the coinbase transactions. if (tx.InEntries.Count == 0) { return; } lock (MemoryPool) { if (MemoryPool.ContainsKey(message.ObjectId)) { return; } MemoryPool.Add(message.ObjectId, tx); } } message.Type = Advertise; message.Data = null; // 他の人に教えてあげる await ConnectionManager.BroadcastAsync(message, peerId); }
async Task HandleBody(InventoryMessage message, int peerId) { // Data should not exceed the maximum size. var data = message.Data; if (data.Length > MaximumBlockSize) { throw new ArgumentException(); } var id = message.IsBlock ? BlockchainUtil.ComputeBlockId(data) : Hash.ComputeDoubleSHA256(data); if (!ByteString.CopyFrom(id).Equals(message.ObjectId)) { return; } if (message.IsBlock) { lock (Blocks)//並列処理を避ける { if (Blocks.ContainsKey(message.ObjectId)) { return; } Blocks.Add(message.ObjectId, data); } var prevId = Deserialize <Block>(data).PreviousHash; if (!Blocks.ContainsKey(prevId)) { await ConnectionManager.SendAsync(new InventoryMessage//当該ブロックの前のブロックを持っていない場合はそれもRequestする(前のblockのIDが必要なため { Type = Request, IsBlock = true, ObjectId = prevId, }, peerId); } else { Executor.ProcessBlock(data, prevId); } } else { if (MemoryPool.ContainsKey(message.ObjectId)) { return; } var tx = BlockchainUtil.DeserializeTransaction(data); // Ignore the coinbase transactions. if (tx.InEntries.Count == 0) { return; } lock (MemoryPool) { if (MemoryPool.ContainsKey(message.ObjectId)) { return; } MemoryPool.Add(message.ObjectId, tx); } } message.Type = Advertise; message.Data = null; await ConnectionManager.BroadcastAsync(message, peerId); }//ネットワーク全体にもらったブロックを発信する