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:
Pieter Wuille
2011-03-17 22:51:59 +01:00
parent b7ebc662eb
commit 335e878be8
2 changed files with 147 additions and 43 deletions

124
main.h
View File

@@ -738,6 +738,7 @@ public:
fMerkleVerified = false;
}
IMPLEMENT_SERIALIZE
(
nSerSize += SerReadWrite(s, *(CTransaction*)this, nType, nVersion, ser_action);
@@ -774,15 +775,17 @@ public:
unsigned int fTimeReceivedIsTxTime;
unsigned int nTimeReceived; // time received by this node
char fFromMe;
char fSpent;
string strFromAccount;
vector<char> vfSpent;
// memory only
mutable char fDebitCached;
mutable char fCreditCached;
mutable char fAvailableCreditCached;
mutable char fChangeCached;
mutable int64 nDebitCached;
mutable int64 nCreditCached;
mutable int64 nAvailableCreditCached;
mutable int64 nChangeCached;
// memory only UI hints
@@ -814,13 +817,15 @@ public:
fTimeReceivedIsTxTime = false;
nTimeReceived = 0;
fFromMe = false;
fSpent = false;
strFromAccount.clear();
vfSpent.clear();
fDebitCached = false;
fCreditCached = false;
fAvailableCreditCached = false;
fChangeCached = false;
nDebitCached = 0;
nCreditCached = 0;
nAvailableCreditCached = 0;
nChangeCached = 0;
nTimeDisplayed = 0;
nLinesDisplayed = 0;
@@ -832,22 +837,96 @@ public:
CWalletTx* pthis = const_cast<CWalletTx*>(this);
if (fRead)
pthis->Init();
nSerSize += SerReadWrite(s, *(CMerkleTx*)this, nType, nVersion, ser_action);
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);
READWRITE(vtxPrev);
pthis->mapValue["fromaccount"] = pthis->strFromAccount;
READWRITE(mapValue);
pthis->strFromAccount = pthis->mapValue["fromaccount"];
pthis->mapValue.erase("fromaccount");
pthis->mapValue.erase("version");
READWRITE(vOrderForm);
READWRITE(fTimeReceivedIsTxTime);
READWRITE(nTimeReceived);
READWRITE(fFromMe);
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
{
if (vin.empty())
@@ -873,6 +952,33 @@ public:
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
{
if (fChangeCached)