/// <summary>
        /// Build a transacting using the coins client.
        /// This will communicate with the client over https.
        /// </summary>
        public override Task Build(IBitcoinClient client, TransactionContext transactionContext)
        {
            // this code should not be use in production as clients are not ssl enabled (no need for that)
            // to use in test scenarios delete this exception
            throw new NotImplementedException();

            ////// create the transaction hex
            ////transactionContext.UnsignedRawTransaction = client.CreateRawTransactionAsync(transactionContext.CreateRawTransaction).Result;
            ////transactionContext.UnsignedTransaction = client.DecodeRawTransactionAsync(transactionContext.UnsignedRawTransaction).Result;

            ////// create transaction signature.
            ////transactionContext.SignRawTransaction = new SignRawTransaction(transactionContext.UnsignedRawTransaction);
            ////transactionContext.SendItems.Where(m => !m.Failed).ForEach(a => a.SpendFromAddresses.ForEach(m => transactionContext.SignRawTransaction.AddKey(m.PrivateKey)));

            ////// sign the transaction.
            ////transactionContext.SignedRawTransaction = client.SignRawTransactionAsync(transactionContext.SignRawTransaction).Result;
            ////transactionContext.DecodedRawTransaction = client.DecodeRawTransactionAsync(transactionContext.SignedRawTransaction.Hex).Result;

            ////return Task.FromResult(0);
        }
        /// <summary>
        /// Build a transaction.
        /// </summary>
        public virtual Task Build(IBitcoinClient client, TransactionContext transactionContext)
        {
            var transaction = this.CreatePubKeyHashTransaction(this.CoinParameters, transactionContext.CreateRawTransaction);

            // create the transaction hex
            transactionContext.UnsignedRawTransaction = this.Serializer.ToHex(transaction);
            ////var createdHex = await client.CreateRawTransactionAsync(transactionContext.CreateRawTransaction);
            ////var created = this.Serializer.FromHex(createdHex);

            // validate
            ////transactionContext.UnsignedTransaction = await client.DecodeRawTransactionAsync(transactionContext.UnsignedRawTransaction);

            // create transaction signature and add the relevant private keys and previous outputs.
            transactionContext.SignRawTransaction = new SignRawTransaction(transactionContext.UnsignedRawTransaction);

            //transactionContext.SendItems.Where(m => !m.Failed).ForEach(m => transactionContext.SignRawTransaction.AddKey(m.SpendFromAddress.PrivateKey));
            //transactionContext.PreviousRawTransactions.ForEach(p => p.VOut.ForEach(output => transactionContext.SignRawTransaction.AddInput(p.TxId, output.N, output.ScriptPubKey.Hex)));
            transactionContext.SendItems
            .Where(m => !m.Failed)
            .ForEach(m =>
                     m.SpendFromAddresses.ForEach(b =>
            {
                transactionContext.SignRawTransaction.AddKey(b.PrivateKey);
                b.Transactions.ForEach(a => transactionContext.SignRawTransaction.AddInput(a.Hash, (int)a.Index, a.ScriptPubKeyHex));
            }));

            // ready to sign
            transaction = this.Sign(this.CoinParameters, transactionContext.SignRawTransaction);
            transactionContext.SignedRawTransaction = new SignedRawTransaction {
                Hex = this.Serializer.ToHex(transaction), Complete = true
            };

            ////var signed = await client.SignRawTransactionAsync(transactionContext.SignRawTransaction);

            // validate the transaction
            //transactionContext.DecodedRawTransaction = await client.DecodeRawTransactionAsync(transactionContext.SignedRawTransaction.Hex);

            return(Task.FromResult(true));
        }
Exemple #3
0
        /// <summary>
        ///  Create a transaction.
        /// This allows to create a transaction using the private keys,
        /// This means we don't need to open the wallet to send the transaction and all the handling of open wallet synchronization is not required.
        /// </summary>
        public static async Task <TransactionContext> CreateAsync(CoinParameters param, IBitcoinClient client, IEnumerable <TransactionInfo> items)
        {
            if (string.Equals(param.CoinTag, "DST"))
            {
                return(await CreateDeStreamAsync(param, client, items));
            }

            var context = new TransactionContext
            {
                CoinParameters          = param,
                PreviousRawTransactions = new List <DecodedRawTransaction>(),
                CreateRawTransaction    = new CreateRawTransaction(),
                SendItems = new List <TransactionInfo>()
            };

            foreach (var item in items)
            {
                context.SendItems.Add(item);

                // check if receiving address is already in the transaction, this is allowed but not supported by the client.
                if (item.SpendToAddresses.Select(s => s.PublicKey)
                    .Intersect(item.SpendFromAddresses.Select(s => s.PublicKey)).Any())
                {
                    item.Fail(false, FailedReason.CannotSendToSelfUnlessChange);
                    continue;
                }

                if (item.SpendFromAddresses.SelectMany(s => s.Transactions).None())
                {
                    item.Fail(false, FailedReason.NoTransactionsFound);
                    continue;
                }

                // check if a transaction is already in use
                if (context.CreateRawTransaction.Inputs.
                    Any(source => item.SpendFromAddresses.SelectMany(s => s.Transactions)
                        .Any(target => source.TransactionId == target.Hash && source.Output == target.Index)))
                {
                    item.Fail(true, FailedReason.TransactionInputAlreadyInUse);
                    continue;
                }

                // check if receiving address is already in the transaction, this is allowed but not supported by the client.
                if (context.CreateRawTransaction.Outputs
                    .Any(source => item.SpendToAddresses.Any(target => source.Key == target.PublicKey)))
                {
                    item.Fail(true, FailedReason.ReceiveAddressAlreadyInOutput);
                    continue;
                }

                if (item.RequestedFee <= 0)
                {
                    item.Fail(false, FailedReason.FeeCannotBeZero);
                    continue;
                }

                ////try
                ////{
                ////    var rawTrxListAsync = item.SpendFromAddresses
                ////        .SelectMany(s => s.Transactions)
                ////        .DistinctBy(t => t.Hash)
                ////        .Where(a => context.PreviousRawTransactions.None(b => b.TxId == a.Hash))
                ////        .Select(async trx => await client.GetRawTransactionAsync(trx.Hash, 1));

                ////    var previousRawTransactions = (await Task.WhenAll(rawTrxListAsync)).ToList();
                ////    context.PreviousRawTransactions.AddRange(previousRawTransactions);

                ////    // the Gridcoin hack
                ////    GridcoinHack(context.CoinParameters, previousRawTransactions);
                ////}
                ////catch (Exception)
                ////{
                ////    item.Fail(false, FailedReason.FailedReadingTransactionsFromClient);
                ////    continue;
                ////}

                // validate all the transaction outpoints have the ScriptPubKey Hex
                ////item.SpendFromAddresses.ForEach(spend =>
                ////    spend.Transactions.ForEach(strx =>
                ////    {
                ////        var output = context.PreviousRawTransactions
                ////            .Where(t => t.TxId == strx.Hash)
                ////            .SelectMany(s => s.VOut)
                ////            .SingleOrDefault(v => v.N == strx.Index);

                ////        if (output.IsNotNull() && output.Value == strx.Value)
                ////        {
                ////            if (output.ScriptPubKey.IsNotNull())
                ////            {
                ////                if (output.ScriptPubKey.Addresses.Any(addr => addr == spend.PublicKey))
                ////                {
                ////                    strx.ScriptPubKeyHex = output.ScriptPubKey.Hex;
                ////                }
                ////            }
                ////        }
                ////    }));

                if (item.SpendFromAddresses.SelectMany(s => s.Transactions).Any(t => t.ScriptPubKeyHex.IsNullOrEmpty()))
                {
                    item.Fail(false, FailedReason.ScriptPubKeyHexNotFound);
                    continue;
                }

                //// get the outputs associated with the sender address
                var spendToEnumerated   = item.SpendToAddresses.ToList();
                var spendFromEnumerated = item.SpendFromAddresses.SelectMany(s => s.Transactions).ToList();
                ////var outputs = previousRawTransactions
                ////    .ToDictionary(rawtrx => rawtrx.TxId, rawtrx => rawtrx.VOut
                ////        .Where(vout => vout.ScriptPubKey.Addresses
                ////            .Any(add => item.SpendFromAddresses.Any(a => a.PublicKey == add))));

                //// calculate the sum of inputs and outputs.
                var inputSum  = spendFromEnumerated.Select(t => t.Value).Sum();
                var outputSum = spendToEnumerated.Select(spt => spt.Amount).Sum();

                var change = inputSum - outputSum;

                if (change < 0)
                {
                    item.Fail(false, FailedReason.InvalidSum);
                    continue;
                }

                if (item.SpendToAddresses.Any(s => s.TakeFee))
                {
                    //// take the fee from a receiver address.
                    var receiver = item.SpendToAddresses.First(s => s.TakeFee);

                    if (receiver.Amount < item.RequestedFee)
                    {
                        item.Fail(false, FailedReason.InsufficientFeeOnReceiver);
                        continue;
                    }

                    receiver.Amount -= item.RequestedFee;
                }
                else
                {
                    //// take the fee from the sender
                    if (change < item.RequestedFee)
                    {
                        item.Fail(false, FailedReason.InsufficientFeeOnSender);
                        continue;
                    }

                    change -= item.RequestedFee;
                }

                if (change > 0)
                {
                    if (item.ChangeAddress.IsNullOrEmpty())
                    {
                        item.Fail(false, FailedReason.NoChangeAddressFound);
                        continue;
                    }
                }

                // try to calculate the fee if trx is too big the default fee may not be enough
                // http://bitcoin.stackexchange.com/questions/7537/calculator-for-estimated-tx-fees
                // https://en.bitcoin.it/wiki/Transaction_fees
                // http://bitcoin.stackexchange.com/questions/3400/what-is-the-exact-formula-for-calculating-transaction-fees
                // http://bitcoin.stackexchange.com/questions/1195/how-to-calculate-transaction-size-before-sending
                // formula fee = [Normal fee * (500 KB) / (500KB - Block size)] - [Normal fee / (1 - Block size / 500KB)]
                // trx size = [in*148 + out*34 + 10 plus or minus 'in']

                //// TODO: move this code to a facotry to allow calculation of fee per coin
                //// coins have different block sizes sand this needs to be acocunted for as well

                var ins      = spendFromEnumerated.Count();
                var outs     = spendToEnumerated.Count();
                var sizeByte = Convert.ToDecimal((ins * 148) + (outs * 34) + 10 + (ins / 2));

                // we'll calculate roughly the coin fee times size of transaction
                const decimal AvgSizeBytes = 1000;
                if (sizeByte >= AvgSizeBytes)
                {
                    var feeRatio    = sizeByte / AvgSizeBytes;
                    var expectedFee = feeRatio * item.CoinFee;

                    if (expectedFee > item.RequestedFee)
                    {
                        item.Fail(false, FailedReason.InsufficientFeeForTransactionSize);
                        continue;
                    }
                }

                spendFromEnumerated.ForEach(vout => context.CreateRawTransaction.AddInput(vout.Hash, (int)vout.Index));
                spendToEnumerated.ForEach(spt => context.CreateRawTransaction.AddOutput(spt.PublicKey, spt.Amount));

                if (change > 0)
                {
                    context.CreateRawTransaction.AddOutput(item.ChangeAddress, change);
                }
            }

            if (context.SendItems.All(w => w.Failed))
            {
                return(context);
            }

            //// create the builder and build the transaction (either a client using the coins client or locally if supported).
            await TransactionBuilder.Create(param).Build(client, context);

            // seems we are done here
            return(context);
        }
Exemple #4
0
        private static async Task <TransactionContext> CreateDeStreamAsync(CoinParameters param, IBitcoinClient client,
                                                                           IEnumerable <TransactionInfo> items)
        {
            const decimal feeRate = (decimal)0.0077;

            var context = new TransactionContext
            {
                CoinParameters          = param,
                PreviousRawTransactions = new List <DecodedRawTransaction>(),
                CreateRawTransaction    = new CreateRawTransaction(),
                SendItems = new List <TransactionInfo>()
            };

            foreach (var item in items)
            {
                context.SendItems.Add(item);

                // check if receiving address is already in the transaction, this is allowed but not supported by the client.
                if (item.SpendToAddresses.Select(s => s.PublicKey)
                    .Intersect(item.SpendFromAddresses.Select(s => s.PublicKey)).Any())
                {
                    item.Fail(false, FailedReason.CannotSendToSelfUnlessChange);
                    continue;
                }

                if (item.SpendFromAddresses.SelectMany(s => s.Transactions).None())
                {
                    item.Fail(false, FailedReason.NoTransactionsFound);
                    continue;
                }

                // check if a transaction is already in use
                if (context.CreateRawTransaction.Inputs.Any(source => item.SpendFromAddresses
                                                            .SelectMany(s => s.Transactions)
                                                            .Any(target => source.TransactionId == target.Hash && source.Output == target.Index)))
                {
                    item.Fail(true, FailedReason.TransactionInputAlreadyInUse);
                    continue;
                }

                // check if receiving address is already in the transaction, this is allowed but not supported by the client.
                if (context.CreateRawTransaction.Outputs
                    .Any(source => item.SpendToAddresses.Any(target => source.Key == target.PublicKey)))
                {
                    item.Fail(true, FailedReason.ReceiveAddressAlreadyInOutput);
                    continue;
                }

                if (item.SpendFromAddresses.SelectMany(s => s.Transactions).Any(t => t.ScriptPubKeyHex.IsNullOrEmpty()))
                {
                    item.Fail(false, FailedReason.ScriptPubKeyHexNotFound);
                    continue;
                }

                //// get the outputs associated with the sender address
                var spendToEnumerated   = item.SpendToAddresses.ToList();
                var spendFromEnumerated = item.SpendFromAddresses.SelectMany(s => s.Transactions).ToList();

                //// calculate the sum of inputs and outputs.
                var inputSum  = spendFromEnumerated.Select(t => t.Value).Sum();
                var outputSum = spendToEnumerated.Select(spt => spt.Amount).Sum();

                var change = inputSum - outputSum;

                if (change < 0)
                {
                    item.Fail(false, FailedReason.InvalidSum);
                    continue;
                }

                if (item.SpendToAddresses.Any(s => s.TakeFee))
                {
                    item.SpendToAddresses.First(s => s.TakeFee).Amount /= decimal.One + feeRate;
                }
                else
                {
                    change -= outputSum * feeRate;
                }

                if (change > 0)
                {
                    if (item.ChangeAddress.IsNullOrEmpty())
                    {
                        item.Fail(false, FailedReason.NoChangeAddressFound);
                        continue;
                    }
                }

                spendFromEnumerated.ForEach(vout => context.CreateRawTransaction.AddInput(vout.Hash, (int)vout.Index));
                spendToEnumerated.ForEach(spt => context.CreateRawTransaction.AddOutput(spt.PublicKey, spt.Amount));

                if (change > 0)
                {
                    context.CreateRawTransaction.AddOutput(item.ChangeAddress, change);
                    context.CreateRawTransaction.AddInput(item.ChangeAddress, -1);
                }
            }

            if (context.SendItems.All(w => w.Failed))
            {
                return(context);
            }

            //// create the builder and build the transaction (either a client using the coins client or locally if supported).
            await TransactionBuilder.Create(param).Build(client, context);

            // seems we are done here
            return(context);
        }