Merge #7558: [RPC] Add import/removeprunedfunds rpc call
f1bb13c
Added companion removeprunedfunds call. (instagibbs)7eb7029
Add importprunedfunds rpc call (instagibbs)
This commit is contained in:
commit
b35a591793
13 changed files with 351 additions and 23 deletions
|
@ -116,6 +116,7 @@ testScripts = [
|
||||||
'invalidtxrequest.py',
|
'invalidtxrequest.py',
|
||||||
'abandonconflict.py',
|
'abandonconflict.py',
|
||||||
'p2p-versionbits-warning.py',
|
'p2p-versionbits-warning.py',
|
||||||
|
'importprunedfunds.py',
|
||||||
]
|
]
|
||||||
testScriptsExt = [
|
testScriptsExt = [
|
||||||
'bip65-cltv.py',
|
'bip65-cltv.py',
|
||||||
|
@ -127,6 +128,7 @@ testScriptsExt = [
|
||||||
'getblocktemplate_proposals.py',
|
'getblocktemplate_proposals.py',
|
||||||
'txn_doublespend.py',
|
'txn_doublespend.py',
|
||||||
'txn_clone.py --mineblock',
|
'txn_clone.py --mineblock',
|
||||||
|
'pruning.py',
|
||||||
'forknotify.py',
|
'forknotify.py',
|
||||||
'invalidateblock.py',
|
'invalidateblock.py',
|
||||||
# 'rpcbind_test.py', #temporary, bug in libevent, see #6655
|
# 'rpcbind_test.py', #temporary, bug in libevent, see #6655
|
||||||
|
|
140
qa/rpc-tests/importprunedfunds.py
Executable file
140
qa/rpc-tests/importprunedfunds.py
Executable file
|
@ -0,0 +1,140 @@
|
||||||
|
#!/usr/bin/env python2
|
||||||
|
# Copyright (c) 2014-2016 The Bitcoin Core developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
|
from test_framework.util import *
|
||||||
|
import decimal
|
||||||
|
|
||||||
|
class ImportPrunedFundsTest(BitcoinTestFramework):
|
||||||
|
|
||||||
|
def setup_chain(self):
|
||||||
|
print("Initializing test directory "+self.options.tmpdir)
|
||||||
|
initialize_chain_clean(self.options.tmpdir, 4)
|
||||||
|
|
||||||
|
def setup_network(self, split=False):
|
||||||
|
self.nodes = start_nodes(2, self.options.tmpdir)
|
||||||
|
connect_nodes_bi(self.nodes,0,1)
|
||||||
|
self.is_network_split=False
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
def run_test (self):
|
||||||
|
import time
|
||||||
|
begintime = int(time.time())
|
||||||
|
|
||||||
|
print "Mining blocks..."
|
||||||
|
self.nodes[0].generate(101)
|
||||||
|
|
||||||
|
# sync
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
# address
|
||||||
|
address1 = self.nodes[0].getnewaddress()
|
||||||
|
# pubkey
|
||||||
|
address2 = self.nodes[0].getnewaddress()
|
||||||
|
address2_pubkey = self.nodes[0].validateaddress(address2)['pubkey'] # Using pubkey
|
||||||
|
# privkey
|
||||||
|
address3 = self.nodes[0].getnewaddress()
|
||||||
|
address3_privkey = self.nodes[0].dumpprivkey(address3) # Using privkey
|
||||||
|
|
||||||
|
#Check only one address
|
||||||
|
address_info = self.nodes[0].validateaddress(address1)
|
||||||
|
assert_equal(address_info['ismine'], True)
|
||||||
|
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
#Node 1 sync test
|
||||||
|
assert_equal(self.nodes[1].getblockcount(),101)
|
||||||
|
|
||||||
|
#Address Test - before import
|
||||||
|
address_info = self.nodes[1].validateaddress(address1)
|
||||||
|
assert_equal(address_info['iswatchonly'], False)
|
||||||
|
assert_equal(address_info['ismine'], False)
|
||||||
|
|
||||||
|
address_info = self.nodes[1].validateaddress(address2)
|
||||||
|
assert_equal(address_info['iswatchonly'], False)
|
||||||
|
assert_equal(address_info['ismine'], False)
|
||||||
|
|
||||||
|
address_info = self.nodes[1].validateaddress(address3)
|
||||||
|
assert_equal(address_info['iswatchonly'], False)
|
||||||
|
assert_equal(address_info['ismine'], False)
|
||||||
|
|
||||||
|
#Send funds to self
|
||||||
|
txnid1 = self.nodes[0].sendtoaddress(address1, 0.1)
|
||||||
|
self.nodes[0].generate(1)
|
||||||
|
rawtxn1 = self.nodes[0].gettransaction(txnid1)['hex']
|
||||||
|
proof1 = self.nodes[0].gettxoutproof([txnid1])
|
||||||
|
|
||||||
|
txnid2 = self.nodes[0].sendtoaddress(address2, 0.05)
|
||||||
|
self.nodes[0].generate(1)
|
||||||
|
rawtxn2 = self.nodes[0].gettransaction(txnid2)['hex']
|
||||||
|
proof2 = self.nodes[0].gettxoutproof([txnid2])
|
||||||
|
|
||||||
|
|
||||||
|
txnid3 = self.nodes[0].sendtoaddress(address3, 0.025)
|
||||||
|
self.nodes[0].generate(1)
|
||||||
|
rawtxn3 = self.nodes[0].gettransaction(txnid3)['hex']
|
||||||
|
proof3 = self.nodes[0].gettxoutproof([txnid3])
|
||||||
|
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
#Import with no affiliated address
|
||||||
|
try:
|
||||||
|
result1 = self.nodes[1].importprunedfunds(rawtxn1, proof1, "")
|
||||||
|
except JSONRPCException,e:
|
||||||
|
errorString = e.error['message']
|
||||||
|
|
||||||
|
assert('No addresses' in errorString)
|
||||||
|
|
||||||
|
balance1 = self.nodes[1].getbalance("", 0, True)
|
||||||
|
assert_equal(balance1, Decimal(0))
|
||||||
|
|
||||||
|
#Import with affiliated address with no rescan
|
||||||
|
self.nodes[1].importaddress(address2, "", False)
|
||||||
|
result2 = self.nodes[1].importprunedfunds(rawtxn2, proof2, "")
|
||||||
|
balance2 = Decimal(self.nodes[1].getbalance("", 0, True))
|
||||||
|
assert_equal(balance2, Decimal('0.05'))
|
||||||
|
|
||||||
|
#Import with private key with no rescan
|
||||||
|
self.nodes[1].importprivkey(address3_privkey, "", False)
|
||||||
|
result3 = self.nodes[1].importprunedfunds(rawtxn3, proof3, "")
|
||||||
|
balance3 = Decimal(self.nodes[1].getbalance("", 0, False))
|
||||||
|
assert_equal(balance3, Decimal('0.025'))
|
||||||
|
balance3 = Decimal(self.nodes[1].getbalance("", 0, True))
|
||||||
|
assert_equal(balance3, Decimal('0.075'))
|
||||||
|
|
||||||
|
#Addresses Test - after import
|
||||||
|
address_info = self.nodes[1].validateaddress(address1)
|
||||||
|
assert_equal(address_info['iswatchonly'], False)
|
||||||
|
assert_equal(address_info['ismine'], False)
|
||||||
|
address_info = self.nodes[1].validateaddress(address2)
|
||||||
|
assert_equal(address_info['iswatchonly'], True)
|
||||||
|
assert_equal(address_info['ismine'], False)
|
||||||
|
address_info = self.nodes[1].validateaddress(address3)
|
||||||
|
assert_equal(address_info['iswatchonly'], False)
|
||||||
|
assert_equal(address_info['ismine'], True)
|
||||||
|
|
||||||
|
#Remove transactions
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.nodes[1].removeprunedfunds(txnid1)
|
||||||
|
except JSONRPCException,e:
|
||||||
|
errorString = e.error['message']
|
||||||
|
|
||||||
|
assert('does not exist' in errorString)
|
||||||
|
|
||||||
|
balance1 = Decimal(self.nodes[1].getbalance("", 0, True))
|
||||||
|
assert_equal(balance1, Decimal('0.075'))
|
||||||
|
|
||||||
|
|
||||||
|
self.nodes[1].removeprunedfunds(txnid2)
|
||||||
|
balance2 = Decimal(self.nodes[1].getbalance("", 0, True))
|
||||||
|
assert_equal(balance2, Decimal('0.025'))
|
||||||
|
|
||||||
|
self.nodes[1].removeprunedfunds(txnid3)
|
||||||
|
balance3 = Decimal(self.nodes[1].getbalance("", 0, True))
|
||||||
|
assert_equal(balance3, Decimal('0.0'))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
ImportPrunedFundsTest ().main ()
|
|
@ -95,7 +95,7 @@ void CPartialMerkleTree::TraverseAndBuild(int height, unsigned int pos, const st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 CPartialMerkleTree::TraverseAndExtract(int height, unsigned int pos, unsigned int &nBitsUsed, unsigned int &nHashUsed, std::vector<uint256> &vMatch) {
|
uint256 CPartialMerkleTree::TraverseAndExtract(int height, unsigned int pos, unsigned int &nBitsUsed, unsigned int &nHashUsed, std::vector<uint256> &vMatch, std::vector<unsigned int> &vnIndex) {
|
||||||
if (nBitsUsed >= vBits.size()) {
|
if (nBitsUsed >= vBits.size()) {
|
||||||
// overflowed the bits array - failure
|
// overflowed the bits array - failure
|
||||||
fBad = true;
|
fBad = true;
|
||||||
|
@ -110,14 +110,16 @@ uint256 CPartialMerkleTree::TraverseAndExtract(int height, unsigned int pos, uns
|
||||||
return uint256();
|
return uint256();
|
||||||
}
|
}
|
||||||
const uint256 &hash = vHash[nHashUsed++];
|
const uint256 &hash = vHash[nHashUsed++];
|
||||||
if (height==0 && fParentOfMatch) // in case of height 0, we have a matched txid
|
if (height==0 && fParentOfMatch) { // in case of height 0, we have a matched txid
|
||||||
vMatch.push_back(hash);
|
vMatch.push_back(hash);
|
||||||
|
vnIndex.push_back(pos);
|
||||||
|
}
|
||||||
return hash;
|
return hash;
|
||||||
} else {
|
} else {
|
||||||
// otherwise, descend into the subtrees to extract matched txids and hashes
|
// otherwise, descend into the subtrees to extract matched txids and hashes
|
||||||
uint256 left = TraverseAndExtract(height-1, pos*2, nBitsUsed, nHashUsed, vMatch), right;
|
uint256 left = TraverseAndExtract(height-1, pos*2, nBitsUsed, nHashUsed, vMatch, vnIndex), right;
|
||||||
if (pos*2+1 < CalcTreeWidth(height-1)) {
|
if (pos*2+1 < CalcTreeWidth(height-1)) {
|
||||||
right = TraverseAndExtract(height-1, pos*2+1, nBitsUsed, nHashUsed, vMatch);
|
right = TraverseAndExtract(height-1, pos*2+1, nBitsUsed, nHashUsed, vMatch, vnIndex);
|
||||||
if (right == left) {
|
if (right == left) {
|
||||||
// The left and right branches should never be identical, as the transaction
|
// The left and right branches should never be identical, as the transaction
|
||||||
// hashes covered by them must each be unique.
|
// hashes covered by them must each be unique.
|
||||||
|
@ -147,7 +149,7 @@ CPartialMerkleTree::CPartialMerkleTree(const std::vector<uint256> &vTxid, const
|
||||||
|
|
||||||
CPartialMerkleTree::CPartialMerkleTree() : nTransactions(0), fBad(true) {}
|
CPartialMerkleTree::CPartialMerkleTree() : nTransactions(0), fBad(true) {}
|
||||||
|
|
||||||
uint256 CPartialMerkleTree::ExtractMatches(std::vector<uint256> &vMatch) {
|
uint256 CPartialMerkleTree::ExtractMatches(std::vector<uint256> &vMatch, std::vector<unsigned int> &vnIndex) {
|
||||||
vMatch.clear();
|
vMatch.clear();
|
||||||
// An empty set will not work
|
// An empty set will not work
|
||||||
if (nTransactions == 0)
|
if (nTransactions == 0)
|
||||||
|
@ -167,7 +169,7 @@ uint256 CPartialMerkleTree::ExtractMatches(std::vector<uint256> &vMatch) {
|
||||||
nHeight++;
|
nHeight++;
|
||||||
// traverse the partial tree
|
// traverse the partial tree
|
||||||
unsigned int nBitsUsed = 0, nHashUsed = 0;
|
unsigned int nBitsUsed = 0, nHashUsed = 0;
|
||||||
uint256 hashMerkleRoot = TraverseAndExtract(nHeight, 0, nBitsUsed, nHashUsed, vMatch);
|
uint256 hashMerkleRoot = TraverseAndExtract(nHeight, 0, nBitsUsed, nHashUsed, vMatch, vnIndex);
|
||||||
// verify that no problems occurred during the tree traversal
|
// verify that no problems occurred during the tree traversal
|
||||||
if (fBad)
|
if (fBad)
|
||||||
return uint256();
|
return uint256();
|
||||||
|
|
|
@ -75,9 +75,9 @@ protected:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* recursive function that traverses tree nodes, consuming the bits and hashes produced by TraverseAndBuild.
|
* recursive function that traverses tree nodes, consuming the bits and hashes produced by TraverseAndBuild.
|
||||||
* it returns the hash of the respective node.
|
* it returns the hash of the respective node and its respective index.
|
||||||
*/
|
*/
|
||||||
uint256 TraverseAndExtract(int height, unsigned int pos, unsigned int &nBitsUsed, unsigned int &nHashUsed, std::vector<uint256> &vMatch);
|
uint256 TraverseAndExtract(int height, unsigned int pos, unsigned int &nBitsUsed, unsigned int &nHashUsed, std::vector<uint256> &vMatch, std::vector<unsigned int> &vnIndex);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -110,10 +110,11 @@ public:
|
||||||
CPartialMerkleTree();
|
CPartialMerkleTree();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* extract the matching txid's represented by this partial merkle tree.
|
* extract the matching txid's represented by this partial merkle tree
|
||||||
|
* and their respective indices within the partial tree.
|
||||||
* returns the merkle root, or 0 in case of failure
|
* returns the merkle root, or 0 in case of failure
|
||||||
*/
|
*/
|
||||||
uint256 ExtractMatches(std::vector<uint256> &vMatch);
|
uint256 ExtractMatches(std::vector<uint256> &vMatch, std::vector<unsigned int> &vnIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -303,7 +303,8 @@ UniValue verifytxoutproof(const UniValue& params, bool fHelp)
|
||||||
UniValue res(UniValue::VARR);
|
UniValue res(UniValue::VARR);
|
||||||
|
|
||||||
vector<uint256> vMatch;
|
vector<uint256> vMatch;
|
||||||
if (merkleBlock.txn.ExtractMatches(vMatch) != merkleBlock.header.hashMerkleRoot)
|
vector<unsigned int> vIndex;
|
||||||
|
if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot)
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
LOCK(cs_main);
|
LOCK(cs_main);
|
||||||
|
|
|
@ -204,7 +204,8 @@ BOOST_AUTO_TEST_CASE(merkle_block_1)
|
||||||
BOOST_CHECK(merkleBlock.vMatchedTxn[0].first == 8);
|
BOOST_CHECK(merkleBlock.vMatchedTxn[0].first == 8);
|
||||||
|
|
||||||
vector<uint256> vMatched;
|
vector<uint256> vMatched;
|
||||||
BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched) == block.hashMerkleRoot);
|
vector<unsigned int> vIndex;
|
||||||
|
BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched, vIndex) == block.hashMerkleRoot);
|
||||||
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
|
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
|
||||||
for (unsigned int i = 0; i < vMatched.size(); i++)
|
for (unsigned int i = 0; i < vMatched.size(); i++)
|
||||||
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
|
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
|
||||||
|
@ -221,7 +222,7 @@ BOOST_AUTO_TEST_CASE(merkle_block_1)
|
||||||
BOOST_CHECK(merkleBlock.vMatchedTxn[0].second == uint256S("0xdd1fd2a6fc16404faf339881a90adbde7f4f728691ac62e8f168809cdfae1053"));
|
BOOST_CHECK(merkleBlock.vMatchedTxn[0].second == uint256S("0xdd1fd2a6fc16404faf339881a90adbde7f4f728691ac62e8f168809cdfae1053"));
|
||||||
BOOST_CHECK(merkleBlock.vMatchedTxn[0].first == 7);
|
BOOST_CHECK(merkleBlock.vMatchedTxn[0].first == 7);
|
||||||
|
|
||||||
BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched) == block.hashMerkleRoot);
|
BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched, vIndex) == block.hashMerkleRoot);
|
||||||
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
|
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
|
||||||
for (unsigned int i = 0; i < vMatched.size(); i++)
|
for (unsigned int i = 0; i < vMatched.size(); i++)
|
||||||
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
|
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
|
||||||
|
@ -249,7 +250,8 @@ BOOST_AUTO_TEST_CASE(merkle_block_2)
|
||||||
BOOST_CHECK(merkleBlock.vMatchedTxn[0].first == 0);
|
BOOST_CHECK(merkleBlock.vMatchedTxn[0].first == 0);
|
||||||
|
|
||||||
vector<uint256> vMatched;
|
vector<uint256> vMatched;
|
||||||
BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched) == block.hashMerkleRoot);
|
vector<unsigned int> vIndex;
|
||||||
|
BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched, vIndex) == block.hashMerkleRoot);
|
||||||
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
|
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
|
||||||
for (unsigned int i = 0; i < vMatched.size(); i++)
|
for (unsigned int i = 0; i < vMatched.size(); i++)
|
||||||
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
|
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
|
||||||
|
@ -275,7 +277,7 @@ BOOST_AUTO_TEST_CASE(merkle_block_2)
|
||||||
BOOST_CHECK(merkleBlock.vMatchedTxn[3].second == uint256S("0x3c1d7e82342158e4109df2e0b6348b6e84e403d8b4046d7007663ace63cddb23"));
|
BOOST_CHECK(merkleBlock.vMatchedTxn[3].second == uint256S("0x3c1d7e82342158e4109df2e0b6348b6e84e403d8b4046d7007663ace63cddb23"));
|
||||||
BOOST_CHECK(merkleBlock.vMatchedTxn[3].first == 3);
|
BOOST_CHECK(merkleBlock.vMatchedTxn[3].first == 3);
|
||||||
|
|
||||||
BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched) == block.hashMerkleRoot);
|
BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched, vIndex) == block.hashMerkleRoot);
|
||||||
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
|
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
|
||||||
for (unsigned int i = 0; i < vMatched.size(); i++)
|
for (unsigned int i = 0; i < vMatched.size(); i++)
|
||||||
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
|
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
|
||||||
|
@ -303,7 +305,8 @@ BOOST_AUTO_TEST_CASE(merkle_block_2_with_update_none)
|
||||||
BOOST_CHECK(merkleBlock.vMatchedTxn[0].first == 0);
|
BOOST_CHECK(merkleBlock.vMatchedTxn[0].first == 0);
|
||||||
|
|
||||||
vector<uint256> vMatched;
|
vector<uint256> vMatched;
|
||||||
BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched) == block.hashMerkleRoot);
|
vector<unsigned int> vIndex;
|
||||||
|
BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched, vIndex) == block.hashMerkleRoot);
|
||||||
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
|
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
|
||||||
for (unsigned int i = 0; i < vMatched.size(); i++)
|
for (unsigned int i = 0; i < vMatched.size(); i++)
|
||||||
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
|
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
|
||||||
|
@ -326,7 +329,7 @@ BOOST_AUTO_TEST_CASE(merkle_block_2_with_update_none)
|
||||||
BOOST_CHECK(merkleBlock.vMatchedTxn[2].second == uint256S("0x3c1d7e82342158e4109df2e0b6348b6e84e403d8b4046d7007663ace63cddb23"));
|
BOOST_CHECK(merkleBlock.vMatchedTxn[2].second == uint256S("0x3c1d7e82342158e4109df2e0b6348b6e84e403d8b4046d7007663ace63cddb23"));
|
||||||
BOOST_CHECK(merkleBlock.vMatchedTxn[2].first == 3);
|
BOOST_CHECK(merkleBlock.vMatchedTxn[2].first == 3);
|
||||||
|
|
||||||
BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched) == block.hashMerkleRoot);
|
BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched, vIndex) == block.hashMerkleRoot);
|
||||||
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
|
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
|
||||||
for (unsigned int i = 0; i < vMatched.size(); i++)
|
for (unsigned int i = 0; i < vMatched.size(); i++)
|
||||||
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
|
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
|
||||||
|
@ -353,7 +356,8 @@ BOOST_AUTO_TEST_CASE(merkle_block_3_and_serialize)
|
||||||
BOOST_CHECK(merkleBlock.vMatchedTxn[0].first == 0);
|
BOOST_CHECK(merkleBlock.vMatchedTxn[0].first == 0);
|
||||||
|
|
||||||
vector<uint256> vMatched;
|
vector<uint256> vMatched;
|
||||||
BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched) == block.hashMerkleRoot);
|
vector<unsigned int> vIndex;
|
||||||
|
BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched, vIndex) == block.hashMerkleRoot);
|
||||||
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
|
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
|
||||||
for (unsigned int i = 0; i < vMatched.size(); i++)
|
for (unsigned int i = 0; i < vMatched.size(); i++)
|
||||||
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
|
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
|
||||||
|
@ -392,7 +396,8 @@ BOOST_AUTO_TEST_CASE(merkle_block_4)
|
||||||
BOOST_CHECK(merkleBlock.vMatchedTxn[0].first == 6);
|
BOOST_CHECK(merkleBlock.vMatchedTxn[0].first == 6);
|
||||||
|
|
||||||
vector<uint256> vMatched;
|
vector<uint256> vMatched;
|
||||||
BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched) == block.hashMerkleRoot);
|
vector<unsigned int> vIndex;
|
||||||
|
BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched, vIndex) == block.hashMerkleRoot);
|
||||||
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
|
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
|
||||||
for (unsigned int i = 0; i < vMatched.size(); i++)
|
for (unsigned int i = 0; i < vMatched.size(); i++)
|
||||||
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
|
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
|
||||||
|
@ -409,7 +414,7 @@ BOOST_AUTO_TEST_CASE(merkle_block_4)
|
||||||
|
|
||||||
BOOST_CHECK(merkleBlock.vMatchedTxn[1] == pair);
|
BOOST_CHECK(merkleBlock.vMatchedTxn[1] == pair);
|
||||||
|
|
||||||
BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched) == block.hashMerkleRoot);
|
BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched, vIndex) == block.hashMerkleRoot);
|
||||||
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
|
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
|
||||||
for (unsigned int i = 0; i < vMatched.size(); i++)
|
for (unsigned int i = 0; i < vMatched.size(); i++)
|
||||||
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
|
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
|
||||||
|
|
|
@ -88,7 +88,8 @@ BOOST_AUTO_TEST_CASE(pmt_test1)
|
||||||
|
|
||||||
// extract merkle root and matched txids from copy
|
// extract merkle root and matched txids from copy
|
||||||
std::vector<uint256> vMatchTxid2;
|
std::vector<uint256> vMatchTxid2;
|
||||||
uint256 merkleRoot2 = pmt2.ExtractMatches(vMatchTxid2);
|
std::vector<unsigned int> vIndex;
|
||||||
|
uint256 merkleRoot2 = pmt2.ExtractMatches(vMatchTxid2, vIndex);
|
||||||
|
|
||||||
// check that it has the same merkle root as the original, and a valid one
|
// check that it has the same merkle root as the original, and a valid one
|
||||||
BOOST_CHECK(merkleRoot1 == merkleRoot2);
|
BOOST_CHECK(merkleRoot1 == merkleRoot2);
|
||||||
|
@ -102,7 +103,7 @@ BOOST_AUTO_TEST_CASE(pmt_test1)
|
||||||
CPartialMerkleTreeTester pmt3(pmt2);
|
CPartialMerkleTreeTester pmt3(pmt2);
|
||||||
pmt3.Damage();
|
pmt3.Damage();
|
||||||
std::vector<uint256> vMatchTxid3;
|
std::vector<uint256> vMatchTxid3;
|
||||||
uint256 merkleRoot3 = pmt3.ExtractMatches(vMatchTxid3);
|
uint256 merkleRoot3 = pmt3.ExtractMatches(vMatchTxid3, vIndex);
|
||||||
BOOST_CHECK(merkleRoot3 != merkleRoot1);
|
BOOST_CHECK(merkleRoot3 != merkleRoot1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +123,8 @@ BOOST_AUTO_TEST_CASE(pmt_malleability)
|
||||||
|
|
||||||
CPartialMerkleTree tree(vTxid, vMatch);
|
CPartialMerkleTree tree(vTxid, vMatch);
|
||||||
std::vector<uint256> vTxid2;
|
std::vector<uint256> vTxid2;
|
||||||
BOOST_CHECK(tree.ExtractMatches(vTxid).IsNull());
|
std::vector<unsigned int> vIndex;
|
||||||
|
BOOST_CHECK(tree.ExtractMatches(vTxid, vIndex).IsNull());
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "utiltime.h"
|
#include "utiltime.h"
|
||||||
#include "wallet.h"
|
#include "wallet.h"
|
||||||
|
#include "merkleblock.h"
|
||||||
|
#include "core_io.h"
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
@ -243,6 +245,109 @@ UniValue importaddress(const UniValue& params, bool fHelp)
|
||||||
return NullUniValue;
|
return NullUniValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UniValue importprunedfunds(const UniValue& params, bool fHelp)
|
||||||
|
{
|
||||||
|
if (!EnsureWalletIsAvailable(fHelp))
|
||||||
|
return NullUniValue;
|
||||||
|
|
||||||
|
if (fHelp || params.size() < 2 || params.size() > 3)
|
||||||
|
throw runtime_error(
|
||||||
|
"importprunedfunds\n"
|
||||||
|
"\nImports funds without rescan. Corresponding address or script must previously be included in wallet. Aimed towards pruned wallets. The end-user is responsible to import additional transactions that subsequently spend the imported outputs or rescan after the point in the blockchain the transaction is included.\n"
|
||||||
|
"\nArguments:\n"
|
||||||
|
"1. \"rawtransaction\" (string, required) A raw transaction in hex funding an already-existing address in wallet\n"
|
||||||
|
"2. \"txoutproof\" (string, required) The hex output from gettxoutproof that contains the transaction\n"
|
||||||
|
"3. \"label\" (string, optional) An optional label\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
CTransaction tx;
|
||||||
|
if (!DecodeHexTx(tx, params[0].get_str()))
|
||||||
|
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
|
||||||
|
uint256 hashTx = tx.GetHash();
|
||||||
|
CWalletTx wtx(pwalletMain,tx);
|
||||||
|
|
||||||
|
CDataStream ssMB(ParseHexV(params[1], "proof"), SER_NETWORK, PROTOCOL_VERSION);
|
||||||
|
CMerkleBlock merkleBlock;
|
||||||
|
ssMB >> merkleBlock;
|
||||||
|
|
||||||
|
string strLabel = "";
|
||||||
|
if (params.size() == 3)
|
||||||
|
strLabel = params[2].get_str();
|
||||||
|
|
||||||
|
//Search partial merkle tree in proof for our transaction and index in valid block
|
||||||
|
vector<uint256> vMatch;
|
||||||
|
vector<unsigned int> vIndex;
|
||||||
|
unsigned int txnIndex = 0;
|
||||||
|
if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) == merkleBlock.header.hashMerkleRoot) {
|
||||||
|
|
||||||
|
LOCK(cs_main);
|
||||||
|
|
||||||
|
if (!mapBlockIndex.count(merkleBlock.header.GetHash()) || !chainActive.Contains(mapBlockIndex[merkleBlock.header.GetHash()]))
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
|
||||||
|
|
||||||
|
vector<uint256>::const_iterator it;
|
||||||
|
if ((it = std::find(vMatch.begin(), vMatch.end(), hashTx))==vMatch.end()) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof");
|
||||||
|
}
|
||||||
|
|
||||||
|
txnIndex = vIndex[it - vMatch.begin()];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock");
|
||||||
|
}
|
||||||
|
|
||||||
|
wtx.nIndex = txnIndex;
|
||||||
|
wtx.hashBlock = merkleBlock.header.GetHash();
|
||||||
|
|
||||||
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||||
|
|
||||||
|
if (pwalletMain->IsMine(tx)) {
|
||||||
|
CWalletDB walletdb(pwalletMain->strWalletFile, "r+", false);
|
||||||
|
pwalletMain->AddToWallet(wtx, false, &walletdb);
|
||||||
|
return NullUniValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction");
|
||||||
|
}
|
||||||
|
|
||||||
|
UniValue removeprunedfunds(const UniValue& params, bool fHelp)
|
||||||
|
{
|
||||||
|
if (!EnsureWalletIsAvailable(fHelp))
|
||||||
|
return NullUniValue;
|
||||||
|
|
||||||
|
if (fHelp || params.size() != 1)
|
||||||
|
throw runtime_error(
|
||||||
|
"removeprunedfunds \"txid\"\n"
|
||||||
|
"\nDeletes the specified transaction from the wallet. Meant for use with pruned wallets and as a companion to importprunedfunds. This will effect wallet balances.\n"
|
||||||
|
"\nArguments:\n"
|
||||||
|
"1. \"txid\" (string, required) The hex-encoded id of the transaction you are deleting\n"
|
||||||
|
"\nExamples:\n"
|
||||||
|
+ HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") +
|
||||||
|
"\nAs a JSON-RPC call\n"
|
||||||
|
+ HelpExampleRpc("removprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"")
|
||||||
|
);
|
||||||
|
|
||||||
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||||
|
|
||||||
|
uint256 hash;
|
||||||
|
hash.SetHex(params[0].get_str());
|
||||||
|
vector<uint256> vHash;
|
||||||
|
vHash.push_back(hash);
|
||||||
|
vector<uint256> vHashOut;
|
||||||
|
|
||||||
|
if(pwalletMain->ZapSelectTx(vHash, vHashOut) != DB_LOAD_OK) {
|
||||||
|
throw JSONRPCError(RPC_INTERNAL_ERROR, "Could not properly delete the transaction.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(vHashOut.empty()) {
|
||||||
|
throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction does not exist in wallet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadFlushWalletDB(pwalletMain->strWalletFile);
|
||||||
|
|
||||||
|
return NullUniValue;
|
||||||
|
}
|
||||||
|
|
||||||
UniValue importpubkey(const UniValue& params, bool fHelp)
|
UniValue importpubkey(const UniValue& params, bool fHelp)
|
||||||
{
|
{
|
||||||
if (!EnsureWalletIsAvailable(fHelp))
|
if (!EnsureWalletIsAvailable(fHelp))
|
||||||
|
|
|
@ -2506,6 +2506,8 @@ extern UniValue importaddress(const UniValue& params, bool fHelp);
|
||||||
extern UniValue importpubkey(const UniValue& params, bool fHelp);
|
extern UniValue importpubkey(const UniValue& params, bool fHelp);
|
||||||
extern UniValue dumpwallet(const UniValue& params, bool fHelp);
|
extern UniValue dumpwallet(const UniValue& params, bool fHelp);
|
||||||
extern UniValue importwallet(const UniValue& params, bool fHelp);
|
extern UniValue importwallet(const UniValue& params, bool fHelp);
|
||||||
|
extern UniValue importprunedfunds(const UniValue& params, bool fHelp);
|
||||||
|
extern UniValue removeprunedfunds(const UniValue& params, bool fHelp);
|
||||||
|
|
||||||
const CRPCCommand vWalletRPCCommands[] =
|
const CRPCCommand vWalletRPCCommands[] =
|
||||||
{ // category name actor (function) okSafeMode
|
{ // category name actor (function) okSafeMode
|
||||||
|
@ -2532,6 +2534,7 @@ const CRPCCommand vWalletRPCCommands[] =
|
||||||
{ "wallet", "importprivkey", &importprivkey, true },
|
{ "wallet", "importprivkey", &importprivkey, true },
|
||||||
{ "wallet", "importwallet", &importwallet, true },
|
{ "wallet", "importwallet", &importwallet, true },
|
||||||
{ "wallet", "importaddress", &importaddress, true },
|
{ "wallet", "importaddress", &importaddress, true },
|
||||||
|
{ "wallet", "importprunedfunds", &importprunedfunds, true },
|
||||||
{ "wallet", "importpubkey", &importpubkey, true },
|
{ "wallet", "importpubkey", &importpubkey, true },
|
||||||
{ "wallet", "keypoolrefill", &keypoolrefill, true },
|
{ "wallet", "keypoolrefill", &keypoolrefill, true },
|
||||||
{ "wallet", "listaccounts", &listaccounts, false },
|
{ "wallet", "listaccounts", &listaccounts, false },
|
||||||
|
@ -2553,6 +2556,7 @@ const CRPCCommand vWalletRPCCommands[] =
|
||||||
{ "wallet", "walletlock", &walletlock, true },
|
{ "wallet", "walletlock", &walletlock, true },
|
||||||
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, true },
|
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, true },
|
||||||
{ "wallet", "walletpassphrase", &walletpassphrase, true },
|
{ "wallet", "walletpassphrase", &walletpassphrase, true },
|
||||||
|
{ "wallet", "removeprunedfunds", &removeprunedfunds, true },
|
||||||
};
|
};
|
||||||
|
|
||||||
void walletRegisterRPCCommands()
|
void walletRegisterRPCCommands()
|
||||||
|
|
|
@ -2353,6 +2353,31 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
|
||||||
return DB_LOAD_OK;
|
return DB_LOAD_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DBErrors CWallet::ZapSelectTx(vector<uint256>& vHashIn, vector<uint256>& vHashOut)
|
||||||
|
{
|
||||||
|
if (!fFileBacked)
|
||||||
|
return DB_LOAD_OK;
|
||||||
|
DBErrors nZapSelectTxRet = CWalletDB(strWalletFile,"cr+").ZapSelectTx(this, vHashIn, vHashOut);
|
||||||
|
if (nZapSelectTxRet == DB_NEED_REWRITE)
|
||||||
|
{
|
||||||
|
if (CDB::Rewrite(strWalletFile, "\x04pool"))
|
||||||
|
{
|
||||||
|
LOCK(cs_wallet);
|
||||||
|
setKeyPool.clear();
|
||||||
|
// Note: can't top-up keypool here, because wallet is locked.
|
||||||
|
// User will be prompted to unlock wallet the next operation
|
||||||
|
// that requires a new key.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nZapSelectTxRet != DB_LOAD_OK)
|
||||||
|
return nZapSelectTxRet;
|
||||||
|
|
||||||
|
MarkDirty();
|
||||||
|
|
||||||
|
return DB_LOAD_OK;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx)
|
DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx)
|
||||||
{
|
{
|
||||||
|
|
|
@ -792,6 +792,7 @@ public:
|
||||||
|
|
||||||
DBErrors LoadWallet(bool& fFirstRunRet);
|
DBErrors LoadWallet(bool& fFirstRunRet);
|
||||||
DBErrors ZapWalletTx(std::vector<CWalletTx>& vWtx);
|
DBErrors ZapWalletTx(std::vector<CWalletTx>& vWtx);
|
||||||
|
DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut);
|
||||||
|
|
||||||
bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& purpose);
|
bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& purpose);
|
||||||
|
|
||||||
|
|
|
@ -785,6 +785,45 @@ DBErrors CWalletDB::FindWalletTx(CWallet* pwallet, vector<uint256>& vTxHash, vec
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DBErrors CWalletDB::ZapSelectTx(CWallet* pwallet, vector<uint256>& vTxHashIn, vector<uint256>& vTxHashOut)
|
||||||
|
{
|
||||||
|
// build list of wallet TXs and hashes
|
||||||
|
vector<uint256> vTxHash;
|
||||||
|
vector<CWalletTx> vWtx;
|
||||||
|
DBErrors err = FindWalletTx(pwallet, vTxHash, vWtx);
|
||||||
|
if (err != DB_LOAD_OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(vTxHash.begin(), vTxHash.end());
|
||||||
|
std::sort(vTxHashIn.begin(), vTxHashIn.end());
|
||||||
|
|
||||||
|
// erase each matching wallet TX
|
||||||
|
bool delerror = false;
|
||||||
|
vector<uint256>::iterator it = vTxHashIn.begin();
|
||||||
|
BOOST_FOREACH (uint256 hash, vTxHash) {
|
||||||
|
while (it < vTxHashIn.end() && (*it) < hash) {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
if (it == vTxHashIn.end()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if ((*it) == hash) {
|
||||||
|
pwallet->mapWallet.erase(hash);
|
||||||
|
if(!EraseTx(hash)) {
|
||||||
|
LogPrint("db", "Transaction was found for deletion but returned database error: %s\n", hash.GetHex());
|
||||||
|
delerror = true;
|
||||||
|
}
|
||||||
|
vTxHashOut.push_back(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delerror) {
|
||||||
|
return DB_CORRUPT;
|
||||||
|
}
|
||||||
|
return DB_LOAD_OK;
|
||||||
|
}
|
||||||
|
|
||||||
DBErrors CWalletDB::ZapWalletTx(CWallet* pwallet, vector<CWalletTx>& vWtx)
|
DBErrors CWalletDB::ZapWalletTx(CWallet* pwallet, vector<CWalletTx>& vWtx)
|
||||||
{
|
{
|
||||||
// build list of wallet TXs
|
// build list of wallet TXs
|
||||||
|
|
|
@ -130,6 +130,7 @@ public:
|
||||||
DBErrors LoadWallet(CWallet* pwallet);
|
DBErrors LoadWallet(CWallet* pwallet);
|
||||||
DBErrors FindWalletTx(CWallet* pwallet, std::vector<uint256>& vTxHash, std::vector<CWalletTx>& vWtx);
|
DBErrors FindWalletTx(CWallet* pwallet, std::vector<uint256>& vTxHash, std::vector<CWalletTx>& vWtx);
|
||||||
DBErrors ZapWalletTx(CWallet* pwallet, std::vector<CWalletTx>& vWtx);
|
DBErrors ZapWalletTx(CWallet* pwallet, std::vector<CWalletTx>& vWtx);
|
||||||
|
DBErrors ZapSelectTx(CWallet* pwallet, std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut);
|
||||||
static bool Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKeys);
|
static bool Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKeys);
|
||||||
static bool Recover(CDBEnv& dbenv, const std::string& filename);
|
static bool Recover(CDBEnv& dbenv, const std::string& filename);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue