inital commit for getSnapshot2 changes
This commit is contained in:
114
src/main.cpp
114
src/main.cpp
@@ -2967,6 +2967,33 @@ void DisconnectNotarisations(const CBlock &block)
|
||||
}
|
||||
}
|
||||
|
||||
int8_t GetAddressType(const CScript &scriptPubKey, CTxDestination &vDest, txnouttype &txType, vector<vector<unsigned char>> &vSols)
|
||||
{
|
||||
int8_t keyType = 0;
|
||||
// some non-standard types, like time lock coinbases, don't solve, but do extract
|
||||
if ( (Solver(scriptPubKey, txType, vSols) || ExtractDestination(scriptPubKey, vDest)) )
|
||||
{
|
||||
keyType = 1;
|
||||
if (vDest.which())
|
||||
{
|
||||
// if we failed to solve, and got a vDest, assume P2PKH or P2PK address returned
|
||||
CKeyID kid;
|
||||
if (CBitcoinAddress(vDest).GetKeyID(kid))
|
||||
{
|
||||
vSols.push_back(vector<unsigned char>(kid.begin(), kid.end()));
|
||||
}
|
||||
}
|
||||
else if (txType == TX_SCRIPTHASH)
|
||||
{
|
||||
keyType = 2;
|
||||
}
|
||||
else if (txType == TX_CRYPTOCONDITION )
|
||||
{
|
||||
keyType = 3;
|
||||
}
|
||||
}
|
||||
return keyType;
|
||||
}
|
||||
|
||||
bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& view, bool* pfClean)
|
||||
{
|
||||
@@ -3002,20 +3029,9 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex
|
||||
vector<vector<unsigned char>> vSols;
|
||||
CTxDestination vDest;
|
||||
txnouttype txType = TX_PUBKEYHASH;
|
||||
int keyType = 1;
|
||||
if ((Solver(out.scriptPubKey, txType, vSols) || ExtractDestination(out.scriptPubKey, vDest)) && txType != TX_MULTISIG) {
|
||||
if (vDest.which())
|
||||
{
|
||||
CKeyID kid;
|
||||
if (CBitcoinAddress(vDest).GetKeyID(kid))
|
||||
{
|
||||
vSols.push_back(vector<unsigned char>(kid.begin(), kid.end()));
|
||||
}
|
||||
}
|
||||
else if (txType == TX_SCRIPTHASH)
|
||||
{
|
||||
keyType = 2;
|
||||
}
|
||||
int keyType = GetAddressType(out.scriptPubKey, vDest, txType, vSols);
|
||||
if ( keyType != 0 )
|
||||
{
|
||||
for (auto addr : vSols)
|
||||
{
|
||||
uint160 addrHash = addr.size() == 20 ? uint160(addr) : Hash160(addr);
|
||||
@@ -3072,23 +3088,9 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex
|
||||
vector<vector<unsigned char>> vSols;
|
||||
CTxDestination vDest;
|
||||
txnouttype txType = TX_PUBKEYHASH;
|
||||
int keyType = 1;
|
||||
// some non-standard types, like time lock coinbases, don't solve, but do extract
|
||||
if ((Solver(prevout.scriptPubKey, txType, vSols) || ExtractDestination(prevout.scriptPubKey, vDest)))
|
||||
int keyType = GetAddressType(prevout.scriptPubKey, vDest, txType, vSols);
|
||||
if ( keyType != 0 )
|
||||
{
|
||||
// if we failed to solve, and got a vDest, assume P2PKH or P2PK address returned
|
||||
if (vDest.which())
|
||||
{
|
||||
CKeyID kid;
|
||||
if (CBitcoinAddress(vDest).GetKeyID(kid))
|
||||
{
|
||||
vSols.push_back(vector<unsigned char>(kid.begin(), kid.end()));
|
||||
}
|
||||
}
|
||||
else if (txType == TX_SCRIPTHASH)
|
||||
{
|
||||
keyType = 2;
|
||||
}
|
||||
for (auto addr : vSols)
|
||||
{
|
||||
uint160 addrHash = addr.size() == 20 ? uint160(addr) : Hash160(addr);
|
||||
@@ -3448,8 +3450,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
||||
|
||||
if (fAddressIndex || fSpentIndex)
|
||||
{
|
||||
for (size_t j = 0; j < tx.vin.size(); j++) {
|
||||
|
||||
for (size_t j = 0; j < tx.vin.size(); j++)
|
||||
{
|
||||
const CTxIn input = tx.vin[j];
|
||||
const CTxOut &prevout = view.GetOutputFor(tx.vin[j]);
|
||||
|
||||
@@ -3457,25 +3459,9 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
||||
CTxDestination vDest;
|
||||
txnouttype txType = TX_PUBKEYHASH;
|
||||
uint160 addrHash;
|
||||
int keyType = 0;
|
||||
// some non-standard types, like time lock coinbases, don't solve, but do extract
|
||||
if ((Solver(prevout.scriptPubKey, txType, vSols) || ExtractDestination(prevout.scriptPubKey, vDest)))
|
||||
int keyType = GetAddressType(prevout.scriptPubKey, vDest, txType, vSols);
|
||||
if ( keyType != 0 )
|
||||
{
|
||||
keyType = 1;
|
||||
|
||||
// if we failed to solve, and got a vDest, assume P2PKH or P2PK address returned
|
||||
if (vDest.which())
|
||||
{
|
||||
CKeyID kid;
|
||||
if (CBitcoinAddress(vDest).GetKeyID(kid))
|
||||
{
|
||||
vSols.push_back(vector<unsigned char>(kid.begin(), kid.end()));
|
||||
}
|
||||
}
|
||||
else if (txType == TX_SCRIPTHASH)
|
||||
{
|
||||
keyType = 2;
|
||||
}
|
||||
for (auto addr : vSols)
|
||||
{
|
||||
addrHash = addr.size() == 20 ? uint160(addr) : Hash160(addr);
|
||||
@@ -3485,12 +3471,12 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
||||
// remove address from unspent index
|
||||
addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(keyType, addrHash, input.prevout.hash, input.prevout.n), CAddressUnspentValue()));
|
||||
}
|
||||
}
|
||||
|
||||
if (fSpentIndex) {
|
||||
// add the spent index to determine the txid and input that spent an output
|
||||
// and to find the amount and address from an input
|
||||
spentIndex.push_back(make_pair(CSpentIndexKey(input.prevout.hash, input.prevout.n), CSpentIndexValue(txhash, j, pindex->GetHeight(), prevout.nValue, keyType, addrHash)));
|
||||
if (fSpentIndex) {
|
||||
// add the spent index to determine the txid and input that spent an output
|
||||
// and to find the amount and address from an input
|
||||
spentIndex.push_back(make_pair(CSpentIndexKey(input.prevout.hash, input.prevout.n), CSpentIndexValue(txhash, j, pindex->GetHeight(), prevout.nValue, keyType, addrHash)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3541,23 +3527,9 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
||||
vector<vector<unsigned char>> vSols;
|
||||
CTxDestination vDest;
|
||||
txnouttype txType = TX_PUBKEYHASH;
|
||||
int keyType = 1;
|
||||
// some non-standard types, like time lock coinbases, don't solve, but do extract
|
||||
if ((Solver(out.scriptPubKey, txType, vSols) || ExtractDestination(out.scriptPubKey, vDest)) && txType != TX_MULTISIG)
|
||||
int keyType = GetAddressType(out.scriptPubKey, vDest, txType, vSols);
|
||||
if ( keyType != 0 )
|
||||
{
|
||||
// if we failed to solve, and got a vDest, assume P2PKH or P2PK address returned
|
||||
if (vDest.which())
|
||||
{
|
||||
CKeyID kid;
|
||||
if (CBitcoinAddress(vDest).GetKeyID(kid))
|
||||
{
|
||||
vSols.push_back(vector<unsigned char>(kid.begin(), kid.end()));
|
||||
}
|
||||
}
|
||||
else if (txType == TX_SCRIPTHASH)
|
||||
{
|
||||
keyType = 2;
|
||||
}
|
||||
for (auto addr : vSols)
|
||||
{
|
||||
addrHash = addr.size() == 20 ? uint160(addr) : Hash160(addr);
|
||||
|
||||
@@ -853,8 +853,9 @@ bool getAddressFromIndex(const int &type, const uint160 &hash, std::string &addr
|
||||
address = CBitcoinAddress(CScriptID(hash)).ToString();
|
||||
} else if (type == 1) {
|
||||
address = CBitcoinAddress(CKeyID(hash)).ToString();
|
||||
}
|
||||
else {
|
||||
} else if (type == 3) {
|
||||
address = CBitcoinAddress(CKeyID(hash)).ToString();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
192
src/txdb.cpp
192
src/txdb.cpp
@@ -458,10 +458,10 @@ uint32_t komodo_segid32(char *coinaddr);
|
||||
{"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPVMY", 1} \
|
||||
};
|
||||
|
||||
int32_t CBlockTreeDB::Snapshot2(int64_t dustthreshold,int32_t top,std::vector <std::pair<CAmount, std::string>> &vaddr)
|
||||
int32_t CBlockTreeDB::Snapshot2(int64_t dustthreshold, int32_t top, bool fRPC ,std::vector <std::pair<CAmount, std::string>> &vaddr, UniValue &ret)
|
||||
{
|
||||
int64_t total = 0; int64_t totalAddresses = 0; std::string address;
|
||||
int64_t utxos = 0; int64_t ignoredAddresses = 0;
|
||||
int64_t utxos = 0; int64_t ignoredAddresses = 0, cryptoConditionsUTXOs = 0, cryptoConditionsTotals = 0;
|
||||
DECLARE_IGNORELIST
|
||||
boost::scoped_ptr<CDBIterator> iter(NewIterator());
|
||||
std::map <std::string, CAmount> addressAmounts;
|
||||
@@ -482,6 +482,12 @@ int32_t CBlockTreeDB::Snapshot2(int64_t dustthreshold,int32_t top,std::vector <s
|
||||
CAmount nValue;
|
||||
iter->GetValue(nValue);
|
||||
getAddressFromIndex(indexKey.type, indexKey.hashBytes, address);
|
||||
if ( indexKey.type == 3 )
|
||||
{
|
||||
cryptoConditionsUTXOs++;
|
||||
cryptoConditionsTotals += nValue;
|
||||
continue;
|
||||
}
|
||||
if ( nValue > dustthreshold )
|
||||
{
|
||||
std::map <std::string, int>::iterator ignored = ignoredMap.find(address);
|
||||
@@ -508,7 +514,7 @@ int32_t CBlockTreeDB::Snapshot2(int64_t dustthreshold,int32_t top,std::vector <s
|
||||
//fprintf(stderr,"{\"%s\", %.8f},\n",address.c_str(),(double)nValue/COIN);
|
||||
// total += nValue;
|
||||
utxos++;
|
||||
} else fprintf(stderr,"ignoring amount=0 UTXO for %s\n", address.c_str());
|
||||
} //else fprintf(stderr,"ignoring amount=0 UTXO for %s\n", address.c_str());
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
@@ -523,166 +529,70 @@ int32_t CBlockTreeDB::Snapshot2(int64_t dustthreshold,int32_t top,std::vector <s
|
||||
break;
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "total=%f, totalAddresses=%li, utxos=%li, ignored=%li\n", (double) total / COIN, totalAddresses, utxos, ignoredAddresses);
|
||||
//fprintf(stderr, "total=%f, totalAddresses=%li, utxos=%li, ignored=%li\n", (double) total / COIN, totalAddresses, utxos, ignoredAddresses);
|
||||
for (std::pair<std::string, CAmount> element : addressAmounts)
|
||||
vaddr.push_back( make_pair(element.second, element.first) );
|
||||
std::sort(vaddr.rbegin(), vaddr.rend());
|
||||
int topN = 0;
|
||||
for (std::vector<std::pair<CAmount, std::string>>::iterator it = vaddr.begin(); it!=vaddr.end(); ++it)
|
||||
{
|
||||
//obj.push_back( make_pair("addr", it->second.c_str() ) );
|
||||
//char amount[32];
|
||||
//sprintf(amount, "%.8f", (double) it->first / COIN);
|
||||
//obj.push_back( make_pair("amount", amount) );
|
||||
//obj.push_back( make_pair("segid",(int32_t)komodo_segid32((char *)it->second.c_str()) & 0x3f) );
|
||||
//addressesSorted.push_back(obj);
|
||||
total += it->first;
|
||||
topN++;
|
||||
// If requested, only show top N addresses in output JSON
|
||||
if ( top == topN )
|
||||
break;
|
||||
}
|
||||
// this is for the snapshot RPC, you can skip this by passing false to the 3rd arugment.
|
||||
// Still needs UniValue defined to use SnapShot2 though, can this be changed to just pass 0?
|
||||
// I tried to make it a pointer, and check if the pointer was 0, but UniValue class didnt like that.
|
||||
if ( fRPC )
|
||||
{
|
||||
// Total amount in this snapshot, which is less than circulating supply if top parameter is used
|
||||
ret.push_back(make_pair("total", (double) total / COIN ));
|
||||
// Average amount in each address of this snapshot
|
||||
ret.push_back(make_pair("average",(double) (total/COIN) / totalAddresses ));
|
||||
// Total number of utxos processed in this snaphot
|
||||
ret.push_back(make_pair("utxos", utxos));
|
||||
// Total number of addresses in this snaphot
|
||||
ret.push_back(make_pair("total_addresses", top ? top : totalAddresses ));
|
||||
// Total number of ignored addresses in this snaphot
|
||||
ret.push_back(make_pair("ignored_addresses", ignoredAddresses));
|
||||
// Total number of crypto condition utxos we skipped
|
||||
ret.push_back(make_pair("skipped_cc_utxos", cryptoConditionsUTXOs));
|
||||
// Total value of skipped crypto condition utxos
|
||||
ret.push_back(make_pair("cc_utxo_value", (double) cryptoConditionsTotals / COIN));
|
||||
// The snapshot finished at this block height
|
||||
ret.push_back(make_pair("ending_height", chainActive.Height()));
|
||||
}
|
||||
return(topN);
|
||||
}
|
||||
|
||||
UniValue CBlockTreeDB::Snapshot(int top)
|
||||
{
|
||||
int64_t total = 0; int64_t totalAddresses = 0; std::string address;
|
||||
int64_t utxos = 0; int64_t ignoredAddresses = 0;
|
||||
DECLARE_IGNORELIST
|
||||
boost::scoped_ptr<CDBIterator> iter(NewIterator());
|
||||
std::map <std::string, CAmount> addressAmounts;
|
||||
int topN = 0;
|
||||
std::vector <std::pair<CAmount, std::string>> vaddr;
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.push_back(Pair("start_time", (int) time(NULL)));
|
||||
|
||||
/* std::map <std::string,int> ignoredMap = {
|
||||
{"RReUxSs5hGE39ELU23DfydX8riUuzdrHAE", 1},
|
||||
{"RMUF3UDmzWFLSKV82iFbMaqzJpUnrWjcT4", 1},
|
||||
{"RA5imhVyJa7yHhggmBytWuDr923j2P1bxx", 1},
|
||||
{"RBM5LofZFodMeewUzoMWcxedm3L3hYRaWg", 1},
|
||||
{"RAdcko2d94TQUcJhtFHZZjMyWBKEVfgn4J", 1},
|
||||
{"RLzUaZ934k2EFCsAiVjrJqM8uU1vmMRFzk", 1},
|
||||
{"RMSZMWZXv4FhUgWhEo4R3AQXmRDJ6rsGyt", 1},
|
||||
{"RUDrX1v5toCsJMUgtvBmScKjwCB5NaR8py", 1},
|
||||
{"RMSZMWZXv4FhUgWhEo4R3AQXmRDJ6rsGyt", 1},
|
||||
{"RRvwmbkxR5YRzPGL5kMFHMe1AH33MeD8rN", 1},
|
||||
{"RQLQvSgpPAJNPgnpc8MrYsbBhep95nCS8L", 1},
|
||||
{"RK8JtBV78HdvEPvtV5ckeMPSTojZPzHUTe", 1},
|
||||
{"RHVs2KaCTGUMNv3cyWiG1jkEvZjigbCnD2", 1},
|
||||
{"RE3SVaDgdjkRPYA6TRobbthsfCmxQedVgF", 1},
|
||||
{"RW6S5Lw5ZCCvDyq4QV9vVy7jDHfnynr5mn", 1},
|
||||
{"RTkJwAYtdXXhVsS3JXBAJPnKaBfMDEswF8", 1},
|
||||
{"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPVMY", 1} //Burnaddress for null privkey
|
||||
};*/
|
||||
|
||||
int64_t startingHeight = chainActive.Height();
|
||||
//fprintf(stderr, "Starting snapshot at height %lli\n", startingHeight);
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev())
|
||||
{
|
||||
boost::this_thread::interruption_point();
|
||||
try
|
||||
{
|
||||
std::vector<unsigned char> slKey = std::vector<unsigned char>();
|
||||
pair<char, CAddressIndexIteratorKey> keyObj;
|
||||
iter->GetKey(keyObj);
|
||||
|
||||
char chType = keyObj.first;
|
||||
CAddressIndexIteratorKey indexKey = keyObj.second;
|
||||
|
||||
//fprintf(stderr, "chType=%d\n", chType);
|
||||
if (chType == DB_ADDRESSUNSPENTINDEX)
|
||||
{
|
||||
try {
|
||||
CAmount nValue;
|
||||
iter->GetValue(nValue);
|
||||
|
||||
getAddressFromIndex(indexKey.type, indexKey.hashBytes, address);
|
||||
|
||||
if (nValue > 0) {
|
||||
std::map <std::string, int>::iterator ignored = ignoredMap.find(address);
|
||||
if (ignored != ignoredMap.end()) {
|
||||
fprintf(stderr,"ignoring %s\n", address.c_str());
|
||||
ignoredAddresses++;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::map <std::string, CAmount>::iterator pos = addressAmounts.find(address);
|
||||
if (pos == addressAmounts.end()) {
|
||||
// insert new address + utxo amount
|
||||
//fprintf(stderr, "inserting new address %s with amount %li\n", address.c_str(), nValue);
|
||||
addressAmounts[address] = nValue;
|
||||
totalAddresses++;
|
||||
} else {
|
||||
// update unspent tally for this address
|
||||
//fprintf(stderr, "updating address %s with new utxo amount %li\n", address.c_str(), nValue);
|
||||
addressAmounts[address] += nValue;
|
||||
}
|
||||
//fprintf(stderr,"{\"%s\", %.8f},\n",address.c_str(),(double)nValue/COIN);
|
||||
// total += nValue;
|
||||
utxos++;
|
||||
} else {
|
||||
fprintf(stderr,"ignoring amount=0 UTXO for %s\n", address.c_str());
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
fprintf(stderr, "DONE %s: LevelDB addressindex exception! - %s\n", __func__, e.what());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
fprintf(stderr, "DONE reading index entries\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
UniValue addresses(UniValue::VARR);
|
||||
//fprintf(stderr, "total=%f, totalAddresses=%li, utxos=%li, ignored=%li\n", (double) total / COIN, totalAddresses, utxos, ignoredAddresses);
|
||||
|
||||
for (std::pair<std::string, CAmount> element : addressAmounts) {
|
||||
vaddr.push_back( make_pair(element.second, element.first) );
|
||||
}
|
||||
std::sort(vaddr.rbegin(), vaddr.rend());
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
UniValue addressesSorted(UniValue::VARR);
|
||||
int topN = 0;
|
||||
for (std::vector<std::pair<CAmount, std::string>>::iterator it = vaddr.begin(); it!=vaddr.end(); ++it)
|
||||
result.push_back(Pair("start_time", (int) time(NULL)));
|
||||
if ( Snapshot2(0,top,true,vaddr,result) != 0 )
|
||||
{
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.push_back( make_pair("addr", it->second.c_str() ) );
|
||||
char amount[32];
|
||||
sprintf(amount, "%.8f", (double) it->first / COIN);
|
||||
obj.push_back( make_pair("amount", amount) );
|
||||
obj.push_back( make_pair("segid",(int32_t)komodo_segid32((char *)it->second.c_str()) & 0x3f) );
|
||||
total += it->first;
|
||||
addressesSorted.push_back(obj);
|
||||
topN++;
|
||||
// If requested, only show top N addresses in output JSON
|
||||
if (top == topN)
|
||||
break;
|
||||
}
|
||||
|
||||
if (top)
|
||||
totalAddresses = top;
|
||||
|
||||
if (totalAddresses > 0) {
|
||||
// Array of all addreses with balances
|
||||
for (std::vector<std::pair<CAmount, std::string>>::iterator it = vaddr.begin(); it!=vaddr.end(); ++it)
|
||||
{
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.push_back( make_pair("addr", it->second.c_str() ) );
|
||||
char amount[32];
|
||||
sprintf(amount, "%.8f", (double) it->first / COIN);
|
||||
obj.push_back( make_pair("amount", amount) );
|
||||
obj.push_back( make_pair("segid",(int32_t)komodo_segid32((char *)it->second.c_str()) & 0x3f) );
|
||||
addressesSorted.push_back(obj);
|
||||
topN++;
|
||||
// If requested, only show top N addresses in output JSON
|
||||
if ( top == topN )
|
||||
break;
|
||||
}
|
||||
// Array of all addreses with balances
|
||||
result.push_back(make_pair("addresses", addressesSorted));
|
||||
// Total amount in this snapshot, which is less than circulating supply if top parameter is used
|
||||
result.push_back(make_pair("total", (double) total / COIN ));
|
||||
// Average amount in each address of this snapshot
|
||||
result.push_back(make_pair("average",(double) (total/COIN) / totalAddresses ));
|
||||
}
|
||||
// Total number of utxos processed in this snaphot
|
||||
result.push_back(make_pair("utxos", utxos));
|
||||
// Total number of addresses in this snaphot
|
||||
result.push_back(make_pair("total_addresses", totalAddresses));
|
||||
// Total number of ignored addresses in this snaphot
|
||||
result.push_back(make_pair("ignored_addresses", ignoredAddresses));
|
||||
// The snapshot began at this block height
|
||||
result.push_back(make_pair("start_height", startingHeight));
|
||||
// The snapshot finished at this block height
|
||||
result.push_back(make_pair("ending_height", chainActive.Height()));
|
||||
} else result.push_back(make_pair("error", "problem doing snapshot"));
|
||||
return(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ public:
|
||||
bool LoadBlockIndexGuts();
|
||||
bool blockOnchainActive(const uint256 &hash);
|
||||
UniValue Snapshot(int top);
|
||||
int32_t Snapshot2(int64_t dustthreshold,int32_t top,std::vector <std::pair<CAmount, std::string>> &vaddr);
|
||||
int32_t Snapshot2(int64_t dustthreshold, int32_t top, bool fRPC ,std::vector <std::pair<CAmount, std::string>> &vaddr, UniValue &ret);
|
||||
};
|
||||
|
||||
#endif // BITCOIN_TXDB_H
|
||||
|
||||
Reference in New Issue
Block a user