public void Run(Transaction tx, DateTime blockTime, ulong coinbase = 0, List <TransactionOutput> spentTxo = null) { logger.LogDebug($@"Attempt to run TX:{ tx.Id.ToString().Substring(0, 7)}"); // Transaction header validity check. if (tx.Timestamp > blockTime || !(coinbase == 0 ^ tx.InEntries.Count == 0)) { throw new ArgumentException(); } // In-Entry validity check. ulong inSum = coinbase; var redeemed = new List <TransactionOutput>(); var signHash = BlockchainUtil.GetTransactionSignHash(tx.Original); foreach (var inEntry in tx.InEntries) { // Signature check. var verified = EccService.Verify( signHash, inEntry.Signature, inEntry.PublicKey); // UTXO check. The transaction output must not be spent by // previous transactions. var txo = new TransactionOutput { TransactionId = inEntry.TransactionId, OutIndex = inEntry.OutEntryIndex, }; var unspent = !(spentTxo?.Contains(txo) ?? false) && Utxos.TryGetValue(txo, out txo); // Recipient address check. var addr = BlockchainUtil.ToAddress(inEntry.PublicKey); var redeemable = txo.Recipient.Equals( ByteString.CopyFrom(addr)); // Sum all the reedemable. inSum = checked (inSum + txo.Amount); if (!verified || !unspent || !redeemable) { throw new ArgumentException(); } redeemed.Add(txo); } // Out-entry validity check. ulong outSum = 0; ushort outIndex = 0; var generated = new List <TransactionOutput>(); foreach (var outEntry in tx.OutEntries) { if (outEntry.RecipientHash.IsNull() || outEntry.Amount <= 0) { throw new ArgumentException(); } // Sum all the transferred. outSum = checked (outSum + outEntry.Amount); // Create new UTXO entry. generated.Add(new TransactionOutput { TransactionId = tx.Id, OutIndex = outIndex++, Recipient = outEntry.RecipientHash, Amount = outEntry.Amount, }); } // Output exceeds input or coinbase. if (outSum > inSum) { throw new ArgumentException(); } tx.ExecInfo = new TransactionExecInformation { Coinbase = coinbase != 0, RedeemedOutputs = redeemed, GeneratedOutputs = generated, TransactionFee = inSum - outSum, }; }
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)); }