Ejemplo n.º 1
0
        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,
            };
        }
Ejemplo n.º 2
0
        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));
        }