main: index unspent outputs by address
This commit is contained in:
parent
8597289d8b
commit
0b42ba227a
@ -169,6 +169,12 @@ class AddressIndexTest(BitcoinTestFramework):
|
||||
deltas = self.nodes[1].getaddressdeltas({"addresses": [address2], "start": 113, "end": 113})
|
||||
assert_equal(len(deltas), 1)
|
||||
|
||||
# Check that unspent outputs can be queried
|
||||
print "Testing utxos..."
|
||||
utxos = self.nodes[1].getaddressutxos({"addresses": [address2]})
|
||||
assert_equal(len(utxos), 2)
|
||||
assert_equal(utxos[0]["satoshis"], 5000000000)
|
||||
|
||||
# Check that indexes will be updated with a reorg
|
||||
print "Testing reorg..."
|
||||
|
||||
|
46
src/main.cpp
46
src/main.cpp
@ -1463,6 +1463,18 @@ bool GetAddressIndex(uint160 addressHash, int type,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetAddressUnspent(uint160 addressHash, int type,
|
||||
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > &unspentOutputs)
|
||||
{
|
||||
if (!fAddressIndex)
|
||||
return error("address index not enabled");
|
||||
|
||||
if (!pblocktree->ReadAddressUnspentIndex(addressHash, type, unspentOutputs))
|
||||
return error("unable to get txids for address");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Return transaction in tx, and if it was found inside a block, its hash is placed in hashBlock */
|
||||
bool GetTransaction(const uint256 &hash, CTransaction &txOut, const Consensus::Params& consensusParams, uint256 &hashBlock, bool fAllowSlow)
|
||||
{
|
||||
@ -2389,6 +2401,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
||||
vPos.reserve(block.vtx.size());
|
||||
blockundo.vtxundo.reserve(block.vtx.size() - 1);
|
||||
std::vector<std::pair<CAddressIndexKey, CAmount> > addressIndex;
|
||||
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > addressUnspentIndex;
|
||||
|
||||
for (unsigned int i = 0; i < block.vtx.size(); i++)
|
||||
{
|
||||
@ -2426,10 +2439,21 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
||||
const CTxOut &prevout = view.GetOutputFor(tx.vin[j]);
|
||||
if (prevout.scriptPubKey.IsPayToScriptHash()) {
|
||||
vector<unsigned char> hashBytes(prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22);
|
||||
|
||||
// record spending activity
|
||||
addressIndex.push_back(make_pair(CAddressIndexKey(2, uint160(hashBytes), pindex->nHeight, i, txhash, j, true), prevout.nValue * -1));
|
||||
|
||||
// remove address from unspent index
|
||||
addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(2, uint160(hashBytes), pindex->nHeight, i, txhash, j), CAddressUnspentValue()));
|
||||
} else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) {
|
||||
vector<unsigned char> hashBytes(prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23);
|
||||
|
||||
// record spending activity
|
||||
addressIndex.push_back(make_pair(CAddressIndexKey(1, uint160(hashBytes), pindex->nHeight, i, txhash, j, true), prevout.nValue * -1));
|
||||
|
||||
// remove address from unspent index
|
||||
addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, uint160(hashBytes), pindex->nHeight, i, txhash, j), CAddressUnspentValue()));
|
||||
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
@ -2463,10 +2487,22 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
||||
|
||||
if (out.scriptPubKey.IsPayToScriptHash()) {
|
||||
vector<unsigned char> hashBytes(out.scriptPubKey.begin()+2, out.scriptPubKey.begin()+22);
|
||||
|
||||
// record receiving activity
|
||||
addressIndex.push_back(make_pair(CAddressIndexKey(2, uint160(hashBytes), pindex->nHeight, i, txhash, k, false), out.nValue));
|
||||
|
||||
// record unspent output
|
||||
addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(2, uint160(hashBytes), pindex->nHeight, i, txhash, k), CAddressUnspentValue(out.nValue, out.scriptPubKey)));
|
||||
|
||||
} else if (out.scriptPubKey.IsPayToPublicKeyHash()) {
|
||||
vector<unsigned char> hashBytes(out.scriptPubKey.begin()+3, out.scriptPubKey.begin()+23);
|
||||
|
||||
// record receiving activity
|
||||
addressIndex.push_back(make_pair(CAddressIndexKey(1, uint160(hashBytes), pindex->nHeight, i, txhash, k, false), out.nValue));
|
||||
|
||||
// record unspent output
|
||||
addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, uint160(hashBytes), pindex->nHeight, i, txhash, k), CAddressUnspentValue(out.nValue, out.scriptPubKey)));
|
||||
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
@ -2524,9 +2560,15 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
||||
if (!pblocktree->WriteTxIndex(vPos))
|
||||
return AbortNode(state, "Failed to write transaction index");
|
||||
|
||||
if (fAddressIndex)
|
||||
if (!pblocktree->WriteAddressIndex(addressIndex))
|
||||
if (fAddressIndex) {
|
||||
if (!pblocktree->WriteAddressIndex(addressIndex)) {
|
||||
return AbortNode(state, "Failed to write address index");
|
||||
}
|
||||
|
||||
if (!pblocktree->UpdateAddressUnspentIndex(addressUnspentIndex)) {
|
||||
return AbortNode(state, "Failed to write address unspent index");
|
||||
}
|
||||
}
|
||||
|
||||
if (fTimestampIndex)
|
||||
if (!pblocktree->WriteTimestampIndex(CTimestampIndexKey(pindex->nTime, pindex->GetBlockHash())))
|
||||
|
91
src/main.h
91
src/main.h
@ -353,6 +353,93 @@ struct CTimestampIndexKey {
|
||||
}
|
||||
};
|
||||
|
||||
struct CAddressUnspentKey {
|
||||
unsigned int type;
|
||||
uint160 hashBytes;
|
||||
int blockHeight;
|
||||
unsigned int txindex;
|
||||
uint256 txhash;
|
||||
size_t index;
|
||||
|
||||
size_t GetSerializeSize(int nType, int nVersion) const {
|
||||
return 65;
|
||||
}
|
||||
template<typename Stream>
|
||||
void Serialize(Stream& s, int nType, int nVersion) const {
|
||||
ser_writedata8(s, type);
|
||||
hashBytes.Serialize(s, nType, nVersion);
|
||||
// Heights are stored big-endian for key sorting in LevelDB
|
||||
ser_writedata32be(s, blockHeight);
|
||||
ser_writedata32be(s, txindex);
|
||||
txhash.Serialize(s, nType, nVersion);
|
||||
ser_writedata32(s, index);
|
||||
}
|
||||
template<typename Stream>
|
||||
void Unserialize(Stream& s, int nType, int nVersion) {
|
||||
type = ser_readdata8(s);
|
||||
hashBytes.Unserialize(s, nType, nVersion);
|
||||
blockHeight = ser_readdata32be(s);
|
||||
txindex = ser_readdata32be(s);
|
||||
txhash.Unserialize(s, nType, nVersion);
|
||||
index = ser_readdata32(s);
|
||||
}
|
||||
|
||||
CAddressUnspentKey(unsigned int addressType, uint160 addressHash, int height, int blockindex,
|
||||
uint256 txid, size_t indexValue) {
|
||||
type = addressType;
|
||||
hashBytes = addressHash;
|
||||
blockHeight = height;
|
||||
txindex = blockindex;
|
||||
txhash = txid;
|
||||
index = indexValue;
|
||||
}
|
||||
|
||||
CAddressUnspentKey() {
|
||||
SetNull();
|
||||
}
|
||||
|
||||
void SetNull() {
|
||||
type = 0;
|
||||
hashBytes.SetNull();
|
||||
blockHeight = 0;
|
||||
txindex = 0;
|
||||
txhash.SetNull();
|
||||
index = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct CAddressUnspentValue {
|
||||
CAmount satoshis;
|
||||
CScript script;
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
|
||||
READWRITE(satoshis);
|
||||
READWRITE(*(CScriptBase*)(&script));
|
||||
}
|
||||
|
||||
CAddressUnspentValue(CAmount sats, CScript scriptPubKey) {
|
||||
satoshis = sats;
|
||||
script = scriptPubKey;
|
||||
}
|
||||
|
||||
CAddressUnspentValue() {
|
||||
SetNull();
|
||||
}
|
||||
|
||||
void SetNull() {
|
||||
satoshis = 0;
|
||||
script = CScript();
|
||||
}
|
||||
|
||||
bool IsNull() const {
|
||||
return (satoshis == 0);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct CAddressIndexKey {
|
||||
unsigned int type;
|
||||
uint160 hashBytes;
|
||||
@ -363,7 +450,7 @@ struct CAddressIndexKey {
|
||||
bool spending;
|
||||
|
||||
size_t GetSerializeSize(int nType, int nVersion) const {
|
||||
return 65;
|
||||
return 66;
|
||||
}
|
||||
template<typename Stream>
|
||||
void Serialize(Stream& s, int nType, int nVersion) const {
|
||||
@ -601,6 +688,8 @@ bool GetTimestampIndex(const unsigned int &high, const unsigned int &low, std::v
|
||||
bool GetAddressIndex(uint160 addressHash, int type,
|
||||
std::vector<std::pair<CAddressIndexKey, CAmount> > &addressIndex,
|
||||
int start = 0, int end = 0);
|
||||
bool GetAddressUnspent(uint160 addressHash, int type,
|
||||
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > &unspentOutputs);
|
||||
|
||||
/** Functions for disk access for blocks */
|
||||
bool WriteBlockToDisk(const CBlock& block, CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& messageStart);
|
||||
|
@ -435,6 +435,56 @@ bool getAddressesFromParams(const UniValue& params, std::vector<std::pair<uint16
|
||||
|
||||
}
|
||||
|
||||
UniValue getaddressutxos(const UniValue& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() != 1 || !params[0].isObject())
|
||||
throw runtime_error(
|
||||
"getaddressutxos\n"
|
||||
"\nReturns all unspent outputs for an address (requires addressindex to be enabled).\n"
|
||||
"\nResult\n"
|
||||
"[\n"
|
||||
" {\n"
|
||||
" \"address\" (string) The address base58check encoded\n"
|
||||
" \"txid\" (string) The output txid\n"
|
||||
" \"height\" (number) The block height\n"
|
||||
" \"outputIndex\" (number) The output index\n"
|
||||
" \"script\" (strin) The script hex encoded\n"
|
||||
" \"satoshis\" (number) The number of satoshis of the output\n"
|
||||
" }\n"
|
||||
"]\n"
|
||||
);
|
||||
|
||||
std::vector<std::pair<uint160, int> > addresses;
|
||||
|
||||
if (!getAddressesFromParams(params, addresses)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
|
||||
}
|
||||
|
||||
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > unspentOutputs;
|
||||
|
||||
for (std::vector<std::pair<uint160, int> >::iterator it = addresses.begin(); it != addresses.end(); it++) {
|
||||
if (!GetAddressUnspent((*it).first, (*it).second, unspentOutputs)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for address");
|
||||
}
|
||||
}
|
||||
|
||||
UniValue result(UniValue::VARR);
|
||||
|
||||
for (std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> >::const_iterator it=unspentOutputs.begin(); it!=unspentOutputs.end(); it++) {
|
||||
UniValue output(UniValue::VOBJ);
|
||||
output.push_back(Pair("addressType", (int)it->first.type));
|
||||
output.push_back(Pair("addressHash", it->first.hashBytes.GetHex()));
|
||||
output.push_back(Pair("txid", it->first.txhash.GetHex()));
|
||||
output.push_back(Pair("height", it->first.blockHeight));
|
||||
output.push_back(Pair("outputIndex", it->first.index));
|
||||
output.push_back(Pair("script", HexStr(it->second.script.begin(), it->second.script.end())));
|
||||
output.push_back(Pair("satoshis", it->second.satoshis));
|
||||
result.push_back(output);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
UniValue getaddressdeltas(const UniValue& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() != 1 || !params[0].isObject())
|
||||
|
@ -315,6 +315,7 @@ static const CRPCCommand vRPCCommands[] =
|
||||
#endif
|
||||
|
||||
/* Address index */
|
||||
{ "addressindex", "getaddressutxos", &getaddressutxos, false },
|
||||
{ "addressindex", "getaddressdeltas", &getaddressdeltas, false },
|
||||
{ "addressindex", "getaddresstxids", &getaddresstxids, false },
|
||||
{ "addressindex", "getaddressbalance", &getaddressbalance, false },
|
||||
|
@ -166,6 +166,7 @@ extern std::string HelpExampleRpc(const std::string& methodname, const std::stri
|
||||
extern void EnsureWalletIsUnlocked();
|
||||
|
||||
extern UniValue getconnectioncount(const UniValue& params, bool fHelp); // in rpcnet.cpp
|
||||
extern UniValue getaddressutxos(const UniValue& params, bool fHelp);
|
||||
extern UniValue getaddressdeltas(const UniValue& params, bool fHelp);
|
||||
extern UniValue getaddresstxids(const UniValue& params, bool fHelp);
|
||||
extern UniValue getaddressbalance(const UniValue& params, bool fHelp);
|
||||
|
39
src/txdb.cpp
39
src/txdb.cpp
@ -22,6 +22,7 @@ static const char DB_COINS = 'c';
|
||||
static const char DB_BLOCK_FILES = 'f';
|
||||
static const char DB_TXINDEX = 't';
|
||||
static const char DB_ADDRESSINDEX = 'a';
|
||||
static const char DB_ADDRESSUNSPENTINDEX = 'u';
|
||||
static const char DB_TIMESTAMPINDEX = 's';
|
||||
static const char DB_BLOCK_INDEX = 'b';
|
||||
|
||||
@ -165,6 +166,44 @@ bool CBlockTreeDB::WriteTxIndex(const std::vector<std::pair<uint256, CDiskTxPos>
|
||||
return WriteBatch(batch);
|
||||
}
|
||||
|
||||
bool CBlockTreeDB::UpdateAddressUnspentIndex(const std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue > >&vect) {
|
||||
CDBBatch batch(&GetObfuscateKey());
|
||||
for (std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> >::const_iterator it=vect.begin(); it!=vect.end(); it++) {
|
||||
if (it->second.IsNull()) {
|
||||
batch.Erase(make_pair(DB_ADDRESSUNSPENTINDEX, it->first));
|
||||
} else {
|
||||
batch.Write(make_pair(DB_ADDRESSUNSPENTINDEX, it->first), it->second);
|
||||
}
|
||||
}
|
||||
return WriteBatch(batch);
|
||||
}
|
||||
|
||||
bool CBlockTreeDB::ReadAddressUnspentIndex(uint160 addressHash, int type,
|
||||
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > &unspentOutputs) {
|
||||
|
||||
boost::scoped_ptr<CDBIterator> pcursor(NewIterator());
|
||||
|
||||
pcursor->Seek(make_pair(DB_ADDRESSUNSPENTINDEX, CAddressIndexIteratorKey(type, addressHash)));
|
||||
|
||||
while (pcursor->Valid()) {
|
||||
boost::this_thread::interruption_point();
|
||||
std::pair<char,CAddressUnspentKey> key;
|
||||
if (pcursor->GetKey(key) && key.first == DB_ADDRESSUNSPENTINDEX && key.second.hashBytes == addressHash) {
|
||||
CAddressUnspentValue nValue;
|
||||
if (pcursor->GetValue(nValue)) {
|
||||
unspentOutputs.push_back(make_pair(key.second, nValue));
|
||||
pcursor->Next();
|
||||
} else {
|
||||
return error("failed to get address unspent value");
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBlockTreeDB::WriteAddressIndex(const std::vector<std::pair<CAddressIndexKey, CAmount > >&vect) {
|
||||
CDBBatch batch(&GetObfuscateKey());
|
||||
for (std::vector<std::pair<CAddressIndexKey, CAmount> >::const_iterator it=vect.begin(); it!=vect.end(); it++)
|
||||
|
@ -17,6 +17,8 @@
|
||||
class CBlockFileInfo;
|
||||
class CBlockIndex;
|
||||
struct CDiskTxPos;
|
||||
struct CAddressUnspentKey;
|
||||
struct CAddressUnspentValue;
|
||||
struct CAddressIndexKey;
|
||||
struct CAddressIndexIteratorKey;
|
||||
struct CTimestampIndexKey;
|
||||
@ -61,6 +63,9 @@ public:
|
||||
bool ReadReindexing(bool &fReindex);
|
||||
bool ReadTxIndex(const uint256 &txid, CDiskTxPos &pos);
|
||||
bool WriteTxIndex(const std::vector<std::pair<uint256, CDiskTxPos> > &list);
|
||||
bool UpdateAddressUnspentIndex(const std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue > >&vect);
|
||||
bool ReadAddressUnspentIndex(uint160 addressHash, int type,
|
||||
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > &vect);
|
||||
bool WriteAddressIndex(const std::vector<std::pair<CAddressIndexKey, CAmount> > &vect);
|
||||
bool EraseAddressIndex(const std::vector<std::pair<CAddressIndexKey, CAmount> > &vect);
|
||||
bool ReadAddressIndex(uint160 addressHash, int type,
|
||||
|
Loading…
Reference in New Issue
Block a user