Esempio n. 1
0
        public bool CreateCoinStake(ChainedBlock pindexBest, uint bits, long nSearchInterval, long fees, ref Transaction txNew,
                                    ref Key key)
        {
            var pindexPrev         = pindexBest;
            var bnTargetPerCoinDay = new Target(bits).ToCompact();

            txNew.Inputs.Clear();
            txNew.Outputs.Clear();

            // Mark coin stake transaction
            txNew.Outputs.Add(new TxOut(Money.Zero, new Script()));

            // Choose coins to use
            var nBalance = this.GetBalance().Satoshi;

            if (nBalance <= this.reserveBalance)
            {
                return(false);
            }

            List <WalletTx> vwtxPrev = new List <WalletTx>();

            List <WalletTx> setCoins;
            long            nValueIn = 0;

            // Select coins with suitable depth
            if (!SelectCoinsForStaking(nBalance - this.reserveBalance, txNew.Time, out setCoins, out nValueIn))
            {
                return(false);
            }

            // check if coins are already staking
            // this is different from the c++ implementation
            // which pushes the new block to the main chain
            // and removes it when a longer chain is found
            foreach (var walletTx in setCoins.ToList())
            {
                if (this.minerService.IsStaking(walletTx.Transactionid, walletTx.OutputIndex))
                {
                    setCoins.Remove(walletTx);
                }
            }

            if (setCoins.Empty())
            {
                return(false);
            }

            long   nCredit            = 0;
            Script scriptPubKeyKernel = null;

            foreach (var coin in setCoins)
            {
                int  maxStakeSearchInterval = 60;
                bool fKernelFound           = false;

                for (uint n = 0;
                     n < Math.Min(nSearchInterval, maxStakeSearchInterval) && !fKernelFound && pindexPrev == this.chainIndex.Tip;
                     n++)
                {
                    var  prevoutStake = new OutPoint(coin.Transactionid, coin.OutputIndex);
                    long nBlockTime   = 0;

                    if (BlockValidator.CheckKernel(this.chainIndex, this.chainIndex, this.chainIndex, pindexPrev, bits,
                                                   txNew.Time - n, prevoutStake, ref nBlockTime))
                    {
                        scriptPubKeyKernel = coin.TxOut.ScriptPubKey;

                        key = null;
                        // calculate the key type
                        if (PayToPubkeyTemplate.Instance.CheckScriptPubKey(scriptPubKeyKernel))
                        {
                            var outPubKey = scriptPubKeyKernel.GetDestinationAddress(this.Context.Network);
                            key = this.walletStore.GetKey(outPubKey);
                        }
                        else if (PayToPubkeyHashTemplate.Instance.CheckScriptPubKey(scriptPubKeyKernel))
                        {
                            var outPubKey = scriptPubKeyKernel.GetDestinationAddress(this.Context.Network);
                            key = this.walletStore.GetKey(outPubKey);
                        }
                        else
                        {
                            //LogPrint("coinstake", "CreateCoinStake : no support for kernel type=%d\n", whichType);
                            break;                             // only support pay to public key and pay to address
                        }

                        // create a pubkey script form the current script
                        var scriptPubKeyOut = PayToPubkeyTemplate.Instance.GenerateScriptPubKey(key.PubKey);                         //scriptPubKeyKernel;

                        txNew.Time -= n;
                        txNew.AddInput(new TxIn(prevoutStake));
                        nCredit += coin.TxOut.Value;
                        vwtxPrev.Add(coin);
                        txNew.Outputs.Add(new TxOut(0, scriptPubKeyOut));

                        //LogPrint("coinstake", "CreateCoinStake : added kernel type=%d\n", whichType);
                        fKernelFound = true;
                        break;
                    }
                }

                if (fKernelFound)
                {
                    break;                     // if kernel is found stop searching
                }
            }

            if (nCredit == 0 || nCredit > nBalance - this.reserveBalance)
            {
                return(false);
            }

            foreach (var coin in setCoins)
            {
                var cointrx = coin;
                //var coinIndex = coin.Value;

                // Attempt to add more inputs
                // Only add coins of the same key/address as kernel
                if (txNew.Outputs.Count == 2 &&
                    (
                        cointrx.TxOut.ScriptPubKey == scriptPubKeyKernel ||
                        cointrx.TxOut.ScriptPubKey == txNew.Outputs[1].ScriptPubKey
                    ) &&
                    cointrx.Transactionid != txNew.Inputs[0].PrevOut.Hash)
                {
                    long nTimeWeight = BlockValidator.GetWeight((long)cointrx.Transaction.Time, (long)txNew.Time);

                    // Stop adding more inputs if already too many inputs
                    if (txNew.Inputs.Count >= 100)
                    {
                        break;
                    }
                    // Stop adding inputs if reached reserve limit
                    if (nCredit + cointrx.TxOut.Value > nBalance - this.reserveBalance)
                    {
                        break;
                    }
                    // Do not add additional significant input
                    if (cointrx.TxOut.Value >= GetStakeCombineThreshold())
                    {
                        continue;
                    }
                    // Do not add input that is still too young
                    if (BlockValidator.IsProtocolV3((int)txNew.Time))
                    {
                        // properly handled by selection function
                    }
                    else
                    {
                        if (nTimeWeight < BlockValidator.StakeMinAge)
                        {
                            continue;
                        }
                    }

                    txNew.Inputs.Add(new TxIn(new OutPoint(cointrx.Transactionid, cointrx.OutputIndex)));

                    nCredit += cointrx.TxOut.Value;
                    vwtxPrev.Add(coin);
                }
            }

            // Calculate coin age reward
            ulong nCoinAge;

            if (!BlockValidator.GetCoinAge(this.chainIndex, this.chainIndex, this.chainIndex, txNew, pindexPrev, out nCoinAge))
            {
                return(false);                //error("CreateCoinStake : failed to calculate coin age");
            }
            long nReward = BlockValidator.GetProofOfStakeReward(pindexPrev, nCoinAge, fees);

            if (nReward <= 0)
            {
                return(false);
            }

            nCredit += nReward;


            if (nCredit >= GetStakeSplitThreshold())
            {
                txNew.Outputs.Add(new TxOut(0, txNew.Outputs[1].ScriptPubKey));                 //split stake
            }
            // Set output amount
            if (txNew.Outputs.Count == 3)
            {
                txNew.Outputs[1].Value = (nCredit / 2 / BlockValidator.CENT) * BlockValidator.CENT;
                txNew.Outputs[2].Value = nCredit - txNew.Outputs[1].Value;
            }
            else
            {
                txNew.Outputs[1].Value = nCredit;
            }

            // Sign
            int nIn = 0;

            foreach (var walletTx in vwtxPrev)
            {
                if (!SignSignature(new[] { key }, walletTx.Transaction, txNew, nIn++))
                {
                    return(false);                    // error("CreateCoinStake : failed to sign coinstake");
                }
            }

            // Limit size
            int nBytes = txNew.GetSerializedSize(ProtocolVersion.PROTOCOL_VERSION, SerializationType.Network);

            if (nBytes >= MAX_BLOCK_SIZE_GEN / 5)
            {
                return(false);                // error("CreateCoinStake : exceeded coinstake size limit");
            }
            // Successfully generated coinstake
            return(true);
        }