136d446157
The sync checkpoint (hashSyncCheckpoint) was persisted in LevelDB pointing to block 2,186,940. On startup it was loaded from DB, overriding any code change to the initial value. CheckSync then rejected every block below that height during IBD since they weren't in mapBlockIndex yet. Three-pronged fix: - CheckSync now always returns true (master key disabled, no new sync checkpoints will ever be broadcast) - AcceptBlock no longer calls sync checkpoint enforcement - LoadBlockIndex resets sync checkpoint to genesis if stored hash is not in the block index (prevents assert crash) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
417 lines
18 KiB
C++
417 lines
18 KiB
C++
// Copyright (c) 2009-2012 The Bitcoin developers
|
|
// Distributed under the MIT/X11 software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include <boost/assign/list_of.hpp> // for 'map_list_of()'
|
|
#include <boost/foreach.hpp>
|
|
|
|
#include "checkpoints.h"
|
|
|
|
#include "txdb.h"
|
|
#include "main.h"
|
|
#include "uint256.h"
|
|
|
|
namespace Checkpoints
|
|
{
|
|
typedef std::map<int, uint256> MapCheckpoints;
|
|
|
|
//
|
|
// What makes a good checkpoint block?
|
|
// + Is surrounded by blocks with reasonable timestamps
|
|
// (no blocks before with a timestamp after, none after with
|
|
// timestamp before)
|
|
// + Contains no strange transactions
|
|
//
|
|
static MapCheckpoints mapCheckpoints =
|
|
boost::assign::map_list_of
|
|
( 0, hashGenesisBlockOfficial )
|
|
( 2000, uint256("0x0000000000b5f20078bf46ebdf1500813bb6b2cb482065aa93b89e073b2c6467"))
|
|
( 2101, uint256("0xd4ea1ac45b63c8162a7fc8033cec441db8d532ba988202849d7831e32fe2d059"))
|
|
( 2847, uint256("0xb5015e2835f13fd3bb6135cff9b31ac33310c9b77d694bdb592b8680d98d018e"))
|
|
( 3589, uint256("0xb12a2ca3db4e288cada98aa2139768532bd4474c49dd7b8158031032dac08d51"))
|
|
( 3935, uint256("0xe16290c9757d1368b8d7c35de07d4f8f70c2c9f9c785b667df0c3bff85086ca6"))
|
|
( 5703, uint256("0x587db07bb2172ad7db72c5fabc2518262a1b27f503f99417510b2c6fafa6557b"))
|
|
( 9000, uint256("0x00000000019ef6b2f5e7c324c7d083ee94502305aabc7e9cd73a7fb2a57bb8db"))
|
|
( 9001, uint256("0x6d5c6c5f201cc9e59659ee0da30d1430dc6bf3b12a8ff4c3864ab8d6286b0007"))
|
|
( 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249"))
|
|
( 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6"))
|
|
( 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47"))
|
|
(2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0"))
|
|
;
|
|
|
|
static MapCheckpoints mapCheckpointsTestnet =
|
|
boost::assign::map_list_of
|
|
( 0, hashGenesisBlockTestNet )
|
|
( 2000, uint256("0x0000000000b5f20078bf46ebdf1500813bb6b2cb482065aa93b89e073b2c6467"))
|
|
( 2101, uint256("0xd4ea1ac45b63c8162a7fc8033cec441db8d532ba988202849d7831e32fe2d059"))
|
|
( 2847, uint256("0xb5015e2835f13fd3bb6135cff9b31ac33310c9b77d694bdb592b8680d98d018e"))
|
|
( 3589, uint256("0xb12a2ca3db4e288cada98aa2139768532bd4474c49dd7b8158031032dac08d51"))
|
|
( 3935, uint256("0xe16290c9757d1368b8d7c35de07d4f8f70c2c9f9c785b667df0c3bff85086ca6"))
|
|
( 5703, uint256("0x587db07bb2172ad7db72c5fabc2518262a1b27f503f99417510b2c6fafa6557b"))
|
|
( 9000, uint256("0x00000000019ef6b2f5e7c324c7d083ee94502305aabc7e9cd73a7fb2a57bb8db"))
|
|
( 9001, uint256("0x6d5c6c5f201cc9e59659ee0da30d1430dc6bf3b12a8ff4c3864ab8d6286b0007"))
|
|
( 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249"))
|
|
( 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6"))
|
|
( 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47"))
|
|
(2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0"))
|
|
;
|
|
|
|
bool CheckHardened(int nHeight, const uint256& hash)
|
|
{
|
|
MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints);
|
|
|
|
MapCheckpoints::const_iterator i = checkpoints.find(nHeight);
|
|
if (i == checkpoints.end()) return true;
|
|
return hash == i->second;
|
|
}
|
|
|
|
int GetTotalBlocksEstimate()
|
|
{
|
|
MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints);
|
|
|
|
return checkpoints.rbegin()->first;
|
|
}
|
|
|
|
CBlockIndex* GetLastCheckpoint(const std::map<uint256, CBlockIndex*>& mapBlockIndex)
|
|
{
|
|
MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints);
|
|
|
|
BOOST_REVERSE_FOREACH(const MapCheckpoints::value_type& i, checkpoints)
|
|
{
|
|
const uint256& hash = i.second;
|
|
std::map<uint256, CBlockIndex*>::const_iterator t = mapBlockIndex.find(hash);
|
|
if (t != mapBlockIndex.end())
|
|
return t->second;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// triangles: synchronized checkpoint (centrally broadcasted)
|
|
uint256 hashSyncCheckpoint = uint256("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
|
|
uint256 hashPendingCheckpoint = uint256("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
|
|
CSyncCheckpoint checkpointMessage;
|
|
CSyncCheckpoint checkpointMessagePending;
|
|
uint256 hashInvalidCheckpoint = 0;
|
|
CCriticalSection cs_hashSyncCheckpoint;
|
|
|
|
// triangles: get last synchronized checkpoint
|
|
CBlockIndex* GetLastSyncCheckpoint()
|
|
{
|
|
LOCK(cs_hashSyncCheckpoint);
|
|
if (!mapBlockIndex.count(hashSyncCheckpoint))
|
|
error("GetSyncCheckpoint: block index missing for current sync-checkpoint %s", hashSyncCheckpoint.ToString().c_str());
|
|
else
|
|
return mapBlockIndex[hashSyncCheckpoint];
|
|
return NULL;
|
|
}
|
|
|
|
// triangles: only descendant of current sync-checkpoint is allowed
|
|
bool ValidateSyncCheckpoint(uint256 hashCheckpoint)
|
|
{
|
|
if (!mapBlockIndex.count(hashSyncCheckpoint))
|
|
return error("ValidateSyncCheckpoint: block index missing for current sync-checkpoint %s", hashSyncCheckpoint.ToString().c_str());
|
|
if (!mapBlockIndex.count(hashCheckpoint))
|
|
return error("ValidateSyncCheckpoint: block index missing for received sync-checkpoint %s", hashCheckpoint.ToString().c_str());
|
|
|
|
CBlockIndex* pindexSyncCheckpoint = mapBlockIndex[hashSyncCheckpoint];
|
|
CBlockIndex* pindexCheckpointRecv = mapBlockIndex[hashCheckpoint];
|
|
|
|
if (pindexCheckpointRecv->nHeight <= pindexSyncCheckpoint->nHeight)
|
|
{
|
|
// Received an older checkpoint, trace back from current checkpoint
|
|
// to the same height of the received checkpoint to verify
|
|
// that current checkpoint should be a descendant block
|
|
CBlockIndex* pindex = pindexSyncCheckpoint;
|
|
while (pindex->nHeight > pindexCheckpointRecv->nHeight)
|
|
if (!(pindex = pindex->pprev))
|
|
return error("ValidateSyncCheckpoint: pprev null - block index structure failure");
|
|
if (pindex->GetBlockHash() != hashCheckpoint)
|
|
{
|
|
hashInvalidCheckpoint = hashCheckpoint;
|
|
return error("ValidateSyncCheckpoint: new sync-checkpoint %s is conflicting with current sync-checkpoint %s", hashCheckpoint.ToString().c_str(), hashSyncCheckpoint.ToString().c_str());
|
|
}
|
|
return false; // ignore older checkpoint
|
|
}
|
|
|
|
// Received checkpoint should be a descendant block of the current
|
|
// checkpoint. Trace back to the same height of current checkpoint
|
|
// to verify.
|
|
CBlockIndex* pindex = pindexCheckpointRecv;
|
|
while (pindex->nHeight > pindexSyncCheckpoint->nHeight)
|
|
if (!(pindex = pindex->pprev))
|
|
return error("ValidateSyncCheckpoint: pprev2 null - block index structure failure");
|
|
if (pindex->GetBlockHash() != hashSyncCheckpoint)
|
|
{
|
|
hashInvalidCheckpoint = hashCheckpoint;
|
|
return error("ValidateSyncCheckpoint: new sync-checkpoint %s is not a descendant of current sync-checkpoint %s", hashCheckpoint.ToString().c_str(), hashSyncCheckpoint.ToString().c_str());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WriteSyncCheckpoint(const uint256& hashCheckpoint)
|
|
{
|
|
CTxDB txdb;
|
|
txdb.TxnBegin();
|
|
if (!txdb.WriteSyncCheckpoint(hashCheckpoint))
|
|
{
|
|
txdb.TxnAbort();
|
|
return error("WriteSyncCheckpoint(): failed to write to db sync checkpoint %s", hashCheckpoint.ToString().c_str());
|
|
}
|
|
if (!txdb.TxnCommit())
|
|
return error("WriteSyncCheckpoint(): failed to commit to db sync checkpoint %s", hashCheckpoint.ToString().c_str());
|
|
|
|
Checkpoints::hashSyncCheckpoint = hashCheckpoint;
|
|
return true;
|
|
}
|
|
|
|
bool AcceptPendingSyncCheckpoint()
|
|
{
|
|
LOCK(cs_hashSyncCheckpoint);
|
|
if (hashPendingCheckpoint != 0 && mapBlockIndex.count(hashPendingCheckpoint))
|
|
{
|
|
if (!ValidateSyncCheckpoint(hashPendingCheckpoint))
|
|
{
|
|
hashPendingCheckpoint = 0;
|
|
checkpointMessagePending.SetNull();
|
|
return false;
|
|
}
|
|
|
|
CTxDB txdb;
|
|
CBlockIndex* pindexCheckpoint = mapBlockIndex[hashPendingCheckpoint];
|
|
if (!pindexCheckpoint->IsInMainChain())
|
|
{
|
|
CBlock block;
|
|
if (!block.ReadFromDisk(pindexCheckpoint))
|
|
return error("AcceptPendingSyncCheckpoint: ReadFromDisk failed for sync checkpoint %s", hashPendingCheckpoint.ToString().c_str());
|
|
if (!block.SetBestChain(txdb, pindexCheckpoint))
|
|
{
|
|
hashInvalidCheckpoint = hashPendingCheckpoint;
|
|
return error("AcceptPendingSyncCheckpoint: SetBestChain failed for sync checkpoint %s", hashPendingCheckpoint.ToString().c_str());
|
|
}
|
|
}
|
|
|
|
if (!WriteSyncCheckpoint(hashPendingCheckpoint))
|
|
return error("AcceptPendingSyncCheckpoint(): failed to write sync checkpoint %s", hashPendingCheckpoint.ToString().c_str());
|
|
hashPendingCheckpoint = 0;
|
|
checkpointMessage = checkpointMessagePending;
|
|
checkpointMessagePending.SetNull();
|
|
printf("AcceptPendingSyncCheckpoint : sync-checkpoint at %s\n", hashSyncCheckpoint.ToString().c_str());
|
|
// relay the checkpoint
|
|
if (!checkpointMessage.IsNull())
|
|
{
|
|
BOOST_FOREACH(CNode* pnode, vNodes)
|
|
checkpointMessage.RelayTo(pnode);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Automatically select a suitable sync-checkpoint
|
|
uint256 AutoSelectSyncCheckpoint()
|
|
{
|
|
const CBlockIndex *pindex = pindexBest;
|
|
// Search backward for a block within max span and maturity window
|
|
while (pindex->pprev && (pindex->GetBlockTime() + CHECKPOINT_MAX_SPAN > pindexBest->GetBlockTime() || pindex->nHeight + 8 > pindexBest->nHeight))
|
|
pindex = pindex->pprev;
|
|
return pindex->GetBlockHash();
|
|
}
|
|
|
|
// Check against synchronized checkpoint
|
|
// Disabled: master key removed in V5, no new sync checkpoints possible.
|
|
// Always returns true to prevent stale DB-persisted checkpoints from blocking IBD.
|
|
bool CheckSync(const uint256& hashBlock, const CBlockIndex* pindexPrev)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool WantedByPendingSyncCheckpoint(uint256 hashBlock)
|
|
{
|
|
LOCK(cs_hashSyncCheckpoint);
|
|
if (hashPendingCheckpoint == 0)
|
|
return false;
|
|
if (hashBlock == hashPendingCheckpoint)
|
|
return true;
|
|
if (mapOrphanBlocks.count(hashPendingCheckpoint)
|
|
&& hashBlock == WantedByOrphan(mapOrphanBlocks[hashPendingCheckpoint]))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// triangles: reset synchronized checkpoint to last hardened checkpoint
|
|
bool ResetSyncCheckpoint()
|
|
{
|
|
LOCK(cs_hashSyncCheckpoint);
|
|
const uint256& hash = mapCheckpoints.rbegin()->second;
|
|
if (mapBlockIndex.count(hash) && !mapBlockIndex[hash]->IsInMainChain())
|
|
{
|
|
// checkpoint block accepted but not yet in main chain
|
|
printf("ResetSyncCheckpoint: SetBestChain to hardened checkpoint %s\n", hash.ToString().c_str());
|
|
CTxDB txdb;
|
|
CBlock block;
|
|
if (!block.ReadFromDisk(mapBlockIndex[hash]))
|
|
return error("ResetSyncCheckpoint: ReadFromDisk failed for hardened checkpoint %s", hash.ToString().c_str());
|
|
if (!block.SetBestChain(txdb, mapBlockIndex[hash]))
|
|
{
|
|
return error("ResetSyncCheckpoint: SetBestChain failed for hardened checkpoint %s", hash.ToString().c_str());
|
|
}
|
|
}
|
|
else if(!mapBlockIndex.count(hash))
|
|
{
|
|
// checkpoint block not yet accepted
|
|
hashPendingCheckpoint = hash;
|
|
checkpointMessagePending.SetNull();
|
|
printf("ResetSyncCheckpoint: pending for sync-checkpoint %s\n", hashPendingCheckpoint.ToString().c_str());
|
|
}
|
|
|
|
BOOST_REVERSE_FOREACH(const MapCheckpoints::value_type& i, mapCheckpoints)
|
|
{
|
|
const uint256& hash = i.second;
|
|
if (mapBlockIndex.count(hash) && mapBlockIndex[hash]->IsInMainChain())
|
|
{
|
|
if (!WriteSyncCheckpoint(hash))
|
|
return error("ResetSyncCheckpoint: failed to write sync checkpoint %s", hash.ToString().c_str());
|
|
printf("ResetSyncCheckpoint: sync-checkpoint reset to %s\n", hashSyncCheckpoint.ToString().c_str());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void AskForPendingSyncCheckpoint(CNode* pfrom)
|
|
{
|
|
LOCK(cs_hashSyncCheckpoint);
|
|
if (pfrom && hashPendingCheckpoint != 0 && (!mapBlockIndex.count(hashPendingCheckpoint)) && (!mapOrphanBlocks.count(hashPendingCheckpoint)))
|
|
pfrom->AskFor(CInv(MSG_BLOCK, hashPendingCheckpoint));
|
|
}
|
|
|
|
bool SetCheckpointPrivKey(std::string strPrivKey)
|
|
{
|
|
// Test signing a sync-checkpoint with genesis block
|
|
CSyncCheckpoint checkpoint;
|
|
checkpoint.hashCheckpoint = !fTestNet ? hashGenesisBlockOfficial : hashGenesisBlockTestNet;
|
|
CDataStream sMsg(SER_NETWORK, PROTOCOL_VERSION);
|
|
sMsg << (CUnsignedSyncCheckpoint)checkpoint;
|
|
checkpoint.vchMsg = std::vector<unsigned char>(sMsg.begin(), sMsg.end());
|
|
|
|
std::vector<unsigned char> vchPrivKey = ParseHex(strPrivKey);
|
|
CKey key;
|
|
key.SetPrivKey(CPrivKey(vchPrivKey.begin(), vchPrivKey.end())); // if key is not correct openssl may crash
|
|
if (!key.Sign(Hash(checkpoint.vchMsg.begin(), checkpoint.vchMsg.end()), checkpoint.vchSig))
|
|
return false;
|
|
|
|
// Test signing successful, proceed
|
|
CSyncCheckpoint::strMasterPrivKey = strPrivKey;
|
|
return true;
|
|
}
|
|
|
|
bool SendSyncCheckpoint(uint256 hashCheckpoint)
|
|
{
|
|
CSyncCheckpoint checkpoint;
|
|
checkpoint.hashCheckpoint = hashCheckpoint;
|
|
CDataStream sMsg(SER_NETWORK, PROTOCOL_VERSION);
|
|
sMsg << (CUnsignedSyncCheckpoint)checkpoint;
|
|
checkpoint.vchMsg = std::vector<unsigned char>(sMsg.begin(), sMsg.end());
|
|
|
|
if (CSyncCheckpoint::strMasterPrivKey.empty())
|
|
return error("SendSyncCheckpoint: Checkpoint master key unavailable.");
|
|
std::vector<unsigned char> vchPrivKey = ParseHex(CSyncCheckpoint::strMasterPrivKey);
|
|
CKey key;
|
|
key.SetPrivKey(CPrivKey(vchPrivKey.begin(), vchPrivKey.end())); // if key is not correct openssl may crash
|
|
if (!key.Sign(Hash(checkpoint.vchMsg.begin(), checkpoint.vchMsg.end()), checkpoint.vchSig))
|
|
return error("SendSyncCheckpoint: Unable to sign checkpoint, check private key?");
|
|
|
|
if(!checkpoint.ProcessSyncCheckpoint(NULL))
|
|
{
|
|
printf("WARNING: SendSyncCheckpoint: Failed to process checkpoint.\n");
|
|
return false;
|
|
}
|
|
|
|
// Relay checkpoint
|
|
{
|
|
LOCK(cs_vNodes);
|
|
BOOST_FOREACH(CNode* pnode, vNodes)
|
|
checkpoint.RelayTo(pnode);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Is the sync-checkpoint outside maturity window?
|
|
bool IsMatureSyncCheckpoint()
|
|
{
|
|
LOCK(cs_hashSyncCheckpoint);
|
|
if (!mapBlockIndex.count(hashSyncCheckpoint))
|
|
return true; // no valid sync checkpoint, treat as mature
|
|
const CBlockIndex* pindexSync = mapBlockIndex[hashSyncCheckpoint];
|
|
return (nBestHeight >= pindexSync->nHeight + nCoinbaseMaturity ||
|
|
pindexSync->GetBlockTime() + nStakeMinAge < GetAdjustedTime());
|
|
}
|
|
}
|
|
|
|
// triangles: sync-checkpoint master key (DISABLED for decentralization - v5 hard fork)
|
|
const std::string CSyncCheckpoint::strMasterPubKey = "";
|
|
|
|
std::string CSyncCheckpoint::strMasterPrivKey = "";
|
|
|
|
// triangles: verify signature of sync-checkpoint message
|
|
// Master key system disabled - checkpoint signatures are no longer required
|
|
bool CSyncCheckpoint::CheckSignature()
|
|
{
|
|
// Deserialize the checkpoint data without signature verification
|
|
CDataStream sMsg(vchMsg, SER_NETWORK, PROTOCOL_VERSION);
|
|
sMsg >> *(CUnsignedSyncCheckpoint*)this;
|
|
return true;
|
|
}
|
|
|
|
// triangles: process synchronized checkpoint
|
|
bool CSyncCheckpoint::ProcessSyncCheckpoint(CNode* pfrom)
|
|
{
|
|
if (!CheckSignature())
|
|
return false;
|
|
|
|
LOCK(Checkpoints::cs_hashSyncCheckpoint);
|
|
if (!mapBlockIndex.count(hashCheckpoint))
|
|
{
|
|
// We haven't received the checkpoint chain, keep the checkpoint as pending
|
|
Checkpoints::hashPendingCheckpoint = hashCheckpoint;
|
|
Checkpoints::checkpointMessagePending = *this;
|
|
printf("ProcessSyncCheckpoint: pending for sync-checkpoint %s\n", hashCheckpoint.ToString().c_str());
|
|
// Ask this guy to fill in what we're missing
|
|
if (pfrom)
|
|
{
|
|
pfrom->PushGetBlocks(pindexBest, hashCheckpoint);
|
|
// ask directly as well in case rejected earlier by duplicate
|
|
// proof-of-stake because getblocks may not get it this time
|
|
pfrom->AskFor(CInv(MSG_BLOCK, mapOrphanBlocks.count(hashCheckpoint)? WantedByOrphan(mapOrphanBlocks[hashCheckpoint]) : hashCheckpoint));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (!Checkpoints::ValidateSyncCheckpoint(hashCheckpoint))
|
|
return false;
|
|
|
|
CTxDB txdb;
|
|
CBlockIndex* pindexCheckpoint = mapBlockIndex[hashCheckpoint];
|
|
if (!pindexCheckpoint->IsInMainChain())
|
|
{
|
|
// checkpoint chain received but not yet main chain
|
|
CBlock block;
|
|
if (!block.ReadFromDisk(pindexCheckpoint))
|
|
return error("ProcessSyncCheckpoint: ReadFromDisk failed for sync checkpoint %s", hashCheckpoint.ToString().c_str());
|
|
if (!block.SetBestChain(txdb, pindexCheckpoint))
|
|
{
|
|
Checkpoints::hashInvalidCheckpoint = hashCheckpoint;
|
|
return error("ProcessSyncCheckpoint: SetBestChain failed for sync checkpoint %s", hashCheckpoint.ToString().c_str());
|
|
}
|
|
}
|
|
|
|
if (!Checkpoints::WriteSyncCheckpoint(hashCheckpoint))
|
|
return error("ProcessSyncCheckpoint(): failed to write sync checkpoint %s", hashCheckpoint.ToString().c_str());
|
|
Checkpoints::checkpointMessage = *this;
|
|
Checkpoints::hashPendingCheckpoint = 0;
|
|
Checkpoints::checkpointMessagePending.SetNull();
|
|
printf("ProcessSyncCheckpoint: sync-checkpoint at %s\n", hashCheckpoint.ToString().c_str());
|
|
return true;
|
|
}
|