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