mirror of
https://github.com/kaspanet/kaspad.git
synced 2026-03-22 16:13:45 +00:00
Compare commits
343 Commits
BTCD_0_3_2
...
BTCD_0_7_0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40cdacde23 | ||
|
|
264c89099f | ||
|
|
cb1f3cf48c | ||
|
|
28929ff429 | ||
|
|
8ac86f1053 | ||
|
|
7162a11995 | ||
|
|
7d35bc9460 | ||
|
|
54203d7db0 | ||
|
|
97e0149dc3 | ||
|
|
9a15453806 | ||
|
|
a293212581 | ||
|
|
66e93f5163 | ||
|
|
41da7ae606 | ||
|
|
8ebbee1f05 | ||
|
|
3a195b9100 | ||
|
|
9306270a84 | ||
|
|
12242ee589 | ||
|
|
e5a1c6e5ac | ||
|
|
f9922c7305 | ||
|
|
e20a3e9f2c | ||
|
|
33082445c5 | ||
|
|
4431fd9c0d | ||
|
|
44e3a44a9c | ||
|
|
db87b9e85f | ||
|
|
1d60e5dba5 | ||
|
|
354cc38d2d | ||
|
|
b89e93e52f | ||
|
|
642c834ada | ||
|
|
476000193f | ||
|
|
02e594f826 | ||
|
|
bc6ff038e3 | ||
|
|
6f063e0c1b | ||
|
|
c51df0ca3c | ||
|
|
75b59ef6e4 | ||
|
|
1716136f62 | ||
|
|
a39f4a0698 | ||
|
|
5d70935b04 | ||
|
|
6f5f582c42 | ||
|
|
f8c843e2e3 | ||
|
|
7cfef69f23 | ||
|
|
bcb5a21b37 | ||
|
|
238d942a69 | ||
|
|
c1df0bfce6 | ||
|
|
9f044fb946 | ||
|
|
979357c5f1 | ||
|
|
038c8fb278 | ||
|
|
71c35ec521 | ||
|
|
cdbe387545 | ||
|
|
d949072d6d | ||
|
|
aab3a6643c | ||
|
|
ba5e457c38 | ||
|
|
591b0f431d | ||
|
|
2a7d725a09 | ||
|
|
5ec951f6a7 | ||
|
|
c6d865f3b5 | ||
|
|
79ff9b76ee | ||
|
|
bd0c799e2f | ||
|
|
9c2abd1fa1 | ||
|
|
1a64f33fca | ||
|
|
ed4ffc44a1 | ||
|
|
0951ffa1c7 | ||
|
|
ea689489d3 | ||
|
|
46709bda08 | ||
|
|
8749cde081 | ||
|
|
2b9f5b8932 | ||
|
|
b532860477 | ||
|
|
82fca37eae | ||
|
|
042d9206a1 | ||
|
|
8d930ceed1 | ||
|
|
970c0cdb30 | ||
|
|
112525bd7a | ||
|
|
6c9a206709 | ||
|
|
34849a181c | ||
|
|
5cef5bc05c | ||
|
|
2697a0a9ea | ||
|
|
c0d6180685 | ||
|
|
b354015426 | ||
|
|
72afc787e6 | ||
|
|
a5e5903caf | ||
|
|
36e5aa8e92 | ||
|
|
ffe767c679 | ||
|
|
82552e4465 | ||
|
|
221352586b | ||
|
|
b9a641ab79 | ||
|
|
0bf4e0e097 | ||
|
|
d58af1c3cf | ||
|
|
fea4109eb2 | ||
|
|
dcef4128b8 | ||
|
|
5736dc05ae | ||
|
|
109ca258af | ||
|
|
136aa95446 | ||
|
|
5859deea7e | ||
|
|
8f43dc758e | ||
|
|
0d40bf901d | ||
|
|
9cb5190ac2 | ||
|
|
1487a352da | ||
|
|
d3e4bcdcf5 | ||
|
|
f12ca20372 | ||
|
|
f309e899f3 | ||
|
|
33bb455365 | ||
|
|
7ad6e235ad | ||
|
|
8c7d44c8dc | ||
|
|
0fbd962f8a | ||
|
|
737e69594b | ||
|
|
9474ef29d7 | ||
|
|
75554fab09 | ||
|
|
f089853d4d | ||
|
|
20e56d6eda | ||
|
|
871481ce1b | ||
|
|
e3122c1b1d | ||
|
|
fcdddd499f | ||
|
|
bd98836a2b | ||
|
|
d8227c2751 | ||
|
|
86600a0356 | ||
|
|
477b733ed9 | ||
|
|
051a9013ce | ||
|
|
6abad1d8ac | ||
|
|
6222b1d8cc | ||
|
|
d8ec5bd33c | ||
|
|
8b479794ab | ||
|
|
c99a227df2 | ||
|
|
427fb3cd94 | ||
|
|
aec17304a0 | ||
|
|
035f8f82b7 | ||
|
|
3a59e4d064 | ||
|
|
dacf9d77c5 | ||
|
|
4f8c2a3aaf | ||
|
|
d33e9b4165 | ||
|
|
cd3084afcd | ||
|
|
a5cc3196b4 | ||
|
|
f93c8a4af1 | ||
|
|
af0750dd76 | ||
|
|
b3f63cf35e | ||
|
|
371dfdd76a | ||
|
|
3946d84887 | ||
|
|
462bc5a031 | ||
|
|
96ded50f6b | ||
|
|
d00ccc0d3c | ||
|
|
dd7c910e86 | ||
|
|
34657d43d9 | ||
|
|
47c92c0e62 | ||
|
|
8b5277e73a | ||
|
|
ae347a6f67 | ||
|
|
8a73f9b245 | ||
|
|
92eee5cb96 | ||
|
|
4cb1500a0b | ||
|
|
47a78ea5c2 | ||
|
|
a8ff7fecb4 | ||
|
|
b6b2fd15b3 | ||
|
|
405eca4a44 | ||
|
|
082ad7caf2 | ||
|
|
aeec39c1ff | ||
|
|
30802fdd52 | ||
|
|
835cee229a | ||
|
|
9b166b3876 | ||
|
|
5ad6d543d6 | ||
|
|
426db5ae21 | ||
|
|
f2a2744bec | ||
|
|
28d08f8b16 | ||
|
|
eb624acfd4 | ||
|
|
bf7c5fa062 | ||
|
|
13868e76ec | ||
|
|
d17a97b485 | ||
|
|
3bfeabb47a | ||
|
|
bd29b12d31 | ||
|
|
ca14702e5d | ||
|
|
3faa256f75 | ||
|
|
674ef590bb | ||
|
|
f0cc672d23 | ||
|
|
ca0e38e58b | ||
|
|
e0fab228a4 | ||
|
|
af3609d861 | ||
|
|
8477ef569a | ||
|
|
67b5c2fb7e | ||
|
|
ca4cf29e49 | ||
|
|
23ff70d682 | ||
|
|
195ada0979 | ||
|
|
6be128a843 | ||
|
|
dc200d002e | ||
|
|
edf8f2f224 | ||
|
|
4c184ead54 | ||
|
|
17a9b41bef | ||
|
|
d3a7f15a87 | ||
|
|
cc9aadf041 | ||
|
|
2c81f61616 | ||
|
|
d72255bce3 | ||
|
|
8310661c29 | ||
|
|
7b304515d6 | ||
|
|
8aaad1e97b | ||
|
|
e4fa45ff08 | ||
|
|
011025dc0d | ||
|
|
7b406dcb0f | ||
|
|
c34ab6a95e | ||
|
|
bbc3c1cf7e | ||
|
|
95563691cd | ||
|
|
7df65008be | ||
|
|
2a554c43b0 | ||
|
|
f8e88df237 | ||
|
|
1145fb57ed | ||
|
|
8968f7dd74 | ||
|
|
d2d899d157 | ||
|
|
f93203b91e | ||
|
|
1e836d26f4 | ||
|
|
d2dd40aae2 | ||
|
|
a3d783e9e8 | ||
|
|
c8e88d383e | ||
|
|
9e44506fdc | ||
|
|
f1c807231d | ||
|
|
6995910df5 | ||
|
|
7654eb1eb5 | ||
|
|
22b61f634a | ||
|
|
dd10de9e8b | ||
|
|
762fc2c11c | ||
|
|
305be0c29f | ||
|
|
bbb10dc387 | ||
|
|
786409d06e | ||
|
|
f22164b261 | ||
|
|
aea23ddff3 | ||
|
|
41ecc9f835 | ||
|
|
9e57a6c5be | ||
|
|
295cc873f4 | ||
|
|
de9176b94f | ||
|
|
d1570c5d87 | ||
|
|
dfbb9446c4 | ||
|
|
45732c99fb | ||
|
|
766aae5a72 | ||
|
|
7e21226ca6 | ||
|
|
8e3ede441b | ||
|
|
6f2b96b7e2 | ||
|
|
d00ca48475 | ||
|
|
f843c141ae | ||
|
|
a9bf28af4d | ||
|
|
ef47455b05 | ||
|
|
daa5310e2f | ||
|
|
e930dc55f0 | ||
|
|
07c656c8b5 | ||
|
|
93c1f7d31b | ||
|
|
48ab97c271 | ||
|
|
eb8688df79 | ||
|
|
41d2d36643 | ||
|
|
694fccefa8 | ||
|
|
3f37e881dc | ||
|
|
0bee8478a9 | ||
|
|
4a290162ee | ||
|
|
0301690499 | ||
|
|
9643cb6d23 | ||
|
|
b1ed5f75ca | ||
|
|
9c26f6c4c5 | ||
|
|
0cc756337e | ||
|
|
f5a7dcdcbf | ||
|
|
5d13288174 | ||
|
|
9edf7d44fa | ||
|
|
d18c34a628 | ||
|
|
02e6d47590 | ||
|
|
deb19c2fa3 | ||
|
|
35eea401e1 | ||
|
|
c2bec24f51 | ||
|
|
04a2f66d64 | ||
|
|
c3fab78e2c | ||
|
|
5cd4c8265c | ||
|
|
9a853fdf90 | ||
|
|
6fcc1c9d1b | ||
|
|
e433439308 | ||
|
|
22600c7c67 | ||
|
|
5d3fcc660e | ||
|
|
75e577c82e | ||
|
|
5da5dfe1c4 | ||
|
|
5ec4aaff09 | ||
|
|
5bf879dcfc | ||
|
|
37d3d83ed3 | ||
|
|
7b7d4e8555 | ||
|
|
6b8c10d1fb | ||
|
|
527a08eb14 | ||
|
|
166f8c9ae5 | ||
|
|
9fb17c3a6d | ||
|
|
50388bcf66 | ||
|
|
e3eca752da | ||
|
|
c3a3fbcabf | ||
|
|
3902a71bee | ||
|
|
ac375df71f | ||
|
|
50484c5841 | ||
|
|
7b86bec825 | ||
|
|
6116a6cb02 | ||
|
|
3108b94401 | ||
|
|
58fdcec6e2 | ||
|
|
31a97d5c09 | ||
|
|
bb276b53aa | ||
|
|
31f27cffd5 | ||
|
|
afc520634f | ||
|
|
77e1af792b | ||
|
|
8b5413a4ac | ||
|
|
dcf2994905 | ||
|
|
89eae6f590 | ||
|
|
72c186f9a9 | ||
|
|
41838b83b0 | ||
|
|
af311078b4 | ||
|
|
d81a2c9067 | ||
|
|
ced24946a4 | ||
|
|
d403863e2b | ||
|
|
b97a2145d8 | ||
|
|
c1a1e6b6b2 | ||
|
|
6949a4f940 | ||
|
|
4d80750afe | ||
|
|
d8c5222474 | ||
|
|
8c2b9ae06e | ||
|
|
b72f0c6474 | ||
|
|
1f087adf15 | ||
|
|
53e1c2d6bd | ||
|
|
2511fee152 | ||
|
|
5a46de5103 | ||
|
|
a4794798d4 | ||
|
|
470ef8c58e | ||
|
|
70094fcee8 | ||
|
|
b866e9f14c | ||
|
|
e76fada2d2 | ||
|
|
d7d2385fb0 | ||
|
|
9442d96929 | ||
|
|
231efa35f5 | ||
|
|
4f25d45e77 | ||
|
|
59e2b204a3 | ||
|
|
da2901a4fd | ||
|
|
b1f14732b1 | ||
|
|
d647eea2b7 | ||
|
|
d26b8b2d43 | ||
|
|
c242d75866 | ||
|
|
b96cb4317a | ||
|
|
975640c6b3 | ||
|
|
3c405563bd | ||
|
|
9f96e59392 | ||
|
|
08fc3050a3 | ||
|
|
7d8bb5ab4c | ||
|
|
1aa65e6863 | ||
|
|
36bd4b8994 | ||
|
|
7b01074f5e | ||
|
|
261e61f8ee | ||
|
|
1fadf619c7 | ||
|
|
24a5645fce | ||
|
|
618b885e9e | ||
|
|
2bb8c5d5cc | ||
|
|
5a4772bdc6 | ||
|
|
8970d4bf99 | ||
|
|
cc6cfdad9e | ||
|
|
7846b2f4e2 |
4
.travis.yml
Normal file
4
.travis.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
language: go
|
||||
go:
|
||||
- release
|
||||
- tip
|
||||
303
CHANGES
303
CHANGES
@@ -3,6 +3,309 @@ User visible changes for btcd
|
||||
A full-node bitcoin implementation written in Go
|
||||
============================================================================
|
||||
|
||||
Changes in 0.7.0 (Thu Feb 20 2014)
|
||||
- Fix an issue when parsing scripts which contain a multi-signature script
|
||||
which require zero signatures such as testnet block
|
||||
000000001881dccfeda317393c261f76d09e399e15e27d280e5368420f442632
|
||||
(https://github.com/conformal/btcscript/issues/7)
|
||||
- Add check to ensure all transactions accepted to mempool only contain
|
||||
canonical data pushes (https://github.com/conformal/btcscript/issues/6)
|
||||
- Fix an issue causing excessive memory consumption
|
||||
- Significantly rework and improve the websocket notification system:
|
||||
- Each client is now independent so slow clients no longer limit the
|
||||
speed of other connected clients
|
||||
- Potentially long-running operations such as rescans are now run in
|
||||
their own handler and rate-limited to one operation at a time without
|
||||
preventing simultaneous requests from the same client for the faster
|
||||
requests or notifications
|
||||
- A couple of scenarios which could cause shutdown to hang have been
|
||||
resolved
|
||||
- Update notifynewtx notifications to support all address types instead
|
||||
of only pay-to-pubkey-hash
|
||||
- Provide a --rpcmaxwebsockets option to allow limiting the number of
|
||||
concurrent websocket clients
|
||||
- Add a new websocket command notifyallnewtxs to request notifications
|
||||
(https://github.com/conformal/btcd/issues/86) (thanks @flammit)
|
||||
- Improve btcctl utility in the following ways:
|
||||
- Add getnetworkhashps command
|
||||
- Add gettransaction command (wallet-specific)
|
||||
- Add signmessage command (wallet-specific)
|
||||
- Update getwork command to accept
|
||||
- Continue cleanup and work on implementing the RPC API:
|
||||
- Implement getnettotals command
|
||||
(https://github.com/conformal/btcd/issues/84)
|
||||
- Implement networkhashps command
|
||||
(https://github.com/conformal/btcd/issues/87)
|
||||
- Update getpeerinfo to always include syncnode field even when false
|
||||
- Remove help addenda for getpeerinfo now that it supports all fields
|
||||
- Close standard RPC connections on auth failure
|
||||
- Provide a --rpcmaxclients option to allow limiting the number of
|
||||
concurrent RPC clients (https://github.com/conformal/btcd/issues/68)
|
||||
- Include IP address in RPC auth failure log messages
|
||||
- Resolve a rather harmless data races found by the race detector
|
||||
(https://github.com/conformal/btcd/issues/94)
|
||||
- Increase block priority size and max standard transaction size to 50k
|
||||
and 100k, respectively (https://github.com/conformal/btcd/issues/71)
|
||||
- Add rate limiting of free transactions to the memory pool to prevent
|
||||
penny flooding (https://github.com/conformal/btcd/issues/40)
|
||||
- Provide a --logdir option (https://github.com/conformal/btcd/issues/95)
|
||||
- Change the default log file path to include the network
|
||||
- Add a new ScriptBuilder interface to btcscript to support creation of
|
||||
custom scripts (https://github.com/conformal/btcscript/issues/5)
|
||||
- General code cleanup
|
||||
|
||||
Changes in 0.6.0 (Tue Feb 04 2014)
|
||||
- Fix an issue when parsing scripts which contain invalid signatures that
|
||||
caused a chain fork on block
|
||||
0000000000000001e4241fd0b3469a713f41c5682605451c05d3033288fb2244
|
||||
- Correct an issue which could lead to an error in removeBlockNode
|
||||
(https://github.com/conformal/btcchain/issues/4)
|
||||
- Improve addblock utility as follows:
|
||||
- Check imported blocks against all chain rules and checkpoints
|
||||
- Skip blocks which are already known so you can stop and restart the
|
||||
import or start the import after you have already downloaded a portion
|
||||
of the chain
|
||||
- Correct an issue where the utility did not shutdown cleanly after
|
||||
processing all blocks
|
||||
- Add error on attempt to import orphan blocks
|
||||
- Improve error handling and reporting
|
||||
- Display statistics after input file has been fully processed
|
||||
- Rework, optimize, and improve headers-first mode:
|
||||
- Resuming the chain sync from any point before the final checkpoint
|
||||
will now use headers-first mode
|
||||
(https://github.com/conformal/btcd/issues/69)
|
||||
- Verify all checkpoints as opposed to only the final one
|
||||
- Reduce and bound memory usage
|
||||
- Rollback to the last known good point when a header does not match a
|
||||
checkpoint
|
||||
- Log information about what is happening with headers
|
||||
- Improve btcctl utility in the following ways:
|
||||
- Add getaddednodeinfo command
|
||||
- Add getnettotals command
|
||||
- Add getblocktemplate command (wallet-specific)
|
||||
- Add getwork command (wallet-specific)
|
||||
- Add getnewaddress command (wallet-specific)
|
||||
- Add walletpassphrasechange command (wallet-specific)
|
||||
- Add walletlock command (wallet-specific)
|
||||
- Add sendfrom command (wallet-specific)
|
||||
- Add sendmany command (wallet-specific)
|
||||
- Add settxfee command (wallet-specific)
|
||||
- Add listsinceblock command (wallet-specific)
|
||||
- Add listaccounts command (wallet-specific)
|
||||
- Add keypoolrefill command (wallet-specific)
|
||||
- Add getreceivedbyaccount command (wallet-specific)
|
||||
- Add getrawchangeaddress command (wallet-specific)
|
||||
- Add gettxoutsetinfo command (wallet-specific)
|
||||
- Add listaddressgroupings command (wallet-specific)
|
||||
- Add listlockunspent command (wallet-specific)
|
||||
- Add listlock command (wallet-specific)
|
||||
- Add listreceivedbyaccount command (wallet-specific)
|
||||
- Add validateaddress command (wallet-specific)
|
||||
- Add verifymessage command (wallet-specific)
|
||||
- Add sendtoaddress command (wallet-specific)
|
||||
- Continue cleanup and work on implementing the RPC API:
|
||||
- Implement submitblock command
|
||||
(https://github.com/conformal/btcd/issues/61)
|
||||
- Implement help command
|
||||
- Implement ping command
|
||||
- Implement getaddednodeinfo command
|
||||
(https://github.com/conformal/btcd/issues/78)
|
||||
- Implement getinfo command
|
||||
- Update getpeerinfo to support bytesrecv and bytessent
|
||||
(https://github.com/conformal/btcd/issues/83)
|
||||
- Improve and correct several RPC server and websocket areas:
|
||||
- Change the connection endpoint for websockets from /wallet to /ws
|
||||
(https://github.com/conformal/btcd/issues/80)
|
||||
- Implement an alternative authentication for websockets so clients
|
||||
such as javascript from browsers that don't support setting HTTP
|
||||
headers can authenticate (https://github.com/conformal/btcd/issues/77)
|
||||
- Add an authentication deadline for RPC connections
|
||||
(https://github.com/conformal/btcd/issues/68)
|
||||
- Use standard authentication failure responses for RPC connections
|
||||
- Make automatically generated certificate more standard so it works
|
||||
from client such as node.js and Firefox
|
||||
- Correct some minor issues which could prevent the RPC server from
|
||||
shutting down in an orderly fashion
|
||||
- Make all websocket notifications require registration
|
||||
- Change the data sent over websockets to text since it is JSON-RPC
|
||||
- Allow connections that do not have an Origin header set
|
||||
- Expose and track the number of bytes read and written per peer
|
||||
(https://github.com/conformal/btcwire/issues/6)
|
||||
- Correct an issue with sendrawtransaction when invoked via websockets
|
||||
which prevented a minedtx notification from being added
|
||||
- Rescan operations issued from remote wallets are no stopped when
|
||||
the wallet disconnects mid-operation
|
||||
(https://github.com/conformal/btcd/issues/66)
|
||||
- Several optimizations related to fetching block information from the
|
||||
database
|
||||
- General code cleanup
|
||||
|
||||
Changes in 0.5.0 (Mon Jan 13 2014)
|
||||
- Optimize initial block download by introducing a new mode which
|
||||
downloads the block headers first (up to the final checkpoint)
|
||||
- Improve peer handling to remove the potential for slow peers to cause
|
||||
sluggishness amongst all peers
|
||||
(https://github.com/conformal/btcd/issues/63)
|
||||
- Fix an issue where the initial block sync could stall when the sync peer
|
||||
disconnects (https://github.com/conformal/btcd/issues/62)
|
||||
- Correct an issue where --externalip was doing a DNS lookup on the full
|
||||
host:port instead of just the host portion
|
||||
(https://github.com/conformal/btcd/issues/38)
|
||||
- Fix an issue which could lead to a panic on chain switches
|
||||
(https://github.com/conformal/btcd/issues/70)
|
||||
- Improve btcctl utility in the following ways:
|
||||
- Show getdifficulty output as floating point to 6 digits of precision
|
||||
- Show all JSON object replies formatted as standard JSON
|
||||
- Allow btcctl getblock to accept optional params
|
||||
- Add getaccount command (wallet-specific)
|
||||
- Add getaccountaddress command (wallet-specific)
|
||||
- Add sendrawtransaction command
|
||||
- Continue cleanup and work on implementing RPC API calls
|
||||
- Update getrawmempool to support new optional verbose flag
|
||||
- Update getrawtransaction to match the reference client
|
||||
- Update getblock to support new optional verbose flag
|
||||
- Update raw transactions to fully match the reference client including
|
||||
support for all transaction types and address types
|
||||
- Correct getrawmempool fee field to return BTC instead of Satoshi
|
||||
- Correct getpeerinfo service flag to return 8 digit string so it
|
||||
matches the reference client
|
||||
- Correct verifychain to return a boolean
|
||||
- Implement decoderawtransaction command
|
||||
- Implement createrawtransaction command
|
||||
- Implement decodescript command
|
||||
- Implement gethashespersec command
|
||||
- Allow RPC handler overrides when invoked via a websocket versus
|
||||
legacy connection
|
||||
- Add new DNS seed for peer discovery
|
||||
- Display user agent on new valid peer log message
|
||||
(https://github.com/conformal/btcd/issues/64)
|
||||
- Notify wallet when new transactions that pay to registered addresses
|
||||
show up in the mempool before being mined into a block
|
||||
- Support a tor-specific proxy in addition to a normal proxy
|
||||
(https://github.com/conformal/btcd/issues/47)
|
||||
- Remove deprecated sqlite3 imports from utilities
|
||||
- Remove leftover profile write from addblock utility
|
||||
- Quite a bit of code cleanup and refactoring to improve maintainability
|
||||
|
||||
Changes in 0.4.0 (Thu Dec 12 2013)
|
||||
- Allow listen interfaces to be specified via --listen instead of only the
|
||||
port (https://github.com/conformal/btcd/issues/33)
|
||||
- Allow listen interfaces for the RPC server to be specified via
|
||||
--rpclisten instead of only the port
|
||||
(https://github.com/conformal/btcd/issues/34)
|
||||
- Only disable listening when --connect or --proxy are used when no
|
||||
--listen interface are specified
|
||||
(https://github.com/conformal/btcd/issues/10)
|
||||
- Add several new standard transaction checks to transaction memory pool:
|
||||
- Support nulldata scripts as standard
|
||||
- Only allow a max of one nulldata output per transaction
|
||||
- Enforce a maximum of 3 public keys in multi-signature transactions
|
||||
- The number of signatures in multi-signature transactions must not
|
||||
exceed the number of public keys
|
||||
- The number of inputs to a signature script must match the expected
|
||||
number of inputs for the script type
|
||||
- The number of inputs pushed onto the stack by a redeeming signature
|
||||
script must match the number of inputs consumed by the referenced
|
||||
public key script
|
||||
- When a block is connected, remove any transactions from the memory pool
|
||||
which are now double spends as a result of the newly connected
|
||||
transactions
|
||||
- Don't relay transactions resurrected during a chain switch since
|
||||
other peers will also be switching chains and therefore already know
|
||||
about them
|
||||
- Cleanup a few cases where rejected transactions showed as an error
|
||||
rather than as a rejected transaction
|
||||
- Ignore the default configuration file when --regtest (regression test
|
||||
mode) is specified
|
||||
- Implement TLS support for RPC including automatic certificate generation
|
||||
- Support HTTP authentication headers for web sockets
|
||||
- Update address manager to recognize and properly work with Tor
|
||||
addresses (https://github.com/conformal/btcd/issues/36) and
|
||||
(https://github.com/conformal/btcd/issues/37)
|
||||
- Improve btcctl utility in the following ways:
|
||||
- Add the ability to specify a configuration file
|
||||
- Add a default entry for the RPC cert to point to the location
|
||||
it will likely be in the btcd home directory
|
||||
- Implement --version flag
|
||||
- Provide a --notls option to support non-TLS configurations
|
||||
- Fix a couple of minor races found by the Go race detector
|
||||
- Improve logging
|
||||
- Allow logging level to be specified on a per subsystem basis
|
||||
(https://github.com/conformal/btcd/issues/48)
|
||||
- Allow logging levels to be dynamically changed via RPC
|
||||
(https://github.com/conformal/btcd/issues/15)
|
||||
- Implement a rolling log file with a max of 10MB per file and a
|
||||
rotation size of 3 which results in a max logging size of 30 MB
|
||||
- Correct a minor issue with the rescanning websocket call
|
||||
(https://github.com/conformal/btcd/issues/54)
|
||||
- Fix a race with pushing address messages that could lead to a panic
|
||||
(https://github.com/conformal/btcd/issues/58)
|
||||
- Improve which external IP address is reported to peers based on which
|
||||
interface they are connected through
|
||||
(https://github.com/conformal/btcd/issues/35)
|
||||
- Add --externalip option to allow an external IP address to be specified
|
||||
for cases such as tor hidden services or advanced network configurations
|
||||
(https://github.com/conformal/btcd/issues/38)
|
||||
- Add --upnp option to support automatic port mapping via UPnP
|
||||
(https://github.com/conformal/btcd/issues/51)
|
||||
- Update Ctrl+C interrupt handler to properly sync address manager and
|
||||
remove the UPnP port mapping (if needed)
|
||||
- Continue cleanup and work on implementing RPC API calls
|
||||
- Add importprivkey (import private key) command to btcctl
|
||||
- Update getrawtransaction to provide addresses properly, support
|
||||
new verbose param, and match the reference implementation with the
|
||||
exception of MULTISIG (thanks @flammit)
|
||||
- Update getblock with new verbose flag (thanks @flammit)
|
||||
- Add listtransactions command to btcctl
|
||||
- Add getbalance command to btcctl
|
||||
- Add basic support for btcd to run as a native Windows service
|
||||
(https://github.com/conformal/btcd/issues/42)
|
||||
- Package addblock utility with Windows MSIs
|
||||
- Add support for TravisCI (continuous build integration)
|
||||
- Cleanup some documentation and usage
|
||||
- Several other minor bug fixes and general code cleanup
|
||||
|
||||
Changes in 0.3.3 (Wed Nov 13 2013)
|
||||
- Significantly improve initial block chain download speed
|
||||
(https://github.com/conformal/btcd/issues/20)
|
||||
- Add a new checkpoint at block height 267300
|
||||
- Optimize most recently used inventory handling
|
||||
(https://github.com/conformal/btcd/issues/21)
|
||||
- Optimize duplicate transaction input check
|
||||
(https://github.com/conformal/btcchain/issues/2)
|
||||
- Optimize transaction hashing
|
||||
(https://github.com/conformal/btcd/issues/25)
|
||||
- Rework and optimize wallet listener notifications
|
||||
(https://github.com/conformal/btcd/issues/22)
|
||||
- Optimize serialization and deserialization
|
||||
(https://github.com/conformal/btcd/issues/27)
|
||||
- Add support for minimum transaction fee to memory pool acceptance
|
||||
(https://github.com/conformal/btcd/issues/29)
|
||||
- Improve leveldb database performance by removing explicit GC call
|
||||
- Fix an issue where Ctrl+C was not always finishing orderly database
|
||||
shutdown
|
||||
- Fix an issue in the script handling for OP_CHECKSIG
|
||||
- Impose max limits on all variable length protocol entries to prevent
|
||||
abuse from malicious peers
|
||||
- Enforce DER signatures for transactions allowed into the memory pool
|
||||
- Separate the debug profile http server from the RPC server
|
||||
- Rework of the RPC code to improve performance and make the code cleaner
|
||||
- The getrawtransaction RPC call now properly checks the memory pool
|
||||
before consulting the db (https://github.com/conformal/btcd/issues/26)
|
||||
- Add support for the following RPC calls: getpeerinfo, getconnectedcount,
|
||||
addnode, verifychain
|
||||
(https://github.com/conformal/btcd/issues/13)
|
||||
(https://github.com/conformal/btcd/issues/17)
|
||||
- Implement rescan websocket extension to allow wallet rescans
|
||||
- Use correct paths for application data storage for all supported
|
||||
operating systems (https://github.com/conformal/btcd/issues/30)
|
||||
- Add a default redirect to the http profiling page when accessing the
|
||||
http profile server
|
||||
- Add a new --cpuprofile option which can be used to generate CPU
|
||||
profiling data on platforms that support it
|
||||
- Several other minor performance optimizations
|
||||
- Other minor bug fixes and general code cleanup
|
||||
|
||||
Changes in 0.3.2 (Tue Oct 22 2013)
|
||||
- Fix an issue that could cause the download of the block chain to stall
|
||||
(https://github.com/conformal/btcd/issues/12)
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2013 Conformal Systems LLC.
|
||||
Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
|
||||
32
README.md
32
README.md
@@ -1,6 +1,9 @@
|
||||
btcd
|
||||
====
|
||||
|
||||
[]
|
||||
(https://travis-ci.org/conformal/btcd)
|
||||
|
||||
btcd is an alternative full node bitcoin implementation written in Go (golang).
|
||||
|
||||
This project is currently under active development and is in an Alpha state.
|
||||
@@ -22,8 +25,14 @@ One key difference between btcd and bitcoind is that btcd does *NOT* include
|
||||
wallet functionality and this was a very intentional design decision. See the
|
||||
blog entry [here](https://blog.conformal.com/btcd-not-your-moms-bitcoin-daemon)
|
||||
for more details. This means you can't actually make or receive payments
|
||||
directly with btcd. That functionality will be provided by the forthcoming
|
||||
btcwallet and btcgui.
|
||||
directly with btcd. That functionality is provided by the
|
||||
[btcwallet](https://github.com/conformal/btcwallet) and
|
||||
[btcgui](https://github.com/conformal/btcgui) projects which are both under
|
||||
active development.
|
||||
|
||||
## Requirements
|
||||
|
||||
[Go](http://golang.org) 1.2 or newer.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -76,19 +85,14 @@ $ ./btcd
|
||||
|
||||
To subscribe to a given list, send email to list+subscribe@opensource.conformal.com
|
||||
|
||||
## TODO
|
||||
## Issue Tracker
|
||||
|
||||
The following is a brief overview of the next things we have planned to work on
|
||||
for btcd. Note this does not include the separate btcwallet and btcgui which
|
||||
are currently under heavy development:
|
||||
The [integrated github issue tracker](https://github.com/conformal/btcd/issues)
|
||||
is used for this project.
|
||||
|
||||
- Documentation
|
||||
- Code cleanup
|
||||
- Add remaining missing RPC calls
|
||||
- Add option to allow btcd run as a daemon/service
|
||||
- Complete several TODO items in the code
|
||||
- Offer 32-bit MSI as well as the 64-bit one
|
||||
- Offer cross-compiled binaries for popular OSes (Fedora, Ubuntu, FreeBSD, OpenBSD)
|
||||
## Documentation
|
||||
|
||||
The documentation is a work-in-progress. It uses the [github wiki](https://github.com/conformal/btcd/wiki) facility.
|
||||
|
||||
## GPG Verification Key
|
||||
|
||||
@@ -112,4 +116,4 @@ signature perform the following:
|
||||
|
||||
## License
|
||||
|
||||
btcd is licensed under the liberal ISC License.
|
||||
btcd is licensed under the [copyfree](http://copyfree.org) ISC License.
|
||||
|
||||
296
addrmanager.go
296
addrmanager.go
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
crand "crypto/rand" // for seeding
|
||||
"encoding/base32"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -19,6 +20,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -138,7 +140,11 @@ func (a *AddrManager) updateAddress(netAddr, srcAddr *btcwire.NetAddress) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ka = &knownAddress{na: netAddr, srcAddr: srcAddr}
|
||||
// Make a copy of the net address to avoid races since it is
|
||||
// updated elsewhere in the addrmanager code and would otherwise
|
||||
// change the actual netaddress on the peer.
|
||||
netAddrCopy := *netAddr
|
||||
ka = &knownAddress{na: &netAddrCopy, srcAddr: srcAddr}
|
||||
a.addrIndex[addr] = ka
|
||||
a.nNew++
|
||||
// XXX time penalty?
|
||||
@@ -153,7 +159,7 @@ func (a *AddrManager) updateAddress(netAddr, srcAddr *btcwire.NetAddress) {
|
||||
|
||||
// Enforce max addresses.
|
||||
if len(a.addrNew[bucket]) > newBucketSize {
|
||||
log.Tracef("AMGR: new bucket is full, expiring old ")
|
||||
amgrLog.Tracef("new bucket is full, expiring old ")
|
||||
a.expireNew(bucket)
|
||||
}
|
||||
|
||||
@@ -161,7 +167,7 @@ func (a *AddrManager) updateAddress(netAddr, srcAddr *btcwire.NetAddress) {
|
||||
ka.refs++
|
||||
a.addrNew[bucket][addr] = ka
|
||||
|
||||
log.Tracef("AMGR: Added new address %s for a total of %d addresses",
|
||||
amgrLog.Tracef("Added new address %s for a total of %d addresses",
|
||||
addr, a.nTried+a.nNew)
|
||||
}
|
||||
|
||||
@@ -259,7 +265,7 @@ func (a *AddrManager) expireNew(bucket int) {
|
||||
var oldest *knownAddress
|
||||
for k, v := range a.addrNew[bucket] {
|
||||
if bad(v) {
|
||||
log.Tracef("AMGR: expiring bad address %v", k)
|
||||
amgrLog.Tracef("expiring bad address %v", k)
|
||||
delete(a.addrNew[bucket], k)
|
||||
v.refs--
|
||||
if v.refs == 0 {
|
||||
@@ -277,7 +283,7 @@ func (a *AddrManager) expireNew(bucket int) {
|
||||
|
||||
if oldest != nil {
|
||||
key := NetAddressKey(oldest.na)
|
||||
log.Tracef("AMGR: expiring oldest address %v", key)
|
||||
amgrLog.Tracef("expiring oldest address %v", key)
|
||||
|
||||
delete(a.addrNew[bucket], key)
|
||||
oldest.refs--
|
||||
@@ -320,18 +326,20 @@ type knownAddress struct {
|
||||
// AddrManager provides a concurrency safe address manager for caching potential
|
||||
// peers on the bitcoin network.
|
||||
type AddrManager struct {
|
||||
mtx sync.Mutex
|
||||
rand *rand.Rand
|
||||
key [32]byte
|
||||
addrIndex map[string]*knownAddress // address key to ka for all addrs.
|
||||
addrNew [newBucketCount]map[string]*knownAddress
|
||||
addrTried [triedBucketCount]*list.List
|
||||
started int32
|
||||
shutdown int32
|
||||
wg sync.WaitGroup
|
||||
quit chan bool
|
||||
nTried int
|
||||
nNew int
|
||||
mtx sync.Mutex
|
||||
rand *rand.Rand
|
||||
key [32]byte
|
||||
addrIndex map[string]*knownAddress // address key to ka for all addrs.
|
||||
addrNew [newBucketCount]map[string]*knownAddress
|
||||
addrTried [triedBucketCount]*list.List
|
||||
started int32
|
||||
shutdown int32
|
||||
wg sync.WaitGroup
|
||||
quit chan bool
|
||||
nTried int
|
||||
nNew int
|
||||
lamtx sync.Mutex
|
||||
localAddresses map[string]*localAddress
|
||||
}
|
||||
|
||||
func (a *AddrManager) getNewBucket(netAddr, srcAddr *btcwire.NetAddress) int {
|
||||
@@ -393,7 +401,7 @@ out:
|
||||
dumpAddressTicker.Stop()
|
||||
a.savePeers()
|
||||
a.wg.Done()
|
||||
log.Trace("AMGR: Address handler done")
|
||||
amgrLog.Trace("Address handler done")
|
||||
}
|
||||
|
||||
type serialisedKnownAddress struct {
|
||||
@@ -465,7 +473,7 @@ func (a *AddrManager) savePeers() {
|
||||
|
||||
w, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
log.Error("Error opening file: ", filePath, err)
|
||||
amgrLog.Error("Error opening file: ", filePath, err)
|
||||
return
|
||||
}
|
||||
enc := json.NewEncoder(w)
|
||||
@@ -485,19 +493,17 @@ func (a *AddrManager) loadPeers() {
|
||||
|
||||
err := a.deserialisePeers(filePath)
|
||||
if err != nil {
|
||||
log.Errorf("AMGR: Failed to parse %s: %v", filePath,
|
||||
err)
|
||||
amgrLog.Errorf("Failed to parse %s: %v", filePath, err)
|
||||
// if it is invalid we nuke the old one unconditionally.
|
||||
err = os.Remove(filePath)
|
||||
if err != nil {
|
||||
log.Warn("Failed to remove corrupt peers "+
|
||||
amgrLog.Warn("Failed to remove corrupt peers "+
|
||||
"file: ", err)
|
||||
}
|
||||
a.reset()
|
||||
return
|
||||
}
|
||||
log.Infof("AMGR: Loaded %d addresses from '%s'", a.nNew+a.nTried,
|
||||
filePath)
|
||||
amgrLog.Infof("Loaded %d addresses from '%s'", a.nNew+a.nTried, filePath)
|
||||
}
|
||||
|
||||
func (a *AddrManager) deserialisePeers(filePath string) error {
|
||||
@@ -593,14 +599,12 @@ func deserialiseNetAddress(addr string) (*btcwire.NetAddress, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
port, err := strconv.ParseUint(portStr, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
na := btcwire.NewNetAddressIPPort(ip, uint16(port),
|
||||
btcwire.SFNodeNetwork)
|
||||
return na, nil
|
||||
|
||||
return hostToNetAddress(host, uint16(port), btcwire.SFNodeNetwork)
|
||||
}
|
||||
|
||||
// Start begins the core address handler which manages a pool of known
|
||||
@@ -611,7 +615,7 @@ func (a *AddrManager) Start() {
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("AMGR: Starting address manager")
|
||||
amgrLog.Trace("Starting address manager")
|
||||
|
||||
a.wg.Add(1)
|
||||
|
||||
@@ -625,12 +629,12 @@ func (a *AddrManager) Start() {
|
||||
// Stop gracefully shuts down the address manager by stopping the main handler.
|
||||
func (a *AddrManager) Stop() error {
|
||||
if atomic.AddInt32(&a.shutdown, 1) != 1 {
|
||||
log.Warnf("AMGR: Address manager is already in the process of " +
|
||||
amgrLog.Warnf("Address manager is already in the process of " +
|
||||
"shutting down")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infof("AMGR: Address manager shutting down")
|
||||
amgrLog.Infof("Address manager shutting down")
|
||||
close(a.quit)
|
||||
a.wg.Wait()
|
||||
return nil
|
||||
@@ -660,7 +664,7 @@ func (a *AddrManager) AddAddressByIP(addrIP string) {
|
||||
// Split IP and port
|
||||
addr, portStr, err := net.SplitHostPort(addrIP)
|
||||
if err != nil {
|
||||
log.Warnf("AMGR: AddADddressByIP given bullshit adddress"+
|
||||
amgrLog.Warnf("AddADddressByIP given bullshit adddress"+
|
||||
"(%s): %v", err)
|
||||
return
|
||||
}
|
||||
@@ -669,12 +673,12 @@ func (a *AddrManager) AddAddressByIP(addrIP string) {
|
||||
na.Timestamp = time.Now()
|
||||
na.IP = net.ParseIP(addr)
|
||||
if na.IP == nil {
|
||||
log.Error("AMGR: Invalid ip address:", addr)
|
||||
amgrLog.Error("Invalid ip address:", addr)
|
||||
return
|
||||
}
|
||||
port, err := strconv.ParseUint(portStr, 10, 0)
|
||||
if err != nil {
|
||||
log.Error("AMGR: Invalid port: ", portStr, err)
|
||||
amgrLog.Error("Invalid port: ", portStr, err)
|
||||
return
|
||||
}
|
||||
na.Port = uint16(port)
|
||||
@@ -710,7 +714,8 @@ func (a *AddrManager) AddressCache() []*btcwire.NetAddress {
|
||||
i := 0
|
||||
// Iteration order is undefined here, but we randomise it anyway.
|
||||
for _, v := range a.addrIndex {
|
||||
allAddr[i] = v.na
|
||||
copyNa := *v.na
|
||||
allAddr[i] = ©Na
|
||||
i++
|
||||
}
|
||||
// Fisher-Yates shuffle the array
|
||||
@@ -748,18 +753,63 @@ func (a *AddrManager) reset() {
|
||||
// Use Start to begin processing asynchronous address updates.
|
||||
func NewAddrManager() *AddrManager {
|
||||
am := AddrManager{
|
||||
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
quit: make(chan bool),
|
||||
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
quit: make(chan bool),
|
||||
localAddresses: make(map[string]*localAddress),
|
||||
}
|
||||
am.reset()
|
||||
return &am
|
||||
}
|
||||
|
||||
// hostToNetAddress returns a netaddress given a host address. If the address is
|
||||
// a tor .onion address this will be taken care of. else if the host is not an
|
||||
// IP address it will be resolved (via tor if required).
|
||||
func hostToNetAddress(host string, port uint16, services btcwire.ServiceFlag) (*btcwire.NetAddress, error) {
|
||||
// tor address is 16 char base32 + ".onion"
|
||||
var ip net.IP
|
||||
if len(host) == 22 && host[16:] == ".onion" {
|
||||
// go base32 encoding uses capitals (as does the rfc
|
||||
// but tor and bitcoind tend to user lowercase, so we switch
|
||||
// case here.
|
||||
data, err := base32.StdEncoding.DecodeString(
|
||||
strings.ToUpper(host[:16]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prefix := []byte{0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43}
|
||||
ip = net.IP(append(prefix, data...))
|
||||
} else if ip = net.ParseIP(host); ip == nil {
|
||||
ips, err := btcdLookup(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
return nil, fmt.Errorf("No addresses found for %s", host)
|
||||
}
|
||||
ip = ips[0]
|
||||
}
|
||||
|
||||
return btcwire.NewNetAddressIPPort(ip, port, services), nil
|
||||
}
|
||||
|
||||
// ipString returns a string for the ip from the provided NetAddress. If the
|
||||
// ip is in the range used for tor addresses then it will be transformed into
|
||||
// the relavent .onion address.
|
||||
func ipString(na *btcwire.NetAddress) string {
|
||||
if Tor(na) {
|
||||
// We know now that na.IP is long enogh.
|
||||
base32 := base32.StdEncoding.EncodeToString(na.IP[6:])
|
||||
return strings.ToLower(base32) + ".onion"
|
||||
} else {
|
||||
return na.IP.String()
|
||||
}
|
||||
}
|
||||
|
||||
// NetAddressKey returns a string key in the form of ip:port for IPv4 addresses
|
||||
// or [ip]:port for IPv6 addresses.
|
||||
func NetAddressKey(na *btcwire.NetAddress) string {
|
||||
port := strconv.FormatUint(uint64(na.Port), 10)
|
||||
addr := net.JoinHostPort(na.IP.String(), port)
|
||||
addr := net.JoinHostPort(ipString(na), port)
|
||||
return addr
|
||||
}
|
||||
|
||||
@@ -809,8 +859,8 @@ func (a *AddrManager) GetAddress(class string, newBias int) *knownAddress {
|
||||
ka := e.Value.(*knownAddress)
|
||||
randval := a.rand.Intn(large)
|
||||
if float64(randval) < (factor * chance(ka) * float64(large)) {
|
||||
log.Tracef("AMGR: Selected %v from tried "+
|
||||
"bucket", NetAddressKey(ka.na))
|
||||
amgrLog.Tracef("Selected %v from tried bucket",
|
||||
NetAddressKey(ka.na))
|
||||
return ka
|
||||
}
|
||||
factor *= 1.2
|
||||
@@ -837,7 +887,7 @@ func (a *AddrManager) GetAddress(class string, newBias int) *knownAddress {
|
||||
}
|
||||
randval := a.rand.Intn(large)
|
||||
if float64(randval) < (factor * chance(ka) * float64(large)) {
|
||||
log.Tracef("AMGR: Selected %v from new bucket",
|
||||
amgrLog.Tracef("Selected %v from new bucket",
|
||||
NetAddressKey(ka.na))
|
||||
return ka
|
||||
}
|
||||
@@ -972,7 +1022,7 @@ func (a *AddrManager) Good(addr *btcwire.NetAddress) {
|
||||
a.nNew++
|
||||
|
||||
rmkey := NetAddressKey(rmka.na)
|
||||
log.Tracef("AMGR: replacing %s with %s in tried", rmkey, addrKey)
|
||||
amgrLog.Tracef("Replacing %s with %s in tried", rmkey, addrKey)
|
||||
|
||||
// We made sure there is space here just above.
|
||||
a.addrNew[newBucket][rmkey] = rmka
|
||||
@@ -1063,17 +1113,19 @@ func RFC6145(na *btcwire.NetAddress) bool {
|
||||
return rfc6145.Contains(na.IP)
|
||||
}
|
||||
|
||||
var onioncatrange = net.IPNet{IP: net.ParseIP("FD87:d87e:eb43::"),
|
||||
Mask: net.CIDRMask(48, 128)}
|
||||
|
||||
func Tor(na *btcwire.NetAddress) bool {
|
||||
// bitcoind encodes a .onion address as a 16 byte number by decoding the
|
||||
// address prior to the .onion (i.e. the key hash) base32 into a ten
|
||||
// byte number. it then stores the first 6 bytes of the address as
|
||||
// 0xfD, 0x87, 0xD8, 0x7e, 0xeb, 0x43
|
||||
// making the format
|
||||
// this is the same range used by onioncat, part of the
|
||||
// RFC4193 Unique local IPv6 range.
|
||||
// In summary the format is:
|
||||
// { magic 6 bytes, 10 bytes base32 decode of key hash }
|
||||
// Since we use btcwire.NetAddress to represent and address we may
|
||||
// well have to emulate this.
|
||||
// XXX fillmein
|
||||
return false
|
||||
return onioncatrange.Contains(na.IP)
|
||||
}
|
||||
|
||||
var zero4 = net.IPNet{IP: net.ParseIP("0.0.0.0"),
|
||||
@@ -1092,7 +1144,7 @@ func Valid(na *btcwire.NetAddress) bool {
|
||||
// invalid protocol addresses from earlier versions of bitcoind (before
|
||||
// 0.2.9), however, since protocol versions before 70001 are
|
||||
// disconnected by the bitcoin network now we have elided it.
|
||||
return !(na.IP.IsUnspecified() || RFC3849(na) ||
|
||||
return na.IP != nil && !(na.IP.IsUnspecified() || RFC3849(na) ||
|
||||
na.IP.Equal(net.IPv4bcast))
|
||||
}
|
||||
|
||||
@@ -1100,8 +1152,9 @@ func Valid(na *btcwire.NetAddress) bool {
|
||||
// not. This is true as long as the address is valid and is not in any reserved
|
||||
// ranges.
|
||||
func Routable(na *btcwire.NetAddress) bool {
|
||||
// TODO(oga) bitcoind doesn't include RFC3849 here, but should we?
|
||||
return Valid(na) && !(RFC1918(na) || RFC3927(na) || RFC4862(na) ||
|
||||
RFC4193(na) || Tor(na) || RFC4843(na) || Local(na))
|
||||
(RFC4193(na) && !Tor(na)) || RFC4843(na) || Local(na))
|
||||
}
|
||||
|
||||
// GroupKey returns a string representing the network group an address
|
||||
@@ -1140,9 +1193,9 @@ func GroupKey(na *btcwire.NetAddress) string {
|
||||
}
|
||||
return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String()
|
||||
}
|
||||
// XXX tor?
|
||||
if Tor(na) {
|
||||
panic("oga should have implemented me")
|
||||
// group is keyed off the first 4 bits of the actual onion key.
|
||||
return fmt.Sprintf("tor:%d", na.IP[6]&((1<<4)-1))
|
||||
}
|
||||
|
||||
// OK, so now we know ourselves to be a IPv6 address.
|
||||
@@ -1159,3 +1212,142 @@ func GroupKey(na *btcwire.NetAddress) string {
|
||||
|
||||
return (&net.IPNet{IP: na.IP, Mask: net.CIDRMask(bits, 128)}).String()
|
||||
}
|
||||
|
||||
// addressPrio is an enum type used to describe the heirarchy of local address
|
||||
// discovery methods.
|
||||
type addressPrio int
|
||||
|
||||
const (
|
||||
InterfacePrio addressPrio = iota // address of local interface.
|
||||
BoundPrio // Address explicitly bound to.
|
||||
UpnpPrio // External IP discovered from UPnP
|
||||
HttpPrio // Obtained from internet service.
|
||||
ManualPrio // provided by --externalip.
|
||||
)
|
||||
|
||||
type localAddress struct {
|
||||
na *btcwire.NetAddress
|
||||
score addressPrio
|
||||
}
|
||||
|
||||
// addLocalAddress adds na to the list of known local addresses to advertise
|
||||
// with the given priority.
|
||||
func (a *AddrManager) addLocalAddress(na *btcwire.NetAddress,
|
||||
priority addressPrio) {
|
||||
// sanity check.
|
||||
if !Routable(na) {
|
||||
amgrLog.Debugf("rejecting address %s:%d due to routability",
|
||||
na.IP, na.Port)
|
||||
return
|
||||
}
|
||||
amgrLog.Debugf("adding address %s:%d",
|
||||
na.IP, na.Port)
|
||||
|
||||
a.lamtx.Lock()
|
||||
defer a.lamtx.Unlock()
|
||||
|
||||
key := NetAddressKey(na)
|
||||
la, ok := a.localAddresses[key]
|
||||
if !ok || la.score < priority {
|
||||
if ok {
|
||||
la.score = priority + 1
|
||||
} else {
|
||||
a.localAddresses[key] = &localAddress{
|
||||
na: na,
|
||||
score: priority,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getReachabilityFrom returns the relative reachability of na from fromna.
|
||||
func getReachabilityFrom(na, fromna *btcwire.NetAddress) int {
|
||||
const (
|
||||
Unreachable = 0
|
||||
Default = iota
|
||||
Teredo
|
||||
Ipv6_weak
|
||||
Ipv4
|
||||
Ipv6_strong
|
||||
Private
|
||||
)
|
||||
if !Routable(fromna) {
|
||||
return Unreachable
|
||||
} else if Tor(fromna) {
|
||||
if Tor(na) {
|
||||
return Private
|
||||
} else if Routable(na) && na.IP.To4() != nil {
|
||||
return Ipv4
|
||||
} else {
|
||||
return Default
|
||||
}
|
||||
} else if RFC4380(fromna) {
|
||||
if !Routable(na) {
|
||||
return Default
|
||||
} else if RFC4380(na) {
|
||||
return Teredo
|
||||
} else if na.IP.To4() != nil {
|
||||
return Ipv4
|
||||
} else { // ipv6
|
||||
return Ipv6_weak
|
||||
}
|
||||
} else if fromna.IP.To4() != nil {
|
||||
if Routable(na) && na.IP.To4() != nil {
|
||||
return Ipv4
|
||||
}
|
||||
return Default
|
||||
} else /* ipv6 */ {
|
||||
var tunnelled bool
|
||||
// Is our v6 is tunnelled?
|
||||
if RFC3964(na) || RFC6052(na) || RFC6145(na) {
|
||||
tunnelled = true
|
||||
}
|
||||
if !Routable(na) {
|
||||
return Default
|
||||
} else if RFC4380(na) {
|
||||
return Teredo
|
||||
} else if na.IP.To4() != nil {
|
||||
return Ipv4
|
||||
} else if tunnelled {
|
||||
// only prioritise ipv6 if we aren't tunnelling it.
|
||||
return Ipv6_weak
|
||||
}
|
||||
return Ipv6_strong
|
||||
}
|
||||
}
|
||||
|
||||
// getBestLocalAddress returns the most appropriate local address that we know
|
||||
// of to be contacted by rna
|
||||
func (a *AddrManager) getBestLocalAddress(rna *btcwire.NetAddress) *btcwire.NetAddress {
|
||||
a.lamtx.Lock()
|
||||
defer a.lamtx.Unlock()
|
||||
|
||||
bestreach := 0
|
||||
var bestscore addressPrio
|
||||
var bestna *btcwire.NetAddress
|
||||
for _, la := range a.localAddresses {
|
||||
reach := getReachabilityFrom(la.na, rna)
|
||||
if reach > bestreach ||
|
||||
(reach == bestreach && la.score > bestscore) {
|
||||
bestreach = reach
|
||||
bestscore = la.score
|
||||
bestna = la.na
|
||||
}
|
||||
}
|
||||
if bestna != nil {
|
||||
amgrLog.Debugf("Suggesting address %s:%d for %s:%d",
|
||||
bestna.IP, bestna.Port, rna.IP, rna.Port)
|
||||
} else {
|
||||
amgrLog.Debugf("No worthy address for %s:%d",
|
||||
rna.IP, rna.Port)
|
||||
// Send something unroutable if nothing suitable.
|
||||
bestna = &btcwire.NetAddress{
|
||||
Timestamp: time.Now(),
|
||||
Services: 0,
|
||||
IP: net.IP([]byte{0, 0, 0, 0}),
|
||||
Port: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return bestna
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
||||
599
blockmanager.go
599
blockmanager.go
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -6,7 +6,6 @@ package main
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"github.com/conformal/btcchain"
|
||||
"github.com/conformal/btcdb"
|
||||
"github.com/conformal/btcutil"
|
||||
@@ -22,6 +21,11 @@ import (
|
||||
const (
|
||||
chanBufferSize = 50
|
||||
|
||||
// minInFlightBlocks is the minimum number of blocks that should be
|
||||
// in the request queue for headers-first mode before requesting
|
||||
// more.
|
||||
minInFlightBlocks = 10
|
||||
|
||||
// blockDbNamePrefix is the prefix for the block database name. The
|
||||
// database type is appended to this value to form the full block
|
||||
// database name.
|
||||
@@ -47,6 +51,13 @@ type invMsg struct {
|
||||
peer *peer
|
||||
}
|
||||
|
||||
// blockMsg packages a bitcoin block message and the peer it came from together
|
||||
// so the block handler has access to that information.
|
||||
type headersMsg struct {
|
||||
headers *btcwire.MsgHeaders
|
||||
peer *peer
|
||||
}
|
||||
|
||||
// donePeerMsg signifies a newly disconnected peer to the block handler.
|
||||
type donePeerMsg struct {
|
||||
peer *peer
|
||||
@@ -55,10 +66,23 @@ type donePeerMsg struct {
|
||||
// txMsg packages a bitcoin tx message and the peer it came from together
|
||||
// so the block handler has access to that information.
|
||||
type txMsg struct {
|
||||
tx *btcwire.MsgTx
|
||||
tx *btcutil.Tx
|
||||
peer *peer
|
||||
}
|
||||
|
||||
// getSyncPeerMsg is a message type to be sent across the query channel for
|
||||
// retrieving the current sync peer.
|
||||
type getSyncPeerMsg struct {
|
||||
reply chan *peer
|
||||
}
|
||||
|
||||
// headerNode is used as a node in a list of headers that are linked together
|
||||
// between checkpoints.
|
||||
type headerNode struct {
|
||||
height int64
|
||||
sha *btcwire.ShaHash
|
||||
}
|
||||
|
||||
// blockManager provides a concurrency safe block manager for handling all
|
||||
// incoming blocks.
|
||||
type blockManager struct {
|
||||
@@ -75,8 +99,59 @@ type blockManager struct {
|
||||
processingReqs bool
|
||||
syncPeer *peer
|
||||
msgChan chan interface{}
|
||||
query chan interface{}
|
||||
wg sync.WaitGroup
|
||||
quit chan bool
|
||||
|
||||
// The following fields are used for headers-first mode.
|
||||
headersFirstMode bool
|
||||
headerList *list.List
|
||||
startHeader *list.Element
|
||||
nextCheckpoint *btcchain.Checkpoint
|
||||
}
|
||||
|
||||
// resetHeaderState sets the headers-first mode state to values appropriate for
|
||||
// syncing from a new peer.
|
||||
func (b *blockManager) resetHeaderState(newestHash *btcwire.ShaHash, newestHeight int64) {
|
||||
b.headersFirstMode = false
|
||||
b.headerList.Init()
|
||||
b.startHeader = nil
|
||||
|
||||
// When there is a next checkpoint, add an entry for the latest known
|
||||
// block into the header pool. This allows the next downloaded header
|
||||
// to prove it links to the chain properly.
|
||||
if b.nextCheckpoint != nil {
|
||||
node := headerNode{height: newestHeight, sha: newestHash}
|
||||
b.headerList.PushBack(&node)
|
||||
}
|
||||
}
|
||||
|
||||
// findNextHeaderCheckpoint returns the next checkpoint after the passed height.
|
||||
// It returns nil when there is not one either because the height is already
|
||||
// later than the final checkpoint or some other reason such as disabled
|
||||
// checkpoints.
|
||||
func (b *blockManager) findNextHeaderCheckpoint(height int64) *btcchain.Checkpoint {
|
||||
checkpoints := b.blockChain.Checkpoints()
|
||||
if checkpoints == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// There is no next checkpoint if the height is already after the final
|
||||
// checkpoint.
|
||||
finalCheckpoint := &checkpoints[len(checkpoints)-1]
|
||||
if height >= finalCheckpoint.Height {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find the next checkpoint.
|
||||
nextCheckpoint := finalCheckpoint
|
||||
for i := len(checkpoints) - 2; i >= 0; i-- {
|
||||
if height >= checkpoints[i].Height {
|
||||
break
|
||||
}
|
||||
nextCheckpoint = &checkpoints[i]
|
||||
}
|
||||
return nextCheckpoint
|
||||
}
|
||||
|
||||
// startSync will choose the best peer among the available candidate peers to
|
||||
@@ -92,7 +167,7 @@ func (b *blockManager) startSync(peers *list.List) {
|
||||
// Find the height of the current known best block.
|
||||
_, height, err := b.server.db.NewestSha()
|
||||
if err != nil {
|
||||
log.Errorf("BMGR: %v", err)
|
||||
bmgrLog.Errorf("%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -122,17 +197,45 @@ func (b *blockManager) startSync(peers *list.List) {
|
||||
if bestPeer != nil {
|
||||
locator, err := b.blockChain.LatestBlockLocator()
|
||||
if err != nil {
|
||||
log.Errorf("BMGR: Failed to get block locator for the "+
|
||||
bmgrLog.Errorf("Failed to get block locator for the "+
|
||||
"latest block: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("BMGR: Syncing to block height %d from peer %v",
|
||||
bmgrLog.Infof("Syncing to block height %d from peer %v",
|
||||
bestPeer.lastBlock, bestPeer.addr)
|
||||
bestPeer.PushGetBlocksMsg(locator, &zeroHash)
|
||||
|
||||
// When the current height is less than a known checkpoint we
|
||||
// can use block headers to learn about which blocks comprise
|
||||
// the chain up to the checkpoint and perform less validation
|
||||
// for them. This is possible since each header contains the
|
||||
// hash of the previous header and a merkle root. Therefore if
|
||||
// we validate all of the received headers link together
|
||||
// properly and the checkpoint hashes match, we can be sure the
|
||||
// hashes for the blocks in between are accurate. Further, once
|
||||
// the full blocks are downloaded, the merkle root is computed
|
||||
// and compared against the value in the header which proves the
|
||||
// full block hasn't been tampered with.
|
||||
//
|
||||
// Once we have passed the final checkpoint, or checkpoints are
|
||||
// disabled, use standard inv messages learn about the blocks
|
||||
// and fully validate them. Finally, regression test mode does
|
||||
// not support the headers-first approach so do normal block
|
||||
// downloads when in regression test mode.
|
||||
if b.nextCheckpoint != nil && height < b.nextCheckpoint.Height &&
|
||||
!cfg.RegressionTest && !cfg.DisableCheckpoints {
|
||||
|
||||
bestPeer.PushGetHeadersMsg(locator, b.nextCheckpoint.Hash)
|
||||
b.headersFirstMode = true
|
||||
bmgrLog.Infof("Downloading headers for blocks %d to "+
|
||||
"%d from peer %s", height+1,
|
||||
b.nextCheckpoint.Height, bestPeer.addr)
|
||||
} else {
|
||||
bestPeer.PushGetBlocksMsg(locator, &zeroHash)
|
||||
}
|
||||
b.syncPeer = bestPeer
|
||||
} else {
|
||||
log.Warnf("BMGR: No sync peer candidates available")
|
||||
bmgrLog.Warnf("No sync peer candidates available")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +276,7 @@ func (b *blockManager) handleNewPeerMsg(peers *list.List, p *peer) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("BMGR: New valid peer %s", p)
|
||||
bmgrLog.Infof("New valid peer %s (%s)", p, p.userAgent)
|
||||
|
||||
// Ignore the peer if it's not a sync candidate.
|
||||
if !b.isSyncCandidate(p) {
|
||||
@@ -200,7 +303,7 @@ func (b *blockManager) handleDonePeerMsg(peers *list.List, p *peer) {
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("BMGR: Lost peer %s", p)
|
||||
bmgrLog.Infof("Lost peer %s", p)
|
||||
|
||||
// Remove requested transactions from the global map so that they will
|
||||
// be fetched from elsewhere next time we get an inv.
|
||||
@@ -217,9 +320,22 @@ func (b *blockManager) handleDonePeerMsg(peers *list.List, p *peer) {
|
||||
}
|
||||
|
||||
// Attempt to find a new peer to sync from if the quitting peer is the
|
||||
// sync peer.
|
||||
// sync peer. Also, reset the headers-first state if in headers-first
|
||||
// mode so
|
||||
if b.syncPeer != nil && b.syncPeer == p {
|
||||
b.syncPeer = nil
|
||||
if b.headersFirstMode {
|
||||
// This really shouldn't fail. We have a fairly
|
||||
// unrecoverable database issue if it does.
|
||||
newestHash, height, err := b.server.db.NewestSha()
|
||||
if err != nil {
|
||||
bmgrLog.Warnf("Unable to obtain latest "+
|
||||
"block information from the database: "+
|
||||
"%v", err)
|
||||
return
|
||||
}
|
||||
b.resetHeaderState(newestHash, height)
|
||||
}
|
||||
b.startSync(peers)
|
||||
}
|
||||
}
|
||||
@@ -227,9 +343,9 @@ func (b *blockManager) handleDonePeerMsg(peers *list.List, p *peer) {
|
||||
// logBlockHeight logs a new block height as an information message to show
|
||||
// progress to the user. In order to prevent spam, it limits logging to one
|
||||
// message every 10 seconds with duration and totals included.
|
||||
func (b *blockManager) logBlockHeight(numTx, height int64, latestHash *btcwire.ShaHash) {
|
||||
func (b *blockManager) logBlockHeight(block *btcutil.Block) {
|
||||
b.receivedLogBlocks++
|
||||
b.receivedLogTx += numTx
|
||||
b.receivedLogTx += int64(len(block.MsgBlock().Transactions))
|
||||
|
||||
now := time.Now()
|
||||
duration := now.Sub(b.lastBlockLogTime)
|
||||
@@ -237,17 +353,10 @@ func (b *blockManager) logBlockHeight(numTx, height int64, latestHash *btcwire.S
|
||||
return
|
||||
}
|
||||
|
||||
// Truncated the duration to 10s of milliseconds.
|
||||
// Truncate the duration to 10s of milliseconds.
|
||||
durationMillis := int64(duration / time.Millisecond)
|
||||
tDuration := 10 * time.Millisecond * time.Duration(durationMillis/10)
|
||||
|
||||
// Attempt to get the timestamp of the latest block.
|
||||
blockTimeStr := ""
|
||||
block, err := b.server.db.FetchBlockBySha(latestHash)
|
||||
if err == nil {
|
||||
blockTimeStr = fmt.Sprintf(", %s", block.MsgBlock().Header.Timestamp)
|
||||
}
|
||||
|
||||
// Log information about new block height.
|
||||
blockStr := "blocks"
|
||||
if b.receivedLogBlocks == 1 {
|
||||
@@ -257,9 +366,9 @@ func (b *blockManager) logBlockHeight(numTx, height int64, latestHash *btcwire.S
|
||||
if b.receivedLogTx == 1 {
|
||||
txStr = "transaction"
|
||||
}
|
||||
log.Infof("BMGR: Processed %d %s in the last %s (%d %s, height %d%s)",
|
||||
bmgrLog.Infof("Processed %d %s in the last %s (%d %s, height %d, %s)",
|
||||
b.receivedLogBlocks, blockStr, tDuration, b.receivedLogTx,
|
||||
txStr, height, blockTimeStr)
|
||||
txStr, block.Height(), block.MsgBlock().Header.Timestamp)
|
||||
|
||||
b.receivedLogBlocks = 0
|
||||
b.receivedLogTx = 0
|
||||
@@ -269,12 +378,12 @@ func (b *blockManager) logBlockHeight(numTx, height int64, latestHash *btcwire.S
|
||||
// handleTxMsg handles transaction messages from all peers.
|
||||
func (b *blockManager) handleTxMsg(tmsg *txMsg) {
|
||||
// Keep track of which peer the tx was sent from.
|
||||
txHash, _ := tmsg.tx.TxSha()
|
||||
txHash := tmsg.tx.Sha()
|
||||
|
||||
// If we didn't ask for this block then the peer is misbehaving.
|
||||
if _, ok := tmsg.peer.requestedTxns[txHash]; !ok {
|
||||
log.Warnf("BMGR: Got unrequested transaction %v from %s -- "+
|
||||
"disconnecting", &txHash, tmsg.peer.addr)
|
||||
// If we didn't ask for this transaction then the peer is misbehaving.
|
||||
if _, ok := tmsg.peer.requestedTxns[*txHash]; !ok {
|
||||
bmgrLog.Warnf("Got unrequested transaction %v from %s -- "+
|
||||
"disconnecting", txHash, tmsg.peer.addr)
|
||||
tmsg.peer.Disconnect()
|
||||
return
|
||||
}
|
||||
@@ -287,8 +396,8 @@ func (b *blockManager) handleTxMsg(tmsg *txMsg) {
|
||||
// already knows about it and as such we shouldn't have any more
|
||||
// instances of trying to fetch it, or we failed to insert and thus
|
||||
// we'll retry next time we get an inv.
|
||||
delete(tmsg.peer.requestedTxns, txHash)
|
||||
delete(b.requestedTxns, txHash)
|
||||
delete(tmsg.peer.requestedTxns, *txHash)
|
||||
delete(b.requestedTxns, *txHash)
|
||||
|
||||
if err != nil {
|
||||
// When the error is a rule error, it means the transaction was
|
||||
@@ -296,9 +405,9 @@ func (b *blockManager) handleTxMsg(tmsg *txMsg) {
|
||||
// so log it as such. Otherwise, something really did go wrong,
|
||||
// so log it as an actual error.
|
||||
if _, ok := err.(TxRuleError); ok {
|
||||
log.Debugf("Rejected transaction %v: %v", txHash, err)
|
||||
bmgrLog.Debugf("Rejected transaction %v: %v", txHash, err)
|
||||
} else {
|
||||
log.Errorf("Failed to process transaction %v: %v", txHash, err)
|
||||
bmgrLog.Errorf("Failed to process transaction %v: %v", txHash, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -331,11 +440,8 @@ func (b *blockManager) current() bool {
|
||||
|
||||
// handleBlockMsg handles block messages from all peers.
|
||||
func (b *blockManager) handleBlockMsg(bmsg *blockMsg) {
|
||||
// Keep track of which peer the block was sent from so the notification
|
||||
// handler can request the parent blocks from the appropriate peer.
|
||||
blockSha, _ := bmsg.block.Sha()
|
||||
|
||||
// If we didn't ask for this block then the peer is misbehaving.
|
||||
blockSha, _ := bmsg.block.Sha()
|
||||
if _, ok := bmsg.peer.requestedBlocks[*blockSha]; !ok {
|
||||
// The regression test intentionally sends some blocks twice
|
||||
// to test duplicate block insertion fails. Don't disconnect
|
||||
@@ -343,24 +449,50 @@ func (b *blockManager) handleBlockMsg(bmsg *blockMsg) {
|
||||
// mode in this case so the chain code is actually fed the
|
||||
// duplicate blocks.
|
||||
if !cfg.RegressionTest {
|
||||
log.Warnf("BMGR: Got unrequested block %v from %s -- "+
|
||||
bmgrLog.Warnf("Got unrequested block %v from %s -- "+
|
||||
"disconnecting", blockSha, bmsg.peer.addr)
|
||||
bmsg.peer.Disconnect()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Keep track of which peer the block was sent from so the notification
|
||||
// handler can request the parent blocks from the appropriate peer.
|
||||
b.blockPeer[*blockSha] = bmsg.peer
|
||||
|
||||
// Process the block to include validation, best chain selection, orphan
|
||||
// handling, etc.
|
||||
err := b.blockChain.ProcessBlock(bmsg.block)
|
||||
// When in headers-first mode, if the block matches the hash of the
|
||||
// first header in the list of headers that are being fetched, it's
|
||||
// eligible for less validation since the headers have already been
|
||||
// verified to link together and are valid up to the next checkpoint.
|
||||
// Also, remove the list entry for all blocks except the checkpoint
|
||||
// since it is needed to verify the next round of headers links
|
||||
// properly.
|
||||
isCheckpointBlock := false
|
||||
fastAdd := false
|
||||
if b.headersFirstMode {
|
||||
firstNodeEl := b.headerList.Front()
|
||||
if firstNodeEl != nil {
|
||||
firstNode := firstNodeEl.Value.(*headerNode)
|
||||
if blockSha.IsEqual(firstNode.sha) {
|
||||
fastAdd = true
|
||||
if firstNode.sha.IsEqual(b.nextCheckpoint.Hash) {
|
||||
isCheckpointBlock = true
|
||||
} else {
|
||||
b.headerList.Remove(firstNodeEl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove block from request maps. Either chain knows about it and such
|
||||
// we shouldn't have any more instances of trying to fetch it, or we
|
||||
// failed to insert and thus we'll retry next time we get an inv.
|
||||
// Remove block from request maps. Either chain will know about it and
|
||||
// so we shouldn't have any more instances of trying to fetch it, or we
|
||||
// will fail the insert and thus we'll retry next time we get an inv.
|
||||
delete(bmsg.peer.requestedBlocks, *blockSha)
|
||||
delete(b.requestedBlocks, *blockSha)
|
||||
|
||||
// Process the block to include validation, best chain selection, orphan
|
||||
// handling, etc.
|
||||
err := b.blockChain.ProcessBlock(bmsg.block, fastAdd)
|
||||
if err != nil {
|
||||
delete(b.blockPeer, *blockSha)
|
||||
|
||||
@@ -369,45 +501,233 @@ func (b *blockManager) handleBlockMsg(bmsg *blockMsg) {
|
||||
// it as such. Otherwise, something really did go wrong, so log
|
||||
// it as an actual error.
|
||||
if _, ok := err.(btcchain.RuleError); ok {
|
||||
log.Infof("BMGR: Rejected block %v: %v", blockSha, err)
|
||||
bmgrLog.Infof("Rejected block %v: %v", blockSha, err)
|
||||
} else {
|
||||
log.Errorf("BMGR: Failed to process block %v: %v", blockSha, err)
|
||||
bmgrLog.Errorf("Failed to process block %v: %v", blockSha, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Don't keep track of the peer that sent the block any longer if it's
|
||||
// not an orphan.
|
||||
// When the block is not an orphan, don't keep track of the peer that
|
||||
// sent it any longer and log information about it.
|
||||
if !b.blockChain.IsKnownOrphan(blockSha) {
|
||||
delete(b.blockPeer, *blockSha)
|
||||
b.logBlockHeight(bmsg.block)
|
||||
}
|
||||
|
||||
// Log info about the new block height.
|
||||
latestHash, height, err := b.server.db.NewestSha()
|
||||
if err != nil {
|
||||
log.Warnf("BMGR: Failed to obtain latest sha - %v", err)
|
||||
return
|
||||
}
|
||||
b.logBlockHeight(int64(len(bmsg.block.MsgBlock().Transactions)), height,
|
||||
latestHash)
|
||||
|
||||
// Sync the db to disk.
|
||||
b.server.db.Sync()
|
||||
|
||||
// Nothing more to do if we aren't in headers-first mode.
|
||||
if !b.headersFirstMode {
|
||||
return
|
||||
}
|
||||
|
||||
// This is headers-first mode, so if the block is not a checkpoint
|
||||
// request more blocks using the header list when the request queue is
|
||||
// getting short.
|
||||
if !isCheckpointBlock {
|
||||
if b.startHeader != nil &&
|
||||
len(bmsg.peer.requestedBlocks) < minInFlightBlocks {
|
||||
b.fetchHeaderBlocks()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// This is headers-first mode and the block is a checkpoint. When
|
||||
// there is a next checkpoint, get the next round of headers by asking
|
||||
// for headers starting from the block after this one up to the next
|
||||
// checkpoint.
|
||||
prevHeight := b.nextCheckpoint.Height
|
||||
prevHash := b.nextCheckpoint.Hash
|
||||
b.nextCheckpoint = b.findNextHeaderCheckpoint(prevHeight)
|
||||
if b.nextCheckpoint != nil {
|
||||
locator := btcchain.BlockLocator([]*btcwire.ShaHash{prevHash})
|
||||
err := bmsg.peer.PushGetHeadersMsg(locator, b.nextCheckpoint.Hash)
|
||||
if err != nil {
|
||||
bmgrLog.Warnf("Failed to send getheaders message to "+
|
||||
"peer %s: %v", bmsg.peer.addr, err)
|
||||
return
|
||||
}
|
||||
bmgrLog.Infof("Downloading headers for blocks %d to %d from "+
|
||||
"peer %s", prevHeight+1, b.nextCheckpoint.Height,
|
||||
b.syncPeer.addr)
|
||||
return
|
||||
}
|
||||
|
||||
// This is headers-first mode, the block is a checkpoint, and there are
|
||||
// no more checkpoints, so switch to normal mode by requesting blocks
|
||||
// from the block after this one up to the end of the chain (zero hash).
|
||||
b.headersFirstMode = false
|
||||
b.headerList.Init()
|
||||
bmgrLog.Infof("Reached the final checkpoint -- switching to normal mode")
|
||||
locator := btcchain.BlockLocator([]*btcwire.ShaHash{blockSha})
|
||||
err = bmsg.peer.PushGetBlocksMsg(locator, &zeroHash)
|
||||
if err != nil {
|
||||
bmgrLog.Warnf("Failed to send getblocks message to peer %s: %v",
|
||||
bmsg.peer.addr, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// fetchHeaderBlocks creates and sends a request to the syncPeer for the next
|
||||
// list of blocks to be downloaded based on the current list of headers.
|
||||
func (b *blockManager) fetchHeaderBlocks() {
|
||||
// Nothing to do if there is not start header.
|
||||
if b.startHeader == nil {
|
||||
bmgrLog.Warnf("fetchHeaderBlocks called with no start header")
|
||||
return
|
||||
}
|
||||
|
||||
// Build up a getdata request for the list of blocks the headers
|
||||
// describe. The size hint will be limited to btcwire.MaxInvPerMsg by
|
||||
// the function, so no need to double check it here.
|
||||
gdmsg := btcwire.NewMsgGetDataSizeHint(uint(b.headerList.Len()))
|
||||
numRequested := 0
|
||||
for e := b.startHeader; e != nil; e = e.Next() {
|
||||
node, ok := e.Value.(*headerNode)
|
||||
if !ok {
|
||||
bmgrLog.Warn("Header list node type is not a headerNode")
|
||||
continue
|
||||
}
|
||||
|
||||
iv := btcwire.NewInvVect(btcwire.InvTypeBlock, node.sha)
|
||||
if !b.haveInventory(iv) {
|
||||
b.requestedBlocks[*node.sha] = true
|
||||
b.syncPeer.requestedBlocks[*node.sha] = true
|
||||
gdmsg.AddInvVect(iv)
|
||||
numRequested++
|
||||
}
|
||||
b.startHeader = e.Next()
|
||||
if numRequested >= btcwire.MaxInvPerMsg {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(gdmsg.InvList) > 0 {
|
||||
b.syncPeer.QueueMessage(gdmsg, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// handleHeadersMsghandles headers messages from all peers.
|
||||
func (b *blockManager) handleHeadersMsg(hmsg *headersMsg) {
|
||||
// The remote peer is misbehaving if we didn't request headers.
|
||||
msg := hmsg.headers
|
||||
numHeaders := len(msg.Headers)
|
||||
if !b.headersFirstMode {
|
||||
bmgrLog.Warnf("Got %d unrequested headers from %s -- "+
|
||||
"disconnecting", numHeaders, hmsg.peer.addr)
|
||||
hmsg.peer.Disconnect()
|
||||
return
|
||||
}
|
||||
|
||||
// Nothing to do for an empty headers message.
|
||||
if numHeaders == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Process all of the received headers ensuring each one connects to the
|
||||
// previous and that checkpoints match.
|
||||
receivedCheckpoint := false
|
||||
var finalHash *btcwire.ShaHash
|
||||
for _, blockHeader := range msg.Headers {
|
||||
blockHash, err := blockHeader.BlockSha()
|
||||
if err != nil {
|
||||
bmgrLog.Warnf("Failed to compute hash of header "+
|
||||
"received from peer %s -- disconnecting",
|
||||
hmsg.peer.addr)
|
||||
hmsg.peer.Disconnect()
|
||||
return
|
||||
}
|
||||
finalHash = &blockHash
|
||||
|
||||
// Ensure there is a previous header to compare against.
|
||||
prevNodeEl := b.headerList.Back()
|
||||
if prevNodeEl == nil {
|
||||
bmgrLog.Warnf("Header list does not contain a previous" +
|
||||
"element as expected -- disconnecting peer")
|
||||
hmsg.peer.Disconnect()
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure the header properly connects to the previous one and
|
||||
// add it to the list of headers.
|
||||
node := headerNode{sha: &blockHash}
|
||||
prevNode := prevNodeEl.Value.(*headerNode)
|
||||
if prevNode.sha.IsEqual(&blockHeader.PrevBlock) {
|
||||
node.height = prevNode.height + 1
|
||||
e := b.headerList.PushBack(&node)
|
||||
if b.startHeader == nil {
|
||||
b.startHeader = e
|
||||
}
|
||||
} else {
|
||||
bmgrLog.Warnf("Received block header that does not"+
|
||||
"properly connect to the chain from peer %s "+
|
||||
"-- disconnecting", hmsg.peer.addr)
|
||||
hmsg.peer.Disconnect()
|
||||
return
|
||||
}
|
||||
|
||||
// Verify the header at the next checkpoint height matches.
|
||||
if node.height == b.nextCheckpoint.Height {
|
||||
if node.sha.IsEqual(b.nextCheckpoint.Hash) {
|
||||
receivedCheckpoint = true
|
||||
bmgrLog.Infof("Verified downloaded block "+
|
||||
"header against checkpoint at height "+
|
||||
"%d/hash %s", node.height, node.sha)
|
||||
} else {
|
||||
bmgrLog.Warnf("Block header at height %d/hash "+
|
||||
"%s from peer %s does NOT match "+
|
||||
"expected checkpoint hash of %s -- "+
|
||||
"disconnecting", node.height,
|
||||
node.sha, hmsg.peer.addr,
|
||||
b.nextCheckpoint.Hash)
|
||||
hmsg.peer.Disconnect()
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// When this header is a checkpoint, switch to fetching the blocks for
|
||||
// all of the headers since the last checkpoint.
|
||||
if receivedCheckpoint {
|
||||
// Since the first entry of the list is always the final block
|
||||
// that is already in the database and is only used to ensure
|
||||
// the next header links properly, it must be removed before
|
||||
// fetching the blocks.
|
||||
b.headerList.Remove(b.headerList.Front())
|
||||
bmgrLog.Infof("Received %v block headers: Fetching blocks",
|
||||
b.headerList.Len())
|
||||
b.lastBlockLogTime = time.Now()
|
||||
b.fetchHeaderBlocks()
|
||||
return
|
||||
}
|
||||
|
||||
// This header is not a checkpoint, so request the next batch of
|
||||
// headers starting from the latest known header and ending with the
|
||||
// next checkpoint.
|
||||
locator := btcchain.BlockLocator([]*btcwire.ShaHash{finalHash})
|
||||
err := hmsg.peer.PushGetHeadersMsg(locator, b.nextCheckpoint.Hash)
|
||||
if err != nil {
|
||||
bmgrLog.Warnf("Failed to send getheaders message to "+
|
||||
"peer %s: %v", hmsg.peer.addr, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// haveInventory returns whether or not the inventory represented by the passed
|
||||
// inventory vector is known. This includes checking all of the various places
|
||||
// inventory can be when it is in different states such as blocks that are part
|
||||
// of the main chain, on a side chain, in the orphan pool, and transactions that
|
||||
// in the memory pool (either the main pool or orphan pool).
|
||||
// are in the memory pool (either the main pool or orphan pool).
|
||||
func (b *blockManager) haveInventory(invVect *btcwire.InvVect) bool {
|
||||
switch invVect.Type {
|
||||
case btcwire.InvVect_Block:
|
||||
case btcwire.InvTypeBlock:
|
||||
// Ask chain if the block is known to it in any form (main
|
||||
// chain, side chain, or orphan).
|
||||
return b.blockChain.HaveBlock(&invVect.Hash)
|
||||
|
||||
case btcwire.InvVect_Tx:
|
||||
case btcwire.InvTypeTx:
|
||||
// Ask the transaction memory pool if the transaction is known
|
||||
// to it in any form (main pool or orphan).
|
||||
if b.server.txMemPool.HaveTransaction(&invVect.Hash) {
|
||||
@@ -457,7 +777,12 @@ func (b *blockManager) handleInvMsg(imsg *invMsg) {
|
||||
|
||||
// Add the inventory to the cache of known inventory
|
||||
// for the peer.
|
||||
imsg.peer.addKnownInventory(iv)
|
||||
imsg.peer.AddKnownInventory(iv)
|
||||
|
||||
// Ignore inventory when we're in headers-first mode.
|
||||
if b.headersFirstMode {
|
||||
continue
|
||||
}
|
||||
|
||||
// Request the inventory if we don't already have it.
|
||||
if !b.haveInventory(iv) {
|
||||
@@ -484,7 +809,7 @@ func (b *blockManager) handleInvMsg(imsg *invMsg) {
|
||||
orphanRoot := chain.GetOrphanRoot(&iv.Hash)
|
||||
locator, err := chain.LatestBlockLocator()
|
||||
if err != nil {
|
||||
log.Errorf("PEER: Failed to get block "+
|
||||
bmgrLog.Errorf("PEER: Failed to get block "+
|
||||
"locator for the latest block: "+
|
||||
"%v", err)
|
||||
continue
|
||||
@@ -517,7 +842,7 @@ func (b *blockManager) handleInvMsg(imsg *invMsg) {
|
||||
imsg.peer.requestQueue.Remove(e)
|
||||
|
||||
switch iv.Type {
|
||||
case btcwire.InvVect_Block:
|
||||
case btcwire.InvTypeBlock:
|
||||
// Request the block if there is not already a pending
|
||||
// request.
|
||||
if _, exists := b.requestedBlocks[iv.Hash]; !exists {
|
||||
@@ -527,7 +852,7 @@ func (b *blockManager) handleInvMsg(imsg *invMsg) {
|
||||
numRequested++
|
||||
}
|
||||
|
||||
case btcwire.InvVect_Tx:
|
||||
case btcwire.InvTypeTx:
|
||||
// Request the transaction if there is not already a
|
||||
// pending request.
|
||||
if _, exists := b.requestedTxns[iv.Hash]; !exists {
|
||||
@@ -574,11 +899,26 @@ out:
|
||||
case *invMsg:
|
||||
b.handleInvMsg(msg)
|
||||
|
||||
case *headersMsg:
|
||||
b.handleHeadersMsg(msg)
|
||||
|
||||
case *donePeerMsg:
|
||||
b.handleDonePeerMsg(candidatePeers, msg.peer)
|
||||
|
||||
default:
|
||||
// bitch and whine.
|
||||
bmgrLog.Warnf("Invalid message type in block "+
|
||||
"handler: %T", msg)
|
||||
}
|
||||
|
||||
// Queries used for atomically retrieving internal state.
|
||||
case qmsg := <-b.query:
|
||||
switch msg := qmsg.(type) {
|
||||
case getSyncPeerMsg:
|
||||
msg.reply <- b.syncPeer
|
||||
|
||||
default:
|
||||
bmgrLog.Warnf("Invalid query type in block "+
|
||||
"handler query: %T", msg)
|
||||
}
|
||||
|
||||
case <-b.quit:
|
||||
@@ -586,7 +926,7 @@ out:
|
||||
}
|
||||
}
|
||||
b.wg.Done()
|
||||
log.Trace("BMGR: Block handler done")
|
||||
bmgrLog.Trace("Block handler done")
|
||||
}
|
||||
|
||||
// handleNotifyMsg handles notifications from btcchain. It does things such
|
||||
@@ -601,14 +941,14 @@ func (b *blockManager) handleNotifyMsg(notification *btcchain.Notification) {
|
||||
orphanRoot := b.blockChain.GetOrphanRoot(orphanHash)
|
||||
locator, err := b.blockChain.LatestBlockLocator()
|
||||
if err != nil {
|
||||
log.Errorf("BMGR: Failed to get block locator "+
|
||||
bmgrLog.Errorf("Failed to get block locator "+
|
||||
"for the latest block: %v", err)
|
||||
break
|
||||
}
|
||||
peer.PushGetBlocksMsg(locator, orphanRoot)
|
||||
delete(b.blockPeer, *orphanRoot)
|
||||
} else {
|
||||
log.Warnf("Notification for orphan %v with no peer",
|
||||
bmgrLog.Warnf("Notification for orphan %v with no peer",
|
||||
orphanHash)
|
||||
}
|
||||
|
||||
@@ -624,7 +964,7 @@ func (b *blockManager) handleNotifyMsg(notification *btcchain.Notification) {
|
||||
|
||||
block, ok := notification.Data.(*btcutil.Block)
|
||||
if !ok {
|
||||
log.Warnf("BMGR: Chain accepted notification is not a block.")
|
||||
bmgrLog.Warnf("Chain accepted notification is not a block.")
|
||||
break
|
||||
}
|
||||
|
||||
@@ -640,45 +980,52 @@ func (b *blockManager) handleNotifyMsg(notification *btcchain.Notification) {
|
||||
case btcchain.NTBlockConnected:
|
||||
block, ok := notification.Data.(*btcutil.Block)
|
||||
if !ok {
|
||||
log.Warnf("BMGR: Chain connected notification is not a block.")
|
||||
bmgrLog.Warnf("Chain connected notification is not a block.")
|
||||
break
|
||||
}
|
||||
|
||||
// Remove all of the transactions (except the coinbase) in the
|
||||
// connected block from the transaction pool.
|
||||
for _, tx := range block.MsgBlock().Transactions[1:] {
|
||||
b.server.txMemPool.removeTransaction(tx)
|
||||
// connected block from the transaction pool. Also, remove any
|
||||
// transactions which are now double spends as a result of these
|
||||
// new transactions. Note that removing a transaction from
|
||||
// pool also removes any transactions which depend on it,
|
||||
// recursively.
|
||||
for _, tx := range block.Transactions()[1:] {
|
||||
b.server.txMemPool.RemoveTransaction(tx)
|
||||
b.server.txMemPool.RemoveDoubleSpends(tx)
|
||||
}
|
||||
|
||||
// Notify frontends
|
||||
if r := b.server.rpcServer; r != nil {
|
||||
go r.NotifyBlockConnected(block)
|
||||
go r.NotifyNewTxListeners(b.server.db, block)
|
||||
go func() {
|
||||
r.ntfnMgr.NotifyBlockTXs(block)
|
||||
r.ntfnMgr.NotifyBlockConnected(block)
|
||||
}()
|
||||
}
|
||||
|
||||
// A block has been disconnected from the main block chain.
|
||||
case btcchain.NTBlockDisconnected:
|
||||
block, ok := notification.Data.(*btcutil.Block)
|
||||
if !ok {
|
||||
log.Warnf("BMGR: Chain disconnected notification is not a block.")
|
||||
bmgrLog.Warnf("Chain disconnected notification is not a block.")
|
||||
break
|
||||
}
|
||||
|
||||
// Reinsert all of the transactions (except the coinbase) into
|
||||
// the transaction pool.
|
||||
for _, tx := range block.MsgBlock().Transactions[1:] {
|
||||
err := b.server.txMemPool.ProcessTransaction(tx)
|
||||
for _, tx := range block.Transactions()[1:] {
|
||||
err := b.server.txMemPool.MaybeAcceptTransaction(tx, nil, false)
|
||||
if err != nil {
|
||||
// Remove the transaction and all transactions
|
||||
// that depend on it if it wasn't accepted into
|
||||
// the transaction pool.
|
||||
b.server.txMemPool.removeTransaction(tx)
|
||||
b.server.txMemPool.RemoveTransaction(tx)
|
||||
}
|
||||
}
|
||||
|
||||
// Notify frontends
|
||||
if r := b.server.rpcServer; r != nil {
|
||||
go r.NotifyBlockDisconnected(block)
|
||||
go r.ntfnMgr.NotifyBlockDisconnected(block)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -695,7 +1042,7 @@ func (b *blockManager) NewPeer(p *peer) {
|
||||
|
||||
// QueueTx adds the passed transaction message and peer to the block handling
|
||||
// queue.
|
||||
func (b *blockManager) QueueTx(tx *btcwire.MsgTx, p *peer) {
|
||||
func (b *blockManager) QueueTx(tx *btcutil.Tx, p *peer) {
|
||||
// Don't accept more transactions if we're shutting down.
|
||||
if atomic.LoadInt32(&b.shutdown) != 0 {
|
||||
p.txProcessed <- false
|
||||
@@ -727,6 +1074,18 @@ func (b *blockManager) QueueInv(inv *btcwire.MsgInv, p *peer) {
|
||||
b.msgChan <- &invMsg{inv: inv, peer: p}
|
||||
}
|
||||
|
||||
// QueueHeaders adds the passed headers message and peer to the block handling
|
||||
// queue.
|
||||
func (b *blockManager) QueueHeaders(headers *btcwire.MsgHeaders, p *peer) {
|
||||
// No channel handling here because peers do not need to block on
|
||||
// headers messages.
|
||||
if atomic.LoadInt32(&b.shutdown) != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
b.msgChan <- &headersMsg{headers: headers, peer: p}
|
||||
}
|
||||
|
||||
// DonePeer informs the blockmanager that a peer has disconnected.
|
||||
func (b *blockManager) DonePeer(p *peer) {
|
||||
// Ignore if we are shutting down.
|
||||
@@ -744,7 +1103,7 @@ func (b *blockManager) Start() {
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("BMGR: Starting block manager")
|
||||
bmgrLog.Trace("Starting block manager")
|
||||
b.wg.Add(1)
|
||||
go b.blockHandler()
|
||||
}
|
||||
@@ -753,20 +1112,32 @@ func (b *blockManager) Start() {
|
||||
// handlers and waiting for them to finish.
|
||||
func (b *blockManager) Stop() error {
|
||||
if atomic.AddInt32(&b.shutdown, 1) != 1 {
|
||||
log.Warnf("BMGR: Block manager is already in the process of " +
|
||||
bmgrLog.Warnf("Block manager is already in the process of " +
|
||||
"shutting down")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infof("BMGR: Block manager shutting down")
|
||||
bmgrLog.Infof("Block manager shutting down")
|
||||
close(b.quit)
|
||||
b.wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncPeer returns the current sync peer.
|
||||
func (b *blockManager) SyncPeer() *peer {
|
||||
reply := make(chan *peer)
|
||||
b.query <- getSyncPeerMsg{reply: reply}
|
||||
return <-reply
|
||||
}
|
||||
|
||||
// newBlockManager returns a new bitcoin block manager.
|
||||
// Use Start to begin processing asynchronous block and inv updates.
|
||||
func newBlockManager(s *server) (*blockManager, error) {
|
||||
newestHash, height, err := s.db.NewestSha()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bm := blockManager{
|
||||
server: s,
|
||||
blockPeer: make(map[btcwire.ShaHash]*peer),
|
||||
@@ -774,21 +1145,29 @@ func newBlockManager(s *server) (*blockManager, error) {
|
||||
requestedBlocks: make(map[btcwire.ShaHash]bool),
|
||||
lastBlockLogTime: time.Now(),
|
||||
msgChan: make(chan interface{}, cfg.MaxPeers*3),
|
||||
query: make(chan interface{}),
|
||||
headerList: list.New(),
|
||||
quit: make(chan bool),
|
||||
}
|
||||
bm.blockChain = btcchain.New(s.db, s.btcnet, bm.handleNotifyMsg)
|
||||
bm.blockChain.DisableCheckpoints(cfg.DisableCheckpoints)
|
||||
if cfg.DisableCheckpoints {
|
||||
log.Info("BMGR: Checkpoints are disabled")
|
||||
if !cfg.DisableCheckpoints {
|
||||
// Initialize the next checkpoint based on the current height.
|
||||
bm.nextCheckpoint = bm.findNextHeaderCheckpoint(height)
|
||||
if bm.nextCheckpoint != nil {
|
||||
bm.resetHeaderState(newestHash, height)
|
||||
}
|
||||
} else {
|
||||
bmgrLog.Info("Checkpoints are disabled")
|
||||
}
|
||||
|
||||
log.Infof("BMGR: Generating initial block node index. This may " +
|
||||
bmgrLog.Infof("Generating initial block node index. This may " +
|
||||
"take a while...")
|
||||
err := bm.blockChain.GenerateInitialIndex()
|
||||
err = bm.blockChain.GenerateInitialIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Infof("BMGR: Block index generation complete")
|
||||
bmgrLog.Infof("Block index generation complete")
|
||||
|
||||
return &bm, nil
|
||||
}
|
||||
@@ -804,7 +1183,7 @@ func removeRegressionDB(dbPath string) error {
|
||||
// Remove the old regression test database if it already exists.
|
||||
fi, err := os.Stat(dbPath)
|
||||
if err == nil {
|
||||
log.Infof("BMGR: Removing regression test database from '%s'", dbPath)
|
||||
btcdLog.Infof("Removing regression test database from '%s'", dbPath)
|
||||
if fi.IsDir() {
|
||||
err := os.RemoveAll(dbPath)
|
||||
if err != nil {
|
||||
@@ -856,7 +1235,7 @@ func warnMultipeDBs() {
|
||||
// Warn if there are extra databases.
|
||||
if len(duplicateDbPaths) > 0 {
|
||||
selectedDbPath := blockDbPath(cfg.DbType)
|
||||
log.Warnf("WARNING: There are multiple block chain databases "+
|
||||
btcdLog.Warnf("WARNING: There are multiple block chain databases "+
|
||||
"using different database types.\nYou probably don't "+
|
||||
"want to waste disk space by having more than one.\n"+
|
||||
"Your current database is located at [%v].\nThe "+
|
||||
@@ -865,8 +1244,24 @@ func warnMultipeDBs() {
|
||||
}
|
||||
}
|
||||
|
||||
// loadBlockDB opens the block database and returns a handle to it.
|
||||
func loadBlockDB() (btcdb.Db, error) {
|
||||
// setupBlockDB loads (or creates when needed) the block database taking into
|
||||
// account the selected database backend. It also contains additional logic
|
||||
// such warning the user if there are multiple databases which consume space on
|
||||
// the file system and ensuring the regression test database is clean when in
|
||||
// regression test mode.
|
||||
func setupBlockDB() (btcdb.Db, error) {
|
||||
// The memdb backend does not have a file path associated with it, so
|
||||
// handle it uniquely. We also don't want to worry about the multiple
|
||||
// database type warnings when running with the memory database.
|
||||
if cfg.DbType == "memdb" {
|
||||
btcdLog.Infof("Creating block database in memory.")
|
||||
db, err := btcdb.CreateDB(cfg.DbType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
warnMultipeDBs()
|
||||
|
||||
// The database name is based on the database type.
|
||||
@@ -876,11 +1271,11 @@ func loadBlockDB() (btcdb.Db, error) {
|
||||
// each run, so remove it now if it already exists.
|
||||
removeRegressionDB(dbPath)
|
||||
|
||||
log.Infof("BMGR: Loading block database from '%s'", dbPath)
|
||||
btcdLog.Infof("Loading block database from '%s'", dbPath)
|
||||
db, err := btcdb.OpenDB(cfg.DbType, dbPath)
|
||||
if err != nil {
|
||||
// Return the error if it's not because the database doesn't
|
||||
// exist.
|
||||
// Return the error if it's not because the database
|
||||
// doesn't exist.
|
||||
if err != btcdb.DbDoesNotExist {
|
||||
return nil, err
|
||||
}
|
||||
@@ -896,6 +1291,16 @@ func loadBlockDB() (btcdb.Db, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// loadBlockDB opens the block database and returns a handle to it.
|
||||
func loadBlockDB() (btcdb.Db, error) {
|
||||
db, err := setupBlockDB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the latest block height from the database.
|
||||
_, height, err := db.NewestSha()
|
||||
if err != nil {
|
||||
@@ -912,11 +1317,11 @@ func loadBlockDB() (btcdb.Db, error) {
|
||||
db.Close()
|
||||
return nil, err
|
||||
}
|
||||
log.Infof("BMGR: Inserted genesis block %v",
|
||||
btcdLog.Infof("Inserted genesis block %v",
|
||||
activeNetParams.genesisHash)
|
||||
height = 0
|
||||
}
|
||||
|
||||
log.Infof("BMGR: Block database loaded with block height %d", height)
|
||||
btcdLog.Infof("Block database loaded with block height %d", height)
|
||||
return db, nil
|
||||
}
|
||||
|
||||
115
btcd.go
115
btcd.go
@@ -1,86 +1,125 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/conformal/btcd/limits"
|
||||
"net"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
)
|
||||
|
||||
var (
|
||||
cfg *config
|
||||
cfg *config
|
||||
shutdownChannel = make(chan bool)
|
||||
)
|
||||
|
||||
// btcdMain is the real main function for btcd. It is necessary to work around
|
||||
// the fact that deferred functions do not run when os.Exit() is called.
|
||||
func btcdMain() error {
|
||||
// Initialize logging and setup deferred flushing to ensure all
|
||||
// outstanding messages are written on shutdown.
|
||||
loggers := setLogLevel(defaultLogLevel)
|
||||
defer func() {
|
||||
for _, logger := range loggers {
|
||||
logger.Flush()
|
||||
}
|
||||
}()
|
||||
// winServiceMain is only invoked on Windows. It detects when btcd is running
|
||||
// as a service and reacts accordingly.
|
||||
var winServiceMain func() (bool, error)
|
||||
|
||||
// Load configuration and parse command line.
|
||||
// btcdMain is the real main function for btcd. It is necessary to work around
|
||||
// the fact that deferred functions do not run when os.Exit() is called. The
|
||||
// optional serverChan parameter is mainly used by the service code to be
|
||||
// notified with the server once it is setup so it can gracefully stop it when
|
||||
// requested from the service control manager.
|
||||
func btcdMain(serverChan chan<- *server) error {
|
||||
// Load configuration and parse command line. This function also
|
||||
// initializes logging and configures it accordingly.
|
||||
tcfg, _, err := loadConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg = tcfg
|
||||
|
||||
// Change the logging level if needed.
|
||||
if cfg.DebugLevel != defaultLogLevel {
|
||||
loggers = setLogLevel(cfg.DebugLevel)
|
||||
}
|
||||
defer backendLog.Flush()
|
||||
|
||||
// Show version at startup.
|
||||
log.Infof("Version %s", version())
|
||||
btcdLog.Infof("Version %s", version())
|
||||
|
||||
// See if we want to enable profiling.
|
||||
// Enable http profiling server if requested.
|
||||
if cfg.Profile != "" {
|
||||
go func() {
|
||||
listenAddr := net.JoinHostPort("", cfg.Profile)
|
||||
log.Infof("Profile server listening on %s", listenAddr)
|
||||
log.Errorf("%v", http.ListenAndServe(listenAddr, nil))
|
||||
btcdLog.Infof("Profile server listening on %s", listenAddr)
|
||||
profileRedirect := http.RedirectHandler("/debug/pprof",
|
||||
http.StatusSeeOther)
|
||||
http.Handle("/", profileRedirect)
|
||||
btcdLog.Errorf("%v", http.ListenAndServe(listenAddr, nil))
|
||||
}()
|
||||
}
|
||||
|
||||
// Write cpu profile if requested.
|
||||
if cfg.CpuProfile != "" {
|
||||
f, err := os.Create(cfg.CpuProfile)
|
||||
if err != nil {
|
||||
btcdLog.Errorf("Unable to create cpu profile: %v", err)
|
||||
return err
|
||||
}
|
||||
pprof.StartCPUProfile(f)
|
||||
defer f.Close()
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
// Perform upgrades to btcd as new versions require it.
|
||||
if err := doUpgrades(); err != nil {
|
||||
log.Errorf("%v", err)
|
||||
btcdLog.Errorf("%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Load the block database.
|
||||
db, err := loadBlockDB()
|
||||
if err != nil {
|
||||
log.Errorf("%v", err)
|
||||
btcdLog.Errorf("%v", err)
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Ensure the database is sync'd and closed on Ctrl+C.
|
||||
addInterruptHandler(func() {
|
||||
btcdLog.Infof("Gracefully shutting down the database...")
|
||||
db.RollbackClose()
|
||||
})
|
||||
|
||||
// Create server and start it.
|
||||
listenAddr := net.JoinHostPort("", cfg.Port)
|
||||
server, err := newServer(listenAddr, db, activeNetParams.btcnet)
|
||||
server, err := newServer(cfg.Listeners, db, activeNetParams.btcnet)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to start server on %v: %v", listenAddr, err)
|
||||
// TODO(oga) this logging could do with some beautifying.
|
||||
btcdLog.Errorf("Unable to start server on %v: %v",
|
||||
cfg.Listeners, err)
|
||||
return err
|
||||
}
|
||||
addInterruptHandler(func() {
|
||||
btcdLog.Infof("Gracefully shutting down the server...")
|
||||
server.Stop()
|
||||
server.WaitForShutdown()
|
||||
})
|
||||
server.Start()
|
||||
if serverChan != nil {
|
||||
serverChan <- server
|
||||
}
|
||||
|
||||
server.WaitForShutdown()
|
||||
// Monitor for graceful server shutdown and signal the main goroutine
|
||||
// when done. This is done in a separate goroutine rather than waiting
|
||||
// directly so the main goroutine can be signaled for shutdown by either
|
||||
// a graceful shutdown or from the main interrupt handler. This is
|
||||
// necessary since the main goroutine must be kept running long enough
|
||||
// for the interrupt handler goroutine to finish.
|
||||
go func() {
|
||||
server.WaitForShutdown()
|
||||
srvrLog.Infof("Server shutdown complete")
|
||||
shutdownChannel <- true
|
||||
}()
|
||||
|
||||
// Wait for shutdown signal from either a graceful server stop or from
|
||||
// the interrupt handler.
|
||||
<-shutdownChannel
|
||||
btcdLog.Info("Shutdown complete")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -89,12 +128,26 @@ func main() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
// Up some limits.
|
||||
if err := setLimits(); err != nil {
|
||||
if err := limits.SetLimits(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Call serviceMain on Windows to handle running as a service. When
|
||||
// the return isService flag is true, exit now since we ran as a
|
||||
// service. Otherwise, just fall through to normal operation.
|
||||
if runtime.GOOS == "windows" {
|
||||
isService, err := winServiceMain()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if isService {
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
// Work around defer not working after os.Exit()
|
||||
if err := btcdMain(); err != nil {
|
||||
if err := btcdMain(nil); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
471
config.go
471
config.go
@@ -1,39 +1,60 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/conformal/btcdb"
|
||||
_ "github.com/conformal/btcdb/ldb"
|
||||
_ "github.com/conformal/btcdb/memdb"
|
||||
"github.com/conformal/btcutil"
|
||||
"github.com/conformal/btcwire"
|
||||
"github.com/conformal/go-flags"
|
||||
"github.com/conformal/go-socks"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultConfigFilename = "btcd.conf"
|
||||
defaultLogLevel = "info"
|
||||
defaultBtcnet = btcwire.MainNet
|
||||
defaultMaxPeers = 125
|
||||
defaultBanDuration = time.Hour * 24
|
||||
defaultVerifyEnabled = false
|
||||
defaultDbType = "leveldb"
|
||||
defaultConfigFilename = "btcd.conf"
|
||||
defaultDataDirname = "data"
|
||||
defaultLogLevel = "info"
|
||||
defaultLogDirname = "logs"
|
||||
defaultLogFilename = "btcd.log"
|
||||
defaultBtcnet = btcwire.MainNet
|
||||
defaultMaxPeers = 125
|
||||
defaultBanDuration = time.Hour * 24
|
||||
defaultMaxRPCClients = 10
|
||||
defaultMaxRPCWebsockets = 25
|
||||
defaultVerifyEnabled = false
|
||||
defaultDbType = "leveldb"
|
||||
defaultFreeTxRelayLimit = 15.0
|
||||
)
|
||||
|
||||
var (
|
||||
defaultConfigFile = filepath.Join(btcdHomeDir(), defaultConfigFilename)
|
||||
defaultDataDir = filepath.Join(btcdHomeDir(), "data")
|
||||
knownDbTypes = btcdb.SupportedDBs()
|
||||
btcdHomeDir = btcutil.AppDataDir("btcd", false)
|
||||
defaultConfigFile = filepath.Join(btcdHomeDir, defaultConfigFilename)
|
||||
defaultDataDir = filepath.Join(btcdHomeDir, defaultDataDirname)
|
||||
defaultListener = net.JoinHostPort("", netParams(defaultBtcnet).listenPort)
|
||||
knownDbTypes = btcdb.SupportedDBs()
|
||||
defaultRPCKeyFile = filepath.Join(btcdHomeDir, "rpc.key")
|
||||
defaultRPCCertFile = filepath.Join(btcdHomeDir, "rpc.cert")
|
||||
defaultLogDir = filepath.Join(btcdHomeDir, defaultLogDirname)
|
||||
)
|
||||
|
||||
// runServiceCommand is only set to a real function on Windows. It is used
|
||||
// to parse and execute service commands specified via the -s flag.
|
||||
var runServiceCommand func(string) error
|
||||
|
||||
// config defines the configuration options for btcd.
|
||||
//
|
||||
// See loadConfig for details on the configuration load process.
|
||||
@@ -41,45 +62,49 @@ type config struct {
|
||||
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
|
||||
ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`
|
||||
DataDir string `short:"b" long:"datadir" description:"Directory to store data"`
|
||||
LogDir string `long:"logdir" description:"Directory to log output."`
|
||||
AddPeers []string `short:"a" long:"addpeer" description:"Add a peer to connect with at startup"`
|
||||
ConnectPeers []string `long:"connect" description:"Connect only to the specified peers at startup"`
|
||||
DisableListen bool `long:"nolisten" description:"Disable listening for incoming connections -- NOTE: Listening is automatically disabled if the --connect option is used or if the --proxy option is used without the --tor option"`
|
||||
Port string `short:"p" long:"port" description:"Listen for connections on this port (default: 8333, testnet: 18333)"`
|
||||
DisableListen bool `long:"nolisten" description:"Disable listening for incoming connections -- NOTE: Listening is automatically disabled if the --connect or --proxy options are used without also specifying listen interfaces via --listen"`
|
||||
Listeners []string `long:"listen" description:"Add an interface/port to listen for connections (default all interfaces port: 8333, testnet: 18333)"`
|
||||
MaxPeers int `long:"maxpeers" description:"Max number of inbound and outbound peers"`
|
||||
BanDuration time.Duration `long:"banduration" description:"How long to ban misbehaving peers. Valid time units are {s, m, h}. Minimum 1 second"`
|
||||
RPCUser string `short:"u" long:"rpcuser" description:"Username for RPC connections"`
|
||||
RPCPass string `short:"P" long:"rpcpass" description:"Password for RPC connections"`
|
||||
RPCPort string `short:"r" long:"rpcport" description:"Listen for JSON/RPC messages on this port"`
|
||||
RPCPass string `short:"P" long:"rpcpass" default-mask:"-" description:"Password for RPC connections"`
|
||||
RPCListeners []string `long:"rpclisten" description:"Add an interface/port to listen for RPC connections (default port: 8334, testnet: 18334)"`
|
||||
RPCCert string `long:"rpccert" description:"File containing the certificate file"`
|
||||
RPCKey string `long:"rpckey" description:"File containing the certificate key"`
|
||||
RPCMaxClients int `long:"rpcmaxclients" description:"Max number of RPC clients for standard connections"`
|
||||
RPCMaxWebsockets int `long:"rpcmaxwebsockets" description:"Max number of RPC websocket connections"`
|
||||
DisableRPC bool `long:"norpc" description:"Disable built-in RPC server -- NOTE: The RPC server is disabled by default if no rpcuser/rpcpass is specified"`
|
||||
DisableDNSSeed bool `long:"nodnsseed" description:"Disable DNS seeding for peers"`
|
||||
ExternalIPs []string `long:"externalip" description:"Add an ip to the list of local addresses we claim to listen on to peers"`
|
||||
Proxy string `long:"proxy" description:"Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)"`
|
||||
ProxyUser string `long:"proxyuser" description:"Username for proxy server"`
|
||||
ProxyPass string `long:"proxypass" description:"Password for proxy server"`
|
||||
UseTor bool `long:"tor" description:"Specifies the proxy server used is a Tor node"`
|
||||
ProxyPass string `long:"proxypass" default-mask:"-" description:"Password for proxy server"`
|
||||
OnionProxy string `long:"onion" description:"Connect to tor hidden services via SOCKS5 proxy (eg. 127.0.0.1:9050)"`
|
||||
OnionProxyUser string `long:"onionuser" description:"Username for onion proxy server"`
|
||||
OnionProxyPass string `long:"onionpass" default-mask:"-" description:"Password for onion proxy server"`
|
||||
NoOnion bool `long:"noonion" description:"Disable connecting to tor hidden services"`
|
||||
TestNet3 bool `long:"testnet" description:"Use the test network"`
|
||||
RegressionTest bool `long:"regtest" description:"Use the regression test network"`
|
||||
DisableCheckpoints bool `long:"nocheckpoints" description:"Disable built-in checkpoints. Don't do this unless you know what you're doing."`
|
||||
DbType string `long:"dbtype" description:"Database backend to use for the Block Chain"`
|
||||
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
|
||||
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}"`
|
||||
CpuProfile string `long:"cpuprofile" description:"Write CPU profile to the specified file"`
|
||||
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"`
|
||||
Upnp bool `long:"upnp" description:"Use UPnP to map our listening port outside of NAT"`
|
||||
FreeTxRelayLimit float64 `long:"limitfreerelay" description:"Limit relay of transactions with no transaction fee to the given amount in thousands of bytes per minute"`
|
||||
onionlookup func(string) ([]net.IP, error)
|
||||
lookup func(string) ([]net.IP, error)
|
||||
oniondial func(string, string) (net.Conn, error)
|
||||
dial func(string, string) (net.Conn, error)
|
||||
}
|
||||
|
||||
// btcdHomeDir returns an OS appropriate home directory for btcd.
|
||||
func btcdHomeDir() string {
|
||||
// Search for Windows APPDATA first. This won't exist on POSIX OSes.
|
||||
appData := os.Getenv("APPDATA")
|
||||
if appData != "" {
|
||||
return filepath.Join(appData, "btcd")
|
||||
}
|
||||
|
||||
// Fall back to standard HOME directory that works for most POSIX OSes.
|
||||
home := os.Getenv("HOME")
|
||||
if home != "" {
|
||||
return filepath.Join(home, ".btcd")
|
||||
}
|
||||
|
||||
// In the worst case, use the current directory.
|
||||
return "."
|
||||
// serviceOptions defines the configuration options for btcd as a service on
|
||||
// Windows.
|
||||
type serviceOptions struct {
|
||||
ServiceCommand string `short:"s" long:"service" description:"Service command {install, remove, start, stop}"`
|
||||
}
|
||||
|
||||
// cleanAndExpandPath expands environement variables and leading ~ in the
|
||||
@@ -87,7 +112,7 @@ func btcdHomeDir() string {
|
||||
func cleanAndExpandPath(path string) string {
|
||||
// Expand initial ~ to OS specific home directory.
|
||||
if strings.HasPrefix(path, "~") {
|
||||
homeDir := filepath.Dir(btcdHomeDir())
|
||||
homeDir := filepath.Dir(btcdHomeDir)
|
||||
path = strings.Replace(path, "~", homeDir, 1)
|
||||
}
|
||||
|
||||
@@ -115,6 +140,71 @@ func validLogLevel(logLevel string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// supportedSubsystems returns a sorted slice of the supported subsystems for
|
||||
// logging purposes.
|
||||
func supportedSubsystems() []string {
|
||||
// Convert the subsystemLoggers map keys to a slice.
|
||||
subsystems := make([]string, 0, len(subsystemLoggers))
|
||||
for subsysID := range subsystemLoggers {
|
||||
subsystems = append(subsystems, subsysID)
|
||||
}
|
||||
|
||||
// Sort the subsytems for stable display.
|
||||
sort.Strings(subsystems)
|
||||
return subsystems
|
||||
}
|
||||
|
||||
// parseAndSetDebugLevels attempts to parse the specified debug level and set
|
||||
// the levels accordingly. An appropriate error is returned if anything is
|
||||
// invalid.
|
||||
func parseAndSetDebugLevels(debugLevel string) error {
|
||||
// When the specified string doesn't have any delimters, treat it as
|
||||
// the log level for all subsystems.
|
||||
if !strings.Contains(debugLevel, ",") && !strings.Contains(debugLevel, "=") {
|
||||
// Validate debug log level.
|
||||
if !validLogLevel(debugLevel) {
|
||||
str := "The specified debug level [%v] is invalid"
|
||||
return fmt.Errorf(str, debugLevel)
|
||||
}
|
||||
|
||||
// Change the logging level for all subsystems.
|
||||
setLogLevels(debugLevel)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Split the specified string into subsystem/level pairs while detecting
|
||||
// issues and update the log levels accordingly.
|
||||
for _, logLevelPair := range strings.Split(debugLevel, ",") {
|
||||
if !strings.Contains(logLevelPair, "=") {
|
||||
str := "The specified debug level contains an invalid " +
|
||||
"subsystem/level pair [%v]"
|
||||
return fmt.Errorf(str, logLevelPair)
|
||||
}
|
||||
|
||||
// Extract the specified subsystem and log level.
|
||||
fields := strings.Split(logLevelPair, "=")
|
||||
subsysID, logLevel := fields[0], fields[1]
|
||||
|
||||
// Validate subsystem.
|
||||
if _, exists := subsystemLoggers[subsysID]; !exists {
|
||||
str := "The specified subsystem [%v] is invalid -- " +
|
||||
"supported subsytems %v"
|
||||
return fmt.Errorf(str, subsysID, supportedSubsystems())
|
||||
}
|
||||
|
||||
// Validate log level.
|
||||
if !validLogLevel(logLevel) {
|
||||
str := "The specified debug level [%v] is invalid"
|
||||
return fmt.Errorf(str, logLevel)
|
||||
}
|
||||
|
||||
setLogLevel(subsysID, logLevel)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validDbType returns whether or not dbType is a supported database type.
|
||||
func validDbType(dbType string) bool {
|
||||
for _, knownType := range knownDbTypes {
|
||||
@@ -126,16 +216,6 @@ func validDbType(dbType string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// normalizePeerAddress returns addr with the default peer port appended if
|
||||
// there is not already a port specified.
|
||||
func normalizePeerAddress(addr string) string {
|
||||
_, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return net.JoinHostPort(addr, activeNetParams.peerPort)
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// removeDuplicateAddresses returns a new slice with all duplicate entries in
|
||||
// addrs removed.
|
||||
func removeDuplicateAddresses(addrs []string) []string {
|
||||
@@ -150,29 +230,24 @@ func removeDuplicateAddresses(addrs []string) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
// normalizeAndRemoveDuplicateAddresses return a new slice with all the passed
|
||||
// addresses normalized and duplicates removed.
|
||||
func normalizeAndRemoveDuplicateAddresses(addrs []string) []string {
|
||||
for i, addr := range addrs {
|
||||
addrs[i] = normalizePeerAddress(addr)
|
||||
// normalizeAddress returns addr with the passed default port appended if
|
||||
// there is not already a port specified.
|
||||
func normalizeAddress(addr, defaultPort string) string {
|
||||
_, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return net.JoinHostPort(addr, defaultPort)
|
||||
}
|
||||
addrs = removeDuplicateAddresses(addrs)
|
||||
|
||||
return addrs
|
||||
return addr
|
||||
}
|
||||
|
||||
// updateConfigWithActiveParams update the passed config with parameters
|
||||
// from the active net params if the relevant options in the passed config
|
||||
// object are the default so options specified by the user on the command line
|
||||
// are not overridden.
|
||||
func updateConfigWithActiveParams(cfg *config) {
|
||||
if cfg.Port == netParams(defaultBtcnet).listenPort {
|
||||
cfg.Port = activeNetParams.listenPort
|
||||
// normalizeAddresses returns a new slice with all the passed peer addresses
|
||||
// normalized with the given default port, and all duplicates removed.
|
||||
func normalizeAddresses(addrs []string, defaultPort string) []string {
|
||||
for i, addr := range addrs {
|
||||
addrs[i] = normalizeAddress(addr, defaultPort)
|
||||
}
|
||||
|
||||
if cfg.RPCPort == netParams(defaultBtcnet).rpcPort {
|
||||
cfg.RPCPort = activeNetParams.rpcPort
|
||||
}
|
||||
return removeDuplicateAddresses(addrs)
|
||||
}
|
||||
|
||||
// filesExists reports whether the named file or directory exists.
|
||||
@@ -185,6 +260,15 @@ func fileExists(name string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// newConfigParser returns a new command line flags parser.
|
||||
func newConfigParser(cfg *config, so *serviceOptions, options flags.Options) *flags.Parser {
|
||||
parser := flags.NewParser(cfg, options)
|
||||
if runtime.GOOS == "windows" {
|
||||
parser.AddGroup("Service Options", "Service Options", so)
|
||||
}
|
||||
return parser
|
||||
}
|
||||
|
||||
// loadConfig initializes and parses the config using a config file and command
|
||||
// line options.
|
||||
//
|
||||
@@ -200,27 +284,36 @@ func fileExists(name string) bool {
|
||||
func loadConfig() (*config, []string, error) {
|
||||
// Default config.
|
||||
cfg := config{
|
||||
DebugLevel: defaultLogLevel,
|
||||
Port: netParams(defaultBtcnet).listenPort,
|
||||
RPCPort: netParams(defaultBtcnet).rpcPort,
|
||||
MaxPeers: defaultMaxPeers,
|
||||
BanDuration: defaultBanDuration,
|
||||
ConfigFile: defaultConfigFile,
|
||||
DataDir: defaultDataDir,
|
||||
DbType: defaultDbType,
|
||||
ConfigFile: defaultConfigFile,
|
||||
DebugLevel: defaultLogLevel,
|
||||
MaxPeers: defaultMaxPeers,
|
||||
BanDuration: defaultBanDuration,
|
||||
RPCMaxClients: defaultMaxRPCClients,
|
||||
RPCMaxWebsockets: defaultMaxRPCWebsockets,
|
||||
DataDir: defaultDataDir,
|
||||
LogDir: defaultLogDir,
|
||||
DbType: defaultDbType,
|
||||
RPCKey: defaultRPCKeyFile,
|
||||
RPCCert: defaultRPCCertFile,
|
||||
FreeTxRelayLimit: defaultFreeTxRelayLimit,
|
||||
}
|
||||
|
||||
// Service options which are only added on Windows.
|
||||
serviceOpts := serviceOptions{}
|
||||
|
||||
// Create the home directory if it doesn't already exist.
|
||||
err := os.MkdirAll(btcdHomeDir, 0700)
|
||||
if err != nil {
|
||||
btcdLog.Errorf("%v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Pre-parse the command line options to see if an alternative config
|
||||
// file or the version flag was specified.
|
||||
// file or the version flag was specified. Any errors can be ignored
|
||||
// here since they will be caught be the final parse below.
|
||||
preCfg := cfg
|
||||
preParser := flags.NewParser(&preCfg, flags.Default)
|
||||
_, err := preParser.Parse()
|
||||
if err != nil {
|
||||
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
||||
preParser.WriteHelp(os.Stderr)
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
preParser := newConfigParser(&preCfg, &serviceOpts, flags.None)
|
||||
preParser.Parse()
|
||||
|
||||
// Show the version and exit if the version flag was specified.
|
||||
if preCfg.ShowVersion {
|
||||
@@ -230,16 +323,30 @@ func loadConfig() (*config, []string, error) {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Load additional config from file.
|
||||
parser := flags.NewParser(&cfg, flags.Default)
|
||||
err = parser.ParseIniFile(preCfg.ConfigFile)
|
||||
if err != nil {
|
||||
if _, ok := err.(*os.PathError); !ok {
|
||||
// Perform service command and exit if specified. Invalid service
|
||||
// commands show an appropriate error. Only runs on Windows since
|
||||
// the runServiceCommand function will be nil when not on Windows.
|
||||
if serviceOpts.ServiceCommand != "" && runServiceCommand != nil {
|
||||
err := runServiceCommand(serviceOpts.ServiceCommand)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
parser.WriteHelp(os.Stderr)
|
||||
return nil, nil, err
|
||||
}
|
||||
log.Warnf("%v", err)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Load additional config from file.
|
||||
var configFileError error
|
||||
parser := newConfigParser(&cfg, &serviceOpts, flags.Default)
|
||||
if !preCfg.RegressionTest || preCfg.ConfigFile != defaultConfigFile {
|
||||
err := flags.NewIniParser(parser).ParseFile(preCfg.ConfigFile)
|
||||
if err != nil {
|
||||
if _, ok := err.(*os.PathError); !ok {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
parser.WriteHelp(os.Stderr)
|
||||
return nil, nil, err
|
||||
}
|
||||
configFileError = err
|
||||
}
|
||||
}
|
||||
|
||||
// Don't add peers from the config file when in regression test mode.
|
||||
@@ -273,12 +380,34 @@ func loadConfig() (*config, []string, error) {
|
||||
} else if cfg.RegressionTest {
|
||||
activeNetParams = netParams(btcwire.TestNet)
|
||||
}
|
||||
updateConfigWithActiveParams(&cfg)
|
||||
|
||||
// Validate debug log level.
|
||||
if !validLogLevel(cfg.DebugLevel) {
|
||||
str := "%s: The specified debug level [%v] is invalid"
|
||||
err := fmt.Errorf(str, "loadConfig", cfg.DebugLevel)
|
||||
// Append the network type to the data directory so it is "namespaced"
|
||||
// per network. In addition to the block database, there are other
|
||||
// pieces of data that are saved to disk such as address manager state.
|
||||
// All data is specific to a network, so namespacing the data directory
|
||||
// means each individual piece of serialized data does not have to
|
||||
// worry about changing names per network and such.
|
||||
cfg.DataDir = cleanAndExpandPath(cfg.DataDir)
|
||||
cfg.DataDir = filepath.Join(cfg.DataDir, activeNetParams.netName)
|
||||
|
||||
// Append the network type to the log directory so it is "namespaced"
|
||||
// per network in the same fashion as the data directory.
|
||||
cfg.LogDir = cleanAndExpandPath(cfg.LogDir)
|
||||
cfg.LogDir = filepath.Join(cfg.LogDir, activeNetParams.netName)
|
||||
|
||||
// Special show command to list supported subsystems and exit.
|
||||
if cfg.DebugLevel == "show" {
|
||||
fmt.Println("Supported subsystems", supportedSubsystems())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Initialize logging at the default logging level.
|
||||
initSeelogLogger(filepath.Join(cfg.LogDir, defaultLogFilename))
|
||||
setLogLevels(defaultLogLevel)
|
||||
|
||||
// Parse, validate, and set debug log level(s).
|
||||
if err := parseAndSetDebugLevels(cfg.DebugLevel); err != nil {
|
||||
err := fmt.Errorf("%s: %v", "loadConfig", err.Error())
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
parser.WriteHelp(os.Stderr)
|
||||
return nil, nil, err
|
||||
@@ -306,15 +435,6 @@ func loadConfig() (*config, []string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Append the network type to the data directory so it is "namespaced"
|
||||
// per network. In addition to the block database, there are other
|
||||
// pieces of data that are saved to disk such as address manager state.
|
||||
// All data is specific to a network, so namespacing the data directory
|
||||
// means each individual piece of serialized data does not have to
|
||||
// worry about changing names per network and such.
|
||||
cfg.DataDir = cleanAndExpandPath(cfg.DataDir)
|
||||
cfg.DataDir = filepath.Join(cfg.DataDir, activeNetParams.netName)
|
||||
|
||||
// Don't allow ban durations that are too short.
|
||||
if cfg.BanDuration < time.Duration(time.Second) {
|
||||
str := "%s: The banduration option may not be less than 1s -- parsed [%v]"
|
||||
@@ -334,24 +454,24 @@ func loadConfig() (*config, []string, error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// --tor requires --proxy to be set.
|
||||
if cfg.UseTor && cfg.Proxy == "" {
|
||||
str := "%s: the --tor option requires --proxy to be set"
|
||||
err := fmt.Errorf(str, "loadConfig")
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
parser.WriteHelp(os.Stderr)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// --proxy without --tor means no listening.
|
||||
if cfg.Proxy != "" && !cfg.UseTor {
|
||||
// --proxy or --connect without --listen disables listening.
|
||||
if (cfg.Proxy != "" || len(cfg.ConnectPeers) > 0) &&
|
||||
len(cfg.Listeners) == 0 {
|
||||
cfg.DisableListen = true
|
||||
}
|
||||
|
||||
// Connect means no seeding or listening.
|
||||
// Connect means no DNS seeding.
|
||||
if len(cfg.ConnectPeers) > 0 {
|
||||
cfg.DisableDNSSeed = true
|
||||
cfg.DisableListen = true
|
||||
}
|
||||
|
||||
// Add the default listener if none were specified. The default
|
||||
// listener is all addresses on the listen port for the network
|
||||
// we are to connect to.
|
||||
if len(cfg.Listeners) == 0 {
|
||||
cfg.Listeners = []string{
|
||||
net.JoinHostPort("", activeNetParams.listenPort),
|
||||
}
|
||||
}
|
||||
|
||||
// The RPC server is disabled if no username or password is provided.
|
||||
@@ -359,10 +479,127 @@ func loadConfig() (*config, []string, error) {
|
||||
cfg.DisableRPC = true
|
||||
}
|
||||
|
||||
// Default RPC to listen on localhost only.
|
||||
if !cfg.DisableRPC && len(cfg.RPCListeners) == 0 {
|
||||
addrs, err := net.LookupHost("localhost")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cfg.RPCListeners = make([]string, 0, len(addrs))
|
||||
for _, addr := range addrs {
|
||||
addr = net.JoinHostPort(addr, activeNetParams.rpcPort)
|
||||
cfg.RPCListeners = append(cfg.RPCListeners, addr)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Add default port to all listener addresses if needed and remove
|
||||
// duplicate addresses.
|
||||
cfg.Listeners = normalizeAddresses(cfg.Listeners,
|
||||
activeNetParams.listenPort)
|
||||
|
||||
// Add default port to all rpc listener addresses if needed and remove
|
||||
// duplicate addresses.
|
||||
cfg.RPCListeners = normalizeAddresses(cfg.RPCListeners,
|
||||
activeNetParams.rpcPort)
|
||||
|
||||
// Add default port to all added peer addresses if needed and remove
|
||||
// duplicate addresses.
|
||||
cfg.AddPeers = normalizeAndRemoveDuplicateAddresses(cfg.AddPeers)
|
||||
cfg.ConnectPeers = normalizeAndRemoveDuplicateAddresses(cfg.ConnectPeers)
|
||||
cfg.AddPeers = normalizeAddresses(cfg.AddPeers,
|
||||
activeNetParams.peerPort)
|
||||
cfg.ConnectPeers = normalizeAddresses(cfg.ConnectPeers,
|
||||
activeNetParams.peerPort)
|
||||
|
||||
// Setup dial and DNS resolution (lookup) functions depending on the
|
||||
// specified options. The default is to use the standard net.Dial
|
||||
// function as well as the system DNS resolver. When a proxy is
|
||||
// specified, the dial function is set to the proxy specific dial
|
||||
// function and the lookup is set to use tor (unless --noonion is
|
||||
// specified in which case the system DNS resolver is used).
|
||||
cfg.dial = net.Dial
|
||||
cfg.lookup = net.LookupIP
|
||||
if cfg.Proxy != "" {
|
||||
proxy := &socks.Proxy{
|
||||
Addr: cfg.Proxy,
|
||||
Username: cfg.ProxyUser,
|
||||
Password: cfg.ProxyPass,
|
||||
}
|
||||
cfg.dial = proxy.Dial
|
||||
if !cfg.NoOnion {
|
||||
cfg.lookup = func(host string) ([]net.IP, error) {
|
||||
return torLookupIP(host, cfg.Proxy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Setup onion address dial and DNS resolution (lookup) functions
|
||||
// depending on the specified options. The default is to use the
|
||||
// same dial and lookup functions selected above. However, when an
|
||||
// onion-specific proxy is specified, the onion address dial and
|
||||
// lookup functions are set to use the onion-specific proxy while
|
||||
// leaving the normal dial and lookup functions as selected above.
|
||||
// This allows .onion address traffic to be routed through a different
|
||||
// proxy than normal traffic.
|
||||
if cfg.OnionProxy != "" {
|
||||
cfg.oniondial = func(a, b string) (net.Conn, error) {
|
||||
proxy := &socks.Proxy{
|
||||
Addr: cfg.OnionProxy,
|
||||
Username: cfg.OnionProxyUser,
|
||||
Password: cfg.OnionProxyPass,
|
||||
}
|
||||
return proxy.Dial(a, b)
|
||||
}
|
||||
cfg.onionlookup = func(host string) ([]net.IP, error) {
|
||||
return torLookupIP(host, cfg.OnionProxy)
|
||||
}
|
||||
} else {
|
||||
cfg.oniondial = cfg.dial
|
||||
cfg.onionlookup = cfg.lookup
|
||||
}
|
||||
|
||||
// Specifying --noonion means the onion address dial and DNS resolution
|
||||
// (lookup) functions result in an error.
|
||||
if cfg.NoOnion {
|
||||
cfg.oniondial = func(a, b string) (net.Conn, error) {
|
||||
return nil, errors.New("tor has been disabled")
|
||||
}
|
||||
cfg.onionlookup = func(a string) ([]net.IP, error) {
|
||||
return nil, errors.New("tor has been disabled")
|
||||
}
|
||||
}
|
||||
|
||||
// Warn about missing config file only after all other configuration is
|
||||
// done. This prevents the warning on help messages and invalid
|
||||
// options. Note this should go directly before the return.
|
||||
if configFileError != nil {
|
||||
btcdLog.Warnf("%v", configFileError)
|
||||
}
|
||||
|
||||
return &cfg, remainingArgs, nil
|
||||
}
|
||||
|
||||
// btcdDial connects to the address on the named network using the appropriate
|
||||
// dial function depending on the address and configuration options. For
|
||||
// example, .onion addresses will be dialed using the onion specific proxy if
|
||||
// one was specified, but will otherwise use the normal dial function (which
|
||||
// could itself use a proxy or not).
|
||||
func btcdDial(network, address string) (net.Conn, error) {
|
||||
if strings.HasSuffix(address, ".onion") {
|
||||
return cfg.oniondial(network, address)
|
||||
}
|
||||
return cfg.dial(network, address)
|
||||
}
|
||||
|
||||
// btcdLookup returns the correct DNS lookup function to use depending on the
|
||||
// passed host and configuration options. For example, .onion addresses will be
|
||||
// resolved using the onion specific proxy if one was specified, but will
|
||||
// otherwise treat the normal proxy as tor unless --noonion was specified in
|
||||
// which case the lookup will fail. Meanwhile, normal IP addresses will be
|
||||
// resolved using tor if a proxy was specified unless --noonion was also
|
||||
// specified in which case the normal system DNS resolver will be used.
|
||||
func btcdLookup(host string) ([]net.IP, error) {
|
||||
if strings.HasSuffix(host, ".onion") {
|
||||
return cfg.onionlookup(host)
|
||||
}
|
||||
return cfg.lookup(host)
|
||||
}
|
||||
|
||||
87
deps.txt
87
deps.txt
@@ -42,3 +42,90 @@ go-flags 91d04d6c04078a9a1b665530c08b758ec2fbef85
|
||||
go-socks 92ce162c38f029f7fa66c4336b8b5168f2c75d78
|
||||
goleveldb 1d0f0aa639f784f38fa998bdf50911ae8ccbcff3
|
||||
seelog 6b91ad56123bb473755caa213db2bde5422177bf
|
||||
|
||||
btcd 0.3.3 Alpha
|
||||
----------------
|
||||
btcchain 2300b357319ccf3c27029ef90da7eb7f9ff792ba
|
||||
btcdb d2269684720afa0fa79f7b5f4c65e398304274c2
|
||||
btcec a97fd5fe2c670030f8d77dc13b9fa8401ef9f349
|
||||
btcjson d20f958c92e1444d83215c3cf98d6eef41898dcb
|
||||
btcscript f4a6449ad3b90d0c830bf2895b83ced8d5fb91e9
|
||||
btcutil aa811871654079f5036d3692dcf6c66928d19447
|
||||
btcwire dd41f7e91a682b7c1ceed633e12ece6ba7b6bc72
|
||||
btcws 497f1770445677372557d70621782d921a5318e3
|
||||
go-flags fa177a84d3b73bf7e4b79125b2a963bc134eff77
|
||||
go-socks 92ce162c38f029f7fa66c4336b8b5168f2c75d78
|
||||
goleveldb 1d0f0aa639f784f38fa998bdf50911ae8ccbcff3
|
||||
seelog 6b91ad56123bb473755caa213db2bde5422177bf
|
||||
|
||||
btcd 0.4.0 Alpha
|
||||
----------------
|
||||
btcchain 55331de532b4a4749e242cc434f86e7cd0255166
|
||||
btcdb 472c998c0d761fa29ef2e05ddfa2460e943033a6
|
||||
btcec 95b3c063e382af78e4c6876408c31f68efc67840
|
||||
btcjson 1f52db626dd6b1df6103c6b11cf6e73b72cbe536
|
||||
btclog fa3217f76bac7375db18868dfcbc7c69b8c36552
|
||||
btcscript c0c167cc15aa3b721c983fd775cdef7afb42de38
|
||||
btcutil 9759e8dc64c227fc99c2a01b5c3e52f6700d58f0
|
||||
btcwire e5a09bdfaa139999d8195c10cea07312dbeb1065
|
||||
btcws 630d38b1b91215e711868593790ddcd1e50161ec
|
||||
fastsha256 88f426c18509f838fa498c0e82b0faadd86ecc72
|
||||
go-flags a53ab6481be8dd78e060df308a9f577859dfeab5
|
||||
go-socks 92ce162c38f029f7fa66c4336b8b5168f2c75d78
|
||||
goleveldb 1d0f0aa639f784f38fa998bdf50911ae8ccbcff3
|
||||
seelog 6b91ad56123bb473755caa213db2bde5422177bf
|
||||
winsvc 2a5f78f6f2059b884aad8f6907fb029afda48c43
|
||||
|
||||
btcd 0.5.0 Alpha
|
||||
----------------
|
||||
btcchain 28f485a1d1163b378972708cd478f24e64384b3d
|
||||
btcdb 6578e7345f7dafe775b56f0d2db1e08f5cd0e328
|
||||
btcec 58cab817f0863f60fa3c8c14c4b56e115ee549de
|
||||
btcjson bf90ed21428dac774da33a6b965e46c54cb14d38
|
||||
btclog 1cd4812f9be0b0c88dd43510d6ce98adfd083b75
|
||||
btcscript 565f11409c4a1fca39814e9f616a3dada8ca78c7
|
||||
btcutil 2af3c8263a76bc901c2e2cc7a23f71b91ab97189
|
||||
btcwire 6c7f45fdb74e92e8de38775a691545ef888d8998
|
||||
btcws 05a0ba18b48a12b8d0d615a1371befdaa6bdc5dc
|
||||
fastsha256 a3150791c7d7ccb8050fc0d13528b873fd67e8c3
|
||||
go-flags a53ab6481be8dd78e060df308a9f577859dfeab5
|
||||
go-socks 92ce162c38f029f7fa66c4336b8b5168f2c75d78
|
||||
goleveldb 3ce16077443eab51c7bc8371fef66fddee0b5870
|
||||
seelog 6b91ad56123bb473755caa213db2bde5422177bf
|
||||
winsvc 2a5f78f6f2059b884aad8f6907fb029afda48c43
|
||||
|
||||
btcd 0.6.0 Alpha
|
||||
----------------
|
||||
btcchain e1f66f6103a8775f5a21bf2c531d0167a8789100
|
||||
btcdb 0a86df4a162ddd8311194d44231f69e94abd1d23
|
||||
btcec 58cab817f0863f60fa3c8c14c4b56e115ee549de
|
||||
btcjson 0d1539118b5b6be03b4d2c260177ac978fdb4f3a
|
||||
btclog 1cd4812f9be0b0c88dd43510d6ce98adfd083b75
|
||||
btcscript 971fbf8b28711c26c939833bdf53ab286cde02a4
|
||||
btcutil ca515e278dbc106e15a597e8ac5dc39239672f09
|
||||
btcwire f6b03bf8a8308837a5663e537be297956279dd67
|
||||
btcws 3cba42282e40cbc423ad1302bc44ffd060fc5824
|
||||
fastsha256 a3150791c7d7ccb8050fc0d13528b873fd67e8c3
|
||||
go-flags a53ab6481be8dd78e060df308a9f577859dfeab5
|
||||
go-socks 92ce162c38f029f7fa66c4336b8b5168f2c75d78
|
||||
goleveldb 3ce16077443eab51c7bc8371fef66fddee0b5870
|
||||
seelog 6b91ad56123bb473755caa213db2bde5422177bf
|
||||
winsvc 2a5f78f6f2059b884aad8f6907fb029afda48c43
|
||||
|
||||
btcd 0.7.0 Alpha
|
||||
----------------
|
||||
btcchain 149d8176b0ff0b1fc848bca46ab8bca2079b7ab8
|
||||
btcdb 0a86df4a162ddd8311194d44231f69e94abd1d23
|
||||
btcec ff3fac426d4d037505ea8208b79e93c2852451e0
|
||||
btcjson 21b974e2715f48e36dbcb759314dfe96cdfb094d
|
||||
btclog 1cd4812f9be0b0c88dd43510d6ce98adfd083b75
|
||||
btcscript 2b0b512a83acb2bdfa9766b7dc44b6f81cb89c02
|
||||
btcutil ca515e278dbc106e15a597e8ac5dc39239672f09
|
||||
btcwire f6b03bf8a8308837a5663e537be297956279dd67
|
||||
btcws 5c666417351a31c54a7569553198d0763a2337e9
|
||||
fastsha256 a3150791c7d7ccb8050fc0d13528b873fd67e8c3
|
||||
go-flags a53ab6481be8dd78e060df308a9f577859dfeab5
|
||||
go-socks 92ce162c38f029f7fa66c4336b8b5168f2c75d78
|
||||
goleveldb 3ce16077443eab51c7bc8371fef66fddee0b5870
|
||||
seelog 6b91ad56123bb473755caa213db2bde5422177bf
|
||||
winsvc 2a5f78f6f2059b884aad8f6907fb029afda48c43
|
||||
|
||||
44
discovery.go
44
discovery.go
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -7,7 +7,6 @@ package main
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
@@ -41,29 +40,9 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
// try individual DNS server return list of strings for responses.
|
||||
func doDNSLookup(host, proxy string) ([]net.IP, error) {
|
||||
var err error
|
||||
var addrs []net.IP
|
||||
|
||||
if proxy != "" {
|
||||
addrs, err = torLookupIP(host, proxy)
|
||||
} else {
|
||||
addrs, err = net.LookupIP(host)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
// Use Tor to resolve DNS.
|
||||
/*
|
||||
TODO:
|
||||
* this function must be documented internally
|
||||
* this function does not handle IPv6
|
||||
*/
|
||||
// torLookupIP uses Tor to resolve DNS via the SOCKS extension they provide for
|
||||
// resolution over the Tor network. Tor itself doesnt support ipv6 so this
|
||||
// doesn't either.
|
||||
func torLookupIP(host, proxy string) ([]net.IP, error) {
|
||||
conn, err := net.Dial("tcp", proxy)
|
||||
if err != nil {
|
||||
@@ -149,17 +128,12 @@ func torLookupIP(host, proxy string) ([]net.IP, error) {
|
||||
// resolution. If any errors occur then the seeder that errored will not have
|
||||
// any hosts in the list. Therefore if all hosts failed an empty slice of
|
||||
// strings will be returned.
|
||||
func dnsDiscover(seeder string, proxy string) []net.IP {
|
||||
log.Debugf("DISC: Fetching list of seeds from %v", seeder)
|
||||
peers, err := doDNSLookup(seeder, proxy)
|
||||
func dnsDiscover(seeder string) []net.IP {
|
||||
discLog.Debugf("Fetching list of seeds from %v", seeder)
|
||||
peers, err := btcdLookup(seeder)
|
||||
if err != nil {
|
||||
seederPlusProxy := seeder
|
||||
if proxy != "" {
|
||||
seederPlusProxy = fmt.Sprintf("%s (proxy %s)",
|
||||
seeder, proxy)
|
||||
}
|
||||
log.Debugf("DISC: Unable to fetch dns seeds "+
|
||||
"from %s: %v", seederPlusProxy, err)
|
||||
discLog.Debugf("Unable to fetch dns seeds from %s: %v",
|
||||
seeder, err)
|
||||
return []net.IP{}
|
||||
}
|
||||
|
||||
|
||||
97
doc.go
97
doc.go
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -13,48 +13,73 @@ The following section provides a usage overview which enumerates the flags. An
|
||||
interesting point to note is that the long form of all of these options
|
||||
(except -C) can be specified in a configuration file that is automatically
|
||||
parsed when btcd starts up. By default, the configuration file is located at
|
||||
~/.btcd/btcd.conf on POSIX-style operating systems and %APPDATA%\btcd\btcd.conf
|
||||
~/.btcd/btcd.conf on POSIX-style operating systems and %LOCALAPPDATA%\btcd\btcd.conf
|
||||
on Windows. The -C (--configfile) flag, as shown below, can be used to override
|
||||
this location.
|
||||
|
||||
Usage:
|
||||
btcd [OPTIONS]
|
||||
|
||||
The flags are:
|
||||
Application Options:
|
||||
-V, --version Display version information and exit
|
||||
-C, --configfile= Path to configuration file
|
||||
-b, --datadir= Directory to store data
|
||||
-a, --addpeer= Add a peer to connect with at startup
|
||||
--connect= Connect only to the specified peers at startup
|
||||
--nolisten Disable listening for incoming connections -- NOTE:
|
||||
Listening is automatically disabled if the --connect
|
||||
or --proxy options are used without also specifying
|
||||
listen interfaces via --listen
|
||||
--listen= Add an interface/port to listen for connections
|
||||
(default all interfaces port: 8333, testnet: 18333)
|
||||
--maxpeers= Max number of inbound and outbound peers (125)
|
||||
--banduration= How long to ban misbehaving peers. Valid time units
|
||||
are {s, m, h}. Minimum 1 second (24h0m0s)
|
||||
-u, --rpcuser= Username for RPC connections
|
||||
-P, --rpcpass= Password for RPC connections
|
||||
--rpclisten= Add an interface/port to listen for RPC connections
|
||||
(default port: 8334, testnet: 18334)
|
||||
--rpccert= File containing the certificate file
|
||||
--rpckey= File containing the certificate key
|
||||
--rpcmaxclients= Max number of RPC clients for standard connections
|
||||
(10)
|
||||
--rpcmaxwebsockets= Max number of RPC clients for standard connections
|
||||
(25)
|
||||
--norpc Disable built-in RPC server -- NOTE: The RPC server
|
||||
is disabled by default if no rpcuser/rpcpass is
|
||||
specified
|
||||
--nodnsseed Disable DNS seeding for peers
|
||||
--externalip: Add an ip to the list of local addresses we claim to
|
||||
listen on to peers
|
||||
--proxy= Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)
|
||||
--proxyuser= Username for proxy server
|
||||
--proxypass= Password for proxy server
|
||||
--onion= Connect to tor hidden services via SOCKS5 proxy (eg.
|
||||
127.0.0.1:9050)
|
||||
--onionuser= Username for onion proxy server
|
||||
--onionpass= Password for onion proxy server
|
||||
--noonion= Disable connecting to tor hidden services
|
||||
--tor= Specifies the proxy server used is a Tor node
|
||||
--testnet= Use the test network
|
||||
--regtest= Use the regression test network
|
||||
--nocheckpoints= Disable built-in checkpoints. Don't do this unless
|
||||
you know what you're doing.
|
||||
--dbtype= Database backend to use for the Block Chain (leveldb)
|
||||
--profile= Enable HTTP profiling on given port -- NOTE port must
|
||||
be between 1024 and 65536 (6060)
|
||||
--cpuprofile= Write CPU profile to the specified file
|
||||
-d, --debuglevel: Logging level for all subsystems {trace, debug, info,
|
||||
warn, error, critical} -- You may also specify
|
||||
<subsystem>=<level>,<subsystem2>=<level>,... to set
|
||||
the log level for individual subsystems -- Use show
|
||||
to list available subsystems (info)
|
||||
--upnp Use UPnP to map our listening port outside of NAT
|
||||
--limitfreerelay= Limit relay of transactions with no transaction fee
|
||||
to the given amount in thousands of bytes per minute
|
||||
(15)
|
||||
|
||||
Help Options:
|
||||
-h, --help Show this help message
|
||||
-V, --version Display version information and exit
|
||||
-C, --configfile= Path to configuration file
|
||||
-b, --datadir= Directory to store data
|
||||
-a, --addpeer= Add a peer to connect with at startup
|
||||
--connect= Connect only to the specified peers at startup
|
||||
--nolisten Disable listening for incoming connections -- NOTE:
|
||||
Listening is automatically disabled if the --connect
|
||||
option is used or if the --proxy option is used without
|
||||
the --tor option
|
||||
-p, --port= Listen for connections on this port (default: 8333,
|
||||
testnet: 18333)
|
||||
--maxpeers= Max number of inbound and outbound peers
|
||||
--banduration= How long to ban misbehaving peers. Valid time units are
|
||||
{s, m, h}. Minimum 1 second
|
||||
-u, --rpcuser= Username for RPC connections
|
||||
-P, --rpcpass= Password for RPC connections
|
||||
-r, --rpcport= Listen for JSON/RPC messages on this port
|
||||
--norpc Disable built-in RPC server -- NOTE: The RPC server is
|
||||
disabled by default if no rpcuser/rpcpass is specified
|
||||
--nodnsseed Disable DNS seeding for peers
|
||||
--proxy= Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)
|
||||
--proxyuser= Username for proxy server
|
||||
--proxypass= Password for proxy server
|
||||
--tor Specifies the proxy server used is a Tor node
|
||||
--testnet Use the test network
|
||||
--regtest Use the regression test network
|
||||
--nocheckpoints Disable built-in checkpoints. Don't do this unless you
|
||||
know what you're doing
|
||||
--dbtype= Database backend to use for the Block Chain
|
||||
--profile= Enable HTTP profiling on given port -- NOTE port must be
|
||||
between 1024 and 65536
|
||||
-d, --debuglevel= Logging level {trace, debug, info, warn, error,
|
||||
critical}
|
||||
|
||||
*/
|
||||
package main
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
package limits
|
||||
|
||||
// Plan 9 has no process accounting. no-op here
|
||||
func setLimits() error {
|
||||
func SetLimits() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !windows,!plan9
|
||||
|
||||
package main
|
||||
package limits
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -16,7 +16,7 @@ const (
|
||||
fileLimitMin = 1024
|
||||
)
|
||||
|
||||
func setLimits() error {
|
||||
func SetLimits() error {
|
||||
var rLimit syscall.Rlimit
|
||||
|
||||
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
package limits
|
||||
|
||||
func setLimits() error {
|
||||
func SetLimits() error {
|
||||
return nil
|
||||
}
|
||||
163
log.go
163
log.go
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/conformal/btcchain"
|
||||
"github.com/conformal/btcdb"
|
||||
"github.com/conformal/btclog"
|
||||
"github.com/conformal/btcscript"
|
||||
"github.com/conformal/btcwire"
|
||||
"github.com/conformal/seelog"
|
||||
@@ -24,10 +25,40 @@ const (
|
||||
lockTimeThreshold uint32 = 5e8 // Tue Nov 5 00:53:20 1985 UTC
|
||||
)
|
||||
|
||||
// Loggers per subsytem. Note that backendLog is a seelog logger that all of
|
||||
// the subsystem loggers route their messages to. When adding new subsystems,
|
||||
// add a reference here, to the subsystemLoggers map, and the useLogger
|
||||
// function.
|
||||
var (
|
||||
log = seelog.Disabled
|
||||
backendLog = seelog.Disabled
|
||||
btcdLog = btclog.Disabled
|
||||
bcdbLog = btclog.Disabled
|
||||
chanLog = btclog.Disabled
|
||||
scrpLog = btclog.Disabled
|
||||
amgrLog = btclog.Disabled
|
||||
bmgrLog = btclog.Disabled
|
||||
discLog = btclog.Disabled
|
||||
peerLog = btclog.Disabled
|
||||
rpcsLog = btclog.Disabled
|
||||
srvrLog = btclog.Disabled
|
||||
txmpLog = btclog.Disabled
|
||||
)
|
||||
|
||||
// subsystemLoggers maps each subsystem identifier to its associated logger.
|
||||
var subsystemLoggers = map[string]btclog.Logger{
|
||||
"BTCD": btcdLog,
|
||||
"BCDB": bcdbLog,
|
||||
"CHAN": chanLog,
|
||||
"SCRP": scrpLog,
|
||||
"AMGR": amgrLog,
|
||||
"BMGR": bmgrLog,
|
||||
"DISC": discLog,
|
||||
"PEER": peerLog,
|
||||
"RPCS": rpcsLog,
|
||||
"SRVR": srvrLog,
|
||||
"TXMP": txmpLog,
|
||||
}
|
||||
|
||||
// logClosure is used to provide a closure over expensive logging operations
|
||||
// so don't have to be performed when the logging level doesn't warrant it.
|
||||
type logClosure func() string
|
||||
@@ -44,22 +75,68 @@ func newLogClosure(c func() string) logClosure {
|
||||
return logClosure(c)
|
||||
}
|
||||
|
||||
// newLogger creates a new seelog logger using the provided logging level and
|
||||
// log message prefix.
|
||||
func newLogger(level string, prefix string) seelog.LoggerInterface {
|
||||
//<seelog type="adaptive" mininterval="2000000" maxinterval="100000000"
|
||||
// critmsgcount="500" minlevel="%s">
|
||||
// useLogger updates the logger references for subsystemID to logger. Invalid
|
||||
// subsystems are ignored.
|
||||
func useLogger(subsystemID string, logger btclog.Logger) {
|
||||
if _, ok := subsystemLoggers[subsystemID]; !ok {
|
||||
return
|
||||
}
|
||||
subsystemLoggers[subsystemID] = logger
|
||||
|
||||
fmtstring := `
|
||||
<seelog type="sync" minlevel="%s">
|
||||
switch subsystemID {
|
||||
case "BTCD":
|
||||
btcdLog = logger
|
||||
|
||||
case "BCDB":
|
||||
bcdbLog = logger
|
||||
btcdb.UseLogger(logger)
|
||||
|
||||
case "CHAN":
|
||||
chanLog = logger
|
||||
btcchain.UseLogger(logger)
|
||||
|
||||
case "SCRP":
|
||||
scrpLog = logger
|
||||
btcscript.UseLogger(logger)
|
||||
|
||||
case "AMGR":
|
||||
amgrLog = logger
|
||||
|
||||
case "BMGR":
|
||||
bmgrLog = logger
|
||||
|
||||
case "DISC":
|
||||
discLog = logger
|
||||
|
||||
case "PEER":
|
||||
peerLog = logger
|
||||
|
||||
case "RPCS":
|
||||
rpcsLog = logger
|
||||
|
||||
case "SRVR":
|
||||
srvrLog = logger
|
||||
|
||||
case "TXMP":
|
||||
txmpLog = logger
|
||||
}
|
||||
}
|
||||
|
||||
// initSeelogLogger initializes a new seelog logger that is used as the backend
|
||||
// for all logging subsytems.
|
||||
func initSeelogLogger(logFile string) {
|
||||
config := `
|
||||
<seelog type="adaptive" mininterval="2000000" maxinterval="100000000"
|
||||
critmsgcount="500" minlevel="trace">
|
||||
<outputs formatid="all">
|
||||
<console/>
|
||||
<console />
|
||||
<rollingfile type="size" filename="%s" maxsize="10485760" maxrolls="3" />
|
||||
</outputs>
|
||||
<formats>
|
||||
<format id="all" format="%%Time %%Date [%%LEV] %s: %%Msg%%n" />
|
||||
<format id="all" format="%%Time %%Date [%%LEV] %%Msg%%n" />
|
||||
</formats>
|
||||
</seelog>`
|
||||
config := fmt.Sprintf(fmtstring, level, prefix)
|
||||
config = fmt.Sprintf(config, logFile)
|
||||
|
||||
logger, err := seelog.LoggerFromConfigAsString(config)
|
||||
if err != nil {
|
||||
@@ -67,40 +144,42 @@ func newLogger(level string, prefix string) seelog.LoggerInterface {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return logger
|
||||
backendLog = logger
|
||||
}
|
||||
|
||||
// useLogger sets the btcd logger to the passed logger.
|
||||
func useLogger(logger seelog.LoggerInterface) {
|
||||
log = logger
|
||||
// setLogLevel sets the logging level for provided subsystem. Invalid
|
||||
// subsystems are ignored. Uninitialized subsystems are dynamically created as
|
||||
// needed.
|
||||
func setLogLevel(subsystemID string, logLevel string) {
|
||||
// Ignore invalid subsystems.
|
||||
logger, ok := subsystemLoggers[subsystemID]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Default to info if the log level is invalid.
|
||||
level, ok := btclog.LogLevelFromString(logLevel)
|
||||
if !ok {
|
||||
level = btclog.InfoLvl
|
||||
}
|
||||
|
||||
// Create new logger for the subsystem if needed.
|
||||
if logger == btclog.Disabled {
|
||||
logger = btclog.NewSubsystemLogger(backendLog, subsystemID+": ")
|
||||
useLogger(subsystemID, logger)
|
||||
}
|
||||
logger.SetLevel(level)
|
||||
}
|
||||
|
||||
// setLogLevel sets the log level for the logging system. It initializes a
|
||||
// logger for each subsystem at the provided level.
|
||||
func setLogLevel(logLevel string) []seelog.LoggerInterface {
|
||||
var loggers []seelog.LoggerInterface
|
||||
|
||||
// Define sub-systems.
|
||||
subSystems := []struct {
|
||||
level string
|
||||
prefix string
|
||||
useLogger func(seelog.LoggerInterface)
|
||||
}{
|
||||
{logLevel, "BTCD", useLogger},
|
||||
{logLevel, "BCDB", btcdb.UseLogger},
|
||||
{logLevel, "CHAN", btcchain.UseLogger},
|
||||
{logLevel, "SCRP", btcscript.UseLogger},
|
||||
// setLogLevels sets the log level for all subsystem loggers to the passed
|
||||
// level. It also dynamically creates the subsystem loggers as needed, so it
|
||||
// can be used to initialize the logging system.
|
||||
func setLogLevels(logLevel string) {
|
||||
// Configure all sub-systems with the new logging level. Dynamically
|
||||
// create loggers as needed.
|
||||
for subsystemID := range subsystemLoggers {
|
||||
setLogLevel(subsystemID, logLevel)
|
||||
}
|
||||
|
||||
// Configure all sub-systems with new loggers while keeping track of
|
||||
// the created loggers to return so they can be flushed.
|
||||
for _, s := range subSystems {
|
||||
newLog := newLogger(s.level, s.prefix)
|
||||
loggers = append(loggers, newLog)
|
||||
s.useLogger(newLog)
|
||||
}
|
||||
|
||||
return loggers
|
||||
}
|
||||
|
||||
// directionString is a helper function that returns a string that represents
|
||||
@@ -201,7 +280,7 @@ func messageSummary(msg btcwire.Message) string {
|
||||
header := &msg.Header
|
||||
hash, _ := msg.BlockSha()
|
||||
return fmt.Sprintf("hash %s, ver %d, %d tx, %s", hash,
|
||||
header.Version, header.TxnCount, header.Timestamp)
|
||||
header.Version, len(msg.Transactions), header.Timestamp)
|
||||
|
||||
case *btcwire.MsgInv:
|
||||
return invSummary(msg.InvList)
|
||||
|
||||
510
mempool.go
510
mempool.go
@@ -1,17 +1,17 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"github.com/conformal/btcchain"
|
||||
"github.com/conformal/btcdb"
|
||||
"github.com/conformal/btcscript"
|
||||
"github.com/conformal/btcutil"
|
||||
"github.com/conformal/btcwire"
|
||||
"math"
|
||||
"math/big"
|
||||
@@ -49,7 +49,7 @@ const (
|
||||
// maxStandardTxSize is the maximum size allowed for transactions that
|
||||
// are considered standard and will therefore be relayed and considered
|
||||
// for mining.
|
||||
maxStandardTxSize = btcwire.MaxBlockPayload / 10
|
||||
maxStandardTxSize = 100000
|
||||
|
||||
// maxStandardSigScriptSize is the maximum size allowed for a
|
||||
// transaction input signature script to be considered standard. This
|
||||
@@ -60,27 +60,46 @@ const (
|
||||
// prosperity. 3*80 + 3*65 + 65 = 500
|
||||
maxStandardSigScriptSize = 500
|
||||
|
||||
// maxStandardMultiSigs is the maximum number of signatures
|
||||
// allowed in a multi-signature transaction output script for it to be
|
||||
// maxStandardMultiSigKeys is the maximum number of public keys allowed
|
||||
// in a multi-signature transaction output script for it to be
|
||||
// considered standard.
|
||||
maxStandardMultiSigs = 3
|
||||
maxStandardMultiSigKeys = 3
|
||||
|
||||
// minTxRelayFee is the minimum fee in satoshi that is required for
|
||||
// a transaction to be treated as free for relay purposes. It is also
|
||||
// used to help determine if a transation is considered dust.
|
||||
// minTxRelayFee is the minimum fee in satoshi that is required for a
|
||||
// transaction to be treated as free for relay purposes. It is also
|
||||
// used to help determine if a transaction is considered dust and as a
|
||||
// base for calculating minimum required fees for larger transactions.
|
||||
// This value is in Satoshi/KB (kilobyte, not kibibyte).
|
||||
minTxRelayFee = 10000
|
||||
|
||||
// blockPrioritySize is the number of bytes reserved in a block for
|
||||
// high-priority transactions. It is mainly used to help determine the
|
||||
// minimum required fee for a transaction.
|
||||
blockPrioritySize = 50000
|
||||
)
|
||||
|
||||
// TxDesc is a descriptor containing a transaction in the mempool and the
|
||||
// metadata we store about it.
|
||||
type TxDesc struct {
|
||||
Tx *btcutil.Tx // Transaction.
|
||||
Added time.Time // Time when added to pool.
|
||||
Height int64 // Blockheight when added to pool.
|
||||
Fee int64 // Transaction fees.
|
||||
}
|
||||
|
||||
// txMemPool is used as a source of transactions that need to be mined into
|
||||
// blocks and relayed to other peers. It is safe for concurrent access from
|
||||
// multiple peers.
|
||||
type txMemPool struct {
|
||||
sync.RWMutex
|
||||
server *server
|
||||
pool map[btcwire.ShaHash]*btcwire.MsgTx
|
||||
orphans map[btcwire.ShaHash]*btcwire.MsgTx
|
||||
pool map[btcwire.ShaHash]*TxDesc
|
||||
orphans map[btcwire.ShaHash]*btcutil.Tx
|
||||
orphansByPrev map[btcwire.ShaHash]*list.List
|
||||
outpoints map[btcwire.OutPoint]*btcwire.MsgTx
|
||||
outpoints map[btcwire.OutPoint]*btcutil.Tx
|
||||
pennyTotal float64 // exponentially decaying total for penny spends.
|
||||
lastPennyUnix int64 // unix time of last ``penny spend''
|
||||
|
||||
}
|
||||
|
||||
// isDust returns whether or not the passed transaction output amount is
|
||||
@@ -88,14 +107,6 @@ type txMemPool struct {
|
||||
// relay fee. In particular, if the cost to the network to spend coins is more
|
||||
// than 1/3 of the minimum transaction relay fee, it is considered dust.
|
||||
func isDust(txOut *btcwire.TxOut) bool {
|
||||
// Get the serialized size of the transaction output.
|
||||
//
|
||||
// TODO(davec): The serialized size should come from btcwire, but it
|
||||
// currently doesn't provide a way to do so for transaction outputs, so
|
||||
// calculate it here based on the current format.
|
||||
// 8 bytes for value + 1 byte for script length + script length
|
||||
txOutSize := 9 + len(txOut.PkScript)
|
||||
|
||||
// The total serialized size consists of the output and the associated
|
||||
// input script to redeem it. Since there is no input script
|
||||
// to redeem it yet, use the minimum size of a typical input script.
|
||||
@@ -138,17 +149,17 @@ func isDust(txOut *btcwire.TxOut) bool {
|
||||
// The most common scripts are pay-to-pubkey-hash, and as per the above
|
||||
// breakdown, the minimum size of a p2pkh input script is 148 bytes. So
|
||||
// that figure is used.
|
||||
totalSize := txOutSize + 148
|
||||
totalSize := txOut.SerializeSize() + 148
|
||||
|
||||
// The output is considered dust if the cost to the network to spend the
|
||||
// coins is more than 1/3 of the minimum transaction relay fee.
|
||||
// minTxRelayFee is in Satoshi/KB (kilobyte, not kibibyte), so
|
||||
// coins is more than 1/3 of the minimum free transaction relay fee.
|
||||
// minFreeTxRelayFee is in Satoshi/KB (kilobyte, not kibibyte), so
|
||||
// multiply by 1000 to convert bytes.
|
||||
//
|
||||
// Using the typical values for a pay-to-pubkey-hash transaction from
|
||||
// the breakdown above and the default minimum transaction relay fee of
|
||||
// 10000, this equates to values less than 5460 satoshi being considered
|
||||
// dust.
|
||||
// the breakdown above and the default minimum free transaction relay
|
||||
// fee of 10000, this equates to values less than 5460 satoshi being
|
||||
// considered dust.
|
||||
//
|
||||
// The following is equivalent to (value/totalSize) * (1/3) * 1000
|
||||
// without needing to do floating point math.
|
||||
@@ -158,22 +169,42 @@ func isDust(txOut *btcwire.TxOut) bool {
|
||||
// checkPkScriptStandard performs a series of checks on a transaction ouput
|
||||
// script (public key script) to ensure it is a "standard" public key script.
|
||||
// A standard public key script is one that is a recognized form, and for
|
||||
// multi-signature scripts, only contains from 1 to 3 signatures.
|
||||
func checkPkScriptStandard(pkScript []byte) error {
|
||||
scriptClass := btcscript.GetScriptClass(pkScript)
|
||||
// multi-signature scripts, only contains from 1 to maxStandardMultiSigKeys
|
||||
// public keys.
|
||||
func checkPkScriptStandard(pkScript []byte, scriptClass btcscript.ScriptClass) error {
|
||||
switch scriptClass {
|
||||
case btcscript.MultiSigTy:
|
||||
// TODO(davec): Need to get the actual number of signatures.
|
||||
numSigs := 1
|
||||
numPubKeys, numSigs, err := btcscript.CalcMultiSigStats(pkScript)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// A standard multi-signature public key script must contain
|
||||
// from 1 to maxStandardMultiSigKeys public keys.
|
||||
if numPubKeys < 1 {
|
||||
str := fmt.Sprintf("multi-signature script with no " +
|
||||
"pubkeys")
|
||||
return TxRuleError(str)
|
||||
}
|
||||
if numPubKeys > maxStandardMultiSigKeys {
|
||||
str := fmt.Sprintf("multi-signature script with %d "+
|
||||
"public keys which is more than the allowed "+
|
||||
"max of %d", numPubKeys, maxStandardMultiSigKeys)
|
||||
return TxRuleError(str)
|
||||
}
|
||||
|
||||
// A standard multi-signature public key script must have at
|
||||
// least 1 signature and no more signatures than available
|
||||
// public keys.
|
||||
if numSigs < 1 {
|
||||
str := fmt.Sprintf("multi-signature script with no " +
|
||||
"signatures")
|
||||
return TxRuleError(str)
|
||||
}
|
||||
if numSigs > maxStandardMultiSigs {
|
||||
if numSigs > numPubKeys {
|
||||
str := fmt.Sprintf("multi-signature script with %d "+
|
||||
"signatures which is more than the allowed max "+
|
||||
"of %d", numSigs, maxStandardMultiSigs)
|
||||
"signatures which is more than the available "+
|
||||
"%d public keys", numSigs, numPubKeys)
|
||||
return TxRuleError(str)
|
||||
}
|
||||
|
||||
@@ -191,11 +222,13 @@ func checkPkScriptStandard(pkScript []byte) error {
|
||||
// finalized, conforming to more stringent size constraints, having scripts
|
||||
// of recognized forms, and not containing "dust" outputs (those that are
|
||||
// so small it costs more to process them than they are worth).
|
||||
func checkTransactionStandard(tx *btcwire.MsgTx, height int64) error {
|
||||
func checkTransactionStandard(tx *btcutil.Tx, height int64) error {
|
||||
msgTx := tx.MsgTx()
|
||||
|
||||
// The transaction must be a currently supported version.
|
||||
if tx.Version > btcwire.TxVersion || tx.Version < 1 {
|
||||
if msgTx.Version > btcwire.TxVersion || msgTx.Version < 1 {
|
||||
str := fmt.Sprintf("transaction version %d is not in the "+
|
||||
"valid range of %d-%d", tx.Version, 1,
|
||||
"valid range of %d-%d", msgTx.Version, 1,
|
||||
btcwire.TxVersion)
|
||||
return TxRuleError(str)
|
||||
}
|
||||
@@ -211,19 +244,14 @@ func checkTransactionStandard(tx *btcwire.MsgTx, height int64) error {
|
||||
// almost as much to process as the sender fees, limit the maximum
|
||||
// size of a transaction. This also helps mitigate CPU exhaustion
|
||||
// attacks.
|
||||
var serializedTxBuf bytes.Buffer
|
||||
err := tx.Serialize(&serializedTxBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serializedLen := serializedTxBuf.Len()
|
||||
serializedLen := msgTx.SerializeSize()
|
||||
if serializedLen > maxStandardTxSize {
|
||||
str := fmt.Sprintf("transaction size of %v is larger than max "+
|
||||
"allowed size of %v", serializedLen, maxStandardTxSize)
|
||||
return TxRuleError(str)
|
||||
}
|
||||
|
||||
for i, txIn := range tx.TxIn {
|
||||
for i, txIn := range msgTx.TxIn {
|
||||
// Each transaction input signature script must not exceed the
|
||||
// maximum size allowed for a standard transaction. See
|
||||
// the comment on maxStandardSigScriptSize for more details.
|
||||
@@ -243,17 +271,34 @@ func checkTransactionStandard(tx *btcwire.MsgTx, height int64) error {
|
||||
"script is not push only", i)
|
||||
return TxRuleError(str)
|
||||
}
|
||||
|
||||
// Each transaction input signature script must only contain
|
||||
// canonical data pushes. A canonical data push is one where
|
||||
// the minimum possible number of bytes is used to represent
|
||||
// the data push as possible.
|
||||
if !btcscript.HasCanonicalPushes(txIn.SignatureScript) {
|
||||
str := fmt.Sprintf("transaction input %d: signature "+
|
||||
"script has a non-canonical data push", i)
|
||||
return TxRuleError(str)
|
||||
}
|
||||
}
|
||||
|
||||
// None of the output public key scripts can be a non-standard script or
|
||||
// be "dust".
|
||||
for i, txOut := range tx.TxOut {
|
||||
err := checkPkScriptStandard(txOut.PkScript)
|
||||
numNullDataOutputs := 0
|
||||
for i, txOut := range msgTx.TxOut {
|
||||
scriptClass := btcscript.GetScriptClass(txOut.PkScript)
|
||||
err := checkPkScriptStandard(txOut.PkScript, scriptClass)
|
||||
if err != nil {
|
||||
str := fmt.Sprintf("transaction output %d: %v", i, err)
|
||||
return TxRuleError(str)
|
||||
}
|
||||
|
||||
// Accumulate the number of outputs which only carry data.
|
||||
if scriptClass == btcscript.NullDataTy {
|
||||
numNullDataOutputs++
|
||||
}
|
||||
|
||||
if isDust(txOut) {
|
||||
str := fmt.Sprintf("transaction output %d: payment "+
|
||||
"of %d is dust", i, txOut.Value)
|
||||
@@ -261,20 +306,99 @@ func checkTransactionStandard(tx *btcwire.MsgTx, height int64) error {
|
||||
}
|
||||
}
|
||||
|
||||
// A standard transaction must not have more than one output script that
|
||||
// only carries data.
|
||||
if numNullDataOutputs > 1 {
|
||||
return TxRuleError("more than one transaction output is a " +
|
||||
"nulldata script")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkInputsStandard performs a series of checks on a transaction's inputs
|
||||
// to ensure they are "standard". A standard transaction input is one that
|
||||
// that consumes the same number of outputs from the stack as the output script
|
||||
// pushes. This help prevent resource exhaustion attacks by "creative" use of
|
||||
// scripts that are super expensive to process like OP_DUP OP_CHECKSIG OP_DROP
|
||||
// repeated a large number of times followed by a final OP_TRUE.
|
||||
func checkInputsStandard(tx *btcwire.MsgTx) error {
|
||||
// TODO(davec): Implement
|
||||
// that consumes the expected number of elements from the stack and that number
|
||||
// is the same as the output script pushes. This help prevent resource
|
||||
// exhaustion attacks by "creative" use of scripts that are super expensive to
|
||||
// process like OP_DUP OP_CHECKSIG OP_DROP repeated a large number of times
|
||||
// followed by a final OP_TRUE.
|
||||
func checkInputsStandard(tx *btcutil.Tx, txStore btcchain.TxStore) error {
|
||||
// NOTE: The reference implementation also does a coinbase check here,
|
||||
// but coinbases have already been rejected prior to calling this
|
||||
// function so no need to recheck.
|
||||
|
||||
for i, txIn := range tx.MsgTx().TxIn {
|
||||
// It is safe to elide existence and index checks here since
|
||||
// they have already been checked prior to calling this
|
||||
// function.
|
||||
prevOut := txIn.PreviousOutpoint
|
||||
originTx := txStore[prevOut.Hash].Tx.MsgTx()
|
||||
originPkScript := originTx.TxOut[prevOut.Index].PkScript
|
||||
|
||||
// Calculate stats for the script pair.
|
||||
scriptInfo, err := btcscript.CalcScriptInfo(txIn.SignatureScript,
|
||||
originPkScript, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// A negative value for expected inputs indicates the script is
|
||||
// non-standard in some way.
|
||||
if scriptInfo.ExpectedInputs < 0 {
|
||||
str := fmt.Sprintf("transaction input #%d expects %d "+
|
||||
"inputs", i, scriptInfo.ExpectedInputs)
|
||||
return TxRuleError(str)
|
||||
}
|
||||
|
||||
// The script pair is non-standard if the number of available
|
||||
// inputs does not match the number of expected inputs.
|
||||
if scriptInfo.NumInputs != scriptInfo.ExpectedInputs {
|
||||
str := fmt.Sprintf("transaction input #%d expects %d "+
|
||||
"inputs, but referenced output script only "+
|
||||
"provides %d", i, scriptInfo.ExpectedInputs,
|
||||
scriptInfo.NumInputs)
|
||||
return TxRuleError(str)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// calcMinRelayFee retuns the minimum transaction fee required for the passed
|
||||
// transaction to be accepted into the memory pool and relayed.
|
||||
func calcMinRelayFee(tx *btcutil.Tx) int64 {
|
||||
// Most miners allow a free transaction area in blocks they mine to go
|
||||
// alongside the area used for high-priority transactions as well as
|
||||
// transactions with fees. A transaction size of up to 1000 bytes is
|
||||
// considered safe to go into this section. Further, the minimum fee
|
||||
// calculated below on its own would encourage several small
|
||||
// transactions to avoid fees rather than one single larger transaction
|
||||
// which is more desirable. Therefore, as long as the size of the
|
||||
// transaction does not exceeed 1000 less than the reserved space for
|
||||
// high-priority transactions, don't require a fee for it.
|
||||
serializedLen := int64(tx.MsgTx().SerializeSize())
|
||||
if serializedLen < (blockPrioritySize - 1000) {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Calculate the minimum fee for a transaction to be allowed into the
|
||||
// mempool and relayed by scaling the base fee (which is the minimum
|
||||
// free transaction relay fee). minTxRelayFee is in Satoshi/KB
|
||||
// (kilobyte, not kibibyte), so divide the transaction size by 1000 to
|
||||
// convert to kilobytes. Also, integer division is used so fees only
|
||||
// increase on full kilobyte boundaries.
|
||||
minFee := (1 + serializedLen/1000) * minTxRelayFee
|
||||
|
||||
// Set the minimum fee to the maximum possible value if the calculated
|
||||
// fee is not in the valid range for monetary amounts.
|
||||
if minFee < 0 || minFee > btcutil.MaxSatoshi {
|
||||
minFee = btcutil.MaxSatoshi
|
||||
}
|
||||
|
||||
return minFee
|
||||
}
|
||||
|
||||
// removeOrphan removes the passed orphan transaction from the orphan pool and
|
||||
// previous orphan index.
|
||||
//
|
||||
@@ -287,11 +411,11 @@ func (mp *txMemPool) removeOrphan(txHash *btcwire.ShaHash) {
|
||||
}
|
||||
|
||||
// Remove the reference from the previous orphan index.
|
||||
for _, txIn := range tx.TxIn {
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
originTxHash := txIn.PreviousOutpoint.Hash
|
||||
if orphans, exists := mp.orphansByPrev[originTxHash]; exists {
|
||||
for e := orphans.Front(); e != nil; e = e.Next() {
|
||||
if e.Value.(*btcwire.MsgTx) == tx {
|
||||
if e.Value.(*btcutil.Tx) == tx {
|
||||
orphans.Remove(e)
|
||||
break
|
||||
}
|
||||
@@ -349,13 +473,13 @@ func (mp *txMemPool) limitNumOrphans() error {
|
||||
// addOrphan adds an orphan transaction to the orphan pool.
|
||||
//
|
||||
// This function MUST be called with the mempool lock held (for writes).
|
||||
func (mp *txMemPool) addOrphan(tx *btcwire.MsgTx, txHash *btcwire.ShaHash) {
|
||||
func (mp *txMemPool) addOrphan(tx *btcutil.Tx) {
|
||||
// Limit the number orphan transactions to prevent memory exhaustion. A
|
||||
// random orphan is evicted to make room if needed.
|
||||
mp.limitNumOrphans()
|
||||
|
||||
mp.orphans[*txHash] = tx
|
||||
for _, txIn := range tx.TxIn {
|
||||
mp.orphans[*tx.Sha()] = tx
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
originTxHash := txIn.PreviousOutpoint.Hash
|
||||
if mp.orphansByPrev[originTxHash] == nil {
|
||||
mp.orphansByPrev[originTxHash] = list.New()
|
||||
@@ -363,14 +487,14 @@ func (mp *txMemPool) addOrphan(tx *btcwire.MsgTx, txHash *btcwire.ShaHash) {
|
||||
mp.orphansByPrev[originTxHash].PushBack(tx)
|
||||
}
|
||||
|
||||
log.Debugf("TXMP: Stored orphan transaction %v (total: %d)", txHash,
|
||||
txmpLog.Debugf("Stored orphan transaction %v (total: %d)", tx.Sha(),
|
||||
len(mp.orphans))
|
||||
}
|
||||
|
||||
// maybeAddOrphan potentially adds an orphan to the orphan pool.
|
||||
//
|
||||
// This function MUST be called with the mempool lock held (for writes).
|
||||
func (mp *txMemPool) maybeAddOrphan(tx *btcwire.MsgTx, txHash *btcwire.ShaHash) error {
|
||||
func (mp *txMemPool) maybeAddOrphan(tx *btcutil.Tx) error {
|
||||
// Ignore orphan transactions that are too large. This helps avoid
|
||||
// a memory exhaustion attack based on sending a lot of really large
|
||||
// orphans. In the case there is a valid transaction larger than this,
|
||||
@@ -381,12 +505,7 @@ func (mp *txMemPool) maybeAddOrphan(tx *btcwire.MsgTx, txHash *btcwire.ShaHash)
|
||||
// also limited, so this equates to a maximum memory used of
|
||||
// maxOrphanTxSize * maxOrphanTransactions (which is 500MB as of the
|
||||
// time this comment was written).
|
||||
var serializedTxBuf bytes.Buffer
|
||||
err := tx.Serialize(&serializedTxBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serializedLen := serializedTxBuf.Len()
|
||||
serializedLen := tx.MsgTx().SerializeSize()
|
||||
if serializedLen > maxOrphanTxSize {
|
||||
str := fmt.Sprintf("orphan transaction size of %d bytes is "+
|
||||
"larger than max allowed size of %d bytes",
|
||||
@@ -395,7 +514,7 @@ func (mp *txMemPool) maybeAddOrphan(tx *btcwire.MsgTx, txHash *btcwire.ShaHash)
|
||||
}
|
||||
|
||||
// Add the orphan if the none of the above disqualified it.
|
||||
mp.addOrphan(tx, txHash)
|
||||
mp.addOrphan(tx)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -468,14 +587,15 @@ func (mp *txMemPool) HaveTransaction(hash *btcwire.ShaHash) bool {
|
||||
return mp.haveTransaction(hash)
|
||||
}
|
||||
|
||||
// removeTransaction removes the passed transaction from the memory pool.
|
||||
// removeTransaction is the internal function which implements the public
|
||||
// RemoveTransaction. See the comment for RemoveTransaction for more details.
|
||||
//
|
||||
// This function MUST be called with the mempool lock held (for writes).
|
||||
func (mp *txMemPool) removeTransaction(tx *btcwire.MsgTx) {
|
||||
func (mp *txMemPool) removeTransaction(tx *btcutil.Tx) {
|
||||
// Remove any transactions which rely on this one.
|
||||
txHash, _ := tx.TxSha()
|
||||
for i := uint32(0); i < uint32(len(tx.TxOut)); i++ {
|
||||
outpoint := btcwire.NewOutPoint(&txHash, i)
|
||||
txHash := tx.Sha()
|
||||
for i := uint32(0); i < uint32(len(tx.MsgTx().TxOut)); i++ {
|
||||
outpoint := btcwire.NewOutPoint(txHash, i)
|
||||
if txRedeemer, exists := mp.outpoints[*outpoint]; exists {
|
||||
mp.removeTransaction(txRedeemer)
|
||||
}
|
||||
@@ -483,11 +603,44 @@ func (mp *txMemPool) removeTransaction(tx *btcwire.MsgTx) {
|
||||
|
||||
// Remove the transaction and mark the referenced outpoints as unspent
|
||||
// by the pool.
|
||||
if tx, exists := mp.pool[txHash]; exists {
|
||||
for _, txIn := range tx.TxIn {
|
||||
if txDesc, exists := mp.pool[*txHash]; exists {
|
||||
for _, txIn := range txDesc.Tx.MsgTx().TxIn {
|
||||
delete(mp.outpoints, txIn.PreviousOutpoint)
|
||||
}
|
||||
delete(mp.pool, txHash)
|
||||
delete(mp.pool, *txHash)
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveTransaction removes the passed transaction and any transactions which
|
||||
// depend on it from the memory pool.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (mp *txMemPool) RemoveTransaction(tx *btcutil.Tx) {
|
||||
// Protect concurrent access.
|
||||
mp.Lock()
|
||||
defer mp.Unlock()
|
||||
|
||||
mp.removeTransaction(tx)
|
||||
}
|
||||
|
||||
// RemoveDoubleSpends removes all transactions which spend outputs spent by the
|
||||
// passed transaction from the memory pool. Removing those transactions then
|
||||
// leads to removing all transactions which rely on them, recursively. This is
|
||||
// necessary when a block is connected to the main chain because the block may
|
||||
// contain transactions which were previously unknown to the memory pool
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (mp *txMemPool) RemoveDoubleSpends(tx *btcutil.Tx) {
|
||||
// Protect concurrent access.
|
||||
mp.Lock()
|
||||
defer mp.Unlock()
|
||||
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
if txRedeemer, ok := mp.outpoints[txIn.PreviousOutpoint]; ok {
|
||||
if !txRedeemer.Sha().IsEqual(tx.Sha()) {
|
||||
mp.removeTransaction(txRedeemer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,11 +649,16 @@ func (mp *txMemPool) removeTransaction(tx *btcwire.MsgTx) {
|
||||
// helper for maybeAcceptTransaction.
|
||||
//
|
||||
// This function MUST be called with the mempool lock held (for writes).
|
||||
func (mp *txMemPool) addTransaction(tx *btcwire.MsgTx, txHash *btcwire.ShaHash) {
|
||||
func (mp *txMemPool) addTransaction(tx *btcutil.Tx, height, fee int64) {
|
||||
// Add the transaction to the pool and mark the referenced outpoints
|
||||
// as spent by the pool.
|
||||
mp.pool[*txHash] = tx
|
||||
for _, txIn := range tx.TxIn {
|
||||
mp.pool[*tx.Sha()] = &TxDesc{
|
||||
Tx: tx,
|
||||
Added: time.Now(),
|
||||
Height: height,
|
||||
Fee: fee,
|
||||
}
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
mp.outpoints[txIn.PreviousOutpoint] = tx
|
||||
}
|
||||
}
|
||||
@@ -511,12 +669,11 @@ func (mp *txMemPool) addTransaction(tx *btcwire.MsgTx, txHash *btcwire.ShaHash)
|
||||
// main chain.
|
||||
//
|
||||
// This function MUST be called with the mempool lock held (for reads).
|
||||
func (mp *txMemPool) checkPoolDoubleSpend(tx *btcwire.MsgTx) error {
|
||||
for _, txIn := range tx.TxIn {
|
||||
func (mp *txMemPool) checkPoolDoubleSpend(tx *btcutil.Tx) error {
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
if txR, exists := mp.outpoints[txIn.PreviousOutpoint]; exists {
|
||||
hash, _ := txR.TxSha()
|
||||
str := fmt.Sprintf("transaction %v in the pool "+
|
||||
"already spends the same coins", hash)
|
||||
"already spends the same coins", txR.Sha())
|
||||
return TxRuleError(str)
|
||||
}
|
||||
}
|
||||
@@ -529,7 +686,7 @@ func (mp *txMemPool) checkPoolDoubleSpend(tx *btcwire.MsgTx) error {
|
||||
// fetch any missing inputs from the transaction pool.
|
||||
//
|
||||
// This function MUST be called with the mempool lock held (for reads).
|
||||
func (mp *txMemPool) fetchInputTransactions(tx *btcwire.MsgTx) (btcchain.TxStore, error) {
|
||||
func (mp *txMemPool) fetchInputTransactions(tx *btcutil.Tx) (btcchain.TxStore, error) {
|
||||
txStore, err := mp.server.blockManager.blockChain.FetchTransactionStore(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -538,10 +695,11 @@ func (mp *txMemPool) fetchInputTransactions(tx *btcwire.MsgTx) (btcchain.TxStore
|
||||
// Attempt to populate any missing inputs from the transaction pool.
|
||||
for _, txD := range txStore {
|
||||
if txD.Err == btcdb.TxShaMissing || txD.Tx == nil {
|
||||
if poolTx, exists := mp.pool[*txD.Hash]; exists {
|
||||
if poolTxDesc, exists := mp.pool[*txD.Hash]; exists {
|
||||
poolTx := poolTxDesc.Tx
|
||||
txD.Tx = poolTx
|
||||
txD.BlockHeight = mempoolHeight
|
||||
txD.Spent = make([]bool, len(poolTx.TxOut))
|
||||
txD.Spent = make([]bool, len(poolTx.MsgTx().TxOut))
|
||||
txD.Err = nil
|
||||
}
|
||||
}
|
||||
@@ -555,35 +713,33 @@ func (mp *txMemPool) fetchInputTransactions(tx *btcwire.MsgTx) (btcchain.TxStore
|
||||
// orphans.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (mp *txMemPool) FetchTransaction(txHash *btcwire.ShaHash) (*btcwire.MsgTx, error) {
|
||||
func (mp *txMemPool) FetchTransaction(txHash *btcwire.ShaHash) (*btcutil.Tx, error) {
|
||||
// Protect concurrent access.
|
||||
mp.RLock()
|
||||
defer mp.RUnlock()
|
||||
|
||||
if tx, exists := mp.pool[*txHash]; exists {
|
||||
return tx, nil
|
||||
if txDesc, exists := mp.pool[*txHash]; exists {
|
||||
return txDesc.Tx, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("transaction is not in the pool")
|
||||
}
|
||||
|
||||
// maybeAcceptTransaction is the main workhorse for handling insertion of new
|
||||
// free-standing transactions into a memory pool. It includes functionality
|
||||
// such as rejecting duplicate transactions, ensuring transactions follow all
|
||||
// rules, orphan transaction handling, and insertion into the memory pool.
|
||||
// maybeAcceptTransaction is the internal function which implements the public
|
||||
// MaybeAcceptTransaction. See the comment for MaybeAcceptTransaction for
|
||||
// more details.
|
||||
//
|
||||
// This function MUST be called with the mempool lock held (for writes).
|
||||
func (mp *txMemPool) maybeAcceptTransaction(tx *btcwire.MsgTx, isOrphan *bool) error {
|
||||
*isOrphan = false
|
||||
txHash, err := tx.TxSha()
|
||||
if err != nil {
|
||||
return err
|
||||
func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNew bool) error {
|
||||
if isOrphan != nil {
|
||||
*isOrphan = false
|
||||
}
|
||||
txHash := tx.Sha()
|
||||
|
||||
// Don't accept the transaction if it already exists in the pool. This
|
||||
// applies to orphan transactions as well. This check is intended to
|
||||
// be a quick check to weed out duplicates.
|
||||
if mp.haveTransaction(&txHash) {
|
||||
if mp.haveTransaction(txHash) {
|
||||
str := fmt.Sprintf("already have transaction %v", txHash)
|
||||
return TxRuleError(str)
|
||||
}
|
||||
@@ -591,7 +747,7 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcwire.MsgTx, isOrphan *bool) e
|
||||
// Perform preliminary sanity checks on the transaction. This makes
|
||||
// use of btcchain which contains the invariant rules for what
|
||||
// transactions are allowed into blocks.
|
||||
err = btcchain.CheckTransactionSanity(tx)
|
||||
err := btcchain.CheckTransactionSanity(tx)
|
||||
if err != nil {
|
||||
if _, ok := err.(btcchain.RuleError); ok {
|
||||
return TxRuleError(err.Error())
|
||||
@@ -610,14 +766,15 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcwire.MsgTx, isOrphan *bool) e
|
||||
// value for now. This is an artifact of older bitcoind clients which
|
||||
// treated this field as an int32 and would treat anything larger
|
||||
// incorrectly (as negative).
|
||||
if tx.LockTime > math.MaxInt32 {
|
||||
if tx.MsgTx().LockTime > math.MaxInt32 {
|
||||
str := fmt.Sprintf("transaction %v is has a lock time after "+
|
||||
"2038 which is not accepted yet", txHash)
|
||||
return TxRuleError(str)
|
||||
}
|
||||
|
||||
// Get the current height of the main chain. A standalone transaction
|
||||
// will be mined into the next block at best, so
|
||||
// will be mined into the next block at best, so it's height is at least
|
||||
// one more than the current height.
|
||||
_, curHeight, err := mp.server.db.NewestSha()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -658,7 +815,7 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcwire.MsgTx, isOrphan *bool) e
|
||||
|
||||
// Don't allow the transaction if it exists in the main chain and is not
|
||||
// not already fully spent.
|
||||
if txD, exists := txStore[txHash]; exists && txD.Err == nil {
|
||||
if txD, exists := txStore[*txHash]; exists && txD.Err == nil {
|
||||
for _, isOutputSpent := range txD.Spent {
|
||||
if !isOutputSpent {
|
||||
str := fmt.Sprintf("transaction already exists")
|
||||
@@ -666,12 +823,14 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcwire.MsgTx, isOrphan *bool) e
|
||||
}
|
||||
}
|
||||
}
|
||||
delete(txStore, txHash)
|
||||
delete(txStore, *txHash)
|
||||
|
||||
// Transaction is an orphan if any of the inputs don't exist.
|
||||
for _, txD := range txStore {
|
||||
if txD.Err == btcdb.TxShaMissing {
|
||||
*isOrphan = true
|
||||
if isOrphan != nil {
|
||||
*isOrphan = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -682,13 +841,16 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcwire.MsgTx, isOrphan *bool) e
|
||||
// used later.
|
||||
txFee, err := btcchain.CheckTransactionInputs(tx, nextBlockHeight, txStore)
|
||||
if err != nil {
|
||||
if _, ok := err.(btcchain.RuleError); ok {
|
||||
return TxRuleError(err.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Don't allow transactions with non-standard inputs on the main
|
||||
// network.
|
||||
if activeNetParams.btcnet == btcwire.MainNet {
|
||||
err := checkInputsStandard(tx)
|
||||
err := checkInputsStandard(tx, txStore)
|
||||
if err != nil {
|
||||
str := fmt.Sprintf("transaction %v has a non-standard "+
|
||||
"input: %v", txHash, err)
|
||||
@@ -696,38 +858,87 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcwire.MsgTx, isOrphan *bool) e
|
||||
}
|
||||
}
|
||||
|
||||
// Note: if you modify this code to accept non-standard transactions,
|
||||
// NOTE: if you modify this code to accept non-standard transactions,
|
||||
// you should add code here to check that the transaction does a
|
||||
// reasonable number of ECDSA signature verifications.
|
||||
|
||||
// TODO(davec): Don't allow the transaction if the transation fee
|
||||
// would be too low to get into an empty block.
|
||||
_ = txFee
|
||||
// Don't allow transactions with fees too low to get into a mined block.
|
||||
minRequiredFee := calcMinRelayFee(tx)
|
||||
if txFee < minRequiredFee {
|
||||
str := fmt.Sprintf("transaction %v has %d fees which is under "+
|
||||
"the required amount of %d", txHash, txFee,
|
||||
minRequiredFee)
|
||||
return TxRuleError(str)
|
||||
}
|
||||
|
||||
// Free-to-relay transactions are rate limited here to prevent
|
||||
// penny-flooding with tiny transactions as a form of attack.
|
||||
if minRequiredFee == 0 {
|
||||
nowUnix := time.Now().Unix()
|
||||
// we decay passed data with an exponentially decaying ~10
|
||||
// minutes window - matches bitcoind handling.
|
||||
mp.pennyTotal *= math.Pow(1.0-1.0/600.0,
|
||||
float64(nowUnix-mp.lastPennyUnix))
|
||||
mp.lastPennyUnix = nowUnix
|
||||
|
||||
// Are we still over the limit?
|
||||
if mp.pennyTotal >= cfg.FreeTxRelayLimit*10*1000 {
|
||||
str := fmt.Sprintf("transaction %v has 0 fees and has "+
|
||||
"been rejected by the rate limiter", txHash)
|
||||
return TxRuleError(str)
|
||||
}
|
||||
oldTotal := mp.pennyTotal
|
||||
|
||||
mp.pennyTotal += float64(tx.MsgTx().SerializeSize())
|
||||
txmpLog.Debugf("rate limit: curTotal %v, nextTotal: %v, "+
|
||||
"limit %v", oldTotal, mp.pennyTotal,
|
||||
cfg.FreeTxRelayLimit*10*1000)
|
||||
}
|
||||
|
||||
// Verify crypto signatures for each input and reject the transaction if
|
||||
// any don't verify.
|
||||
err = btcchain.ValidateTransactionScripts(tx, &txHash, time.Now(), txStore)
|
||||
flags := btcscript.ScriptBip16 | btcscript.ScriptCanonicalSignatures
|
||||
err = btcchain.ValidateTransactionScripts(tx, txStore, flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(davec): Rate-limit free transactions
|
||||
|
||||
// Add to transaction pool.
|
||||
mp.addTransaction(tx, &txHash)
|
||||
mp.addTransaction(tx, curHeight, txFee)
|
||||
|
||||
log.Debugf("TXMP: Accepted transaction %v (pool size: %v)", txHash,
|
||||
txmpLog.Debugf("Accepted transaction %v (pool size: %v)", txHash,
|
||||
len(mp.pool))
|
||||
|
||||
// TODO(davec): Notifications
|
||||
// Notify websocket clients about mempool transactions.
|
||||
if mp.server.rpcServer != nil {
|
||||
go func() {
|
||||
mp.server.rpcServer.ntfnMgr.NotifyForTxOuts(tx, nil)
|
||||
|
||||
// Generate the inventory vector and relay it.
|
||||
iv := btcwire.NewInvVect(btcwire.InvTypeTx, &txHash)
|
||||
mp.server.RelayInventory(iv)
|
||||
if isNew {
|
||||
mp.server.rpcServer.ntfnMgr.NotifyForNewTx(tx)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaybeAcceptTransaction is the main workhorse for handling insertion of new
|
||||
// free-standing transactions into a memory pool. It includes functionality
|
||||
// such as rejecting duplicate transactions, ensuring transactions follow all
|
||||
// rules, orphan transaction handling, and insertion into the memory pool. The
|
||||
// isOrphan parameter can be nil if the caller does not need to know whether
|
||||
// or not the transaction is an orphan.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (mp *txMemPool) MaybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNew bool) error {
|
||||
// Protect concurrent access.
|
||||
mp.Lock()
|
||||
defer mp.Unlock()
|
||||
|
||||
return mp.maybeAcceptTransaction(tx, isOrphan, isNew)
|
||||
}
|
||||
|
||||
// processOrphans determines if there are any orphans which depend on the passed
|
||||
// transaction hash (they are no longer orphans if true) and potentially accepts
|
||||
// them. It repeats the process for the newly accepted transactions (to detect
|
||||
@@ -756,31 +967,32 @@ func (mp *txMemPool) processOrphans(hash *btcwire.ShaHash) error {
|
||||
var enext *list.Element
|
||||
for e := orphans.Front(); e != nil; e = enext {
|
||||
enext = e.Next()
|
||||
tx := e.Value.(*btcwire.MsgTx)
|
||||
tx := e.Value.(*btcutil.Tx)
|
||||
|
||||
// Remove the orphan from the orphan pool.
|
||||
orphanHash, err := tx.TxSha()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mp.removeOrphan(&orphanHash)
|
||||
orphanHash := tx.Sha()
|
||||
mp.removeOrphan(orphanHash)
|
||||
|
||||
// Potentially accept the transaction into the
|
||||
// transaction pool.
|
||||
var isOrphan bool
|
||||
err = mp.maybeAcceptTransaction(tx, &isOrphan)
|
||||
err := mp.maybeAcceptTransaction(tx, &isOrphan, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isOrphan {
|
||||
mp.removeOrphan(&orphanHash)
|
||||
if !isOrphan {
|
||||
// Generate the inventory vector and relay it.
|
||||
iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha())
|
||||
mp.server.RelayInventory(iv)
|
||||
} else {
|
||||
mp.removeOrphan(orphanHash)
|
||||
}
|
||||
|
||||
// Add this transaction to the list of transactions to
|
||||
// process so any orphans that depend on this one are
|
||||
// handled too.
|
||||
processHashes.PushBack(&orphanHash)
|
||||
processHashes.PushBack(orphanHash)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -788,41 +1000,41 @@ func (mp *txMemPool) processOrphans(hash *btcwire.ShaHash) error {
|
||||
}
|
||||
|
||||
// ProcessTransaction is the main workhorse for handling insertion of new
|
||||
// free-standing transactions into a memory pool. It includes functionality
|
||||
// free-standing transactions into the memory pool. It includes functionality
|
||||
// such as rejecting duplicate transactions, ensuring transactions follow all
|
||||
// rules, orphan transaction handling, and insertion into the memory pool.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (mp *txMemPool) ProcessTransaction(tx *btcwire.MsgTx) error {
|
||||
func (mp *txMemPool) ProcessTransaction(tx *btcutil.Tx) error {
|
||||
// Protect concurrent access.
|
||||
mp.Lock()
|
||||
defer mp.Unlock()
|
||||
|
||||
txHash, err := tx.TxSha()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Tracef("TXMP: Processing transaction %v", txHash)
|
||||
txmpLog.Tracef("Processing transaction %v", tx.Sha())
|
||||
|
||||
// Potentially accept the transaction to the memory pool.
|
||||
var isOrphan bool
|
||||
err = mp.maybeAcceptTransaction(tx, &isOrphan)
|
||||
err := mp.maybeAcceptTransaction(tx, &isOrphan, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isOrphan {
|
||||
// Generate the inventory vector and relay it.
|
||||
iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha())
|
||||
mp.server.RelayInventory(iv)
|
||||
|
||||
// Accept any orphan transactions that depend on this
|
||||
// transaction (they are no longer orphans) and repeat for those
|
||||
// accepted transactions until there are no more.
|
||||
err = mp.processOrphans(&txHash)
|
||||
err := mp.processOrphans(tx.Sha())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// When the transaction is an orphan (has inputs missing),
|
||||
// potentially add it to the orphan pool.
|
||||
err := mp.maybeAddOrphan(tx, &txHash)
|
||||
err := mp.maybeAddOrphan(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -850,14 +1062,32 @@ func (mp *txMemPool) TxShas() []*btcwire.ShaHash {
|
||||
return hashes
|
||||
}
|
||||
|
||||
// TxDescs returns a slice of descriptors for all the transactions in the pool.
|
||||
// The descriptors are to be treated as read only.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (mp *txMemPool) TxDescs() []*TxDesc {
|
||||
mp.RLock()
|
||||
defer mp.RUnlock()
|
||||
|
||||
descs := make([]*TxDesc, len(mp.pool))
|
||||
i := 0
|
||||
for _, desc := range mp.pool {
|
||||
descs[i] = desc
|
||||
i++
|
||||
}
|
||||
|
||||
return descs
|
||||
}
|
||||
|
||||
// newTxMemPool returns a new memory pool for validating and storing standalone
|
||||
// transactions until they are mined into a block.
|
||||
func newTxMemPool(server *server) *txMemPool {
|
||||
return &txMemPool{
|
||||
server: server,
|
||||
pool: make(map[btcwire.ShaHash]*btcwire.MsgTx),
|
||||
orphans: make(map[btcwire.ShaHash]*btcwire.MsgTx),
|
||||
pool: make(map[btcwire.ShaHash]*TxDesc),
|
||||
orphans: make(map[btcwire.ShaHash]*btcutil.Tx),
|
||||
orphansByPrev: make(map[btcwire.ShaHash]*list.List),
|
||||
outpoints: make(map[btcwire.OutPoint]*btcwire.MsgTx),
|
||||
outpoints: make(map[btcwire.OutPoint]*btcutil.Tx),
|
||||
}
|
||||
}
|
||||
|
||||
60
mruinvmap.go
60
mruinvmap.go
@@ -1,20 +1,21 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"github.com/conformal/btcwire"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MruInventoryMap provides a map that is limited to a maximum number of items
|
||||
// with eviction for the oldest entry when the limit is exceeded.
|
||||
type MruInventoryMap struct {
|
||||
invMap map[btcwire.InvVect]int64 // Use int64 for time for less mem.
|
||||
limit uint
|
||||
invMap map[btcwire.InvVect]*list.Element // nearly O(1) lookups
|
||||
invList *list.List // O(1) insert, update, delete
|
||||
limit uint
|
||||
}
|
||||
|
||||
// String returns the map as a human-readable string.
|
||||
@@ -34,49 +35,62 @@ func (m *MruInventoryMap) Exists(iv *btcwire.InvVect) bool {
|
||||
// item if adding the new item would exceed the max limit.
|
||||
func (m *MruInventoryMap) Add(iv *btcwire.InvVect) {
|
||||
// When the limit is zero, nothing can be added to the map, so just
|
||||
// return
|
||||
// return.
|
||||
if m.limit == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// When the entry already exists update its last seen time.
|
||||
if m.Exists(iv) {
|
||||
m.invMap[*iv] = time.Now().Unix()
|
||||
// When the entry already exists move it to the front of the list
|
||||
// thereby marking it most recently used.
|
||||
if node, exists := m.invMap[*iv]; exists {
|
||||
m.invList.MoveToFront(node)
|
||||
return
|
||||
}
|
||||
|
||||
// Evict the oldest entry if the the new entry would exceed the size
|
||||
// limit for the map.
|
||||
// Evict the least recently used entry (back of the list) if the the new
|
||||
// entry would exceed the size limit for the map. Also reuse the list
|
||||
// node so a new one doesn't have to be allocated.
|
||||
if uint(len(m.invMap))+1 > m.limit {
|
||||
var oldestEntry btcwire.InvVect
|
||||
var oldestTime int64
|
||||
for iv, lastUpdated := range m.invMap {
|
||||
if oldestTime == 0 || lastUpdated < oldestTime {
|
||||
oldestEntry = iv
|
||||
oldestTime = lastUpdated
|
||||
}
|
||||
node := m.invList.Back()
|
||||
lru, ok := node.Value.(*btcwire.InvVect)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
m.Delete(&oldestEntry)
|
||||
// Evict least recently used item.
|
||||
delete(m.invMap, *lru)
|
||||
|
||||
// Reuse the list node of the item that was just evicted for the
|
||||
// new item.
|
||||
node.Value = iv
|
||||
m.invList.MoveToFront(node)
|
||||
m.invMap[*iv] = node
|
||||
return
|
||||
}
|
||||
|
||||
m.invMap[*iv] = time.Now().Unix()
|
||||
// The limit hasn't been reached yet, so just add the new item.
|
||||
node := m.invList.PushFront(iv)
|
||||
m.invMap[*iv] = node
|
||||
return
|
||||
}
|
||||
|
||||
// Delete deletes the passed inventory item from the map (if it exists).
|
||||
func (m *MruInventoryMap) Delete(iv *btcwire.InvVect) {
|
||||
delete(m.invMap, *iv)
|
||||
if node, exists := m.invMap[*iv]; exists {
|
||||
m.invList.Remove(node)
|
||||
delete(m.invMap, *iv)
|
||||
}
|
||||
}
|
||||
|
||||
// NewMruInventoryMap returns a new inventory map that is limited to the number
|
||||
// of entries specified by limit. When the number of entries exceeds the limit,
|
||||
// the oldest (least recently used) entry will be removed to make room for the
|
||||
// new entry..
|
||||
// new entry.
|
||||
func NewMruInventoryMap(limit uint) *MruInventoryMap {
|
||||
m := MruInventoryMap{
|
||||
invMap: make(map[btcwire.InvVect]int64),
|
||||
limit: limit,
|
||||
invMap: make(map[btcwire.InvVect]*list.Element),
|
||||
invList: list.New(),
|
||||
limit: limit,
|
||||
}
|
||||
return &m
|
||||
}
|
||||
|
||||
36
mruinvmap_test.go
Normal file
36
mruinvmap_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"github.com/conformal/btcwire"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// BenchmarkMruInventoryList performs basic benchmarks on the most recently
|
||||
// used inventory handling.
|
||||
func BenchmarkMruInventoryList(b *testing.B) {
|
||||
// Create a bunch of fake inventory vectors to use in benchmarking
|
||||
// the mru inventory code.
|
||||
b.StopTimer()
|
||||
numInvVects := 100000
|
||||
invVects := make([]*btcwire.InvVect, 0, numInvVects)
|
||||
for i := 0; i < numInvVects; i++ {
|
||||
hashBytes := make([]byte, btcwire.HashSize)
|
||||
rand.Read(hashBytes)
|
||||
hash, _ := btcwire.NewShaHash(hashBytes)
|
||||
iv := btcwire.NewInvVect(btcwire.InvTypeBlock, hash)
|
||||
invVects = append(invVects, iv)
|
||||
}
|
||||
b.StartTimer()
|
||||
|
||||
// Benchmark the add plus evicition code.
|
||||
limit := 20000
|
||||
mruInvMap := NewMruInventoryMap(uint(limit))
|
||||
for i := 0; i < b.N; i++ {
|
||||
mruInvMap.Add(invVects[i%numInvVects])
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -49,6 +49,7 @@ var mainNetParams = params{
|
||||
"seed.bitcoin.sipa.be",
|
||||
"dnsseed.bluematt.me",
|
||||
"dnsseed.bitcoin.dashjr.org",
|
||||
"seed.bitcoinstats.com",
|
||||
"bitseed.xf2.org",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ PROJECT=btcd
|
||||
PROJECT_UC=$(echo $PROJECT | tr '[:lower:]' '[:upper:]')
|
||||
SCRIPT=$(basename $0)
|
||||
VERFILE=../version.go
|
||||
VERFILES="$VERFILE ../util/btcctl/version.go"
|
||||
PROJ_CHANGES=../CHANGES
|
||||
|
||||
# verify params
|
||||
@@ -37,11 +38,13 @@ fi
|
||||
CUR_DIR=$(pwd)
|
||||
cd "$(dirname $0)"
|
||||
|
||||
# verify version file exists
|
||||
if [ ! -f "$VERFILE" ]; then
|
||||
echo "$SCRIPT: error: $VERFILE does not exist" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
# verify version files exist
|
||||
for verfile in $VERFILES; do
|
||||
if [ ! -f "$verfile" ]; then
|
||||
echo "$SCRIPT: error: $verfile does not exist" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# verify changes file exists
|
||||
if [ ! -f "$PROJ_CHANGES" ]; then
|
||||
@@ -172,17 +175,21 @@ awk '
|
||||
second_line==1 { print $0 }
|
||||
' <"$PROJ_CHANGES" >>"${PROJ_CHANGES}.tmp"
|
||||
|
||||
# update version file with new version
|
||||
sed -E "
|
||||
s/${PAT_PREFIX}Major${PAT_SUFFIX}/\1${MAJOR}/;
|
||||
s/${PAT_PREFIX}Minor${PAT_SUFFIX}/\1${MINOR}/;
|
||||
s/${PAT_PREFIX}Patch${PAT_SUFFIX}/\1${PATCH}/;
|
||||
" <"$VERFILE" >"${VERFILE}.tmp"
|
||||
# update version filef with new version
|
||||
for verfile in $VERFILES; do
|
||||
sed -E "
|
||||
s/${PAT_PREFIX}Major${PAT_SUFFIX}/\1${MAJOR}/;
|
||||
s/${PAT_PREFIX}Minor${PAT_SUFFIX}/\1${MINOR}/;
|
||||
s/${PAT_PREFIX}Patch${PAT_SUFFIX}/\1${PATCH}/;
|
||||
" <"$verfile" >"${verfile}.tmp"
|
||||
done
|
||||
|
||||
|
||||
# Apply changes
|
||||
mv "${PROJ_CHANGES}.tmp" "$PROJ_CHANGES"
|
||||
mv "${VERFILE}.tmp" "$VERFILE"
|
||||
for verfile in $VERFILES; do
|
||||
mv "${verfile}.tmp" "$verfile"
|
||||
done
|
||||
|
||||
echo "All files have been prepared for release."
|
||||
echo "Use the following commands to review the changes for accuracy:"
|
||||
|
||||
2535
rpcserver.go
2535
rpcserver.go
File diff suppressed because it is too large
Load Diff
1373
rpcwebsocket.go
Normal file
1373
rpcwebsocket.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,10 +6,11 @@
|
||||
|
||||
; The directory to store data such as the block chain and peer addresses. The
|
||||
; block chain takes several GB, so this location must have a lot of free space.
|
||||
; The default is ~/.btcd/data on POSIX OSes and $APPDATA/btcd/data on Windows.
|
||||
; Environment variables are expanded so they may be used. NOTE: Windows
|
||||
; The default is ~/.btcd/data on POSIX OSes, $LOCALAPPDATA/Btcd/data on Windows,
|
||||
; ~/Library/Application Support/Btcd/data on Mac OS, and $home/btcd/data on
|
||||
; Plan9. Environment variables are expanded so they may be used. NOTE: Windows
|
||||
; environment variables are typically %VARIABLE%, but they must be accessed with
|
||||
; $VARIABLE here. Also, ~ is expanded to $APPDATA on Windows.
|
||||
; $VARIABLE here. Also, ~ is expanded to $LOCALAPPDATA on Windows.
|
||||
; datadir=~/.btcd/data
|
||||
|
||||
|
||||
@@ -20,22 +21,27 @@
|
||||
; Use testnet.
|
||||
; testnet=1
|
||||
|
||||
; Connect via a SOCKS5 proxy. NOTE: Specifying a proxy without the 'tor' option
|
||||
; below will disable listening for incoming connections.
|
||||
; Connect via a SOCKS5 proxy. NOTE: Specifying a proxy will disable listening
|
||||
; for incoming connections unless listen addresses are provided via the 'listen'
|
||||
; option.
|
||||
; proxy=127.0.0.1:9050
|
||||
; proxyuser=
|
||||
; proxypass=
|
||||
|
||||
; The SOCKS5 proxy above is Tor (https://www.torproject.org).
|
||||
; Although not required if the proxy set is indeed Tor, setting this option
|
||||
; does the following:
|
||||
; - Sends DNS queries over the Tor network (during DNS seed lookup). This
|
||||
; stops your IP from being leaked via DNS.
|
||||
; - Does not disable the listening port. This allows the hidden services
|
||||
; feature of Tor to be used.
|
||||
; tor=1
|
||||
; The SOCKS5 proxy above is assumed to be Tor (https://www.torproject.org).
|
||||
; If the proxy is not tor the the following my be used to prevent using
|
||||
; tor specific SOCKS queries to lookup addresses (this increases anonymity when
|
||||
; tor is used by preventing your IP being leaked via DNS).
|
||||
; noonion=1
|
||||
|
||||
; Use an alternative proxy to connect to .onion addresses. The proxy is assumed
|
||||
; to be a Tor node. Non .onion addresses will be contacted with the main proxy
|
||||
; or without a proxy if none is set.
|
||||
; onion=127.0.0.1:9051
|
||||
|
||||
; ******************************************************************************
|
||||
; Summary of 'addpeer' versus 'connect'.
|
||||
;
|
||||
; Only one of the following two options, 'addpeer' and 'connect', may be
|
||||
; specified. Both allow you to specify peers that you want to stay connected
|
||||
; with, but the behavior is slightly different. By default, btcd will query DNS
|
||||
@@ -49,7 +55,8 @@
|
||||
; reason (perhaps due to a firewall).
|
||||
;
|
||||
; 'connect', on the other hand, will ONLY connect to the specified peers and
|
||||
; no others. It also disables listening and DNS seeding, so you will not be
|
||||
; no others. It also disables listening (unless you explicitly set listen
|
||||
; addresses via the 'listen' option) and DNS seeding, so you will not be
|
||||
; advertised as an available peer to the peers you connect to and won't accept
|
||||
; connections from any other peers. So, the 'connect' option effectively allows
|
||||
; you to only connect to "trusted" peers.
|
||||
@@ -66,8 +73,8 @@
|
||||
; Add persistent peers that you ONLY want to connect to as desired. One peer
|
||||
; per line. You may specify each IP address with or without a port. The
|
||||
; default port will be added automatically if one is not specified here.
|
||||
; NOTE: Specifying this option will disable listening for incoming connections
|
||||
; and DNS seeding for peers.
|
||||
; NOTE: Specifying this option has other side effects as described above in
|
||||
; the 'addpeer' versus 'connect' summary section.
|
||||
; connect=192.168.1.1
|
||||
; connect=10.0.0.2:8333
|
||||
; connect=fe80::1
|
||||
@@ -85,7 +92,24 @@
|
||||
; DNS to query for available peers to connect with.
|
||||
; nodnsseed=1
|
||||
|
||||
; Disable listening for incoming connections.
|
||||
; Specify the interfaces to listen on. One listen address per line.
|
||||
; NOTE: The default port is modified by some options such as 'testnet', so it is
|
||||
; recommended to not specify a port and allow a proper default to be chosen
|
||||
; unless you have a specific reason to do otherwise.
|
||||
; listen= ; all interfaces on default port (this is the default)
|
||||
; listen=0.0.0.0 ; all ipv4 interfaces on default port
|
||||
; listen=:: ; all ipv6 interfaces on default port
|
||||
; listen=:8333 ; all interfaces on port 8333
|
||||
; listen=0.0.0.0:8333 ; all ipv4 interfaces on port 8333
|
||||
; listen=[::]:8333 ; all ipv6 interfaces on port 8333
|
||||
; listen=127.0.0.1:8333 ; only ipv4 localhost on port 8333
|
||||
; listen=[::1]:8333 ; only ipv6 localhost on port 8333
|
||||
; listen=127.0.0.1:8336 ; only ipv4 localhost on non-standard port 8336
|
||||
; listen=:8336 ; all interfaces on non-standard port 8336
|
||||
; listen=0.0.0.0:8336 ; all ipv4 interfaces on non-standard port 8336
|
||||
; listen=[::]:8336 ; all ipv6 interfaces on non-standard port 8336
|
||||
|
||||
; Disable listening for incoming connections. This will override all listeners.
|
||||
; nolisten=1
|
||||
|
||||
|
||||
@@ -102,14 +126,34 @@
|
||||
; rpcuser=whatever_username_you_want
|
||||
; rpcpass=
|
||||
|
||||
; Specify the interfaces for the RPC server listen on. One listen address per
|
||||
; line. NOTE: The default port is modified by some options such as 'testnet',
|
||||
; so it is recommended to not specify a port and allow a proper default to be
|
||||
; chosen unless you have a specific reason to do otherwise.
|
||||
; rpclisten= ; all interfaces on default port (this is the default)
|
||||
; rpclisten=0.0.0.0 ; all ipv4 interfaces on default port
|
||||
; rpclisten=:: ; all ipv6 interfaces on default port
|
||||
; rpclisten=:8334 ; all interfaces on port 8334
|
||||
; rpclisten=0.0.0.0:8334 ; all ipv4 interfaces on port 8334
|
||||
; rpclisten=[::]:8334 ; all ipv6 interfaces on port 8334
|
||||
; rpclisten=127.0.0.1:8334 ; only ipv4 localhost on port 8334
|
||||
; rpclisten=[::1]:8334 ; only ipv6 localhost on port 8334
|
||||
; rpclisten=127.0.0.1:8337 ; only ipv4 localhost on non-standard port 8337
|
||||
; rpclisten=:8337 ; all interfaces on non-standard port 8337
|
||||
; rpclisten=0.0.0.0:8337 ; all ipv4 interfaces on non-standard port 8337
|
||||
; rpclisten=[::]:8337 ; all ipv6 interfaces on non-standard port 8337
|
||||
|
||||
; Specify the maximum number of concurrent RPC clients for standard connections.
|
||||
; rpcmaxclients=10
|
||||
|
||||
; Specify the maximum number of concurrent RPC websocket clients.
|
||||
; rpcmaxwebsockets=25
|
||||
|
||||
; Use the following setting to disable the RPC server even if the rpcuser and
|
||||
; rpcpass are specified above. This allows one to quickly disable the RPC
|
||||
; server without having to remove credentials from the config file.
|
||||
; norpc=1
|
||||
|
||||
; The port used to listen for RPC connections.
|
||||
; rpcport=8334
|
||||
|
||||
|
||||
; ------------------------------------------------------------------------------
|
||||
; Debug
|
||||
|
||||
666
server.go
666
server.go
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -6,10 +6,13 @@ package main
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/conformal/btcdb"
|
||||
"github.com/conformal/btcjson"
|
||||
"github.com/conformal/btcwire"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -49,9 +52,12 @@ type server struct {
|
||||
nonce uint64
|
||||
listeners []net.Listener
|
||||
btcnet btcwire.BitcoinNet
|
||||
started int32 // atomic
|
||||
shutdown int32 // atomic
|
||||
shutdownSched int32 // atomic
|
||||
started int32 // atomic
|
||||
shutdown int32 // atomic
|
||||
shutdownSched int32 // atomic
|
||||
bytesMutex sync.Mutex // For the following two fields.
|
||||
bytesReceived uint64 // Total bytes received from all peers since start.
|
||||
bytesSent uint64 // Total bytes sent by all peers since start.
|
||||
addrManager *AddrManager
|
||||
rpcServer *rpcServer
|
||||
blockManager *blockManager
|
||||
@@ -60,23 +66,67 @@ type server struct {
|
||||
donePeers chan *peer
|
||||
banPeers chan *peer
|
||||
wakeup chan bool
|
||||
query chan interface{}
|
||||
relayInv chan *btcwire.InvVect
|
||||
broadcast chan broadcastMsg
|
||||
wg sync.WaitGroup
|
||||
quit chan bool
|
||||
nat NAT
|
||||
db btcdb.Db
|
||||
}
|
||||
|
||||
type peerState struct {
|
||||
peers *list.List
|
||||
outboundPeers *list.List
|
||||
persistentPeers *list.List
|
||||
banned map[string]time.Time
|
||||
outboundGroups map[string]int
|
||||
maxOutboundPeers int
|
||||
}
|
||||
|
||||
func (p *peerState) Count() int {
|
||||
return p.peers.Len() + p.outboundPeers.Len() + p.persistentPeers.Len()
|
||||
}
|
||||
|
||||
func (p *peerState) OutboundCount() int {
|
||||
return p.outboundPeers.Len() + p.persistentPeers.Len()
|
||||
}
|
||||
|
||||
func (p *peerState) NeedMoreOutbound() bool {
|
||||
return p.OutboundCount() < p.maxOutboundPeers &&
|
||||
p.Count() < cfg.MaxPeers
|
||||
}
|
||||
|
||||
// forAllOutboundPeers is a helper function that runs closure on all outbound
|
||||
// peers known to peerState.
|
||||
func (p *peerState) forAllOutboundPeers(closure func(p *peer)) {
|
||||
for e := p.outboundPeers.Front(); e != nil; e = e.Next() {
|
||||
closure(e.Value.(*peer))
|
||||
}
|
||||
for e := p.persistentPeers.Front(); e != nil; e = e.Next() {
|
||||
closure(e.Value.(*peer))
|
||||
}
|
||||
}
|
||||
|
||||
// forAllPeers is a helper function that runs closure on all peers known to
|
||||
// peerState.
|
||||
func (p *peerState) forAllPeers(closure func(p *peer)) {
|
||||
for e := p.peers.Front(); e != nil; e = e.Next() {
|
||||
closure(e.Value.(*peer))
|
||||
}
|
||||
p.forAllOutboundPeers(closure)
|
||||
}
|
||||
|
||||
// handleAddPeerMsg deals with adding new peers. It is invoked from the
|
||||
// peerHandler goroutine.
|
||||
func (s *server) handleAddPeerMsg(peers *list.List, banned map[string]time.Time, p *peer) bool {
|
||||
func (s *server) handleAddPeerMsg(state *peerState, p *peer) bool {
|
||||
if p == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ignore new peers if we're shutting down.
|
||||
if atomic.LoadInt32(&s.shutdown) != 0 {
|
||||
log.Infof("SRVR: New peer %s ignored - server is shutting "+
|
||||
srvrLog.Infof("New peer %s ignored - server is shutting "+
|
||||
"down", p)
|
||||
p.Shutdown()
|
||||
return false
|
||||
@@ -85,27 +135,27 @@ func (s *server) handleAddPeerMsg(peers *list.List, banned map[string]time.Time,
|
||||
// Disconnect banned peers.
|
||||
host, _, err := net.SplitHostPort(p.addr)
|
||||
if err != nil {
|
||||
log.Debugf("SRVR: can't split hostport %v", err)
|
||||
srvrLog.Debugf("can't split hostport %v", err)
|
||||
p.Shutdown()
|
||||
return false
|
||||
}
|
||||
if banEnd, ok := banned[host]; ok {
|
||||
if banEnd, ok := state.banned[host]; ok {
|
||||
if time.Now().Before(banEnd) {
|
||||
log.Debugf("SRVR: Peer %s is banned for another %v - "+
|
||||
srvrLog.Debugf("Peer %s is banned for another %v - "+
|
||||
"disconnecting", host, banEnd.Sub(time.Now()))
|
||||
p.Shutdown()
|
||||
return false
|
||||
}
|
||||
|
||||
log.Infof("SRVR: Peer %s is no longer banned", host)
|
||||
delete(banned, host)
|
||||
srvrLog.Infof("Peer %s is no longer banned", host)
|
||||
delete(state.banned, host)
|
||||
}
|
||||
|
||||
// TODO: Check for max peers from a single IP.
|
||||
|
||||
// Limit max number of total peers.
|
||||
if peers.Len() >= cfg.MaxPeers {
|
||||
log.Infof("SRVR: Max peers reached [%d] - disconnecting "+
|
||||
if state.Count() >= cfg.MaxPeers {
|
||||
srvrLog.Infof("Max peers reached [%d] - disconnecting "+
|
||||
"peer %s", cfg.MaxPeers, p)
|
||||
p.Shutdown()
|
||||
// TODO(oga) how to handle permanent peers here?
|
||||
@@ -114,10 +164,17 @@ func (s *server) handleAddPeerMsg(peers *list.List, banned map[string]time.Time,
|
||||
}
|
||||
|
||||
// Add the new peer and start it.
|
||||
log.Debugf("SRVR: New peer %s", p)
|
||||
peers.PushBack(p)
|
||||
srvrLog.Debugf("New peer %s", p)
|
||||
if p.inbound {
|
||||
state.peers.PushBack(p)
|
||||
p.Start()
|
||||
} else {
|
||||
state.outboundGroups[GroupKey(p.na)]++
|
||||
if p.persistent {
|
||||
state.persistentPeers.PushBack(p)
|
||||
} else {
|
||||
state.outboundPeers.PushBack(p)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
@@ -125,69 +182,76 @@ func (s *server) handleAddPeerMsg(peers *list.List, banned map[string]time.Time,
|
||||
|
||||
// handleDonePeerMsg deals with peers that have signalled they are done. It is
|
||||
// invoked from the peerHandler goroutine.
|
||||
func (s *server) handleDonePeerMsg(peers *list.List, p *peer) bool {
|
||||
for e := peers.Front(); e != nil; e = e.Next() {
|
||||
func (s *server) handleDonePeerMsg(state *peerState, p *peer) {
|
||||
var list *list.List
|
||||
if p.persistent {
|
||||
list = state.persistentPeers
|
||||
} else if p.inbound {
|
||||
list = state.peers
|
||||
} else {
|
||||
list = state.outboundPeers
|
||||
}
|
||||
for e := list.Front(); e != nil; e = e.Next() {
|
||||
if e.Value == p {
|
||||
// Issue an asynchronous reconnect if the peer was a
|
||||
// persistent outbound connection.
|
||||
if !p.inbound && p.persistent &&
|
||||
atomic.LoadInt32(&s.shutdown) == 0 {
|
||||
e.Value = newOutboundPeer(s, p.addr, true)
|
||||
return false
|
||||
return
|
||||
}
|
||||
peers.Remove(e)
|
||||
log.Debugf("SRVR: Removed peer %s", p)
|
||||
return true
|
||||
if !p.inbound {
|
||||
state.outboundGroups[GroupKey(p.na)]--
|
||||
}
|
||||
list.Remove(e)
|
||||
srvrLog.Debugf("Removed peer %s", p)
|
||||
return
|
||||
}
|
||||
}
|
||||
log.Warnf("SRVR: Lost peer %v that we never had!", p)
|
||||
return false
|
||||
// If we get here it means that either we didn't know about the peer
|
||||
// or we purposefully deleted it.
|
||||
}
|
||||
|
||||
// handleBanPeerMsg deals with banning peers. It is invoked from the
|
||||
// peerHandler goroutine.
|
||||
func (s *server) handleBanPeerMsg(banned map[string]time.Time, p *peer) {
|
||||
func (s *server) handleBanPeerMsg(state *peerState, p *peer) {
|
||||
host, _, err := net.SplitHostPort(p.addr)
|
||||
if err != nil {
|
||||
log.Debugf("SRVR: can't split ban peer %s %v", p.addr, err)
|
||||
srvrLog.Debugf("can't split ban peer %s %v", p.addr, err)
|
||||
return
|
||||
}
|
||||
direction := directionString(p.inbound)
|
||||
log.Infof("SRVR: Banned peer %s (%s) for %v", host, direction,
|
||||
srvrLog.Infof("Banned peer %s (%s) for %v", host, direction,
|
||||
cfg.BanDuration)
|
||||
banned[host] = time.Now().Add(cfg.BanDuration)
|
||||
state.banned[host] = time.Now().Add(cfg.BanDuration)
|
||||
|
||||
}
|
||||
|
||||
// handleRelayInvMsg deals with relaying inventory to peers that are not already
|
||||
// known to have it. It is invoked from the peerHandler goroutine.
|
||||
func (s *server) handleRelayInvMsg(peers *list.List, iv *btcwire.InvVect) {
|
||||
// Loop through all connected peers and relay the inventory to those
|
||||
// which are not already known to have it.
|
||||
for e := peers.Front(); e != nil; e = e.Next() {
|
||||
p := e.Value.(*peer)
|
||||
func (s *server) handleRelayInvMsg(state *peerState, iv *btcwire.InvVect) {
|
||||
state.forAllPeers(func(p *peer) {
|
||||
if !p.Connected() {
|
||||
continue
|
||||
return
|
||||
}
|
||||
|
||||
// Queue the inventory to be relayed with the next batch. It
|
||||
// will be ignored if the peer is already known to have the
|
||||
// inventory.
|
||||
p.QueueInventory(iv)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// handleBroadcastMsg deals with broadcasting messages to peers. It is invoked
|
||||
// from the peerHandler goroutine.
|
||||
func (s *server) handleBroadcastMsg(peers *list.List, bmsg *broadcastMsg) {
|
||||
for e := peers.Front(); e != nil; e = e.Next() {
|
||||
func (s *server) handleBroadcastMsg(state *peerState, bmsg *broadcastMsg) {
|
||||
state.forAllPeers(func(p *peer) {
|
||||
excluded := false
|
||||
for _, p := range bmsg.excludePeers {
|
||||
if e.Value == p {
|
||||
for _, ep := range bmsg.excludePeers {
|
||||
if p == ep {
|
||||
excluded = true
|
||||
}
|
||||
}
|
||||
p := e.Value.(*peer)
|
||||
// Don't broadcast to still connecting outbound peers .
|
||||
if !p.Connected() {
|
||||
excluded = true
|
||||
@@ -195,19 +259,148 @@ func (s *server) handleBroadcastMsg(peers *list.List, bmsg *broadcastMsg) {
|
||||
if !excluded {
|
||||
p.QueueMessage(bmsg.message, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type getConnCountMsg struct {
|
||||
reply chan int
|
||||
}
|
||||
|
||||
type getPeerInfoMsg struct {
|
||||
reply chan []*btcjson.GetPeerInfoResult
|
||||
}
|
||||
|
||||
type addNodeMsg struct {
|
||||
addr string
|
||||
permanent bool
|
||||
reply chan error
|
||||
}
|
||||
|
||||
type delNodeMsg struct {
|
||||
addr string
|
||||
reply chan error
|
||||
}
|
||||
|
||||
type getAddedNodesMsg struct {
|
||||
reply chan []*peer
|
||||
}
|
||||
|
||||
// handleQuery is the central handler for all queries and commands from other
|
||||
// goroutines related to peer state.
|
||||
func (s *server) handleQuery(querymsg interface{}, state *peerState) {
|
||||
switch msg := querymsg.(type) {
|
||||
case getConnCountMsg:
|
||||
nconnected := 0
|
||||
state.forAllPeers(func(p *peer) {
|
||||
if p.Connected() {
|
||||
nconnected++
|
||||
}
|
||||
})
|
||||
msg.reply <- nconnected
|
||||
|
||||
case getPeerInfoMsg:
|
||||
syncPeer := s.blockManager.SyncPeer()
|
||||
infos := make([]*btcjson.GetPeerInfoResult, 0, state.peers.Len())
|
||||
state.forAllPeers(func(p *peer) {
|
||||
if !p.Connected() {
|
||||
return
|
||||
}
|
||||
|
||||
// A lot of this will make the race detector go mad,
|
||||
// however it is statistics for purely informational purposes
|
||||
// and we don't really care if they are raced to get the new
|
||||
// version.
|
||||
info := &btcjson.GetPeerInfoResult{
|
||||
Addr: p.addr,
|
||||
Services: fmt.Sprintf("%08d", p.services),
|
||||
LastSend: p.lastSend.Unix(),
|
||||
LastRecv: p.lastRecv.Unix(),
|
||||
BytesSent: p.bytesSent,
|
||||
BytesRecv: p.bytesReceived,
|
||||
ConnTime: p.timeConnected.Unix(),
|
||||
Version: p.protocolVersion,
|
||||
SubVer: p.userAgent,
|
||||
Inbound: p.inbound,
|
||||
StartingHeight: p.lastBlock,
|
||||
BanScore: 0,
|
||||
SyncNode: p == syncPeer,
|
||||
}
|
||||
p.pingStatsMtx.Lock()
|
||||
info.PingTime = p.lastPingMicros
|
||||
if p.lastPingNonce != 0 {
|
||||
wait := time.Now().Sub(p.lastPingTime).Nanoseconds()
|
||||
// We actually want microseconds.
|
||||
info.PingWait = wait / 1000
|
||||
}
|
||||
p.pingStatsMtx.Unlock()
|
||||
infos = append(infos, info)
|
||||
})
|
||||
msg.reply <- infos
|
||||
|
||||
case addNodeMsg:
|
||||
// XXX(oga) duplicate oneshots?
|
||||
if msg.permanent {
|
||||
for e := state.persistentPeers.Front(); e != nil; e = e.Next() {
|
||||
peer := e.Value.(*peer)
|
||||
if peer.addr == msg.addr {
|
||||
msg.reply <- errors.New("peer already connected")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO(oga) if too many, nuke a non-perm peer.
|
||||
if s.handleAddPeerMsg(state,
|
||||
newOutboundPeer(s, msg.addr, msg.permanent)) {
|
||||
msg.reply <- nil
|
||||
} else {
|
||||
msg.reply <- errors.New("failed to add peer")
|
||||
}
|
||||
|
||||
case delNodeMsg:
|
||||
found := false
|
||||
for e := state.persistentPeers.Front(); e != nil; e = e.Next() {
|
||||
peer := e.Value.(*peer)
|
||||
if peer.addr == msg.addr {
|
||||
// Keep group counts ok since we remove from
|
||||
// the list now.
|
||||
state.outboundGroups[GroupKey(peer.na)]--
|
||||
// This is ok because we are not continuing
|
||||
// to iterate so won't corrupt the loop.
|
||||
state.persistentPeers.Remove(e)
|
||||
peer.Disconnect()
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
msg.reply <- nil
|
||||
} else {
|
||||
msg.reply <- errors.New("peer not found")
|
||||
}
|
||||
|
||||
// Request a list of the persistent (added) peers.
|
||||
case getAddedNodesMsg:
|
||||
// Respond with a slice of the relavent peers.
|
||||
peers := make([]*peer, 0, state.persistentPeers.Len())
|
||||
for e := state.persistentPeers.Front(); e != nil; e = e.Next() {
|
||||
peer := e.Value.(*peer)
|
||||
peers = append(peers, peer)
|
||||
}
|
||||
msg.reply <- peers
|
||||
}
|
||||
}
|
||||
|
||||
// listenHandler is the main listener which accepts incoming connections for the
|
||||
// server. It must be run as a goroutine.
|
||||
func (s *server) listenHandler(listener net.Listener) {
|
||||
log.Infof("SRVR: Server listening on %s", listener.Addr())
|
||||
srvrLog.Infof("Server listening on %s", listener.Addr())
|
||||
for atomic.LoadInt32(&s.shutdown) == 0 {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
// Only log the error if we're not forcibly shutting down.
|
||||
if atomic.LoadInt32(&s.shutdown) == 0 {
|
||||
log.Errorf("SRVR: can't accept connection: %v",
|
||||
srvrLog.Errorf("can't accept connection: %v",
|
||||
err)
|
||||
}
|
||||
continue
|
||||
@@ -215,7 +408,7 @@ func (s *server) listenHandler(listener net.Listener) {
|
||||
s.AddPeer(newInboundPeer(s, conn))
|
||||
}
|
||||
s.wg.Done()
|
||||
log.Tracef("SRVR: Listener handler done for %s", listener.Addr())
|
||||
srvrLog.Tracef("Listener handler done for %s", listener.Addr())
|
||||
}
|
||||
|
||||
// seedFromDNS uses DNS seeding to populate the address manager with peers.
|
||||
@@ -225,12 +418,8 @@ func (s *server) seedFromDNS() {
|
||||
return
|
||||
}
|
||||
|
||||
proxy := ""
|
||||
if cfg.Proxy != "" && cfg.UseTor {
|
||||
proxy = cfg.Proxy
|
||||
}
|
||||
for _, seeder := range activeNetParams.dnsSeeds {
|
||||
seedpeers := dnsDiscover(seeder, proxy)
|
||||
seedpeers := dnsDiscover(seeder)
|
||||
if len(seedpeers) == 0 {
|
||||
continue
|
||||
}
|
||||
@@ -271,13 +460,17 @@ func (s *server) peerHandler() {
|
||||
s.addrManager.Start()
|
||||
s.blockManager.Start()
|
||||
|
||||
log.Tracef("SRVR: Starting peer handler")
|
||||
peers := list.New()
|
||||
bannedPeers := make(map[string]time.Time)
|
||||
outboundPeers := 0
|
||||
maxOutbound := defaultMaxOutbound
|
||||
if cfg.MaxPeers < maxOutbound {
|
||||
maxOutbound = cfg.MaxPeers
|
||||
srvrLog.Tracef("Starting peer handler")
|
||||
state := &peerState{
|
||||
peers: list.New(),
|
||||
persistentPeers: list.New(),
|
||||
outboundPeers: list.New(),
|
||||
banned: make(map[string]time.Time),
|
||||
maxOutboundPeers: defaultMaxOutbound,
|
||||
outboundGroups: make(map[string]int),
|
||||
}
|
||||
if cfg.MaxPeers < state.maxOutboundPeers {
|
||||
state.maxOutboundPeers = cfg.MaxPeers
|
||||
}
|
||||
|
||||
// Add peers discovered through DNS to the address manager.
|
||||
@@ -289,10 +482,7 @@ func (s *server) peerHandler() {
|
||||
permanentPeers = cfg.AddPeers
|
||||
}
|
||||
for _, addr := range permanentPeers {
|
||||
if s.handleAddPeerMsg(peers, bannedPeers,
|
||||
newOutboundPeer(s, addr, true)) {
|
||||
outboundPeers++
|
||||
}
|
||||
s.handleAddPeerMsg(state, newOutboundPeer(s, addr, true))
|
||||
}
|
||||
|
||||
// if nothing else happens, wake us up soon.
|
||||
@@ -303,67 +493,54 @@ out:
|
||||
select {
|
||||
// New peers connected to the server.
|
||||
case p := <-s.newPeers:
|
||||
if s.handleAddPeerMsg(peers, bannedPeers, p) &&
|
||||
!p.inbound {
|
||||
outboundPeers++
|
||||
}
|
||||
s.handleAddPeerMsg(state, p)
|
||||
|
||||
// Disconnected peers.
|
||||
case p := <-s.donePeers:
|
||||
// handleDonePeerMsg return true if it removed a peer
|
||||
if s.handleDonePeerMsg(peers, p) {
|
||||
outboundPeers--
|
||||
}
|
||||
s.handleDonePeerMsg(state, p)
|
||||
|
||||
// Peer to ban.
|
||||
case p := <-s.banPeers:
|
||||
s.handleBanPeerMsg(bannedPeers, p)
|
||||
s.handleBanPeerMsg(state, p)
|
||||
|
||||
// New inventory to potentially be relayed to other peers.
|
||||
case invMsg := <-s.relayInv:
|
||||
s.handleRelayInvMsg(peers, invMsg)
|
||||
s.handleRelayInvMsg(state, invMsg)
|
||||
|
||||
// Message to broadcast to all connected peers except those
|
||||
// which are excluded by the message.
|
||||
case bmsg := <-s.broadcast:
|
||||
s.handleBroadcastMsg(peers, &bmsg)
|
||||
s.handleBroadcastMsg(state, &bmsg)
|
||||
|
||||
// Used by timers below to wake us back up.
|
||||
case <-s.wakeup:
|
||||
// this page left intentionally blank
|
||||
|
||||
case qmsg := <-s.query:
|
||||
s.handleQuery(qmsg, state)
|
||||
|
||||
// Shutdown the peer handler.
|
||||
case <-s.quit:
|
||||
// Shutdown peers.
|
||||
for e := peers.Front(); e != nil; e = e.Next() {
|
||||
p := e.Value.(*peer)
|
||||
state.forAllPeers(func(p *peer) {
|
||||
p.Shutdown()
|
||||
}
|
||||
})
|
||||
break out
|
||||
}
|
||||
|
||||
// Only try connect to more peers if we actually need more
|
||||
if outboundPeers >= maxOutbound || len(cfg.ConnectPeers) > 0 ||
|
||||
if !state.NeedMoreOutbound() || len(cfg.ConnectPeers) > 0 ||
|
||||
atomic.LoadInt32(&s.shutdown) != 0 {
|
||||
continue
|
||||
}
|
||||
groups := make(map[string]int)
|
||||
for e := peers.Front(); e != nil; e = e.Next() {
|
||||
peer := e.Value.(*peer)
|
||||
if !peer.inbound {
|
||||
groups[GroupKey(peer.na)]++
|
||||
}
|
||||
}
|
||||
|
||||
tries := 0
|
||||
for outboundPeers < maxOutbound &&
|
||||
peers.Len() < cfg.MaxPeers &&
|
||||
for state.NeedMoreOutbound() &&
|
||||
atomic.LoadInt32(&s.shutdown) == 0 {
|
||||
// We bias like bitcoind does, 10 for no outgoing
|
||||
// up to 90 (8) for the selection of new vs tried
|
||||
//addresses.
|
||||
|
||||
nPeers := outboundPeers
|
||||
nPeers := state.OutboundCount()
|
||||
if nPeers > 8 {
|
||||
nPeers = 8
|
||||
}
|
||||
@@ -379,7 +556,7 @@ out:
|
||||
// to the same network segment at the expense of
|
||||
// others. bitcoind breaks out of the loop here, but
|
||||
// we continue to try other addresses.
|
||||
if groups[key] != 0 {
|
||||
if state.outboundGroups[key] != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -410,15 +587,13 @@ out:
|
||||
tries = 0
|
||||
// any failure will be due to banned peers etc. we have
|
||||
// already checked that we have room for more peers.
|
||||
if s.handleAddPeerMsg(peers, bannedPeers,
|
||||
if s.handleAddPeerMsg(state,
|
||||
newOutboundPeer(s, addrStr, false)) {
|
||||
outboundPeers++
|
||||
groups[key]++
|
||||
}
|
||||
}
|
||||
|
||||
// We we need more peers, wake up in ten seconds and try again.
|
||||
if outboundPeers < maxOutbound && peers.Len() < cfg.MaxPeers {
|
||||
if state.NeedMoreOutbound() {
|
||||
time.AfterFunc(10*time.Second, func() {
|
||||
s.wakeup <- true
|
||||
})
|
||||
@@ -428,7 +603,7 @@ out:
|
||||
s.blockManager.Stop()
|
||||
s.addrManager.Stop()
|
||||
s.wg.Done()
|
||||
log.Tracef("SRVR: Peer handler done")
|
||||
srvrLog.Tracef("Peer handler done")
|
||||
}
|
||||
|
||||
// AddPeer adds a new peer that has already been connected to the server.
|
||||
@@ -456,6 +631,81 @@ func (s *server) BroadcastMessage(msg btcwire.Message, exclPeers ...*peer) {
|
||||
s.broadcast <- bmsg
|
||||
}
|
||||
|
||||
// ConnectedCount returns the number of currently connected peers.
|
||||
func (s *server) ConnectedCount() int {
|
||||
replyChan := make(chan int)
|
||||
|
||||
s.query <- getConnCountMsg{reply: replyChan}
|
||||
|
||||
return <-replyChan
|
||||
}
|
||||
|
||||
// AddedNodeInfo returns an array of btcjson.GetAddedNodeInfoResult structures
|
||||
// describing the persistent (added) nodes.
|
||||
func (s *server) AddedNodeInfo() []*peer {
|
||||
replyChan := make(chan []*peer)
|
||||
s.query <- getAddedNodesMsg{reply: replyChan}
|
||||
return <-replyChan
|
||||
}
|
||||
|
||||
// PeerInfo returns an array of PeerInfo structures describing all connected
|
||||
// peers.
|
||||
func (s *server) PeerInfo() []*btcjson.GetPeerInfoResult {
|
||||
replyChan := make(chan []*btcjson.GetPeerInfoResult)
|
||||
|
||||
s.query <- getPeerInfoMsg{reply: replyChan}
|
||||
|
||||
return <-replyChan
|
||||
}
|
||||
|
||||
// AddAddr adds `addr' as a new outbound peer. If permanent is true then the
|
||||
// peer will be persistent and reconnect if the connection is lost.
|
||||
// It is an error to call this with an already existing peer.
|
||||
func (s *server) AddAddr(addr string, permanent bool) error {
|
||||
replyChan := make(chan error)
|
||||
|
||||
s.query <- addNodeMsg{addr: addr, permanent: permanent, reply: replyChan}
|
||||
|
||||
return <-replyChan
|
||||
}
|
||||
|
||||
// RemoveAddr removes `addr' from the list of persistent peers if present.
|
||||
// An error will be returned if the peer was not found.
|
||||
func (s *server) RemoveAddr(addr string) error {
|
||||
replyChan := make(chan error)
|
||||
|
||||
s.query <- delNodeMsg{addr: addr, reply: replyChan}
|
||||
|
||||
return <-replyChan
|
||||
}
|
||||
|
||||
// AddBytesSent adds the passed number of bytes to the total bytes sent counter
|
||||
// for the server. It is safe for concurrent access.
|
||||
func (s *server) AddBytesSent(bytesSent uint64) {
|
||||
s.bytesMutex.Lock()
|
||||
defer s.bytesMutex.Unlock()
|
||||
|
||||
s.bytesSent += bytesSent
|
||||
}
|
||||
|
||||
// AddBytesReceived adds the passed number of bytes to the total bytes received
|
||||
// counter for the server. It is safe for concurrent access.
|
||||
func (s *server) AddBytesReceived(bytesReceived uint64) {
|
||||
s.bytesMutex.Lock()
|
||||
defer s.bytesMutex.Unlock()
|
||||
|
||||
s.bytesReceived += bytesReceived
|
||||
}
|
||||
|
||||
// NetTotals returns the sum of all bytes received and sent across the network
|
||||
// for all peers. It is safe for concurrent access.
|
||||
func (s *server) NetTotals() (uint64, uint64) {
|
||||
s.bytesMutex.Lock()
|
||||
defer s.bytesMutex.Unlock()
|
||||
|
||||
return s.bytesReceived, s.bytesSent
|
||||
}
|
||||
|
||||
// Start begins accepting connections from peers.
|
||||
func (s *server) Start() {
|
||||
// Already started?
|
||||
@@ -463,7 +713,7 @@ func (s *server) Start() {
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("SRVR: Starting server")
|
||||
srvrLog.Trace("Starting server")
|
||||
|
||||
// Start all the listeners. There will not be any if listening is
|
||||
// disabled.
|
||||
@@ -476,6 +726,10 @@ func (s *server) Start() {
|
||||
// managers.
|
||||
s.wg.Add(1)
|
||||
go s.peerHandler()
|
||||
if s.nat != nil {
|
||||
s.wg.Add(1)
|
||||
go s.upnpUpdateThread()
|
||||
}
|
||||
|
||||
// Start the RPC server if it's not disabled.
|
||||
if !cfg.DisableRPC {
|
||||
@@ -488,11 +742,11 @@ func (s *server) Start() {
|
||||
func (s *server) Stop() error {
|
||||
// Make sure this only happens once.
|
||||
if atomic.AddInt32(&s.shutdown, 1) != 1 {
|
||||
log.Infof("SRVR: Server is already in the process of shutting down")
|
||||
srvrLog.Infof("Server is already in the process of shutting down")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Warnf("SRVR: Server shutting down")
|
||||
srvrLog.Warnf("Server shutting down")
|
||||
|
||||
// Stop all the listeners. There will not be any listeners if
|
||||
// listening is disabled.
|
||||
@@ -516,7 +770,6 @@ func (s *server) Stop() error {
|
||||
// WaitForShutdown blocks until the main listener and peer handlers are stopped.
|
||||
func (s *server) WaitForShutdown() {
|
||||
s.wg.Wait()
|
||||
log.Infof("SRVR: Server shutdown complete")
|
||||
}
|
||||
|
||||
// ScheduleShutdown schedules a server shutdown after the specified duration.
|
||||
@@ -527,7 +780,7 @@ func (s *server) ScheduleShutdown(duration time.Duration) {
|
||||
if atomic.AddInt32(&s.shutdownSched, 1) != 1 {
|
||||
return
|
||||
}
|
||||
log.Warnf("SRVR: Server shutdown in %v", duration)
|
||||
srvrLog.Warnf("Server shutdown in %v", duration)
|
||||
go func() {
|
||||
remaining := duration
|
||||
tickDuration := dynamicTickDuration(remaining)
|
||||
@@ -553,50 +806,243 @@ func (s *server) ScheduleShutdown(duration time.Duration) {
|
||||
ticker.Stop()
|
||||
ticker = time.NewTicker(tickDuration)
|
||||
}
|
||||
log.Warnf("SRVR: Server shutdown in %v", remaining)
|
||||
srvrLog.Warnf("Server shutdown in %v", remaining)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// parseListeners splits the list of listen addresses passed in addrs into
|
||||
// IPv4 and IPv6 slices and returns them. This allows easy creation of the
|
||||
// listeners on the correct interface "tcp4" and "tcp6". It also properly
|
||||
// detects addresses which apply to "all interfaces" and adds the address to
|
||||
// both slices.
|
||||
func parseListeners(addrs []string) ([]string, []string, bool, error) {
|
||||
ipv4ListenAddrs := make([]string, 0, len(addrs)*2)
|
||||
ipv6ListenAddrs := make([]string, 0, len(addrs)*2)
|
||||
haveWildcard := false
|
||||
|
||||
for _, addr := range addrs {
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
// Shouldn't happen due to already being normalized.
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
// Empty host or host of * on plan9 is both IPv4 and IPv6.
|
||||
if host == "" || (host == "*" && runtime.GOOS == "plan9") {
|
||||
ipv4ListenAddrs = append(ipv4ListenAddrs, addr)
|
||||
ipv6ListenAddrs = append(ipv6ListenAddrs, addr)
|
||||
haveWildcard = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse the IP.
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
return nil, nil, false, fmt.Errorf("'%s' is not a "+
|
||||
"valid IP address", host)
|
||||
}
|
||||
|
||||
// To4 returns nil when the IP is not an IPv4 address, so use
|
||||
// this determine the address type.
|
||||
if ip.To4() == nil {
|
||||
ipv6ListenAddrs = append(ipv6ListenAddrs, addr)
|
||||
} else {
|
||||
ipv4ListenAddrs = append(ipv4ListenAddrs, addr)
|
||||
}
|
||||
}
|
||||
return ipv4ListenAddrs, ipv6ListenAddrs, haveWildcard, nil
|
||||
}
|
||||
|
||||
func (s *server) upnpUpdateThread() {
|
||||
// Go off immediately to prevent code duplication, thereafter we renew
|
||||
// lease every 15 minutes.
|
||||
timer := time.NewTimer(0 * time.Second)
|
||||
lport, _ := strconv.ParseInt(activeNetParams.listenPort, 10, 16)
|
||||
first := true
|
||||
out:
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
// TODO(oga) pick external port more cleverly
|
||||
// TODO(oga) know which ports we are listening to on an external net.
|
||||
// TODO(oga) if specific listen port doesn't work then ask for wildcard
|
||||
// listen port?
|
||||
// XXX this assumes timeout is in seconds.
|
||||
listenPort, err := s.nat.AddPortMapping("tcp", int(lport), int(lport),
|
||||
"btcd listen port", 20*60)
|
||||
if err != nil {
|
||||
srvrLog.Warnf("can't add UPnP port mapping: %v", err)
|
||||
}
|
||||
if first && err == nil {
|
||||
// TODO(oga): look this up periodically to see if upnp domain changed
|
||||
// and so did ip.
|
||||
externalip, err := s.nat.GetExternalAddress()
|
||||
if err != nil {
|
||||
srvrLog.Warnf("UPnP can't get external address: %v", err)
|
||||
continue out
|
||||
}
|
||||
na := btcwire.NewNetAddressIPPort(externalip, uint16(listenPort),
|
||||
btcwire.SFNodeNetwork)
|
||||
s.addrManager.addLocalAddress(na, UpnpPrio)
|
||||
srvrLog.Warnf("Successfully bound via UPnP to %s", NetAddressKey(na))
|
||||
first = false
|
||||
}
|
||||
timer.Reset(time.Minute * 15)
|
||||
case <-s.quit:
|
||||
break out
|
||||
}
|
||||
}
|
||||
|
||||
timer.Stop()
|
||||
|
||||
if err := s.nat.DeletePortMapping("tcp", int(lport), int(lport)); err != nil {
|
||||
srvrLog.Warnf("unable to remove UPnP port mapping: %v", err)
|
||||
} else {
|
||||
srvrLog.Debugf("succesfully disestablished UPnP port mapping")
|
||||
}
|
||||
|
||||
s.wg.Done()
|
||||
}
|
||||
|
||||
// newServer returns a new btcd server configured to listen on addr for the
|
||||
// bitcoin network type specified in btcnet. Use start to begin accepting
|
||||
// connections from peers.
|
||||
func newServer(addr string, db btcdb.Db, btcnet btcwire.BitcoinNet) (*server, error) {
|
||||
func newServer(listenAddrs []string, db btcdb.Db, btcnet btcwire.BitcoinNet) (*server, error) {
|
||||
nonce, err := btcwire.RandomUint64()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var listeners []net.Listener
|
||||
if !cfg.DisableListen {
|
||||
// IPv4 listener.
|
||||
listener4, err := net.Listen("tcp4", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
listeners = append(listeners, listener4)
|
||||
amgr := NewAddrManager()
|
||||
|
||||
// IPv6 listener.
|
||||
listener6, err := net.Listen("tcp6", addr)
|
||||
var listeners []net.Listener
|
||||
var nat NAT
|
||||
if !cfg.DisableListen {
|
||||
ipv4Addrs, ipv6Addrs, wildcard, err :=
|
||||
parseListeners(listenAddrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
listeners = append(listeners, listener6)
|
||||
listeners = make([]net.Listener, 0, len(ipv4Addrs)+len(ipv6Addrs))
|
||||
discover := true
|
||||
if len(cfg.ExternalIPs) != 0 {
|
||||
discover = false
|
||||
// if this fails we have real issues.
|
||||
port, _ := strconv.ParseUint(
|
||||
activeNetParams.listenPort, 10, 16)
|
||||
|
||||
for _, sip := range cfg.ExternalIPs {
|
||||
eport := uint16(port)
|
||||
host, portstr, err := net.SplitHostPort(sip)
|
||||
if err != nil {
|
||||
// no port, use default.
|
||||
host = sip
|
||||
} else {
|
||||
port, err := strconv.ParseUint(
|
||||
portstr, 10, 16)
|
||||
if err != nil {
|
||||
srvrLog.Warnf("Can not parse "+
|
||||
"port from %s for "+
|
||||
"externalip: %v", sip,
|
||||
err)
|
||||
continue
|
||||
}
|
||||
eport = uint16(port)
|
||||
}
|
||||
na, err := hostToNetAddress(host, eport,
|
||||
btcwire.SFNodeNetwork)
|
||||
if err != nil {
|
||||
srvrLog.Warnf("Not adding %s as "+
|
||||
"externalip: %v", sip, err)
|
||||
continue
|
||||
}
|
||||
|
||||
amgr.addLocalAddress(na, ManualPrio)
|
||||
}
|
||||
} else if discover && cfg.Upnp {
|
||||
nat, err = Discover()
|
||||
if err != nil {
|
||||
srvrLog.Warnf("Can't discover upnp: %v", err)
|
||||
}
|
||||
// nil nat here is fine, just means no upnp on network.
|
||||
}
|
||||
|
||||
// TODO(oga) nonstandard port...
|
||||
if wildcard {
|
||||
port, err :=
|
||||
strconv.ParseUint(activeNetParams.listenPort,
|
||||
10, 16)
|
||||
if err != nil {
|
||||
// I can't think of a cleaner way to do this...
|
||||
goto nowc
|
||||
}
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
for _, a := range addrs {
|
||||
ip, _, err := net.ParseCIDR(a.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
na := btcwire.NewNetAddressIPPort(ip,
|
||||
uint16(port), btcwire.SFNodeNetwork)
|
||||
if discover {
|
||||
amgr.addLocalAddress(na, InterfacePrio)
|
||||
}
|
||||
}
|
||||
}
|
||||
nowc:
|
||||
|
||||
for _, addr := range ipv4Addrs {
|
||||
listener, err := net.Listen("tcp4", addr)
|
||||
if err != nil {
|
||||
srvrLog.Warnf("Can't listen on %s: %v", addr,
|
||||
err)
|
||||
continue
|
||||
}
|
||||
listeners = append(listeners, listener)
|
||||
|
||||
if discover {
|
||||
if na, err := deserialiseNetAddress(addr); err == nil {
|
||||
amgr.addLocalAddress(na, BoundPrio)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, addr := range ipv6Addrs {
|
||||
listener, err := net.Listen("tcp6", addr)
|
||||
if err != nil {
|
||||
srvrLog.Warnf("Can't listen on %s: %v", addr,
|
||||
err)
|
||||
continue
|
||||
}
|
||||
listeners = append(listeners, listener)
|
||||
if discover {
|
||||
if na, err := deserialiseNetAddress(addr); err == nil {
|
||||
amgr.addLocalAddress(na, BoundPrio)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(listeners) == 0 {
|
||||
return nil, errors.New("No valid listen address")
|
||||
}
|
||||
}
|
||||
|
||||
s := server{
|
||||
nonce: nonce,
|
||||
listeners: listeners,
|
||||
btcnet: btcnet,
|
||||
addrManager: NewAddrManager(),
|
||||
addrManager: amgr,
|
||||
newPeers: make(chan *peer, cfg.MaxPeers),
|
||||
donePeers: make(chan *peer, cfg.MaxPeers),
|
||||
banPeers: make(chan *peer, cfg.MaxPeers),
|
||||
wakeup: make(chan bool),
|
||||
query: make(chan interface{}),
|
||||
relayInv: make(chan *btcwire.InvVect, cfg.MaxPeers),
|
||||
broadcast: make(chan broadcastMsg, cfg.MaxPeers),
|
||||
quit: make(chan bool),
|
||||
nat: nat,
|
||||
db: db,
|
||||
}
|
||||
bm, err := newBlockManager(&s)
|
||||
@@ -607,7 +1053,7 @@ func newServer(addr string, db btcdb.Db, btcnet btcwire.BitcoinNet) (*server, er
|
||||
s.txMemPool = newTxMemPool(&s)
|
||||
|
||||
if !cfg.DisableRPC {
|
||||
s.rpcServer, err = newRPCServer(&s)
|
||||
s.rpcServer, err = newRPCServer(cfg.RPCListeners, &s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
326
service_windows.go
Normal file
326
service_windows.go
Normal file
@@ -0,0 +1,326 @@
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/conformal/winsvc/eventlog"
|
||||
"github.com/conformal/winsvc/mgr"
|
||||
"github.com/conformal/winsvc/svc"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// svcName is the name of btcd service.
|
||||
svcName = "btcdsvc"
|
||||
|
||||
// svcDisplayName is the service name that will be shown in the windows
|
||||
// services list. Not the svcName is the "real" name which is used
|
||||
// to control the service. This is only for display purposes.
|
||||
svcDisplayName = "Btcd Service"
|
||||
|
||||
// svcDesc is the description of the service.
|
||||
svcDesc = "Downloads and stays synchronized with the bitcoin block " +
|
||||
"chain and provides chain services to applications."
|
||||
)
|
||||
|
||||
// elog is used to send messages to the Windows event log.
|
||||
var elog *eventlog.Log
|
||||
|
||||
// logServiceStartOfDay logs information about btcd when the main server has
|
||||
// been started to the Windows event log.
|
||||
func logServiceStartOfDay(srvr *server) {
|
||||
var message string
|
||||
message += fmt.Sprintf("Version %s\n", version())
|
||||
message += fmt.Sprintf("Configuration directory: %s\n", btcdHomeDir)
|
||||
message += fmt.Sprintf("Configuration file: %s\n", cfg.ConfigFile)
|
||||
message += fmt.Sprintf("Data directory: %s\n", cfg.DataDir)
|
||||
|
||||
elog.Info(1, message)
|
||||
}
|
||||
|
||||
// btcdService houses the main service handler which handles all service
|
||||
// updates and launching btcdMain.
|
||||
type btcdService struct{}
|
||||
|
||||
// Execute is the main entry point the winsvc package calls when receiving
|
||||
// information from the Windows service control manager. It launches the
|
||||
// long-running btcdMain (which is the real meat of btcd), handles service
|
||||
// change requests, and notifies the service control manager of changes.
|
||||
func (s *btcdService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
|
||||
// Service start is pending.
|
||||
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
|
||||
changes <- svc.Status{State: svc.StartPending}
|
||||
|
||||
// Start btcdMain in a separate goroutine so the service can start
|
||||
// quickly. Shutdown (along with a potential error) is reported via
|
||||
// doneChan. serverChan is notified with the main server instance once
|
||||
// it is started so it can be gracefully stopped.
|
||||
doneChan := make(chan error)
|
||||
serverChan := make(chan *server)
|
||||
go func() {
|
||||
err := btcdMain(serverChan)
|
||||
doneChan <- err
|
||||
}()
|
||||
|
||||
// Service is now started.
|
||||
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
|
||||
|
||||
var mainServer *server
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case c := <-r:
|
||||
switch c.Cmd {
|
||||
case svc.Interrogate:
|
||||
changes <- c.CurrentStatus
|
||||
|
||||
case svc.Stop, svc.Shutdown:
|
||||
// Service stop is pending. Don't accept any
|
||||
// more commands while pending.
|
||||
changes <- svc.Status{State: svc.StopPending}
|
||||
|
||||
// Stop the main server gracefully when it is
|
||||
// already setup or just break out and allow
|
||||
// the service to exit immediately if it's not
|
||||
// setup yet. Note that calling Stop will cause
|
||||
// btcdMain to exit in the goroutine above which
|
||||
// will in turn send a signal (and a potential
|
||||
// error) to doneChan.
|
||||
if mainServer != nil {
|
||||
mainServer.Stop()
|
||||
} else {
|
||||
break loop
|
||||
}
|
||||
|
||||
default:
|
||||
elog.Error(1, fmt.Sprintf("Unexpected control "+
|
||||
"request #%d.", c))
|
||||
}
|
||||
|
||||
case srvr := <-serverChan:
|
||||
mainServer = srvr
|
||||
logServiceStartOfDay(mainServer)
|
||||
|
||||
case err := <-doneChan:
|
||||
if err != nil {
|
||||
elog.Error(1, err.Error())
|
||||
}
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
// Service is now stopped.
|
||||
changes <- svc.Status{State: svc.Stopped}
|
||||
return false, 0
|
||||
}
|
||||
|
||||
// installService attempts to install the btcd service. Typically this should
|
||||
// be done by the msi installer, but it is provided here since it can be useful
|
||||
// for development.
|
||||
func installService() error {
|
||||
// Get the path of the current executable. This is needed because
|
||||
// os.Args[0] can vary depending on how the application was launched.
|
||||
// For example, under cmd.exe it will only be the name of the app
|
||||
// without the path or extension, but under mingw it will be the full
|
||||
// path including the extension.
|
||||
exePath, err := filepath.Abs(os.Args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if filepath.Ext(exePath) == "" {
|
||||
exePath += ".exe"
|
||||
}
|
||||
|
||||
// Connect to the windows service manager.
|
||||
serviceManager, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer serviceManager.Disconnect()
|
||||
|
||||
// Ensure the service doesn't already exist.
|
||||
service, err := serviceManager.OpenService(svcName)
|
||||
if err == nil {
|
||||
service.Close()
|
||||
return fmt.Errorf("service %s already exists", svcName)
|
||||
}
|
||||
|
||||
// Install the service.
|
||||
service, err = serviceManager.CreateService(svcName, exePath, mgr.Config{
|
||||
DisplayName: svcDisplayName,
|
||||
Description: svcDesc,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer service.Close()
|
||||
|
||||
// Support events to the event log using the standard "standard" Windows
|
||||
// EventCreate.exe message file. This allows easy logging of custom
|
||||
// messges instead of needing to create our own message catalog.
|
||||
eventlog.Remove(svcName)
|
||||
eventsSupported := uint32(eventlog.Error | eventlog.Warning | eventlog.Info)
|
||||
err = eventlog.InstallAsEventCreate(svcName, eventsSupported)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeService attempts to uninstall the btcd service. Typically this should
|
||||
// be done by the msi uninstaller, but it is provided here since it can be
|
||||
// useful for development. Not the eventlog entry is intentionally not removed
|
||||
// since it would invalidate any existing event log messages.
|
||||
func removeService() error {
|
||||
// Connect to the windows service manager.
|
||||
serviceManager, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer serviceManager.Disconnect()
|
||||
|
||||
// Ensure the service exists.
|
||||
service, err := serviceManager.OpenService(svcName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("service %s is not installed", svcName)
|
||||
}
|
||||
defer service.Close()
|
||||
|
||||
// Remove the service.
|
||||
err = service.Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// startService attempts to start the btcd service.
|
||||
func startService() error {
|
||||
// Connect to the windows service manager.
|
||||
serviceManager, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer serviceManager.Disconnect()
|
||||
|
||||
service, err := serviceManager.OpenService(svcName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not access service: %v", err)
|
||||
}
|
||||
defer service.Close()
|
||||
|
||||
err = service.Start(os.Args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not start service: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// controlService allows commands which change the status of the service. It
|
||||
// also waits for up to 10 seconds for the service to change to the passed
|
||||
// state.
|
||||
func controlService(c svc.Cmd, to svc.State) error {
|
||||
// Connect to the windows service manager.
|
||||
serviceManager, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer serviceManager.Disconnect()
|
||||
|
||||
service, err := serviceManager.OpenService(svcName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not access service: %v", err)
|
||||
}
|
||||
defer service.Close()
|
||||
|
||||
status, err := service.Control(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not send control=%d: %v", c, err)
|
||||
}
|
||||
|
||||
// Send the control message.
|
||||
timeout := time.Now().Add(10 * time.Second)
|
||||
for status.State != to {
|
||||
if timeout.Before(time.Now()) {
|
||||
return fmt.Errorf("timeout waiting for service to go "+
|
||||
"to state=%d", to)
|
||||
}
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
status, err = service.Query()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not retrieve service "+
|
||||
"status: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// performServiceCommand attempts to run one of the supported service commands
|
||||
// provided on the command line via the service command flag. An appropriate
|
||||
// error is returned if an invalid command is specified.
|
||||
func performServiceCommand(command string) error {
|
||||
var err error
|
||||
switch command {
|
||||
case "install":
|
||||
err = installService()
|
||||
|
||||
case "remove":
|
||||
err = removeService()
|
||||
|
||||
case "start":
|
||||
err = startService()
|
||||
|
||||
case "stop":
|
||||
err = controlService(svc.Stop, svc.Stopped)
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("invalid service command [%s]", command)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// serviceMain checks whether we're being invoked as a service, and if so uses
|
||||
// the service control manager to start the long-running server. A flag is
|
||||
// returned to the caller so the application can determine whether to exit (when
|
||||
// running as a service) or launch in normal interactive mode.
|
||||
func serviceMain() (bool, error) {
|
||||
// Don't run as a service if we're running interactively (or that can't
|
||||
// be determined due to an error).
|
||||
isInteractive, err := svc.IsAnInteractiveSession()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if isInteractive {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
elog, err = eventlog.Open(svcName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer elog.Close()
|
||||
|
||||
err = svc.Run(svcName, &btcdService{})
|
||||
if err != nil {
|
||||
elog.Error(1, fmt.Sprintf("Service start failed: %v", err))
|
||||
return true, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Set windows specific functions to real functions.
|
||||
func init() {
|
||||
runServiceCommand = performServiceCommand
|
||||
winServiceMain = serviceMain
|
||||
}
|
||||
12
signal.go
12
signal.go
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -27,10 +27,16 @@ func mainInterruptHandler() {
|
||||
for {
|
||||
select {
|
||||
case <-interruptChannel:
|
||||
for _, callback := range interruptCallbacks {
|
||||
btcdLog.Infof("Received SIGINT (Ctrl+C). Shutting down...")
|
||||
// run handlers in LIFO order.
|
||||
for i := range interruptCallbacks {
|
||||
idx := len(interruptCallbacks) - 1 - i
|
||||
callback := interruptCallbacks[idx]
|
||||
callback()
|
||||
}
|
||||
os.Exit(0)
|
||||
|
||||
// Signal the main goroutine to shutdown.
|
||||
shutdownChannel <- true
|
||||
|
||||
case handler := <-addHandlerChannel:
|
||||
interruptCallbacks = append(interruptCallbacks, handler)
|
||||
|
||||
107
upgrade.go
107
upgrade.go
@@ -1,14 +1,50 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// dirEmpty returns whether or not the specified directory path is empty.
|
||||
func dirEmpty(dirPath string) (bool, error) {
|
||||
f, err := os.Open(dirPath)
|
||||
defer f.Close()
|
||||
|
||||
// Read the names of a max of one entry from the directory. When the
|
||||
// directory is empty, an io.EOF error will be returned, so allow it.
|
||||
names, err := f.Readdirnames(1)
|
||||
if err != nil && err != io.EOF {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(names) == 0, nil
|
||||
}
|
||||
|
||||
// oldBtcdHomeDir returns the OS specific home directory btcd used prior to
|
||||
// version 0.3.3. This has since been replaced with btcutil.AppDataDir, but
|
||||
// this function is still provided for the automatic upgrade path.
|
||||
func oldBtcdHomeDir() string {
|
||||
// Search for Windows APPDATA first. This won't exist on POSIX OSes.
|
||||
appData := os.Getenv("APPDATA")
|
||||
if appData != "" {
|
||||
return filepath.Join(appData, "btcd")
|
||||
}
|
||||
|
||||
// Fall back to standard HOME directory that works for most POSIX OSes.
|
||||
home := os.Getenv("HOME")
|
||||
if home != "" {
|
||||
return filepath.Join(home, ".btcd")
|
||||
}
|
||||
|
||||
// In the worst case, use the current directory.
|
||||
return "."
|
||||
}
|
||||
|
||||
// upgradeDBPathNet moves the database for a specific network from its
|
||||
// location prior to btcd version 0.2.0 and uses heuristics to ascertain the old
|
||||
// database type to rename to the new format.
|
||||
@@ -56,7 +92,7 @@ func upgradeDBPaths() error {
|
||||
// their names were suffixed by "testnet" and "regtest" for their
|
||||
// respective networks. Check for the old database and update it to the
|
||||
// new path introduced with version 0.2.0 accodingly.
|
||||
oldDbRoot := filepath.Join(btcdHomeDir(), "db")
|
||||
oldDbRoot := filepath.Join(oldBtcdHomeDir(), "db")
|
||||
upgradeDBPathNet(filepath.Join(oldDbRoot, "btcd.db"), "mainnet")
|
||||
upgradeDBPathNet(filepath.Join(oldDbRoot, "btcd_testnet.db"), "testnet")
|
||||
upgradeDBPathNet(filepath.Join(oldDbRoot, "btcd_regtest.db"), "regtest")
|
||||
@@ -70,7 +106,72 @@ func upgradeDBPaths() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// upgradeDataPaths moves the application data from its location prior to btcd
|
||||
// version 0.3.3 to its new location.
|
||||
func upgradeDataPaths() error {
|
||||
// No need to migrate if the old and new home paths are the same.
|
||||
oldHomePath := oldBtcdHomeDir()
|
||||
newHomePath := btcdHomeDir
|
||||
if oldHomePath == newHomePath {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only migrate if the old path exists and the new one doesn't.
|
||||
if fileExists(oldHomePath) && !fileExists(newHomePath) {
|
||||
// Create the new path.
|
||||
btcdLog.Infof("Migrating application home path from '%s' to '%s'",
|
||||
oldHomePath, newHomePath)
|
||||
err := os.MkdirAll(newHomePath, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Move old btcd.conf into new location if needed.
|
||||
oldConfPath := filepath.Join(oldHomePath, defaultConfigFilename)
|
||||
newConfPath := filepath.Join(newHomePath, defaultConfigFilename)
|
||||
if fileExists(oldConfPath) && !fileExists(newConfPath) {
|
||||
err := os.Rename(oldConfPath, newConfPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Move old data directory into new location if needed.
|
||||
oldDataPath := filepath.Join(oldHomePath, defaultDataDirname)
|
||||
newDataPath := filepath.Join(newHomePath, defaultDataDirname)
|
||||
if fileExists(oldDataPath) && !fileExists(newDataPath) {
|
||||
err := os.Rename(oldDataPath, newDataPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the old home if it is empty or show a warning if not.
|
||||
ohpEmpty, err := dirEmpty(oldHomePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ohpEmpty {
|
||||
err := os.Remove(oldHomePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
btcdLog.Warnf("Not removing '%s' since it contains files "+
|
||||
"not created by this application. You may "+
|
||||
"want to manually move them or delete them.",
|
||||
oldHomePath)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// doUpgrades performs upgrades to btcd as new versions require it.
|
||||
func doUpgrades() error {
|
||||
return upgradeDBPaths()
|
||||
err := upgradeDBPaths()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return upgradeDataPaths()
|
||||
}
|
||||
|
||||
405
upnp.go
Normal file
405
upnp.go
Normal file
@@ -0,0 +1,405 @@
|
||||
package main
|
||||
|
||||
// Upnp code taken from Taipei Torrent license is below:
|
||||
// Copyright (c) 2010 Jack Palevich. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Just enough UPnP to be able to forward ports
|
||||
//
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NAT is an interface representing a NAT traversal options for example UPNP or
|
||||
// NAT-PMP. It provides methods to query and manipulate this traversal to allow
|
||||
// access to services.
|
||||
type NAT interface {
|
||||
// Get the external address from outside the NAT.
|
||||
GetExternalAddress() (addr net.IP, err error)
|
||||
// Add a port mapping for protocol ("udp" or "tcp") from externalport to
|
||||
// internal port with description lasting for timeout.
|
||||
AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error)
|
||||
// Remove a previously added port mapping from externalport to
|
||||
// internal port.
|
||||
DeletePortMapping(protocol string, externalPort, internalPort int) (err error)
|
||||
}
|
||||
|
||||
type upnpNAT struct {
|
||||
serviceURL string
|
||||
ourIP string
|
||||
}
|
||||
|
||||
// Discover searches the local network for a UPnP router returning a NAT
|
||||
// for the network if so, nil if not.
|
||||
func Discover() (nat NAT, err error) {
|
||||
ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
conn, err := net.ListenPacket("udp4", ":0")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
socket := conn.(*net.UDPConn)
|
||||
defer socket.Close()
|
||||
|
||||
err = socket.SetDeadline(time.Now().Add(3 * time.Second))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
st := "ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n"
|
||||
buf := bytes.NewBufferString(
|
||||
"M-SEARCH * HTTP/1.1\r\n" +
|
||||
"HOST: 239.255.255.250:1900\r\n" +
|
||||
st +
|
||||
"MAN: \"ssdp:discover\"\r\n" +
|
||||
"MX: 2\r\n\r\n")
|
||||
message := buf.Bytes()
|
||||
answerBytes := make([]byte, 1024)
|
||||
for i := 0; i < 3; i++ {
|
||||
_, err = socket.WriteToUDP(message, ssdp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var n int
|
||||
n, _, err = socket.ReadFromUDP(answerBytes)
|
||||
if err != nil {
|
||||
continue
|
||||
// socket.Close()
|
||||
// return
|
||||
}
|
||||
answer := string(answerBytes[0:n])
|
||||
if strings.Index(answer, "\r\n"+st) < 0 {
|
||||
continue
|
||||
}
|
||||
// HTTP header field names are case-insensitive.
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
||||
locString := "\r\nlocation: "
|
||||
answer = strings.ToLower(answer)
|
||||
locIndex := strings.Index(answer, locString)
|
||||
if locIndex < 0 {
|
||||
continue
|
||||
}
|
||||
loc := answer[locIndex+len(locString):]
|
||||
endIndex := strings.Index(loc, "\r\n")
|
||||
if endIndex < 0 {
|
||||
continue
|
||||
}
|
||||
locURL := loc[0:endIndex]
|
||||
var serviceURL string
|
||||
serviceURL, err = getServiceURL(locURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var ourIP string
|
||||
ourIP, err = getOurIP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP}
|
||||
return
|
||||
}
|
||||
err = errors.New("UPnP port discovery failed")
|
||||
return
|
||||
}
|
||||
|
||||
// service represents the Service type in an UPnP xml description.
|
||||
// Only the parts we care about are present and thus the xml may have more
|
||||
// fields than present in the structure.
|
||||
type service struct {
|
||||
ServiceType string `xml:"serviceType"`
|
||||
ControlURL string `xml:"controlURL"`
|
||||
}
|
||||
|
||||
// deviceList represents the deviceList type in an UPnP xml description.
|
||||
// Only the parts we care about are present and thus the xml may have more
|
||||
// fields than present in the structure.
|
||||
type deviceList struct {
|
||||
XMLName xml.Name `xml:"deviceList"`
|
||||
Device []device `xml:"device"`
|
||||
}
|
||||
|
||||
// serviceList represents the serviceList type in an UPnP xml description.
|
||||
// Only the parts we care about are present and thus the xml may have more
|
||||
// fields than present in the structure.
|
||||
type serviceList struct {
|
||||
XMLName xml.Name `xml:"serviceList"`
|
||||
Service []service `xml:"service"`
|
||||
}
|
||||
|
||||
// device represents the device type in an UPnP xml description.
|
||||
// Only the parts we care about are present and thus the xml may have more
|
||||
// fields than present in the structure.
|
||||
type device struct {
|
||||
XMLName xml.Name `xml:"device"`
|
||||
DeviceType string `xml:"deviceType"`
|
||||
DeviceList deviceList `xml:"deviceList"`
|
||||
ServiceList serviceList `xml:"serviceList"`
|
||||
}
|
||||
|
||||
// specVersion represents the specVersion in a UPnP xml description.
|
||||
// Only the parts we care about are present and thus the xml may have more
|
||||
// fields than present in the structure.
|
||||
type specVersion struct {
|
||||
XMLName xml.Name `xml:"specVersion"`
|
||||
Major int `xml:"major"`
|
||||
Minor int `xml:"minor"`
|
||||
}
|
||||
|
||||
// root represents the Root document for a UPnP xml description.
|
||||
// Only the parts we care about are present and thus the xml may have more
|
||||
// fields than present in the structure.
|
||||
type root struct {
|
||||
XMLName xml.Name `xml:"root"`
|
||||
SpecVersion specVersion
|
||||
Device device
|
||||
}
|
||||
|
||||
// getChildDevice searches the children of device for a device with the given
|
||||
// type.
|
||||
func getChildDevice(d *device, deviceType string) *device {
|
||||
for i := range d.DeviceList.Device {
|
||||
if d.DeviceList.Device[i].DeviceType == deviceType {
|
||||
return &d.DeviceList.Device[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getChildDevice searches the service list of device for a service with the
|
||||
// given type.
|
||||
func getChildService(d *device, serviceType string) *service {
|
||||
for i := range d.ServiceList.Service {
|
||||
if d.ServiceList.Service[i].ServiceType == serviceType {
|
||||
return &d.ServiceList.Service[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getOurIP returns a best guess at what the local IP is.
|
||||
func getOurIP() (ip string, err error) {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return net.LookupCNAME(hostname)
|
||||
}
|
||||
|
||||
// getServiceURL parses the xml description at the given root url to find the
|
||||
// url for the WANIPConnection service to be used for port forwarding.
|
||||
func getServiceURL(rootURL string) (url string, err error) {
|
||||
r, err := http.Get(rootURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
if r.StatusCode >= 400 {
|
||||
err = errors.New(string(r.StatusCode))
|
||||
return
|
||||
}
|
||||
var root root
|
||||
err = xml.NewDecoder(r.Body).Decode(&root)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
a := &root.Device
|
||||
if a.DeviceType != "urn:schemas-upnp-org:device:InternetGatewayDevice:1" {
|
||||
err = errors.New("no InternetGatewayDevice")
|
||||
return
|
||||
}
|
||||
b := getChildDevice(a, "urn:schemas-upnp-org:device:WANDevice:1")
|
||||
if b == nil {
|
||||
err = errors.New("no WANDevice")
|
||||
return
|
||||
}
|
||||
c := getChildDevice(b, "urn:schemas-upnp-org:device:WANConnectionDevice:1")
|
||||
if c == nil {
|
||||
err = errors.New("no WANConnectionDevice")
|
||||
return
|
||||
}
|
||||
d := getChildService(c, "urn:schemas-upnp-org:service:WANIPConnection:1")
|
||||
if d == nil {
|
||||
err = errors.New("no WANIPConnection")
|
||||
return
|
||||
}
|
||||
url = combineURL(rootURL, d.ControlURL)
|
||||
return
|
||||
}
|
||||
|
||||
// combineURL appends subURL onto rootURL.
|
||||
func combineURL(rootURL, subURL string) string {
|
||||
protocolEnd := "://"
|
||||
protoEndIndex := strings.Index(rootURL, protocolEnd)
|
||||
a := rootURL[protoEndIndex+len(protocolEnd):]
|
||||
rootIndex := strings.Index(a, "/")
|
||||
return rootURL[0:protoEndIndex+len(protocolEnd)+rootIndex] + subURL
|
||||
}
|
||||
|
||||
// soapBody represents the <s:Body> element in a SOAP reply.
|
||||
// fields we don't care about are elided.
|
||||
type soapBody struct {
|
||||
XMLName xml.Name `xml:"Body"`
|
||||
Data []byte `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// soapEnvelope represents the <s:Envelope> element in a SOAP reply.
|
||||
// fields we don't care about are elided.
|
||||
type soapEnvelope struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body soapBody `xml:"Body"`
|
||||
}
|
||||
|
||||
// soapRequests performs a soap request with the given parameters and returns
|
||||
// the xml replied stripped of the soap headers. in the case that the request is
|
||||
// unsuccessful the an error is returned.
|
||||
func soapRequest(url, function, message string) (replyXML []byte, err error) {
|
||||
fullMessage := "<?xml version=\"1.0\" ?>" +
|
||||
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" +
|
||||
"<s:Body>" + message + "</s:Body></s:Envelope>"
|
||||
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(fullMessage))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"")
|
||||
req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3")
|
||||
//req.Header.Set("Transfer-Encoding", "chunked")
|
||||
req.Header.Set("SOAPAction", "\"urn:schemas-upnp-org:service:WANIPConnection:1#"+function+"\"")
|
||||
req.Header.Set("Connection", "Close")
|
||||
req.Header.Set("Cache-Control", "no-cache")
|
||||
req.Header.Set("Pragma", "no-cache")
|
||||
|
||||
r, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.Body != nil {
|
||||
defer r.Body.Close()
|
||||
}
|
||||
|
||||
if r.StatusCode >= 400 {
|
||||
// log.Stderr(function, r.StatusCode)
|
||||
err = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function)
|
||||
r = nil
|
||||
return
|
||||
}
|
||||
var reply soapEnvelope
|
||||
err = xml.NewDecoder(r.Body).Decode(&reply)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return reply.Body.Data, nil
|
||||
}
|
||||
|
||||
// getExternalIPAddressResponse represents the XML response to a
|
||||
// GetExternalIPAddress SOAP request.
|
||||
type getExternalIPAddressResponse struct {
|
||||
XMLName xml.Name `xml:"GetExternalIPAddressResponse"`
|
||||
ExternalIPAddress string `xml:"NewExternalIPAddress"`
|
||||
}
|
||||
|
||||
// GetExternalAddress implements the NAT interface by fetching the external IP
|
||||
// from the UPnP router.
|
||||
func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) {
|
||||
message := "<u:GetExternalIPAddress xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\"/>\r\n"
|
||||
response, err := soapRequest(n.serviceURL, "GetExternalIPAddress", message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reply getExternalIPAddressResponse
|
||||
err = xml.Unmarshal(response, &reply)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr = net.ParseIP(reply.ExternalIPAddress)
|
||||
if addr == nil {
|
||||
return nil, errors.New("unable to parse ip address")
|
||||
}
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// AddPortMapping implements the NAT interface by setting up a port forwarding
|
||||
// from the UPnP router to the local machine with the given ports and protocol.
|
||||
func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) {
|
||||
// A single concatenation would break ARM compilation.
|
||||
message := "<u:AddPortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n" +
|
||||
"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort)
|
||||
message += "</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>"
|
||||
message += "<NewInternalPort>" + strconv.Itoa(internalPort) + "</NewInternalPort>" +
|
||||
"<NewInternalClient>" + n.ourIP + "</NewInternalClient>" +
|
||||
"<NewEnabled>1</NewEnabled><NewPortMappingDescription>"
|
||||
message += description +
|
||||
"</NewPortMappingDescription><NewLeaseDuration>" + strconv.Itoa(timeout) +
|
||||
"</NewLeaseDuration></u:AddPortMapping>"
|
||||
|
||||
response, err := soapRequest(n.serviceURL, "AddPortMapping", message)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: check response to see if the port was forwarded
|
||||
// If the port was not wildcard we don't get an reply with the port in
|
||||
// it. Not sure about wildcard yet. miniupnpc just checks for error
|
||||
// codes here.
|
||||
mappedExternalPort = externalPort
|
||||
_ = response
|
||||
return
|
||||
}
|
||||
|
||||
// AddPortMapping implements the NAT interface by removing up a port forwarding
|
||||
// from the UPnP router to the local machine with the given ports and.
|
||||
func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) {
|
||||
|
||||
message := "<u:DeletePortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n" +
|
||||
"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) +
|
||||
"</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>" +
|
||||
"</u:DeletePortMapping>"
|
||||
|
||||
response, err := soapRequest(n.serviceURL, "DeletePortMapping", message)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: check response to see if the port was deleted
|
||||
// log.Println(message, response)
|
||||
_ = response
|
||||
return
|
||||
}
|
||||
@@ -1,266 +1,134 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/conformal/btcchain"
|
||||
"github.com/conformal/btcd/limits"
|
||||
"github.com/conformal/btcdb"
|
||||
_ "github.com/conformal/btcdb/ldb"
|
||||
_ "github.com/conformal/btcdb/sqlite3"
|
||||
"github.com/conformal/btcutil"
|
||||
"github.com/conformal/btcwire"
|
||||
"github.com/conformal/go-flags"
|
||||
"github.com/conformal/seelog"
|
||||
"io"
|
||||
"github.com/conformal/btclog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
)
|
||||
|
||||
type ShaHash btcwire.ShaHash
|
||||
|
||||
type config struct {
|
||||
DataDir string `short:"b" long:"datadir" description:"Directory to store data"`
|
||||
DbType string `long:"dbtype" description:"Database backend"`
|
||||
TestNet3 bool `long:"testnet" description:"Use the test network"`
|
||||
Progress bool `short:"p" description:"show progress"`
|
||||
InFile string `short:"i" long:"infile" description:"File containing the block(s)" required:"true"`
|
||||
}
|
||||
|
||||
var log seelog.LoggerInterface
|
||||
|
||||
const (
|
||||
ArgSha = iota
|
||||
ArgHeight
|
||||
// blockDbNamePrefix is the prefix for the btcd block database.
|
||||
blockDbNamePrefix = "blocks"
|
||||
)
|
||||
|
||||
type bufQueue struct {
|
||||
height int64
|
||||
blkbuf []byte
|
||||
}
|
||||
var (
|
||||
cfg *config
|
||||
log btclog.Logger
|
||||
)
|
||||
|
||||
type blkQueue struct {
|
||||
complete chan bool
|
||||
height int64
|
||||
blk *btcutil.Block
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := config{
|
||||
DbType: "leveldb",
|
||||
DataDir: filepath.Join(btcdHomeDir(), "data"),
|
||||
}
|
||||
parser := flags.NewParser(&cfg, flags.Default)
|
||||
_, err := parser.Parse()
|
||||
if err != nil {
|
||||
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
||||
parser.WriteHelp(os.Stderr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
log, err = seelog.LoggerFromWriterWithMinLevel(os.Stdout,
|
||||
seelog.InfoLvl)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to create logger: %v", err)
|
||||
return
|
||||
}
|
||||
defer log.Flush()
|
||||
btcdb.UseLogger(log)
|
||||
|
||||
var testnet string
|
||||
if cfg.TestNet3 {
|
||||
testnet = "testnet"
|
||||
} else {
|
||||
testnet = "mainnet"
|
||||
}
|
||||
|
||||
cfg.DataDir = filepath.Join(cfg.DataDir, testnet)
|
||||
|
||||
err = os.MkdirAll(cfg.DataDir, 0700)
|
||||
if err != nil {
|
||||
fmt.Printf("unable to create db repo area %v, %v", cfg.DataDir, err)
|
||||
}
|
||||
|
||||
blockDbNamePrefix := "blocks"
|
||||
// loadBlockDB opens the block database and returns a handle to it.
|
||||
func loadBlockDB() (btcdb.Db, error) {
|
||||
// The database name is based on the database type.
|
||||
dbName := blockDbNamePrefix + "_" + cfg.DbType
|
||||
if cfg.DbType == "sqlite" {
|
||||
dbName = dbName + ".db"
|
||||
}
|
||||
dbPath := filepath.Join(cfg.DataDir, dbName)
|
||||
|
||||
log.Infof("loading db")
|
||||
db, err := btcdb.CreateDB(cfg.DbType, dbPath)
|
||||
log.Infof("Loading block database from '%s'", dbPath)
|
||||
db, err := btcdb.OpenDB(cfg.DbType, dbPath)
|
||||
if err != nil {
|
||||
log.Warnf("db open failed: %v", err)
|
||||
return
|
||||
// Return the error if it's not because the database doesn't
|
||||
// exist.
|
||||
if err != btcdb.DbDoesNotExist {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the db if it does not exist.
|
||||
err = os.MkdirAll(cfg.DataDir, 0700)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db, err = btcdb.CreateDB(cfg.DbType, dbPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the latest block height from the database.
|
||||
_, height, err := db.NewestSha()
|
||||
if err != nil {
|
||||
db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infof("Block database loaded with block height %d", height)
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// realMain is the real main function for the utility. It is necessary to work
|
||||
// around the fact that deferred functions do not run when os.Exit() is called.
|
||||
func realMain() error {
|
||||
// Load configuration and parse command line.
|
||||
tcfg, _, err := loadConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg = tcfg
|
||||
|
||||
// Setup logging.
|
||||
backendLogger := btclog.NewDefaultBackendLogger()
|
||||
defer backendLogger.Flush()
|
||||
log = btclog.NewSubsystemLogger(backendLogger, "")
|
||||
btcdb.UseLogger(btclog.NewSubsystemLogger(backendLogger, "BCDB: "))
|
||||
btcchain.UseLogger(btclog.NewSubsystemLogger(backendLogger, "CHAN: "))
|
||||
|
||||
// Load the block database.
|
||||
db, err := loadBlockDB()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to load database: %v", err)
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
log.Infof("db created")
|
||||
|
||||
var fi io.ReadCloser
|
||||
|
||||
fi, err = os.Open(cfg.InFile)
|
||||
fi, err := os.Open(cfg.InFile)
|
||||
if err != nil {
|
||||
log.Warnf("failed to open file %v, err %v", cfg.InFile, err)
|
||||
log.Errorf("Failed to open file %v: %v", cfg.InFile, err)
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := fi.Close(); err != nil {
|
||||
log.Warn("failed to close file %v %v", cfg.InFile, err)
|
||||
}
|
||||
}()
|
||||
defer fi.Close()
|
||||
|
||||
bufqueue := make(chan *bufQueue, 2)
|
||||
blkqueue := make(chan *blkQueue, 2)
|
||||
// Create a block importer for the database and input file and start it.
|
||||
// The done channel returned from start will contain an error if
|
||||
// anything went wrong.
|
||||
importer := newBlockImporter(db, fi)
|
||||
|
||||
for i := 0; i < runtime.NumCPU(); i++ {
|
||||
go processBuf(i, bufqueue, blkqueue)
|
||||
// Perform the import asynchronously. This allows blocks to be
|
||||
// processed and read in parallel. The results channel returned from
|
||||
// Import contains the statistics about the import including an error
|
||||
// if something went wrong.
|
||||
log.Info("Starting import")
|
||||
resultsChan := importer.Import()
|
||||
results := <-resultsChan
|
||||
if results.err != nil {
|
||||
log.Errorf("%v", results.err)
|
||||
return results.err
|
||||
}
|
||||
go processBuf(0, bufqueue, blkqueue)
|
||||
|
||||
go readBlocks(fi, bufqueue)
|
||||
|
||||
var eheight int64
|
||||
doneMap := map[int64]*blkQueue{}
|
||||
for {
|
||||
|
||||
select {
|
||||
case blkM := <-blkqueue:
|
||||
doneMap[blkM.height] = blkM
|
||||
|
||||
for {
|
||||
if blkP, ok := doneMap[eheight]; ok {
|
||||
delete(doneMap, eheight)
|
||||
blkP.complete <- true
|
||||
db.InsertBlock(blkP.blk)
|
||||
|
||||
if cfg.Progress && eheight%int64(1) == 0 {
|
||||
log.Infof("Processing block %v", eheight)
|
||||
}
|
||||
eheight++
|
||||
|
||||
if eheight%2000 == 0 {
|
||||
f, err := os.Create(fmt.Sprintf("profile.%d", eheight))
|
||||
if err == nil {
|
||||
pprof.WriteHeapProfile(f)
|
||||
f.Close()
|
||||
} else {
|
||||
log.Warnf("profile failed %v", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Infof("Processed a total of %d blocks (%d imported, %d already "+
|
||||
"known)", results.blocksProcessed, results.blocksImported,
|
||||
results.blocksProcessed-results.blocksImported)
|
||||
return nil
|
||||
}
|
||||
|
||||
func processBuf(idx int, bufqueue chan *bufQueue, blkqueue chan *blkQueue) {
|
||||
complete := make(chan bool)
|
||||
for {
|
||||
select {
|
||||
case bq := <-bufqueue:
|
||||
var blkmsg blkQueue
|
||||
|
||||
blkmsg.height = bq.height
|
||||
|
||||
if len(bq.blkbuf) == 0 {
|
||||
// we are done
|
||||
blkqueue <- &blkmsg
|
||||
}
|
||||
|
||||
blk, err := btcutil.NewBlockFromBytes(bq.blkbuf)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to parse block %v", bq.height)
|
||||
return
|
||||
}
|
||||
blkmsg.blk = blk
|
||||
blkmsg.complete = complete
|
||||
blkqueue <- &blkmsg
|
||||
select {
|
||||
case <-complete:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readBlocks(fi io.Reader, bufqueue chan *bufQueue) {
|
||||
var height int64
|
||||
for {
|
||||
var net, blen uint32
|
||||
|
||||
var bufM bufQueue
|
||||
bufM.height = height
|
||||
|
||||
// generate and write header values
|
||||
err := binary.Read(fi, binary.LittleEndian, &net)
|
||||
if err != nil {
|
||||
break
|
||||
bufqueue <- &bufM
|
||||
}
|
||||
if net != uint32(btcwire.MainNet) {
|
||||
fmt.Printf("network mismatch %v %v",
|
||||
net, uint32(btcwire.MainNet))
|
||||
|
||||
bufqueue <- &bufM
|
||||
}
|
||||
err = binary.Read(fi, binary.LittleEndian, &blen)
|
||||
if err != nil {
|
||||
bufqueue <- &bufM
|
||||
}
|
||||
blkbuf := make([]byte, blen)
|
||||
err = binary.Read(fi, binary.LittleEndian, blkbuf)
|
||||
bufM.blkbuf = blkbuf
|
||||
bufqueue <- &bufM
|
||||
height++
|
||||
}
|
||||
}
|
||||
|
||||
// newLogger creates a new seelog logger using the provided logging level and
|
||||
// log message prefix.
|
||||
func newLogger(level string, prefix string) seelog.LoggerInterface {
|
||||
fmtstring := `
|
||||
<seelog type="adaptive" mininterval="2000000" maxinterval="100000000"
|
||||
critmsgcount="500" minlevel="%s">
|
||||
<outputs formatid="all">
|
||||
<console/>
|
||||
</outputs>
|
||||
<formats>
|
||||
<format id="all" format="[%%Time %%Date] [%%LEV] [%s] %%Msg%%n" />
|
||||
</formats>
|
||||
</seelog>`
|
||||
config := fmt.Sprintf(fmtstring, level, prefix)
|
||||
|
||||
logger, err := seelog.LoggerFromConfigAsString(config)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to create logger: %v", err)
|
||||
func main() {
|
||||
// Use all processor cores and up some limits.
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
if err := limits.SetLimits(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return logger
|
||||
}
|
||||
|
||||
// btcdHomeDir returns an OS appropriate home directory for btcd.
|
||||
func btcdHomeDir() string {
|
||||
// Search for Windows APPDATA first. This won't exist on POSIX OSes.
|
||||
appData := os.Getenv("APPDATA")
|
||||
if appData != "" {
|
||||
return filepath.Join(appData, "btcd")
|
||||
// Work around defer not working after os.Exit()
|
||||
if err := realMain(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Fall back to standard HOME directory that works for most POSIX OSes.
|
||||
home := os.Getenv("HOME")
|
||||
if home != "" {
|
||||
return filepath.Join(home, ".btcd")
|
||||
}
|
||||
|
||||
// In the worst case, use the current directory.
|
||||
return "."
|
||||
}
|
||||
|
||||
125
util/addblock/config.go
Normal file
125
util/addblock/config.go
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/conformal/btcdb"
|
||||
_ "github.com/conformal/btcdb/ldb"
|
||||
"github.com/conformal/btcutil"
|
||||
"github.com/conformal/btcwire"
|
||||
"github.com/conformal/go-flags"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultDbType = "leveldb"
|
||||
defaultDataFile = "bootstrap.dat"
|
||||
defaultProgress = 10000
|
||||
)
|
||||
|
||||
var (
|
||||
btcdHomeDir = btcutil.AppDataDir("btcd", false)
|
||||
defaultDataDir = filepath.Join(btcdHomeDir, "data")
|
||||
knownDbTypes = btcdb.SupportedDBs()
|
||||
activeNetwork = btcwire.MainNet
|
||||
)
|
||||
|
||||
// config defines the configuration options for findcheckpoint.
|
||||
//
|
||||
// See loadConfig for details on the configuration load process.
|
||||
type config struct {
|
||||
DataDir string `short:"b" long:"datadir" description:"Location of the btcd data directory"`
|
||||
DbType string `long:"dbtype" description:"Database backend to use for the Block Chain"`
|
||||
TestNet3 bool `long:"testnet" description:"Use the test network"`
|
||||
InFile string `short:"i" long:"infile" description:"File containing the block(s)"`
|
||||
Progress int `short:"p" long:"progress" description:"Show a progress message every time this number of blocks is processed -- Use 0 to disable progress announcements"`
|
||||
}
|
||||
|
||||
// filesExists reports whether the named file or directory exists.
|
||||
func fileExists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// validDbType returns whether or not dbType is a supported database type.
|
||||
func validDbType(dbType string) bool {
|
||||
for _, knownType := range knownDbTypes {
|
||||
if dbType == knownType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// netName returns a human-readable name for the passed bitcoin network.
|
||||
func netName(btcnet btcwire.BitcoinNet) string {
|
||||
net := "mainnet"
|
||||
if btcnet == btcwire.TestNet3 {
|
||||
net = "testnet"
|
||||
}
|
||||
return net
|
||||
}
|
||||
|
||||
// loadConfig initializes and parses the config using command line options.
|
||||
func loadConfig() (*config, []string, error) {
|
||||
// Default config.
|
||||
cfg := config{
|
||||
DataDir: defaultDataDir,
|
||||
DbType: defaultDbType,
|
||||
InFile: defaultDataFile,
|
||||
Progress: defaultProgress,
|
||||
}
|
||||
|
||||
// Parse command line options.
|
||||
parser := flags.NewParser(&cfg, flags.Default)
|
||||
remainingArgs, err := parser.Parse()
|
||||
if err != nil {
|
||||
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
||||
parser.WriteHelp(os.Stderr)
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Choose the active network based on the flags.
|
||||
if cfg.TestNet3 {
|
||||
activeNetwork = btcwire.TestNet3
|
||||
}
|
||||
|
||||
// Validate database type.
|
||||
if !validDbType(cfg.DbType) {
|
||||
str := "%s: The specified database type [%v] is invalid -- " +
|
||||
"supported types %v"
|
||||
err := fmt.Errorf(str, "loadConfig", cfg.DbType, knownDbTypes)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
parser.WriteHelp(os.Stderr)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Append the network type to the data directory so it is "namespaced"
|
||||
// per network. In addition to the block database, there are other
|
||||
// pieces of data that are saved to disk such as address manager state.
|
||||
// All data is specific to a network, so namespacing the data directory
|
||||
// means each individual piece of serialized data does not have to
|
||||
// worry about changing names per network and such.
|
||||
cfg.DataDir = filepath.Join(cfg.DataDir, netName(activeNetwork))
|
||||
|
||||
// Ensure the specified block file exists.
|
||||
if !fileExists(cfg.InFile) {
|
||||
str := "%s: The specified block file [%v] does not exist"
|
||||
err := fmt.Errorf(str, "loadConfig", cfg.InFile)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
parser.WriteHelp(os.Stderr)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return &cfg, remainingArgs, nil
|
||||
}
|
||||
251
util/addblock/import.go
Normal file
251
util/addblock/import.go
Normal file
@@ -0,0 +1,251 @@
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/conformal/btcchain"
|
||||
"github.com/conformal/btcdb"
|
||||
_ "github.com/conformal/btcdb/ldb"
|
||||
"github.com/conformal/btcutil"
|
||||
"github.com/conformal/btcwire"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var zeroHash = btcwire.ShaHash{}
|
||||
|
||||
// importResults houses the stats and result as an import operation.
|
||||
type importResults struct {
|
||||
blocksProcessed int64
|
||||
blocksImported int64
|
||||
err error
|
||||
}
|
||||
|
||||
// blockImporter houses information about an ongoing import from a block data
|
||||
// file to the block database.
|
||||
type blockImporter struct {
|
||||
db btcdb.Db
|
||||
chain *btcchain.BlockChain
|
||||
r io.ReadSeeker
|
||||
processQueue chan []byte
|
||||
doneChan chan bool
|
||||
errChan chan error
|
||||
quit chan bool
|
||||
wg sync.WaitGroup
|
||||
blocksProcessed int64
|
||||
blocksImported int64
|
||||
}
|
||||
|
||||
// readBlock reads the next block from the input file.
|
||||
func (bi *blockImporter) readBlock() ([]byte, error) {
|
||||
// The block file format is:
|
||||
// <network> <block length> <serialized block>
|
||||
var net uint32
|
||||
err := binary.Read(bi.r, binary.LittleEndian, &net)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// No block and no error means there are no more blocks to read.
|
||||
return nil, nil
|
||||
}
|
||||
if net != uint32(activeNetwork) {
|
||||
return nil, fmt.Errorf("network mismatch -- got %x, want %x",
|
||||
net, uint32(activeNetwork))
|
||||
}
|
||||
|
||||
// Read the block length and ensure it is sane.
|
||||
var blockLen uint32
|
||||
if err := binary.Read(bi.r, binary.LittleEndian, &blockLen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if blockLen > btcwire.MaxBlockPayload {
|
||||
return nil, fmt.Errorf("block payload of %d bytes is larger "+
|
||||
"than the max allowed %d bytes", blockLen,
|
||||
btcwire.MaxBlockPayload)
|
||||
}
|
||||
|
||||
serializedBlock := make([]byte, blockLen)
|
||||
if _, err := io.ReadFull(bi.r, serializedBlock); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return serializedBlock, nil
|
||||
}
|
||||
|
||||
// processBlock potentially imports the block into the database. It first
|
||||
// deserializes the raw block while checking for errors. Already known blocks
|
||||
// are skipped and orphan blocks are considered errors. Finally, it runs the
|
||||
// block through the chain rules to ensure it follows all rules and matches
|
||||
// up to the known checkpoint. Returns whether the block was imported along
|
||||
// with any potential errors.
|
||||
func (bi *blockImporter) processBlock(serializedBlock []byte) (bool, error) {
|
||||
// Deserialize the block which includes checks for malformed blocks.
|
||||
block, err := btcutil.NewBlockFromBytes(serializedBlock)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
blockSha, err := block.Sha()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Skip blocks that already exist.
|
||||
if bi.db.ExistsSha(blockSha) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Don't bother trying to process orphans.
|
||||
prevHash := &block.MsgBlock().Header.PrevBlock
|
||||
if !prevHash.IsEqual(&zeroHash) && !bi.db.ExistsSha(prevHash) {
|
||||
return false, fmt.Errorf("import file contains block %v which "+
|
||||
"does not link to the available block chain", blockSha)
|
||||
}
|
||||
|
||||
// Ensure the blocks follows all of the chain rules and match up to the
|
||||
// known checkpoints.
|
||||
if err := bi.chain.ProcessBlock(block, true); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// readHandler is the main handler for reading blocks from the import file.
|
||||
// This allows block processing to take place in parallel with block reads.
|
||||
// It must be run as a goroutine.
|
||||
func (bi *blockImporter) readHandler() {
|
||||
out:
|
||||
for {
|
||||
// Read the next block from the file and if anything goes wrong
|
||||
// notify the status handler with the error and bail.
|
||||
serializedBlock, err := bi.readBlock()
|
||||
if err != nil {
|
||||
bi.errChan <- fmt.Errorf("Error reading from input "+
|
||||
"file: %v", err.Error())
|
||||
break out
|
||||
}
|
||||
|
||||
// A nil block with no error means we're done.
|
||||
if serializedBlock == nil {
|
||||
break out
|
||||
}
|
||||
|
||||
// Send the block or quit if we've been signalled to exit by
|
||||
// the status handler due to an error elsewhere.
|
||||
select {
|
||||
case bi.processQueue <- serializedBlock:
|
||||
case <-bi.quit:
|
||||
break out
|
||||
}
|
||||
}
|
||||
|
||||
// Close the processing channel to signal no more blocks are coming.
|
||||
close(bi.processQueue)
|
||||
bi.wg.Done()
|
||||
}
|
||||
|
||||
// processHandler is the main handler for processing blocks. This allows block
|
||||
// processing to take place in parallel with block reads from the import file.
|
||||
// It must be run as a goroutine.
|
||||
func (bi *blockImporter) processHandler() {
|
||||
out:
|
||||
for {
|
||||
select {
|
||||
case serializedBlock, ok := <-bi.processQueue:
|
||||
// We're done when the channel is closed.
|
||||
if !ok {
|
||||
break out
|
||||
}
|
||||
|
||||
bi.blocksProcessed++
|
||||
imported, err := bi.processBlock(serializedBlock)
|
||||
if err != nil {
|
||||
bi.errChan <- err
|
||||
break out
|
||||
}
|
||||
|
||||
if imported {
|
||||
bi.blocksImported++
|
||||
}
|
||||
|
||||
if cfg.Progress != 0 && bi.blocksProcessed > 0 &&
|
||||
bi.blocksProcessed%int64(cfg.Progress) == 0 {
|
||||
log.Infof("Processed %d blocks", bi.blocksProcessed)
|
||||
}
|
||||
|
||||
case <-bi.quit:
|
||||
break out
|
||||
}
|
||||
}
|
||||
bi.wg.Done()
|
||||
}
|
||||
|
||||
// statusHandler waits for updates from the import operation and notifies
|
||||
// the passed doneChan with the results of the import. It also causes all
|
||||
// goroutines to exit if an error is reported from any of them.
|
||||
func (bi *blockImporter) statusHandler(resultsChan chan *importResults) {
|
||||
select {
|
||||
// An error from either of the goroutines means we're done so signal
|
||||
// caller with the error and signal all goroutines to quit.
|
||||
case err := <-bi.errChan:
|
||||
resultsChan <- &importResults{
|
||||
blocksProcessed: bi.blocksProcessed,
|
||||
blocksImported: bi.blocksImported,
|
||||
err: err,
|
||||
}
|
||||
close(bi.quit)
|
||||
|
||||
// The import finished normally.
|
||||
case <-bi.doneChan:
|
||||
resultsChan <- &importResults{
|
||||
blocksProcessed: bi.blocksProcessed,
|
||||
blocksImported: bi.blocksImported,
|
||||
err: nil,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import is the core function which handles importing the blocks from the file
|
||||
// associated with the block importer to the database. It returns a channel
|
||||
// on which the results will be returned when the operation has completed.
|
||||
func (bi *blockImporter) Import() chan *importResults {
|
||||
// Start up the read and process handling goroutines. This setup allows
|
||||
// blocks to be read from disk in parallel while being processed.
|
||||
bi.wg.Add(2)
|
||||
go bi.readHandler()
|
||||
go bi.processHandler()
|
||||
|
||||
// Wait for the import to finish in a separate goroutine and signal
|
||||
// the status handler when done.
|
||||
go func() {
|
||||
bi.wg.Wait()
|
||||
bi.doneChan <- true
|
||||
}()
|
||||
|
||||
// Start the status handler and return the result the channel that it
|
||||
// will send the results on when the import is done.
|
||||
resultChan := make(chan *importResults)
|
||||
go bi.statusHandler(resultChan)
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// newBlockImporter returns a new importer for the provided file reader seeker
|
||||
// and database.
|
||||
func newBlockImporter(db btcdb.Db, r io.ReadSeeker) *blockImporter {
|
||||
return &blockImporter{
|
||||
db: db,
|
||||
r: r,
|
||||
processQueue: make(chan []byte, 2),
|
||||
doneChan: make(chan bool),
|
||||
errChan: make(chan error),
|
||||
quit: make(chan bool),
|
||||
chain: btcchain.New(db, activeNetwork, nil),
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/conformal/btcjson"
|
||||
"github.com/conformal/btcutil"
|
||||
"github.com/conformal/go-flags"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
Help bool `short:"h" long:"help" description:"Help"`
|
||||
RpcUser string `short:"u" description:"RPC username"`
|
||||
RpcPassword string `short:"P" long:"rpcpass" description:"RPC password"`
|
||||
RpcServer string `short:"s" long:"rpcserver" description:"RPC server to connect to"`
|
||||
}
|
||||
|
||||
// conversionHandler is a handler that is used to convert parameters from the
|
||||
// command line to a specific type. This is needed since the btcjson API
|
||||
// expects certain types for various parameters.
|
||||
@@ -34,9 +31,11 @@ type handlerData struct {
|
||||
optionalArgs int
|
||||
displayHandler displayHandler
|
||||
conversionHandlers []conversionHandler
|
||||
makeCmd func([]interface{}) (btcjson.Cmd, error)
|
||||
usage string
|
||||
}
|
||||
|
||||
// Errors used in the various handlers.
|
||||
var (
|
||||
ErrNoData = errors.New("No data returned.")
|
||||
ErrNoDisplayHandler = errors.New("No display handler specified.")
|
||||
@@ -46,19 +45,78 @@ var (
|
||||
// commandHandlers is a map of commands and associated handler data that is used
|
||||
// to validate correctness and perform the command.
|
||||
var commandHandlers = map[string]*handlerData{
|
||||
"addnode": &handlerData{2, 0, displaySpewDump, nil, "<ip> <add/remove/onetry>"},
|
||||
"decoderawtransaction": &handlerData{1, 0, displaySpewDump, nil, "<txhash>"},
|
||||
"getbestblockhash": &handlerData{0, 0, displayGeneric, nil, ""},
|
||||
"getblock": &handlerData{1, 0, displaySpewDump, nil, "<blockhash>"},
|
||||
"getblockcount": &handlerData{0, 0, displayFloat64, nil, ""},
|
||||
"getblockhash": &handlerData{1, 0, displayGeneric, []conversionHandler{toInt}, "<blocknumber>"},
|
||||
"getconnectioncount": &handlerData{0, 0, displayFloat64, nil, ""},
|
||||
"getdifficulty": &handlerData{0, 0, displayFloat64, nil, ""},
|
||||
"getgenerate": &handlerData{0, 0, displayGeneric, nil, ""},
|
||||
"getpeerinfo": &handlerData{0, 0, displaySpewDump, nil, ""},
|
||||
"getrawmempool": &handlerData{0, 0, displaySpewDump, nil, ""},
|
||||
"getrawtransaction": &handlerData{1, 1, displaySpewDump, []conversionHandler{nil, toInt}, "<txhash> [verbose=0]"},
|
||||
"stop": &handlerData{0, 0, displayGeneric, nil, ""},
|
||||
"addnode": {2, 0, displayJSONDump, nil, makeAddNode, "<ip> <add/remove/onetry>"},
|
||||
"createrawtransaction": {2, 0, displayGeneric, nil, makeCreateRawTransaction, "\"[{\"txid\":\"id\",\"vout\":n},...]\" \"{\"address\":amount,...}\""},
|
||||
"debuglevel": {1, 0, displayGeneric, nil, makeDebugLevel, "<levelspec>"},
|
||||
"decoderawtransaction": {1, 0, displayJSONDump, nil, makeDecodeRawTransaction, "<txhash>"},
|
||||
"decodescript": {1, 0, displayJSONDump, nil, makeDecodeScript, "<hex>"},
|
||||
"dumpprivkey": {1, 0, displayGeneric, nil, makeDumpPrivKey, "<bitcoinaddress>"},
|
||||
"getaccount": {1, 0, displayGeneric, nil, makeGetAccount, "<address>"},
|
||||
"getaccountaddress": {1, 0, displayGeneric, nil, makeGetAccountAddress, "<account>"},
|
||||
"getaddednodeinfo": {1, 1, displayJSONDump, []conversionHandler{toBool, nil}, makeGetAddedNodeInfo, "<dns> [node]"},
|
||||
"getaddressesbyaccount": {1, 0, displayJSONDump, nil, makeGetAddressesByAccount, "[account]"},
|
||||
"getbalance": {0, 2, displayGeneric, []conversionHandler{nil, toInt}, makeGetBalance, "[account] [minconf=1]"},
|
||||
"getbestblockhash": {0, 0, displayGeneric, nil, makeGetBestBlockHash, ""},
|
||||
"getblock": {1, 2, displayJSONDump, []conversionHandler{nil, toBool, toBool}, makeGetBlock, "<blockhash>"},
|
||||
"getblockcount": {0, 0, displayGeneric, nil, makeGetBlockCount, ""},
|
||||
"getblockhash": {1, 0, displayGeneric, []conversionHandler{toInt64}, makeGetBlockHash, "<blocknumber>"},
|
||||
"getblocktemplate": {0, 1, displayJSONDump, nil, makeGetBlockTemplate, "[jsonrequestobject]"},
|
||||
"getconnectioncount": {0, 0, displayGeneric, nil, makeGetConnectionCount, ""},
|
||||
"getdifficulty": {0, 0, displayFloat64, nil, makeGetDifficulty, ""},
|
||||
"getgenerate": {0, 0, displayGeneric, nil, makeGetGenerate, ""},
|
||||
"gethashespersec": {0, 0, displayGeneric, nil, makeGetHashesPerSec, ""},
|
||||
"getinfo": {0, 0, displayJSONDump, nil, makeGetInfo, ""},
|
||||
"getnetworkhashps": {0, 2, displayGeneric, []conversionHandler{toInt, toInt}, makeGetNetworkHashPS, "[blocks height]"},
|
||||
"getnettotals": {0, 0, displayJSONDump, nil, makeGetNetTotals, ""},
|
||||
"getnewaddress": {0, 1, displayGeneric, nil, makeGetNewAddress, "[account]"},
|
||||
"getpeerinfo": {0, 0, displayJSONDump, nil, makeGetPeerInfo, ""},
|
||||
"getrawchangeaddress": {0, 0, displayGeneric, nil, makeGetRawChangeAddress, ""},
|
||||
"getrawmempool": {0, 1, displayJSONDump, []conversionHandler{toBool}, makeGetRawMempool, "[verbose=false]"},
|
||||
"getrawtransaction": {1, 1, displayJSONDump, []conversionHandler{nil, toInt}, makeGetRawTransaction, "<txhash> [verbose=0]"},
|
||||
"getreceivedbyaccount": {1, 1, displayGeneric, []conversionHandler{nil, toInt}, makeGetReceivedByAccount, "<account> [minconf=1]"},
|
||||
"getreceivedbyaddress": {1, 1, displayGeneric, []conversionHandler{nil, toInt}, makeGetReceivedByAddress, "<address> [minconf=1]"},
|
||||
"gettransaction": {1, 1, displayJSONDump, nil, makeGetTransaction, "txid"},
|
||||
"gettxoutsetinfo": {0, 0, displayJSONDump, nil, makeGetTxOutSetInfo, ""},
|
||||
"getwork": {0, 1, displayJSONDump, nil, makeGetWork, "[data]"},
|
||||
"help": {0, 1, displayGeneric, nil, makeHelp, "[commandName]"},
|
||||
"importprivkey": {1, 2, displayGeneric, []conversionHandler{nil, nil, toBool}, makeImportPrivKey, "<wifprivkey> [label] [rescan=true]"},
|
||||
"keypoolrefill": {0, 1, displayGeneric, []conversionHandler{toInt}, makeKeyPoolRefill, "[newsize]"},
|
||||
"listaccounts": {0, 1, displayJSONDump, []conversionHandler{toInt}, makeListAccounts, "[minconf=1]"},
|
||||
"listaddressgroupings": {0, 0, displayJSONDump, nil, makeListAddressGroupings, ""},
|
||||
"listreceivedbyaccount": {0, 2, displayJSONDump, []conversionHandler{toInt, toBool}, makeListReceivedByAccount, "[minconf] [includeempty]"},
|
||||
"listreceivedbyaddress": {0, 2, displayJSONDump, []conversionHandler{toInt, toBool}, makeListReceivedByAddress, "[minconf] [includeempty]"},
|
||||
"listlockunspent": {0, 0, displayJSONDump, nil, makeListLockUnspent, ""},
|
||||
"listsinceblock": {0, 2, displayJSONDump, []conversionHandler{nil, toInt}, makeListSinceBlock, "[blockhash] [minconf=10]"},
|
||||
"listtransactions": {0, 3, displayJSONDump, []conversionHandler{nil, toInt, toInt}, makeListTransactions, "[account] [count=10] [from=0]"},
|
||||
"listunspent": {0, 3, displayJSONDump, []conversionHandler{toInt, toInt, nil}, makeListUnspent, "[minconf=1] [maxconf=9999999] [jsonaddressarray]"},
|
||||
"ping": {0, 0, displayGeneric, nil, makePing, ""},
|
||||
"sendfrom": {3, 3, displayGeneric, []conversionHandler{nil, nil, toSatoshi, toInt, nil, nil},
|
||||
makeSendFrom, "<account> <address> <amount> [minconf=1] [comment] [comment-to]"},
|
||||
"sendmany": {2, 2, displayGeneric, []conversionHandler{nil, nil, toInt, nil}, makeSendMany, "<account> <{\"address\":amount,...}> [minconf=1] [comment]"},
|
||||
"sendrawtransaction": {1, 0, displayGeneric, nil, makeSendRawTransaction, "<hextx>"},
|
||||
"sendtoaddress": {2, 2, displayGeneric, []conversionHandler{nil, toSatoshi, nil, nil}, makeSendToAddress, "<address> <amount> [comment] [comment-to]"},
|
||||
"settxfee": {1, 0, displayGeneric, []conversionHandler{toSatoshi}, makeSetTxFee, "<amount>"},
|
||||
"signmessage": {2, 2, displayGeneric, nil, makeSignMessage, "<address> <message>"},
|
||||
"stop": {0, 0, displayGeneric, nil, makeStop, ""},
|
||||
"submitblock": {1, 1, displayGeneric, nil, makeSubmitBlock, "<hexdata> [jsonparametersobject]"},
|
||||
"validateaddress": {1, 0, displayJSONDump, nil, makeValidateAddress, "<address>"},
|
||||
"verifychain": {0, 2, displayJSONDump, []conversionHandler{toInt, toInt}, makeVerifyChain, "[level] [numblocks]"},
|
||||
"verifymessage": {3, 0, displayGeneric, nil, makeVerifyMessage, "<address> <signature> <message>"},
|
||||
"walletlock": {0, 0, displayGeneric, nil, makeWalletLock, ""},
|
||||
"walletpassphrase": {1, 1, displayGeneric, []conversionHandler{nil, toInt64}, makeWalletPassphrase, "<passphrase> [timeout]"},
|
||||
"walletpassphrasechange": {2, 0, displayGeneric, nil, makeWalletPassphraseChange, "<oldpassphrase> <newpassphrase>"},
|
||||
}
|
||||
|
||||
// toSatoshi attempts to convert the passed string to a satoshi amount returned
|
||||
// as an int64. It returns the int64 packed into an interface so it can be used
|
||||
// in the calls which expect interfaces. An error will be returned if the string
|
||||
// can't be converted first to a float64.
|
||||
func toSatoshi(val string) (interface{}, error) {
|
||||
idx, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return int64(float64(btcutil.SatoshiPerBitcoin) * idx), nil
|
||||
}
|
||||
|
||||
// toInt attempts to convert the passed string to an integer. It returns the
|
||||
@@ -74,6 +132,27 @@ func toInt(val string) (interface{}, error) {
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
// toInt64 attempts to convert the passed string to an int64. It returns the
|
||||
// integer packed into an interface so it can be used in the calls which expect
|
||||
// interfaces. An error will be returned if the string can't be converted to an
|
||||
// integer.
|
||||
func toInt64(val string) (interface{}, error) {
|
||||
idx, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
// toBool attempts to convert the passed string to a bool. It returns the
|
||||
// bool packed into the empty interface so it can be used in the calls which
|
||||
// accept interfaces. An error will be returned if the string can't be
|
||||
// converted to a bool.
|
||||
func toBool(val string) (interface{}, error) {
|
||||
return strconv.ParseBool(val)
|
||||
}
|
||||
|
||||
// displayGeneric is a displayHandler that simply displays the passed interface
|
||||
// using fmt.Println.
|
||||
func displayGeneric(reply interface{}) error {
|
||||
@@ -82,11 +161,11 @@ func displayGeneric(reply interface{}) error {
|
||||
}
|
||||
|
||||
// displayFloat64 is a displayHandler that ensures the concrete type of the
|
||||
// passed interface is a float64 and displays it using fmt.Println. An error
|
||||
// passed interface is a float64 and displays it using fmt.Printf. An error
|
||||
// is returned if a float64 is not passed.
|
||||
func displayFloat64(reply interface{}) error {
|
||||
if val, ok := reply.(float64); ok {
|
||||
fmt.Println(val)
|
||||
fmt.Printf("%f\n", val)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -100,11 +179,581 @@ func displaySpewDump(reply interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// displayJSONDump is a displayHandler that uses json.Indent to display the
|
||||
// passed interface.
|
||||
func displayJSONDump(reply interface{}) error {
|
||||
marshaledBytes, err := json.Marshal(reply)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = json.Indent(&buf, marshaledBytes, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(buf.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeAddNode generates the cmd structure for addnode commands.
|
||||
func makeAddNode(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewAddNodeCmd("btcctl", args[0].(string),
|
||||
args[1].(string))
|
||||
}
|
||||
|
||||
// makeCreateRawTransaction generates the cmd structure for createrawtransaction
|
||||
// commands.
|
||||
func makeCreateRawTransaction(args []interface{}) (btcjson.Cmd, error) {
|
||||
// First unmarshal the JSON provided by the parameters into interfaces.
|
||||
var iinputs, iamounts interface{}
|
||||
err := json.Unmarshal([]byte(args[0].(string)), &iinputs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal([]byte(args[1].(string)), &iamounts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validate and convert the interfaces to concrete types.
|
||||
inputs, amounts, err := btcjson.ConvertCreateRawTxParams(iinputs,
|
||||
iamounts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return btcjson.NewCreateRawTransactionCmd("btcctl", inputs, amounts)
|
||||
}
|
||||
|
||||
// makeDebugLevel generates the cmd structure for debuglevel commands.
|
||||
func makeDebugLevel(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewDebugLevelCmd("btcctl", args[0].(string))
|
||||
}
|
||||
|
||||
// makeDecodeRawTransaction generates the cmd structure for
|
||||
// decoderawtransaction commands.
|
||||
func makeDecodeRawTransaction(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewDecodeRawTransactionCmd("btcctl", args[0].(string))
|
||||
}
|
||||
|
||||
// makeDecodeScript generates the cmd structure for decodescript commands.
|
||||
func makeDecodeScript(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewDecodeScriptCmd("btcctl", args[0].(string))
|
||||
}
|
||||
|
||||
// makeDumpPrivKey generates the cmd structure for
|
||||
// dumpprivkey commands.
|
||||
func makeDumpPrivKey(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewDumpPrivKeyCmd("btcctl", args[0].(string))
|
||||
}
|
||||
|
||||
// makeGetAccount generates the cmd structure for
|
||||
// getaccount commands.
|
||||
func makeGetAccount(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewGetAccountCmd("btcctl", args[0].(string))
|
||||
}
|
||||
|
||||
// makeGetAccountAddress generates the cmd structure for
|
||||
// getaccountaddress commands.
|
||||
func makeGetAccountAddress(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewGetAccountAddressCmd("btcctl", args[0].(string))
|
||||
}
|
||||
|
||||
// makeGetAddedNodeInfo generates the cmd structure for
|
||||
// getaccountaddress commands.
|
||||
func makeGetAddedNodeInfo(args []interface{}) (btcjson.Cmd, error) {
|
||||
// Create the getaddednodeinfo command with defaults for the optional
|
||||
// parameters.
|
||||
cmd, err := btcjson.NewGetAddedNodeInfoCmd("btcctl", args[0].(bool))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Override the optional parameter if it was specified.
|
||||
if len(args) > 1 {
|
||||
cmd.Node = args[1].(string)
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// makeGetAddressesByAccount generates the cmd structure for
|
||||
// getaddressesbyaccount commands.
|
||||
func makeGetAddressesByAccount(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewGetAddressesByAccountCmd("btcctl", args[0].(string))
|
||||
}
|
||||
|
||||
// makeGetBalance generates the cmd structure for
|
||||
// getbalance commands.
|
||||
func makeGetBalance(args []interface{}) (btcjson.Cmd, error) {
|
||||
optargs := make([]interface{}, 0, 2)
|
||||
if len(args) > 0 {
|
||||
optargs = append(optargs, args[0].(string))
|
||||
}
|
||||
if len(args) > 1 {
|
||||
optargs = append(optargs, args[1].(int))
|
||||
}
|
||||
|
||||
return btcjson.NewGetBalanceCmd("btcctl", optargs...)
|
||||
}
|
||||
|
||||
// makeGetBestBlockHash generates the cmd structure for
|
||||
// makebestblockhash commands.
|
||||
func makeGetBestBlockHash(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewGetBestBlockHashCmd("btcctl")
|
||||
}
|
||||
|
||||
// makeGetBlock generates the cmd structure for getblock commands.
|
||||
func makeGetBlock(args []interface{}) (btcjson.Cmd, error) {
|
||||
// Create the getblock command with defaults for the optional
|
||||
// parameters.
|
||||
getBlockCmd, err := btcjson.NewGetBlockCmd("btcctl", args[0].(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Override the optional parameters if they were specified.
|
||||
if len(args) > 1 {
|
||||
getBlockCmd.Verbose = args[1].(bool)
|
||||
}
|
||||
if len(args) > 2 {
|
||||
getBlockCmd.VerboseTx = args[2].(bool)
|
||||
}
|
||||
|
||||
return getBlockCmd, nil
|
||||
}
|
||||
|
||||
// makeGetBlockCount generates the cmd structure for getblockcount commands.
|
||||
func makeGetBlockCount(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewGetBlockCountCmd("btcctl")
|
||||
}
|
||||
|
||||
// makeGetBlockHash generates the cmd structure for getblockhash commands.
|
||||
func makeGetBlockHash(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewGetBlockHashCmd("btcctl", args[0].(int64))
|
||||
}
|
||||
|
||||
// makeGetBlockTemplate generates the cmd structure for getblocktemplate commands.
|
||||
func makeGetBlockTemplate(args []interface{}) (btcjson.Cmd, error) {
|
||||
cmd, err := btcjson.NewGetBlockTemplateCmd("btcctl")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(args) == 1 {
|
||||
err = cmd.UnmarshalJSON([]byte(args[0].(string)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// makeGetConnectionCount generates the cmd structure for
|
||||
// getconnectioncount commands.
|
||||
func makeGetConnectionCount(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewGetConnectionCountCmd("btcctl")
|
||||
}
|
||||
|
||||
// makeGetDifficulty generates the cmd structure for
|
||||
// getdifficulty commands.
|
||||
func makeGetDifficulty(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewGetDifficultyCmd("btcctl")
|
||||
}
|
||||
|
||||
// makeGetGenerate generates the cmd structure for
|
||||
// getgenerate commands.
|
||||
func makeGetGenerate(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewGetGenerateCmd("btcctl")
|
||||
}
|
||||
|
||||
// makeGetHashesPerSec generates the cmd structure for gethashespersec commands.
|
||||
func makeGetHashesPerSec(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewGetHashesPerSecCmd("btcctl")
|
||||
}
|
||||
|
||||
// makeGetInfo generates the cmd structure for getinfo commands.
|
||||
func makeGetInfo(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewGetInfoCmd("btcctl")
|
||||
}
|
||||
|
||||
// makeGetNetworkHashPS generates the cmd structure for getnetworkhashps
|
||||
// commands.
|
||||
func makeGetNetworkHashPS(args []interface{}) (btcjson.Cmd, error) {
|
||||
// Create the getnetworkhashps command with defaults for the optional
|
||||
// parameters.
|
||||
cmd, err := btcjson.NewGetNetworkHashPSCmd("btcctl")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Override the optional blocks if specified.
|
||||
if len(args) > 0 {
|
||||
cmd.Blocks = args[0].(int)
|
||||
}
|
||||
|
||||
// Override the optional height if specified.
|
||||
if len(args) > 1 {
|
||||
cmd.Height = args[1].(int)
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// makeGetNetTotals generates the cmd structure for getnettotals commands.
|
||||
func makeGetNetTotals(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewGetNetTotalsCmd("btcctl")
|
||||
}
|
||||
|
||||
// makeGetNewAddress generates the cmd structure for getnewaddress commands.
|
||||
func makeGetNewAddress(args []interface{}) (btcjson.Cmd, error) {
|
||||
var account string
|
||||
if len(args) > 0 {
|
||||
account = args[0].(string)
|
||||
}
|
||||
return btcjson.NewGetNewAddressCmd("btcctl", account)
|
||||
}
|
||||
|
||||
// makePeerInfo generates the cmd structure for
|
||||
// getpeerinfo commands.
|
||||
func makeGetPeerInfo(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewGetPeerInfoCmd("btcctl")
|
||||
}
|
||||
|
||||
// makeGetRawChangeAddress generates the cmd structure for getrawchangeaddress commands.
|
||||
func makeGetRawChangeAddress(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewGetRawChangeAddressCmd("btcctl")
|
||||
}
|
||||
|
||||
// makeRawMempool generates the cmd structure for
|
||||
// getrawmempool commands.
|
||||
func makeGetRawMempool(args []interface{}) (btcjson.Cmd, error) {
|
||||
opt := make([]bool, 0, 1)
|
||||
if len(args) > 0 {
|
||||
opt = append(opt, args[0].(bool))
|
||||
}
|
||||
return btcjson.NewGetRawMempoolCmd("btcctl", opt...)
|
||||
}
|
||||
|
||||
// makeGetReceivedByAccount generates the cmd structure for
|
||||
// getreceivedbyaccount commands.
|
||||
func makeGetReceivedByAccount(args []interface{}) (btcjson.Cmd, error) {
|
||||
opt := make([]int, 0, 1)
|
||||
if len(args) > 1 {
|
||||
opt = append(opt, args[1].(int))
|
||||
}
|
||||
return btcjson.NewGetReceivedByAccountCmd("btcctl", args[0].(string), opt...)
|
||||
}
|
||||
|
||||
// makeGetReceivedByAddress generates the cmd structure for
|
||||
// getreceivedbyaddress commands.
|
||||
func makeGetReceivedByAddress(args []interface{}) (btcjson.Cmd, error) {
|
||||
opt := make([]int, 0, 1)
|
||||
if len(args) > 1 {
|
||||
opt = append(opt, args[1].(int))
|
||||
}
|
||||
return btcjson.NewGetReceivedByAddressCmd("btcctl", args[0].(string), opt...)
|
||||
}
|
||||
|
||||
// makeGetTransaction generates the cmd structure for gettransaction commands.
|
||||
func makeGetTransaction(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewGetTransactionCmd("btcctl", args[0].(string))
|
||||
}
|
||||
|
||||
// makeGetTxOutSetInfo generates the cmd structure for gettxoutsetinfo commands.
|
||||
func makeGetTxOutSetInfo(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewGetTxOutSetInfoCmd("btcctl")
|
||||
}
|
||||
|
||||
func makeGetWork(args []interface{}) (btcjson.Cmd, error) {
|
||||
cmd, err := btcjson.NewGetWorkCmd("btcctl")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(args) == 1 {
|
||||
cmd.Data = args[0].(string)
|
||||
}
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func makeHelp(args []interface{}) (btcjson.Cmd, error) {
|
||||
opt := make([]string, 0, 1)
|
||||
if len(args) > 0 {
|
||||
opt = append(opt, args[0].(string))
|
||||
}
|
||||
return btcjson.NewHelpCmd("btcctl", opt...)
|
||||
}
|
||||
|
||||
// makeRawTransaction generates the cmd structure for
|
||||
// getrawtransaction commands.
|
||||
func makeGetRawTransaction(args []interface{}) (btcjson.Cmd, error) {
|
||||
opt := make([]int, 0, 1)
|
||||
if len(args) > 1 {
|
||||
opt = append(opt, args[1].(int))
|
||||
}
|
||||
|
||||
return btcjson.NewGetRawTransactionCmd("btcctl", args[0].(string), opt...)
|
||||
}
|
||||
|
||||
// makeImportPrivKey generates the cmd structure for
|
||||
// importprivkey commands.
|
||||
func makeImportPrivKey(args []interface{}) (btcjson.Cmd, error) {
|
||||
var optargs = make([]interface{}, 0, 2)
|
||||
if len(args) > 1 {
|
||||
optargs = append(optargs, args[1].(string))
|
||||
}
|
||||
if len(args) > 2 {
|
||||
optargs = append(optargs, args[2].(bool))
|
||||
}
|
||||
|
||||
return btcjson.NewImportPrivKeyCmd("btcctl", args[0].(string), optargs...)
|
||||
}
|
||||
|
||||
// makeKeyPoolRefill generates the cmd structure for keypoolrefill commands.
|
||||
func makeKeyPoolRefill(args []interface{}) (btcjson.Cmd, error) {
|
||||
var optargs = make([]uint, 0, 1)
|
||||
if len(args) > 0 {
|
||||
optargs = append(optargs, uint(args[0].(int)))
|
||||
}
|
||||
|
||||
return btcjson.NewKeyPoolRefillCmd("btcctl", optargs...)
|
||||
}
|
||||
|
||||
// makeListAccounts generates the cmd structure for listaccounts commands.
|
||||
func makeListAccounts(args []interface{}) (btcjson.Cmd, error) {
|
||||
var optargs = make([]int, 0, 1)
|
||||
if len(args) > 0 {
|
||||
optargs = append(optargs, args[0].(int))
|
||||
}
|
||||
return btcjson.NewListAccountsCmd("btcctl", optargs...)
|
||||
}
|
||||
|
||||
// makeListAddressGroupings generates the cmd structure for listaddressgroupings commands.
|
||||
func makeListAddressGroupings(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewListAddressGroupingsCmd("btcctl")
|
||||
}
|
||||
|
||||
// makeListReceivedByAccount generates the cmd structure for listreceivedbyaccount commands.
|
||||
func makeListReceivedByAccount(args []interface{}) (btcjson.Cmd, error) {
|
||||
var optargs = make([]interface{}, 0, 2)
|
||||
if len(args) > 0 {
|
||||
optargs = append(optargs, args[0].(int))
|
||||
}
|
||||
if len(args) > 1 {
|
||||
optargs = append(optargs, args[1].(bool))
|
||||
}
|
||||
return btcjson.NewListReceivedByAccountCmd("btcctl", optargs...)
|
||||
}
|
||||
|
||||
// makeListReceivedByAddress generates the cmd structure for listreceivedbyaddress commands.
|
||||
func makeListReceivedByAddress(args []interface{}) (btcjson.Cmd, error) {
|
||||
var optargs = make([]interface{}, 0, 2)
|
||||
if len(args) > 0 {
|
||||
optargs = append(optargs, args[0].(int))
|
||||
}
|
||||
if len(args) > 1 {
|
||||
optargs = append(optargs, args[1].(bool))
|
||||
}
|
||||
return btcjson.NewListReceivedByAddressCmd("btcctl", optargs...)
|
||||
}
|
||||
|
||||
// makeListLockUnspent generates the cmd structure for listlockunspent commands.
|
||||
func makeListLockUnspent(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewListLockUnspentCmd("btcctl")
|
||||
}
|
||||
|
||||
// makeListSinceBlock generates the cmd structure for
|
||||
// listsinceblock commands.
|
||||
func makeListSinceBlock(args []interface{}) (btcjson.Cmd, error) {
|
||||
var optargs = make([]interface{}, 0, 2)
|
||||
if len(args) > 0 {
|
||||
optargs = append(optargs, args[0].(string))
|
||||
}
|
||||
if len(args) > 1 {
|
||||
optargs = append(optargs, args[1].(int))
|
||||
}
|
||||
|
||||
return btcjson.NewListSinceBlockCmd("btcctl", optargs...)
|
||||
}
|
||||
|
||||
// makeListTransactions generates the cmd structure for
|
||||
// listtransactions commands.
|
||||
func makeListTransactions(args []interface{}) (btcjson.Cmd, error) {
|
||||
var optargs = make([]interface{}, 0, 3)
|
||||
if len(args) > 0 {
|
||||
optargs = append(optargs, args[0].(string))
|
||||
}
|
||||
if len(args) > 1 {
|
||||
optargs = append(optargs, args[1].(int))
|
||||
}
|
||||
if len(args) > 2 {
|
||||
optargs = append(optargs, args[2].(int))
|
||||
}
|
||||
|
||||
return btcjson.NewListTransactionsCmd("btcctl", optargs...)
|
||||
}
|
||||
|
||||
// makeListUnspent generates the cmd structure for listunspent commands.
|
||||
func makeListUnspent(args []interface{}) (btcjson.Cmd, error) {
|
||||
var optargs = make([]interface{}, 0, 3)
|
||||
if len(args) > 0 {
|
||||
optargs = append(optargs, args[0].(int))
|
||||
}
|
||||
if len(args) > 1 {
|
||||
optargs = append(optargs, args[1].(int))
|
||||
}
|
||||
if len(args) > 2 {
|
||||
var addrs []string
|
||||
err := json.Unmarshal([]byte(args[2].(string)), &addrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optargs = append(optargs, addrs)
|
||||
}
|
||||
return btcjson.NewListUnspentCmd("btcctl", optargs...)
|
||||
}
|
||||
|
||||
// makePing generates the cmd structure for ping commands.
|
||||
func makePing(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewPingCmd("btcctl")
|
||||
}
|
||||
|
||||
// makeSendFrom generates the cmd structure for sendfrom commands.
|
||||
func makeSendFrom(args []interface{}) (btcjson.Cmd, error) {
|
||||
var optargs = make([]interface{}, 0, 3)
|
||||
if len(args) > 3 {
|
||||
optargs = append(optargs, args[3].(int))
|
||||
}
|
||||
if len(args) > 4 {
|
||||
optargs = append(optargs, args[4].(string))
|
||||
}
|
||||
if len(args) > 5 {
|
||||
optargs = append(optargs, args[5].(string))
|
||||
}
|
||||
return btcjson.NewSendFromCmd("btcctl", args[0].(string),
|
||||
args[1].(string), args[2].(int64), optargs...)
|
||||
}
|
||||
|
||||
// makeSendMany generates the cmd structure for sendmany commands.
|
||||
func makeSendMany(args []interface{}) (btcjson.Cmd, error) {
|
||||
origPairs := make(map[string]float64)
|
||||
err := json.Unmarshal([]byte(args[1].(string)), &origPairs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pairs := make(map[string]int64)
|
||||
for addr, value := range origPairs {
|
||||
pairs[addr] = int64(float64(btcutil.SatoshiPerBitcoin) * value)
|
||||
}
|
||||
|
||||
var optargs = make([]interface{}, 0, 2)
|
||||
if len(args) > 2 {
|
||||
optargs = append(optargs, args[2].(int))
|
||||
}
|
||||
if len(args) > 3 {
|
||||
optargs = append(optargs, args[3].(string))
|
||||
}
|
||||
return btcjson.NewSendManyCmd("btcctl", args[0].(string), pairs, optargs...)
|
||||
}
|
||||
|
||||
// makeSendRawTransaction generates the cmd structure for sendrawtransaction
|
||||
// commands.
|
||||
func makeSendRawTransaction(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewSendRawTransactionCmd("btcctl", args[0].(string))
|
||||
}
|
||||
|
||||
// makeSendToAddress generates the cmd struture for sendtoaddress commands.
|
||||
func makeSendToAddress(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewSendToAddressCmd("btcctl", args[0].(string), args[1].(int64), args[2:]...)
|
||||
}
|
||||
|
||||
// makeSetTxFee generates the cmd structure for settxfee commands.
|
||||
func makeSetTxFee(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewSetTxFeeCmd("btcctl", args[0].(int64))
|
||||
}
|
||||
|
||||
// makeSignMessage generates the cmd structure for signmessage commands.
|
||||
func makeSignMessage(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewSignMessageCmd("btcctl", args[0].(string),
|
||||
args[1].(string))
|
||||
}
|
||||
|
||||
// makeStop generates the cmd structure for stop commands.
|
||||
func makeStop(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewStopCmd("btcctl")
|
||||
}
|
||||
|
||||
// makeSubmitBlock generates the cmd structure for submitblock commands.
|
||||
func makeSubmitBlock(args []interface{}) (btcjson.Cmd, error) {
|
||||
opts := &btcjson.SubmitBlockOptions{}
|
||||
if len(args) == 2 {
|
||||
opts.WorkId = args[1].(string)
|
||||
}
|
||||
|
||||
return btcjson.NewSubmitBlockCmd("btcctl", args[0].(string), opts)
|
||||
}
|
||||
|
||||
// makeValidateAddress generates the cmd structure for validateaddress commands.
|
||||
func makeValidateAddress(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewValidateAddressCmd("btcctl", args[0].(string))
|
||||
}
|
||||
|
||||
// makeVerifyChain generates the cmd structure for verifychain commands.
|
||||
func makeVerifyChain(args []interface{}) (btcjson.Cmd, error) {
|
||||
iargs := make([]int32, 0, 2)
|
||||
for _, i := range args {
|
||||
iargs = append(iargs, int32(i.(int)))
|
||||
}
|
||||
return btcjson.NewVerifyChainCmd("btcctl", iargs...)
|
||||
}
|
||||
|
||||
func makeVerifyMessage(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewVerifyMessageCmd("btcctl", args[0].(string),
|
||||
args[1].(string), args[2].(string))
|
||||
}
|
||||
|
||||
// makeWalletLock generates the cmd structure for walletlock commands.
|
||||
func makeWalletLock(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewWalletLockCmd("btcctl")
|
||||
}
|
||||
|
||||
// makeWalletPassphrase generates the cmd structure for walletpassphrase commands.
|
||||
func makeWalletPassphrase(args []interface{}) (btcjson.Cmd, error) {
|
||||
timeout := int64(60)
|
||||
if len(args) > 1 {
|
||||
timeout = args[1].(int64)
|
||||
}
|
||||
return btcjson.NewWalletPassphraseCmd("btcctl", args[0].(string), timeout)
|
||||
}
|
||||
|
||||
// makeWalletPassphraseChange generates the cmd structure for
|
||||
// walletpassphrasechange commands.
|
||||
func makeWalletPassphraseChange(args []interface{}) (btcjson.Cmd, error) {
|
||||
return btcjson.NewWalletPassphraseChangeCmd("btcctl", args[0].(string),
|
||||
args[1].(string))
|
||||
}
|
||||
|
||||
// send sends a JSON-RPC command to the specified RPC server and examines the
|
||||
// results for various error conditions. It either returns a valid result or
|
||||
// an appropriate error.
|
||||
func send(cfg *config, msg []byte) (interface{}, error) {
|
||||
reply, err := btcjson.RpcCommand(cfg.RpcUser, cfg.RpcPassword, cfg.RpcServer, msg)
|
||||
var reply btcjson.Reply
|
||||
var err error
|
||||
if cfg.NoTls || (cfg.RPCCert == "" && !cfg.TlsSkipVerify) {
|
||||
reply, err = btcjson.RpcCommand(cfg.RPCUser, cfg.RPCPassword,
|
||||
cfg.RPCServer, msg)
|
||||
} else {
|
||||
var pem []byte
|
||||
if cfg.RPCCert != "" {
|
||||
pem, err = ioutil.ReadFile(cfg.RPCCert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
reply, err = btcjson.TlsRpcCommand(cfg.RPCUser,
|
||||
cfg.RPCPassword, cfg.RPCServer, msg, pem,
|
||||
cfg.TlsSkipVerify)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -123,8 +772,8 @@ func send(cfg *config, msg []byte) (interface{}, error) {
|
||||
// sendCommand creates a JSON-RPC command using the passed command and arguments
|
||||
// and then sends it. A prefix is added to any errors that occur indicating
|
||||
// what step failed.
|
||||
func sendCommand(cfg *config, command string, args ...interface{}) (interface{}, error) {
|
||||
msg, err := btcjson.CreateMessage(command, args...)
|
||||
func sendCommand(cfg *config, command btcjson.Cmd) (interface{}, error) {
|
||||
msg, err := json.Marshal(command)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CreateMessage: %v", err.Error())
|
||||
}
|
||||
@@ -177,9 +826,13 @@ func commandHandler(cfg *config, command string, data *handlerData, args []strin
|
||||
}
|
||||
}
|
||||
}
|
||||
cmd, err := data.makeCmd(iargs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create and send the appropriate JSON-RPC command.
|
||||
reply, err := sendCommand(cfg, command, iargs...)
|
||||
reply, err := sendCommand(cfg, cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -216,19 +869,12 @@ func usage(parser *flags.Parser) {
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Parse command line and show usage if needed.
|
||||
cfg := config{
|
||||
RpcServer: "127.0.0.1:8334",
|
||||
}
|
||||
parser := flags.NewParser(&cfg, flags.PassDoubleDash)
|
||||
args, err := parser.Parse()
|
||||
parser, cfg, args, err := loadConfig()
|
||||
if err != nil {
|
||||
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
||||
usage(parser)
|
||||
}
|
||||
usage(parser)
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(args) < 1 || cfg.Help {
|
||||
if len(args) < 1 {
|
||||
usage(parser)
|
||||
return
|
||||
}
|
||||
@@ -242,7 +888,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Execute the command.
|
||||
err = commandHandler(&cfg, args[0], data, args[1:])
|
||||
err = commandHandler(cfg, args[0], data, args[1:])
|
||||
if err != nil {
|
||||
if err == ErrUsage {
|
||||
usage(parser)
|
||||
|
||||
92
util/btcctl/config.go
Normal file
92
util/btcctl/config.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/conformal/btcutil"
|
||||
"github.com/conformal/go-flags"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
btcdHomeDir = btcutil.AppDataDir("btcd", false)
|
||||
btcctlHomeDir = btcutil.AppDataDir("btcctl", false)
|
||||
defaultConfigFile = filepath.Join(btcctlHomeDir, "btcctl.conf")
|
||||
defaultRPCCertFile = filepath.Join(btcdHomeDir, "rpc.cert")
|
||||
)
|
||||
|
||||
// config defines the configuration options for btcctl.
|
||||
//
|
||||
// See loadConfig for details on the configuration load process.
|
||||
type config struct {
|
||||
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
|
||||
ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`
|
||||
RPCUser string `short:"u" long:"rpcuser" description:"RPC username"`
|
||||
RPCPassword string `short:"P" long:"rpcpass" default-mask:"-" description:"RPC password"`
|
||||
RPCServer string `short:"s" long:"rpcserver" description:"RPC server to connect to"`
|
||||
RPCCert string `short:"c" long:"rpccert" description:"RPC server certificate chain for validation"`
|
||||
NoTls bool `long:"notls" description:"Disable TLS"`
|
||||
TlsSkipVerify bool `long:"skipverify" description:"Do not verify tls certificates (not recommended!)"`
|
||||
}
|
||||
|
||||
// loadConfig initializes and parses the config using a config file and command
|
||||
// line options.
|
||||
//
|
||||
// The configuration proceeds as follows:
|
||||
// 1) Start with a default config with sane settings
|
||||
// 2) Pre-parse the command line to check for an alternative config file
|
||||
// 3) Load configuration file overwriting defaults with any specified options
|
||||
// 4) Parse CLI options and overwrite/add any specified options
|
||||
//
|
||||
// The above results in functioning properly without any config settings
|
||||
// while still allowing the user to override settings with config files and
|
||||
// command line options. Command line options always take precedence.
|
||||
func loadConfig() (*flags.Parser, *config, []string, error) {
|
||||
// Default config.
|
||||
cfg := config{
|
||||
ConfigFile: defaultConfigFile,
|
||||
RPCServer: "localhost:8334",
|
||||
RPCCert: defaultRPCCertFile,
|
||||
}
|
||||
|
||||
// Create the home directory if it doesn't already exist.
|
||||
err := os.MkdirAll(btcdHomeDir, 0700)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
// Pre-parse the command line options to see if an alternative config
|
||||
// file or the version flag was specified. Any errors can be ignored
|
||||
// here since they will be caught be the final parse below.
|
||||
preCfg := cfg
|
||||
preParser := flags.NewParser(&preCfg, flags.None)
|
||||
preParser.Parse()
|
||||
|
||||
// Show the version and exit if the version flag was specified.
|
||||
if preCfg.ShowVersion {
|
||||
appName := filepath.Base(os.Args[0])
|
||||
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
|
||||
fmt.Println(appName, "version", version())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Load additional config from file.
|
||||
parser := flags.NewParser(&cfg, flags.PassDoubleDash|flags.HelpFlag)
|
||||
err = flags.NewIniParser(parser).ParseFile(preCfg.ConfigFile)
|
||||
if err != nil {
|
||||
if _, ok := err.(*os.PathError); !ok {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return parser, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Parse command line options again to ensure they take precedence.
|
||||
remainingArgs, err := parser.Parse()
|
||||
if err != nil {
|
||||
return parser, nil, nil, err
|
||||
}
|
||||
|
||||
return parser, &cfg, remainingArgs, nil
|
||||
}
|
||||
72
util/btcctl/version.go
Normal file
72
util/btcctl/version.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// semanticAlphabet
|
||||
const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
|
||||
|
||||
// These constants define the application version and follow the semantic
|
||||
// versioning 2.0.0 spec (http://semver.org/).
|
||||
const (
|
||||
appMajor uint = 0
|
||||
appMinor uint = 7
|
||||
appPatch uint = 0
|
||||
|
||||
// appPreRelease MUST only contain characters from semanticAlphabet
|
||||
// per the semantic versioning spec.
|
||||
appPreRelease = "alpha"
|
||||
)
|
||||
|
||||
// appBuild is defined as a variable so it can be overridden during the build
|
||||
// process with '-ldflags "-X main.appBuild foo' if needed. It MUST only
|
||||
// contain characters from semanticAlphabet per the semantic versioning spec.
|
||||
var appBuild string
|
||||
|
||||
// version returns the application version as a properly formed string per the
|
||||
// semantic versioning 2.0.0 spec (http://semver.org/).
|
||||
func version() string {
|
||||
// Start with the major, minor, and patch versions.
|
||||
version := fmt.Sprintf("%d.%d.%d", appMajor, appMinor, appPatch)
|
||||
|
||||
// Append pre-release version if there is one. The hyphen called for
|
||||
// by the semantic versioning spec is automatically appended and should
|
||||
// not be contained in the pre-release string. The pre-release version
|
||||
// is not appended if it contains invalid characters.
|
||||
preRelease := normalizeVerString(appPreRelease)
|
||||
if preRelease != "" {
|
||||
version = fmt.Sprintf("%s-%s", version, preRelease)
|
||||
}
|
||||
|
||||
// Append build metadata if there is any. The plus called for
|
||||
// by the semantic versioning spec is automatically appended and should
|
||||
// not be contained in the build metadata string. The build metadata
|
||||
// string is not appended if it contains invalid characters.
|
||||
build := normalizeVerString(appBuild)
|
||||
if build != "" {
|
||||
version = fmt.Sprintf("%s+%s", version, build)
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
// normalizeVerString returns the passed string stripped of all characters which
|
||||
// are not valid according to the semantic versioning guidelines for pre-release
|
||||
// version and build metadata strings. In particular they MUST only contain
|
||||
// characters in semanticAlphabet.
|
||||
func normalizeVerString(str string) string {
|
||||
var result bytes.Buffer
|
||||
for _, r := range str {
|
||||
if strings.ContainsRune(semanticAlphabet, r) {
|
||||
result.WriteRune(r)
|
||||
}
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
@@ -9,9 +9,10 @@ import (
|
||||
"fmt"
|
||||
"github.com/conformal/btcdb"
|
||||
_ "github.com/conformal/btcdb/ldb"
|
||||
"github.com/conformal/btclog"
|
||||
"github.com/conformal/btcutil"
|
||||
"github.com/conformal/btcwire"
|
||||
"github.com/conformal/go-flags"
|
||||
"github.com/conformal/seelog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -26,7 +27,11 @@ type config struct {
|
||||
ShaString string `short:"s" description:"Block SHA to process" required:"true"`
|
||||
}
|
||||
|
||||
var log seelog.LoggerInterface
|
||||
var (
|
||||
btcdHomeDir = btcutil.AppDataDir("btcd", false)
|
||||
defaultDataDir = filepath.Join(btcdHomeDir, "data")
|
||||
log btclog.Logger
|
||||
)
|
||||
|
||||
const (
|
||||
ArgSha = iota
|
||||
@@ -36,7 +41,7 @@ const (
|
||||
func main() {
|
||||
cfg := config{
|
||||
DbType: "leveldb",
|
||||
DataDir: filepath.Join(btcdHomeDir(), "data"),
|
||||
DataDir: defaultDataDir,
|
||||
}
|
||||
parser := flags.NewParser(&cfg, flags.Default)
|
||||
_, err := parser.Parse()
|
||||
@@ -47,13 +52,9 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
log, err = seelog.LoggerFromWriterWithMinLevel(os.Stdout,
|
||||
seelog.TraceLvl)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to create logger: %v", err)
|
||||
return
|
||||
}
|
||||
defer log.Flush()
|
||||
backendLogger := btclog.NewDefaultBackendLogger()
|
||||
defer backendLogger.Flush()
|
||||
log = btclog.NewSubsystemLogger(backendLogger, "")
|
||||
btcdb.UseLogger(log)
|
||||
|
||||
var testnet string
|
||||
@@ -185,21 +186,3 @@ func parsesha(argstr string) (argtype int, height int64, psha *btcwire.ShaHash,
|
||||
psha = &sha
|
||||
return
|
||||
}
|
||||
|
||||
// btcdHomeDir returns an OS appropriate home directory for btcd.
|
||||
func btcdHomeDir() string {
|
||||
// Search for Windows APPDATA first. This won't exist on POSIX OSes.
|
||||
appData := os.Getenv("APPDATA")
|
||||
if appData != "" {
|
||||
return filepath.Join(appData, "btcd")
|
||||
}
|
||||
|
||||
// Fall back to standard HOME directory that works for most POSIX OSes.
|
||||
home := os.Getenv("HOME")
|
||||
if home != "" {
|
||||
return filepath.Join(home, ".btcd")
|
||||
}
|
||||
|
||||
// In the worst case, use the current directory.
|
||||
return "."
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/conformal/btcdb"
|
||||
_ "github.com/conformal/btcdb/ldb"
|
||||
_ "github.com/conformal/btcdb/sqlite3"
|
||||
"github.com/conformal/btcutil"
|
||||
"github.com/conformal/btcwire"
|
||||
"github.com/conformal/go-flags"
|
||||
"os"
|
||||
@@ -23,7 +23,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
defaultDataDir = filepath.Join(btcdHomeDir(), "data")
|
||||
btcdHomeDir = btcutil.AppDataDir("btcd", false)
|
||||
defaultDataDir = filepath.Join(btcdHomeDir, "data")
|
||||
knownDbTypes = btcdb.SupportedDBs()
|
||||
activeNetwork = btcwire.MainNet
|
||||
)
|
||||
@@ -39,24 +40,6 @@ type config struct {
|
||||
UseGoOutput bool `short:"g" long:"gooutput" description:"Display the candidates using Go syntax that is ready to insert into the btcchain checkpoint list"`
|
||||
}
|
||||
|
||||
// btcdHomeDir returns an OS appropriate home directory for btcd.
|
||||
func btcdHomeDir() string {
|
||||
// Search for Windows APPDATA first. This won't exist on POSIX OSes.
|
||||
appData := os.Getenv("APPDATA")
|
||||
if appData != "" {
|
||||
return filepath.Join(appData, "btcd")
|
||||
}
|
||||
|
||||
// Fall back to standard HOME directory that works for most POSIX OSes.
|
||||
home := os.Getenv("HOME")
|
||||
if home != "" {
|
||||
return filepath.Join(home, ".btcd")
|
||||
}
|
||||
|
||||
// In the worst case, use the current directory.
|
||||
return "."
|
||||
}
|
||||
|
||||
// validDbType returns whether or not dbType is a supported database type.
|
||||
func validDbType(dbType string) bool {
|
||||
for _, knownType := range knownDbTypes {
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/conformal/btcchain"
|
||||
"github.com/conformal/btcdb"
|
||||
_ "github.com/conformal/btcdb/ldb"
|
||||
_ "github.com/conformal/btcdb/sqlite3"
|
||||
"github.com/conformal/btcwire"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
101
util/gencerts/gencerts.go
Normal file
101
util/gencerts/gencerts.go
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/conformal/btcutil"
|
||||
"github.com/conformal/go-flags"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
Directory string `short:"d" long:"directory" description:"Directory to write certificate pair"`
|
||||
Years int `short:"y" long:"years" description:"How many years a certificate is valid for"`
|
||||
Organization string `short:"o" long:"org" description:"Organization in certificate"`
|
||||
ExtraHosts []string `short:"H" long:"host" description:"Additional hosts/IPs to create certificate for"`
|
||||
Force bool `short:"f" long:"force" description:"Force overwriting of any old certs and keys"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := config{
|
||||
Years: 10,
|
||||
Organization: "gencerts",
|
||||
}
|
||||
parser := flags.NewParser(&cfg, flags.Default)
|
||||
_, err := parser.Parse()
|
||||
if err != nil {
|
||||
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
||||
parser.WriteHelp(os.Stderr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.Directory == "" {
|
||||
var err error
|
||||
cfg.Directory, err = os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "no directory specified and cannot get working directory\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
cfg.Directory = cleanAndExpandPath(cfg.Directory)
|
||||
certFile := filepath.Join(cfg.Directory, "rpc.cert")
|
||||
keyFile := filepath.Join(cfg.Directory, "rpc.key")
|
||||
|
||||
if !cfg.Force {
|
||||
if fileExists(certFile) || fileExists(keyFile) {
|
||||
fmt.Fprintf(os.Stderr, "%v: certificate and/or key files exist; use -f to force\n", cfg.Directory)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
validUntil := time.Now().Add(time.Duration(cfg.Years) * 365 * 24 * time.Hour)
|
||||
cert, key, err := btcutil.NewTLSCertPair(cfg.Organization, validUntil, cfg.ExtraHosts)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cannot generate certificate pair: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Write cert and key files.
|
||||
if err = ioutil.WriteFile(certFile, cert, 0666); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cannot write cert: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err = ioutil.WriteFile(keyFile, key, 0600); err != nil {
|
||||
os.Remove(certFile)
|
||||
fmt.Fprintf(os.Stderr, "cannot write key: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// cleanAndExpandPath expands environement variables and leading ~ in the
|
||||
// passed path, cleans the result, and returns it.
|
||||
func cleanAndExpandPath(path string) string {
|
||||
// Expand initial ~ to OS specific home directory.
|
||||
if strings.HasPrefix(path, "~") {
|
||||
appHomeDir := btcutil.AppDataDir("gencerts", false)
|
||||
homeDir := filepath.Dir(appHomeDir)
|
||||
path = strings.Replace(path, "~", homeDir, 1)
|
||||
}
|
||||
|
||||
// NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%,
|
||||
// but they variables can still be expanded via POSIX-style $VARIABLE.
|
||||
return filepath.Clean(os.ExpandEnv(path))
|
||||
}
|
||||
|
||||
// filesExists reports whether the named file or directory exists.
|
||||
func fileExists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -7,13 +7,12 @@ package main
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/conformal/btcdb"
|
||||
_ "github.com/conformal/btcdb/ldb"
|
||||
_ "github.com/conformal/btcdb/sqlite3"
|
||||
"github.com/conformal/btclog"
|
||||
"github.com/conformal/btcutil"
|
||||
"github.com/conformal/btcwire"
|
||||
"github.com/conformal/go-flags"
|
||||
"github.com/conformal/seelog"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"io"
|
||||
"os"
|
||||
@@ -36,7 +35,11 @@ type config struct {
|
||||
ShowTx bool `short:"t" description:"Show transaction"`
|
||||
}
|
||||
|
||||
var log seelog.LoggerInterface
|
||||
var (
|
||||
btcdHomeDir = btcutil.AppDataDir("btcd", false)
|
||||
defaultDataDir = filepath.Join(btcdHomeDir, "data")
|
||||
log btclog.Logger
|
||||
)
|
||||
|
||||
const (
|
||||
ArgSha = iota
|
||||
@@ -48,7 +51,7 @@ func main() {
|
||||
|
||||
cfg := config{
|
||||
DbType: "leveldb",
|
||||
DataDir: filepath.Join(btcdHomeDir(), "data"),
|
||||
DataDir: defaultDataDir,
|
||||
}
|
||||
parser := flags.NewParser(&cfg, flags.Default)
|
||||
_, err := parser.Parse()
|
||||
@@ -59,13 +62,9 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
log, err = seelog.LoggerFromWriterWithMinLevel(os.Stdout,
|
||||
seelog.InfoLvl)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to create logger: %v", err)
|
||||
return
|
||||
}
|
||||
defer log.Flush()
|
||||
backendLogger := btclog.NewDefaultBackendLogger()
|
||||
defer backendLogger.Flush()
|
||||
log = btclog.NewSubsystemLogger(backendLogger, "")
|
||||
btcdb.UseLogger(log)
|
||||
|
||||
var testnet string
|
||||
@@ -270,21 +269,3 @@ func parsesha(argstr string) (argtype int, height int64, psha *btcwire.ShaHash,
|
||||
psha = &sha
|
||||
return
|
||||
}
|
||||
|
||||
// btcdHomeDir returns an OS appropriate home directory for btcd.
|
||||
func btcdHomeDir() string {
|
||||
// Search for Windows APPDATA first. This won't exist on POSIX OSes.
|
||||
appData := os.Getenv("APPDATA")
|
||||
if appData != "" {
|
||||
return filepath.Join(appData, "btcd")
|
||||
}
|
||||
|
||||
// Fall back to standard HOME directory that works for most POSIX OSes.
|
||||
home := os.Getenv("HOME")
|
||||
if home != "" {
|
||||
return filepath.Join(home, ".btcd")
|
||||
}
|
||||
|
||||
// In the worst case, use the current directory.
|
||||
return "."
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2013 Conformal Systems LLC.
|
||||
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -17,8 +17,8 @@ const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr
|
||||
// versioning 2.0.0 spec (http://semver.org/).
|
||||
const (
|
||||
appMajor uint = 0
|
||||
appMinor uint = 3
|
||||
appPatch uint = 2
|
||||
appMinor uint = 7
|
||||
appPatch uint = 0
|
||||
|
||||
// appPreRelease MUST only contain characters from semanticAlphabet
|
||||
// per the semantic versioning spec.
|
||||
@@ -33,7 +33,7 @@ var appBuild string
|
||||
// version returns the application version as a properly formed string per the
|
||||
// semantic versioning 2.0.0 spec (http://semver.org/).
|
||||
func version() string {
|
||||
// Start with the major, minor, and path versions.
|
||||
// Start with the major, minor, and patch versions.
|
||||
version := fmt.Sprintf("%d.%d.%d", appMajor, appMinor, appPatch)
|
||||
|
||||
// Append pre-release version if there is one. The hyphen called for
|
||||
|
||||
Reference in New Issue
Block a user