Пример #1
0
        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
            });
        }
Пример #2
0
        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);
Пример #5
0
        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);
        }
Пример #8
0
        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);
        }
Пример #9
0
        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));
        }
Пример #10
0
    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));
    }
Пример #11
0
        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);
        }
Пример #12
0
        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);
        }
Пример #13
0
        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);
        }
Пример #14
0
        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);
        }
Пример #15
0
        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);
        }
Пример #16
0
        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
            });
        }
Пример #17
0
        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);
        }
Пример #18
0
        /// <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;
                }
            }
        }
Пример #19
0
        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));
        }
Пример #20
0
        // 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);
        }
Пример #21
0
        // 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);
            }
        }
Пример #22
0
 public Money CalculateRemainingAmountCredentials(FeeRate feeRate) => TotalInputAmount - feeRate.GetFee(TotalInputVsize);
Пример #23
0
 public Money CalculateOutputAmount(FeeRate feeRate)
 => CredentialAmount - feeRate.GetFee(OutputVsize);
Пример #24
0
#pragma warning disable CS0618
        private static Money GetTxFee(StoreBlob storeBlob, FeeRate feeRate)
        {
            return(storeBlob.NetworkFeeDisabled ? Money.Zero : feeRate.GetFee(100));
        }
Пример #25
0
        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);
        }
Пример #26
0
        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)));
        }