Spent per txout
Change some internal data structures to keep track of spentness of each wallet transaction output separately, to support partially-spent transactions: * an update to the data structures (vfSpent in CWalletTx instead of fSpent) * a backward-compatible update to the wallet disk format. Old clients reading back an updated wallet will ignore partially spent transactions when creating new ones, and may report a wrong balance, though. * some helper functions (CWalletTx: IsSpent, MarkSpent, MarkDirty to reset cached values, GetAvailableCredit which only counts unredeemed outputs)
This commit is contained in:
parent
b7ebc662eb
commit
335e878be8
2 changed files with 147 additions and 43 deletions
42
main.cpp
42
main.cpp
|
@ -136,11 +136,7 @@ bool AddToWallet(const CWalletTx& wtxIn)
|
||||||
wtx.fFromMe = wtxIn.fFromMe;
|
wtx.fFromMe = wtxIn.fFromMe;
|
||||||
fUpdated = true;
|
fUpdated = true;
|
||||||
}
|
}
|
||||||
if (wtxIn.fSpent && wtxIn.fSpent != wtx.fSpent)
|
fUpdated |= wtx.UpdateSpent(wtxIn.vfSpent);
|
||||||
{
|
|
||||||
wtx.fSpent = wtxIn.fSpent;
|
|
||||||
fUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//// debug print
|
//// debug print
|
||||||
|
@ -221,10 +217,10 @@ void WalletUpdateSpent(const COutPoint& prevout)
|
||||||
if (mi != mapWallet.end())
|
if (mi != mapWallet.end())
|
||||||
{
|
{
|
||||||
CWalletTx& wtx = (*mi).second;
|
CWalletTx& wtx = (*mi).second;
|
||||||
if (!wtx.fSpent && wtx.vout[prevout.n].IsMine())
|
if (!wtx.IsSpent(prevout.n) && wtx.vout[prevout.n].IsMine())
|
||||||
{
|
{
|
||||||
printf("WalletUpdateSpent found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str());
|
printf("WalletUpdateSpent found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str());
|
||||||
wtx.fSpent = true;
|
wtx.MarkSpent(prevout.n);
|
||||||
wtx.WriteToDisk();
|
wtx.WriteToDisk();
|
||||||
vWalletUpdated.push_back(prevout.hash);
|
vWalletUpdated.push_back(prevout.hash);
|
||||||
}
|
}
|
||||||
|
@ -939,15 +935,14 @@ void ReacceptWalletTransactions()
|
||||||
foreach(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
|
foreach(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
|
||||||
{
|
{
|
||||||
CWalletTx& wtx = item.second;
|
CWalletTx& wtx = item.second;
|
||||||
if (wtx.fSpent && wtx.IsCoinBase())
|
if (wtx.IsCoinBase() && wtx.IsSpent(0))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
CTxIndex txindex;
|
CTxIndex txindex;
|
||||||
|
bool fUpdated = false;
|
||||||
if (txdb.ReadTxIndex(wtx.GetHash(), txindex))
|
if (txdb.ReadTxIndex(wtx.GetHash(), txindex))
|
||||||
{
|
{
|
||||||
// Update fSpent if a tx got spent somewhere else by a copy of wallet.dat
|
// Update fSpent if a tx got spent somewhere else by a copy of wallet.dat
|
||||||
if (!wtx.fSpent)
|
|
||||||
{
|
|
||||||
if (txindex.vSpent.size() != wtx.vout.size())
|
if (txindex.vSpent.size() != wtx.vout.size())
|
||||||
{
|
{
|
||||||
printf("ERROR: ReacceptWalletTransactions() : txindex.vSpent.size() %d != wtx.vout.size() %d\n", txindex.vSpent.size(), wtx.vout.size());
|
printf("ERROR: ReacceptWalletTransactions() : txindex.vSpent.size() %d != wtx.vout.size() %d\n", txindex.vSpent.size(), wtx.vout.size());
|
||||||
|
@ -957,17 +952,18 @@ void ReacceptWalletTransactions()
|
||||||
{
|
{
|
||||||
if (!txindex.vSpent[i].IsNull() && wtx.vout[i].IsMine())
|
if (!txindex.vSpent[i].IsNull() && wtx.vout[i].IsMine())
|
||||||
{
|
{
|
||||||
wtx.fSpent = true;
|
wtx.MarkSpent(i);
|
||||||
|
fUpdated = true;
|
||||||
vMissingTx.push_back(txindex.vSpent[i]);
|
vMissingTx.push_back(txindex.vSpent[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (wtx.fSpent)
|
if (fUpdated)
|
||||||
{
|
{
|
||||||
printf("ReacceptWalletTransactions found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str());
|
printf("ReacceptWalletTransactions found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str());
|
||||||
|
wtx.MarkDirty();
|
||||||
wtx.WriteToDisk();
|
wtx.WriteToDisk();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Reaccept any txes of ours that aren't already in a block
|
// Reaccept any txes of ours that aren't already in a block
|
||||||
|
@ -3732,9 +3728,9 @@ int64 GetBalance()
|
||||||
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
|
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
|
||||||
{
|
{
|
||||||
CWalletTx* pcoin = &(*it).second;
|
CWalletTx* pcoin = &(*it).second;
|
||||||
if (!pcoin->IsFinal() || pcoin->fSpent || !pcoin->IsConfirmed())
|
if (!pcoin->IsFinal() || !pcoin->IsConfirmed())
|
||||||
continue;
|
continue;
|
||||||
nTotal += pcoin->GetCredit();
|
nTotal += pcoin->GetAvailableCredit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3763,14 +3759,17 @@ bool SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfTheirs, set<
|
||||||
|
|
||||||
foreach(CWalletTx* pcoin, vCoins)
|
foreach(CWalletTx* pcoin, vCoins)
|
||||||
{
|
{
|
||||||
if (!pcoin->IsFinal() || pcoin->fSpent || !pcoin->IsConfirmed())
|
if (!pcoin->IsFinal() || !pcoin->IsConfirmed())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int nDepth = pcoin->GetDepthInMainChain();
|
int nDepth = pcoin->GetDepthInMainChain();
|
||||||
if (nDepth < (pcoin->IsFromMe() ? nConfMine : nConfTheirs))
|
if (nDepth < (pcoin->IsFromMe() ? nConfMine : nConfTheirs))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int64 n = pcoin->GetCredit();
|
int64 n = pcoin->GetAvailableCredit();
|
||||||
if (n <= 0)
|
if (n <= 0)
|
||||||
continue;
|
continue;
|
||||||
if (n == nTargetValue)
|
if (n == nTargetValue)
|
||||||
|
@ -4017,12 +4016,11 @@ bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey)
|
||||||
// Mark old coins as spent
|
// Mark old coins as spent
|
||||||
set<CWalletTx*> setCoins;
|
set<CWalletTx*> setCoins;
|
||||||
foreach(const CTxIn& txin, wtxNew.vin)
|
foreach(const CTxIn& txin, wtxNew.vin)
|
||||||
setCoins.insert(&mapWallet[txin.prevout.hash]);
|
|
||||||
foreach(CWalletTx* pcoin, setCoins)
|
|
||||||
{
|
{
|
||||||
pcoin->fSpent = true;
|
CWalletTx &pcoin = mapWallet[txin.prevout.hash];
|
||||||
pcoin->WriteToDisk();
|
pcoin.MarkSpent(txin.prevout.n);
|
||||||
vWalletUpdated.push_back(pcoin->GetHash());
|
pcoin.WriteToDisk();
|
||||||
|
vWalletUpdated.push_back(pcoin.GetHash());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
122
main.h
122
main.h
|
@ -738,6 +738,7 @@ public:
|
||||||
fMerkleVerified = false;
|
fMerkleVerified = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
IMPLEMENT_SERIALIZE
|
IMPLEMENT_SERIALIZE
|
||||||
(
|
(
|
||||||
nSerSize += SerReadWrite(s, *(CTransaction*)this, nType, nVersion, ser_action);
|
nSerSize += SerReadWrite(s, *(CTransaction*)this, nType, nVersion, ser_action);
|
||||||
|
@ -774,15 +775,17 @@ public:
|
||||||
unsigned int fTimeReceivedIsTxTime;
|
unsigned int fTimeReceivedIsTxTime;
|
||||||
unsigned int nTimeReceived; // time received by this node
|
unsigned int nTimeReceived; // time received by this node
|
||||||
char fFromMe;
|
char fFromMe;
|
||||||
char fSpent;
|
|
||||||
string strFromAccount;
|
string strFromAccount;
|
||||||
|
vector<char> vfSpent;
|
||||||
|
|
||||||
// memory only
|
// memory only
|
||||||
mutable char fDebitCached;
|
mutable char fDebitCached;
|
||||||
mutable char fCreditCached;
|
mutable char fCreditCached;
|
||||||
|
mutable char fAvailableCreditCached;
|
||||||
mutable char fChangeCached;
|
mutable char fChangeCached;
|
||||||
mutable int64 nDebitCached;
|
mutable int64 nDebitCached;
|
||||||
mutable int64 nCreditCached;
|
mutable int64 nCreditCached;
|
||||||
|
mutable int64 nAvailableCreditCached;
|
||||||
mutable int64 nChangeCached;
|
mutable int64 nChangeCached;
|
||||||
|
|
||||||
// memory only UI hints
|
// memory only UI hints
|
||||||
|
@ -814,13 +817,15 @@ public:
|
||||||
fTimeReceivedIsTxTime = false;
|
fTimeReceivedIsTxTime = false;
|
||||||
nTimeReceived = 0;
|
nTimeReceived = 0;
|
||||||
fFromMe = false;
|
fFromMe = false;
|
||||||
fSpent = false;
|
|
||||||
strFromAccount.clear();
|
strFromAccount.clear();
|
||||||
|
vfSpent.clear();
|
||||||
fDebitCached = false;
|
fDebitCached = false;
|
||||||
fCreditCached = false;
|
fCreditCached = false;
|
||||||
|
fAvailableCreditCached = false;
|
||||||
fChangeCached = false;
|
fChangeCached = false;
|
||||||
nDebitCached = 0;
|
nDebitCached = 0;
|
||||||
nCreditCached = 0;
|
nCreditCached = 0;
|
||||||
|
nAvailableCreditCached = 0;
|
||||||
nChangeCached = 0;
|
nChangeCached = 0;
|
||||||
nTimeDisplayed = 0;
|
nTimeDisplayed = 0;
|
||||||
nLinesDisplayed = 0;
|
nLinesDisplayed = 0;
|
||||||
|
@ -832,22 +837,96 @@ public:
|
||||||
CWalletTx* pthis = const_cast<CWalletTx*>(this);
|
CWalletTx* pthis = const_cast<CWalletTx*>(this);
|
||||||
if (fRead)
|
if (fRead)
|
||||||
pthis->Init();
|
pthis->Init();
|
||||||
|
char fSpent = false;
|
||||||
|
|
||||||
|
if (!fRead)
|
||||||
|
{
|
||||||
|
pthis->mapValue["fromaccount"] = pthis->strFromAccount;
|
||||||
|
|
||||||
|
string str;
|
||||||
|
foreach(char f, vfSpent)
|
||||||
|
{
|
||||||
|
str += (f ? '1' : '0');
|
||||||
|
if (f)
|
||||||
|
fSpent = true;
|
||||||
|
}
|
||||||
|
pthis->mapValue["spent"] = str;
|
||||||
|
}
|
||||||
|
|
||||||
nSerSize += SerReadWrite(s, *(CMerkleTx*)this, nType, nVersion,ser_action);
|
nSerSize += SerReadWrite(s, *(CMerkleTx*)this, nType, nVersion,ser_action);
|
||||||
READWRITE(vtxPrev);
|
READWRITE(vtxPrev);
|
||||||
|
|
||||||
pthis->mapValue["fromaccount"] = pthis->strFromAccount;
|
|
||||||
READWRITE(mapValue);
|
READWRITE(mapValue);
|
||||||
pthis->strFromAccount = pthis->mapValue["fromaccount"];
|
|
||||||
pthis->mapValue.erase("fromaccount");
|
|
||||||
pthis->mapValue.erase("version");
|
|
||||||
|
|
||||||
READWRITE(vOrderForm);
|
READWRITE(vOrderForm);
|
||||||
READWRITE(fTimeReceivedIsTxTime);
|
READWRITE(fTimeReceivedIsTxTime);
|
||||||
READWRITE(nTimeReceived);
|
READWRITE(nTimeReceived);
|
||||||
READWRITE(fFromMe);
|
READWRITE(fFromMe);
|
||||||
READWRITE(fSpent);
|
READWRITE(fSpent);
|
||||||
|
|
||||||
|
if (fRead)
|
||||||
|
{
|
||||||
|
pthis->strFromAccount = pthis->mapValue["fromaccount"];
|
||||||
|
|
||||||
|
if (mapValue.count("spent"))
|
||||||
|
foreach(char c, pthis->mapValue["spent"])
|
||||||
|
pthis->vfSpent.push_back(c != '0');
|
||||||
|
else
|
||||||
|
pthis->vfSpent.assign(vout.size(), fSpent);
|
||||||
|
}
|
||||||
|
|
||||||
|
pthis->mapValue.erase("fromaccount");
|
||||||
|
pthis->mapValue.erase("version");
|
||||||
|
pthis->mapValue.erase("spent");
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// marks certain txout's as spent
|
||||||
|
// returns true if any update took place
|
||||||
|
bool UpdateSpent(const vector<char>& vfNewSpent)
|
||||||
|
{
|
||||||
|
bool fReturn = false;
|
||||||
|
for (int i=0; i < vfNewSpent.size(); i++)
|
||||||
|
{
|
||||||
|
if (i == vfSpent.size())
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (vfNewSpent[i] && !vfSpent[i])
|
||||||
|
{
|
||||||
|
vfSpent[i] = true;
|
||||||
|
fReturn = true;
|
||||||
|
fAvailableCreditCached = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MarkDirty()
|
||||||
|
{
|
||||||
|
fCreditCached = false;
|
||||||
|
fAvailableCreditCached = false;
|
||||||
|
fDebitCached = false;
|
||||||
|
fChangeCached = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MarkSpent(unsigned int nOut)
|
||||||
|
{
|
||||||
|
if (nOut >= vout.size())
|
||||||
|
throw runtime_error("CWalletTx::MarkSpent() : nOut out of range");
|
||||||
|
vfSpent.resize(vout.size());
|
||||||
|
if (!vfSpent[nOut])
|
||||||
|
{
|
||||||
|
vfSpent[nOut] = true;
|
||||||
|
fAvailableCreditCached = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsSpent(unsigned int nOut) const
|
||||||
|
{
|
||||||
|
if (nOut >= vout.size())
|
||||||
|
throw runtime_error("CWalletTx::IsSpent() : nOut out of range");
|
||||||
|
if (nOut >= vfSpent.size())
|
||||||
|
return false;
|
||||||
|
return (!!vfSpent[nOut]);
|
||||||
|
}
|
||||||
|
|
||||||
int64 GetDebit() const
|
int64 GetDebit() const
|
||||||
{
|
{
|
||||||
if (vin.empty())
|
if (vin.empty())
|
||||||
|
@ -873,6 +952,33 @@ public:
|
||||||
return nCreditCached;
|
return nCreditCached;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int64 GetAvailableCredit(bool fUseCache=true) const
|
||||||
|
{
|
||||||
|
// Must wait until coinbase is safely deep enough in the chain before valuing it
|
||||||
|
if (IsCoinBase() && GetBlocksToMaturity() > 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (fUseCache && fAvailableCreditCached)
|
||||||
|
return nAvailableCreditCached;
|
||||||
|
|
||||||
|
int64 nCredit = 0;
|
||||||
|
for (int i = 0; i < vout.size(); i++)
|
||||||
|
{
|
||||||
|
if (!IsSpent(i))
|
||||||
|
{
|
||||||
|
const CTxOut &txout = vout[i];
|
||||||
|
nCredit += txout.GetCredit();
|
||||||
|
if (!MoneyRange(nCredit))
|
||||||
|
throw runtime_error("CWalletTx::GetAvailableCredit() : value out of range");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nAvailableCreditCached = nCredit;
|
||||||
|
fAvailableCreditCached = true;
|
||||||
|
return nCredit;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int64 GetChange() const
|
int64 GetChange() const
|
||||||
{
|
{
|
||||||
if (fChangeCached)
|
if (fChangeCached)
|
||||||
|
|
Loading…
Reference in a new issue