aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xcontrib/devtools/github-merge.py20
-rwxr-xr-xqa/pull-tester/rpc-tests.py1
-rwxr-xr-xqa/rpc-tests/importprunedfunds.py140
-rw-r--r--qa/rpc-tests/test_framework/authproxy.py7
-rwxr-xr-xqa/rpc-tests/wallet.py4
-rw-r--r--src/merkleblock.cpp14
-rw-r--r--src/merkleblock.h9
-rw-r--r--src/rpc/rawtransaction.cpp3
-rw-r--r--src/test/bloom_tests.cpp23
-rw-r--r--src/test/pmt_tests.cpp8
-rw-r--r--src/wallet/rpcdump.cpp105
-rw-r--r--src/wallet/rpcwallet.cpp5
-rw-r--r--src/wallet/wallet.cpp25
-rw-r--r--src/wallet/wallet.h1
-rw-r--r--src/wallet/walletdb.cpp39
-rw-r--r--src/wallet/walletdb.h1
16 files changed, 371 insertions, 34 deletions
diff --git a/contrib/devtools/github-merge.py b/contrib/devtools/github-merge.py
index c8dcaae268..9a62fccbbb 100755
--- a/contrib/devtools/github-merge.py
+++ b/contrib/devtools/github-merge.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
# Copyright (c) 2016 Bitcoin Core Developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -19,6 +19,11 @@ import os,sys
from sys import stdin,stdout,stderr
import argparse
import subprocess
+import json,codecs
+try:
+ from urllib.request import Request,urlopen
+except:
+ from urllib2 import Request,urlopen
# External tools (can be overridden using environment)
GIT = os.getenv('GIT','git')
@@ -38,7 +43,7 @@ def git_config_get(option, default=None):
Get named configuration option from git repository.
'''
try:
- return subprocess.check_output([GIT,'config','--get',option]).rstrip()
+ return subprocess.check_output([GIT,'config','--get',option]).rstrip().decode('utf-8')
except subprocess.CalledProcessError as e:
return default
@@ -47,18 +52,19 @@ def retrieve_pr_title(repo,pull):
Retrieve pull request title from github.
Return None if no title can be found, or an error happens.
'''
- import urllib2,json
try:
- req = urllib2.Request("https://api.github.com/repos/"+repo+"/pulls/"+pull)
- result = urllib2.urlopen(req)
- result = json.load(result)
- return result['title']
+ req = Request("https://api.github.com/repos/"+repo+"/pulls/"+pull)
+ result = urlopen(req)
+ reader = codecs.getreader('utf-8')
+ obj = json.load(reader(result))
+ return obj['title']
except Exception as e:
print('Warning: unable to retrieve pull title from github: %s' % e)
return None
def ask_prompt(text):
print(text,end=" ",file=stderr)
+ stderr.flush()
reply = stdin.readline().rstrip()
print("",file=stderr)
return reply
diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py
index 74be96da74..d5d5f10610 100755
--- a/qa/pull-tester/rpc-tests.py
+++ b/qa/pull-tester/rpc-tests.py
@@ -116,6 +116,7 @@ testScripts = [
'invalidtxrequest.py',
'abandonconflict.py',
'p2p-versionbits-warning.py',
+ 'importprunedfunds.py',
]
testScriptsExt = [
'bip65-cltv.py',
diff --git a/qa/rpc-tests/importprunedfunds.py b/qa/rpc-tests/importprunedfunds.py
new file mode 100755
index 0000000000..5cbdcde9aa
--- /dev/null
+++ b/qa/rpc-tests/importprunedfunds.py
@@ -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 ()
diff --git a/qa/rpc-tests/test_framework/authproxy.py b/qa/rpc-tests/test_framework/authproxy.py
index fba469a0dd..1eb2772592 100644
--- a/qa/rpc-tests/test_framework/authproxy.py
+++ b/qa/rpc-tests/test_framework/authproxy.py
@@ -61,7 +61,7 @@ class JSONRPCException(Exception):
def EncodeDecimal(o):
if isinstance(o, decimal.Decimal):
- return round(o, 8)
+ return str(o)
raise TypeError(repr(o) + " is not JSON serializable")
class AuthServiceProxy(object):
@@ -92,11 +92,10 @@ class AuthServiceProxy(object):
self.__conn = connection
elif self.__url.scheme == 'https':
self.__conn = httplib.HTTPSConnection(self.__url.hostname, port,
- None, None, False,
- timeout)
+ timeout=timeout)
else:
self.__conn = httplib.HTTPConnection(self.__url.hostname, port,
- False, timeout)
+ timeout=timeout)
def __getattr__(self, name):
if name.startswith('__') and name.endswith('__'):
diff --git a/qa/rpc-tests/wallet.py b/qa/rpc-tests/wallet.py
index df176601a5..e6ce397119 100755
--- a/qa/rpc-tests/wallet.py
+++ b/qa/rpc-tests/wallet.py
@@ -279,6 +279,7 @@ class WalletTest (BitcoinTestFramework):
blocks = self.nodes[0].generate(2)
self.sync_all()
balance_nodes = [self.nodes[i].getbalance() for i in range(3)]
+ block_count = self.nodes[0].getblockcount()
maintenance = [
'-rescan',
@@ -292,6 +293,9 @@ class WalletTest (BitcoinTestFramework):
stop_nodes(self.nodes)
wait_bitcoinds()
self.nodes = start_nodes(3, self.options.tmpdir, [[m]] * 3)
+ while m == '-reindex' and [block_count] * 3 != [self.nodes[i].getblockcount() for i in range(3)]:
+ # reindex will leave rpc warm up "early"; Wait for it to finish
+ time.sleep(0.1)
assert_equal(balance_nodes, [self.nodes[i].getbalance() for i in range(3)])
# Exercise listsinceblock with the last two blocks
diff --git a/src/merkleblock.cpp b/src/merkleblock.cpp
index 8447f924e4..dca4973cc4 100644
--- a/src/merkleblock.cpp
+++ b/src/merkleblock.cpp
@@ -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()) {
// overflowed the bits array - failure
fBad = true;
@@ -110,14 +110,16 @@ uint256 CPartialMerkleTree::TraverseAndExtract(int height, unsigned int pos, uns
return uint256();
}
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);
+ vnIndex.push_back(pos);
+ }
return hash;
} else {
// 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)) {
- right = TraverseAndExtract(height-1, pos*2+1, nBitsUsed, nHashUsed, vMatch);
+ right = TraverseAndExtract(height-1, pos*2+1, nBitsUsed, nHashUsed, vMatch, vnIndex);
if (right == left) {
// The left and right branches should never be identical, as the transaction
// 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) {}
-uint256 CPartialMerkleTree::ExtractMatches(std::vector<uint256> &vMatch) {
+uint256 CPartialMerkleTree::ExtractMatches(std::vector<uint256> &vMatch, std::vector<unsigned int> &vnIndex) {
vMatch.clear();
// An empty set will not work
if (nTransactions == 0)
@@ -167,7 +169,7 @@ uint256 CPartialMerkleTree::ExtractMatches(std::vector<uint256> &vMatch) {
nHeight++;
// traverse the partial tree
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
if (fBad)
return uint256();
diff --git a/src/merkleblock.h b/src/merkleblock.h
index 996cd12624..835cbcce55 100644
--- a/src/merkleblock.h
+++ b/src/merkleblock.h
@@ -75,9 +75,9 @@ protected:
/**
* 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:
@@ -110,10 +110,11 @@ public:
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
*/
- uint256 ExtractMatches(std::vector<uint256> &vMatch);
+ uint256 ExtractMatches(std::vector<uint256> &vMatch, std::vector<unsigned int> &vnIndex);
};
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index c72339313c..34dd3b30f5 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -303,7 +303,8 @@ UniValue verifytxoutproof(const UniValue& params, bool fHelp)
UniValue res(UniValue::VARR);
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;
LOCK(cs_main);
diff --git a/src/test/bloom_tests.cpp b/src/test/bloom_tests.cpp
index 98f9de7673..9557000ddc 100644
--- a/src/test/bloom_tests.cpp
+++ b/src/test/bloom_tests.cpp
@@ -204,7 +204,8 @@ BOOST_AUTO_TEST_CASE(merkle_block_1)
BOOST_CHECK(merkleBlock.vMatchedTxn[0].first == 8);
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());
for (unsigned int i = 0; i < vMatched.size(); i++)
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].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());
for (unsigned int i = 0; i < vMatched.size(); i++)
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);
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());
for (unsigned int i = 0; i < vMatched.size(); i++)
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].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());
for (unsigned int i = 0; i < vMatched.size(); i++)
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);
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());
for (unsigned int i = 0; i < vMatched.size(); i++)
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].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());
for (unsigned int i = 0; i < vMatched.size(); i++)
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);
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());
for (unsigned int i = 0; i < vMatched.size(); i++)
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);
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());
for (unsigned int i = 0; i < vMatched.size(); i++)
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.txn.ExtractMatches(vMatched) == block.hashMerkleRoot);
+ BOOST_CHECK(merkleBlock.txn.ExtractMatches(vMatched, vIndex) == block.hashMerkleRoot);
BOOST_CHECK(vMatched.size() == merkleBlock.vMatchedTxn.size());
for (unsigned int i = 0; i < vMatched.size(); i++)
BOOST_CHECK(vMatched[i] == merkleBlock.vMatchedTxn[i].second);
diff --git a/src/test/pmt_tests.cpp b/src/test/pmt_tests.cpp
index 113b9437e0..2f3f607889 100644
--- a/src/test/pmt_tests.cpp
+++ b/src/test/pmt_tests.cpp
@@ -88,7 +88,8 @@ BOOST_AUTO_TEST_CASE(pmt_test1)
// extract merkle root and matched txids from copy
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
BOOST_CHECK(merkleRoot1 == merkleRoot2);
@@ -102,7 +103,7 @@ BOOST_AUTO_TEST_CASE(pmt_test1)
CPartialMerkleTreeTester pmt3(pmt2);
pmt3.Damage();
std::vector<uint256> vMatchTxid3;
- uint256 merkleRoot3 = pmt3.ExtractMatches(vMatchTxid3);
+ uint256 merkleRoot3 = pmt3.ExtractMatches(vMatchTxid3, vIndex);
BOOST_CHECK(merkleRoot3 != merkleRoot1);
}
}
@@ -122,7 +123,8 @@ BOOST_AUTO_TEST_CASE(pmt_malleability)
CPartialMerkleTree tree(vTxid, vMatch);
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()
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 6e50f92428..bb40cf7245 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -13,6 +13,8 @@
#include "util.h"
#include "utiltime.h"
#include "wallet.h"
+#include "merkleblock.h"
+#include "core_io.h"
#include <fstream>
#include <stdint.h>
@@ -243,6 +245,109 @@ UniValue importaddress(const UniValue& params, bool fHelp)
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)
{
if (!EnsureWalletIsAvailable(fHelp))
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 1d23505961..a1733bae77 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -1439,6 +1439,7 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
" \"vout\": n, (numeric) the vout value\n"
" \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
" 'send' category of transactions.\n"
+ " \"abandoned\": xxx (bool) 'true' if the transaction has been abandoned (inputs are respendable).\n"
" \"confirmations\": n, (numeric) The number of confirmations for the transaction. Available for 'send' and \n"
" 'receive' category of transactions. Negative confirmations indicate the\n"
" transaction conflicts with the block chain\n"
@@ -2505,6 +2506,8 @@ extern UniValue importaddress(const UniValue& params, bool fHelp);
extern UniValue importpubkey(const UniValue& params, bool fHelp);
extern UniValue dumpwallet(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[] =
{ // category name actor (function) okSafeMode
@@ -2531,6 +2534,7 @@ const CRPCCommand vWalletRPCCommands[] =
{ "wallet", "importprivkey", &importprivkey, true },
{ "wallet", "importwallet", &importwallet, true },
{ "wallet", "importaddress", &importaddress, true },
+ { "wallet", "importprunedfunds", &importprunedfunds, true },
{ "wallet", "importpubkey", &importpubkey, true },
{ "wallet", "keypoolrefill", &keypoolrefill, true },
{ "wallet", "listaccounts", &listaccounts, false },
@@ -2552,6 +2556,7 @@ const CRPCCommand vWalletRPCCommands[] =
{ "wallet", "walletlock", &walletlock, true },
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, true },
{ "wallet", "walletpassphrase", &walletpassphrase, true },
+ { "wallet", "removeprunedfunds", &removeprunedfunds, true },
};
void walletRegisterRPCCommands()
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index e85ccd2135..1ea8d2ffe7 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -2353,6 +2353,31 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
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)
{
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index e37d972a15..5db36f52d1 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -792,6 +792,7 @@ public:
DBErrors LoadWallet(bool& fFirstRunRet);
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);
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 0a4a1dae2f..f2b5408e92 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -785,6 +785,45 @@ DBErrors CWalletDB::FindWalletTx(CWallet* pwallet, vector<uint256>& vTxHash, vec
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)
{
// build list of wallet TXs
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index 7e8cc4084e..fe6c366343 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -130,6 +130,7 @@ public:
DBErrors LoadWallet(CWallet* pwallet);
DBErrors FindWalletTx(CWallet* pwallet, std::vector<uint256>& vTxHash, 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);