diff --git a/db.cpp b/db.cpp index 8be1e20e4..6717c04ba 100644 --- a/db.cpp +++ b/db.cpp @@ -419,6 +419,9 @@ bool CTxDB::LoadBlockIndex() // Watch for genesis block if (pindexGenesisBlock == NULL && diskindex.GetBlockHash() == hashGenesisBlock) pindexGenesisBlock = pindexNew; + + if (!pindexNew->CheckIndex()) + return error("LoadBlockIndex() : CheckIndex failed at %d", pindexNew->nHeight); } else { @@ -454,7 +457,7 @@ bool CTxDB::LoadBlockIndex() pindexBest = mapBlockIndex[hashBestChain]; nBestHeight = pindexBest->nHeight; bnBestChainWork = pindexBest->bnChainWork; - printf("LoadBlockIndex(): hashBestChain=%s height=%d\n", hashBestChain.ToString().substr(0,16).c_str(), nBestHeight); + printf("LoadBlockIndex(): hashBestChain=%s height=%d\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight); // Load bnBestInvalidWork, OK if it doesn't exist ReadBestInvalidWork(bnBestInvalidWork); @@ -463,6 +466,8 @@ bool CTxDB::LoadBlockIndex() CBlockIndex* pindexFork = NULL; for (CBlockIndex* pindex = pindexBest; pindex && pindex->pprev; pindex = pindex->pprev) { + if (pindex->nHeight < 74000 && !mapArgs.count("-checkblocks")) + break; CBlock block; if (!block.ReadFromDisk(pindex)) return error("LoadBlockIndex() : block.ReadFromDisk failed"); @@ -629,8 +634,8 @@ bool CWalletDB::LoadWallet() //printf("LoadWallet %s\n", wtx.GetHash().ToString().c_str()); //printf(" %12I64d %s %s %s\n", // wtx.vout[0].nValue, - // DateTimeStrFormat("%x %H:%M:%S", wtx.nTime).c_str(), - // wtx.hashBlock.ToString().substr(0,16).c_str(), + // DateTimeStrFormat("%x %H:%M:%S", wtx.GetBlockTime()).c_str(), + // wtx.hashBlock.ToString().substr(0,20).c_str(), // wtx.mapValue["message"].c_str()); } else if (strType == "key" || strType == "wkey") diff --git a/main.cpp b/main.cpp index 953a43f0e..f5a3555e0 100644 --- a/main.cpp +++ b/main.cpp @@ -500,8 +500,8 @@ bool CTransaction::AcceptTransaction(CTxDB& txdb, bool fCheckInputs, bool* pfMis return error("AcceptTransaction() : CheckTransaction failed"); // To help v0.1.5 clients who would see it as a negative number - if (nLockTime > INT_MAX) - return error("AcceptTransaction() : not accepting nLockTime beyond 2038"); + if ((int64)nLockTime > INT_MAX) + return error("AcceptTransaction() : not accepting nLockTime beyond 2038 yet"); // Do we already have it? uint256 hash = GetHash(); @@ -519,6 +519,9 @@ bool CTransaction::AcceptTransaction(CTxDB& txdb, bool fCheckInputs, bool* pfMis COutPoint outpoint = vin[i].prevout; if (mapNextTx.count(outpoint)) { + // Disable replacement feature for now + return false; + // Allow replacing with a newer version of the same transaction if (i != 0) return false; @@ -550,8 +553,8 @@ bool CTransaction::AcceptTransaction(CTxDB& txdb, bool fCheckInputs, bool* pfMis { if (ptxOld) { - printf("mapTransaction.erase(%s) replacing with new version\n", ptxOld->GetHash().ToString().c_str()); - mapTransactions.erase(ptxOld->GetHash()); + printf("AcceptTransaction() : replacing tx %s with new version\n", ptxOld->GetHash().ToString().c_str()); + ptxOld->RemoveFromMemoryPool(); } AddToMemoryPool(); } @@ -748,6 +751,12 @@ void ResendWalletTransactions() if (fFirst) return; + // Only do it if there's been a new block since last time + static int64 nLastTime; + if (nTimeBestReceived < nLastTime) + return; + nLastTime = GetTime(); + // Rebroadcast any of our txes that aren't in a block yet printf("ResendWalletTransactions()\n"); CTxDB txdb("r"); @@ -785,9 +794,13 @@ void ResendWalletTransactions() // CBlock and CBlockIndex // -bool CBlock::ReadFromDisk(const CBlockIndex* pblockindex, bool fReadTransactions) +bool CBlock::ReadFromDisk(const CBlockIndex* pindex, bool fReadTransactions) { - return ReadFromDisk(pblockindex->nFile, pblockindex->nBlockPos, fReadTransactions); + if (!ReadFromDisk(pindex->nFile, pindex->nBlockPos, fReadTransactions)) + return false; + if (GetHash() != pindex->GetBlockHash()) + return error("CBlock::ReadFromDisk() : GetHash() doesn't match index"); + return true; } uint256 GetOrphanRoot(const CBlock* pblock) @@ -829,7 +842,7 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast) assert(pindexFirst); // Limit adjustment step - int64 nActualTimespan = (int64)pindexLast->nTime - (int64)pindexFirst->nTime; + int64 nActualTimespan = pindexLast->GetBlockTime() - pindexFirst->GetBlockTime(); printf(" nActualTimespan = %"PRI64d" before bounds\n", nActualTimespan); if (nActualTimespan < nTargetTimespan/4) nActualTimespan = nTargetTimespan/4; @@ -854,6 +867,22 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast) return bnNew.GetCompact(); } +bool CheckProofOfWork(uint256 hash, unsigned int nBits) +{ + CBigNum bnTarget; + bnTarget.SetCompact(nBits); + + // Check range + if (bnTarget <= 0 || bnTarget > bnProofOfWorkLimit) + return error("CheckProofOfWork() : nBits below minimum work"); + + // Check proof of work matches claimed amount + if (hash > bnTarget.getuint256()) + return error("CheckProofOfWork() : hash doesn't match nBits"); + + return true; +} + bool IsInitialBlockDownload() { if (pindexBest == NULL) @@ -866,7 +895,7 @@ bool IsInitialBlockDownload() nLastUpdate = GetTime(); } return (GetTime() - nLastUpdate < 10 && - pindexBest->nTime < GetTime() - 24 * 60 * 60); + pindexBest->GetBlockTime() < GetTime() - 24 * 60 * 60); } bool IsLockdown() @@ -884,8 +913,8 @@ void Lockdown(CBlockIndex* pindexNew) CTxDB().WriteBestInvalidWork(bnBestInvalidWork); MainFrameRepaint(); } - printf("Lockdown: invalid block=%s height=%d work=%s\n", pindexNew->GetBlockHash().ToString().substr(0,22).c_str(), pindexNew->nHeight, pindexNew->bnChainWork.ToString().c_str()); - printf("Lockdown: current best=%s height=%d work=%s\n", hashBestChain.ToString().substr(0,22).c_str(), nBestHeight, bnBestChainWork.ToString().c_str()); + printf("Lockdown: invalid block=%s height=%d work=%s\n", pindexNew->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->nHeight, pindexNew->bnChainWork.ToString().c_str()); + printf("Lockdown: current best=%s height=%d work=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, bnBestChainWork.ToString().c_str()); printf("Lockdown: IsLockdown()=%d\n", (IsLockdown() ? 1 : 0)); if (IsLockdown()) printf("Lockdown: WARNING: Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade.\n"); @@ -1008,14 +1037,13 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map& mapTestPoo nValueIn += txPrev.vout[prevout.n].nValue; // Check for negative or overflow input values - if (txPrev.vout[prevout.n].nValue < 0) - return error("ConnectInputs() : txin.nValue negative"); - if (txPrev.vout[prevout.n].nValue > MAX_MONEY) - return error("ConnectInputs() : txin.nValue too high"); - if (nValueIn > MAX_MONEY) - return error("ConnectInputs() : txin total too high"); + if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn)) + return error("ConnectInputs() : txin values out of range"); } + if (nValueIn < GetValueOut()) + return error("ConnectInputs() : %s value in < value out", GetHash().ToString().substr(0,6).c_str()); + // Tally transaction fees int64 nTxFee = nValueIn - GetValueOut(); if (nTxFee < 0) @@ -1180,7 +1208,7 @@ bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) foreach(CBlockIndex* pindex, vDisconnect) { CBlock block; - if (!block.ReadFromDisk(pindex->nFile, pindex->nBlockPos)) + if (!block.ReadFromDisk(pindex)) return error("Reorganize() : ReadFromDisk for disconnect failed"); if (!block.DisconnectBlock(txdb, pindex)) return error("Reorganize() : DisconnectBlock failed"); @@ -1197,7 +1225,7 @@ bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) { CBlockIndex* pindex = vConnect[i]; CBlock block; - if (!block.ReadFromDisk(pindex->nFile, pindex->nBlockPos)) + if (!block.ReadFromDisk(pindex)) return error("Reorganize() : ReadFromDisk for connect failed"); if (!block.ConnectBlock(txdb, pindex)) { @@ -1283,7 +1311,7 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) bnBestChainWork = pindexNew->bnChainWork; nTimeBestReceived = GetTime(); nTransactionsUpdated++; - printf("SetBestChain: new best=%s height=%d work=%s\n", hashBestChain.ToString().substr(0,22).c_str(), nBestHeight, bnBestChainWork.ToString().c_str()); + printf("SetBestChain: new best=%s height=%d work=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, bnBestChainWork.ToString().c_str()); return true; } @@ -1294,7 +1322,7 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) // Check for duplicate uint256 hash = GetHash(); if (mapBlockIndex.count(hash)) - return error("AddToBlockIndex() : %s already exists", hash.ToString().substr(0,16).c_str()); + return error("AddToBlockIndex() : %s already exists", hash.ToString().substr(0,20).c_str()); // Construct new block index object CBlockIndex* pindexNew = new CBlockIndex(nFile, nBlockPos, *this); @@ -1346,7 +1374,7 @@ bool CBlock::CheckBlock() const return error("CheckBlock() : size limits failed"); // Check timestamp - if (nTime > GetAdjustedTime() + 2 * 60 * 60) + if (GetBlockTime() > GetAdjustedTime() + 2 * 60 * 60) return error("CheckBlock() : block timestamp too far in the future"); // First transaction must be coinbase, the rest must not be @@ -1362,10 +1390,8 @@ bool CBlock::CheckBlock() const return error("CheckBlock() : CheckTransaction failed"); // Check proof of work matches claimed amount - if (CBigNum().SetCompact(nBits) > bnProofOfWorkLimit) - return error("CheckBlock() : nBits below minimum work"); - if (GetHash() > CBigNum().SetCompact(nBits).getuint256()) - return error("CheckBlock() : hash doesn't match nBits"); + if (!CheckProofOfWork(GetHash(), nBits)) + return error("CheckBlock() : proof of work failed"); // Check merkleroot if (hashMerkleRoot != BuildMerkleTree()) @@ -1388,12 +1414,12 @@ bool CBlock::AcceptBlock() CBlockIndex* pindexPrev = (*mi).second; // Check timestamp against prev - if (nTime <= pindexPrev->GetMedianTimePast()) + if (GetBlockTime() <= pindexPrev->GetMedianTimePast()) return error("AcceptBlock() : block's timestamp is too early"); // Check that all transactions are finalized foreach(const CTransaction& tx, vtx) - if (!tx.IsFinal(pindexPrev->nHeight+1, nTime)) + if (!tx.IsFinal(pindexPrev->nHeight+1, GetBlockTime())) return error("AcceptBlock() : contains a non-final transaction"); // Check proof of work @@ -1442,9 +1468,9 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) // Check for duplicate uint256 hash = pblock->GetHash(); if (mapBlockIndex.count(hash)) - return error("ProcessBlock() : already have block %d %s", mapBlockIndex[hash]->nHeight, hash.ToString().substr(0,16).c_str()); + return error("ProcessBlock() : already have block %d %s", mapBlockIndex[hash]->nHeight, hash.ToString().substr(0,20).c_str()); if (mapOrphanBlocks.count(hash)) - return error("ProcessBlock() : already have block (orphan) %s", hash.ToString().substr(0,16).c_str()); + return error("ProcessBlock() : already have block (orphan) %s", hash.ToString().substr(0,20).c_str()); // Preliminary checks if (!pblock->CheckBlock()) @@ -1456,7 +1482,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) // If don't already have its previous block, shunt it off to holding area until we get it if (!mapBlockIndex.count(pblock->hashPrevBlock)) { - printf("ProcessBlock: ORPHAN BLOCK, prev=%s\n", pblock->hashPrevBlock.ToString().substr(0,16).c_str()); + printf("ProcessBlock: ORPHAN BLOCK, prev=%s\n", pblock->hashPrevBlock.ToString().substr(0,20).c_str()); mapOrphanBlocks.insert(make_pair(hash, pblock)); mapOrphanBlocksByPrev.insert(make_pair(pblock->hashPrevBlock, pblock)); @@ -1731,8 +1757,8 @@ void PrintBlockTree() pindex->nHeight, pindex->nFile, pindex->nBlockPos, - block.GetHash().ToString().substr(0,16).c_str(), - DateTimeStrFormat("%x %H:%M:%S", block.nTime).c_str(), + block.GetHash().ToString().substr(0,20).c_str(), + DateTimeStrFormat("%x %H:%M:%S", block.GetBlockTime()).c_str(), block.vtx.size()); CRITICAL_BLOCK(cs_mapWallet) @@ -2157,12 +2183,12 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) if (pindex) pindex = pindex->pnext; int nLimit = 500 + locator.GetDistanceBack(); - printf("getblocks %d to %s limit %d\n", (pindex ? pindex->nHeight : -1), hashStop.ToString().substr(0,16).c_str(), nLimit); + printf("getblocks %d to %s limit %d\n", (pindex ? pindex->nHeight : -1), hashStop.ToString().substr(0,20).c_str(), nLimit); for (; pindex; pindex = pindex->pnext) { if (pindex->GetBlockHash() == hashStop) { - printf(" getblocks stopping at %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,16).c_str()); + printf(" getblocks stopping at %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,20).c_str()); break; } pfrom->PushInventory(CInv(MSG_BLOCK, pindex->GetBlockHash())); @@ -2170,7 +2196,7 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) { // When this block is requested, we'll send an inv that'll make them // getblocks the next batch of inventory. - printf(" getblocks stopping at limit %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,16).c_str()); + printf(" getblocks stopping at limit %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,20).c_str()); pfrom->hashContinue = pindex->GetBlockHash(); break; } @@ -2237,7 +2263,7 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) vRecv >> *pblock; //// debug print - printf("received block %s\n", pblock->GetHash().ToString().substr(0,16).c_str()); + printf("received block %s\n", pblock->GetHash().ToString().substr(0,20).c_str()); // pblock->print(); CInv inv(MSG_BLOCK, pblock->GetHash()); diff --git a/main.h b/main.h index f5467d173..333fa8390 100644 --- a/main.h +++ b/main.h @@ -19,8 +19,8 @@ static const unsigned int MAX_BLOCK_SIZE = 1000000; static const int64 COIN = 100000000; static const int64 CENT = 1000000; static const int64 MAX_MONEY = 21000000 * COIN; +inline bool MoneyRange(int64 nValue) { return (nValue >= 0 && nValue <= MAX_MONEY); } static const int COINBASE_MATURITY = 100; - static const CBigNum bnProofOfWorkLimit(~uint256(0) >> 32); @@ -83,6 +83,7 @@ string SendMoneyToBitcoinAddress(string strAddress, int64 nValue, CWalletTx& wtx void GenerateBitcoins(bool fGenerate); void ThreadBitcoinMiner(void* parg); void BitcoinMiner(); +bool CheckProofOfWork(uint256 hash, unsigned int nBits); bool IsInitialBlockDownload(); bool IsLockdown(); @@ -338,6 +339,8 @@ public: int64 GetCredit() const { + if (!MoneyRange(nValue)) + throw runtime_error("CTxOut::GetCredit() : value out of range"); if (IsMine()) return nValue; return 0; @@ -424,7 +427,7 @@ public: nBlockHeight = nBestHeight; if (nBlockTime == 0) nBlockTime = GetAdjustedTime(); - if (nLockTime < (nLockTime < 500000000 ? nBlockHeight : nBlockTime)) + if ((int64)nLockTime < (nLockTime < 500000000 ? (int64)nBlockHeight : nBlockTime)) return true; foreach(const CTxIn& txin, vin) if (!txin.IsFinal()) @@ -481,8 +484,8 @@ public: if (txout.nValue > MAX_MONEY) return error("CTransaction::CheckTransaction() : txout.nValue too high"); nValueOut += txout.nValue; - if (nValueOut > MAX_MONEY) - return error("CTransaction::CheckTransaction() : txout total too high"); + if (!MoneyRange(nValueOut)) + return error("CTransaction::CheckTransaction() : txout total out of range"); } if (IsCoinBase()) @@ -512,7 +515,11 @@ public: { int64 nDebit = 0; foreach(const CTxIn& txin, vin) + { nDebit += txin.GetDebit(); + if (!MoneyRange(nDebit)) + throw runtime_error("CTransaction::GetDebit() : value out of range"); + } return nDebit; } @@ -520,7 +527,11 @@ public: { int64 nCredit = 0; foreach(const CTxOut& txout, vout) + { nCredit += txout.GetCredit(); + if (!MoneyRange(nCredit)) + throw runtime_error("CTransaction::GetCredit() : value out of range"); + } return nCredit; } @@ -529,9 +540,9 @@ public: int64 nValueOut = 0; foreach(const CTxOut& txout, vout) { - if (txout.nValue < 0) - throw runtime_error("CTransaction::GetValueOut() : negative value"); nValueOut += txout.nValue; + if (!MoneyRange(txout.nValue) || !MoneyRange(nValueOut)) + throw runtime_error("CTransaction::GetValueOut() : value out of range"); } return nValueOut; } @@ -930,6 +941,11 @@ public: return Hash(BEGIN(nVersion), END(nNonce)); } + int64 GetBlockTime() const + { + return (int64)nTime; + } + uint256 BuildMerkleTree() const { @@ -1030,10 +1046,8 @@ public: filein >> *this; // Check the header - if (CBigNum().SetCompact(nBits) > bnProofOfWorkLimit) - return error("CBlock::ReadFromDisk() : nBits errors in block header"); - if (GetHash() > CBigNum().SetCompact(nBits).getuint256()) - return error("CBlock::ReadFromDisk() : GetHash() errors in block header"); + if (!CheckProofOfWork(GetHash(), nBits)) + return error("CBlock::ReadFromDisk() : errors in block header"); return true; } @@ -1043,9 +1057,9 @@ public: void print() const { printf("CBlock(hash=%s, ver=%d, hashPrevBlock=%s, hashMerkleRoot=%s, nTime=%u, nBits=%08x, nNonce=%u, vtx=%d)\n", - GetHash().ToString().substr(0,16).c_str(), + GetHash().ToString().substr(0,20).c_str(), nVersion, - hashPrevBlock.ToString().substr(0,16).c_str(), + hashPrevBlock.ToString().substr(0,20).c_str(), hashMerkleRoot.ToString().substr(0,6).c_str(), nTime, nBits, nNonce, vtx.size()); @@ -1064,7 +1078,7 @@ public: int64 GetBlockValue(int nHeight, int64 nFees) const; bool DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex); bool ConnectBlock(CTxDB& txdb, CBlockIndex* pindex); - bool ReadFromDisk(const CBlockIndex* blockindex, bool fReadTransactions=true); + bool ReadFromDisk(const CBlockIndex* pindex, bool fReadTransactions=true); bool SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew); bool AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos); bool CheckBlock() const; @@ -1142,8 +1156,15 @@ public: return *phashBlock; } + int64 GetBlockTime() const + { + return (int64)nTime; + } + CBigNum GetBlockWork() const { + if (CBigNum().SetCompact(nBits) <= 0) + return 0; return (CBigNum(1)<<256) / (CBigNum().SetCompact(nBits)+1); } @@ -1152,6 +1173,11 @@ public: return (pnext || this == pindexBest); } + bool CheckIndex() const + { + return CheckProofOfWork(GetBlockHash(), nBits); + } + bool EraseBlockFromDisk() { // Open history file @@ -1171,13 +1197,13 @@ public: int64 GetMedianTimePast() const { - unsigned int pmedian[nMedianTimeSpan]; - unsigned int* pbegin = &pmedian[nMedianTimeSpan]; - unsigned int* pend = &pmedian[nMedianTimeSpan]; + int64 pmedian[nMedianTimeSpan]; + int64* pbegin = &pmedian[nMedianTimeSpan]; + int64* pend = &pmedian[nMedianTimeSpan]; const CBlockIndex* pindex = this; for (int i = 0; i < nMedianTimeSpan && pindex; i++, pindex = pindex->pprev) - *(--pbegin) = pindex->nTime; + *(--pbegin) = pindex->GetBlockTime(); sort(pbegin, pend); return pbegin[(pend - pbegin)/2]; @@ -1189,7 +1215,7 @@ public: for (int i = 0; i < nMedianTimeSpan/2; i++) { if (!pindex->pnext) - return nTime; + return GetBlockTime(); pindex = pindex->pnext; } return pindex->GetMedianTimePast(); @@ -1202,7 +1228,7 @@ public: return strprintf("CBlockIndex(nprev=%08x, pnext=%08x, nFile=%d, nBlockPos=%-6d nHeight=%d, merkle=%s, hashBlock=%s)", pprev, pnext, nFile, nBlockPos, nHeight, hashMerkleRoot.ToString().substr(0,6).c_str(), - GetBlockHash().ToString().substr(0,16).c_str()); + GetBlockHash().ToString().substr(0,20).c_str()); } void print() const @@ -1272,8 +1298,8 @@ public: str += CBlockIndex::ToString(); str += strprintf("\n hashBlock=%s, hashPrev=%s, hashNext=%s)", GetBlockHash().ToString().c_str(), - hashPrev.ToString().substr(0,16).c_str(), - hashNext.ToString().substr(0,16).c_str()); + hashPrev.ToString().substr(0,20).c_str(), + hashNext.ToString().substr(0,20).c_str()); return str; } diff --git a/net.h b/net.h index 5a52077ff..5b24f7a1c 100644 --- a/net.h +++ b/net.h @@ -424,7 +424,7 @@ public: string ToString() const { - return strprintf("%s %s", GetCommand(), hash.ToString().substr(0,16).c_str()); + return strprintf("%s %s", GetCommand(), hash.ToString().substr(0,20).c_str()); } void print() const diff --git a/serialize.h b/serialize.h index d65fe71cb..6495cfe01 100644 --- a/serialize.h +++ b/serialize.h @@ -20,7 +20,7 @@ class CDataStream; class CAutoFile; static const int VERSION = 310; -static const char* pszSubVer = ".3"; +static const char* pszSubVer = ".4"; @@ -81,6 +81,13 @@ enum #define READWRITE(obj) (nSerSize += ::SerReadWrite(s, (obj), nType, nVersion, ser_action)) +#define READWRITEVER(obj) \ + do { \ + READWRITE((obj)); \ + if ((obj) == 10300) \ + (obj) = 300; \ + } while (false) +