public bool CreateCoinStake(List <StakeTx> stakeTxes, ChainedBlock pindexBest, Block block, long nSearchInterval, long fees, ref Transaction txNew, ref Key key) { var pindexPrev = pindexBest; var bnTargetPerCoinDay = new Target(block.Header.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(stakeTxes).Satoshi; if (nBalance <= this.reserveBalance) { return(false); } List <StakeTx> vwtxPrev = new List <StakeTx>(); List <StakeTx> setCoins; long nValueIn = 0; // Select coins with suitable depth if (!SelectCoinsForStaking(stakeTxes, 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.TransactionHash, walletTx.OutputIndex)) // setCoins.Remove(walletTx); if (!setCoins.Any()) { return(false); } long nCredit = 0; Script scriptPubKeyKernel = null; // Note: I would expect to see coins sorted by weight on the original implementation // sort the coins from heighest weight setCoins = setCoins.OrderByDescending(o => o.TxOut.Value).ToList(); foreach (var coin in setCoins) { int maxStakeSearchInterval = 60; bool fKernelFound = false; for (uint n = 0; n < Math.Min(nSearchInterval, maxStakeSearchInterval) && !fKernelFound && pindexPrev == this.chain.Tip; n++) { try { var prevoutStake = new OutPoint(coin.UtxoSet.TransactionId, coin.OutputIndex); long nBlockTime = 0; var context = new ContextInformation(new BlockResult { Block = block }, network.Consensus); context.SetStake(); this.posConsensusValidator.StakeValidator.CheckKernel(context, pindexPrev, block.Header.Bits, txNew.Time - n, prevoutStake, ref nBlockTime); var timemaskceck = txNew.Time - n; if ((timemaskceck & PosConsensusValidator.STAKE_TIMESTAMP_MASK) != 0) { continue; } if (context.Stake.HashProofOfStake != null) { scriptPubKeyKernel = coin.TxOut.ScriptPubKey; key = null; // calculate the key type if (PayToPubkeyTemplate.Instance.CheckScriptPubKey(scriptPubKeyKernel)) { var outPubKey = scriptPubKeyKernel.GetDestinationAddress(this.network); key = coin.PrvKey; } else if (PayToPubkeyHashTemplate.Instance.CheckScriptPubKey(scriptPubKeyKernel)) { var outPubKey = scriptPubKeyKernel.GetDestinationAddress(this.network); key = coin.PrvKey; } 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; } } catch (ConsensusErrorException cex) { if (cex.ConsensusError != ConsensusErrors.StakeHashInvalidTarget) { throw; } } } 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.UtxoSet.TransactionId != txNew.Inputs[0].PrevOut.Hash) { long nTimeWeight = BlockValidator.GetWeight((long)cointrx.UtxoSet.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.UtxoSet.TransactionId, cointrx.OutputIndex))); nCredit += cointrx.TxOut.Value; vwtxPrev.Add(coin); } } // Calculate coin age reward ulong nCoinAge; if (!this.posConsensusValidator.StakeValidator.GetCoinAge(this.chain, this.coinView, txNew, pindexPrev, out nCoinAge)) { return(false); //error("CreateCoinStake : failed to calculate coin age"); } long nReward = fees + this.posConsensusValidator.GetProofOfStakeReward(pindexPrev.Height); 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 foreach (var walletTx in vwtxPrev) { if (!SignSignature(walletTx, txNew)) { return(false); // error("CreateCoinStake : failed to sign coinstake"); } } // Limit size int nBytes = txNew.GetSerializedSize(ProtocolVersion.ALT_PROTOCOL_VERSION, SerializationType.Network); if (nBytes >= MAX_BLOCK_SIZE_GEN / 5) { return(false); // error("CreateCoinStake : exceeded coinstake size limit"); } // Successfully generated coinstake return(true); }
private void CheckState(ContextInformation context, ChainedBlock pindexPrev) { var block = context.BlockResult.Block; if (!BlockStake.IsProofOfStake(block)) { return; } // verify hash target and signature of coinstake tx var prevBlockStake = this.stakeChain.Get(pindexPrev.HashBlock); if (prevBlockStake == null) { ConsensusErrors.PrevStakeNull.Throw(); } context.SetStake(); this.posConsensusValidator.StakeValidator.CheckProofOfStake(context, pindexPrev, prevBlockStake, block.Transactions[1], block.Header.Bits.ToCompact()); // Found a solution if (block.Header.HashPrevBlock != this.chain.Tip.HashBlock) { return; } // validate the block this.consensusLoop.AcceptBlock(context); if (context.BlockResult.ChainedBlock == null) { return; //reorg } if (context.BlockResult.Error != null) { return; } if (context.BlockResult.ChainedBlock.ChainWork <= this.chain.Tip.ChainWork) { return; } // similar logic to what's in the full node code this.chain.SetTip(context.BlockResult.ChainedBlock); this.consensusLoop.Puller.SetLocation(this.consensusLoop.Tip); this.chainState.HighestValidatedPoW = this.consensusLoop.Tip; this.blockRepository.PutAsync(context.BlockResult.ChainedBlock.HashBlock, new List <Block> { block }).GetAwaiter().GetResult(); this.signals.Blocks.Broadcast(block); Logs.Mining.LogInformation($"Found new POS block {context.BlockResult.ChainedBlock.HashBlock}"); // wait for peers to get the block Thread.Sleep(1000); // ask peers for thier headers foreach (var node in this.connection.ConnectedNodes) { node.Behavior <ChainBehavior>().TrySync(); } // wait for all peers to accept the block var retry = 0; foreach (var node in this.connection.ConnectedNodes) { var chainBehaviour = node.Behavior <ChainBehavior>(); while (++retry < 100 && chainBehaviour.PendingTip != this.chain.Tip) { Thread.Sleep(1000); } } if (retry == 100) { // seems the block was not accepted throw new MinerException("Block rejected by peers"); } }