Commit 23daf4d4 authored by Sergei Zubov's avatar Sergei Zubov

Add DeStream fee to transactions

Fee is calculated based on fixed rate and splits up between miner and
DeStream wallet. Fee and split rates are network parameters.
Fee check is disabled, DeStream don't have minimal or maximal fee.
Funds are sent to DeStream wallet via additional output of CoinStake
transaction.
parent afa4ab6b
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Company>NStratis</Company>
<Copyright>Copyright © Stratis Platform SA 2017</Copyright>
<Description>The C# Bitcoin Library based on NBitcoin</Description>
</PropertyGroup>
<PropertyGroup>
<Version>4.0.0.54</Version>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>NBitcoin</RootNamespace>
......@@ -17,30 +14,24 @@
<Authors>NStratis</Authors>
<Product>NStratis</Product>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE;DEBUG;NETSTANDARD1_6;NETCORE;NOSTRNORMALIZE;NOCUSTOMSSLVALIDATION;</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>TRACE;NETSTANDARD1_6;NETCORE;NOSTRNORMALIZE;NOCUSTOMSSLVALIDATION;</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Protocol\Payloads\**" />
<EmbeddedResource Remove="Protocol\Payloads\**" />
<None Remove="Protocol\Payloads\**" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Protocol\AddressManager.cs" />
<Compile Remove="Protocol\NodesGroup.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="BitcoinStream.Partial.tt" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="NStratis.HashLib" Version="1.0.0.1" />
......@@ -51,9 +42,7 @@
<PackageReference Include="System.Threading.Tasks.Parallel" Version="4.3.0" />
<PackageReference Include="System.Threading.Thread" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
</Project>
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using NBitcoin.BouncyCastle.Math;
using NBitcoin.DataEncoders;
namespace NBitcoin
{
public abstract partial class Network
{
// TODO: Implement custom circular collection
public readonly IReadOnlyCollection<string> DeStreamWallets = new ReadOnlyCollection<string>(new List<string>()
{
"DQAa8Fg1ytS5wiXbn1qToRpe9wYSQhCAWc",
"DMoFqYQNfsoorMbmTbyErxk43ev9B2EuEe",
});
/// <summary>
///
/// </summary>
public double DeStreamFeePart { get; set; }
/// <summary>
/// Fee applied to all transactions
/// </summary>
public double FeeRate { get; set; }
}
}
namespace NBitcoin.Networks
{
public class DeStreamMain : Network
......
......@@ -12,16 +12,16 @@ namespace NBitcoin.Networks
public DeStreamTest() //: base()
{
var initialWalletAddresses = new []{
"TWyLf11aUSQvorSvG4oc3asMGXbqkf8MEa",
"TSX8RGmEod8K4a2SvPPWZtmJ5KtrBzzXSw",
"TTp1D1NrV1uwbuL2YvWm46M3xY8nYQLRHr",
"TBgvA3dKhGMGeWXpzCG9UUviXLFjZjsQ2S",
"TV37E8whdDUEzVFSsWRHHcj7bWbeDTv9gw",
"TWyiGrPmuKvcMj9s9SGR4BWzMxhZQXJxZk",
"TNL98Epf3ASKFod2QuincwNi2CxHLkkjMD",
"TG3N5ARtJaajqdNHgC9pxnW5kL9CeWkcDa",
"TA9GwihBb9KcW3evjxdVkUh1XdQ5wbEcif",
"TBxudKvSsw1hL7aGf9a34dSdxV4e97dx5y"
"DC6UcLUzq645UeqCkdk4iJk9tvMVDQ2Ytd",
"D9CKCEtU5cJ5BReBwf4YnWpSqcC7tr1oXv",
"DU3cTLWubkzMRGoCSef1G1Jp1tj8z9TGPD",
"D95x2iYdVVUwY5RnPjBmDKiJHToTgHhdor",
"DPPnSDe416McZ2CKgmUagnJwXZuZ8b31ZM",
"DHdc7gkwZRpKPTZzEf8TBQEthmfxuAJoUM",
"DJzLTGxadMGnHqByQtyUW3zsLq5f7mSvJz",
"D7UwtqLsCNkKb94tb6TiagUHRgF4UDEXMt",
"D7a8q2Ldfmh1vBaGrANPFwyKU7oNKBRtQH",
"DDmLwBBEoerPy8nZCAxcoyzwGwBs9zUhFq"
};
const decimal initialCoins = 6000000000;
......@@ -52,13 +52,16 @@ namespace NBitcoin.Networks
this.Consensus.LastPOWBlock = 12500;
this.Consensus.DefaultAssumeValid =
new uint256("0x98fa6ef0bca5b431f15fd79dc6f879dc45b83ed4b1bbe933a383ef438321958e"); // 372652
this.Consensus.CoinbaseMaturity = 10;
this.Consensus.CoinbaseMaturity = 1;
this.Consensus.MaxMoney = long.MaxValue;
this.Consensus.ProofOfWorkReward = Money.Zero;
this.Consensus.ProofOfStakeReward = Money.COIN;
this.Consensus.ProofOfStakeReward = Money.Zero;
this.Consensus.LastPOWBlock = 12500;
this.Consensus.CoinType = 3564;
this.DeStreamFeePart = 0.9;
this.FeeRate = 0.0077;
this.Base58Prefixes[(int) Base58Type.PUBKEY_ADDRESS] = new byte[] {30};
this.Base58Prefixes[(int) Base58Type.SCRIPT_ADDRESS] = new byte[] {90};
this.Base58Prefixes[(int) Base58Type.SECRET_KEY] = new byte[] {30 + 90};
......
......@@ -16,7 +16,7 @@ namespace NBitcoin.Policy
// TODO: replace fee params with whats in Network.
this.MaxTxFee = new FeeRate(Money.Coins(0.1m));
this.MinRelayTxFee = new FeeRate(Money.Satoshis(5000)); // TODO: new FeeRate(Money.Satoshis(network.MinRelayTxFee));
this.CheckFee = true;
this.CheckFee = false;
this.CheckScriptPubKey = true;
}
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NBitcoin;
using NBitcoin.DataEncoders;
using NBitcoin.Protocol;
using Stratis.Bitcoin.Base;
using Stratis.Bitcoin.Connection;
using Stratis.Bitcoin.Features.Consensus;
using Stratis.Bitcoin.Features.Consensus.CoinViews;
using Stratis.Bitcoin.Features.Consensus.Interfaces;
using Stratis.Bitcoin.Features.Consensus.Rules.CommonRules;
using Stratis.Bitcoin.Features.MemoryPool;
using Stratis.Bitcoin.Features.MemoryPool.Interfaces;
using Stratis.Bitcoin.Features.Wallet.Interfaces;
using Stratis.Bitcoin.Interfaces;
using Stratis.Bitcoin.Mining;
using Stratis.Bitcoin.Utilities;
namespace Stratis.Bitcoin.Features.Miner
{
public class DeStreamPosMinting : PosMinting
{
public DeStreamPosMinting(IBlockProvider blockProvider, IConsensusLoop consensusLoop, ConcurrentChain chain,
Network network, IConnectionManager connectionManager, IDateTimeProvider dateTimeProvider,
IInitialBlockDownloadState initialBlockDownloadState, INodeLifetime nodeLifetime, CoinView coinView,
IStakeChain stakeChain, IStakeValidator stakeValidator, MempoolSchedulerLock mempoolLock,
ITxMempool mempool, IWalletManager walletManager, IAsyncLoopFactory asyncLoopFactory,
ITimeSyncBehaviorState timeSyncBehaviorState, ILoggerFactory loggerFactory) : base(blockProvider,
consensusLoop, chain, network, connectionManager, dateTimeProvider, initialBlockDownloadState, nodeLifetime,
coinView, stakeChain, stakeValidator, mempoolLock, mempool, walletManager, asyncLoopFactory,
timeSyncBehaviorState, loggerFactory)
{
}
protected override void CoinstakeWorker(CoinstakeWorkerContext context, ChainedHeader chainTip, Block block,
long minimalAllowedTime, long searchInterval)
{
base.CoinstakeWorker(context, chainTip, block, minimalAllowedTime, searchInterval);
if (context.Result.KernelFoundIndex == CoinstakeWorkerResult.KernelNotFound)
return;
Script key = new KeyId(new uint160(Encoders.Base58Check.DecodeData(this.network.DeStreamWallets.First())
.Skip(this.network.Base58Prefixes[(int) Base58Type.PUBKEY_ADDRESS].Length).ToArray())).ScriptPubKey;
context.CoinstakeContext.CoinstakeTx.AddOutput(new TxOut(0, key));
}
// No way to call base function and change smth after this, replacing whole function is the only way.
// All modified code is extraced to functions.
public override async Task<bool> CreateCoinstakeAsync(List<UtxoStakeDescription> utxoStakeDescriptions,
Block block, ChainedHeader chainTip, long searchInterval,
long fees, CoinstakeContext coinstakeContext)
{
this.logger.LogTrace("({0}.{1}:{2},{3}:'{4}',{5}:{6},{7}:{8})", nameof(utxoStakeDescriptions),
nameof(utxoStakeDescriptions.Count), utxoStakeDescriptions.Count, nameof(chainTip), chainTip,
nameof(searchInterval), searchInterval, nameof(fees), fees);
int nonEmptyUtxos = utxoStakeDescriptions.Count;
coinstakeContext.CoinstakeTx.Inputs.Clear();
coinstakeContext.CoinstakeTx.Outputs.Clear();
// Mark coinstake transaction.
coinstakeContext.CoinstakeTx.Outputs.Add(new TxOut(Money.Zero, new Script()));
long balance = this.GetMatureBalance(utxoStakeDescriptions).Satoshi;
if (balance <= this.targetReserveBalance)
{
this.rpcGetStakingInfoModel.Staking = false;
this.logger.LogTrace(
"Total balance of available UTXOs is {0}, which is less than or equal to reserve balance {1}.",
balance, this.targetReserveBalance);
this.logger.LogTrace("(-)[BELOW_RESERVE]:false");
return false;
}
// Select UTXOs with suitable depth.
List<UtxoStakeDescription> stakingUtxoDescriptions =
this.GetUtxoStakeDescriptionsSuitableForStaking(utxoStakeDescriptions, chainTip,
coinstakeContext.CoinstakeTx.Time, balance - this.targetReserveBalance);
if (!stakingUtxoDescriptions.Any())
{
this.rpcGetStakingInfoModel.Staking = false;
this.logger.LogTrace("(-)[NO_SELECTION]:false");
return false;
}
long ourWeight = stakingUtxoDescriptions.Sum(s => s.TxOut.Value);
long expectedTime = StakeValidator.TargetSpacingSeconds * this.networkWeight / ourWeight;
decimal ourPercent = this.networkWeight != 0 ? 100.0m * ourWeight / this.networkWeight : 0;
this.logger.LogInformation(
"Node staking with {0} ({1:0.00} % of the network weight {2}), est. time to find new block is {3}.",
new Money(ourWeight), ourPercent, new Money(this.networkWeight), TimeSpan.FromSeconds(expectedTime));
this.rpcGetStakingInfoModel.Staking = true;
this.rpcGetStakingInfoModel.Weight = ourWeight;
this.rpcGetStakingInfoModel.ExpectedTime = expectedTime;
this.rpcGetStakingInfoModel.Errors = null;
long minimalAllowedTime = chainTip.Header.Time + 1;
this.logger.LogTrace(
"Trying to find staking solution among {0} transactions, minimal allowed time is {1}, coinstake time is {2}.",
stakingUtxoDescriptions.Count, minimalAllowedTime, coinstakeContext.CoinstakeTx.Time);
// If the time after applying the mask is lower than minimal allowed time,
// it is simply too early for us to mine, there can't be any valid solution.
if ((coinstakeContext.CoinstakeTx.Time & ~BlockHeaderPosContextualRule.StakeTimestampMask) <
minimalAllowedTime)
{
this.logger.LogTrace("(-)[TOO_EARLY_TIME_AFTER_LAST_BLOCK]:false");
return false;
}
// Create worker tasks that will look for kernel.
// Run task in parallel using the default task scheduler.
int coinIndex = 0;
int workerCount = (stakingUtxoDescriptions.Count + UtxoStakeDescriptionsPerCoinstakeWorker - 1) /
UtxoStakeDescriptionsPerCoinstakeWorker;
var workers = new Task[workerCount];
var workerContexts = new CoinstakeWorkerContext[workerCount];
var workersResult = new CoinstakeWorkerResult();
for (int workerIndex = 0; workerIndex < workerCount; workerIndex++)
{
var cwc = new CoinstakeWorkerContext
{
Index = workerIndex,
Logger = this.loggerFactory.CreateLogger(this.GetType().FullName, $"[Worker #{workerIndex}] "),
utxoStakeDescriptions = new List<UtxoStakeDescription>(),
CoinstakeContext = coinstakeContext,
Result = workersResult
};
int stakingUtxoCount = Math.Min(stakingUtxoDescriptions.Count - coinIndex,
UtxoStakeDescriptionsPerCoinstakeWorker);
cwc.utxoStakeDescriptions.AddRange(stakingUtxoDescriptions.GetRange(coinIndex, stakingUtxoCount));
coinIndex += stakingUtxoCount;
workerContexts[workerIndex] = cwc;
workers[workerIndex] = Task.Run(() =>
this.CoinstakeWorker(cwc, chainTip, block, minimalAllowedTime, searchInterval));
}
await Task.WhenAll(workers).ConfigureAwait(false);
if (workersResult.KernelFoundIndex == CoinstakeWorkerResult.KernelNotFound)
{
this.logger.LogTrace("(-)[KERNEL_NOT_FOUND]:false");
return false;
}
this.logger.LogTrace("Worker #{0} found the kernel.", workersResult.KernelFoundIndex);
// Get reward for newly created block.
long reward = this.GetReward(fees, chainTip.Height);
if (reward < 0)
{
// TODO: This can't happen unless we remove reward for mined block.
// If this can happen over time then this check could be done much sooner
// to avoid a lot of computation.
this.logger.LogTrace("(-)[NO_REWARD]:false");
return false;
}
// Split stake if above threshold.
this.SplitStake(nonEmptyUtxos, chainTip, coinstakeContext.CoinstakeTx.Outputs);
// Input to coinstake transaction.
UtxoStakeDescription coinstakeInput = workersResult.KernelCoin;
// Total amount of input values in coinstake transaction.
long coinstakeInputValue = coinstakeInput.TxOut.Value + reward;
// Set output amount.
this.SetOutputAmount(coinstakeContext.CoinstakeTx.Outputs, coinstakeInputValue, fees);
// Sign.
if (!this.SignTransactionInput(coinstakeInput, coinstakeContext.CoinstakeTx))
{
this.logger.LogTrace("(-)[SIGN_FAILED]:false");
return false;
}
// Limit size.
int serializedSize =
coinstakeContext.CoinstakeTx.GetSerializedSize(ProtocolVersion.ALT_PROTOCOL_VERSION,
SerializationType.Network);
if (serializedSize >= MaxBlockSizeGen / 5)
{
this.logger.LogTrace("Coinstake size {0} bytes exceeded limit {1} bytes.", serializedSize,
MaxBlockSizeGen / 5);
this.logger.LogTrace("(-)[SIZE_EXCEEDED]:false");
return false;
}
// Successfully generated coinstake.
this.logger.LogTrace("(-):true");
return true;
}
private void SetOutputAmount(TxOutList outputs, long coinstakeInputValue, long fees)
{
if (outputs.Count == 4)
{
outputs[1].Value = coinstakeInputValue / 2 / Money.CENT * Money.CENT;
outputs[2].Value = coinstakeInputValue - outputs[1].Value;
outputs[3].Value = (long) (fees * this.network.DeStreamFeePart);
this.logger.LogTrace("Coinstake first output value is {0}, second is {1}, third is {3}.",
outputs[1].Value, outputs[2].Value, outputs[3].Value);
}
else
{
outputs[1].Value = coinstakeInputValue;
outputs[2].Value = (long) (fees * this.network.DeStreamFeePart);
this.logger.LogTrace("Coinstake first output value is {0}, second is {1} .", outputs[1].Value,
outputs[2].Value);
}
}
private long GetReward(long fees, int chainTipHeight)
{
return (long) (fees * (1 - this.network.DeStreamFeePart)) +
this.consensusLoop.ConsensusRules.GetRule<PosCoinviewRule>()
.GetProofOfStakeReward(chainTipHeight + 1);
}
private void SplitStake(int nonEmptyUtxos, ChainedHeader chainTip, TxOutList outputs)
{
if (!this.GetSplitStake(nonEmptyUtxos, chainTip)) return;
this.logger.LogTrace("Coinstake UTXO will be split to two.");
outputs.Insert(2, new TxOut(0, outputs[1].ScriptPubKey));
}
}
}
\ No newline at end of file
......@@ -250,7 +250,7 @@ namespace Stratis.Bitcoin.Features.Miner
.FeatureServices(services =>
{
services.AddSingleton<IPowMining, PowMining>();
services.AddSingleton<IPosMinting, PosMinting>();
services.AddSingleton<IPosMinting, DeStreamPosMinting>();
services.AddSingleton<IBlockProvider, BlockProvider>();
services.AddSingleton<BlockDefinition, PowBlockDefinition>();
services.AddSingleton<BlockDefinition, PosBlockDefinition>();
......
......@@ -198,16 +198,16 @@ namespace Stratis.Bitcoin.Features.Miner
/// <summary>Number of UTXO descriptions that a single worker's task will process.</summary>
/// <remarks>To achieve a good level of parallelism, this should be low enough so that CPU threads are used,
/// but high enough to compensate for tasks' overhead.</remarks>
private const int UtxoStakeDescriptionsPerCoinstakeWorker = 25;
protected const int UtxoStakeDescriptionsPerCoinstakeWorker = 25;
/// <summary>Consumes incoming blocks, validates and executes them.</summary>
private readonly IConsensusLoop consensusLoop;
protected readonly IConsensusLoop consensusLoop;
/// <summary>Thread safe access to the best chain of block headers (that the node is aware of) from genesis.</summary>
private readonly ConcurrentChain chain;
/// <summary>Specification of the network the node runs on - regtest/testnet/mainnet.</summary>
private readonly Network network;
protected readonly Network network;
/// <summary>Provider of information about the node's connection to it's network peers.</summary>
/// <remarks>Used to verify that node is connected to network before we start staking.</remarks>
......@@ -232,13 +232,13 @@ namespace Stratis.Bitcoin.Features.Miner
private readonly IAsyncLoopFactory asyncLoopFactory;
/// <summary>A manager providing operations on wallets.</summary>
private readonly IWalletManager walletManager;
protected readonly IWalletManager walletManager;
/// <summary>Factory for creating loggers.</summary>
private readonly ILoggerFactory loggerFactory;
protected readonly ILoggerFactory loggerFactory;
/// <summary>Instance logger.</summary>
private readonly ILogger logger;
protected readonly ILogger logger;
/// <summary>Loop in which the node attempts to generate new POS blocks by staking coins from its wallet.</summary>
private IAsyncLoop stakingLoop;
......@@ -256,7 +256,7 @@ namespace Stratis.Bitcoin.Features.Miner
/// Target reserved balance that will not participate in staking.
/// It is possible that less than this amount will be reserved.
/// </summary>
private Money targetReserveBalance;
protected Money targetReserveBalance;
/// <summary>Time in milliseconds between attempts to generate PoS blocks.</summary>
private readonly int minerSleep;
......@@ -273,10 +273,10 @@ namespace Stratis.Bitcoin.Features.Miner
/// <summary>Information about node's staking for RPC "getstakinginfo" command.</summary>
/// <remarks>This object does not need a synchronized access because there is no execution logic
/// that depends on the reported information.</remarks>
private Models.GetStakingInfoModel rpcGetStakingInfoModel;
protected Models.GetStakingInfoModel rpcGetStakingInfoModel;
/// <summary>Estimation of the total staking weight of all nodes on the network.</summary>
private long networkWeight;
protected long networkWeight;
/// <summary>
/// Timestamp of the last attempt to search for POS solution.
......@@ -695,7 +695,7 @@ namespace Stratis.Bitcoin.Features.Miner
}
/// <inheritdoc/>
public async Task<bool> CreateCoinstakeAsync(List<UtxoStakeDescription> utxoStakeDescriptions, Block block, ChainedHeader chainTip, long searchInterval, long fees, CoinstakeContext coinstakeContext)
public virtual async Task<bool> CreateCoinstakeAsync(List<UtxoStakeDescription> utxoStakeDescriptions, Block block, ChainedHeader chainTip, long searchInterval, long fees, CoinstakeContext coinstakeContext)
{
this.logger.LogTrace("({0}.{1}:{2},{3}:'{4}',{5}:{6},{7}:{8})", nameof(utxoStakeDescriptions), nameof(utxoStakeDescriptions.Count), utxoStakeDescriptions.Count, nameof(chainTip), chainTip, nameof(searchInterval), searchInterval, nameof(fees), fees);
......@@ -855,7 +855,7 @@ namespace Stratis.Bitcoin.Features.Miner
/// <param name="block">Template of the block that we are trying to mine.</param>
/// <param name="minimalAllowedTime">Minimal valid timestamp for new coinstake transaction.</param>
/// <param name="searchInterval">Length of an unexplored block time space in seconds. It only makes sense to look for a solution within this interval.</param>
private void CoinstakeWorker(CoinstakeWorkerContext context, ChainedHeader chainTip, Block block, long minimalAllowedTime, long searchInterval)
protected virtual void CoinstakeWorker(CoinstakeWorkerContext context, ChainedHeader chainTip, Block block, long minimalAllowedTime, long searchInterval)
{
context.Logger.LogTrace("({0}:'{1}',{2}:{3},{4}:{5})", nameof(chainTip), chainTip, nameof(minimalAllowedTime), minimalAllowedTime, nameof(searchInterval), searchInterval);
......@@ -935,6 +935,7 @@ namespace Stratis.Bitcoin.Features.Miner
utxoStakeInfo.Key = context.CoinstakeContext.Key;
context.CoinstakeContext.CoinstakeTx.Time = txTime;
context.CoinstakeContext.CoinstakeTx.AddInput(new TxIn(prevoutStake));
context.CoinstakeContext.CoinstakeTx.Outputs.Add(new TxOut(0, scriptPubKeyOut));
context.Result.KernelCoin = utxoStakeInfo;
......@@ -970,7 +971,7 @@ namespace Stratis.Bitcoin.Features.Miner
/// <param name="input">Transaction input.</param>
/// <param name="transaction">Transaction being built.</param>
/// <returns><c>true</c> if the function succeeds, <c>false</c> otherwise.</returns>
private bool SignTransactionInput(UtxoStakeDescription input, Transaction transaction)
protected bool SignTransactionInput(UtxoStakeDescription input, Transaction transaction)
{
this.logger.LogTrace("({0}:'{1}')", nameof(input), input.OutPoint);
......@@ -1205,7 +1206,7 @@ namespace Stratis.Bitcoin.Features.Miner
/// <remarks>The coinstake is split if the number of non-empty UTXOs we have in the wallet
/// is under the given threshold.</remarks>
/// <seealso cref="CoinstakeSplitLimitMultiplier"/>
private bool GetSplitStake(int utxoCount, ChainedHeader chainTip)
protected bool GetSplitStake(int utxoCount, ChainedHeader chainTip)
{
this.logger.LogTrace("({0}:{1})", nameof(utxoCount), utxoCount);
......
......@@ -29,7 +29,7 @@ namespace Stratis.Bitcoin.Features.Wallet
.FeatureServices(services =>
{
services.AddSingleton<IWalletSyncManager, DeStreamWalletSyncManager>();
services.AddSingleton<IWalletTransactionHandler, WalletTransactionHandler>();
services.AddSingleton<IWalletTransactionHandler, DeStreamWalletTransactionHandler>();
services.AddSingleton<IDeStreamWalletManager, DeStreamWalletManager>();
services.AddSingleton<IWalletManager>(p => p.GetService<IDeStreamWalletManager>());
services.AddSingleton<IWalletFeePolicy, WalletFeePolicy>();
......
using System.Linq;
using Microsoft.Extensions.Logging;
using NBitcoin;
using Stratis.Bitcoin.Features.Wallet.Interfaces;
namespace Stratis.Bitcoin.Features.Wallet
{
public class DeStreamWalletTransactionHandler : WalletTransactionHandler
{
public DeStreamWalletTransactionHandler(ILoggerFactory loggerFactory, IWalletManager walletManager,
IWalletFeePolicy walletFeePolicy, Network network) : base(loggerFactory, walletManager, walletFeePolicy,
network)
{
}
/// <inheritdoc />
protected override void AddFee(TransactionBuildContext context)
{
long fee = (long) (context.Recipients.Sum(p => p.Amount) * this.Network.FeeRate);
context.TransactionFee = fee;
context.TransactionBuilder.SendFees(fee);
}
}
}
\ No newline at end of file
......@@ -350,7 +350,7 @@ namespace Stratis.Bitcoin.Features.Wallet
/// Use the <see cref="FeeRate"/> from the <see cref="walletFeePolicy"/>.
/// </summary>
/// <param name="context">The context associated with the current transaction being built.</param>
private void AddFee(TransactionBuildContext context)
protected virtual void AddFee(TransactionBuildContext context)
{
Money fee;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment