Skip to content
This repository has been archived by the owner on Jun 4, 2018. It is now read-only.

Commit

Permalink
Add RFP0001 work done so far.
Browse files Browse the repository at this point in the history
Remaining issues should be fixed on feature branches and submitted
with PRs.
  • Loading branch information
jrick committed Jun 29, 2016
1 parent aabe292 commit 64d92fc
Show file tree
Hide file tree
Showing 129 changed files with 5,873 additions and 2,553 deletions.
22 changes: 18 additions & 4 deletions Paymetheus.Decred/Amount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,37 @@ public Amount(long value)

public struct Denomination
{
public static readonly Denomination Decred = new Denomination((long)1e8, "{0}{1:#,0}{2,-9:.0#######}", "DCR");
public static readonly Denomination MillaDecred = new Denomination((long)1e5, "{0}{1:#,0}{2,-6:.0####}", "mDCR");
public static readonly Denomination MicroDecred = new Denomination((long)1e2, "{0}{1:#,0}{2:.00}", "μDCR");
public static readonly Denomination Decred = new Denomination((long)1e8, 8, "{0}{1:#,0}{2,-2:.0#######}", "DCR");
public static readonly Denomination MillaDecred = new Denomination((long)1e5, 5, "{0}{1:#,0}{2,-2:.0####}", "mDCR");
public static readonly Denomination MicroDecred = new Denomination((long)1e2, 2, "{0}{1:#,0}{2:.00}", "μDCR");

private Denomination(long atomsPerUnit, string formatString, string ticker)
private Denomination(long atomsPerUnit, int decimalPoints, string formatString, string ticker)
{
_atomsPerUnit = atomsPerUnit;
_formatString = formatString;

DecimalPoints = decimalPoints;
Ticker = ticker;
}

private readonly long _atomsPerUnit;
private readonly string _formatString;

public int DecimalPoints { get; }
public string Ticker { get; }

public Tuple<long, double> Split(Amount amount)
{
if (amount < 0)
{
amount = -amount;
}

var wholePart = amount / _atomsPerUnit;
var decimalPart = (double)(amount % _atomsPerUnit) / _atomsPerUnit;
return Tuple.Create(wholePart, decimalPart);
}

public string FormatAmount(Amount amount)
{
// The negative sign has to be printed separately. It can not be displayed as a
Expand Down
14 changes: 14 additions & 0 deletions Paymetheus.Decred/Errors.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) 2016 The btcsuite developers
// Copyright (c) 2016 The Decred developers
// Licensed under the ISC license. See LICENSE file in the project root for full license information.

using System;

namespace Paymetheus.Decred
{
internal static class Errors
{
public static ArgumentOutOfRangeException RequireNonNegative(string paramName) =>
new ArgumentOutOfRangeException(paramName, "Non-negative number required.");
}
}
5 changes: 5 additions & 0 deletions Paymetheus.Decred/Paymetheus.Decred.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
<Compile Include="BlockChainConsistencyException.cs" />
<Compile Include="BlockChainIdentity.cs" />
<Compile Include="BlockIdentity.cs" />
<Compile Include="Errors.cs" />
<Compile Include="Script\SignatureAlgorithm.cs" />
<Compile Include="TransactionRules.cs" />
<Compile Include="Util\Base58.cs" />
Expand All @@ -75,8 +76,10 @@
<Compile Include="Transaction.cs" />
<Compile Include="Util\DateTimeOffsetExtras.cs" />
<Compile Include="Util\LittleEndian.cs" />
<Compile Include="Util\TupleValue.cs" />
<Compile Include="Util\ValueArray.cs" />
<Compile Include="Wallet\Account.cs" />
<Compile Include="Wallet\Balances.cs" />
<Compile Include="Wallet\AccountProperties.cs" />
<Compile Include="Wallet\Address.cs" />
<Compile Include="Wallet\AddressPrefix.cs" />
Expand All @@ -86,6 +89,8 @@
<Compile Include="Wallet\Accounting.cs" />
<Compile Include="Wallet\PgpWordList.cs" />
<Compile Include="Wallet\PgpWordListData.cs" />
<Compile Include="Wallet\TransactionAuthor.cs" />
<Compile Include="Wallet\TransactionFees.cs" />
<Compile Include="Wallet\TransactionSet.cs" />
<Compile Include="Wallet\UnspentOutput.cs" />
<Compile Include="Wallet\Wallet.cs" />
Expand Down
39 changes: 39 additions & 0 deletions Paymetheus.Decred/Transaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using Paymetheus.Decred.Util;
using System;
using System.Linq;

namespace Paymetheus.Decred
{
Expand Down Expand Up @@ -255,5 +256,43 @@ public void SerializeTo(byte[] destination, int offset = 0)
cursor.WriteVarBytes(input.SignatureScript);
}
}

private const int RedeemPayToPubKeyHashSigScriptSize = 1 + 73 + 1 + 33;
private const int PayToPubKeyHashPkScriptSize = 1 + 1 + 1 + 20 + 1 + 1;

// Worst case sizes for compressed p2pkh inputs and outputs.
// Used for estimating an unsigned transaction's worst case serialize size
// after it has been signed.
public const int RedeemPayToPubKeyHashInputSize = 32 + 4 + 1 + 8 + 4 + 4 + 1 + RedeemPayToPubKeyHashSigScriptSize + 4;
public const int PayToPubKeyHashOutputSize = 8 + 2 + 1 + PayToPubKeyHashPkScriptSize;

/// <summary>
/// Estimates the signed serialize size of an unsigned transaction, assuming all
/// inputs spend P2PKH outputs. The estimate is a worst case, so the actual
/// serialize size after signing should always be less than or equal to this value.
/// </summary>
/// <param name="inputCount">Number of P2PKH outputs the transaction will redeem</param>
/// <param name="outputs">Transaction output array</param>
/// <param name="addChangeOutput">Add the serialize size for an additional P2PKH change output</param>
/// <returns>Estimated signed serialize size of the transaction</returns>
public static int EstimateSerializeSize(int inputCount, Output[] outputs, bool addChangeOutput)
{
if (outputs == null)
throw new ArgumentNullException(nameof(outputs));

var changeSize = 0;
var outputCount = outputs.Length;
if (addChangeOutput)
{
changeSize = PayToPubKeyHashOutputSize;
outputCount++;
}

return
12 + (2 * CompactInt.SerializeSize((ulong)inputCount)) + CompactInt.SerializeSize((ulong)outputCount) +
inputCount * RedeemPayToPubKeyHashInputSize +
outputs.Sum(o => o.SerializeSize) +
changeSize;
}
}
}
20 changes: 20 additions & 0 deletions Paymetheus.Decred/TransactionRules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,26 @@ public static class TransactionRules
/// </summary>
public static bool IsSaneOutputValue(Amount a) => a >= 0 && a <= MaxOutputValue;

/// <summary>
/// Check whether an output is considered dust for a given transaction relay fee.
/// Transactions with dust outputs are rejected by mempool.
/// </summary>
/// <param name="output">Transaction output to check</param>
/// <param name="relayFeePerKb">Mempool relay fee/kB</param>
/// <returns>Whether the output is dust</returns>
public static bool IsDust(Transaction.Output output, Amount relayFeePerKb)
{
// TODO: Rather than assumming the output is P2PKH and using the size of a
// P2PKH input script to estimate the total cost to the network, a better
// estimate could be used if the output script is one of the other recognized
// script kinds.
var totalSize = output.SerializeSize + Transaction.RedeemPayToPubKeyHashInputSize;

// Dust is defined as an output value where the total cost to the network
// (output size + input size) is greater than 1/3 of the relay fee.
return output.Amount * 1000 / (3 * totalSize) < relayFeePerKb;
}

public static void CheckSanity(Transaction tx)
{
if (tx == null)
Expand Down
47 changes: 47 additions & 0 deletions Paymetheus.Decred/Util/TupleValue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) 2016 The btcsuite developers
// Copyright (c) 2016 The Decred developers
// Licensed under the ISC license. See LICENSE file in the project root for full license information.

namespace Paymetheus.Decred.Util
{
public struct TupleValue<T1, T2>
{
public TupleValue(T1 item1, T2 item2)
{
Item1 = item1;
Item2 = item2;
}

public T1 Item1 { get; }
public T2 Item2 { get; }
}

public struct TupleValue<T1, T2, T3>
{
public TupleValue(T1 item1, T2 item2, T3 item3)
{
Item1 = item1;
Item2 = item2;
Item3 = item3;
}

public T1 Item1 { get; }
public T2 Item2 { get; }
public T3 Item3 { get; }
}

/// <summary>
/// TupleValue is an efficient alternative for the System.Tuple types. TupleValue is a
/// struct (value type) while Tuple is a class (reference type). A similar TupleValue struct
/// and tuple syntax sugar is planned for C# 7, which would eliminate the need for these
/// types if added.
/// </summary>
public static class TupleValue
{
public static TupleValue<T1, T2> Create<T1, T2>(T1 item1, T2 item2) =>
new TupleValue<T1, T2>(item1, item2);

public static TupleValue<T1, T2, T3> Create<T1, T2, T3>(T1 item1, T2 item2, T3 item3) =>
new TupleValue<T1, T2, T3>(item1, item2, item3);
}
}
25 changes: 25 additions & 0 deletions Paymetheus.Decred/Wallet/Balances.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) 2016 The Decred developers
// Licensed under the ISC license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Paymetheus.Decred.Wallet
{
public struct Balances
{
public Balances(Amount total, Amount spendable, Amount locked)
{
TotalBalance = total;
SpendableBalance = spendable;
LockedBalance = locked;
}

public Amount TotalBalance { get; internal set; }
public Amount SpendableBalance { get; internal set; }
public Amount LockedBalance { get; internal set; }
}
}
82 changes: 82 additions & 0 deletions Paymetheus.Decred/Wallet/TransactionAuthor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) 2016 The btcsuite developers
// Copyright (c) 2016 The Decred developers
// Licensed under the ISC license. See LICENSE file in the project root for full license information.

using Paymetheus.Decred.Script;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace Paymetheus.Decred.Wallet
{
public static class TransactionAuthor
{
/// <summary>
/// Provides transaction inputs referencing spendable outputs to construct a
/// transaction with some target output amount.
/// </summary>
/// <param name="target">Target amount the previous output value must meet or exceed.</param>
/// <returns>Total previous output amount and array of transaction inputs if the target was met.</returns>
public delegate Task<Tuple<Amount, Transaction.Input[]>> InputSource(Amount target);

/// <summary>
/// Constructs an unsigned transaction by referencing previous unspent outputs.
/// A change output is added when necessary to return extra value back to the wallet.
/// </summary>
/// <param name="outputs">Transaction output array without change.</param>
/// <param name="changeScript">Output script to pay change to.</param>
/// <param name="fetchInputsAsync">Input selection source.</param>
/// <returns>Unsigned transaction and total input amount.</returns>
/// <exception cref="InsufficientFundsException">Input source was unable to provide enough input value.</exception>
public static async Task<Tuple<Transaction, Amount>> BuildUnsignedTransaction(Transaction.Output[] outputs,
OutputScript changeScript,
Amount feePerKb,
InputSource fetchInputsAsync)
{
if (outputs == null)
throw new ArgumentNullException(nameof(outputs));
if (changeScript == null)
throw new ArgumentNullException(nameof(changeScript));
if (fetchInputsAsync == null)
throw new ArgumentNullException(nameof(fetchInputsAsync));

var targetAmount = outputs.Sum(o => o.Amount);
var estimatedSize = Transaction.EstimateSerializeSize(1, outputs, true);
var targetFee = TransactionFees.FeeForSerializeSize(feePerKb, estimatedSize);

while (true)
{
var funding = await fetchInputsAsync(targetAmount + targetFee);
var inputAmount = funding.Item1;
var inputs = funding.Item2;
if (inputAmount < targetAmount + targetFee)
{
throw new InsufficientFundsException();
}

var unsignedTransaction = new Transaction(Transaction.SupportedVersion, inputs, outputs, 0, 0);
if (inputAmount > targetAmount + targetFee)
{
unsignedTransaction = TransactionFees.AddChange(unsignedTransaction, inputAmount,
changeScript, feePerKb);
}

if (TransactionFees.EstimatedFeePerKb(unsignedTransaction, inputAmount) < feePerKb)
{
estimatedSize = Transaction.EstimateSerializeSize(inputs.Length, outputs, true);
targetFee = TransactionFees.FeeForSerializeSize(feePerKb, estimatedSize);
}
else
{
return Tuple.Create(unsignedTransaction, inputAmount);
}
}
}
}

public class InsufficientFundsException : Exception
{
public InsufficientFundsException() : base("Insufficient funds available to construct transaction.") { }
}
}

Loading

0 comments on commit 64d92fc

Please sign in to comment.