upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile1
-rw-r--r--src/main.cpp104
-rw-r--r--src/makefile.mingw1
-rw-r--r--src/net.h15
-rw-r--r--src/selftest.cpp65
-rw-r--r--src/walletcmd.cpp7
6 files changed, 176 insertions, 17 deletions
diff --git a/src/Makefile b/src/Makefile
index 5ded7d6..88dc08f 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -139,3 +139,4 @@ tests: test_bip32 bitflash-node
139 ./test_bip32 139 ./test_bip32
140 ./bitflash-node -selftest=wallet-keypool 140 ./bitflash-node -selftest=wallet-keypool
141 ./bitflash-node -selftest=wallet-hd 141 ./bitflash-node -selftest=wallet-hd
142 ./bitflash-node -selftest=net-message
diff --git a/src/main.cpp b/src/main.cpp
index 9082bd5..9bb3c24 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -181,6 +181,39 @@ static bool AddKeyIfMissing(const CKey& key)
181 return AddKey(key); 181 return AddKey(key);
182} 182}
183 183
184static bool DeriveNextHDWalletKey(vector<unsigned char>& vchPubKeyRet, string& strErrorRet)
185{
186 vchPubKeyRet.clear();
187 CRITICAL_BLOCK(cs_keyPool)
188 {
189 if (!HaveHDSeed())
190 {
191 strErrorRet = "no seed";
192 return false;
193 }
194
195 CKey key;
196 if (!DeriveHDKey(nHDNext, key, strErrorRet))
197 return false;
198 if (!AddKeyIfMissing(key))
199 {
200 strErrorRet = "could not write the derived key to wallet.dat";
201 return false;
202 }
203
204 unsigned int nNext = nHDNext + 1;
205 if (!CWalletDB().WriteHDNext(nNext))
206 {
207 strErrorRet = "could not advance the derivation counter";
208 return false;
209 }
210
211 nHDNext = nNext;
212 vchPubKeyRet = key.GetPubKey();
213 }
214 return true;
215}
216
184vector<unsigned char> GenerateNewKey() 217vector<unsigned char> GenerateNewKey()
185{ 218{
186 CKey key; 219 CKey key;
@@ -2310,27 +2343,55 @@ bool ProcessMessages(CNode* pfrom)
2310 if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE SKIPPED %d BYTES\n\n", (int)(pstart - vRecv.begin())); 2343 if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE SKIPPED %d BYTES\n\n", (int)(pstart - vRecv.begin()));
2311 vRecv.erase(vRecv.begin(), pstart); 2344 vRecv.erase(vRecv.begin(), pstart);
2312 2345
2313 // Read header 2346 // Read the header from a copy first. The old path consumed the header,
2347 // noticed the payload was incomplete, inserted the header back at the
2348 // front of the receive buffer, then slept inside the shared message
2349 // thread. A peer could announce a large payload and drip bytes forever,
2350 // making every pass pay for an O(n) insert and a 100 ms stall.
2314 CMessageHeader hdr; 2351 CMessageHeader hdr;
2315 vRecv >> hdr; 2352 CDataStream vHeader(vRecv.begin(), vRecv.begin() + sizeof(CMessageHeader),
2353 vRecv.nType, vRecv.nVersion);
2354 vHeader >> hdr;
2316 if (!hdr.IsValid()) 2355 if (!hdr.IsValid())
2317 { 2356 {
2318 if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE: ERRORS IN HEADER %s\n\n\n", hdr.GetCommand().c_str()); 2357 if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE: ERRORS IN HEADER %s\n\n\n", hdr.GetCommand().c_str());
2358 if (hdr.nMessageSize > MAX_PROTOCOL_MESSAGE_SIZE)
2359 pfrom->fDisconnect = true;
2360 pfrom->nIncompleteMessageStart = 0;
2361 vRecv.ignore(sizeof(CMessageHeader));
2319 continue; 2362 continue;
2320 } 2363 }
2321 string strCommand = hdr.GetCommand(); 2364 string strCommand = hdr.GetCommand();
2322 2365
2323 // Message size 2366 // Message size
2324 unsigned int nMessageSize = hdr.nMessageSize; 2367 unsigned int nMessageSize = hdr.nMessageSize;
2325 if (nMessageSize > vRecv.size()) 2368 unsigned int nPayloadAvailable = vRecv.size() - sizeof(CMessageHeader);
2369 if (nMessageSize > nPayloadAvailable)
2326 { 2370 {
2327 // Rewind and wait for rest of message 2371 if (pfrom->nIncompleteMessageStart == 0 ||
2328 ///// need a mechanism to give up waiting for overlong message size error 2372 pfrom->nIncompleteMessageSize != nMessageSize ||
2329 if (LogAcceptsCategory("net")) printf("MESSAGE-BREAK 2\n"); 2373 pfrom->strIncompleteMessageCommand != strCommand)
2330 vRecv.insert(vRecv.begin(), BEGIN(hdr), END(hdr)); 2374 {
2331 Sleep(100); 2375 pfrom->nIncompleteMessageStart = GetTime();
2376 pfrom->nIncompleteMessageSize = nMessageSize;
2377 pfrom->strIncompleteMessageCommand = strCommand;
2378 }
2379 else if (GetTime() - pfrom->nIncompleteMessageStart >
2380 BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS)
2381 {
2382 LogPrint("net",
2383 "disconnecting %s: incomplete %s message, %u of %u bytes after %d seconds\n",
2384 pfrom->addr.ToString().c_str(), strCommand.c_str(),
2385 nPayloadAvailable, nMessageSize,
2386 BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS);
2387 pfrom->fDisconnect = true;
2388 }
2332 break; 2389 break;
2333 } 2390 }
2391 pfrom->nIncompleteMessageStart = 0;
2392 pfrom->nIncompleteMessageSize = 0;
2393 pfrom->strIncompleteMessageCommand.clear();
2394 vRecv.ignore(sizeof(CMessageHeader));
2334 2395
2335 // Copy message to its own buffer 2396 // Copy message to its own buffer
2336 CDataStream vMsg(vRecv.begin(), vRecv.begin() + nMessageSize, vRecv.nType, vRecv.nVersion); 2397 CDataStream vMsg(vRecv.begin(), vRecv.begin() + nMessageSize, vRecv.nType, vRecv.nVersion);
@@ -3774,13 +3835,28 @@ bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, in
3774 // Fill vout[1] back to self with any change 3835 // Fill vout[1] back to self with any change
3775 if (nValueIn > nTotalValue) 3836 if (nValueIn > nTotalValue)
3776 { 3837 {
3777 // Use the same key as one of the coins
3778 vector<unsigned char> vchPubKey; 3838 vector<unsigned char> vchPubKey;
3779 CTransaction& txFirst = *(*setCoins.begin()); 3839 if (HaveHDSeed())
3780 foreach(const CTxOut& txout, txFirst.vout) 3840 {
3781 if (txout.IsMine()) 3841 string strError;
3782 if (ExtractPubKey(txout.scriptPubKey, true, vchPubKey)) 3842 if (!DeriveNextHDWalletKey(vchPubKey, strError))
3783 break; 3843 {
3844 printf("CreateTransaction() : could not derive a phrase-backed change key: %s\n",
3845 strError.c_str());
3846 return false;
3847 }
3848 }
3849 else
3850 {
3851 // Legacy wallets have no deterministic seed. Preserve
3852 // the 0.1.0 behaviour for them: return change to a key
3853 // already proven to belong to this wallet.
3854 CTransaction& txFirst = *(*setCoins.begin());
3855 foreach(const CTxOut& txout, txFirst.vout)
3856 if (txout.IsMine())
3857 if (ExtractPubKey(txout.scriptPubKey, true, vchPubKey))
3858 break;
3859 }
3784 if (vchPubKey.empty()) 3860 if (vchPubKey.empty())
3785 return false; 3861 return false;
3786 3862
diff --git a/src/makefile.mingw b/src/makefile.mingw
index 5be3704..5025acb 100644
--- a/src/makefile.mingw
+++ b/src/makefile.mingw
@@ -111,3 +111,4 @@ tests: test_bip32.exe bitflash.exe
111 ./test_bip32.exe 111 ./test_bip32.exe
112 ./bitflash.exe -selftest=wallet-keypool 112 ./bitflash.exe -selftest=wallet-keypool
113 ./bitflash.exe -selftest=wallet-hd 113 ./bitflash.exe -selftest=wallet-hd
114 ./bitflash.exe -selftest=net-message
diff --git a/src/net.h b/src/net.h
index 1c0c24e..37f335e 100644
--- a/src/net.h
+++ b/src/net.h
@@ -67,6 +67,13 @@ static const int BTF_RECV_TIMEOUT_SECS = 30 * 60;
67static const int BTF_SEND_STALL_SECS = 10 * 60; 67static const int BTF_SEND_STALL_SECS = 10 * 60;
68static const int BTF_HANDSHAKE_GRACE_SECS = 60; 68static const int BTF_HANDSHAKE_GRACE_SECS = 60;
69 69
70// A serialized object cannot be larger than MAX_SIZE, so no valid network
71// message needs the old 256 MB header allowance. Keep the wire cap aligned with
72// what the deserializer can accept and bound how long one peer may sit on a
73// partial payload after a complete header has arrived.
74static const unsigned int MAX_PROTOCOL_MESSAGE_SIZE = MAX_SIZE;
75static const int BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS = 2 * 60;
76
70// Ceiling on simultaneous connections. 77// Ceiling on simultaneous connections.
71// 78//
72// ThreadSocketHandler watches every peer through one select(), and select() 79// ThreadSocketHandler watches every peer through one select(), and select()
@@ -225,7 +232,7 @@ public:
225 } 232 }
226 233
227 // Message size 234 // Message size
228 if (nMessageSize > 0x10000000) 235 if (nMessageSize > MAX_PROTOCOL_MESSAGE_SIZE)
229 { 236 {
230 if (LogAcceptsCategory("net")) printf("CMessageHeader::IsValid() : nMessageSize too large %u\n", nMessageSize); 237 if (LogAcceptsCategory("net")) printf("CMessageHeader::IsValid() : nMessageSize too large %u\n", nMessageSize);
231 return false; 238 return false;
@@ -596,6 +603,9 @@ public:
596 int64 nLastSend; 603 int64 nLastSend;
597 int64 nLastRecv; 604 int64 nLastRecv;
598 int64 nLastSendEmpty; 605 int64 nLastSendEmpty;
606 int64 nIncompleteMessageStart;
607 unsigned int nIncompleteMessageSize;
608 string strIncompleteMessageCommand;
599 609
600 // Height this peer announced in its version message, or -1 if it sent a 610 // Height this peer announced in its version message, or -1 if it sent a
601 // version message that predates the field. Knowing how far along everyone 611 // version message that predates the field. Knowing how far along everyone
@@ -626,6 +636,9 @@ public:
626 nLastSend = 0; 636 nLastSend = 0;
627 nLastRecv = 0; 637 nLastRecv = 0;
628 nLastSendEmpty = GetTime(); 638 nLastSendEmpty = GetTime();
639 nIncompleteMessageStart = 0;
640 nIncompleteMessageSize = 0;
641 strIncompleteMessageCommand.clear();
629 nStartingHeight = -1; 642 nStartingHeight = -1;
630 vfSubscribe.assign(256, false); 643 vfSubscribe.assign(256, false);
631 644
diff --git a/src/selftest.cpp b/src/selftest.cpp
index 88e9863..c04adf4 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -470,6 +470,67 @@ static int RunWalletHDSelfTest()
470 return nFail == 0 ? 0 : 1; 470 return nFail == 0 ? 0 : 1;
471} 471}
472 472
473static int RunNetMessageSelfTest()
474{
475 fflush(stdout);
476 printf("net-message self-test\n");
477
478 int nFail = 0;
479 try
480 {
481 CNode complete(INVALID_SOCKET, CAddress("127.0.0.1"));
482 complete.nVersion = VERSION;
483 complete.vRecv << CMessageHeader("ping", 0);
484 nFail += Check(ProcessMessages(&complete), "a complete empty message processes") ? 0 : 1;
485 nFail += Check(complete.vRecv.empty(), "a complete message is consumed") ? 0 : 1;
486 nFail += Check(!complete.fDisconnect, "a valid ping does not disconnect the peer") ? 0 : 1;
487
488 CNode partial(INVALID_SOCKET, CAddress("127.0.0.1"));
489 partial.nVersion = VERSION;
490 partial.vRecv << CMessageHeader("block", 100);
491 unsigned int nPartialBefore = partial.vRecv.size();
492 nFail += Check(ProcessMessages(&partial), "an incomplete message returns cleanly") ? 0 : 1;
493 nFail += Check(partial.vRecv.size() == nPartialBefore,
494 "an incomplete message keeps one header in the buffer") ? 0 : 1;
495 nFail += Check(partial.nIncompleteMessageStart != 0,
496 "an incomplete message starts a timeout clock") ? 0 : 1;
497 nFail += Check(!partial.fDisconnect,
498 "a fresh incomplete message does not disconnect immediately") ? 0 : 1;
499
500 CNode stale(INVALID_SOCKET, CAddress("127.0.0.1"));
501 stale.nVersion = VERSION;
502 stale.vRecv << CMessageHeader("block", 100);
503 stale.nIncompleteMessageStart = GetTime() - BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS - 1;
504 stale.nIncompleteMessageSize = 100;
505 stale.strIncompleteMessageCommand = "block";
506 ProcessMessages(&stale);
507 nFail += Check(stale.fDisconnect,
508 "a stale incomplete message disconnects the peer") ? 0 : 1;
509
510 CNode oversized(INVALID_SOCKET, CAddress("127.0.0.1"));
511 oversized.nVersion = VERSION;
512 oversized.vRecv << CMessageHeader("block", MAX_PROTOCOL_MESSAGE_SIZE + 1);
513 ProcessMessages(&oversized);
514 nFail += Check(oversized.fDisconnect,
515 "an oversized message header disconnects the peer") ? 0 : 1;
516 }
517 catch (const std::exception& e)
518 {
519 printf(" FAIL exception: %s\n", e.what());
520 nFail++;
521 }
522 catch (...)
523 {
524 printf(" FAIL unknown exception\n");
525 nFail++;
526 }
527
528 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
529 nFail, nFail == 1 ? "" : "s");
530 fflush(stdout);
531 return nFail == 0 ? 0 : 1;
532}
533
473int RunSelfTest(const std::string& name) 534int RunSelfTest(const std::string& name)
474{ 535{
475 AttachTerminal(); 536 AttachTerminal();
@@ -478,8 +539,10 @@ int RunSelfTest(const std::string& name)
478 return RunWalletKeyPoolSelfTest(); 539 return RunWalletKeyPoolSelfTest();
479 if (name == "wallet-hd") 540 if (name == "wallet-hd")
480 return RunWalletHDSelfTest(); 541 return RunWalletHDSelfTest();
542 if (name == "net-message")
543 return RunNetMessageSelfTest();
481 544
482 printf("Unknown self-test '%s'\n", name.c_str()); 545 printf("Unknown self-test '%s'\n", name.c_str());
483 printf("Known self-tests: wallet-keypool, wallet-hd\n"); 546 printf("Known self-tests: wallet-keypool, wallet-hd, net-message\n");
484 return 1; 547 return 1;
485} 548}
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp
index 8eb3592..fae2095 100644
--- a/src/walletcmd.cpp
+++ b/src/walletcmd.cpp
@@ -25,6 +25,10 @@
25// would restore a wallet that looks empty. 25// would restore a wallet that looks empty.
26static const int RESTORE_BATCH = 100; 26static const int RESTORE_BATCH = 100;
27static const int RESTORE_MAX = 10000; 27static const int RESTORE_MAX = 10000;
28// The wallet may have a full unused key pool in front of a change address
29// created while spending pre-phrase coins. Looking only one empty batch ahead
30// can stop just before that change output.
31static const int RESTORE_MIN_SCAN = KEYPOOL_SIZE + RESTORE_BATCH;
28 32
29int CmdNewPhrase() 33int CmdNewPhrase()
30{ 34{
@@ -105,6 +109,7 @@ bool RestoreFromPhrase(const std::string& strMnemonic,
105 // up nothing. Every derived key is written to the wallet before the scan, 109 // up nothing. Every derived key is written to the wallet before the scan,
106 // because the scan asks the wallet what belongs to it. 110 // because the scan asks the wallet what belongs to it.
107 int nTotalDerived = (int)nHDNext; 111 int nTotalDerived = (int)nHDNext;
112 int nStopDepth = max(nMinDepth, RESTORE_MIN_SCAN);
108 while (nTotalDerived < RESTORE_MAX) 113 while (nTotalDerived < RESTORE_MAX)
109 { 114 {
110 // Count wallet transactions, not scan hits. 115 // Count wallet transactions, not scan hits.
@@ -146,7 +151,7 @@ bool RestoreFromPhrase(const std::string& strMnemonic,
146 151
147 // A whole batch with nothing in it means far enough -- unless the 152 // A whole batch with nothing in it means far enough -- unless the
148 // caller asked to look deeper anyway. 153 // caller asked to look deeper anyway.
149 if (nWalletAfter == nWalletBefore && nTotalDerived >= nMinDepth) 154 if (nWalletAfter == nWalletBefore && nTotalDerived >= nStopDepth)
150 break; 155 break;
151 } 156 }
152 157