CreateCoinStake方法Bug排查:隐身地址私钥正常但奖励转公钥地址
CreateCoinStake Let's break down the issues in your code that are causing stake rewards to go to the public key address instead of your stealth address, even when the private key appears valid:
Key Bugs Identified
1. Incorrect Type Check Logic
The condition checking for supported kernel types is logically flawed:
if (whichType != TX_PUBKEY && whichType != TX_PUBKEYHASH || whichType != TX_SCRIPTHASH)
This will always evaluate to true (because even if whichType is TX_SCRIPTHASH, the first part whichType != TX_PUBKEY && whichType != TX_PUBKEYHASH will be true, making the whole OR condition true). So your code is incorrectly breaking execution for valid stealth address types.
2. Broken Stealth Address Handling
The TX_SCRIPTHASH (stealth address) branch has multiple critical issues:
- You're immediately returning
truebefore setting up the output script (scriptPubKeyOut) for the stealth address. This skips all the logic needed to associate the reward with your stealth address. - The iterator logic for accessing
stealthAddressesis wrong:stealthAddresses.begin()->Encoded()always uses the first stealth address instead of matching the one associated with the kernel script. - You're not actually deriving the correct output script for the stealth address, so even if you didn't return early, the reward wouldn't be directed properly.
3. Missing Stealth Address Output Setup
Even if you fixed the return statement, there's no code to create the correct scriptPubKeyOut for the stealth address, which is required to send the reward to that address.
Fixed Code Implementation
Here's the corrected version of the relevant sections, with comments explaining the changes:
bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int64_t nSearchInterval, int64_t nFees, CTransaction& txNew, CKey& key) { CBlockIndex* pindexPrev = pindexBest; CBigNum bnTargetPerCoinDay; bnTargetPerCoinDay.SetCompact(nBits); txNew.vin.clear(); txNew.vout.clear(); // Mark coin stake transaction CScript scriptEmpty; scriptEmpty.clear(); txNew.vout.push_back(CTxOut(0, scriptEmpty)); // Choose coins to use int64_t nBalance = GetBalance(); if (nBalance <= nReserveBalance) return false; vector<const CWalletTx*> vwtxPrev; set<pair<const CWalletTx*,unsigned int> > setCoins; int64_t nValueIn = 0; // Select coins with suitable depth if (!SelectCoinsSimple(nBalance - nReserveBalance, txNew.nTime, nCoinbaseMaturity + 10, setCoins, nValueIn)) return false; if (setCoins.empty()) return false; int64_t nCredit = 0; CScript scriptPubKeyKernel; CTxDB txdb("r"); BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins) { CTxIndex txindex; { LOCK2(cs_main, cs_wallet); if (!txdb.ReadTxIndex(pcoin.first->GetHash(), txindex)) continue; } // Read block header CBlock block; { LOCK2(cs_main, cs_wallet); if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false)) continue; } static int nMaxStakeSearchInterval = 60; if (block.GetBlockTime() + nStakeMinAge > txNew.nTime - nMaxStakeSearchInterval) continue; // only count coins meeting min age requirement bool fKernelFound = false; for (unsigned int n=0; n<min(nSearchInterval,(int64_t)nMaxStakeSearchInterval) && !fKernelFound && !fShutdown && pindexPrev == pindexBest; n++) { // Search backward in time from the given txNew timestamp // Search nSearchInterval seconds back up to nMaxStakeSearchInterval uint256 hashProofOfStake = 0, targetProofOfStake = 0; COutPoint prevoutStake = COutPoint(pcoin.first->GetHash(), pcoin.second); if (CheckStakeKernelHash(nBits, block, txindex.pos.nTxPos - txindex.pos.nBlockPos, *pcoin.first, prevoutStake, txNew.nTime - n, hashProofOfStake, targetProofOfStake)) { // Found a kernel if (fDebug && GetBoolArg("-printcoinstake")) printf("CreateCoinStake : kernel found\n"); vector<valtype> vSolutions; txnouttype whichType; CScript scriptPubKeyOut; scriptPubKeyKernel = pcoin.first->vout[pcoin.second].scriptPubKey; if (!Solver(scriptPubKeyKernel, whichType, vSolutions)) { if (fDebug && GetBoolArg("-printcoinstake")) printf("CreateCoinStake : failed to parse kernel\n"); break; } if (fDebug && GetBoolArg("-printcoinstake")) printf("CreateCoinStake : parsed kernel type=%d\n", whichType); // FIX: Correct type check - only proceed if we support the type if (!(whichType == TX_PUBKEY || whichType == TX_PUBKEYHASH || whichType == TX_SCRIPTHASH)) { if (fDebug && GetBoolArg("-printcoinstake")) printf("CreateCoinStake : no support for kernel type=%d\n", whichType); break; // only support pay to public key, stealth key and pay to address } if(whichType == TX_SCRIPTHASH){ // Handle pay-to-stealth address // FIX: Find the matching stealth address for the script hash bool foundStealthKey = false; uint160 scriptHash = Hash160(scriptPubKeyKernel); for (const auto& stealthAddr : stealthAddresses) { if (stealthAddr.GetHash() == scriptHash) { // FIX: Retrieve the private key for this stealth address if (!keystore.GetKey(stealthAddr.GetSpendKey(), key)) { if (fDebug && GetBoolArg("-printcoinstake")) printf("CreateCoinStake : failed to get key for stealth address\n"); break; } // FIX: Create the correct output script for the stealth address scriptPubKeyOut = stealthAddr.GetOutputScript(); foundStealthKey = true; break; } } if (!foundStealthKey) { if (fDebug && GetBoolArg("-printcoinstake")) printf("CreateCoinStake : no matching stealth address found\n"); break; } } else { // Handle non-stealth address types if (whichType == TX_PUBKEYHASH) { // pay to address type // convert to pay to public key type if (!keystore.GetKey(uint160(vSolutions[0]), key)) { if (fDebug && GetBoolArg("-printcoinstake")) printf("CreateCoinStake : failed to get key for kernel type=%d\n", whichType); break; // unable to find corresponding public key } scriptPubKeyOut << key.GetPubKey() << OP_CHECKSIG; } if (whichType == TX_PUBKEY) { valtype& vchPubKey = vSolutions[0]; if (!keystore.GetKey(Hash160(vchPubKey), key)) { if (fDebug && GetBoolArg("-printcoinstake")) printf("CreateCoinStake : failed to get key for kernel type=%d\n", whichType); break; // unable to find corresponding public key } if (key.GetPubKey() != vchPubKey) { if (fDebug && GetBoolArg("-printcoinstake")) printf("CreateCoinStake : invalid key for kernel type=%d\n", whichType); break; // keys mismatch } scriptPubKeyOut = scriptPubKeyKernel; } } txNew.nTime -= n; txNew.vin.push_back(CTxIn(pcoin.first->GetHash(), pcoin.second)); nCredit += pcoin.first->vout[pcoin.second].nValue; vwtxPrev.push_back(pcoin.first); txNew.vout.push_back(CTxOut(0, scriptPubKeyOut)); if (GetWeight(block.GetBlockTime(), (int64_t)txNew.nTime) <= bnTargetPerCoinDay * nCredit / COIN) { fKernelFound = true; } } } if (fKernelFound) break; } if (nCredit == 0) return false; // Calculate reward int64_t nReward = GetProofOfStakeReward(txNew.nTime, nCredit); txNew.vout[0].nValue = nReward - nFees; // Sign the transaction if (!SignTransaction(txNew, keystore)) { if (fDebug && GetBoolArg("-printcoinstake")) printf("CreateCoinStake : failed to sign transaction\n"); return false; } return true; }
Key Fixes Explained
- Corrected Type Check: Changed the invalid OR/AND condition to properly filter unsupported address types.
- Proper Stealth Address Matching: Iterates through
stealthAddressesto find the one that matches the kernel's script hash, instead of hardcoding the first address. - Removed Early Return: Got rid of the premature
return truein the stealth address branch, allowing the code to set up the output script and proceed with the transaction. - Valid Output Script Creation: Uses
stealthAddr.GetOutputScript()to generate the correct script for sending rewards to the stealth address. - Correct Private Key Retrieval: Fetches the spend key associated with the matched stealth address instead of using an incorrect lookup.
After applying these fixes, your stake rewards should correctly be sent to the stealth address associated with the staked coins, instead of falling back to the public key address.
内容的提问来源于stack exchange,提问作者Josh Lake

