Cache signature verifications
[novacoin.git] / src / test / DoS_tests.cpp
1 //
2 // Unit tests for denial-of-service detection/prevention code
3 //
4 #include <algorithm>
5
6 #include <boost/assign/list_of.hpp> // for 'map_list_of()'
7 #include <boost/date_time/posix_time/posix_time_types.hpp>
8 #include <boost/test/unit_test.hpp>
9 #include <boost/foreach.hpp>
10
11 #include "main.h"
12 #include "wallet.h"
13 #include "net.h"
14 #include "util.h"
15
16 #include <stdint.h>
17
18 // Tests this internal-to-main.cpp method:
19 extern bool AddOrphanTx(const CDataStream& vMsg);
20 extern unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans);
21 extern std::map<uint256, CDataStream*> mapOrphanTransactions;
22 extern std::map<uint256, std::map<uint256, CDataStream*> > mapOrphanTransactionsByPrev;
23
24 CService ip(uint32_t i)
25 {
26     struct in_addr s;
27     s.s_addr = i;
28     return CService(CNetAddr(s), GetDefaultPort());
29 }
30
31 BOOST_AUTO_TEST_SUITE(DoS_tests)
32
33 BOOST_AUTO_TEST_CASE(DoS_banning)
34 {
35     CNode::ClearBanned();
36     CAddress addr1(ip(0xa0b0c001));
37     CNode dummyNode1(INVALID_SOCKET, addr1, true);
38     dummyNode1.Misbehaving(100); // Should get banned
39     BOOST_CHECK(CNode::IsBanned(addr1));
40     BOOST_CHECK(!CNode::IsBanned(ip(0xa0b0c001|0x0000ff00))); // Different ip, not banned
41
42     CAddress addr2(ip(0xa0b0c002));
43     CNode dummyNode2(INVALID_SOCKET, addr2, true);
44     dummyNode2.Misbehaving(50);
45     BOOST_CHECK(!CNode::IsBanned(addr2)); // 2 not banned yet...
46     BOOST_CHECK(CNode::IsBanned(addr1));  // ... but 1 still should be
47     dummyNode2.Misbehaving(50);
48     BOOST_CHECK(CNode::IsBanned(addr2));
49 }    
50
51 BOOST_AUTO_TEST_CASE(DoS_banscore)
52 {
53     CNode::ClearBanned();
54     mapArgs["-banscore"] = "111"; // because 11 is my favorite number
55     CAddress addr1(ip(0xa0b0c001));
56     CNode dummyNode1(INVALID_SOCKET, addr1, true);
57     dummyNode1.Misbehaving(100);
58     BOOST_CHECK(!CNode::IsBanned(addr1));
59     dummyNode1.Misbehaving(10);
60     BOOST_CHECK(!CNode::IsBanned(addr1));
61     dummyNode1.Misbehaving(1);
62     BOOST_CHECK(CNode::IsBanned(addr1));
63     mapArgs.erase("-banscore");
64 }
65
66 BOOST_AUTO_TEST_CASE(DoS_bantime)
67 {
68     CNode::ClearBanned();
69     int64 nStartTime = GetTime();
70     SetMockTime(nStartTime); // Overrides future calls to GetTime()
71
72     CAddress addr(ip(0xa0b0c001));
73     CNode dummyNode(INVALID_SOCKET, addr, true);
74
75     dummyNode.Misbehaving(100);
76     BOOST_CHECK(CNode::IsBanned(addr));
77
78     SetMockTime(nStartTime+60*60);
79     BOOST_CHECK(CNode::IsBanned(addr));
80
81     SetMockTime(nStartTime+60*60*24+1);
82     BOOST_CHECK(!CNode::IsBanned(addr));
83 }
84
85 static bool CheckNBits(unsigned int nbits1, int64 time1, unsigned int nbits2, int64 time2)\
86 {
87     if (time1 > time2)
88         return CheckNBits(nbits2, time2, nbits1, time1);
89     int64 deltaTime = time2-time1;
90
91     CBigNum required;
92     required.SetCompact(ComputeMinWork(nbits1, deltaTime));
93     CBigNum have;
94     have.SetCompact(nbits2);
95     return (have <= required);
96 }
97
98 BOOST_AUTO_TEST_CASE(DoS_checknbits)
99 {
100     using namespace boost::assign; // for 'map_list_of()'
101
102     // Timestamps,nBits from the bitcoin blockchain.
103     // These are the block-chain checkpoint blocks
104     typedef std::map<int64, unsigned int> BlockData;
105     BlockData chainData =
106         map_list_of(1239852051,486604799)(1262749024,486594666)
107         (1279305360,469854461)(1280200847,469830746)(1281678674,469809688)
108         (1296207707,453179945)(1302624061,453036989)(1309640330,437004818)
109         (1313172719,436789733);
110
111     // Make sure CheckNBits considers every combination of block-chain-lock-in-points
112     // "sane":
113     BOOST_FOREACH(const BlockData::value_type& i, chainData)
114     {
115         BOOST_FOREACH(const BlockData::value_type& j, chainData)
116         {
117             BOOST_CHECK(CheckNBits(i.second, i.first, j.second, j.first));
118         }
119     }
120
121     // Test a couple of insane combinations:
122     BlockData::value_type firstcheck = *(chainData.begin());
123     BlockData::value_type lastcheck = *(chainData.rbegin());
124
125     // First checkpoint difficulty at or a while after the last checkpoint time should fail when
126     // compared to last checkpoint
127     BOOST_CHECK(!CheckNBits(firstcheck.second, lastcheck.first+60*10, lastcheck.second, lastcheck.first));
128     BOOST_CHECK(!CheckNBits(firstcheck.second, lastcheck.first+60*60*24*14, lastcheck.second, lastcheck.first));
129
130     // ... but OK if enough time passed for difficulty to adjust downward:
131     BOOST_CHECK(CheckNBits(firstcheck.second, lastcheck.first+60*60*24*365*4, lastcheck.second, lastcheck.first));
132     
133 }
134
135 static uint256 RandomHash()
136 {
137     std::vector<unsigned char> randbytes(32);
138     RAND_bytes(&randbytes[0], 32);
139     uint256 randomhash(randbytes);
140     return randomhash;
141 }
142
143 CTransaction RandomOrphan()
144 {
145     std::map<uint256, CDataStream*>::iterator it;
146     it = mapOrphanTransactions.lower_bound(RandomHash());
147     if (it == mapOrphanTransactions.end())
148         it = mapOrphanTransactions.begin();
149     const CDataStream* pvMsg = it->second;
150     CTransaction tx;
151     CDataStream(*pvMsg) >> tx;
152     return tx;
153 }
154
155 BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
156 {
157     CKey key;
158     key.MakeNewKey(true);
159     CBasicKeyStore keystore;
160     keystore.AddKey(key);
161
162     // 50 orphan transactions:
163     for (int i = 0; i < 50; i++)
164     {
165         CTransaction tx;
166         tx.vin.resize(1);
167         tx.vin[0].prevout.n = 0;
168         tx.vin[0].prevout.hash = RandomHash();
169         tx.vin[0].scriptSig << OP_1;
170         tx.vout.resize(1);
171         tx.vout[0].nValue = 1*CENT;
172         tx.vout[0].scriptPubKey.SetBitcoinAddress(key.GetPubKey());
173
174         CDataStream ds(SER_DISK, CLIENT_VERSION);
175         ds << tx;
176         AddOrphanTx(ds);
177     }
178
179     // ... and 50 that depend on other orphans:
180     for (int i = 0; i < 50; i++)
181     {
182         CTransaction txPrev = RandomOrphan();
183
184         CTransaction tx;
185         tx.vin.resize(1);
186         tx.vin[0].prevout.n = 0;
187         tx.vin[0].prevout.hash = txPrev.GetHash();
188         tx.vout.resize(1);
189         tx.vout[0].nValue = 1*CENT;
190         tx.vout[0].scriptPubKey.SetBitcoinAddress(key.GetPubKey());
191         SignSignature(keystore, txPrev, tx, 0);
192
193         CDataStream ds(SER_DISK, CLIENT_VERSION);
194         ds << tx;
195         AddOrphanTx(ds);
196     }
197
198     // This really-big orphan should be ignored:
199     for (int i = 0; i < 10; i++)
200     {
201         CTransaction txPrev = RandomOrphan();
202
203         CTransaction tx;
204         tx.vout.resize(1);
205         tx.vout[0].nValue = 1*CENT;
206         tx.vout[0].scriptPubKey.SetBitcoinAddress(key.GetPubKey());
207         tx.vin.resize(500);
208         for (int j = 0; j < tx.vin.size(); j++)
209         {
210             tx.vin[j].prevout.n = j;
211             tx.vin[j].prevout.hash = txPrev.GetHash();
212         }
213         SignSignature(keystore, txPrev, tx, 0);
214         // Re-use same signature for other inputs
215         // (they don't have to be valid for this test)
216         for (int j = 1; j < tx.vin.size(); j++)
217             tx.vin[j].scriptSig = tx.vin[0].scriptSig;
218
219         CDataStream ds(SER_DISK, CLIENT_VERSION);
220         ds << tx;
221         BOOST_CHECK(!AddOrphanTx(ds));
222     }
223
224     // Test LimitOrphanTxSize() function:
225     LimitOrphanTxSize(40);
226     BOOST_CHECK(mapOrphanTransactions.size() <= 40);
227     LimitOrphanTxSize(10);
228     BOOST_CHECK(mapOrphanTransactions.size() <= 10);
229     LimitOrphanTxSize(0);
230     BOOST_CHECK(mapOrphanTransactions.empty());
231     BOOST_CHECK(mapOrphanTransactionsByPrev.empty());
232 }
233
234 BOOST_AUTO_TEST_CASE(DoS_checkSig)
235 {
236     // Test signature caching code (see key.cpp Verify() methods)
237
238     CKey key;
239     key.MakeNewKey(true);
240     CBasicKeyStore keystore;
241     keystore.AddKey(key);
242
243     // 100 orphan transactions:
244     static const int NPREV=100;
245     CTransaction orphans[NPREV];
246     for (int i = 0; i < NPREV; i++)
247     {
248         CTransaction& tx = orphans[i];
249         tx.vin.resize(1);
250         tx.vin[0].prevout.n = 0;
251         tx.vin[0].prevout.hash = GetRandHash();
252         tx.vin[0].scriptSig << OP_1;
253         tx.vout.resize(1);
254         tx.vout[0].nValue = 1*CENT;
255         tx.vout[0].scriptPubKey.SetBitcoinAddress(key.GetPubKey());
256
257         CDataStream ds(SER_DISK, CLIENT_VERSION);
258         ds << tx;
259         AddOrphanTx(ds);
260     }
261
262     // Create a transaction that depends on orphans:
263     CTransaction tx;
264     tx.vout.resize(1);
265     tx.vout[0].nValue = 1*CENT;
266     tx.vout[0].scriptPubKey.SetBitcoinAddress(key.GetPubKey());
267     tx.vin.resize(NPREV);
268     for (int j = 0; j < tx.vin.size(); j++)
269     {
270         tx.vin[j].prevout.n = 0;
271         tx.vin[j].prevout.hash = orphans[j].GetHash();
272     }
273     // Creating signatures primes the cache:
274     boost::posix_time::ptime mst1 = boost::posix_time::microsec_clock::local_time();
275     for (int j = 0; j < tx.vin.size(); j++)
276         BOOST_CHECK(SignSignature(keystore, orphans[j], tx, j));
277     boost::posix_time::ptime mst2 = boost::posix_time::microsec_clock::local_time();
278     boost::posix_time::time_duration msdiff = mst2 - mst1;
279     long nOneValidate = msdiff.total_milliseconds();
280     if (fDebug) printf("DoS_Checksig sign: %ld\n", nOneValidate);
281
282     // ... now validating repeatedly should be quick:
283     // 2.8GHz machine, -g build: Sign takes ~760ms,
284     // uncached Verify takes ~250ms, cached Verify takes ~50ms
285     // (for 100 single-signature inputs)
286     mst1 = boost::posix_time::microsec_clock::local_time();
287     for (int i = 0; i < 5; i++)
288         for (int j = 0; j < tx.vin.size(); j++)
289             BOOST_CHECK(VerifySignature(orphans[j], tx, j, true, SIGHASH_ALL));
290     mst2 = boost::posix_time::microsec_clock::local_time();
291     msdiff = mst2 - mst1;
292     long nManyValidate = msdiff.total_milliseconds();
293     if (fDebug) printf("DoS_Checksig five: %ld\n", nManyValidate);
294
295     BOOST_CHECK_MESSAGE(nManyValidate < nOneValidate, "Signature cache timing failed");
296
297     // Empty a signature, validation should fail:
298     CScript save = tx.vin[0].scriptSig;
299     tx.vin[0].scriptSig = CScript();
300     BOOST_CHECK(!VerifySignature(orphans[0], tx, 0, true, SIGHASH_ALL));
301     tx.vin[0].scriptSig = save;
302
303     // Swap signatures, validation should fail:
304     std::swap(tx.vin[0].scriptSig, tx.vin[1].scriptSig);
305     BOOST_CHECK(!VerifySignature(orphans[0], tx, 0, true, SIGHASH_ALL));
306     BOOST_CHECK(!VerifySignature(orphans[1], tx, 1, true, SIGHASH_ALL));
307     std::swap(tx.vin[0].scriptSig, tx.vin[1].scriptSig);
308
309     // Exercise -maxsigcachesize code:
310     mapArgs["-maxsigcachesize"] = "10";
311     // Generate a new, different signature for vin[0] to trigger cache clear:
312     CScript oldSig = tx.vin[0].scriptSig;
313     BOOST_CHECK(SignSignature(keystore, orphans[0], tx, 0));
314     BOOST_CHECK(tx.vin[0].scriptSig != oldSig);
315     for (int j = 0; j < tx.vin.size(); j++)
316         BOOST_CHECK(VerifySignature(orphans[j], tx, j, true, SIGHASH_ALL));
317     mapArgs.erase("-maxsigcachesize");
318
319     LimitOrphanTxSize(0);
320 }
321
322 BOOST_AUTO_TEST_SUITE_END()