private static WalletTransaction.Output MarshalWalletTransactionOutput(TransactionDetails.Types.Output o, Transaction.Output txOutput) { if (o.Mine) return new WalletTransaction.Output.ControlledOutput(txOutput.Amount, new Account(o.Account), o.Internal); else return new WalletTransaction.Output.UncontrolledOutput(txOutput.Amount, txOutput.PkScript); }
private static void CheckCoinbaseSignatureScript(Transaction.Input coinbaseInput) { var scriptLength = coinbaseInput.SignatureScript.Length; if (scriptLength < BlockChain.MinCoinbaseScriptLength || scriptLength > BlockChain.MaxCoinbaseScriptLength) { throw new TransactionRuleException($"Coinbase transaction input signature script length {scriptLength} is outside valid range"); } }
private static WalletTransaction.Output[] MarshalCombinedOutputs(Transaction transaction, IEnumerator<TransactionDetails.Types.Output> credits) { return transaction.Outputs.Select((output, index) => { while (credits.Current?.Index < index) credits.MoveNext(); return credits.Current?.Index == index ? MarshalControlledOutput(credits.Current, output) : new WalletTransaction.Output.UncontrolledOutput(output.Amount, output.PkScript); }).ToArray(); }
public static bool IsCoinbase(Transaction tx) { if (tx == null) throw new ArgumentNullException(nameof(tx)); if (tx.Inputs.Length != 1) return false; var previousOutput = tx.Inputs[0].PreviousOutpoint; return previousOutput.Index == uint.MaxValue && previousOutput.Hash.Equals(Sha256Hash.Zero); }
private static void CheckHasInputsAndOutputs(Transaction tx) { if (tx.Inputs.Length == 0) { throw new TransactionRuleException("Transaction must have at least one input"); } if (tx.Outputs.Length == 0) { throw new TransactionRuleException("Transaction must have at least one output"); } }
private static void CheckOutputValueSanity(Transaction tx) { Amount outputSum = 0; int outputIndex = 0; foreach (var output in tx.Outputs) { if (!IsSaneOutputValue(output.Amount)) { throw new TransactionRuleException($"Output value {output.Amount} for output {outputIndex} is outside valid range"); } if (outputSum - MaxOutputValue + output.Amount > 0) { throw new TransactionRuleException("Total output value exceeds maximum"); } outputSum += output.Amount; outputIndex++; } }
public static void CheckSanity(Transaction tx) { if (tx == null) throw new ArgumentNullException(nameof(tx)); CheckHasInputsAndOutputs(tx); // TODO: check serialize size CheckOutputValueSanity(tx); if (BlockChain.IsCoinbase(tx)) { CheckCoinbaseSignatureScript(tx.Inputs[0]); } else { CheckNonCoinbaseInputs(tx); } }
public async Task<Tuple<Transaction, bool>> SignTransactionAsync(string passphrase, Transaction tx) { var client = WalletService.NewClient(_channel); var request = new SignTransactionRequest { Passphrase = ByteString.CopyFromUtf8(passphrase), SerializedTransaction = ByteString.CopyFrom(tx.Serialize()), }; var response = await client.SignTransactionAsync(request, cancellationToken: _tokenSource.Token); var signedTransaction = Transaction.Deserialize(response.Transaction.ToByteArray()); var complete = response.UnsignedInputIndexes.Count == 0; return Tuple.Create(signedTransaction, complete); }
private static WalletTransaction.Output MarshalControlledOutput(TransactionDetails.Types.Output o, Transaction.Output txOutput) => new WalletTransaction.Output.ControlledOutput(txOutput.Amount, new Account(o.Account), o.Internal);
private async Task SignTransactionWithPassphrase(string passphrase, Transaction.Output[] outputs, bool publishImmediately) { var walletClient = App.Current.WalletRpcClient; var requiredConfirmations = 1; // TODO: Don't hardcode confs. var targetAmount = outputs.Sum(o => o.Amount); var targetFee = (Amount)1e4; // TODO: Don't hardcode fee/kB. var funding = await walletClient.FundTransactionAsync(_account, targetAmount + targetFee, requiredConfirmations); var fundingAmount = funding.Item2; if (fundingAmount < targetAmount + targetFee) { MessageBox.Show($"Transaction requires {targetAmount + targetFee} but only {fundingAmount} is spendable.", "Insufficient funds to create transaction."); return; } var selectedOutputs = funding.Item1; var inputs = selectedOutputs .Select(o => { var prevOutPoint = new Transaction.OutPoint(o.TransactionHash, o.OutputIndex); return new Transaction.Input(prevOutPoint, new byte[0], TransactionRules.MaxInputSequence); }) .ToArray(); // TODO: Port the fee estimation logic from btcwallet. Using a hardcoded fee is unacceptable. var estimatedFee = targetFee; var changePkScript = funding.Item3; if (changePkScript != null) { // Change output amount is calculated by solving for changeAmount with the equation: // estimatedFee = fundingAmount - (targetAmount + changeAmount) var changeOutput = new Transaction.Output(fundingAmount - targetAmount - estimatedFee, changePkScript.Script); var outputsList = outputs.ToList(); // TODO: Randomize change output position. outputsList.Add(changeOutput); outputs = outputsList.ToArray(); } // TODO: User may want to set the locktime. var unsignedTransaction = new Transaction(Transaction.LatestVersion, inputs, outputs, 0); var signingResponse = await walletClient.SignTransactionAsync(passphrase, unsignedTransaction); var complete = signingResponse.Item2; if (!complete) { MessageBox.Show("Failed to create transaction input signatures."); return; } var signedTransaction = signingResponse.Item1; MessageBox.Show($"Created tx with {estimatedFee} fee."); if (!publishImmediately) { MessageBox.Show("Reviewing signed transaction before publishing is not implemented yet."); return; } // TODO: The client just deserialized the transaction, so serializing it is a // little silly. This could be optimized. await walletClient.PublishTransactionAsync(signedTransaction.Serialize()); MessageBox.Show("Published transaction."); }
private static void CheckNonCoinbaseInputs(Transaction tx) { var seenOutPoints = new HashSet<Transaction.OutPoint>(); foreach (var input in tx.Inputs) { if (seenOutPoints.Contains(input.PreviousOutpoint)) { throw new TransactionRuleException($"Transaction input contains duplicate previous output {input.PreviousOutpoint}"); } seenOutPoints.Add(input.PreviousOutpoint); if (input.PreviousOutpoint.IsNull()) { throw new TransactionRuleException("Non-coinbase transaction may not refer to a null previous output"); } } }
public static void SerializeSizes(Transaction tx, int expectedSerializeSize) { var serializeSize = tx.SerializeSize; Assert.Equal(expectedSerializeSize, serializeSize); }