public TrustedBroadcastRequest FulfillOffer( TransactionSignature clientSignature, Script cashout, FeeRate feeRate) { if (clientSignature == null) { throw new ArgumentNullException(nameof(clientSignature)); } if (feeRate == null) { throw new ArgumentNullException(nameof(feeRate)); } AssertState(SolverServerStates.WaitingFulfillment); var offer = GetUnsignedOfferTransaction(); PubKey clientKey = AssertValidSignature(clientSignature, offer); offer.Inputs[0].ScriptSig = new Script( Op.GetPushOp(clientSignature.ToBytes()), Op.GetPushOp(CreateOfferSignature().ToBytes()), Op.GetPushOp(InternalState.EscrowedCoin.Redeem.ToBytes()) ); offer.Inputs[0].Witnessify(); if (!offer.Inputs.AsIndexedInputs().First().VerifyScript(InternalState.EscrowedCoin)) { throw new PuzzleException("invalid-tumbler-signature"); } var solutions = InternalState.SolvedPuzzles.Select(s => s.SolutionKey).ToArray(); Transaction fulfill = new Transaction(); fulfill.Inputs.Add(new TxIn()); fulfill.Outputs.Add(new TxOut(InternalState.OfferCoin.Amount, cashout)); var fulfillScript = SolverScriptBuilder.CreateFulfillScript(null, solutions); fulfill.Inputs[0].ScriptSig = fulfillScript + Op.GetPushOp(InternalState.OfferCoin.Redeem.ToBytes()); fulfill.Inputs[0].Witnessify(); fulfill.Outputs[0].Value -= feeRate.GetFee(fulfill.GetVirtualSize()); InternalState.OfferClientSignature = clientSignature; InternalState.Status = SolverServerStates.WaitingEscape; return(new TrustedBroadcastRequest { Key = InternalState.FulfillKey, PreviousScriptPubKey = InternalState.OfferCoin.ScriptPubKey, Transaction = fulfill }); }
public OfferInformation CheckBlindedFactors(BlindFactor[] blindFactors, FeeRate feeRate) { if (blindFactors == null) { throw new ArgumentNullException(nameof(blindFactors)); } if (blindFactors.Length != Parameters.RealPuzzleCount) { throw new ArgumentException("Expecting " + Parameters.RealPuzzleCount + " blind factors"); } AssertState(SolverServerStates.WaitingBlindFactor); Puzzle unblindedPuzzle = null; int y = 0; for (int i = 0; i < Parameters.RealPuzzleCount; i++) { var solvedPuzzle = InternalState.SolvedPuzzles[i]; var unblinded = new Puzzle(Parameters.ServerKey, solvedPuzzle.Puzzle).Unblind(blindFactors[i]); if (unblindedPuzzle == null) { unblindedPuzzle = unblinded; } else if (unblinded != unblindedPuzzle) { throw new PuzzleException("Invalid blind factor"); } y++; } InternalState.FulfillKey = new Key(); var offerScript = GetOfferScript(); Transaction tx = new Transaction(); tx.AddInput(new TxIn(InternalState.EscrowedCoin.Outpoint)); tx.AddOutput(new TxOut(InternalState.EscrowedCoin.Amount, offerScript.Hash)); InternalState.OfferTransactionFee = feeRate.GetFee(tx.GetVirtualSize() + 72 * 2 + InternalState.EscrowedCoin.Redeem.Length); tx = GetUnsignedOfferTransaction(); TransactionSignature signature = SignEscrow(tx); InternalState.OfferSignature = signature; InternalState.Status = SolverServerStates.WaitingFulfillment; return(new OfferInformation { FulfillKey = InternalState.FulfillKey.PubKey, Signature = signature, Fee = InternalState.OfferTransactionFee }); }
public static bool HasEnoughFundsForCycle(bool firstCycle, Money walletBalance, FeeRate networkFeeRate, Money tumblerDenomination, Money tumblerFee) { Money networkFee = networkFeeRate.GetFee(AverageClientEscrowTransactionSizeInBytes); Money minimumBalance = tumblerDenomination + tumblerFee + networkFee; Money fundsAllocatedToPreviousCycle = minimumBalance; if (firstCycle) { Logs.Client.LogInformation($"Performing wallet balance check; walletBalance = {walletBalance}, {minimumBalance}(minimumBalance) = {tumblerDenomination}(denomination) + {tumblerFee}(masternode fee) + {networkFee}(network fees)"); return(walletBalance >= minimumBalance); } else { Logs.Client.LogInformation($"Performing wallet balance check taking into account funds already allocated to previous cycle; {walletBalance - fundsAllocatedToPreviousCycle}(walletBalance) = {walletBalance}(current balance) - {fundsAllocatedToPreviousCycle}(fundsAllocatedToPreviousCycle), {minimumBalance}(minimumBalance) = {tumblerDenomination}(denomination) + {tumblerFee}(masternode fee) + {networkFee}(network fees)"); return(walletBalance >= minimumBalance + fundsAllocatedToPreviousCycle); } }
public void GoodSuggestion() { using Key key = new(); List <SmartCoin> coins = GenerateDummySmartCoins(key, 6_025, 6_561, 8_192, 13_122, 50_000, 100_000, 196_939, 524_288); var target = Money.Satoshis(150_000); var feeRate = new FeeRate(Money.Satoshis(2)); var txOut = new TxOut(target, BitcoinFactory.CreateBitcoinAddress(Network.TestNet, key)); long[] inputCosts = coins.Select(x => feeRate.GetFee(x.ScriptPubKey.EstimateInputVsize()).Satoshi).ToArray(); Dictionary <SmartCoin, long> inputEffectiveValues = new(coins.ToDictionary(x => x, x => x.EffectiveValue(feeRate).Satoshi)); var strategy = new MoreSelectionStrategy(target, inputEffectiveValues.Values.ToArray(), inputCosts); bool found = ChangelessTransactionCoinSelector.TryGetCoins(strategy, target, inputEffectiveValues, out var selectedCoins); Assert.True(found); long[] solution = selectedCoins !.Select(x => x.Amount.Satoshi).ToArray(); Assert.Equal(new long[] { 100_000, 50_000, 6_025 }, solution);
public Transaction GetSignedEscapeTransaction(TransactionSignature clientSignature, FeeRate feeRate, Script cashout) { AssertState(SolverServerStates.WaitingEscape); if (clientSignature.SigHash != (SigHash.AnyoneCanPay | SigHash.None)) { throw new PuzzleException("invalid-sighash"); } var escapeTx = new Transaction(); escapeTx.AddInput(new TxIn(InternalState.EscrowedCoin.Outpoint)); escapeTx.AddOutput(new TxOut(InternalState.EscrowedCoin.Amount, cashout)); escapeTx.Inputs[0].ScriptSig = new Script( Op.GetPushOp(TrustedBroadcastRequest.PlaceholderSignature), Op.GetPushOp(TrustedBroadcastRequest.PlaceholderSignature), Op.GetPushOp(InternalState.EscrowedCoin.Redeem.ToBytes()) ); escapeTx.Inputs[0].Witnessify(); var virtualSize = escapeTx.HasWitness ? escapeTx.GetVirtualSize(_Network.Consensus.Options.WitnessScaleFactor) : escapeTx.GetSerializedSize(); escapeTx.Outputs[0].Value -= feeRate.GetFee(virtualSize); AssertValidSignature(clientSignature, escapeTx); var tumblerSignature = escapeTx.SignInput(_Network, InternalState.EscrowKey, InternalState.EscrowedCoin); escapeTx.Inputs[0].ScriptSig = new Script( Op.GetPushOp(clientSignature.ToBytes()), Op.GetPushOp(tumblerSignature.ToBytes()), Op.GetPushOp(InternalState.EscrowedCoin.Redeem.ToBytes()) ); escapeTx.Inputs[0].Witnessify(); if (!escapeTx.Inputs.AsIndexedInputs().First().VerifyScript(_Network, InternalState.EscrowedCoin)) { throw new PuzzleException("invalid-tumbler-signature"); } return(escapeTx); }
public void CheckIsBTCChangeAmountCorrect2() { var tool = new Tool(); var dust = Money.Parse("10"); var fromUnspentCoin = new List <UnspentCoin> { tool.NewUnspentCoin(FromAddress, dust + Money.Parse("0.00000001")) }; var feeUnspentCoins = new List <UnspentCoin> { tool.NewUnspentCoin(FeeAddress, Money.Parse("10")) }; var allCoins = fromUnspentCoin.Concat(feeUnspentCoins).Select(o => o.AsCoin()).ToList(); var total = allCoins.Select(o => o.Amount).Sum(); var feeRate = new FeeRate(Money.Parse("1")); var result = this.InvokeBuild(fromUnspentCoin, feeUnspentCoins, dust, feeRate); var tx = result.Transaction; var changeOutput = tx.Outputs.SingleOrDefault(o => o.Value != dust && o.Value != Money.Zero); Assert.IsNotNull(changeOutput); Assert.IsTrue(changeOutput.IsTo(FeeAddress)); var txSize = Network.RegTest.CreateTransactionBuilder() .AddCoins(allCoins) .ContinueToBuild(tx) .EstimateSize(tx); var fee = feeRate.GetFee(txSize); var expectedChangeAmount = total - dust - fee; var actualChangeAmount = changeOutput.Value; Assert.AreEqual(expectedChangeAmount, actualChangeAmount); }
public void EstimatedFeeWithChangeOutput() { var tool = new Tool(); var dust = Money.Parse(this.DustCostBTC.ToString()); var feeAmount = Money.Parse("100"); var totalAmount = dust + feeAmount; var fromUnspentCoin = new List <UnspentCoin> { tool.NewUnspentCoin(FromAddress, dust) }; var fromCoin = fromUnspentCoin.Single().AsCoin(); var feeUnspentCoins = new List <UnspentCoin> { tool.NewUnspentCoin(FeeAddress, feeAmount) }; // 包含找零output的tx总大小 var size = 405; // 此费率刚好用完所有的Coin var exactFeeRateAmount = new Money(feeAmount.Satoshi / size * 1000); // 小于exactFeeRateAmount,则一定有找零output生成 var feeRateAmount = exactFeeRateAmount / 100; var feeRate = new FeeRate(feeRateAmount); var txInfo = this.InvokeBuild(fromUnspentCoin, feeUnspentCoins, estimateFeeRate: feeRate); var feeInfo = this.InvokeEstimateFinalFee(fromCoin, feeUnspentCoins, estimateFeeRate: feeRate); var tx = txInfo.Transaction; var outputAmount = tx.Outputs.Select(o => o.Value).Sum(); var actualUsedFee = totalAmount - outputAmount; var currentTxFee = feeRate.GetFee(txInfo.EstimatedSize); Assert.IsTrue(feeInfo.Fee == actualUsedFee && actualUsedFee == currentTxFee); }
public TrustedBroadcastRequest CreateOfferRedeemTransaction(FeeRate feeRate) { Transaction tx = new Transaction(); tx.LockTime = EscrowScriptPubKeyParameters.GetFromCoin(InternalState.EscrowedCoin).LockTime; tx.Inputs.Add(new TxIn()); tx.Inputs[0].Sequence = 0; tx.Outputs.Add(new TxOut(InternalState.OfferCoin.Amount, InternalState.RedeemDestination)); tx.Inputs[0].ScriptSig = new Script( Op.GetPushOp(TrustedBroadcastRequest.PlaceholderSignature), Op.GetPushOp(InternalState.OfferCoin.Redeem.ToBytes())); tx.Outputs[0].Value -= feeRate.GetFee(tx.GetVirtualSize()); var redeemTransaction = new TrustedBroadcastRequest { Key = InternalState.EscrowKey, PreviousScriptPubKey = InternalState.OfferCoin.ScriptPubKey, Transaction = tx }; return(redeemTransaction); }
public string CalculateTumblingDuration(string originWalletName) { if (TumblerParameters == null) { return(string.Empty); } /* Tumbling cycles occur up to 117 blocks (cycleDuration) and overlap every 24 blocks (cycleOverlap) :- * * Start block End block * ----------- --------- * 0 117 * 24 141 * 48 165 */ const int cycleDuration = 117; const int cycleOverlap = 24; Money walletBalance = this.runtime.Services.WalletService.GetBalance(originWalletName); FeeRate networkFeeRate = this.runtime.Services.FeeService.GetFeeRateAsync().GetAwaiter().GetResult(); if (!this.HasEnoughFundsForCycle(true, originWalletName)) { return(TimeSpanInWordFormat(0)); } var demonination = TumblerParameters.Denomination; var tumblerFee = TumblerParameters.Fee; var networkFee = networkFeeRate.GetFee(TumblerClientRuntime.AverageClientEscrowTransactionSizeInBytes); var cycleCost = demonination + tumblerFee + networkFee; var numberOfCycles = Math.Truncate(walletBalance.ToUnit(MoneyUnit.BTC) / cycleCost.ToDecimal(MoneyUnit.BTC)); var durationInBlocks = cycleDuration + ((numberOfCycles - 1) * cycleOverlap); var durationInHours = durationInBlocks * 10 / 60; return(TimeSpanInWordFormat(durationInHours)); }
public async Task GenerateCoinsAsync(int numberOfCoins, int seed, CancellationToken cancellationToken) { ThrowIfDisposed(); var feeRate = new FeeRate(4.0m); var(splitTx, spendingCoin) = Wallet.CreateTemplateTransaction(); var availableAmount = spendingCoin.EffectiveValue(feeRate, CoordinationFeeRate.Zero); var rnd = new Random(seed); double NextNotTooSmall() => 0.00001 + (rnd.NextDouble() * 0.99999); var sampling = Enumerable .Range(0, numberOfCoins - 1) .Select(_ => NextNotTooSmall()) .Prepend(0) .Prepend(1) .OrderBy(x => x) .ToArray(); var amounts = sampling .Zip(sampling.Skip(1), (x, y) => y - x) .Select(x => x * availableAmount.Satoshi) .Select(x => Money.Satoshis((long)x)); var scriptPubKey = Wallet.ScriptPubKey; foreach (var amount in amounts) { var effectiveOutputValue = amount - feeRate.GetFee(scriptPubKey.EstimateOutputVsize()); splitTx.Outputs.Add(new TxOut(effectiveOutputValue, scriptPubKey)); } await Wallet.SendRawTransactionAsync(Wallet.SignTransaction(splitTx), cancellationToken).ConfigureAwait(false); SplitTransaction = new SmartTransaction(splitTx, new Height(1)); }
public TrustedBroadcastRequest CreateRedeemTransaction(Network network, FeeRate feeRate) { if (feeRate == null) { throw new ArgumentNullException(nameof(feeRate)); } var escrow = EscrowScriptPubKeyParameters.GetFromCoin(InternalState.EscrowedCoin); var escrowCoin = InternalState.EscrowedCoin; Transaction tx = new Transaction(); tx.LockTime = escrow.LockTime; tx.Inputs.Add(new TxIn()); //Put a dummy signature and the redeem script tx.Inputs[0].ScriptSig = new Script( Op.GetPushOp(TrustedBroadcastRequest.PlaceholderSignature), Op.GetPushOp(escrowCoin.Redeem.ToBytes())); tx.Inputs[0].Witnessify(); tx.Inputs[0].Sequence = 0; tx.Outputs.Add(new TxOut(escrowCoin.Amount, InternalState.RedeemDestination)); var virtualSize = tx.HasWitness ? tx.GetVirtualSize(network.Consensus.Options.WitnessScaleFactor) : tx.GetSerializedSize(); tx.Outputs[0].Value -= feeRate.GetFee(tx.GetVirtualSize(virtualSize)); var redeemTransaction = new TrustedBroadcastRequest { Key = InternalState.EscrowKey, PreviousScriptPubKey = escrowCoin.ScriptPubKey, Transaction = tx, KnownPrevious = new Coin[] { escrowCoin } }; return(redeemTransaction); }
public override void CheckTransaction(MempoolValidationContext context) { if (context.Transaction.IsCoinBase) { return; } List <byte[]> opReturns = context.Transaction.Outputs.Select(o => o.ScriptPubKey.ToBytes(true)).Where(b => IsOpReturn(b)).ToList(); Money transactionFees = context.Fees; FeeRate OpReturnFeeRate = new FeeRate(((x42Consensus)this.network.Consensus).MinOpReturnFee); // If there is OP_RETURN data, we will want to make sure the fee is correct. if (opReturns.Count() > 0) { var opReturnSize = opReturns.Sum(r => r.Length); if (transactionFees < OpReturnFeeRate.GetFee(opReturnSize)) { this.logger.LogTrace($"(-)[FAIL_{nameof(x42OpReturnFeeMempoolRule)}]".ToUpperInvariant()); x42ConsensusErrors.InsufficientOpReturnFee.Throw(); } } base.CheckTransaction(context); }
private async Task <bool> TryConfirmConnectionAsync(long vsizeAllocationToRequest, CancellationToken cancellationToken) { var inputVsize = Coin.ScriptPubKey.EstimateInputVsize(); var vsizesToRequest = new[] { vsizeAllocationToRequest - inputVsize }; var totalFeeToPay = FeeRate.GetFee(Coin.ScriptPubKey.EstimateInputVsize()); var totalAmount = Coin.Amount; var effectiveAmount = totalAmount - totalFeeToPay; if (effectiveAmount <= Money.Zero) { throw new InvalidOperationException($"Round({ RoundId }), Alice({ AliceId}): Not enough funds to pay for the fees."); } var amountsToRequest = new[] { effectiveAmount.Satoshi }; var response = await ArenaClient .ConfirmConnectionAsync( RoundId, AliceId, amountsToRequest, vsizesToRequest, RealAmountCredentials, RealVsizeCredentials, cancellationToken) .ConfigureAwait(false); var isConfirmed = response.Value; if (isConfirmed) { RealAmountCredentials = response.RealAmountCredentials; RealVsizeCredentials = response.RealVsizeCredentials; } return(isConfirmed); }
public SignaturesRequest CreateSignatureRequest(Script cashoutDestination, FeeRate feeRate) { if (cashoutDestination == null) { throw new ArgumentNullException(nameof(cashoutDestination)); } if (feeRate == null) { throw new ArgumentNullException(nameof(feeRate)); } AssertState(PromiseClientStates.WaitingSignatureRequest); Transaction cashout = new Transaction(); cashout.AddInput(new TxIn(InternalState.EscrowedCoin.Outpoint, Script.Empty)); cashout.AddOutput(new TxOut(Money.Zero, cashoutDestination)); var tb = new TransactionBuilder(); tb.Extensions.Add(new EscrowBuilderExtension()); tb.AddCoins(InternalState.EscrowedCoin); var size = tb.EstimateSize(cashout, true); var fee = feeRate.GetFee(size); cashout.Outputs[0].Value = InternalState.EscrowedCoin.Amount - fee; List <HashBase> hashes = new List <HashBase>(); LockTime lockTime = new LockTime(0); for (int i = 0; i < Parameters.RealTransactionCount; i++) { RealHash h = new RealHash(cashout, InternalState.EscrowedCoin); h.LockTime = lockTime; lockTime++; hashes.Add(h); } for (int i = 0; i < Parameters.FakeTransactionCount; i++) { FakeHash h = new FakeHash(Parameters); h.Salt = new uint256(RandomUtils.GetBytes(32)); hashes.Add(h); } _Hashes = hashes.ToArray(); NBitcoin.Utils.Shuffle(_Hashes, RandomUtils.GetInt32()); for (int i = 0; i < _Hashes.Length; i++) { _Hashes[i].Index = i; } var fakeIndices = _Hashes.OfType <FakeHash>().Select(h => h.Index).ToArray(); uint256 indexSalt = null; var request = new SignaturesRequest { Hashes = _Hashes.Select(h => h.GetHash()).ToArray(), FakeIndexesHash = PromiseUtils.HashIndexes(ref indexSalt, fakeIndices), }; InternalState.IndexSalt = indexSalt; InternalState.Cashout = cashout.Clone(); InternalState.Status = PromiseClientStates.WaitingCommitments; InternalState.FakeIndexes = fakeIndices; return(request); }
public SignaturesRequest CreateSignatureRequest(Script cashoutDestination, FeeRate feeRate) { if (cashoutDestination == null) { throw new ArgumentNullException(nameof(cashoutDestination)); } if (feeRate == null) { throw new ArgumentNullException(nameof(feeRate)); } AssertState(PromiseClientStates.WaitingSignatureRequest); Transaction cashout = new Transaction(); cashout.AddInput(new TxIn(InternalState.EscrowedCoin.Outpoint)); cashout.Inputs[0].ScriptSig = new Script( Op.GetPushOp(TrustedBroadcastRequest.PlaceholderSignature), Op.GetPushOp(TrustedBroadcastRequest.PlaceholderSignature), Op.GetPushOp(InternalState.EscrowedCoin.Redeem.ToBytes()) ); cashout.Inputs[0].Witnessify(); cashout.AddOutput(new TxOut(InternalState.EscrowedCoin.Amount, cashoutDestination)); cashout.Outputs[0].Value -= feeRate.GetFee(cashout.GetVirtualSize()); List <HashBase> hashes = new List <HashBase>(); for (int i = 0; i < Parameters.RealTransactionCount; i++) { RealHash h = new RealHash(cashout, InternalState.EscrowedCoin); h.FeeVariation = Money.Satoshis(i); hashes.Add(h); } for (int i = 0; i < Parameters.FakeTransactionCount; i++) { FakeHash h = new FakeHash(Parameters); h.Salt = new uint256(RandomUtils.GetBytes(32)); hashes.Add(h); } _Hashes = hashes.ToArray(); NBitcoin.Utils.Shuffle(_Hashes, RandomUtils.GetInt32()); for (int i = 0; i < _Hashes.Length; i++) { _Hashes[i].Index = i; } var fakeIndices = _Hashes.OfType <FakeHash>().Select(h => h.Index).ToArray(); uint256 indexSalt = null; var request = new SignaturesRequest { Hashes = _Hashes.Select(h => h.GetHash()).ToArray(), FakeIndexesHash = PromiseUtils.HashIndexes(ref indexSalt, fakeIndices), }; InternalState.IndexSalt = indexSalt; InternalState.Cashout = cashout.Clone(); InternalState.Status = PromiseClientStates.WaitingCommitments; InternalState.FakeIndexes = fakeIndices; return(request); }
public OfferInformation CheckBlindedFactors(BlindFactor[] blindFactors, FeeRate feeRate) { if (blindFactors == null) { throw new ArgumentNullException(nameof(blindFactors)); } if (blindFactors.Length != Parameters.RealPuzzleCount) { throw new ArgumentException("Expecting " + Parameters.RealPuzzleCount + " blind factors"); } AssertState(SolverServerStates.WaitingBlindFactor); Puzzle unblindedPuzzle = null; int y = 0; for (int i = 0; i < Parameters.RealPuzzleCount; i++) { var solvedPuzzle = InternalState.SolvedPuzzles[i]; var unblinded = new Puzzle(Parameters.ServerKey, solvedPuzzle.Puzzle).Unblind(blindFactors[i]); if (unblindedPuzzle == null) { unblindedPuzzle = unblinded; } else if (unblinded != unblindedPuzzle) { throw new PuzzleException("Invalid blind factor"); } y++; } InternalState.FulfillKey = new Key(); Transaction dummy = new Transaction(); dummy.AddInput(new TxIn(InternalState.EscrowedCoin.Outpoint)); dummy.Inputs[0].ScriptSig = new Script( Op.GetPushOp(TrustedBroadcastRequest.PlaceholderSignature), Op.GetPushOp(TrustedBroadcastRequest.PlaceholderSignature), Op.GetPushOp(InternalState.EscrowedCoin.Redeem.ToBytes()) ); dummy.Inputs[0].Witnessify(); dummy.AddOutput(new TxOut(InternalState.EscrowedCoin.Amount, new Key().ScriptPubKey.Hash)); var offerTransactionFee = feeRate.GetFee(dummy.GetVirtualSize()); var escrow = InternalState.EscrowedCoin; var escrowInformation = EscrowScriptPubKeyParameters.GetFromCoin(InternalState.EscrowedCoin); var redeem = new OfferScriptPubKeyParameters { Hashes = InternalState.SolvedPuzzles.Select(p => p.SolutionKey.GetHash()).ToArray(), FulfillKey = InternalState.FulfillKey.PubKey, Expiration = escrowInformation.LockTime, RedeemKey = escrowInformation.Initiator }.ToScript(); var txOut = new TxOut(escrow.Amount - offerTransactionFee, redeem.WitHash.ScriptPubKey.Hash); InternalState.OfferCoin = new Coin(escrow.Outpoint, txOut).ToScriptCoin(redeem); InternalState.Status = SolverServerStates.WaitingFulfillment; return(new OfferInformation { FulfillKey = InternalState.FulfillKey.PubKey, Fee = offerTransactionFee }); }
public SignaturesRequest CreateSignatureRequest(Script cashoutDestination, FeeRate feeRate) { // Steps 2-4 // Almost done, just need to figure out the Transaction CashOut things. if (cashoutDestination == null) { throw new ArgumentNullException(nameof(cashoutDestination)); } if (feeRate == null) { throw new ArgumentNullException(nameof(feeRate)); } AssertState(PromiseClientStates.WaitingSignatureRequest); Transaction cashout = new Transaction(); // TODO: Figure out the cashout format to give j Bitcoins to Bob and Q-J to the Tumbler cashout.AddInput(new TxIn(InternalState.EscrowedCoin.Outpoint)); cashout.Inputs[0].ScriptSig = new Script( Op.GetPushOp(TrustedBroadcastRequest.PlaceholderSignature), Op.GetPushOp(TrustedBroadcastRequest.PlaceholderSignature), Op.GetPushOp(InternalState.EscrowedCoin.Redeem.ToBytes()) ); cashout.AddOutput(new TxOut(InternalState.EscrowedCoin.Amount, cashoutDestination)); cashout.Outputs[0].Value -= feeRate.GetFee(cashout.GetVirtualSize()); // If each payment level requires a different cashOut, then this // should be moved to the first loop. HashBase[][] hashes = new HashBase[_Parameters.PaymentsCount][]; //2D for (int i = 0; i < _Parameters.PaymentsCount; i++) { hashes[i] = new HashBase[_Parameters.GetTotalTransactionsCountPerLevel()]; for (int j = 0; j < Parameters.RealTransactionCountPerLevel; j++) { RealHash h = new RealHash(cashout, InternalState.EscrowedCoin) { FeeVariation = Money.Satoshis(i) }; hashes[i][j] = h; } for (int j = Parameters.RealTransactionCountPerLevel; j < hashes[i].Length; j++) { FakeHash h = new FakeHash(Parameters) { Salt = new uint256(RandomUtils.GetBytes(32)) }; hashes[i][j] = h; } } _Hashes = hashes; // Under the assumption that given the same seed the Shuffle will be deterministic. // TODO: Verify this in Debugging or a unit test. var shuffleSeed = RandomUtils.GetInt32(); for (int i = 0; i < _Parameters.PaymentsCount; i++) { NBitcoin.Utils.Shuffle(_Hashes[i], shuffleSeed); } for (int i = 0; i < _Parameters.PaymentsCount; i++) { for (int j = 0; j < _Hashes[i].Length; j++) { _Hashes[i][j].Index = j; } } var fakeIndices = _Hashes.First().OfType <FakeHash>().Select(h => h.Index).ToArray(); uint256 indexSalt = null; var request = new SignaturesRequest { // This looks cool, but double check the use of Select in debugging. Hashes = _Hashes.Select(h => (h.Select(k => k.GetHash()).ToArray())).ToArray(), FakeIndexesHash = PromiseUtils.HashIndexes(ref indexSalt, fakeIndices), }; InternalState.IndexSalt = indexSalt; InternalState.Cashout = cashout.Clone(); InternalState.Status = PromiseClientStates.WaitingCommitments; InternalState.FakeColumns = fakeIndices; return(request); }
/// <summary> /// Sends transaction inventory to attached peer. /// This is executed on a 5 second loop when MempoolSignaled is constructed. /// </summary> public async Task SendTrickleAsync() { INetworkPeer peer = this.AttachedPeer; if (peer == null) { this.logger.LogTrace("(-)[NO_PEER]"); return; } var transactionsToSend = new List <uint256>(); lock (this.lockObject) { if (!this.inventoryTxToSend.Any()) { this.logger.LogTrace("(-)[NO_TXS]"); return; } this.logger.LogDebug("Creating list of transaction inventory to send."); // Determine transactions to relay // Produce a vector with all candidates for sending List <uint256> invs = this.inventoryTxToSend.Take(InventoryBroadcastMax).ToList(); foreach (uint256 hash in invs) { // Remove it from the to-be-sent set this.inventoryTxToSend.Remove(hash); this.logger.LogDebug("Transaction ID '{0}' removed from pending sends list.", hash); // Check if not in the filter already if (this.filterInventoryKnown.Contains(hash)) { this.logger.LogDebug("Transaction ID '{0}' not added to inventory list, exists in known inventory filter.", hash); continue; } transactionsToSend.Add(hash); this.logger.LogDebug("Transaction ID '{0}' added to inventory list.", hash); } this.logger.LogDebug("Transaction inventory list created."); } FeeRate filterrate = new FeeRate(this.MinFeeFilter); List <uint256> findInMempool = transactionsToSend.ToList(); foreach (uint256 hash in findInMempool) { // Not in the mempool anymore? don't bother sending it. TxMempoolInfo txInfo = await this.mempoolManager.InfoAsync(hash); if (txInfo == null) { this.logger.LogDebug("Transaction ID '{0}' not added to inventory list, no longer in mempool.", hash); transactionsToSend.Remove(hash); } else if (txInfo.Fee < filterrate.GetFee((int)txInfo.Size)) { // Peer told you to not send transactions at that feerate? Don't bother sending it. this.logger.LogDebug("Transaction ID '{0}' not added to inventory list, bellow peers fee filter.", hash); transactionsToSend.Remove(hash); } } if (transactionsToSend.Any()) { this.logger.LogDebug("Sending transaction inventory to peer '{0}'.", peer.RemoteSocketEndpoint); try { await this.SendAsTxInventoryAsync(peer, transactionsToSend).ConfigureAwait(false); } catch (OperationCanceledException) { this.logger.LogTrace("(-)[CANCELED_EXCEPTION]"); return; } } }
public ServerCommitmentsProof CheckRevelation(ClientRevelation revelation, Script cashoutDestination, FeeRate feeRate) { /* * Steps 7, 9 * Almost ready, just need to figure out: * - The CashOutFormat for the validation of RealSet. * - How to get the cashoutDestination and the feeRate, * for now I pass them in like in "CreateSignatureRequest" * from ClientSession. */ if (revelation == null) { throw new ArgumentNullException(nameof(revelation)); } var saltCount = revelation.Salts.Select(a => a.Length).Sum(); if (saltCount != Parameters.GetTotalFakeTransactionsCount() || revelation.FakeIndexes.Length != Parameters.FakeTransactionCountPerLevel) { throw new ArgumentNullException($"The revelation should contains {Parameters.GetTotalFakeTransactionsCount()} salts and {Parameters.FakeTransactionCountPerLevel} indices"); } var variationCount = revelation.FeeVariations.Select(a => a.Length).Sum(); if (variationCount != Parameters.GetTotalRealTransactionsCount()) { throw new ArgumentNullException($"The revelation should contains {Parameters.GetTotalRealTransactionsCount()} fee variations"); } AssertState(PromiseServerStates.WaitingRevelation); var indexSalt = revelation.IndexesSalt; if (InternalState.FakeIndexesHash != PromiseUtils.HashIndexes(ref indexSalt, revelation.FakeIndexes)) { throw new PuzzleException("Invalid index salt"); } Transaction cashout = new Transaction(); // TODO: Figure out the cashout format for j Bitcoins cashout.AddInput(new TxIn(InternalState.EscrowedCoin.Outpoint)); cashout.Inputs[0].ScriptSig = new Script( Op.GetPushOp(TrustedBroadcastRequest.PlaceholderSignature), Op.GetPushOp(TrustedBroadcastRequest.PlaceholderSignature), Op.GetPushOp(InternalState.EscrowedCoin.Redeem.ToBytes()) ); cashout.AddOutput(new TxOut(InternalState.EscrowedCoin.Amount, cashoutDestination)); cashout.Outputs[0].Value -= feeRate.GetFee(cashout.GetVirtualSize()); var solutions = new PuzzleSolution[Parameters.PaymentsCount][]; var RealIndexes = Enumerable.Range(0, Parameters.GetTotalTransactionsCountPerLevel()).Where(a => !revelation.FakeIndexes.Contains(a)).ToArray(); for (int i = 0; i < solutions.Length; i++) { // Checking valid Transactions for (int j = 0; j < Parameters.RealTransactionCountPerLevel; j++) { var feeVariation = revelation.FeeVariations[i][j]; var encrypted = InternalState.EncryptedSignatures[i][RealIndexes[j]]; // Check if this function approved! var actualSignedHash = Parameters.CreateRealHash(cashout, InternalState.EscrowedCoin, feeVariation); if (actualSignedHash != encrypted.SignedHash) { throw new PuzzleException("Incorrect feeVariation provided"); } } // Checking Fake Transactions solutions[i] = new PuzzleSolution[Parameters.FakeTransactionCountPerLevel]; // Initialization for (int j = 0; j < solutions[i].Length; j++) { var salt = revelation.Salts[i][j]; var encrypted = InternalState.EncryptedSignatures[i][revelation.FakeIndexes[j]]; var actualSignedHash = Parameters.CreateFakeHash(salt); if (actualSignedHash != encrypted.SignedHash) { throw new PuzzleException("Incorrect salt provided"); } solutions[i][j] = encrypted.PuzzleSolution; } } // We can throw away the fake puzzles InternalState.EncryptedSignatures = InternalState.EncryptedSignatures.Select(a => a.Where((e, i) => !revelation.FakeIndexes.Contains(i)).ToArray()).ToArray(); // Step 9 var quotients = new Quotient[Parameters.PaymentsCount][]; for (int i = 0; i < quotients.Length; i++) { quotients[i] = new Quotient[Parameters.RealTransactionCountPerLevel - 1]; for (int j = 0; j < quotients[i].Length; j++) { var a = InternalState.EncryptedSignatures[i][j].PuzzleSolution._Value; var b = InternalState.EncryptedSignatures[i][j + 1].PuzzleSolution._Value; quotients[i][j] = new Quotient(b.Multiply(a.ModInverse(Parameters.ServerKey._Key.Modulus)).Mod(Parameters.ServerKey._Key.Modulus)); } } InternalState.FakeIndexesHash = null; InternalState.Status = PromiseServerStates.Completed; return(new ServerCommitmentsProof(solutions.ToArray(), quotients)); }
// This interface implementation method is only used by the Tumbler server public async Task <Transaction> ReceiveAsync(ScriptCoin escrowedCoin, TransactionSignature clientSignature, Key escrowKey, FeeRate feeRate) { var input = new ClientEscapeData() { ClientSignature = clientSignature, EscrowedCoin = escrowedCoin, EscrowKey = escrowKey }; var cashout = await GenerateAddressAsync(); var tx = new Transaction(); // Note placeholders - this step is performed again further on var txin = new TxIn(input.EscrowedCoin.Outpoint); txin.ScriptSig = new Script( Op.GetPushOp(TrustedBroadcastRequest.PlaceholderSignature), Op.GetPushOp(TrustedBroadcastRequest.PlaceholderSignature), Op.GetPushOp(input.EscrowedCoin.Redeem.ToBytes()) ); txin.Witnessify(); tx.AddInput(txin); tx.Outputs.Add(new TxOut() { ScriptPubKey = cashout.ScriptPubKey, Value = input.EscrowedCoin.Amount }); ScriptCoin[] coinArray = { input.EscrowedCoin }; var currentFee = tx.GetFee(coinArray); tx.Outputs[0].Value -= feeRate.GetFee(tx) - currentFee; var txin2 = tx.Inputs[0]; var signature = tx.SignInput(input.EscrowKey, input.EscrowedCoin); txin2.ScriptSig = new Script( Op.GetPushOp(input.ClientSignature.ToBytes()), Op.GetPushOp(signature.ToBytes()), Op.GetPushOp(input.EscrowedCoin.Redeem.ToBytes()) ); txin2.Witnessify(); //LogDebug("Trying to broadcast transaction: " + tx.GetHash()); await this.TumblingState.BroadcasterManager.BroadcastTransactionAsync(tx).ConfigureAwait(false); var bcResult = TumblingState.BroadcasterManager.GetTransaction(tx.GetHash()).State; switch (bcResult) { case Stratis.Bitcoin.Broadcasting.State.Broadcasted: case Stratis.Bitcoin.Broadcasting.State.Propagated: //LogDebug("Broadcasted transaction: " + tx.GetHash()); break; case Stratis.Bitcoin.Broadcasting.State.ToBroadcast: // Wait for propagation var waited = TimeSpan.Zero; var period = TimeSpan.FromSeconds(1); while (TimeSpan.FromSeconds(21) > waited) { // Check BroadcasterManager for broadcast success var transactionEntry = this.TumblingState.BroadcasterManager.GetTransaction(tx.GetHash()); if (transactionEntry != null && transactionEntry.State == Stratis.Bitcoin.Broadcasting.State.Propagated) { //LogDebug("Propagated transaction: " + tx.GetHash()); } await Task.Delay(period).ConfigureAwait(false); waited += period; } break; case Stratis.Bitcoin.Broadcasting.State.CantBroadcast: // Do nothing break; } //LogDebug("Uncertain if transaction was propagated: " + tx.GetHash()); return(tx); }
// Methods for how to add transactions to a block. // Add transactions based on feerate including unconfirmed ancestors // Increments nPackagesSelected / nDescendantsUpdated with corresponding // statistics from the package selection (for logging statistics). // This transaction selection algorithm orders the mempool based // on feerate of a transaction including all unconfirmed ancestors. // Since we don't remove transactions from the mempool as we select them // for block inclusion, we need an alternate method of updating the feerate // of a transaction with its not-yet-selected ancestors as we go. // This is accomplished by walking the in-mempool descendants of selected // transactions and storing a temporary modified state in mapModifiedTxs. // Each time through the loop, we compare the best transaction in // mapModifiedTxs with the next transaction in the mempool to decide what // transaction package to work on next. protected virtual void AddTransactions(int nPackagesSelected, int nDescendantsUpdated) { // mapModifiedTx will store sorted packages after they are modified // because some of their txs are already in the block var mapModifiedTx = new Dictionary <uint256, TxMemPoolModifiedEntry>(); //var mapModifiedTxRes = this.mempoolScheduler.ReadAsync(() => mempool.MapTx.Values).GetAwaiter().GetResult(); // mapModifiedTxRes.Select(s => new TxMemPoolModifiedEntry(s)).OrderBy(o => o, new CompareModifiedEntry()); // Keep track of entries that failed inclusion, to avoid duplicate work TxMempool.SetEntries failedTx = new TxMempool.SetEntries(); // Start by adding all descendants of previously added txs to mapModifiedTx // and modifying them for their already included ancestors UpdatePackagesForAdded(inBlock, mapModifiedTx); var ancestorScoreList = this.mempoolScheduler.ReadAsync(() => mempool.MapTx.AncestorScore).GetAwaiter().GetResult().ToList(); TxMempoolEntry iter; // Limit the number of attempts to add transactions to the block when it is // close to full; this is just a simple heuristic to finish quickly if the // mempool has a lot of entries. int MAX_CONSECUTIVE_FAILURES = 1000; int nConsecutiveFailed = 0; while (ancestorScoreList.Any() || mapModifiedTx.Any()) { var mi = ancestorScoreList.FirstOrDefault(); if (mi != null) { // Skip entries in mapTx that are already in a block or are present // in mapModifiedTx (which implies that the mapTx ancestor state is // stale due to ancestor inclusion in the block) // Also skip transactions that we've already failed to add. This can happen if // we consider a transaction in mapModifiedTx and it fails: we can then // potentially consider it again while walking mapTx. It's currently // guaranteed to fail again, but as a belt-and-suspenders check we put it in // failedTx and avoid re-evaluation, since the re-evaluation would be using // cached size/sigops/fee values that are not actually correct. // First try to find a new transaction in mapTx to evaluate. if (mapModifiedTx.ContainsKey(mi.TransactionHash) || inBlock.Contains(mi) || failedTx.Contains(mi)) { ancestorScoreList.Remove(mi); continue; } } // Now that mi is not stale, determine which transaction to evaluate: // the next entry from mapTx, or the best from mapModifiedTx? bool fUsingModified = false; TxMemPoolModifiedEntry modit; var compare = new CompareModifiedEntry(); if (mi == null) { modit = mapModifiedTx.Values.OrderByDescending(o => o, compare).First(); iter = modit.iter; fUsingModified = true; } else { // Try to compare the mapTx entry to the mapModifiedTx entry iter = mi; modit = mapModifiedTx.Values.OrderByDescending(o => o, compare).FirstOrDefault(); if (modit != null && compare.Compare(modit, new TxMemPoolModifiedEntry(iter)) > 0) { // The best entry in mapModifiedTx has higher score // than the one from mapTx. // Switch which transaction (package) to consider iter = modit.iter; fUsingModified = true; } else { // Either no entry in mapModifiedTx, or it's worse than mapTx. // Increment mi for the next loop iteration. ancestorScoreList.Remove(iter); } } // We skip mapTx entries that are inBlock, and mapModifiedTx shouldn't // contain anything that is inBlock. Guard.Assert(!inBlock.Contains(iter)); var packageSize = iter.SizeWithAncestors; var packageFees = iter.ModFeesWithAncestors; var packageSigOpsCost = iter.SizeWithAncestors; if (fUsingModified) { packageSize = modit.SizeWithAncestors; packageFees = modit.ModFeesWithAncestors; packageSigOpsCost = modit.SigOpCostWithAncestors; } if (packageFees < blockMinFeeRate.GetFee((int)packageSize)) { // Everything else we might consider has a lower fee rate return; } if (!TestPackage(packageSize, packageSigOpsCost)) { if (fUsingModified) { // Since we always look at the best entry in mapModifiedTx, // we must erase failed entries so that we can consider the // next best entry on the next loop iteration mapModifiedTx.Remove(modit.iter.TransactionHash); failedTx.Add(iter); } ++nConsecutiveFailed; if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && blockWeight > blockMaxWeight - 4000) { // Give up if we're close to full and haven't succeeded in a while break; } continue; } TxMempool.SetEntries ancestors = new TxMempool.SetEntries(); long nNoLimit = long.MaxValue; string dummy; mempool.CalculateMemPoolAncestors(iter, ancestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, out dummy, false); OnlyUnconfirmed(ancestors); ancestors.Add(iter); // Test if all tx's are Final if (!TestPackageTransactions(ancestors)) { if (fUsingModified) { mapModifiedTx.Remove(modit.iter.TransactionHash); failedTx.Add(iter); } continue; } // This transaction will make it in; reset the failed counter. nConsecutiveFailed = 0; // Package can be added. Sort the entries in a valid order. // Sort package by ancestor count // If a transaction A depends on transaction B, then A's ancestor count // must be greater than B's. So this is sufficient to validly order the // transactions for block inclusion. var sortedEntries = ancestors.ToList().OrderBy(o => o, new CompareTxIterByAncestorCount()).ToList(); foreach (var sortedEntry in sortedEntries) { AddToBlock(sortedEntry); // Erase from the modified set, if present mapModifiedTx.Remove(sortedEntry.TransactionHash); } ++nPackagesSelected; // Update transactions that depend on each of these nDescendantsUpdated += UpdatePackagesForAdded(ancestors, mapModifiedTx); } }
public Money CalculateRemainingAmountCredentials(FeeRate feeRate) => TotalInputAmount - feeRate.GetFee(TotalInputVsize);
public Money CalculateOutputAmount(FeeRate feeRate) => CredentialAmount - feeRate.GetFee(OutputVsize);
#pragma warning disable CS0618 private static Money GetTxFee(StoreBlob storeBlob, FeeRate feeRate) { return(storeBlob.NetworkFeeDisabled ? Money.Zero : feeRate.GetFee(100)); }
public async Task MinerTestPackageSelectionAsync() { var context = new TestContext(); await context.InitializeAsync(); // Test the ancestor feerate transaction selection. var entry = new TestMemPoolEntryHelper(); // Test that a medium fee transaction will be selected after a higher fee // rate package with a low fee rate parent. var tx = context.network.CreateTransaction(); tx.AddInput(new TxIn(new OutPoint(context.txFirst[0].GetHash(), 0), new Script(OpcodeType.OP_1))); tx.AddOutput(new TxOut(new Money(5000000000L - 1000), new Script())); // This tx has a low fee: 1000 satoshis uint256 hashParentTx = tx.GetHash(); // save this txid for later use context.mempool.AddUnchecked(hashParentTx, entry.Fee(1000).Time(context.DateTimeProvider.GetTime()).SpendsCoinbase(true).FromTx(tx)); // This tx has a medium fee: 10000 satoshis tx = context.network.CreateTransaction(tx.ToBytes()); tx.Inputs[0].PrevOut.Hash = context.txFirst[1].GetHash(); tx.Outputs[0].Value = 5000000000L - 10000; uint256 hashMediumFeeTx = tx.GetHash(); context.mempool.AddUnchecked(hashMediumFeeTx, entry.Fee(10000).Time(context.DateTimeProvider.GetTime()).SpendsCoinbase(true).FromTx(tx)); // This tx has a high fee, but depends on the first transaction tx = context.network.CreateTransaction(tx.ToBytes()); tx.Inputs[0].PrevOut.Hash = hashParentTx; tx.Outputs[0].Value = 5000000000L - 1000 - 50000; // 50k satoshi fee uint256 hashHighFeeTx = tx.GetHash(); context.mempool.AddUnchecked(hashHighFeeTx, entry.Fee(50000).Time(context.DateTimeProvider.GetTime()).SpendsCoinbase(false).FromTx(tx)); BlockTemplate pblocktemplate = AssemblerForTest(context).Build(context.ChainIndexer.Tip, context.scriptPubKey); Assert.True(pblocktemplate.Block.Transactions[1].GetHash() == hashParentTx); Assert.True(pblocktemplate.Block.Transactions[2].GetHash() == hashHighFeeTx); Assert.True(pblocktemplate.Block.Transactions[3].GetHash() == hashMediumFeeTx); // Test that a package below the block min tx fee doesn't get included tx = context.network.CreateTransaction(tx.ToBytes()); tx.Inputs[0].PrevOut.Hash = hashHighFeeTx; tx.Outputs[0].Value = 5000000000L - 1000 - 50000; // 0 fee uint256 hashFreeTx = tx.GetHash(); context.mempool.AddUnchecked(hashFreeTx, entry.Fee(0).FromTx(tx)); int freeTxSize = tx.GetSerializedSize(); // Calculate a fee on child transaction that will put the package just // below the block min tx fee (assuming 1 child tx of the same size). Money feeToUse = blockMinFeeRate.GetFee(2 * freeTxSize) - 1; tx = context.network.CreateTransaction(tx.ToBytes()); tx.Inputs[0].PrevOut.Hash = hashFreeTx; tx.Outputs[0].Value = 5000000000L - 1000 - 50000 - feeToUse; uint256 hashLowFeeTx = tx.GetHash(); context.mempool.AddUnchecked(hashLowFeeTx, entry.Fee(feeToUse).FromTx(tx)); pblocktemplate = AssemblerForTest(context).Build(context.ChainIndexer.Tip, context.scriptPubKey); // Verify that the free tx and the low fee tx didn't get selected for (int i = 0; i < pblocktemplate.Block.Transactions.Count; ++i) { Assert.True(pblocktemplate.Block.Transactions[i].GetHash() != hashFreeTx); Assert.True(pblocktemplate.Block.Transactions[i].GetHash() != hashLowFeeTx); } // Test that packages above the min relay fee do get included, even if one // of the transactions is below the min relay fee // Remove the low fee transaction and replace with a higher fee transaction context.mempool.RemoveRecursive(tx); tx = context.network.CreateTransaction(tx.ToBytes()); tx.Outputs[0].Value -= 2; // Now we should be just over the min relay fee hashLowFeeTx = tx.GetHash(); context.mempool.AddUnchecked(hashLowFeeTx, entry.Fee(feeToUse + 2).FromTx(tx)); pblocktemplate = AssemblerForTest(context).Build(context.ChainIndexer.Tip, context.scriptPubKey); Assert.True(pblocktemplate.Block.Transactions[4].GetHash() == hashFreeTx); Assert.True(pblocktemplate.Block.Transactions[5].GetHash() == hashLowFeeTx); // Test that transaction selection properly updates ancestor fee // calculations as ancestor transactions get included in a block. // Add a 0-fee transaction that has 2 outputs. tx = context.network.CreateTransaction(tx.ToBytes()); tx.Inputs[0].PrevOut.Hash = context.txFirst[2].GetHash(); tx.AddOutput(Money.Zero, new Script()); tx.Outputs[0].Value = 5000000000L - 100000000; tx.Outputs[1].Value = 100000000; // 1BTC output uint256 hashFreeTx2 = tx.GetHash(); context.mempool.AddUnchecked(hashFreeTx2, entry.Fee(0).SpendsCoinbase(true).FromTx(tx)); // This tx can't be mined by itself tx = context.network.CreateTransaction(tx.ToBytes()); tx.Inputs[0].PrevOut.Hash = hashFreeTx2; tx.Outputs.RemoveAt(1); feeToUse = blockMinFeeRate.GetFee(freeTxSize); tx.Outputs[0].Value = 5000000000L - 100000000 - feeToUse; uint256 hashLowFeeTx2 = tx.GetHash(); context.mempool.AddUnchecked(hashLowFeeTx2, entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx)); pblocktemplate = AssemblerForTest(context).Build(context.ChainIndexer.Tip, context.scriptPubKey); // Verify that this tx isn't selected. for (int i = 0; i < pblocktemplate.Block.Transactions.Count; ++i) { Assert.True(pblocktemplate.Block.Transactions[i].GetHash() != hashFreeTx2); Assert.True(pblocktemplate.Block.Transactions[i].GetHash() != hashLowFeeTx2); } // This tx will be mineable, and should cause hashLowFeeTx2 to be selected // as well. tx = context.network.CreateTransaction(tx.ToBytes()); tx.Inputs[0].PrevOut.N = 1; tx.Outputs[0].Value = 100000000 - 10000; // 10k satoshi fee context.mempool.AddUnchecked(tx.GetHash(), entry.Fee(10000).FromTx(tx)); pblocktemplate = AssemblerForTest(context).Build(context.ChainIndexer.Tip, context.scriptPubKey); Assert.True(pblocktemplate.Block.Transactions[8].GetHash() == hashLowFeeTx2); }
public FundingPSBT Build(Network network) { var offererCoins = Offerer.FundingInputs.Select(f => f.AsCoin()).ToArray(); var acceptorCoins = Acceptor.FundingInputs.Select(f => f.AsCoin()).ToArray(); var fundingScript = GetFundingScript(); var p2wsh = fundingScript.WitHash.ScriptPubKey; Transaction?tx = null; if (transactionOverride is null) { tx = network.CreateTransaction(); tx.Version = 2; tx.LockTime = 0; foreach (var input in Offerer.FundingInputs.Concat(Acceptor.FundingInputs)) { var txin = tx.Inputs.Add(input.AsCoin().Outpoint, Script.Empty); if (input.RedeemScript is Script) { txin.ScriptSig = new Script(Op.GetPushOp(input.RedeemScript.ToBytes())); } } foreach (var input in tx.Inputs) { input.Sequence = 0xffffffff; } tx.Outputs.Add(Offerer.Collateral + Acceptor.Collateral, p2wsh); var totalInput = offererCoins.Select(s => s.Amount).Sum(); if (Offerer.Change is Script change) { tx.Outputs.Add(totalInput - Offerer.Collateral, change); } totalInput = acceptorCoins.Select(s => s.Amount).Sum(); if (Acceptor.Change is Script change2) { tx.Outputs.Add(totalInput - Acceptor.Collateral, change2); } tx.Outputs[1].Value -= FeeRate.GetFee(Offerer.VSizes.Funding); tx.Outputs[2].Value -= FeeRate.GetFee(Acceptor.VSizes.Funding); var offererFee = FeeRate.GetFee(Offerer.VSizes.CET); var acceptorFee = FeeRate.GetFee(Acceptor.VSizes.CET); tx.Outputs[1].Value -= offererFee; tx.Outputs[2].Value -= acceptorFee; tx.Outputs[0].Value += offererFee + acceptorFee; } else { tx = transactionOverride; } var psbt = PSBT.FromTransaction(tx, network); foreach (var input in Offerer.FundingInputs.Concat(Acceptor.FundingInputs)) { var txin = psbt.Inputs.FindIndexedInput(input.AsCoin().Outpoint); txin.RedeemScript = input.RedeemScript; txin.NonWitnessUtxo = input.InputTransaction; if (txin.NonWitnessUtxo is null) { txin.WitnessUtxo = input.Output; if (txin.WitnessUtxo is null) { throw new InvalidOperationException("Input funding not having witness Utxo nor NonWitnessUtxo"); } } } return(new FundingPSBT(psbt, new ScriptCoin(tx, 0, fundingScript))); }