Skip to content

Commit 9caaf6e

Browse files
committed
Merge pull request #6777
dcd8e27 Refer to obfuscate_key via pointer in peripheral CLevelDB classes (James O'Beirne) 1488506 Add tests for gettxoutsetinfo, CLevelDBBatch, CLevelDBIterator (James O'Beirne) 0fdf8c8 Handle obfuscation in CLevelDBIterator (James O'Beirne) 3499ce1 Encapsulate CLevelDB iterators cleanly (Pieter Wuille)
2 parents 16faccb + dcd8e27 commit 9caaf6e

File tree

6 files changed

+245
-61
lines changed

6 files changed

+245
-61
lines changed

qa/pull-tester/rpc-tests.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
buildDir = BUILDDIR
3939
os.environ["BITCOIND"] = buildDir + '/src/bitcoind' + EXEEXT
4040
os.environ["BITCOINCLI"] = buildDir + '/src/bitcoin-cli' + EXEEXT
41-
41+
4242
#Disable Windows tests by default
4343
if EXEEXT == ".exe" and "-win" not in opts:
4444
print "Win tests currently disabled. Use -win option to enable"
@@ -67,6 +67,7 @@
6767
'reindex.py',
6868
'decodescript.py',
6969
'p2p-fullblocktest.py',
70+
'blockchain.py',
7071
]
7172
testScriptsExt = [
7273
'bipdersig-p2p.py',
@@ -98,20 +99,20 @@
9899
rpcTestDir = buildDir + '/qa/rpc-tests/'
99100
#Run Tests
100101
for i in range(len(testScripts)):
101-
if (len(opts) == 0 or (len(opts) == 1 and "-win" in opts ) or '-extended' in opts
102+
if (len(opts) == 0 or (len(opts) == 1 and "-win" in opts ) or '-extended' in opts
102103
or testScripts[i] in opts or re.sub(".py$", "", testScripts[i]) in opts ):
103-
print "Running testscript " + testScripts[i] + "..."
104-
subprocess.call(rpcTestDir + testScripts[i] + " --srcdir " + buildDir + '/src ' + passOn,shell=True)
104+
print "Running testscript " + testScripts[i] + "..."
105+
subprocess.call(rpcTestDir + testScripts[i] + " --srcdir " + buildDir + '/src ' + passOn,shell=True)
105106
#exit if help is called so we print just one set of instructions
106107
p = re.compile(" -h| --help")
107108
if p.match(passOn):
108109
sys.exit(0)
109110

110111
#Run Extended Tests
111112
for i in range(len(testScriptsExt)):
112-
if ('-extended' in opts or testScriptsExt[i] in opts
113+
if ('-extended' in opts or testScriptsExt[i] in opts
113114
or re.sub(".py$", "", testScriptsExt[i]) in opts):
114-
print "Running 2nd level testscript " + testScriptsExt[i] + "..."
115-
subprocess.call(rpcTestDir + testScriptsExt[i] + " --srcdir " + buildDir + '/src ' + passOn,shell=True)
115+
print "Running 2nd level testscript " + testScriptsExt[i] + "..."
116+
subprocess.call(rpcTestDir + testScriptsExt[i] + " --srcdir " + buildDir + '/src ' + passOn,shell=True)
116117
else:
117118
print "No rpc tests to run. Wallet, utils, and bitcoind must all be enabled"

qa/rpc-tests/blockchain.py

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/usr/bin/env python2
2+
# Copyright (c) 2014 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
#
7+
# Test RPC calls related to blockchain state.
8+
#
9+
10+
import decimal
11+
12+
from test_framework.test_framework import BitcoinTestFramework
13+
from test_framework.util import (
14+
initialize_chain,
15+
assert_equal,
16+
start_nodes,
17+
connect_nodes_bi,
18+
)
19+
20+
class BlockchainTest(BitcoinTestFramework):
21+
"""
22+
Test blockchain-related RPC calls:
23+
24+
- gettxoutsetinfo
25+
26+
"""
27+
28+
def setup_chain(self):
29+
print("Initializing test directory " + self.options.tmpdir)
30+
initialize_chain(self.options.tmpdir)
31+
32+
def setup_network(self, split=False):
33+
self.nodes = start_nodes(2, self.options.tmpdir)
34+
connect_nodes_bi(self.nodes, 0, 1)
35+
self.is_network_split = False
36+
self.sync_all()
37+
38+
def run_test(self):
39+
node = self.nodes[0]
40+
res = node.gettxoutsetinfo()
41+
42+
assert_equal(res[u'total_amount'], decimal.Decimal('8725.00000000'))
43+
assert_equal(res[u'transactions'], 200)
44+
assert_equal(res[u'height'], 200)
45+
assert_equal(res[u'txouts'], 200)
46+
assert_equal(res[u'bytes_serialized'], 13000),
47+
assert_equal(len(res[u'bestblock']), 64)
48+
assert_equal(len(res[u'hash_serialized']), 64)
49+
50+
51+
if __name__ == '__main__':
52+
BlockchainTest().main()

src/leveldbwrapper.cpp

+8-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ std::vector<unsigned char> CLevelDBWrapper::CreateObfuscateKey() const
131131

132132
bool CLevelDBWrapper::IsEmpty()
133133
{
134-
boost::scoped_ptr<leveldb::Iterator> it(NewIterator());
134+
boost::scoped_ptr<CLevelDBIterator> it(NewIterator());
135135
it->SeekToFirst();
136136
return !(it->Valid());
137137
}
@@ -145,3 +145,10 @@ std::string CLevelDBWrapper::GetObfuscateKeyHex() const
145145
{
146146
return HexStr(obfuscate_key);
147147
}
148+
149+
CLevelDBIterator::~CLevelDBIterator() { delete piter; }
150+
bool CLevelDBIterator::Valid() { return piter->Valid(); }
151+
void CLevelDBIterator::SeekToFirst() { piter->SeekToFirst(); }
152+
void CLevelDBIterator::SeekToLast() { piter->SeekToLast(); }
153+
void CLevelDBIterator::Next() { piter->Next(); }
154+
void CLevelDBIterator::Prev() { piter->Prev(); }

src/leveldbwrapper.h

+73-9
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ class CLevelDBBatch
3232

3333
private:
3434
leveldb::WriteBatch batch;
35-
const std::vector<unsigned char> obfuscate_key;
35+
const std::vector<unsigned char> *obfuscate_key;
3636

3737
public:
3838
/**
3939
* @param[in] obfuscate_key If passed, XOR data with this key.
4040
*/
41-
CLevelDBBatch(const std::vector<unsigned char>& obfuscate_key) : obfuscate_key(obfuscate_key) { };
41+
CLevelDBBatch(const std::vector<unsigned char> *obfuscate_key) : obfuscate_key(obfuscate_key) { };
4242

4343
template <typename K, typename V>
4444
void Write(const K& key, const V& value)
@@ -51,7 +51,7 @@ class CLevelDBBatch
5151
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
5252
ssValue.reserve(ssValue.GetSerializeSize(value));
5353
ssValue << value;
54-
ssValue.Xor(obfuscate_key);
54+
ssValue.Xor(*obfuscate_key);
5555
leveldb::Slice slValue(&ssValue[0], ssValue.size());
5656

5757
batch.Put(slKey, slValue);
@@ -68,7 +68,72 @@ class CLevelDBBatch
6868
batch.Delete(slKey);
6969
}
7070
};
71+
72+
class CLevelDBIterator
73+
{
74+
private:
75+
leveldb::Iterator *piter;
76+
const std::vector<unsigned char> *obfuscate_key;
77+
78+
public:
79+
80+
/**
81+
* @param[in] piterIn The original leveldb iterator.
82+
* @param[in] obfuscate_key If passed, XOR data with this key.
83+
*/
84+
CLevelDBIterator(leveldb::Iterator *piterIn, const std::vector<unsigned char>* obfuscate_key) :
85+
piter(piterIn), obfuscate_key(obfuscate_key) { };
86+
~CLevelDBIterator();
87+
88+
bool Valid();
7189

90+
void SeekToFirst();
91+
void SeekToLast();
92+
93+
template<typename K> void Seek(const K& key) {
94+
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
95+
ssKey.reserve(ssKey.GetSerializeSize(key));
96+
ssKey << key;
97+
leveldb::Slice slKey(&ssKey[0], ssKey.size());
98+
piter->Seek(slKey);
99+
}
100+
101+
void Next();
102+
void Prev();
103+
104+
template<typename K> bool GetKey(K& key) {
105+
leveldb::Slice slKey = piter->key();
106+
try {
107+
CDataStream ssKey(slKey.data(), slKey.data() + slKey.size(), SER_DISK, CLIENT_VERSION);
108+
ssKey >> key;
109+
} catch(std::exception &e) {
110+
return false;
111+
}
112+
return true;
113+
}
114+
115+
unsigned int GetKeySize() {
116+
return piter->key().size();
117+
}
118+
119+
template<typename V> bool GetValue(V& value) {
120+
leveldb::Slice slValue = piter->value();
121+
try {
122+
CDataStream ssValue(slValue.data(), slValue.data() + slValue.size(), SER_DISK, CLIENT_VERSION);
123+
ssValue.Xor(*obfuscate_key);
124+
ssValue >> value;
125+
} catch(std::exception &e) {
126+
return false;
127+
}
128+
return true;
129+
}
130+
131+
unsigned int GetValueSize() {
132+
return piter->value().size();
133+
}
134+
135+
};
136+
72137
class CLevelDBWrapper
73138
{
74139
private:
@@ -145,7 +210,7 @@ class CLevelDBWrapper
145210
template <typename K, typename V>
146211
bool Write(const K& key, const V& value, bool fSync = false) throw(leveldb_error)
147212
{
148-
CLevelDBBatch batch(obfuscate_key);
213+
CLevelDBBatch batch(&obfuscate_key);
149214
batch.Write(key, value);
150215
return WriteBatch(batch, fSync);
151216
}
@@ -172,7 +237,7 @@ class CLevelDBWrapper
172237
template <typename K>
173238
bool Erase(const K& key, bool fSync = false) throw(leveldb_error)
174239
{
175-
CLevelDBBatch batch(obfuscate_key);
240+
CLevelDBBatch batch(&obfuscate_key);
176241
batch.Erase(key);
177242
return WriteBatch(batch, fSync);
178243
}
@@ -187,14 +252,13 @@ class CLevelDBWrapper
187252

188253
bool Sync() throw(leveldb_error)
189254
{
190-
CLevelDBBatch batch(obfuscate_key);
255+
CLevelDBBatch batch(&obfuscate_key);
191256
return WriteBatch(batch, true);
192257
}
193258

194-
// not exactly clean encapsulation, but it's easiest for now
195-
leveldb::Iterator* NewIterator()
259+
CLevelDBIterator *NewIterator()
196260
{
197-
return pdb->NewIterator(iteroptions);
261+
return new CLevelDBIterator(pdb->NewIterator(iteroptions), &obfuscate_key);
198262
}
199263

200264
/**

src/test/leveldbwrapper_tests.cpp

+80-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,86 @@ BOOST_AUTO_TEST_CASE(leveldbwrapper)
4646
BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
4747
}
4848
}
49-
49+
50+
// Test batch operations
51+
BOOST_AUTO_TEST_CASE(leveldbwrapper_batch)
52+
{
53+
// Perform tests both obfuscated and non-obfuscated.
54+
for (int i = 0; i < 2; i++) {
55+
bool obfuscate = (bool)i;
56+
path ph = temp_directory_path() / unique_path();
57+
CLevelDBWrapper dbw(ph, (1 << 20), true, false, obfuscate);
58+
59+
char key = 'i';
60+
uint256 in = GetRandHash();
61+
char key2 = 'j';
62+
uint256 in2 = GetRandHash();
63+
char key3 = 'k';
64+
uint256 in3 = GetRandHash();
65+
66+
uint256 res;
67+
CLevelDBBatch batch(&dbw.GetObfuscateKey());
68+
69+
batch.Write(key, in);
70+
batch.Write(key2, in2);
71+
batch.Write(key3, in3);
72+
73+
// Remove key3 before it's even been written
74+
batch.Erase(key3);
75+
76+
dbw.WriteBatch(batch);
77+
78+
BOOST_CHECK(dbw.Read(key, res));
79+
BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
80+
BOOST_CHECK(dbw.Read(key2, res));
81+
BOOST_CHECK_EQUAL(res.ToString(), in2.ToString());
82+
83+
// key3 never should've been written
84+
BOOST_CHECK(dbw.Read(key3, res) == false);
85+
}
86+
}
87+
88+
BOOST_AUTO_TEST_CASE(leveldbwrapper_iterator)
89+
{
90+
// Perform tests both obfuscated and non-obfuscated.
91+
for (int i = 0; i < 2; i++) {
92+
bool obfuscate = (bool)i;
93+
path ph = temp_directory_path() / unique_path();
94+
CLevelDBWrapper dbw(ph, (1 << 20), true, false, obfuscate);
95+
96+
// The two keys are intentionally chosen for ordering
97+
char key = 'j';
98+
uint256 in = GetRandHash();
99+
BOOST_CHECK(dbw.Write(key, in));
100+
char key2 = 'k';
101+
uint256 in2 = GetRandHash();
102+
BOOST_CHECK(dbw.Write(key2, in2));
103+
104+
boost::scoped_ptr<CLevelDBIterator> it(const_cast<CLevelDBWrapper*>(&dbw)->NewIterator());
105+
106+
// Be sure to seek past the obfuscation key (if it exists)
107+
it->Seek(key);
108+
109+
char key_res;
110+
uint256 val_res;
111+
112+
it->GetKey(key_res);
113+
it->GetValue(val_res);
114+
BOOST_CHECK_EQUAL(key_res, key);
115+
BOOST_CHECK_EQUAL(val_res.ToString(), in.ToString());
116+
117+
it->Next();
118+
119+
it->GetKey(key_res);
120+
it->GetValue(val_res);
121+
BOOST_CHECK_EQUAL(key_res, key2);
122+
BOOST_CHECK_EQUAL(val_res.ToString(), in2.ToString());
123+
124+
it->Next();
125+
BOOST_CHECK_EQUAL(it->Valid(), false);
126+
}
127+
}
128+
50129
// Test that we do not obfuscation if there is existing data.
51130
BOOST_AUTO_TEST_CASE(existing_data_no_obfuscate)
52131
{

0 commit comments

Comments
 (0)