mirror of
https://github.com/kaspanet/kaspad.git
synced 2026-03-21 07:38:37 +00:00
Compare commits
1 Commits
big-capaci
...
v1.2.3-tes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71aca9c360 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,7 +2,7 @@
|
||||
*~
|
||||
|
||||
# Databases
|
||||
kaspad.db
|
||||
btcd.db
|
||||
*-shm
|
||||
*-wal
|
||||
|
||||
|
||||
955
CHANGES
Normal file
955
CHANGES
Normal file
@@ -0,0 +1,955 @@
|
||||
============================================================================
|
||||
User visible changes for btcd
|
||||
A full-node bitcoin implementation written in Go
|
||||
============================================================================
|
||||
|
||||
Changes in 0.12.0 (Fri Nov 20 2015)
|
||||
- Protocol and network related changes:
|
||||
- Add a new checkpoint at block height 382320 (#555)
|
||||
- Implement BIP0065 which includes support for version 4 blocks, a new
|
||||
consensus opcode (OP_CHECKLOCKTIMEVERIFY) that enforces transaction
|
||||
lock times, and a double-threshold switchover mechanism (#535, #459,
|
||||
#455)
|
||||
- Implement BIP0111 which provides a new bloom filter service flag and
|
||||
hence provides support for protocol version 70011 (#499)
|
||||
- Add a new parameter --nopeerbloomfilters to allow disabling bloom
|
||||
filter support (#499)
|
||||
- Reject non-canonically encoded variable length integers (#507)
|
||||
- Add mainnet peer discovery DNS seed (seed.bitcoin.jonasschnelli.ch)
|
||||
(#496)
|
||||
- Correct reconnect handling for persistent peers (#463, #464)
|
||||
- Ignore requests for block headers if not fully synced (#444)
|
||||
- Add CLI support for specifying the zone id on IPv6 addresses (#538)
|
||||
- Fix a couple of issues where the initial block sync could stall (#518,
|
||||
#229, #486)
|
||||
- Fix an issue which prevented the --onion option from working as
|
||||
intended (#446)
|
||||
- Transaction relay (memory pool) changes:
|
||||
- Require transactions to only include signatures encoded with the
|
||||
canonical 'low-s' encoding (#512)
|
||||
- Add a new parameter --minrelaytxfee to allow the minimum transaction
|
||||
fee in BTC/kB to be overridden (#520)
|
||||
- Retain memory pool transactions when they redeem another one that is
|
||||
removed when a block is accepted (#539)
|
||||
- Do not send reject messages for a transaction if it is valid but
|
||||
causes an orphan transaction which depends on it to be determined
|
||||
as invalid (#546)
|
||||
- Refrain from attempting to add orphans to the memory pool multiple
|
||||
times when the transaction they redeem is added (#551)
|
||||
- Modify minimum transaction fee calculations to scale based on bytes
|
||||
instead of full kilobyte boundaries (#521, #537)
|
||||
- Implement signature cache:
|
||||
- Provides a limited memory cache of validated signatures which is a
|
||||
huge optimization when verifying blocks for transactions that are
|
||||
already in the memory pool (#506)
|
||||
- Add a new parameter '--sigcachemaxsize' which allows the size of the
|
||||
new cache to be manually changed if desired (#506)
|
||||
- Mining support changes:
|
||||
- Notify getblocktemplate long polling clients when a block is pushed
|
||||
via submitblock (#488)
|
||||
- Speed up getblocktemplate by making use of the new signature cache
|
||||
(#506)
|
||||
- RPC changes:
|
||||
- Implement getmempoolinfo command (#453)
|
||||
- Implement getblockheader command (#461)
|
||||
- Modify createrawtransaction command to accept a new optional parameter
|
||||
'locktime' (#529)
|
||||
- Modify listunspent result to include the 'spendable' field (#440)
|
||||
- Modify getinfo command to include 'errors' field (#511)
|
||||
- Add timestamps to blockconnected and blockdisconnected notifications
|
||||
(#450)
|
||||
- Several modifications to searchrawtranscations command:
|
||||
- Accept a new optional parameter 'vinextra' which causes the results
|
||||
to include information about the outputs referenced by a transaction's
|
||||
inputs (#485, #487)
|
||||
- Skip entries in the mempool too (#495)
|
||||
- Accept a new optional parameter 'reverse' to return the results in
|
||||
reverse order (most recent to oldest) (#497)
|
||||
- Accept a new optional parameter 'filteraddrs' which causes the
|
||||
results to only include inputs and outputs which involve the
|
||||
provided addresses (#516)
|
||||
- Change the notification order to notify clients about mined
|
||||
transactions (recvtx, redeemingtx) before the blockconnected
|
||||
notification (#449)
|
||||
- Update verifymessage RPC to use the standard algorithm so it is
|
||||
compatible with other implementations (#515)
|
||||
- Improve ping statistics by pinging on an interval (#517)
|
||||
- Websocket changes:
|
||||
- Implement session command which returns a per-session unique id (#500,
|
||||
#503)
|
||||
- btcctl utility changes:
|
||||
- Add getmempoolinfo command (#453)
|
||||
- Add getblockheader command (#461)
|
||||
- Add getwalletinfo command (#471)
|
||||
- Notable developer-related package changes:
|
||||
- Introduce a new peer package which acts a common base for creating and
|
||||
concurrently managing bitcoin network peers (#445)
|
||||
- Various cleanup of the new peer package (#528, #531, #524, #534,
|
||||
#549)
|
||||
- Blocks heights now consistently use int32 everywhere (#481)
|
||||
- The BlockHeader type in the wire package now provides the BtcDecode
|
||||
and BtcEncode methods (#467)
|
||||
- Update wire package to recognize BIP0064 (getutxo) service bit (#489)
|
||||
- Export LockTimeThreshold constant from txscript package (#454)
|
||||
- Export MaxDataCarrierSize constant from txscript package (#466)
|
||||
- Provide new IsUnspendable function from the txscript package (#478)
|
||||
- Export variable length string functions from the wire package (#514)
|
||||
- Export DNS Seeds for each network from the chaincfg package (#544)
|
||||
- Preliminary work towards separating the memory pool into a separate
|
||||
package (#525, #548)
|
||||
- Misc changes:
|
||||
- Various documentation updates (#442, #462, #465, #460, #470, #473,
|
||||
#505, #530, #545)
|
||||
- Add installation instructions for gentoo (#542)
|
||||
- Ensure an error is shown if OS limits can't be set at startup (#498)
|
||||
- Tighten the standardness checks for multisig scripts (#526)
|
||||
- Test coverage improvement (#468, #494, #527, #543, #550)
|
||||
- Several optimizations (#457, #474, #475, #476, #508, #509)
|
||||
- Minor code cleanup and refactoring (#472, #479, #482, #519, #540)
|
||||
- Contributors (alphabetical order):
|
||||
- Ben Echols
|
||||
- Bruno Clermont
|
||||
- danda
|
||||
- Daniel Krawisz
|
||||
- Dario Nieuwenhuis
|
||||
- Dave Collins
|
||||
- David Hill
|
||||
- Javed Khan
|
||||
- Jonathan Gillham
|
||||
- Joseph Becher
|
||||
- Josh Rickmar
|
||||
- Justus Ranvier
|
||||
- Mawuli Adzoe
|
||||
- Olaoluwa Osuntokun
|
||||
- Rune T. Aune
|
||||
|
||||
Changes in 0.11.1 (Wed May 27 2015)
|
||||
- Protocol and network related changes:
|
||||
- Use correct sub-command in reject message for rejected transactions
|
||||
(#436, #437)
|
||||
- Add a new parameter --torisolation which forces new circuits for each
|
||||
connection when using tor (#430)
|
||||
- Transaction relay (memory pool) changes:
|
||||
- Reduce the default number max number of allowed orphan transactions
|
||||
to 1000 (#419)
|
||||
- Add a new parameter --maxorphantx which allows the maximum number of
|
||||
orphan transactions stored in the mempool to be specified (#419)
|
||||
- RPC changes:
|
||||
- Modify listtransactions result to include the 'involveswatchonly' and
|
||||
'vout' fields (#427)
|
||||
- Update getrawtransaction result to omit the 'confirmations' field
|
||||
when it is 0 (#420, #422)
|
||||
- Update signrawtransaction result to include errors (#423)
|
||||
- btcctl utility changes:
|
||||
- Add gettxoutproof command (#428)
|
||||
- Add verifytxoutproof command (#428)
|
||||
- Notable developer-related package changes:
|
||||
- The btcec package now provides the ability to perform ECDH
|
||||
encryption and decryption (#375)
|
||||
- The block and header validation in the blockchain package has been
|
||||
split to help pave the way toward concurrent downloads (#386)
|
||||
- Misc changes:
|
||||
- Minor peer optimization (#433)
|
||||
- Contributors (alphabetical order):
|
||||
- Dave Collins
|
||||
- David Hill
|
||||
- Federico Bond
|
||||
- Ishbir Singh
|
||||
- Josh Rickmar
|
||||
|
||||
Changes in 0.11.0 (Wed May 06 2015)
|
||||
- Protocol and network related changes:
|
||||
- **IMPORTANT: Update is required due to the following point**
|
||||
- Correct a few corner cases in script handling which could result in
|
||||
forking from the network on non-standard transactions (#425)
|
||||
- Add a new checkpoint at block height 352940 (#418)
|
||||
- Optimized script execution (#395, #400, #404, #409)
|
||||
- Fix a case that could lead stalled syncs (#138, #296)
|
||||
- Network address manager changes:
|
||||
- Implement eclipse attack countermeasures as proposed in
|
||||
http://cs-people.bu.edu/heilman/eclipse (#370, #373)
|
||||
- Optional address indexing changes:
|
||||
- Fix an issue where a reorg could cause an orderly shutdown when the
|
||||
address index is active (#340, #357)
|
||||
- Transaction relay (memory pool) changes:
|
||||
- Increase maximum allowed space for nulldata transactions to 80 bytes
|
||||
(#331)
|
||||
- Implement support for the following rules specified by BIP0062:
|
||||
- The S value in ECDSA signature must be at most half the curve order
|
||||
(rule 5) (#349)
|
||||
- Script execution must result in a single non-zero value on the stack
|
||||
(rule 6) (#347)
|
||||
- NOTE: All 7 rules of BIP0062 are now implemented
|
||||
- Use network adjusted time in finalized transaction checks to improve
|
||||
consistency across nodes (#332)
|
||||
- Process orphan transactions on acceptance of new transactions (#345)
|
||||
- RPC changes:
|
||||
- Add support for a limited RPC user which is not allowed admin level
|
||||
operations on the server (#363)
|
||||
- Implement node command for more unified control over connected peers
|
||||
(#79, #341)
|
||||
- Implement generate command for regtest/simnet to support
|
||||
deterministically mining a specified number of blocks (#362, #407)
|
||||
- Update searchrawtransactions to return the matching transactions in
|
||||
order (#354)
|
||||
- Correct an issue with searchrawtransactions where it could return
|
||||
duplicates (#346, #354)
|
||||
- Increase precision of 'difficulty' field in getblock result to 8
|
||||
(#414, #415)
|
||||
- Omit 'nextblockhash' field from getblock result when it is empty
|
||||
(#416, #417)
|
||||
- Add 'id' and 'timeoffset' fields to getpeerinfo result (#335)
|
||||
- Websocket changes:
|
||||
- Implement new commands stopnotifyspent, stopnotifyreceived,
|
||||
stopnotifyblocks, and stopnotifynewtransactions to allow clients to
|
||||
cancel notification registrations (#122, #342)
|
||||
- btcctl utility changes:
|
||||
- A single dash can now be used as an argument to cause that argument to
|
||||
be read from stdin (#348)
|
||||
- Add generate command
|
||||
- Notable developer-related package changes:
|
||||
- The new version 2 btcjson package has now replaced the deprecated
|
||||
version 1 package (#368)
|
||||
- The btcec package now performs all signing using RFC6979 deterministic
|
||||
signatures (#358, #360)
|
||||
- The txscript package has been significantly cleaned up and had a few
|
||||
API changes (#387, #388, #389, #390, #391, #392, #393, #395, #396,
|
||||
#400, #403, #404, #405, #406, #408, #409, #410, #412)
|
||||
- A new PkScriptLocs function has been added to the wire package MsgTx
|
||||
type which provides callers that deal with scripts optimization
|
||||
opportunities (#343)
|
||||
- Misc changes:
|
||||
- Minor wire hashing optimizations (#366, #367)
|
||||
- Other minor internal optimizations
|
||||
- Contributors (alphabetical order):
|
||||
- Alex Akselrod
|
||||
- Arne Brutschy
|
||||
- Chris Jepson
|
||||
- Daniel Krawisz
|
||||
- Dave Collins
|
||||
- David Hill
|
||||
- Jimmy Song
|
||||
- Jonas Nick
|
||||
- Josh Rickmar
|
||||
- Olaoluwa Osuntokun
|
||||
- Oleg Andreev
|
||||
|
||||
Changes in 0.10.0 (Sun Mar 01 2015)
|
||||
- Protocol and network related changes:
|
||||
- Add a new checkpoint at block height 343185
|
||||
- Implement BIP066 which includes support for version 3 blocks, a new
|
||||
consensus rule which prevents non-DER encoded signatures, and a
|
||||
double-threshold switchover mechanism
|
||||
- Rather than announcing all known addresses on getaddr requests which
|
||||
can possibly result in multiple messages, randomize the results and
|
||||
limit them to the max allowed by a single message (1000 addresses)
|
||||
- Add more reserved IP spaces to the address manager
|
||||
- Transaction relay (memory pool) changes:
|
||||
- Make transactions which contain reserved opcodes nonstandard
|
||||
- No longer accept or relay free and low-fee transactions that have
|
||||
insufficient priority to be mined in the next block
|
||||
- Implement support for the following rules specified by BIP0062:
|
||||
- ECDSA signature must use strict DER encoding (rule 1)
|
||||
- The signature script must only contain push operations (rule 2)
|
||||
- All push operations must use the smallest possible encoding (rule 3)
|
||||
- All stack values interpreted as a number must be encoding using the
|
||||
shortest possible form (rule 4)
|
||||
- NOTE: Rule 1 was already enforced, however the entire script now
|
||||
evaluates to false rather than only the signature verification as
|
||||
required by BIP0062
|
||||
- Allow transactions with nulldata transaction outputs to be treated as
|
||||
standard
|
||||
- Mining support changes:
|
||||
- Modify the getblocktemplate RPC to generate and return block templates
|
||||
for version 3 blocks which are compatible with BIP0066
|
||||
- Allow getblocktemplate to serve blocks when the current time is
|
||||
less than the minimum allowed time for a generated block template
|
||||
(https://github.com/btcsuite/btcd/issues/209)
|
||||
- Crypto changes:
|
||||
- Optimize scalar multiplication by the base point by using a
|
||||
pre-computed table which results in approximately a 35% speedup
|
||||
(https://github.com/btcsuite/btcec/issues/2)
|
||||
- Optimize general scalar multiplication by using the secp256k1
|
||||
endomorphism which results in approximately a 17-20% speedup
|
||||
(https://github.com/btcsuite/btcec/issues/1)
|
||||
- Optimize general scalar multiplication by using non-adjacent form
|
||||
which results in approximately an additional 8% speedup
|
||||
(https://github.com/btcsuite/btcec/issues/3)
|
||||
- Implement optional address indexing:
|
||||
- Add a new parameter --addrindex which will enable the creation of an
|
||||
address index which can be queried to determine all transactions which
|
||||
involve a given address
|
||||
(https://github.com/btcsuite/btcd/issues/190)
|
||||
- Add a new logging subsystem for address index related operations
|
||||
- Support new searchrawtransactions RPC
|
||||
(https://github.com/btcsuite/btcd/issues/185)
|
||||
- RPC changes:
|
||||
- Require TLS version 1.2 as the minimum version for all TLS connections
|
||||
- Provide support for disabling TLS when only listening on localhost
|
||||
(https://github.com/btcsuite/btcd/pull/192)
|
||||
- Modify help output for all commands to provide much more consistent
|
||||
and detailed information
|
||||
- Correct case in getrawtransaction which would refuse to serve certain
|
||||
transactions with invalid scripts
|
||||
(https://github.com/btcsuite/btcd/issues/210)
|
||||
- Correct error handling in the getrawtransaction RPC which could lead
|
||||
to a crash in rare cases
|
||||
(https://github.com/btcsuite/btcd/issues/196)
|
||||
- Update getinfo RPC to include the appropriate 'timeoffset' calculated
|
||||
from the median network time
|
||||
- Modify listreceivedbyaddress result type to include txids field so it
|
||||
is compatible
|
||||
- Add 'iswatchonly' field to validateaddress result
|
||||
- Add 'startingpriority' and 'currentpriority' fields to getrawmempool
|
||||
(https://github.com/btcsuite/btcd/issues/178)
|
||||
- Don't omit the 'confirmations' field from getrawtransaction when it is
|
||||
zero
|
||||
- Websocket changes:
|
||||
- Modify the behavior of the rescan command to automatically register
|
||||
for notifications about transactions paying to rescanned addresses
|
||||
or spending outputs from the final rescan utxo set when the rescan
|
||||
is through the best block in the chain
|
||||
- btcctl utility changes:
|
||||
- Make the list of commands available via the -l option rather than
|
||||
dumping the entire list on usage errors
|
||||
- Alphabetize and categorize the list of commands by chain and wallet
|
||||
- Make the help option only show the help options instead of also
|
||||
dumping all of the commands
|
||||
- Make the usage syntax much more consistent and correct a few cases of
|
||||
misnamed fields
|
||||
(https://github.com/btcsuite/btcd/issues/305)
|
||||
- Improve usage errors to show the specific parameter number, reason,
|
||||
and error code
|
||||
- Only show the usage for specific command is shown when a valid command
|
||||
is provided with invalid parameters
|
||||
- Add support for a SOCK5 proxy
|
||||
- Modify output for integer fields (such as timestamps) to display
|
||||
normally instead in scientific notation
|
||||
- Add invalidateblock command
|
||||
- Add reconsiderblock command
|
||||
- Add createnewaccount command
|
||||
- Add renameaccount command
|
||||
- Add searchrawtransactions command
|
||||
- Add importaddress command
|
||||
- Add importpubkey command
|
||||
- showblock utility changes:
|
||||
- Remove utility in favor of the RPC getblock method
|
||||
- Notable developer-related package changes:
|
||||
- Many of the core packages have been relocated into the btcd repository
|
||||
(https://github.com/btcsuite/btcd/issues/214)
|
||||
- A new version of the btcjson package that has been completely
|
||||
redesigned from the ground up based based upon how the project has
|
||||
evolved and lessons learned while using it since it was first written
|
||||
is now available in the btcjson/v2/btcjson directory
|
||||
- This will ultimately replace the current version so anyone making
|
||||
use of this package will need to update their code accordingly
|
||||
- The btcec package now provides better facilities for working directly
|
||||
with its public and private keys without having to mix elements from
|
||||
the ecdsa package
|
||||
- Update the script builder to ensure all rules specified by BIP0062 are
|
||||
adhered to when creating scripts
|
||||
- The blockchain package now provides a MedianTimeSource interface and
|
||||
concrete implementation for providing time samples from remote peers
|
||||
and using that data to calculate an offset against the local time
|
||||
- Misc changes:
|
||||
- Fix a slow memory leak due to tickers not being stopped
|
||||
(https://github.com/btcsuite/btcd/issues/189)
|
||||
- Fix an issue where a mix of orphans and SPV clients could trigger a
|
||||
condition where peers would no longer be served
|
||||
(https://github.com/btcsuite/btcd/issues/231)
|
||||
- The RPC username and password can now contain symbols which previously
|
||||
conflicted with special symbols used in URLs
|
||||
- Improve handling of obtaining random nonces to prevent cases where it
|
||||
could error when not enough entropy was available
|
||||
- Improve handling of home directory creation errors such as in the case
|
||||
of unmounted symlinks (https://github.com/btcsuite/btcd/issues/193)
|
||||
- Improve the error reporting for rejected transactions to include the
|
||||
inputs which are missing and/or being double spent
|
||||
- Update sample config file with new options and correct a comment
|
||||
regarding the fact the RPC server only listens on localhost by default
|
||||
(https://github.com/btcsuite/btcd/issues/218)
|
||||
- Update the continuous integration builds to run several tools which
|
||||
help keep code quality high
|
||||
- Significant amount of internal code cleanup and improvements
|
||||
- Other minor internal optimizations
|
||||
- Code Contributors (alphabetical order):
|
||||
- Beldur
|
||||
- Ben Holden-Crowther
|
||||
- Dave Collins
|
||||
- David Evans
|
||||
- David Hill
|
||||
- Guilherme Salgado
|
||||
- Javed Khan
|
||||
- Jimmy Song
|
||||
- John C. Vernaleo
|
||||
- Jonathan Gillham
|
||||
- Josh Rickmar
|
||||
- Michael Ford
|
||||
- Michail Kargakis
|
||||
- kac
|
||||
- Olaoluwa Osuntokun
|
||||
|
||||
Changes in 0.9.0 (Sat Sep 20 2014)
|
||||
- Protocol and network related changes:
|
||||
- Add a new checkpoint at block height 319400
|
||||
- Add support for BIP0037 bloom filters
|
||||
(https://github.com/conformal/btcd/issues/132)
|
||||
- Implement BIP0061 reject handling and hence support for protocol
|
||||
version 70002 (https://github.com/conformal/btcd/issues/133)
|
||||
- Add testnet DNS seeds for peer discovery (testnet-seed.alexykot.me
|
||||
and testnet-seed.bitcoin.schildbach.de)
|
||||
- Add mainnet DNS seed for peer discovery (seeds.bitcoin.open-nodes.org)
|
||||
- Make multisig transactions with non-null dummy data nonstandard
|
||||
(https://github.com/conformal/btcd/issues/131)
|
||||
- Make transactions with an excessive number of signature operations
|
||||
nonstandard
|
||||
- Perform initial DNS lookups concurrently which allows connections
|
||||
more quickly
|
||||
- Improve the address manager to significantly reduce memory usage and
|
||||
add tests
|
||||
- Remove orphan transactions when they appear in a mined block
|
||||
(https://github.com/conformal/btcd/issues/166)
|
||||
- Apply incremental back off on connection retries for persistent peers
|
||||
that give invalid replies to mirror the logic used for failed
|
||||
connections (https://github.com/conformal/btcd/issues/103)
|
||||
- Correct rate-limiting of free and low-fee transactions
|
||||
- Mining support changes:
|
||||
- Implement getblocktemplate RPC with the following support:
|
||||
(https://github.com/conformal/btcd/issues/124)
|
||||
- BIP0022 Non-Optional Sections
|
||||
- BIP0022 Long Polling
|
||||
- BIP0023 Basic Pool Extensions
|
||||
- BIP0023 Mutation coinbase/append
|
||||
- BIP0023 Mutations time, time/increment, and time/decrement
|
||||
- BIP0023 Mutation transactions/add
|
||||
- BIP0023 Mutations prevblock, coinbase, and generation
|
||||
- BIP0023 Block Proposals
|
||||
- Implement built-in concurrent CPU miner
|
||||
(https://github.com/conformal/btcd/issues/137)
|
||||
NOTE: CPU mining on mainnet is pointless. This has been provided
|
||||
for testing purposes such as for the new simulation test network
|
||||
- Add --generate flag to enable CPU mining
|
||||
- Deprecate the --getworkkey flag in favor of --miningaddr which
|
||||
specifies which addresses generated blocks will choose from to pay
|
||||
the subsidy to
|
||||
- RPC changes:
|
||||
- Implement gettxout command
|
||||
(https://github.com/conformal/btcd/issues/141)
|
||||
- Implement validateaddress command
|
||||
- Implement verifymessage command
|
||||
- Mark getunconfirmedbalance RPC as wallet-only
|
||||
- Mark getwalletinfo RPC as wallet-only
|
||||
- Update getgenerate, setgenerate, gethashespersec, and getmininginfo
|
||||
to return the appropriate information about new CPU mining status
|
||||
- Modify getpeerinfo pingtime and pingwait field types to float64 so
|
||||
they are compatible
|
||||
- Improve disconnect handling for normal HTTP clients
|
||||
- Make error code returns for invalid hex more consistent
|
||||
- Websocket changes:
|
||||
- Switch to a new more efficient websocket package
|
||||
(https://github.com/conformal/btcd/issues/134)
|
||||
- Add rescanfinished notification
|
||||
- Modify the rescanprogress notification to include block hash as well
|
||||
as height (https://github.com/conformal/btcd/issues/151)
|
||||
- btcctl utility changes:
|
||||
- Accept --simnet flag which automatically selects the appropriate port
|
||||
and TLS certificates needed to communicate with btcd and btcwallet on
|
||||
the simulation test network
|
||||
- Fix createrawtransaction command to send amounts denominated in BTC
|
||||
- Add estimatefee command
|
||||
- Add estimatepriority command
|
||||
- Add getmininginfo command
|
||||
- Add getnetworkinfo command
|
||||
- Add gettxout command
|
||||
- Add lockunspent command
|
||||
- Add signrawtransaction command
|
||||
- addblock utility changes:
|
||||
- Accept --simnet flag which automatically selects the appropriate port
|
||||
and TLS certificates needed to communicate with btcd and btcwallet on
|
||||
the simulation test network
|
||||
- Notable developer-related package changes:
|
||||
- Provide a new bloom package in btcutil which allows creating and
|
||||
working with BIP0037 bloom filters
|
||||
- Provide a new hdkeychain package in btcutil which allows working with
|
||||
BIP0032 hierarchical deterministic key chains
|
||||
- Introduce a new btcnet package which houses network parameters
|
||||
- Provide new simnet network (--simnet) which is useful for private
|
||||
simulation testing
|
||||
- Enforce low S values in serialized signatures as detailed in BIP0062
|
||||
- Return errors from all methods on the btcdb.Db interface
|
||||
(https://github.com/conformal/btcdb/issues/5)
|
||||
- Allow behavior flags to alter btcchain.ProcessBlock
|
||||
(https://github.com/conformal/btcchain/issues/5)
|
||||
- Provide a new SerializeSize API for blocks
|
||||
(https://github.com/conformal/btcwire/issues/19)
|
||||
- Several of the core packages now work with Google App Engine
|
||||
- Misc changes:
|
||||
- Correct an issue where the database could corrupt under certain
|
||||
circumstances which would require a new chain download
|
||||
- Slightly optimize deserialization
|
||||
- Use the correct IP block for he.net
|
||||
- Fix an issue where it was possible the block manager could hang on
|
||||
shutdown
|
||||
- Update sample config file so the comments are on a separate line
|
||||
rather than the end of a line so they are not interpreted as settings
|
||||
(https://github.com/conformal/btcd/issues/135)
|
||||
- Correct an issue where getdata requests were not being properly
|
||||
throttled which could lead to larger than necessary memory usage
|
||||
- Always show help when given the help flag even when the config file
|
||||
contains invalid entries
|
||||
- General code cleanup and minor optimizations
|
||||
|
||||
Changes in 0.8.0-beta (Sun May 25 2014)
|
||||
- Btcd is now Beta (https://github.com/conformal/btcd/issues/130)
|
||||
- Add a new checkpoint at block height 300255
|
||||
- Protocol and network related changes:
|
||||
- Lower the minimum transaction relay fee to 1000 satoshi to match
|
||||
recent reference client changes
|
||||
(https://github.com/conformal/btcd/issues/100)
|
||||
- Raise the maximum signature script size to support standard 15-of-15
|
||||
multi-signature pay-to-sript-hash transactions with compressed pubkeys
|
||||
to remain compatible with the reference client
|
||||
(https://github.com/conformal/btcd/issues/128)
|
||||
- Reduce max bytes allowed for a standard nulldata transaction to 40 for
|
||||
compatibility with the reference client
|
||||
- Introduce a new btcnet package which houses all of the network params
|
||||
for each network (mainnet, testnet, regtest) to ultimately enable
|
||||
easier addition and tweaking of networks without needing to change
|
||||
several packages
|
||||
- Fix several script discrepancies found by reference client test data
|
||||
- Add new DNS seed for peer discovery (seed.bitnodes.io)
|
||||
- Reduce the max known inventory cache from 20000 items to 1000 items
|
||||
- Fix an issue where unknown inventory types could lead to a hung peer
|
||||
- Implement inventory rebroadcast handler for sendrawtransaction
|
||||
(https://github.com/conformal/btcd/issues/99)
|
||||
- Update user agent to fully support BIP0014
|
||||
(https://github.com/conformal/btcwire/issues/10)
|
||||
- Implement initial mining support:
|
||||
- Add a new logging subsystem for mining related operations
|
||||
- Implement infrastructure for creating block templates
|
||||
- Provide options to control block template creation settings
|
||||
- Support the getwork RPC
|
||||
- Allow address identifiers to apply to more than one network since both
|
||||
testnet and the regression test network unfortunately use the same
|
||||
identifier
|
||||
- RPC changes:
|
||||
- Set the content type for HTTP POST RPC connections to application/json
|
||||
(https://github.com/conformal/btcd/issues/121)
|
||||
- Modified the RPC server startup so it only requires at least one valid
|
||||
listen interface
|
||||
- Correct an error path where it was possible certain errors would not
|
||||
be returned
|
||||
- Implement getwork command
|
||||
(https://github.com/conformal/btcd/issues/125)
|
||||
- Update sendrawtransaction command to reject orphans
|
||||
- Update sendrawtransaction command to include the reason a transaction
|
||||
was rejected
|
||||
- Update getinfo command to populate connection count field
|
||||
- Update getinfo command to include relay fee field
|
||||
(https://github.com/conformal/btcd/issues/107)
|
||||
- Allow transactions submitted with sendrawtransaction to bypass the
|
||||
rate limiter
|
||||
- Allow the getcurrentnet and getbestblock extensions to be accessed via
|
||||
HTTP POST in addition to Websockets
|
||||
(https://github.com/conformal/btcd/issues/127)
|
||||
- Websocket changes:
|
||||
- Rework notifications to ensure they are delivered in the order they
|
||||
occur
|
||||
- Rename notifynewtxs command to notifyreceived (funds received)
|
||||
- Rename notifyallnewtxs command to notifynewtransactions
|
||||
- Rename alltx notification to txaccepted
|
||||
- Rename allverbosetx notification to txacceptedverbose
|
||||
(https://github.com/conformal/btcd/issues/98)
|
||||
- Add rescan progress notification
|
||||
- Add recvtx notification
|
||||
- Add redeemingtx notification
|
||||
- Modify notifyspent command to accept an array of outpoints
|
||||
(https://github.com/conformal/btcd/issues/123)
|
||||
- Significantly optimize the rescan command to yield up to a 60x speed
|
||||
increase
|
||||
- btcctl utility changes:
|
||||
- Add createencryptedwallet command
|
||||
- Add getblockchaininfo command
|
||||
- Add importwallet command
|
||||
- Add addmultisigaddress command
|
||||
- Add setgenerate command
|
||||
- Accept --testnet and --wallet flags which automatically select
|
||||
the appropriate port and TLS certificates needed to communicate
|
||||
with btcd and btcwallet (https://github.com/conformal/btcd/issues/112)
|
||||
- Allow path expansion from config file entries
|
||||
(https://github.com/conformal/btcd/issues/113)
|
||||
- Minor refactor simplify handling of options
|
||||
- addblock utility changes:
|
||||
- Improve logging by making it consistent with the logging provided by
|
||||
btcd (https://github.com/conformal/btcd/issues/90)
|
||||
- Improve several package APIs for developers:
|
||||
- Add new amount type for consistently handling monetary values
|
||||
- Add new coin selector API
|
||||
- Add new WIF (Wallet Import Format) API
|
||||
- Add new crypto types for private keys and signatures
|
||||
- Add new API to sign transactions including script merging and hash
|
||||
types
|
||||
- Expose function to extract all pushed data from a script
|
||||
(https://github.com/conformal/btcscript/issues/8)
|
||||
- Misc changes:
|
||||
- Optimize address manager shuffling to do 67% less work on average
|
||||
- Resolve a couple of benign data races found by the race detector
|
||||
(https://github.com/conformal/btcd/issues/101)
|
||||
- Add IP address to all peer related errors to clarify which peer is the
|
||||
cause (https://github.com/conformal/btcd/issues/102)
|
||||
- Fix a UPNP case issue that prevented the --upnp option from working
|
||||
with some UPNP servers
|
||||
- Update documentation in the sample config file regarding debug levels
|
||||
- Adjust some logging levels to improve debug messages
|
||||
- Improve the throughput of query messages to the block manager
|
||||
- Several minor optimizations to reduce GC churn and enhance speed
|
||||
- Other minor refactoring
|
||||
- General code cleanup
|
||||
|
||||
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)
|
||||
- Remove deprecated sqlite as an available database backend
|
||||
- Close sqlite compile issue as sqlite has now been removed
|
||||
(https://github.com/conformal/btcd/issues/11)
|
||||
- Change default RPC ports to 8334 (mainnet) and 18334 (testnet)
|
||||
- Continue cleanup and work on implementing RPC API calls
|
||||
- Add support for the following RPC calls: getrawmempool,
|
||||
getbestblockhash, decoderawtransaction, getdifficulty,
|
||||
getconnectioncount, getpeerinfo, and addnode
|
||||
- Improve the btcctl utility that is used to issue JSON-RPC commands
|
||||
- Fix an issue preventing btcd from cleanly shutting down with the RPC
|
||||
stop command
|
||||
- Add a number of database interface tests to ensure backends implement
|
||||
the expected interface
|
||||
- Expose some additional information from btcscript to be used for
|
||||
identifying "standard"" transactions
|
||||
- Add support for plan9 - thanks @mischief
|
||||
(https://github.com/conformal/btcd/pull/19)
|
||||
- Other minor bug fixes and general code cleanup
|
||||
|
||||
Changes in 0.3.1-alpha (Tue Oct 15 2013)
|
||||
- Change default database to leveldb
|
||||
NOTE: This does mean you will have to redownload the block chain. Since we
|
||||
are still in alpha, we didn't feel writing a converter was worth the time as
|
||||
it would take away from more important issues at this stage
|
||||
- Add a warning if there are multiple block chain databases of different types
|
||||
- Fix issue with unexpected EOF in leveldb -- https://github.com/conformal/btcd/issues/18
|
||||
- Fix issue preventing block 21066 on testnet -- https://github.com/conformal/btcchain/issues/1
|
||||
- Fix issue preventing block 96464 on testnet -- https://github.com/conformal/btcscript/issues/1
|
||||
- Optimize transaction lookups
|
||||
- Correct a few cases of list removal that could result in improper cleanup
|
||||
of no longer needed orphans
|
||||
- Add functionality to increase ulimits on non-Windows platforms
|
||||
- Add support for mempool command which allows remote peers to query the
|
||||
transaction memory pool via the bitcoin protocol
|
||||
- Clean up logging a bit
|
||||
- Add a flag to disable checkpoints for developers
|
||||
- Add a lot of useful debug logging such as message summaries
|
||||
- Other minor bug fixes and general code cleanup
|
||||
|
||||
Initial Release 0.3.0-alpha (Sat Oct 05 2013):
|
||||
- Initial release
|
||||
10
Jenkinsfile
vendored
Normal file
10
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
node {
|
||||
stage 'Checkout'
|
||||
checkout scm
|
||||
|
||||
stage 'Version'
|
||||
sh './deploy.sh version'
|
||||
|
||||
stage 'Build'
|
||||
sh "./deploy.sh build"
|
||||
}
|
||||
5
LICENSE
5
LICENSE
@@ -1,9 +1,8 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2018-2019 The kaspanet developers
|
||||
Copyright (c) 2013-2018 The btcsuite developers
|
||||
Copyright (c) 2018-2019 DAGLabs
|
||||
Copyright (c) 2013-20 18 The btcsuite developers
|
||||
Copyright (c) 2015-2016 The Decred developers
|
||||
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
|
||||
|
||||
123
README.md
123
README.md
@@ -1,24 +1,49 @@
|
||||
|
||||
Kaspad
|
||||
====
|
||||
Warning: This is pre-alpha software. There's no guarantee anything works.
|
||||
btcd
|
||||
====
|
||||
|
||||
[](https://choosealicense.com/licenses/isc/)
|
||||
[](http://godoc.org/github.com/kaspanet/kaspad)
|
||||
[](https://travis-ci.org/btcsuite/btcd)
|
||||
[](http://copyfree.org)
|
||||
[](http://godoc.org/github.com/daglabs/btcd)
|
||||
|
||||
Kaspad is the reference full node Kaspa implementation written in Go (golang).
|
||||
btcd is an alternative full node bitcoin implementation written in Go (golang).
|
||||
|
||||
This project is currently under active development and is in a pre-Alpha state.
|
||||
Some things still don't work and APIs are far from finalized. The code is provided for reference only.
|
||||
This project is currently under active development and is in a Beta state. It
|
||||
is extremely stable and has been in production use since October 2013.
|
||||
|
||||
It properly downloads, validates, and serves the block chain using the exact
|
||||
rules (including consensus bugs) for block acceptance as Bitcoin Core. We have
|
||||
taken great care to avoid btcd causing a fork to the block chain. It includes a
|
||||
full block validation testing framework which contains all of the 'official'
|
||||
block acceptance tests (and some additional ones) that is run on every pull
|
||||
request to help ensure it properly follows consensus. Also, it passes all of
|
||||
the JSON test data in the Bitcoin Core code.
|
||||
|
||||
It also properly relays newly mined blocks, maintains a transaction pool, and
|
||||
relays individual transactions that have not yet made it into a block. It
|
||||
ensures all individual transactions admitted to the pool follow the rules
|
||||
required by the block chain and also includes more strict checks which filter
|
||||
transactions based on miner requirements ("standard" transactions).
|
||||
|
||||
One key difference between btcd and Bitcoin Core 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 is provided by the
|
||||
[btcwallet](https://github.com/btcsuite/btcwallet) and
|
||||
[Paymetheus](https://github.com/btcsuite/Paymetheus) (Windows-only) projects
|
||||
which are both under active development.
|
||||
|
||||
## Requirements
|
||||
|
||||
Latest version of [Go](http://golang.org) (currently 1.13).
|
||||
[Go](http://golang.org) 1.8 or newer.
|
||||
|
||||
## Installation
|
||||
|
||||
#### Build from Source
|
||||
#### Windows - MSI Available
|
||||
|
||||
https://github.com/daglabs/btcd/releases
|
||||
|
||||
#### Linux/BSD/MacOSX/POSIX - Build from Source
|
||||
|
||||
- Install Go according to the installation instructions here:
|
||||
http://golang.org/doc/install
|
||||
@@ -30,48 +55,92 @@ $ go version
|
||||
$ go env GOROOT GOPATH
|
||||
```
|
||||
|
||||
NOTE: The `GOROOT` and `GOPATH` above must not be the same path. It is
|
||||
NOTE: The `GOROOT` and `GOPATH` above must not be the same path. It is
|
||||
recommended that `GOPATH` is set to a directory in your home directory such as
|
||||
`~/dev/go` to avoid write permission issues. It is also recommended to add
|
||||
`~/goprojects` to avoid write permission issues. It is also recommended to add
|
||||
`$GOPATH/bin` to your `PATH` at this point.
|
||||
|
||||
- Run the following commands to obtain and install kaspad including all dependencies:
|
||||
- Run the following commands to obtain btcd, all dependencies, and install it:
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/kaspanet/kaspad $GOPATH/src/github.com/kaspanet/kaspad
|
||||
$ cd $GOPATH/src/github.com/kaspanet/kaspad
|
||||
$ # Install dep: https://golang.github.io/dep/docs/installation.html
|
||||
$ git clone https://github.com/daglabs/btcd $GOPATH/src/github.com/daglabs/btcd
|
||||
$ cd $GOPATH/src/github.com/daglabs/btcd
|
||||
$ dep ensure
|
||||
$ go install . ./cmd/...
|
||||
```
|
||||
|
||||
- Kaspad (and utilities) should now be installed in `$GOPATH/bin`. If you did
|
||||
- btcd (and utilities) will now be installed in ```$GOPATH/bin```. If you did
|
||||
not already add the bin directory to your system path during Go installation,
|
||||
you are encouraged to do so now.
|
||||
we recommend you do so now.
|
||||
|
||||
## Updating
|
||||
|
||||
#### Windows
|
||||
|
||||
Install a newer MSI
|
||||
|
||||
#### Linux/BSD/MacOSX/POSIX - Build from Source
|
||||
|
||||
- Run the following commands to update btcd, all dependencies, and install it:
|
||||
|
||||
```bash
|
||||
$ cd $GOPATH/src/github.com/daglabs/btcd
|
||||
$ git pull && dep ensure
|
||||
$ go install . ./cmd/...
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
Kaspad has several configuration options available to tweak how it runs, but all
|
||||
of the basic operations work with zero configuration.
|
||||
btcd has several configuration options available to tweak how it runs, but all
|
||||
of the basic operations described in the intro section work with zero
|
||||
configuration.
|
||||
|
||||
#### Windows (Installed from MSI)
|
||||
|
||||
Launch btcd from your Start menu.
|
||||
|
||||
#### Linux/BSD/POSIX/Source
|
||||
|
||||
```bash
|
||||
$ ./kaspad
|
||||
$ ./btcd
|
||||
```
|
||||
|
||||
## Discord
|
||||
Join our discord server using the following link: https://discord.gg/WmGhhzk
|
||||
## IRC
|
||||
|
||||
- irc.freenode.net
|
||||
- channel #btcd
|
||||
- [webchat](https://webchat.freenode.net/?channels=btcd)
|
||||
|
||||
## Issue Tracker
|
||||
|
||||
The [integrated github issue tracker](https://github.com/kaspanet/kaspad/issues)
|
||||
The [integrated github issue tracker](https://github.com/daglabs/btcd/issues)
|
||||
is used for this project.
|
||||
|
||||
## Documentation
|
||||
|
||||
The documentation is a work-in-progress. It is located in the [docs](https://github.com/kaspanet/kaspad/tree/master/docs) folder.
|
||||
The documentation is a work-in-progress. It is located in the [docs](https://github.com/daglabs/btcd/tree/master/docs) folder.
|
||||
|
||||
## GPG Verification Key
|
||||
|
||||
All official release tags are signed by Conformal so users can ensure the code
|
||||
has not been tampered with and is coming from the btcsuite developers. To
|
||||
verify the signature perform the following:
|
||||
|
||||
- Download the Conformal public key:
|
||||
https://raw.githubusercontent.com/btcsuite/btcd/master/release/GIT-GPG-KEY-conformal.txt
|
||||
|
||||
- Import the public key into your GPG keyring:
|
||||
```bash
|
||||
gpg --import GIT-GPG-KEY-conformal.txt
|
||||
```
|
||||
|
||||
- Verify the release tag with the following command where `TAG_NAME` is a
|
||||
placeholder for the specific tag:
|
||||
```bash
|
||||
git tag -v TAG_NAME
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Kaspad is licensed under the copyfree [ISC License](https://choosealicense.com/licenses/isc/).
|
||||
|
||||
btcd is licensed under the [copyfree](http://copyfree.org) ISC License.
|
||||
|
||||
1404
addrmgr/addrmanager.go
Normal file
1404
addrmgr/addrmanager.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,30 +2,26 @@
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package addressmanager
|
||||
package addrmgr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/db/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
"github.com/daglabs/btcd/util/subnetworkid"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
)
|
||||
|
||||
// naTest is used to describe a test to be performed against the NetAddressKey
|
||||
// method.
|
||||
type naTest struct {
|
||||
in appmessage.NetAddress
|
||||
want AddressKey
|
||||
in wire.NetAddress
|
||||
want string
|
||||
}
|
||||
|
||||
// naTests houses all of the tests to be performed against the NetAddressKey
|
||||
@@ -39,125 +35,92 @@ var someIP = "173.194.115.66"
|
||||
func addNaTests() {
|
||||
// IPv4
|
||||
// Localhost
|
||||
addNaTest("127.0.0.1", 16111, "127.0.0.1:16111")
|
||||
addNaTest("127.0.0.1", 16110, "127.0.0.1:16110")
|
||||
addNaTest("127.0.0.1", 8333, "127.0.0.1:8333")
|
||||
addNaTest("127.0.0.1", 8334, "127.0.0.1:8334")
|
||||
|
||||
// Class A
|
||||
addNaTest("1.0.0.1", 16111, "1.0.0.1:16111")
|
||||
addNaTest("2.2.2.2", 16110, "2.2.2.2:16110")
|
||||
addNaTest("1.0.0.1", 8333, "1.0.0.1:8333")
|
||||
addNaTest("2.2.2.2", 8334, "2.2.2.2:8334")
|
||||
addNaTest("27.253.252.251", 8335, "27.253.252.251:8335")
|
||||
addNaTest("123.3.2.1", 8336, "123.3.2.1:8336")
|
||||
|
||||
// Private Class A
|
||||
addNaTest("10.0.0.1", 16111, "10.0.0.1:16111")
|
||||
addNaTest("10.1.1.1", 16110, "10.1.1.1:16110")
|
||||
addNaTest("10.0.0.1", 8333, "10.0.0.1:8333")
|
||||
addNaTest("10.1.1.1", 8334, "10.1.1.1:8334")
|
||||
addNaTest("10.2.2.2", 8335, "10.2.2.2:8335")
|
||||
addNaTest("10.10.10.10", 8336, "10.10.10.10:8336")
|
||||
|
||||
// Class B
|
||||
addNaTest("128.0.0.1", 16111, "128.0.0.1:16111")
|
||||
addNaTest("129.1.1.1", 16110, "129.1.1.1:16110")
|
||||
addNaTest("128.0.0.1", 8333, "128.0.0.1:8333")
|
||||
addNaTest("129.1.1.1", 8334, "129.1.1.1:8334")
|
||||
addNaTest("180.2.2.2", 8335, "180.2.2.2:8335")
|
||||
addNaTest("191.10.10.10", 8336, "191.10.10.10:8336")
|
||||
|
||||
// Private Class B
|
||||
addNaTest("172.16.0.1", 16111, "172.16.0.1:16111")
|
||||
addNaTest("172.16.1.1", 16110, "172.16.1.1:16110")
|
||||
addNaTest("172.16.0.1", 8333, "172.16.0.1:8333")
|
||||
addNaTest("172.16.1.1", 8334, "172.16.1.1:8334")
|
||||
addNaTest("172.16.2.2", 8335, "172.16.2.2:8335")
|
||||
addNaTest("172.16.172.172", 8336, "172.16.172.172:8336")
|
||||
|
||||
// Class C
|
||||
addNaTest("193.0.0.1", 16111, "193.0.0.1:16111")
|
||||
addNaTest("200.1.1.1", 16110, "200.1.1.1:16110")
|
||||
addNaTest("193.0.0.1", 8333, "193.0.0.1:8333")
|
||||
addNaTest("200.1.1.1", 8334, "200.1.1.1:8334")
|
||||
addNaTest("205.2.2.2", 8335, "205.2.2.2:8335")
|
||||
addNaTest("223.10.10.10", 8336, "223.10.10.10:8336")
|
||||
|
||||
// Private Class C
|
||||
addNaTest("192.168.0.1", 16111, "192.168.0.1:16111")
|
||||
addNaTest("192.168.1.1", 16110, "192.168.1.1:16110")
|
||||
addNaTest("192.168.0.1", 8333, "192.168.0.1:8333")
|
||||
addNaTest("192.168.1.1", 8334, "192.168.1.1:8334")
|
||||
addNaTest("192.168.2.2", 8335, "192.168.2.2:8335")
|
||||
addNaTest("192.168.192.192", 8336, "192.168.192.192:8336")
|
||||
|
||||
// IPv6
|
||||
// Localhost
|
||||
addNaTest("::1", 16111, "[::1]:16111")
|
||||
addNaTest("fe80::1", 16110, "[fe80::1]:16110")
|
||||
addNaTest("::1", 8333, "[::1]:8333")
|
||||
addNaTest("fe80::1", 8334, "[fe80::1]:8334")
|
||||
|
||||
// Link-local
|
||||
addNaTest("fe80::1:1", 16111, "[fe80::1:1]:16111")
|
||||
addNaTest("fe91::2:2", 16110, "[fe91::2:2]:16110")
|
||||
addNaTest("fe80::1:1", 8333, "[fe80::1:1]:8333")
|
||||
addNaTest("fe91::2:2", 8334, "[fe91::2:2]:8334")
|
||||
addNaTest("fea2::3:3", 8335, "[fea2::3:3]:8335")
|
||||
addNaTest("feb3::4:4", 8336, "[feb3::4:4]:8336")
|
||||
|
||||
// Site-local
|
||||
addNaTest("fec0::1:1", 16111, "[fec0::1:1]:16111")
|
||||
addNaTest("fed1::2:2", 16110, "[fed1::2:2]:16110")
|
||||
addNaTest("fec0::1:1", 8333, "[fec0::1:1]:8333")
|
||||
addNaTest("fed1::2:2", 8334, "[fed1::2:2]:8334")
|
||||
addNaTest("fee2::3:3", 8335, "[fee2::3:3]:8335")
|
||||
addNaTest("fef3::4:4", 8336, "[fef3::4:4]:8336")
|
||||
}
|
||||
|
||||
func addNaTest(ip string, port uint16, want AddressKey) {
|
||||
func addNaTest(ip string, port uint16, want string) {
|
||||
nip := net.ParseIP(ip)
|
||||
na := *appmessage.NewNetAddressIPPort(nip, port, appmessage.SFNodeNetwork)
|
||||
na := *wire.NewNetAddressIPPort(nip, port, wire.SFNodeNetwork)
|
||||
test := naTest{na, want}
|
||||
naTests = append(naTests, test)
|
||||
}
|
||||
|
||||
func lookupFuncForTest(host string) ([]net.IP, error) {
|
||||
func lookupFunc(host string) ([]net.IP, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func newAddrManagerForTest(t *testing.T, testName string,
|
||||
localSubnetworkID *subnetworkid.SubnetworkID) (addressManager *AddressManager, teardown func()) {
|
||||
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.SubnetworkID = localSubnetworkID
|
||||
|
||||
dbPath, err := ioutil.TempDir("", testName)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating temporary directory: %s", err)
|
||||
}
|
||||
|
||||
databaseContext, err := dbaccess.New(dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating db: %s", err)
|
||||
}
|
||||
|
||||
addressManager, err = New(cfg, databaseContext)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating address manager: %s", err)
|
||||
}
|
||||
|
||||
return addressManager, func() {
|
||||
err := databaseContext.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("error closing the database: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartStop(t *testing.T) {
|
||||
amgr, teardown := newAddrManagerForTest(t, "TestStartStop", nil)
|
||||
defer teardown()
|
||||
err := amgr.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Address Manager failed to start: %v", err)
|
||||
}
|
||||
err = amgr.Stop()
|
||||
n := New("teststartstop", lookupFunc, nil)
|
||||
n.Start()
|
||||
err := n.Stop()
|
||||
if err != nil {
|
||||
t.Fatalf("Address Manager failed to stop: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddAddressByIP(t *testing.T) {
|
||||
fmtErr := errors.Errorf("")
|
||||
fmtErr := fmt.Errorf("")
|
||||
addrErr := &net.AddrError{}
|
||||
var tests = []struct {
|
||||
addrIP string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
someIP + ":16111",
|
||||
someIP + ":8333",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
@@ -174,20 +137,19 @@ func TestAddAddressByIP(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
amgr, teardown := newAddrManagerForTest(t, "TestAddAddressByIP", nil)
|
||||
defer teardown()
|
||||
amgr := New("testaddressbyip", nil, nil)
|
||||
for i, test := range tests {
|
||||
err := AddAddressByIP(amgr, test.addrIP, nil)
|
||||
err := amgr.AddAddressByIP(test.addrIP, nil)
|
||||
if test.err != nil && err == nil {
|
||||
t.Errorf("TestAddAddressByIP test %d failed expected an error and got none", i)
|
||||
t.Errorf("TestGood test %d failed expected an error and got none", i)
|
||||
continue
|
||||
}
|
||||
if test.err == nil && err != nil {
|
||||
t.Errorf("TestAddAddressByIP test %d failed expected no error and got one", i)
|
||||
t.Errorf("TestGood test %d failed expected no error and got one", i)
|
||||
continue
|
||||
}
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("TestAddAddressByIP test %d failed got %v, want %v", i,
|
||||
t.Errorf("TestGood test %d failed got %v, want %v", i,
|
||||
reflect.TypeOf(err), reflect.TypeOf(test.err))
|
||||
continue
|
||||
}
|
||||
@@ -196,43 +158,42 @@ func TestAddAddressByIP(t *testing.T) {
|
||||
|
||||
func TestAddLocalAddress(t *testing.T) {
|
||||
var tests = []struct {
|
||||
address appmessage.NetAddress
|
||||
address wire.NetAddress
|
||||
priority AddressPriority
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
appmessage.NetAddress{IP: net.ParseIP("192.168.0.100")},
|
||||
wire.NetAddress{IP: net.ParseIP("192.168.0.100")},
|
||||
InterfacePrio,
|
||||
false,
|
||||
},
|
||||
{
|
||||
appmessage.NetAddress{IP: net.ParseIP("204.124.1.1")},
|
||||
wire.NetAddress{IP: net.ParseIP("204.124.1.1")},
|
||||
InterfacePrio,
|
||||
true,
|
||||
},
|
||||
{
|
||||
appmessage.NetAddress{IP: net.ParseIP("204.124.1.1")},
|
||||
wire.NetAddress{IP: net.ParseIP("204.124.1.1")},
|
||||
BoundPrio,
|
||||
true,
|
||||
},
|
||||
{
|
||||
appmessage.NetAddress{IP: net.ParseIP("::1")},
|
||||
wire.NetAddress{IP: net.ParseIP("::1")},
|
||||
InterfacePrio,
|
||||
false,
|
||||
},
|
||||
{
|
||||
appmessage.NetAddress{IP: net.ParseIP("fe80::1")},
|
||||
wire.NetAddress{IP: net.ParseIP("fe80::1")},
|
||||
InterfacePrio,
|
||||
false,
|
||||
},
|
||||
{
|
||||
appmessage.NetAddress{IP: net.ParseIP("2620:100::1")},
|
||||
wire.NetAddress{IP: net.ParseIP("2620:100::1")},
|
||||
InterfacePrio,
|
||||
true,
|
||||
},
|
||||
}
|
||||
amgr, teardown := newAddrManagerForTest(t, "TestAddLocalAddress", nil)
|
||||
defer teardown()
|
||||
amgr := New("testaddlocaladdress", nil, nil)
|
||||
for x, test := range tests {
|
||||
result := amgr.AddLocalAddress(&test.address, test.priority)
|
||||
if result == nil && !test.valid {
|
||||
@@ -249,22 +210,21 @@ func TestAddLocalAddress(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAttempt(t *testing.T) {
|
||||
amgr, teardown := newAddrManagerForTest(t, "TestAttempt", nil)
|
||||
defer teardown()
|
||||
n := New("testattempt", lookupFunc, nil)
|
||||
|
||||
// Add a new address and get it
|
||||
err := AddAddressByIP(amgr, someIP+":8333", nil)
|
||||
err := n.AddAddressByIP(someIP+":8333", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Adding address failed: %v", err)
|
||||
}
|
||||
ka := amgr.GetAddress()
|
||||
ka := n.GetAddress()
|
||||
|
||||
if !ka.LastAttempt().IsZero() {
|
||||
t.Errorf("Address should not have attempts, but does")
|
||||
}
|
||||
|
||||
na := ka.NetAddress()
|
||||
amgr.Attempt(na)
|
||||
n.Attempt(na)
|
||||
|
||||
if ka.LastAttempt().IsZero() {
|
||||
t.Errorf("Address should have an attempt, but does not")
|
||||
@@ -272,20 +232,19 @@ func TestAttempt(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnected(t *testing.T) {
|
||||
amgr, teardown := newAddrManagerForTest(t, "TestConnected", nil)
|
||||
defer teardown()
|
||||
n := New("testconnected", lookupFunc, nil)
|
||||
|
||||
// Add a new address and get it
|
||||
err := AddAddressByIP(amgr, someIP+":8333", nil)
|
||||
err := n.AddAddressByIP(someIP+":8333", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Adding address failed: %v", err)
|
||||
}
|
||||
ka := amgr.GetAddress()
|
||||
ka := n.GetAddress()
|
||||
na := ka.NetAddress()
|
||||
// make it an hour ago
|
||||
na.Timestamp = mstime.Now().Add(time.Hour * -1)
|
||||
na.Timestamp = time.Unix(time.Now().Add(time.Hour*-1).Unix(), 0)
|
||||
|
||||
amgr.Connected(na)
|
||||
n.Connected(na)
|
||||
|
||||
if !ka.NetAddress().Timestamp.After(na.Timestamp) {
|
||||
t.Errorf("Address should have a new timestamp, but does not")
|
||||
@@ -293,50 +252,48 @@ func TestConnected(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNeedMoreAddresses(t *testing.T) {
|
||||
amgr, teardown := newAddrManagerForTest(t, "TestNeedMoreAddresses", nil)
|
||||
defer teardown()
|
||||
n := New("testneedmoreaddresses", lookupFunc, nil)
|
||||
addrsToAdd := 1500
|
||||
b := amgr.NeedMoreAddresses()
|
||||
b := n.NeedMoreAddresses()
|
||||
if !b {
|
||||
t.Errorf("Expected that we need more addresses")
|
||||
}
|
||||
addrs := make([]*appmessage.NetAddress, addrsToAdd)
|
||||
addrs := make([]*wire.NetAddress, addrsToAdd)
|
||||
|
||||
var err error
|
||||
for i := 0; i < addrsToAdd; i++ {
|
||||
s := AddressKey(fmt.Sprintf("%d.%d.173.147:8333", i/128+60, i%128+60))
|
||||
addrs[i], err = amgr.DeserializeNetAddress(s)
|
||||
s := fmt.Sprintf("%d.%d.173.147:8333", i/128+60, i%128+60)
|
||||
addrs[i], err = n.DeserializeNetAddress(s)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to turn %s into an address: %v", s, err)
|
||||
}
|
||||
}
|
||||
|
||||
srcAddr := appmessage.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
||||
srcAddr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
||||
|
||||
amgr.AddAddresses(addrs, srcAddr, nil)
|
||||
numAddrs := amgr.TotalNumAddresses()
|
||||
n.AddAddresses(addrs, srcAddr, nil)
|
||||
numAddrs := n.TotalNumAddresses()
|
||||
if numAddrs > addrsToAdd {
|
||||
t.Errorf("Number of addresses is too many %d vs %d", numAddrs, addrsToAdd)
|
||||
}
|
||||
|
||||
b = amgr.NeedMoreAddresses()
|
||||
b = n.NeedMoreAddresses()
|
||||
if b {
|
||||
t.Errorf("Expected that we don't need more addresses")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGood(t *testing.T) {
|
||||
amgr, teardown := newAddrManagerForTest(t, "TestGood", nil)
|
||||
defer teardown()
|
||||
n := New("testgood", lookupFunc, nil)
|
||||
addrsToAdd := 64 * 64
|
||||
addrs := make([]*appmessage.NetAddress, addrsToAdd)
|
||||
addrs := make([]*wire.NetAddress, addrsToAdd)
|
||||
subnetworkCount := 32
|
||||
subnetworkIDs := make([]*subnetworkid.SubnetworkID, subnetworkCount)
|
||||
|
||||
var err error
|
||||
for i := 0; i < addrsToAdd; i++ {
|
||||
s := AddressKey(fmt.Sprintf("%d.173.147.%d:8333", i/64+60, i%64+60))
|
||||
addrs[i], err = amgr.DeserializeNetAddress(s)
|
||||
s := fmt.Sprintf("%d.173.147.%d:8333", i/64+60, i%64+60)
|
||||
addrs[i], err = n.DeserializeNetAddress(s)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to turn %s into an address: %v", s, err)
|
||||
}
|
||||
@@ -346,26 +303,26 @@ func TestGood(t *testing.T) {
|
||||
subnetworkIDs[i] = &subnetworkid.SubnetworkID{0xff - byte(i)}
|
||||
}
|
||||
|
||||
srcAddr := appmessage.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
||||
srcAddr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
||||
|
||||
amgr.AddAddresses(addrs, srcAddr, nil)
|
||||
n.AddAddresses(addrs, srcAddr, nil)
|
||||
for i, addr := range addrs {
|
||||
amgr.Good(addr, subnetworkIDs[i%subnetworkCount])
|
||||
n.Good(addr, subnetworkIDs[i%subnetworkCount])
|
||||
}
|
||||
|
||||
numAddrs := amgr.TotalNumAddresses()
|
||||
numAddrs := n.TotalNumAddresses()
|
||||
if numAddrs >= addrsToAdd {
|
||||
t.Errorf("Number of addresses is too many: %d vs %d", numAddrs, addrsToAdd)
|
||||
}
|
||||
|
||||
numCache := len(amgr.AddressCache(true, nil))
|
||||
numCache := len(n.AddressCache(true, nil))
|
||||
if numCache == 0 || numCache >= numAddrs/4 {
|
||||
t.Errorf("Number of addresses in cache: got %d, want positive and less than %d",
|
||||
numCache, numAddrs/4)
|
||||
}
|
||||
|
||||
for i := 0; i < subnetworkCount; i++ {
|
||||
numCache = len(amgr.AddressCache(false, subnetworkIDs[i]))
|
||||
numCache = len(n.AddressCache(false, subnetworkIDs[i]))
|
||||
if numCache == 0 || numCache >= numAddrs/subnetworkCount {
|
||||
t.Errorf("Number of addresses in subnetwork cache: got %d, want positive and less than %d",
|
||||
numCache, numAddrs/4/subnetworkCount)
|
||||
@@ -374,18 +331,17 @@ func TestGood(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGoodChangeSubnetworkID(t *testing.T) {
|
||||
amgr, teardown := newAddrManagerForTest(t, "TestGoodChangeSubnetworkID", nil)
|
||||
defer teardown()
|
||||
addr := appmessage.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
||||
n := New("test_good_change_subnetwork_id", lookupFunc, nil)
|
||||
addr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
||||
addrKey := NetAddressKey(addr)
|
||||
srcAddr := appmessage.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
||||
srcAddr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
||||
|
||||
oldSubnetwork := subnetworkid.SubnetworkIDNative
|
||||
amgr.AddAddress(addr, srcAddr, oldSubnetwork)
|
||||
amgr.Good(addr, oldSubnetwork)
|
||||
n.AddAddress(addr, srcAddr, oldSubnetwork)
|
||||
n.Good(addr, oldSubnetwork)
|
||||
|
||||
// make sure address was saved to addressIndex under oldSubnetwork
|
||||
ka := amgr.knownAddress(addr)
|
||||
// make sure address was saved to addrIndex under oldSubnetwork
|
||||
ka := n.find(addr)
|
||||
if ka == nil {
|
||||
t.Fatalf("Address was not found after first time .Good called")
|
||||
}
|
||||
@@ -394,10 +350,10 @@ func TestGoodChangeSubnetworkID(t *testing.T) {
|
||||
}
|
||||
|
||||
// make sure address was added to correct bucket under oldSubnetwork
|
||||
bucket := amgr.subnetworkTriedAddresBucketArrays[*oldSubnetwork][amgr.triedAddressBucketIndex(addr)]
|
||||
bucket := n.addrTried[*oldSubnetwork][n.getTriedBucket(addr)]
|
||||
wasFound := false
|
||||
for _, ka := range bucket {
|
||||
if NetAddressKey(ka.NetAddress()) == addrKey {
|
||||
for e := bucket.Front(); e != nil; e = e.Next() {
|
||||
if NetAddressKey(e.Value.(*KnownAddress).NetAddress()) == addrKey {
|
||||
wasFound = true
|
||||
}
|
||||
}
|
||||
@@ -407,10 +363,10 @@ func TestGoodChangeSubnetworkID(t *testing.T) {
|
||||
|
||||
// now call .Good again with a different subnetwork
|
||||
newSubnetwork := subnetworkid.SubnetworkIDRegistry
|
||||
amgr.Good(addr, newSubnetwork)
|
||||
n.Good(addr, newSubnetwork)
|
||||
|
||||
// make sure address was updated in addressIndex under newSubnetwork
|
||||
ka = amgr.knownAddress(addr)
|
||||
// make sure address was updated in addrIndex under newSubnetwork
|
||||
ka = n.find(addr)
|
||||
if ka == nil {
|
||||
t.Fatalf("Address was not found after second time .Good called")
|
||||
}
|
||||
@@ -419,10 +375,10 @@ func TestGoodChangeSubnetworkID(t *testing.T) {
|
||||
}
|
||||
|
||||
// make sure address was removed from bucket under oldSubnetwork
|
||||
bucket = amgr.subnetworkTriedAddresBucketArrays[*oldSubnetwork][amgr.triedAddressBucketIndex(addr)]
|
||||
bucket = n.addrTried[*oldSubnetwork][n.getTriedBucket(addr)]
|
||||
wasFound = false
|
||||
for _, ka := range bucket {
|
||||
if NetAddressKey(ka.NetAddress()) == addrKey {
|
||||
for e := bucket.Front(); e != nil; e = e.Next() {
|
||||
if NetAddressKey(e.Value.(*KnownAddress).NetAddress()) == addrKey {
|
||||
wasFound = true
|
||||
}
|
||||
}
|
||||
@@ -431,10 +387,10 @@ func TestGoodChangeSubnetworkID(t *testing.T) {
|
||||
}
|
||||
|
||||
// make sure address was added to correct bucket under newSubnetwork
|
||||
bucket = amgr.subnetworkTriedAddresBucketArrays[*newSubnetwork][amgr.triedAddressBucketIndex(addr)]
|
||||
bucket = n.addrTried[*newSubnetwork][n.getTriedBucket(addr)]
|
||||
wasFound = false
|
||||
for _, ka := range bucket {
|
||||
if NetAddressKey(ka.NetAddress()) == addrKey {
|
||||
for e := bucket.Front(); e != nil; e = e.Next() {
|
||||
if NetAddressKey(e.Value.(*KnownAddress).NetAddress()) == addrKey {
|
||||
wasFound = true
|
||||
}
|
||||
}
|
||||
@@ -445,35 +401,34 @@ func TestGoodChangeSubnetworkID(t *testing.T) {
|
||||
|
||||
func TestGetAddress(t *testing.T) {
|
||||
localSubnetworkID := &subnetworkid.SubnetworkID{0xff}
|
||||
amgr, teardown := newAddrManagerForTest(t, "TestGetAddress", localSubnetworkID)
|
||||
defer teardown()
|
||||
n := New("testgetaddress", lookupFunc, localSubnetworkID)
|
||||
|
||||
// Get an address from an empty set (should error)
|
||||
if rv := amgr.GetAddress(); rv != nil {
|
||||
if rv := n.GetAddress(); rv != nil {
|
||||
t.Errorf("GetAddress failed: got: %v want: %v\n", rv, nil)
|
||||
}
|
||||
|
||||
// Add a new address and get it
|
||||
err := AddAddressByIP(amgr, someIP+":8332", localSubnetworkID)
|
||||
err := n.AddAddressByIP(someIP+":8332", localSubnetworkID)
|
||||
if err != nil {
|
||||
t.Fatalf("Adding address failed: %v", err)
|
||||
}
|
||||
ka := amgr.GetAddress()
|
||||
ka := n.GetAddress()
|
||||
if ka == nil {
|
||||
t.Fatalf("Did not get an address where there is one in the pool")
|
||||
}
|
||||
amgr.Attempt(ka.NetAddress())
|
||||
n.Attempt(ka.NetAddress())
|
||||
|
||||
// Checks that we don't get it if we find that it has other subnetwork ID than expected.
|
||||
actualSubnetworkID := &subnetworkid.SubnetworkID{0xfe}
|
||||
amgr.Good(ka.NetAddress(), actualSubnetworkID)
|
||||
ka = amgr.GetAddress()
|
||||
n.Good(ka.NetAddress(), actualSubnetworkID)
|
||||
ka = n.GetAddress()
|
||||
if ka != nil {
|
||||
t.Errorf("Didn't expect to get an address because there shouldn't be any address from subnetwork ID %s or nil", localSubnetworkID)
|
||||
}
|
||||
|
||||
// Checks that the total number of addresses incremented although the new address is not full node or a partial node of the same subnetwork as the local node.
|
||||
numAddrs := amgr.TotalNumAddresses()
|
||||
numAddrs := n.TotalNumAddresses()
|
||||
if numAddrs != 1 {
|
||||
t.Errorf("Wrong number of addresses: got %d, want %d", numAddrs, 1)
|
||||
}
|
||||
@@ -481,11 +436,11 @@ func TestGetAddress(t *testing.T) {
|
||||
// Now we repeat the same process, but now the address has the expected subnetwork ID.
|
||||
|
||||
// Add a new address and get it
|
||||
err = AddAddressByIP(amgr, someIP+":8333", localSubnetworkID)
|
||||
err = n.AddAddressByIP(someIP+":8333", localSubnetworkID)
|
||||
if err != nil {
|
||||
t.Fatalf("Adding address failed: %v", err)
|
||||
}
|
||||
ka = amgr.GetAddress()
|
||||
ka = n.GetAddress()
|
||||
if ka == nil {
|
||||
t.Fatalf("Did not get an address where there is one in the pool")
|
||||
}
|
||||
@@ -495,11 +450,11 @@ func TestGetAddress(t *testing.T) {
|
||||
if !ka.SubnetworkID().IsEqual(localSubnetworkID) {
|
||||
t.Errorf("Wrong Subnetwork ID: got %v, want %v", *ka.SubnetworkID(), localSubnetworkID)
|
||||
}
|
||||
amgr.Attempt(ka.NetAddress())
|
||||
n.Attempt(ka.NetAddress())
|
||||
|
||||
// Mark this as a good address and get it
|
||||
amgr.Good(ka.NetAddress(), localSubnetworkID)
|
||||
ka = amgr.GetAddress()
|
||||
n.Good(ka.NetAddress(), localSubnetworkID)
|
||||
ka = n.GetAddress()
|
||||
if ka == nil {
|
||||
t.Fatalf("Did not get an address where there is one in the pool")
|
||||
}
|
||||
@@ -510,14 +465,14 @@ func TestGetAddress(t *testing.T) {
|
||||
t.Errorf("Wrong Subnetwork ID: got %v, want %v", ka.SubnetworkID(), localSubnetworkID)
|
||||
}
|
||||
|
||||
numAddrs = amgr.TotalNumAddresses()
|
||||
numAddrs = n.TotalNumAddresses()
|
||||
if numAddrs != 2 {
|
||||
t.Errorf("Wrong number of addresses: got %d, want %d", numAddrs, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBestLocalAddress(t *testing.T) {
|
||||
localAddrs := []appmessage.NetAddress{
|
||||
localAddrs := []wire.NetAddress{
|
||||
{IP: net.ParseIP("192.168.0.100")},
|
||||
{IP: net.ParseIP("::1")},
|
||||
{IP: net.ParseIP("fe80::1")},
|
||||
@@ -525,40 +480,48 @@ func TestGetBestLocalAddress(t *testing.T) {
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
remoteAddr appmessage.NetAddress
|
||||
want0 appmessage.NetAddress
|
||||
want1 appmessage.NetAddress
|
||||
want2 appmessage.NetAddress
|
||||
want3 appmessage.NetAddress
|
||||
remoteAddr wire.NetAddress
|
||||
want0 wire.NetAddress
|
||||
want1 wire.NetAddress
|
||||
want2 wire.NetAddress
|
||||
want3 wire.NetAddress
|
||||
}{
|
||||
{
|
||||
// Remote connection from public IPv4
|
||||
appmessage.NetAddress{IP: net.ParseIP("204.124.8.1")},
|
||||
appmessage.NetAddress{IP: net.IPv4zero},
|
||||
appmessage.NetAddress{IP: net.IPv4zero},
|
||||
appmessage.NetAddress{IP: net.ParseIP("204.124.8.100")},
|
||||
appmessage.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")},
|
||||
wire.NetAddress{IP: net.ParseIP("204.124.8.1")},
|
||||
wire.NetAddress{IP: net.IPv4zero},
|
||||
wire.NetAddress{IP: net.IPv4zero},
|
||||
wire.NetAddress{IP: net.ParseIP("204.124.8.100")},
|
||||
wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")},
|
||||
},
|
||||
{
|
||||
// Remote connection from private IPv4
|
||||
appmessage.NetAddress{IP: net.ParseIP("172.16.0.254")},
|
||||
appmessage.NetAddress{IP: net.IPv4zero},
|
||||
appmessage.NetAddress{IP: net.IPv4zero},
|
||||
appmessage.NetAddress{IP: net.IPv4zero},
|
||||
appmessage.NetAddress{IP: net.IPv4zero},
|
||||
wire.NetAddress{IP: net.ParseIP("172.16.0.254")},
|
||||
wire.NetAddress{IP: net.IPv4zero},
|
||||
wire.NetAddress{IP: net.IPv4zero},
|
||||
wire.NetAddress{IP: net.IPv4zero},
|
||||
wire.NetAddress{IP: net.IPv4zero},
|
||||
},
|
||||
{
|
||||
// Remote connection from public IPv6
|
||||
appmessage.NetAddress{IP: net.ParseIP("2602:100:abcd::102")},
|
||||
appmessage.NetAddress{IP: net.IPv6zero},
|
||||
appmessage.NetAddress{IP: net.ParseIP("2001:470::1")},
|
||||
appmessage.NetAddress{IP: net.ParseIP("2001:470::1")},
|
||||
appmessage.NetAddress{IP: net.ParseIP("2001:470::1")},
|
||||
wire.NetAddress{IP: net.ParseIP("2602:100:abcd::102")},
|
||||
wire.NetAddress{IP: net.IPv6zero},
|
||||
wire.NetAddress{IP: net.ParseIP("2001:470::1")},
|
||||
wire.NetAddress{IP: net.ParseIP("2001:470::1")},
|
||||
wire.NetAddress{IP: net.ParseIP("2001:470::1")},
|
||||
},
|
||||
/* XXX
|
||||
{
|
||||
// Remote connection from Tor
|
||||
wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43::100")},
|
||||
wire.NetAddress{IP: net.IPv4zero},
|
||||
wire.NetAddress{IP: net.ParseIP("204.124.8.100")},
|
||||
wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")},
|
||||
},
|
||||
*/
|
||||
}
|
||||
|
||||
amgr, teardown := newAddrManagerForTest(t, "TestGetBestLocalAddress", nil)
|
||||
defer teardown()
|
||||
amgr := New("testgetbestlocaladdress", nil, nil)
|
||||
|
||||
// Test against default when there's no address
|
||||
for x, test := range tests {
|
||||
@@ -585,7 +548,7 @@ func TestGetBestLocalAddress(t *testing.T) {
|
||||
}
|
||||
|
||||
// Add a public IP to the list of local addresses.
|
||||
localAddr := appmessage.NetAddress{IP: net.ParseIP("204.124.8.100")}
|
||||
localAddr := wire.NetAddress{IP: net.ParseIP("204.124.8.100")}
|
||||
amgr.AddLocalAddress(&localAddr, InterfacePrio)
|
||||
|
||||
// Test against want2
|
||||
@@ -599,8 +562,9 @@ func TestGetBestLocalAddress(t *testing.T) {
|
||||
}
|
||||
/*
|
||||
// Add a Tor generated IP address
|
||||
localAddr = appmessage.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")}
|
||||
localAddr = wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")}
|
||||
amgr.AddLocalAddress(&localAddr, ManualPrio)
|
||||
|
||||
// Test against want3
|
||||
for x, test := range tests {
|
||||
got := amgr.GetBestLocalAddress(&test.remoteAddr)
|
||||
@@ -624,4 +588,5 @@ func TestNetAddressKey(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
17
addrmgr/cov_report.sh
Normal file
17
addrmgr/cov_report.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script uses gocov to generate a test coverage report.
|
||||
# The gocov tool my be obtained with the following command:
|
||||
# go get github.com/axw/gocov/gocov
|
||||
#
|
||||
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
|
||||
|
||||
# Check for gocov.
|
||||
type gocov >/dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo >&2 "This script requires the gocov tool."
|
||||
echo >&2 "You may obtain it with the following command:"
|
||||
echo >&2 "go get github.com/axw/gocov/gocov"
|
||||
exit 1
|
||||
fi
|
||||
gocov test | gocov report
|
||||
38
addrmgr/doc.go
Normal file
38
addrmgr/doc.go
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package addrmgr implements concurrency safe Bitcoin address manager.
|
||||
|
||||
Address Manager Overview
|
||||
|
||||
In order maintain the peer-to-peer Bitcoin network, there needs to be a source
|
||||
of addresses to connect to as nodes come and go. The Bitcoin protocol provides
|
||||
the getaddr and addr messages to allow peers to communicate known addresses with
|
||||
each other. However, there needs to a mechanism to store those results and
|
||||
select peers from them. It is also important to note that remote peers can't
|
||||
be trusted to send valid peers nor attempt to provide you with only peers they
|
||||
control with malicious intent.
|
||||
|
||||
With that in mind, this package provides a concurrency safe address manager for
|
||||
caching and selecting peers in a non-deterministic manner. The general idea is
|
||||
the caller adds addresses to the address manager and notifies it when addresses
|
||||
are connected, known good, and attempted. The caller also requests addresses as
|
||||
it needs them.
|
||||
|
||||
The address manager internally segregates the addresses into groups and
|
||||
non-deterministically selects groups in a cryptographically random manner. This
|
||||
reduce the chances multiple addresses from the same nets are selected which
|
||||
generally helps provide greater peer diversity, and perhaps more importantly,
|
||||
drastically reduces the chances an attacker is able to coerce your peer into
|
||||
only connecting to nodes they control.
|
||||
|
||||
The address manager also understands routability and Tor addresses and tries
|
||||
hard to only return routable addresses. In addition, it uses the information
|
||||
provided by the caller about connected, known good, and attempted addresses to
|
||||
periodically purge peers which no longer appear to be good peers as well as
|
||||
bias the selection toward known good peers. The general idea is to make a best
|
||||
effort at only providing usable addresses.
|
||||
*/
|
||||
package addrmgr
|
||||
25
addrmgr/internal_test.go
Normal file
25
addrmgr/internal_test.go
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package addrmgr
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/daglabs/btcd/wire"
|
||||
)
|
||||
|
||||
func TstKnownAddressIsBad(ka *KnownAddress) bool {
|
||||
return ka.isBad()
|
||||
}
|
||||
|
||||
func TstKnownAddressChance(ka *KnownAddress) float64 {
|
||||
return ka.chance()
|
||||
}
|
||||
|
||||
func TstNewKnownAddress(na *wire.NetAddress, attempts int,
|
||||
lastattempt, lastsuccess time.Time, tried bool, refs int) *KnownAddress {
|
||||
return &KnownAddress{na: na, attempts: attempts, lastattempt: lastattempt,
|
||||
lastsuccess: lastsuccess, tried: tried, refs: refs}
|
||||
}
|
||||
@@ -2,35 +2,33 @@
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package addressmanager
|
||||
package addrmgr
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
"github.com/daglabs/btcd/util/subnetworkid"
|
||||
|
||||
"github.com/daglabs/btcd/wire"
|
||||
)
|
||||
|
||||
// KnownAddress tracks information about a known network address that is used
|
||||
// to determine how viable an address is.
|
||||
type KnownAddress struct {
|
||||
netAddress *appmessage.NetAddress
|
||||
sourceAddress *appmessage.NetAddress
|
||||
attempts int
|
||||
lastAttempt mstime.Time
|
||||
lastSuccess mstime.Time
|
||||
tried bool
|
||||
referenceCount int // reference count of new buckets
|
||||
subnetworkID *subnetworkid.SubnetworkID
|
||||
isBanned bool
|
||||
bannedTime mstime.Time
|
||||
na *wire.NetAddress
|
||||
srcAddr *wire.NetAddress
|
||||
attempts int
|
||||
lastattempt time.Time
|
||||
lastsuccess time.Time
|
||||
tried bool
|
||||
refs int // reference count of new buckets
|
||||
subnetworkID *subnetworkid.SubnetworkID
|
||||
}
|
||||
|
||||
// NetAddress returns the underlying appmessage.NetAddress associated with the
|
||||
// NetAddress returns the underlying wire.NetAddress associated with the
|
||||
// known address.
|
||||
func (ka *KnownAddress) NetAddress() *appmessage.NetAddress {
|
||||
return ka.netAddress
|
||||
func (ka *KnownAddress) NetAddress() *wire.NetAddress {
|
||||
return ka.na
|
||||
}
|
||||
|
||||
// SubnetworkID returns the subnetwork ID of the known address.
|
||||
@@ -39,16 +37,16 @@ func (ka *KnownAddress) SubnetworkID() *subnetworkid.SubnetworkID {
|
||||
}
|
||||
|
||||
// LastAttempt returns the last time the known address was attempted.
|
||||
func (ka *KnownAddress) LastAttempt() mstime.Time {
|
||||
return ka.lastAttempt
|
||||
func (ka *KnownAddress) LastAttempt() time.Time {
|
||||
return ka.lastattempt
|
||||
}
|
||||
|
||||
// chance returns the selection probability for a known address. The priority
|
||||
// chance returns the selection probability for a known address. The priority
|
||||
// depends upon how recently the address has been seen, how recently it was last
|
||||
// attempted and how often attempts to connect to it have failed.
|
||||
func (ka *KnownAddress) chance() float64 {
|
||||
now := mstime.Now()
|
||||
lastAttempt := now.Sub(ka.lastAttempt)
|
||||
now := time.Now()
|
||||
lastAttempt := now.Sub(ka.lastattempt)
|
||||
|
||||
if lastAttempt < 0 {
|
||||
lastAttempt = 0
|
||||
@@ -78,27 +76,27 @@ func (ka *KnownAddress) chance() float64 {
|
||||
// All addresses that meet these criteria are assumed to be worthless and not
|
||||
// worth keeping hold of.
|
||||
func (ka *KnownAddress) isBad() bool {
|
||||
if ka.lastAttempt.After(mstime.Now().Add(-1 * time.Minute)) {
|
||||
if ka.lastattempt.After(time.Now().Add(-1 * time.Minute)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// From the future?
|
||||
if ka.netAddress.Timestamp.After(mstime.Now().Add(10 * time.Minute)) {
|
||||
if ka.na.Timestamp.After(time.Now().Add(10 * time.Minute)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Over a month old?
|
||||
if ka.netAddress.Timestamp.Before(mstime.Now().Add(-1 * numMissingDays * time.Hour * 24)) {
|
||||
if ka.na.Timestamp.Before(time.Now().Add(-1 * numMissingDays * time.Hour * 24)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Never succeeded?
|
||||
if ka.lastSuccess.IsZero() && ka.attempts >= numRetries {
|
||||
if ka.lastsuccess.IsZero() && ka.attempts >= numRetries {
|
||||
return true
|
||||
}
|
||||
|
||||
// Hasn't succeeded in too long?
|
||||
if !ka.lastSuccess.After(mstime.Now().Add(-1*minBadDays*time.Hour*24)) &&
|
||||
if !ka.lastsuccess.After(time.Now().Add(-1*minBadDays*time.Hour*24)) &&
|
||||
ka.attempts >= maxFailures {
|
||||
return true
|
||||
}
|
||||
114
addrmgr/knownaddress_test.go
Normal file
114
addrmgr/knownaddress_test.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package addrmgr_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/daglabs/btcd/addrmgr"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
)
|
||||
|
||||
func TestChance(t *testing.T) {
|
||||
now := time.Unix(time.Now().Unix(), 0)
|
||||
var tests = []struct {
|
||||
addr *addrmgr.KnownAddress
|
||||
expected float64
|
||||
}{
|
||||
{
|
||||
//Test normal case
|
||||
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)},
|
||||
0, time.Now().Add(-30*time.Minute), time.Now(), false, 0),
|
||||
1.0,
|
||||
}, {
|
||||
//Test case in which lastseen < 0
|
||||
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(20 * time.Second)},
|
||||
0, time.Now().Add(-30*time.Minute), time.Now(), false, 0),
|
||||
1.0,
|
||||
}, {
|
||||
//Test case in which lastattempt < 0
|
||||
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)},
|
||||
0, time.Now().Add(30*time.Minute), time.Now(), false, 0),
|
||||
1.0 * .01,
|
||||
}, {
|
||||
//Test case in which lastattempt < ten minutes
|
||||
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)},
|
||||
0, time.Now().Add(-5*time.Minute), time.Now(), false, 0),
|
||||
1.0 * .01,
|
||||
}, {
|
||||
//Test case with several failed attempts.
|
||||
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)},
|
||||
2, time.Now().Add(-30*time.Minute), time.Now(), false, 0),
|
||||
1 / 1.5 / 1.5,
|
||||
},
|
||||
}
|
||||
|
||||
err := .0001
|
||||
for i, test := range tests {
|
||||
chance := addrmgr.TstKnownAddressChance(test.addr)
|
||||
if math.Abs(test.expected-chance) >= err {
|
||||
t.Errorf("case %d: got %f, expected %f", i, chance, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsBad(t *testing.T) {
|
||||
now := time.Unix(time.Now().Unix(), 0)
|
||||
future := now.Add(35 * time.Minute)
|
||||
monthOld := now.Add(-43 * time.Hour * 24)
|
||||
secondsOld := now.Add(-2 * time.Second)
|
||||
minutesOld := now.Add(-27 * time.Minute)
|
||||
hoursOld := now.Add(-5 * time.Hour)
|
||||
zeroTime := time.Time{}
|
||||
|
||||
futureNa := &wire.NetAddress{Timestamp: future}
|
||||
minutesOldNa := &wire.NetAddress{Timestamp: minutesOld}
|
||||
monthOldNa := &wire.NetAddress{Timestamp: monthOld}
|
||||
currentNa := &wire.NetAddress{Timestamp: secondsOld}
|
||||
|
||||
//Test addresses that have been tried in the last minute.
|
||||
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(futureNa, 3, secondsOld, zeroTime, false, 0)) {
|
||||
t.Errorf("test case 1: addresses that have been tried in the last minute are not bad.")
|
||||
}
|
||||
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(monthOldNa, 3, secondsOld, zeroTime, false, 0)) {
|
||||
t.Errorf("test case 2: addresses that have been tried in the last minute are not bad.")
|
||||
}
|
||||
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(currentNa, 3, secondsOld, zeroTime, false, 0)) {
|
||||
t.Errorf("test case 3: addresses that have been tried in the last minute are not bad.")
|
||||
}
|
||||
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(currentNa, 3, secondsOld, monthOld, true, 0)) {
|
||||
t.Errorf("test case 4: addresses that have been tried in the last minute are not bad.")
|
||||
}
|
||||
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(currentNa, 2, secondsOld, secondsOld, true, 0)) {
|
||||
t.Errorf("test case 5: addresses that have been tried in the last minute are not bad.")
|
||||
}
|
||||
|
||||
//Test address that claims to be from the future.
|
||||
if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(futureNa, 0, minutesOld, hoursOld, true, 0)) {
|
||||
t.Errorf("test case 6: addresses that claim to be from the future are bad.")
|
||||
}
|
||||
|
||||
//Test address that has not been seen in over a month.
|
||||
if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(monthOldNa, 0, minutesOld, hoursOld, true, 0)) {
|
||||
t.Errorf("test case 7: addresses more than a month old are bad.")
|
||||
}
|
||||
|
||||
//It has failed at least three times and never succeeded.
|
||||
if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(minutesOldNa, 3, minutesOld, zeroTime, true, 0)) {
|
||||
t.Errorf("test case 8: addresses that have never succeeded are bad.")
|
||||
}
|
||||
|
||||
//It has failed ten times in the last week
|
||||
if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(minutesOldNa, 10, minutesOld, monthOld, true, 0)) {
|
||||
t.Errorf("test case 9: addresses that have not succeeded in too long are bad.")
|
||||
}
|
||||
|
||||
//Test an address that should work.
|
||||
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(minutesOldNa, 2, minutesOld, hoursOld, true, 0)) {
|
||||
t.Errorf("test case 10: This should be a valid address.")
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package addressmanager
|
||||
package addrmgr
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
"github.com/daglabs/btcd/logger"
|
||||
"github.com/daglabs/btcd/util/panics"
|
||||
)
|
||||
|
||||
var log, _ = logger.Get(logger.SubsystemTags.ADXR)
|
||||
var spawn = panics.GoroutineWrapperFunc(log)
|
||||
var spawn = panics.GoroutineWrapperFunc(log, logger.BackendLog)
|
||||
@@ -2,11 +2,15 @@
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package addressmanager
|
||||
package addrmgr
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/daglabs/btcd/config"
|
||||
|
||||
"github.com/daglabs/btcd/wire"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -69,6 +73,19 @@ var (
|
||||
// rfc6598Net specifies the IPv4 block as defined by RFC6598 (100.64.0.0/10)
|
||||
rfc6598Net = ipNet("100.64.0.0", 10, 32)
|
||||
|
||||
// onionCatNet defines the IPv6 address block used to support Tor.
|
||||
// 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.
|
||||
//
|
||||
// This is the same range used by OnionCat, which is part part of the
|
||||
// RFC4193 unique local IPv6 range.
|
||||
//
|
||||
// In summary the format is:
|
||||
// { magic 6 bytes, 10 bytes base32 decode of key hash }
|
||||
onionCatNet = ipNet("fd87:d87e:eb43::", 48, 128)
|
||||
|
||||
// zero4Net defines the IPv4 address block for address staring with 0
|
||||
// (0.0.0.0/8).
|
||||
zero4Net = ipNet("0.0.0.0", 8, 32)
|
||||
@@ -85,19 +102,27 @@ func ipNet(ip string, ones, bits int) net.IPNet {
|
||||
}
|
||||
|
||||
// IsIPv4 returns whether or not the given address is an IPv4 address.
|
||||
func IsIPv4(na *appmessage.NetAddress) bool {
|
||||
func IsIPv4(na *wire.NetAddress) bool {
|
||||
return na.IP.To4() != nil
|
||||
}
|
||||
|
||||
// IsLocal returns whether or not the given address is a local address.
|
||||
func IsLocal(na *appmessage.NetAddress) bool {
|
||||
func IsLocal(na *wire.NetAddress) bool {
|
||||
return na.IP.IsLoopback() || zero4Net.Contains(na.IP)
|
||||
}
|
||||
|
||||
// IsOnionCatTor returns whether or not the passed address is in the IPv6 range
|
||||
// used by bitcoin to support Tor (fd87:d87e:eb43::/48). Note that this range
|
||||
// is the same range used by OnionCat, which is part of the RFC4193 unique local
|
||||
// IPv6 range.
|
||||
func IsOnionCatTor(na *wire.NetAddress) bool {
|
||||
return onionCatNet.Contains(na.IP)
|
||||
}
|
||||
|
||||
// IsRFC1918 returns whether or not the passed address is part of the IPv4
|
||||
// private network address space as defined by RFC1918 (10.0.0.0/8,
|
||||
// 172.16.0.0/12, or 192.168.0.0/16).
|
||||
func IsRFC1918(na *appmessage.NetAddress) bool {
|
||||
func IsRFC1918(na *wire.NetAddress) bool {
|
||||
for _, rfc := range rfc1918Nets {
|
||||
if rfc.Contains(na.IP) {
|
||||
return true
|
||||
@@ -108,56 +133,56 @@ func IsRFC1918(na *appmessage.NetAddress) bool {
|
||||
|
||||
// IsRFC2544 returns whether or not the passed address is part of the IPv4
|
||||
// address space as defined by RFC2544 (198.18.0.0/15)
|
||||
func IsRFC2544(na *appmessage.NetAddress) bool {
|
||||
func IsRFC2544(na *wire.NetAddress) bool {
|
||||
return rfc2544Net.Contains(na.IP)
|
||||
}
|
||||
|
||||
// IsRFC3849 returns whether or not the passed address is part of the IPv6
|
||||
// documentation range as defined by RFC3849 (2001:DB8::/32).
|
||||
func IsRFC3849(na *appmessage.NetAddress) bool {
|
||||
func IsRFC3849(na *wire.NetAddress) bool {
|
||||
return rfc3849Net.Contains(na.IP)
|
||||
}
|
||||
|
||||
// IsRFC3927 returns whether or not the passed address is part of the IPv4
|
||||
// autoconfiguration range as defined by RFC3927 (169.254.0.0/16).
|
||||
func IsRFC3927(na *appmessage.NetAddress) bool {
|
||||
func IsRFC3927(na *wire.NetAddress) bool {
|
||||
return rfc3927Net.Contains(na.IP)
|
||||
}
|
||||
|
||||
// IsRFC3964 returns whether or not the passed address is part of the IPv6 to
|
||||
// IPv4 encapsulation range as defined by RFC3964 (2002::/16).
|
||||
func IsRFC3964(na *appmessage.NetAddress) bool {
|
||||
func IsRFC3964(na *wire.NetAddress) bool {
|
||||
return rfc3964Net.Contains(na.IP)
|
||||
}
|
||||
|
||||
// IsRFC4193 returns whether or not the passed address is part of the IPv6
|
||||
// unique local range as defined by RFC4193 (FC00::/7).
|
||||
func IsRFC4193(na *appmessage.NetAddress) bool {
|
||||
func IsRFC4193(na *wire.NetAddress) bool {
|
||||
return rfc4193Net.Contains(na.IP)
|
||||
}
|
||||
|
||||
// IsRFC4380 returns whether or not the passed address is part of the IPv6
|
||||
// teredo tunneling over UDP range as defined by RFC4380 (2001::/32).
|
||||
func IsRFC4380(na *appmessage.NetAddress) bool {
|
||||
func IsRFC4380(na *wire.NetAddress) bool {
|
||||
return rfc4380Net.Contains(na.IP)
|
||||
}
|
||||
|
||||
// IsRFC4843 returns whether or not the passed address is part of the IPv6
|
||||
// ORCHID range as defined by RFC4843 (2001:10::/28).
|
||||
func IsRFC4843(na *appmessage.NetAddress) bool {
|
||||
func IsRFC4843(na *wire.NetAddress) bool {
|
||||
return rfc4843Net.Contains(na.IP)
|
||||
}
|
||||
|
||||
// IsRFC4862 returns whether or not the passed address is part of the IPv6
|
||||
// stateless address autoconfiguration range as defined by RFC4862 (FE80::/64).
|
||||
func IsRFC4862(na *appmessage.NetAddress) bool {
|
||||
func IsRFC4862(na *wire.NetAddress) bool {
|
||||
return rfc4862Net.Contains(na.IP)
|
||||
}
|
||||
|
||||
// IsRFC5737 returns whether or not the passed address is part of the IPv4
|
||||
// documentation address space as defined by RFC5737 (192.0.2.0/24,
|
||||
// 198.51.100.0/24, 203.0.113.0/24)
|
||||
func IsRFC5737(na *appmessage.NetAddress) bool {
|
||||
func IsRFC5737(na *wire.NetAddress) bool {
|
||||
for _, rfc := range rfc5737Net {
|
||||
if rfc.Contains(na.IP) {
|
||||
return true
|
||||
@@ -169,27 +194,27 @@ func IsRFC5737(na *appmessage.NetAddress) bool {
|
||||
|
||||
// IsRFC6052 returns whether or not the passed address is part of the IPv6
|
||||
// well-known prefix range as defined by RFC6052 (64:FF9B::/96).
|
||||
func IsRFC6052(na *appmessage.NetAddress) bool {
|
||||
func IsRFC6052(na *wire.NetAddress) bool {
|
||||
return rfc6052Net.Contains(na.IP)
|
||||
}
|
||||
|
||||
// IsRFC6145 returns whether or not the passed address is part of the IPv6 to
|
||||
// IPv4 translated address range as defined by RFC6145 (::FFFF:0:0:0/96).
|
||||
func IsRFC6145(na *appmessage.NetAddress) bool {
|
||||
func IsRFC6145(na *wire.NetAddress) bool {
|
||||
return rfc6145Net.Contains(na.IP)
|
||||
}
|
||||
|
||||
// IsRFC6598 returns whether or not the passed address is part of the IPv4
|
||||
// shared address space specified by RFC6598 (100.64.0.0/10)
|
||||
func IsRFC6598(na *appmessage.NetAddress) bool {
|
||||
func IsRFC6598(na *wire.NetAddress) bool {
|
||||
return rfc6598Net.Contains(na.IP)
|
||||
}
|
||||
|
||||
// IsValid returns whether or not the passed address is valid. The address is
|
||||
// IsValid returns whether or not the passed address is valid. The address is
|
||||
// considered invalid under the following circumstances:
|
||||
// IPv4: It is either a zero or all bits set address.
|
||||
// IPv6: It is either a zero or RFC3849 documentation address.
|
||||
func IsValid(na *appmessage.NetAddress) bool {
|
||||
func IsValid(na *wire.NetAddress) bool {
|
||||
// IsUnspecified returns if address is 0, so only all bits set, and
|
||||
// RFC3849 need to be explicitly checked.
|
||||
return na.IP != nil && !(na.IP.IsUnspecified() ||
|
||||
@@ -197,28 +222,29 @@ func IsValid(na *appmessage.NetAddress) bool {
|
||||
}
|
||||
|
||||
// IsRoutable returns whether or not the passed address is routable over
|
||||
// the public internet. This is true as long as the address is valid and is not
|
||||
// the public internet. This is true as long as the address is valid and is not
|
||||
// in any reserved ranges.
|
||||
func (am *AddressManager) IsRoutable(na *appmessage.NetAddress) bool {
|
||||
if am.cfg.NetParams().AcceptUnroutable {
|
||||
func IsRoutable(na *wire.NetAddress) bool {
|
||||
if config.ActiveNetParams().AcceptUnroutable {
|
||||
return !IsLocal(na)
|
||||
}
|
||||
|
||||
return IsValid(na) && !(IsRFC1918(na) || IsRFC2544(na) ||
|
||||
IsRFC3927(na) || IsRFC4862(na) || IsRFC3849(na) ||
|
||||
IsRFC4843(na) || IsRFC5737(na) || IsRFC6598(na) ||
|
||||
IsLocal(na) || (IsRFC4193(na)))
|
||||
IsLocal(na) || (IsRFC4193(na) && !IsOnionCatTor(na)))
|
||||
}
|
||||
|
||||
// GroupKey returns a string representing the network group an address is part
|
||||
// of. This is the /16 for IPv4, the /32 (/36 for he.net) for IPv6, the string
|
||||
// "local" for a local address, and the string "unroutable" for an unroutable
|
||||
// of. This is the /16 for IPv4, the /32 (/36 for he.net) for IPv6, the string
|
||||
// "local" for a local address, the string "tor:key" where key is the /4 of the
|
||||
// onion address for Tor address, and the string "unroutable" for an unroutable
|
||||
// address.
|
||||
func (am *AddressManager) GroupKey(na *appmessage.NetAddress) string {
|
||||
func GroupKey(na *wire.NetAddress) string {
|
||||
if IsLocal(na) {
|
||||
return "local"
|
||||
}
|
||||
if !am.IsRoutable(na) {
|
||||
if !IsRoutable(na) {
|
||||
return "unroutable"
|
||||
}
|
||||
if IsIPv4(na) {
|
||||
@@ -244,10 +270,14 @@ func (am *AddressManager) GroupKey(na *appmessage.NetAddress) string {
|
||||
}
|
||||
return ip.Mask(net.CIDRMask(16, 32)).String()
|
||||
}
|
||||
if IsOnionCatTor(na) {
|
||||
// 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.
|
||||
// We use /32 for everything, except for Hurricane Electric's
|
||||
// (he.net) IP range, which we use /36 for.
|
||||
// bitcoind uses /32 for everything, except for Hurricane Electric's
|
||||
// (he.net) IP range, which it uses /36 for.
|
||||
bits := 32
|
||||
if heNet.Contains(na.IP) {
|
||||
bits = 36
|
||||
@@ -2,21 +2,21 @@
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package addressmanager
|
||||
package addrmgr_test
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/daglabs/btcd/addrmgr"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
)
|
||||
|
||||
// TestIPTypes ensures the various functions which determine the type of an IP
|
||||
// address based on RFCs work as intended.
|
||||
func TestIPTypes(t *testing.T) {
|
||||
amgr, teardown := newAddrManagerForTest(t, "TestAddAddressByIP", nil)
|
||||
defer teardown()
|
||||
type ipTest struct {
|
||||
in appmessage.NetAddress
|
||||
in wire.NetAddress
|
||||
rfc1918 bool
|
||||
rfc2544 bool
|
||||
rfc3849 bool
|
||||
@@ -39,7 +39,7 @@ func TestIPTypes(t *testing.T) {
|
||||
rfc4193, rfc4380, rfc4843, rfc4862, rfc5737, rfc6052, rfc6145, rfc6598,
|
||||
local, valid, routable bool) ipTest {
|
||||
nip := net.ParseIP(ip)
|
||||
na := *appmessage.NewNetAddressIPPort(nip, 16111, appmessage.SFNodeNetwork)
|
||||
na := *wire.NewNetAddressIPPort(nip, 8333, wire.SFNodeNetwork)
|
||||
test := ipTest{na, rfc1918, rfc2544, rfc3849, rfc3927, rfc3964, rfc4193, rfc4380,
|
||||
rfc4843, rfc4862, rfc5737, rfc6052, rfc6145, rfc6598, local, valid, routable}
|
||||
return test
|
||||
@@ -88,55 +88,55 @@ func TestIPTypes(t *testing.T) {
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for _, test := range tests {
|
||||
if rv := IsRFC1918(&test.in); rv != test.rfc1918 {
|
||||
if rv := addrmgr.IsRFC1918(&test.in); rv != test.rfc1918 {
|
||||
t.Errorf("IsRFC1918 %s\n got: %v want: %v", test.in.IP, rv, test.rfc1918)
|
||||
}
|
||||
|
||||
if rv := IsRFC3849(&test.in); rv != test.rfc3849 {
|
||||
if rv := addrmgr.IsRFC3849(&test.in); rv != test.rfc3849 {
|
||||
t.Errorf("IsRFC3849 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3849)
|
||||
}
|
||||
|
||||
if rv := IsRFC3927(&test.in); rv != test.rfc3927 {
|
||||
if rv := addrmgr.IsRFC3927(&test.in); rv != test.rfc3927 {
|
||||
t.Errorf("IsRFC3927 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3927)
|
||||
}
|
||||
|
||||
if rv := IsRFC3964(&test.in); rv != test.rfc3964 {
|
||||
if rv := addrmgr.IsRFC3964(&test.in); rv != test.rfc3964 {
|
||||
t.Errorf("IsRFC3964 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3964)
|
||||
}
|
||||
|
||||
if rv := IsRFC4193(&test.in); rv != test.rfc4193 {
|
||||
if rv := addrmgr.IsRFC4193(&test.in); rv != test.rfc4193 {
|
||||
t.Errorf("IsRFC4193 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4193)
|
||||
}
|
||||
|
||||
if rv := IsRFC4380(&test.in); rv != test.rfc4380 {
|
||||
if rv := addrmgr.IsRFC4380(&test.in); rv != test.rfc4380 {
|
||||
t.Errorf("IsRFC4380 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4380)
|
||||
}
|
||||
|
||||
if rv := IsRFC4843(&test.in); rv != test.rfc4843 {
|
||||
if rv := addrmgr.IsRFC4843(&test.in); rv != test.rfc4843 {
|
||||
t.Errorf("IsRFC4843 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4843)
|
||||
}
|
||||
|
||||
if rv := IsRFC4862(&test.in); rv != test.rfc4862 {
|
||||
if rv := addrmgr.IsRFC4862(&test.in); rv != test.rfc4862 {
|
||||
t.Errorf("IsRFC4862 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4862)
|
||||
}
|
||||
|
||||
if rv := IsRFC6052(&test.in); rv != test.rfc6052 {
|
||||
if rv := addrmgr.IsRFC6052(&test.in); rv != test.rfc6052 {
|
||||
t.Errorf("isRFC6052 %s\n got: %v want: %v", test.in.IP, rv, test.rfc6052)
|
||||
}
|
||||
|
||||
if rv := IsRFC6145(&test.in); rv != test.rfc6145 {
|
||||
if rv := addrmgr.IsRFC6145(&test.in); rv != test.rfc6145 {
|
||||
t.Errorf("IsRFC1918 %s\n got: %v want: %v", test.in.IP, rv, test.rfc6145)
|
||||
}
|
||||
|
||||
if rv := IsLocal(&test.in); rv != test.local {
|
||||
if rv := addrmgr.IsLocal(&test.in); rv != test.local {
|
||||
t.Errorf("IsLocal %s\n got: %v want: %v", test.in.IP, rv, test.local)
|
||||
}
|
||||
|
||||
if rv := IsValid(&test.in); rv != test.valid {
|
||||
if rv := addrmgr.IsValid(&test.in); rv != test.valid {
|
||||
t.Errorf("IsValid %s\n got: %v want: %v", test.in.IP, rv, test.valid)
|
||||
}
|
||||
|
||||
if rv := amgr.IsRoutable(&test.in); rv != test.routable {
|
||||
if rv := addrmgr.IsRoutable(&test.in); rv != test.routable {
|
||||
t.Errorf("IsRoutable %s\n got: %v want: %v", test.in.IP, rv, test.routable)
|
||||
}
|
||||
}
|
||||
@@ -145,9 +145,6 @@ func TestIPTypes(t *testing.T) {
|
||||
// TestGroupKey tests the GroupKey function to ensure it properly groups various
|
||||
// IP addresses.
|
||||
func TestGroupKey(t *testing.T) {
|
||||
amgr, teardown := newAddrManagerForTest(t, "TestAddAddressByIP", nil)
|
||||
defer teardown()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ip string
|
||||
@@ -182,9 +179,9 @@ func TestGroupKey(t *testing.T) {
|
||||
{name: "ipv6 rfc6145 translated ipv4", ip: "::ffff:0:0c01:0203", expected: "12.1.0.0"},
|
||||
|
||||
// Tor.
|
||||
{name: "ipv6 tor onioncat", ip: "fd87:d87e:eb43:1234::5678", expected: "unroutable"},
|
||||
{name: "ipv6 tor onioncat 2", ip: "fd87:d87e:eb43:1245::6789", expected: "unroutable"},
|
||||
{name: "ipv6 tor onioncat 3", ip: "fd87:d87e:eb43:1345::6789", expected: "unroutable"},
|
||||
{name: "ipv6 tor onioncat", ip: "fd87:d87e:eb43:1234::5678", expected: "tor:2"},
|
||||
{name: "ipv6 tor onioncat 2", ip: "fd87:d87e:eb43:1245::6789", expected: "tor:2"},
|
||||
{name: "ipv6 tor onioncat 3", ip: "fd87:d87e:eb43:1345::6789", expected: "tor:3"},
|
||||
|
||||
// IPv6 normal.
|
||||
{name: "ipv6 normal", ip: "2602:100::1", expected: "2602:100::"},
|
||||
@@ -195,8 +192,8 @@ func TestGroupKey(t *testing.T) {
|
||||
|
||||
for i, test := range tests {
|
||||
nip := net.ParseIP(test.ip)
|
||||
na := *appmessage.NewNetAddressIPPort(nip, 8333, appmessage.SFNodeNetwork)
|
||||
if key := amgr.GroupKey(&na); key != test.expected {
|
||||
na := *wire.NewNetAddressIPPort(nip, 8333, wire.SFNodeNetwork)
|
||||
if key := addrmgr.GroupKey(&na); key != test.expected {
|
||||
t.Errorf("TestGroupKey #%d (%s): unexpected group key "+
|
||||
"- got '%s', want '%s'", i, test.name,
|
||||
key, test.expected)
|
||||
62
addrmgr/test_coverage.txt
Normal file
62
addrmgr/test_coverage.txt
Normal file
@@ -0,0 +1,62 @@
|
||||
|
||||
github.com/conformal/btcd/addrmgr/network.go GroupKey 100.00% (23/23)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.reset 100.00% (6/6)
|
||||
github.com/conformal/btcd/addrmgr/network.go IsRFC5737 100.00% (4/4)
|
||||
github.com/conformal/btcd/addrmgr/network.go IsRFC1918 100.00% (4/4)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go New 100.00% (3/3)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go NetAddressKey 100.00% (2/2)
|
||||
github.com/conformal/btcd/addrmgr/network.go IsRFC4862 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.numAddresses 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/log.go init 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/log.go DisableLog 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/network.go ipNet 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/network.go IsIPv4 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/network.go IsLocal 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/network.go IsOnionCatTor 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/network.go IsRFC2544 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/network.go IsRFC3849 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/network.go IsRFC3927 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/network.go IsRFC3964 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/network.go IsRFC4193 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/network.go IsRFC4380 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/network.go IsRFC4843 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/network.go IsRFC6052 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/network.go IsRFC6145 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/network.go IsRFC6598 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/network.go IsValid 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/network.go IsRoutable 100.00% (1/1)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.GetBestLocalAddress 94.74% (18/19)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.AddLocalAddress 90.91% (10/11)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go getReachabilityFrom 51.52% (17/33)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go ipString 50.00% (2/4)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.GetAddress 9.30% (4/43)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.deserializePeers 0.00% (0/50)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.Good 0.00% (0/44)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.savePeers 0.00% (0/39)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.updateAddress 0.00% (0/30)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.expireNew 0.00% (0/22)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.AddressCache 0.00% (0/16)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.HostToNetAddress 0.00% (0/15)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.getNewBucket 0.00% (0/15)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.AddAddressByIP 0.00% (0/14)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.getTriedBucket 0.00% (0/14)
|
||||
github.com/conformal/btcd/addrmgr/knownaddress.go knownAddress.chance 0.00% (0/13)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.loadPeers 0.00% (0/11)
|
||||
github.com/conformal/btcd/addrmgr/knownaddress.go knownAddress.isBad 0.00% (0/11)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.Connected 0.00% (0/10)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.addressHandler 0.00% (0/9)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.pickTried 0.00% (0/8)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.DeserializeNetAddress 0.00% (0/7)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.Stop 0.00% (0/7)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.Attempt 0.00% (0/7)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.Start 0.00% (0/6)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.AddAddresses 0.00% (0/4)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.NeedMoreAddresses 0.00% (0/3)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.NumAddresses 0.00% (0/3)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.AddAddress 0.00% (0/3)
|
||||
github.com/conformal/btcd/addrmgr/knownaddress.go knownAddress.LastAttempt 0.00% (0/1)
|
||||
github.com/conformal/btcd/addrmgr/knownaddress.go knownAddress.NetAddress 0.00% (0/1)
|
||||
github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.find 0.00% (0/1)
|
||||
github.com/conformal/btcd/addrmgr/log.go UseLogger 0.00% (0/1)
|
||||
github.com/conformal/btcd/addrmgr --------------------------------- 21.04% (113/537)
|
||||
|
||||
126
apiserver/config/config.go
Normal file
126
apiserver/config/config.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/daglabs/btcd/apiserver/logger"
|
||||
"github.com/daglabs/btcd/dagconfig"
|
||||
"github.com/daglabs/btcd/util"
|
||||
"github.com/jessevdk/go-flags"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultLogFilename = "apiserver.log"
|
||||
defaultErrLogFilename = "apiserver_err.log"
|
||||
)
|
||||
|
||||
var (
|
||||
// activeNetParams are the currently active net params
|
||||
activeNetParams dagconfig.Params
|
||||
)
|
||||
|
||||
var (
|
||||
// Default configuration options
|
||||
defaultLogDir = util.AppDataDir("apiserver", false)
|
||||
defaultDBAddress = "localhost:3306"
|
||||
defaultHTTPListen = "0.0.0.0:8080"
|
||||
)
|
||||
|
||||
// Config defines the configuration options for the API server.
|
||||
type Config struct {
|
||||
LogDir string `long:"logdir" description:"Directory to log output."`
|
||||
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"`
|
||||
DisableTLS bool `long:"notls" description:"Disable TLS"`
|
||||
DBAddress string `long:"dbaddress" description:"Database address"`
|
||||
DBUser string `long:"dbuser" description:"Database user" required:"true"`
|
||||
DBPassword string `long:"dbpass" description:"Database password" required:"true"`
|
||||
DBName string `long:"dbname" description:"Database name" required:"true"`
|
||||
HTTPListen string `long:"listen" description:"HTTP address to listen on (default: 0.0.0.0:8080)"`
|
||||
Migrate bool `long:"migrate" description:"Migrate the database to the latest version. The server will not start when using this flag."`
|
||||
TestNet bool `long:"testnet" description:"Connect to testnet"`
|
||||
SimNet bool `long:"simnet" description:"Connect to the simulation test network"`
|
||||
DevNet bool `long:"devnet" description:"Connect to the development test network"`
|
||||
}
|
||||
|
||||
// Parse parses the CLI arguments and returns a config struct.
|
||||
func Parse() (*Config, error) {
|
||||
cfg := &Config{
|
||||
LogDir: defaultLogDir,
|
||||
DBAddress: defaultDBAddress,
|
||||
HTTPListen: defaultHTTPListen,
|
||||
}
|
||||
parser := flags.NewParser(cfg, flags.PrintErrors|flags.HelpFlag)
|
||||
_, err := parser.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !cfg.Migrate {
|
||||
if cfg.RPCUser == "" {
|
||||
return nil, errors.New("--rpcuser is required if --migrate flag is not used")
|
||||
}
|
||||
if cfg.RPCPassword == "" {
|
||||
return nil, errors.New("--rpcpass is required if --migrate flag is not used")
|
||||
}
|
||||
if cfg.RPCServer == "" {
|
||||
return nil, errors.New("--rpcserver is required if --migrate flag is not used")
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.RPCCert == "" && !cfg.DisableTLS {
|
||||
return nil, errors.New("--notls has to be disabled if --cert is used")
|
||||
}
|
||||
|
||||
if cfg.RPCCert != "" && cfg.DisableTLS {
|
||||
return nil, errors.New("--cert should be omitted if --notls is used")
|
||||
}
|
||||
|
||||
err = resolveNetwork(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logFile := filepath.Join(cfg.LogDir, defaultLogFilename)
|
||||
errLogFile := filepath.Join(cfg.LogDir, defaultErrLogFilename)
|
||||
logger.InitLog(logFile, errLogFile)
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func resolveNetwork(cfg *Config) error {
|
||||
// Multiple networks can't be selected simultaneously.
|
||||
numNets := 0
|
||||
if cfg.TestNet {
|
||||
numNets++
|
||||
}
|
||||
if cfg.SimNet {
|
||||
numNets++
|
||||
}
|
||||
if cfg.DevNet {
|
||||
numNets++
|
||||
}
|
||||
if numNets > 1 {
|
||||
return errors.New("multiple net params (testnet, simnet, devnet, etc.) can't be used " +
|
||||
"together -- choose one of them")
|
||||
}
|
||||
|
||||
activeNetParams = dagconfig.MainNetParams
|
||||
switch {
|
||||
case cfg.TestNet:
|
||||
activeNetParams = dagconfig.TestNetParams
|
||||
case cfg.SimNet:
|
||||
activeNetParams = dagconfig.SimNetParams
|
||||
case cfg.DevNet:
|
||||
activeNetParams = dagconfig.DevNetParams
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ActiveNetParams returns the currently active net params
|
||||
func ActiveNetParams() *dagconfig.Params {
|
||||
return &activeNetParams
|
||||
}
|
||||
79
apiserver/controllers/block.go
Normal file
79
apiserver/controllers/block.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/daglabs/btcd/apiserver/database"
|
||||
"github.com/daglabs/btcd/apiserver/models"
|
||||
"github.com/daglabs/btcd/apiserver/utils"
|
||||
"github.com/daglabs/btcd/util/daghash"
|
||||
)
|
||||
|
||||
const (
|
||||
// OrderAscending is parameter that can be used
|
||||
// in a get list handler to get a list ordered
|
||||
// in an ascending order.
|
||||
OrderAscending = "asc"
|
||||
|
||||
// OrderDescending is parameter that can be used
|
||||
// in a get list handler to get a list ordered
|
||||
// in an ascending order.
|
||||
OrderDescending = "desc"
|
||||
)
|
||||
|
||||
const maxGetBlocksLimit = 100
|
||||
|
||||
// GetBlockByHashHandler returns a block by a given hash.
|
||||
func GetBlockByHashHandler(blockHash string) (interface{}, *utils.HandlerError) {
|
||||
if bytes, err := hex.DecodeString(blockHash); err != nil || len(bytes) != daghash.HashSize {
|
||||
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity,
|
||||
fmt.Sprintf("The given block hash is not a hex-encoded %d-byte hash.", daghash.HashSize))
|
||||
}
|
||||
|
||||
db, err := database.DB()
|
||||
if err != nil {
|
||||
return nil, utils.NewInternalServerHandlerError(err.Error())
|
||||
}
|
||||
|
||||
block := &models.Block{}
|
||||
dbResult := db.Where(&models.Block{BlockHash: blockHash}).Preload("AcceptingBlock").First(block)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.IsDBRecordNotFoundError(dbErrors) {
|
||||
return nil, utils.NewHandlerError(http.StatusNotFound, "No block with the given block hash was found.")
|
||||
}
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return nil, utils.NewHandlerErrorFromDBErrors("Some errors where encountered when loading transactions from the database:", dbResult.GetErrors())
|
||||
}
|
||||
return convertBlockModelToBlockResponse(block), nil
|
||||
}
|
||||
|
||||
// GetBlocksHandler searches for all blocks
|
||||
func GetBlocksHandler(order string, skip uint64, limit uint64) (interface{}, *utils.HandlerError) {
|
||||
if limit > maxGetBlocksLimit {
|
||||
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("The maximum allowed value for the limit is %d", maxGetTransactionsLimit))
|
||||
}
|
||||
blocks := []*models.Block{}
|
||||
db, err := database.DB()
|
||||
if err != nil {
|
||||
return nil, utils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
query := db.
|
||||
Limit(limit).
|
||||
Offset(skip).
|
||||
Preload("AcceptingBlock")
|
||||
if order == OrderAscending {
|
||||
query = query.Order("`id` ASC")
|
||||
} else if order == OrderDescending {
|
||||
query = query.Order("`id` DESC")
|
||||
} else {
|
||||
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("'%s' is not a valid order", order))
|
||||
}
|
||||
query.Find(&blocks)
|
||||
blockResponses := make([]*blockResponse, len(blocks))
|
||||
for i, block := range blocks {
|
||||
blockResponses[i] = convertBlockModelToBlockResponse(block)
|
||||
}
|
||||
return blockResponses, nil
|
||||
}
|
||||
13
apiserver/controllers/feeestimate.go
Normal file
13
apiserver/controllers/feeestimate.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package controllers
|
||||
|
||||
import "github.com/daglabs/btcd/apiserver/utils"
|
||||
|
||||
// GetFeeEstimatesHandler returns the fee estimates for different priorities
|
||||
// for accepting a transaction in the DAG.
|
||||
func GetFeeEstimatesHandler() (interface{}, *utils.HandlerError) {
|
||||
return &feeEstimateResponse{
|
||||
HighPriority: 3,
|
||||
NormalPriority: 2,
|
||||
LowPriority: 1,
|
||||
}, nil
|
||||
}
|
||||
6
apiserver/controllers/request_types.go
Normal file
6
apiserver/controllers/request_types.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package controllers
|
||||
|
||||
// RawTransaction represents a raw transaction posted to the API server
|
||||
type RawTransaction struct {
|
||||
RawTransaction string `json:"rawTransaction"`
|
||||
}
|
||||
113
apiserver/controllers/response_types.go
Normal file
113
apiserver/controllers/response_types.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"github.com/daglabs/btcd/apiserver/models"
|
||||
"github.com/daglabs/btcd/btcjson"
|
||||
)
|
||||
|
||||
type transactionResponse struct {
|
||||
TransactionHash string `json:"transactionHash"`
|
||||
TransactionID string `json:"transactionId"`
|
||||
AcceptingBlockHash string `json:"acceptingBlockHash,omitempty"`
|
||||
AcceptingBlockBlueScore uint64 `json:"acceptingBlockBlueScore,omitempty"`
|
||||
SubnetworkID string `json:"subnetworkId"`
|
||||
LockTime uint64 `json:"lockTime"`
|
||||
Gas uint64 `json:"gas,omitempty"`
|
||||
PayloadHash string `json:"payloadHash,omitempty"`
|
||||
Payload string `json:"payload,omitempty"`
|
||||
Inputs []*transactionInputResponse `json:"inputs"`
|
||||
Outputs []*transactionOutputResponse `json:"outputs"`
|
||||
Mass uint64 `json:"mass"`
|
||||
}
|
||||
|
||||
type transactionOutputResponse struct {
|
||||
TransactionID string `json:"transactionId,omitempty"`
|
||||
Value uint64 `json:"value"`
|
||||
ScriptPubKey string `json:"scriptPubKey"`
|
||||
Address string `json:"address,omitempty"`
|
||||
AcceptingBlockHash string `json:"acceptingBlockHash,omitempty"`
|
||||
AcceptingBlockBlueScore uint64 `json:"acceptingBlockBlueScore,omitempty"`
|
||||
}
|
||||
|
||||
type transactionInputResponse struct {
|
||||
TransactionID string `json:"transactionId,omitempty"`
|
||||
PreviousTransactionID string `json:"previousTransactionId"`
|
||||
PreviousTransactionOutputIndex uint32 `json:"previousTransactionOutputIndex"`
|
||||
SignatureScript string `json:"signatureScript"`
|
||||
Sequence uint64 `json:"sequence"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type blockResponse struct {
|
||||
BlockHash string
|
||||
Version int32
|
||||
HashMerkleRoot string
|
||||
AcceptedIDMerkleRoot string
|
||||
UTXOCommitment string
|
||||
Timestamp uint64
|
||||
Bits uint32
|
||||
Nonce uint64
|
||||
AcceptingBlockHash *string
|
||||
BlueScore uint64
|
||||
IsChainBlock bool
|
||||
Mass uint64
|
||||
}
|
||||
|
||||
type feeEstimateResponse struct {
|
||||
HighPriority, NormalPriority, LowPriority float64
|
||||
}
|
||||
|
||||
func convertTxModelToTxResponse(tx *models.Transaction) *transactionResponse {
|
||||
txRes := &transactionResponse{
|
||||
TransactionHash: tx.TransactionHash,
|
||||
TransactionID: tx.TransactionID,
|
||||
AcceptingBlockHash: tx.AcceptingBlock.BlockHash,
|
||||
AcceptingBlockBlueScore: tx.AcceptingBlock.BlueScore,
|
||||
SubnetworkID: tx.Subnetwork.SubnetworkID,
|
||||
LockTime: tx.LockTime,
|
||||
Gas: tx.Gas,
|
||||
PayloadHash: tx.PayloadHash,
|
||||
Payload: hex.EncodeToString(tx.Payload),
|
||||
Inputs: make([]*transactionInputResponse, len(tx.TransactionInputs)),
|
||||
Outputs: make([]*transactionOutputResponse, len(tx.TransactionOutputs)),
|
||||
Mass: tx.Mass,
|
||||
}
|
||||
for i, txOut := range tx.TransactionOutputs {
|
||||
txRes.Outputs[i] = &transactionOutputResponse{
|
||||
Value: txOut.Value,
|
||||
ScriptPubKey: hex.EncodeToString(txOut.ScriptPubKey),
|
||||
Address: txOut.Address.Address,
|
||||
}
|
||||
}
|
||||
for i, txIn := range tx.TransactionInputs {
|
||||
txRes.Inputs[i] = &transactionInputResponse{
|
||||
PreviousTransactionID: txIn.PreviousTransactionOutput.Transaction.TransactionID,
|
||||
PreviousTransactionOutputIndex: txIn.PreviousTransactionOutput.Index,
|
||||
SignatureScript: hex.EncodeToString(txIn.SignatureScript),
|
||||
Sequence: txIn.Sequence,
|
||||
Address: txIn.PreviousTransactionOutput.Address.Address,
|
||||
}
|
||||
}
|
||||
return txRes
|
||||
}
|
||||
|
||||
func convertBlockModelToBlockResponse(block *models.Block) *blockResponse {
|
||||
blockRes := &blockResponse{
|
||||
BlockHash: block.BlockHash,
|
||||
Version: block.Version,
|
||||
HashMerkleRoot: block.HashMerkleRoot,
|
||||
AcceptedIDMerkleRoot: block.AcceptedIDMerkleRoot,
|
||||
UTXOCommitment: block.UTXOCommitment,
|
||||
Timestamp: uint64(block.Timestamp.Unix()),
|
||||
Bits: block.Bits,
|
||||
Nonce: block.Nonce,
|
||||
BlueScore: block.BlueScore,
|
||||
IsChainBlock: block.IsChainBlock,
|
||||
Mass: block.Mass,
|
||||
}
|
||||
if block.AcceptingBlock != nil {
|
||||
blockRes.AcceptingBlockHash = btcjson.String(block.AcceptingBlock.BlockHash)
|
||||
}
|
||||
return blockRes
|
||||
}
|
||||
187
apiserver/controllers/transaction.go
Normal file
187
apiserver/controllers/transaction.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/daglabs/btcd/apiserver/database"
|
||||
"github.com/daglabs/btcd/apiserver/jsonrpc"
|
||||
"github.com/daglabs/btcd/apiserver/models"
|
||||
"github.com/daglabs/btcd/apiserver/utils"
|
||||
"github.com/daglabs/btcd/btcjson"
|
||||
"github.com/daglabs/btcd/util/daghash"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
"github.com/jinzhu/gorm"
|
||||
)
|
||||
|
||||
const maxGetTransactionsLimit = 1000
|
||||
|
||||
// GetTransactionByIDHandler returns a transaction by a given transaction ID.
|
||||
func GetTransactionByIDHandler(txID string) (interface{}, *utils.HandlerError) {
|
||||
if bytes, err := hex.DecodeString(txID); err != nil || len(bytes) != daghash.TxIDSize {
|
||||
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity,
|
||||
fmt.Sprintf("The given txid is not a hex-encoded %d-byte hash.", daghash.TxIDSize))
|
||||
}
|
||||
|
||||
db, err := database.DB()
|
||||
if err != nil {
|
||||
return nil, utils.NewInternalServerHandlerError(err.Error())
|
||||
}
|
||||
|
||||
tx := &models.Transaction{}
|
||||
query := db.Where(&models.Transaction{TransactionID: txID})
|
||||
dbResult := addTxPreloadedFields(query).First(&tx)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.IsDBRecordNotFoundError(dbErrors) {
|
||||
return nil, utils.NewHandlerError(http.StatusNotFound, "No transaction with the given txid was found.")
|
||||
}
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return nil, utils.NewHandlerErrorFromDBErrors("Some errors where encountered when loading transaction from the database:", dbErrors)
|
||||
}
|
||||
return convertTxModelToTxResponse(tx), nil
|
||||
}
|
||||
|
||||
// GetTransactionByHashHandler returns a transaction by a given transaction hash.
|
||||
func GetTransactionByHashHandler(txHash string) (interface{}, *utils.HandlerError) {
|
||||
if bytes, err := hex.DecodeString(txHash); err != nil || len(bytes) != daghash.HashSize {
|
||||
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity,
|
||||
fmt.Sprintf("The given txhash is not a hex-encoded %d-byte hash.", daghash.HashSize))
|
||||
}
|
||||
|
||||
db, err := database.DB()
|
||||
if err != nil {
|
||||
return nil, utils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
tx := &models.Transaction{}
|
||||
query := db.Where(&models.Transaction{TransactionHash: txHash})
|
||||
dbResult := addTxPreloadedFields(query).First(&tx)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.IsDBRecordNotFoundError(dbErrors) {
|
||||
return nil, utils.NewHandlerError(http.StatusNotFound, "No transaction with the given txhash was found.")
|
||||
}
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return nil, utils.NewHandlerErrorFromDBErrors("Some errors where encountered when loading transaction from the database:", dbErrors)
|
||||
}
|
||||
return convertTxModelToTxResponse(tx), nil
|
||||
}
|
||||
|
||||
// GetTransactionsByAddressHandler searches for all transactions
|
||||
// where the given address is either an input or an output.
|
||||
func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64) (interface{}, *utils.HandlerError) {
|
||||
if limit > maxGetTransactionsLimit {
|
||||
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity,
|
||||
fmt.Sprintf("The maximum allowed value for the limit is %d", maxGetTransactionsLimit))
|
||||
}
|
||||
|
||||
db, err := database.DB()
|
||||
if err != nil {
|
||||
return nil, utils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
txs := []*models.Transaction{}
|
||||
query := db.
|
||||
Joins("LEFT JOIN `transaction_outputs` ON `transaction_outputs`.`transaction_id` = `transactions`.`id`").
|
||||
Joins("LEFT JOIN `addresses` AS `out_addresses` ON `out_addresses`.`id` = `transaction_outputs`.`address_id`").
|
||||
Joins("LEFT JOIN `transaction_inputs` ON `transaction_inputs`.`transaction_id` = `transactions`.`id`").
|
||||
Joins("LEFT JOIN `transaction_outputs` AS `inputs_outs` ON `inputs_outs`.`id` = `transaction_inputs`.`transaction_output_id`").
|
||||
Joins("LEFT JOIN `addresses` AS `in_addresses` ON `in_addresses`.`id` = `inputs_outs`.`address_id`").
|
||||
Where("`out_addresses`.`address` = ?", address).
|
||||
Or("`in_addresses`.`address` = ?", address).
|
||||
Limit(limit).
|
||||
Offset(skip).
|
||||
Order("`transactions`.`id` ASC")
|
||||
dbResult := addTxPreloadedFields(query).Find(&txs)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return nil, utils.NewHandlerErrorFromDBErrors("Some errors where encountered when loading transactions from the database:", dbErrors)
|
||||
}
|
||||
txResponses := make([]*transactionResponse, len(txs))
|
||||
for i, tx := range txs {
|
||||
txResponses[i] = convertTxModelToTxResponse(tx)
|
||||
}
|
||||
return txResponses, nil
|
||||
}
|
||||
|
||||
// GetUTXOsByAddressHandler searches for all UTXOs that belong to a certain address.
|
||||
func GetUTXOsByAddressHandler(address string) (interface{}, *utils.HandlerError) {
|
||||
db, err := database.DB()
|
||||
if err != nil {
|
||||
return nil, utils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
var transactionOutputs []*models.TransactionOutput
|
||||
dbErrors := db.
|
||||
Joins("LEFT JOIN `addresses` ON `addresses`.`id` = `transaction_outputs`.`address_id`").
|
||||
Where("`addresses`.`address` = ? AND `transaction_outputs`.`is_spent` = 0", address).
|
||||
Preload("Transaction.AcceptingBlock").
|
||||
Find(&transactionOutputs).GetErrors()
|
||||
if len(dbErrors) > 0 {
|
||||
return nil, utils.NewHandlerErrorFromDBErrors("Some errors where encountered when loading UTXOs from the database:", dbErrors)
|
||||
}
|
||||
|
||||
UTXOsResponses := make([]*transactionOutputResponse, len(transactionOutputs))
|
||||
for i, transactionOutput := range transactionOutputs {
|
||||
UTXOsResponses[i] = &transactionOutputResponse{
|
||||
Value: transactionOutput.Value,
|
||||
ScriptPubKey: hex.EncodeToString(transactionOutput.ScriptPubKey),
|
||||
AcceptingBlockHash: transactionOutput.Transaction.AcceptingBlock.BlockHash,
|
||||
AcceptingBlockBlueScore: transactionOutput.Transaction.AcceptingBlock.BlueScore,
|
||||
}
|
||||
}
|
||||
return UTXOsResponses, nil
|
||||
}
|
||||
|
||||
func addTxPreloadedFields(query *gorm.DB) *gorm.DB {
|
||||
return query.Preload("AcceptingBlock").
|
||||
Preload("Subnetwork").
|
||||
Preload("TransactionOutputs").
|
||||
Preload("TransactionOutputs.Address").
|
||||
Preload("TransactionInputs.PreviousTransactionOutput.Transaction").
|
||||
Preload("TransactionInputs.PreviousTransactionOutput.Address")
|
||||
}
|
||||
|
||||
// PostTransaction forwards a raw transaction to the JSON-RPC API server
|
||||
func PostTransaction(requestBody []byte) *utils.HandlerError {
|
||||
client, err := jsonrpc.GetClient()
|
||||
if err != nil {
|
||||
return utils.NewInternalServerHandlerError(err.Error())
|
||||
}
|
||||
|
||||
rawTx := &RawTransaction{}
|
||||
err = json.Unmarshal(requestBody, rawTx)
|
||||
if err != nil {
|
||||
return utils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
|
||||
fmt.Sprintf("Error unmarshalling request body: %s", err),
|
||||
"The request body is not json-formatted")
|
||||
}
|
||||
|
||||
txBytes, err := hex.DecodeString(rawTx.RawTransaction)
|
||||
if err != nil {
|
||||
return utils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
|
||||
fmt.Sprintf("Error decoding hex raw transaction: %s", err),
|
||||
"The raw transaction is not a hex-encoded transaction")
|
||||
}
|
||||
|
||||
txReader := bytes.NewReader(txBytes)
|
||||
tx := &wire.MsgTx{}
|
||||
err = tx.BtcDecode(txReader, 0)
|
||||
if err != nil {
|
||||
return utils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
|
||||
fmt.Sprintf("Error decoding raw transaction: %s", err),
|
||||
"Error decoding raw transaction")
|
||||
}
|
||||
|
||||
_, err = client.SendRawTransaction(tx, true)
|
||||
if err != nil {
|
||||
if rpcErr, ok := err.(btcjson.RPCError); ok && rpcErr.Code == btcjson.ErrRPCVerify {
|
||||
return utils.NewHandlerError(http.StatusInternalServerError, rpcErr.Message)
|
||||
}
|
||||
return utils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
140
apiserver/database/database.go
Normal file
140
apiserver/database/database.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/daglabs/btcd/apiserver/config"
|
||||
"github.com/golang-migrate/migrate/v4/source"
|
||||
"github.com/jinzhu/gorm"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
)
|
||||
|
||||
// db is the API server database.
|
||||
var db *gorm.DB
|
||||
|
||||
// DB returns a reference to the database connection
|
||||
func DB() (*gorm.DB, error) {
|
||||
if db == nil {
|
||||
return nil, errors.New("Database is not connected")
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
type gormLogger struct{}
|
||||
|
||||
func (l gormLogger) Print(v ...interface{}) {
|
||||
str := fmt.Sprint(v...)
|
||||
log.Errorf(str)
|
||||
}
|
||||
|
||||
// Connect connects to the database mentioned in
|
||||
// config variable.
|
||||
func Connect(cfg *config.Config) error {
|
||||
connectionString := buildConnectionString(cfg)
|
||||
migrator, driver, err := openMigrator(connectionString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isCurrent, version, err := isCurrent(migrator, driver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error checking whether the database is current: %s", err)
|
||||
}
|
||||
if !isCurrent {
|
||||
return fmt.Errorf("Database is not current (version %d). Please migrate"+
|
||||
" the database by running the server with --migrate flag and then run it again.", version)
|
||||
}
|
||||
|
||||
db, err = gorm.Open("mysql", connectionString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db.SetLogger(gormLogger{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the connection to the database
|
||||
func Close() error {
|
||||
if db == nil {
|
||||
return nil
|
||||
}
|
||||
err := db.Close()
|
||||
db = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func buildConnectionString(cfg *config.Config) string {
|
||||
return fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True",
|
||||
cfg.DBUser, cfg.DBPassword, cfg.DBAddress, cfg.DBName)
|
||||
}
|
||||
|
||||
// isCurrent resolves whether the database is on the latest
|
||||
// version of the schema.
|
||||
func isCurrent(migrator *migrate.Migrate, driver source.Driver) (bool, uint, error) {
|
||||
// Get the current version
|
||||
version, isDirty, err := migrator.Version()
|
||||
if err == migrate.ErrNilVersion {
|
||||
return false, 0, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
if isDirty {
|
||||
return false, 0, fmt.Errorf("Database is dirty")
|
||||
}
|
||||
|
||||
// The database is current if Next returns ErrNotExist
|
||||
_, err = driver.Next(version)
|
||||
if pathErr, ok := err.(*os.PathError); ok {
|
||||
if pathErr.Err == os.ErrNotExist {
|
||||
return true, version, nil
|
||||
}
|
||||
}
|
||||
return false, version, err
|
||||
}
|
||||
|
||||
func openMigrator(connectionString string) (*migrate.Migrate, source.Driver, error) {
|
||||
driver, err := source.Open("file://migrations")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
migrator, err := migrate.NewWithSourceInstance(
|
||||
"migrations", driver, "mysql://"+connectionString)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return migrator, driver, nil
|
||||
}
|
||||
|
||||
// Migrate database to the latest version.
|
||||
func Migrate(cfg *config.Config) error {
|
||||
connectionString := buildConnectionString(cfg)
|
||||
migrator, driver, err := openMigrator(connectionString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isCurrent, version, err := isCurrent(migrator, driver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error checking whether the database is current: %s", err)
|
||||
}
|
||||
if isCurrent {
|
||||
log.Infof("Database is already up-to-date (version %d)", version)
|
||||
return nil
|
||||
}
|
||||
err = migrator.Up()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
version, isDirty, err := migrator.Version()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isDirty {
|
||||
return fmt.Errorf("error migrating database: database is dirty")
|
||||
}
|
||||
log.Infof("Migrated database to the latest version (version %d)", version)
|
||||
return nil
|
||||
}
|
||||
9
apiserver/database/log.go
Normal file
9
apiserver/database/log.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package database
|
||||
|
||||
import "github.com/daglabs/btcd/util/panics"
|
||||
import "github.com/daglabs/btcd/apiserver/logger"
|
||||
|
||||
var (
|
||||
log = logger.BackendLog.Logger("DTBS")
|
||||
spawn = panics.GoroutineWrapperFunc(log, logger.BackendLog)
|
||||
)
|
||||
28
apiserver/docker/Dockerfile
Normal file
28
apiserver/docker/Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
||||
# -- multistage docker build: stage #1: build stage
|
||||
FROM golang:1.13-alpine AS build
|
||||
|
||||
RUN mkdir -p /go/src/github.com/daglabs/btcd
|
||||
|
||||
WORKDIR /go/src/github.com/daglabs/btcd
|
||||
|
||||
RUN apk add --no-cache curl git
|
||||
|
||||
COPY go.mod .
|
||||
COPY go.sum .
|
||||
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN cd apiserver && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o apiserver .
|
||||
|
||||
# --- multistage docker build: stage #2: runtime image
|
||||
FROM alpine
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache tini
|
||||
|
||||
COPY --from=build /go/src/github.com/daglabs/btcd/apiserver/ /app/
|
||||
|
||||
ENTRYPOINT ["/sbin/tini", "--"]
|
||||
CMD ["/app/apiserver"]
|
||||
125
apiserver/jsonrpc/client.go
Normal file
125
apiserver/jsonrpc/client.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package jsonrpc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/daglabs/btcd/apiserver/config"
|
||||
"github.com/daglabs/btcd/util/daghash"
|
||||
|
||||
"github.com/daglabs/btcd/rpcclient"
|
||||
"github.com/daglabs/btcd/util"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
)
|
||||
|
||||
// Client represents a connection to the JSON-RPC API of a full node
|
||||
type Client struct {
|
||||
*rpcclient.Client
|
||||
OnBlockAdded chan *BlockAddedMsg
|
||||
OnChainChanged chan *ChainChangedMsg
|
||||
}
|
||||
|
||||
var client *Client
|
||||
|
||||
// GetClient returns an instance of the JSON-RPC client, in case we have an active connection
|
||||
func GetClient() (*Client, error) {
|
||||
if client == nil {
|
||||
return nil, errors.New("JSON-RPC is not connected")
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// BlockAddedMsg defines the message received in onBlockAdded
|
||||
type BlockAddedMsg struct {
|
||||
ChainHeight uint64
|
||||
Header *wire.BlockHeader
|
||||
}
|
||||
|
||||
// ChainChangedMsg defines the message received in onChainChanged
|
||||
type ChainChangedMsg struct {
|
||||
RemovedChainBlockHashes []*daghash.Hash
|
||||
AddedChainBlocks []*rpcclient.ChainBlock
|
||||
}
|
||||
|
||||
// Close closes the connection to the JSON-RPC API server
|
||||
func Close() {
|
||||
if client == nil {
|
||||
return
|
||||
}
|
||||
|
||||
client.Disconnect()
|
||||
client = nil
|
||||
}
|
||||
|
||||
// Connect initiates a connection to the JSON-RPC API Server
|
||||
func Connect(cfg *config.Config) error {
|
||||
var cert []byte
|
||||
if !cfg.DisableTLS {
|
||||
var err error
|
||||
cert, err = ioutil.ReadFile(cfg.RPCCert)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error reading certificates file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
connCfg := &rpcclient.ConnConfig{
|
||||
Host: cfg.RPCServer,
|
||||
Endpoint: "ws",
|
||||
User: cfg.RPCUser,
|
||||
Pass: cfg.RPCPassword,
|
||||
DisableTLS: cfg.DisableTLS,
|
||||
RequestTimeout: time.Second * 5,
|
||||
}
|
||||
|
||||
if !cfg.DisableTLS {
|
||||
connCfg.Certificates = cert
|
||||
}
|
||||
|
||||
var err error
|
||||
client, err = newClient(connCfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error connecting to address %s: %s", cfg.RPCServer, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newClient(connCfg *rpcclient.ConnConfig) (*Client, error) {
|
||||
client = &Client{
|
||||
OnBlockAdded: make(chan *BlockAddedMsg),
|
||||
OnChainChanged: make(chan *ChainChangedMsg),
|
||||
}
|
||||
notificationHandlers := &rpcclient.NotificationHandlers{
|
||||
OnFilteredBlockAdded: func(height uint64, header *wire.BlockHeader,
|
||||
txs []*util.Tx) {
|
||||
client.OnBlockAdded <- &BlockAddedMsg{
|
||||
ChainHeight: height,
|
||||
Header: header,
|
||||
}
|
||||
},
|
||||
OnChainChanged: func(removedChainBlockHashes []*daghash.Hash,
|
||||
addedChainBlocks []*rpcclient.ChainBlock) {
|
||||
client.OnChainChanged <- &ChainChangedMsg{
|
||||
RemovedChainBlockHashes: removedChainBlockHashes,
|
||||
AddedChainBlocks: addedChainBlocks,
|
||||
}
|
||||
},
|
||||
}
|
||||
var err error
|
||||
client.Client, err = rpcclient.New(connCfg, notificationHandlers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error connecting to address %s: %s", connCfg.Host, err)
|
||||
}
|
||||
|
||||
if err = client.NotifyBlocks(); err != nil {
|
||||
return nil, fmt.Errorf("Error while registering client %s for block notifications: %s", client.Host(), err)
|
||||
}
|
||||
if err = client.NotifyChainChanges(); err != nil {
|
||||
return nil, fmt.Errorf("Error while registering client %s for chain changes notifications: %s", client.Host(), err)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
11
apiserver/log.go
Normal file
11
apiserver/log.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/daglabs/btcd/logger"
|
||||
"github.com/daglabs/btcd/util/panics"
|
||||
)
|
||||
|
||||
var (
|
||||
log = logger.BackendLog.Logger("APIS")
|
||||
spawn = panics.GoroutineWrapperFunc(log, logger.BackendLog)
|
||||
)
|
||||
24
apiserver/logger/logger.go
Normal file
24
apiserver/logger/logger.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/daglabs/btcd/logs"
|
||||
"os"
|
||||
)
|
||||
|
||||
// BackendLog is the logging backend used to create all subsystem loggers.
|
||||
var BackendLog = logs.NewBackend()
|
||||
|
||||
// InitLog attaches log file and error log file to the backend log.
|
||||
func InitLog(logFile, errLogFile string) {
|
||||
err := BackendLog.AddLogFile(logFile, logs.LevelTrace)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error adding log file %s as log rotator for level %s: %s", logFile, logs.LevelTrace, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = BackendLog.AddLogFile(errLogFile, logs.LevelWarn)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error adding log file %s as log rotator for level %s: %s", errLogFile, logs.LevelWarn, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
73
apiserver/main.go
Normal file
73
apiserver/main.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/daglabs/btcd/apiserver/config"
|
||||
"github.com/daglabs/btcd/apiserver/database"
|
||||
"github.com/daglabs/btcd/apiserver/jsonrpc"
|
||||
"github.com/daglabs/btcd/apiserver/server"
|
||||
"github.com/daglabs/btcd/logger"
|
||||
"github.com/daglabs/btcd/signal"
|
||||
"github.com/daglabs/btcd/util/panics"
|
||||
_ "github.com/golang-migrate/migrate/v4/database/mysql"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||
)
|
||||
|
||||
func main() {
|
||||
defer panics.HandlePanic(log, logger.BackendLog)
|
||||
|
||||
cfg, err := config.Parse()
|
||||
if err != nil {
|
||||
errString := fmt.Sprintf("Error parsing command-line arguments: %s", err)
|
||||
_, fErr := fmt.Fprintf(os.Stderr, errString)
|
||||
if fErr != nil {
|
||||
panic(errString)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.Migrate {
|
||||
err := database.Migrate(cfg)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error migrating database: %s", err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
err = database.Connect(cfg)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error connecting to database: %s", err))
|
||||
}
|
||||
defer func() {
|
||||
err := database.Close()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error closing the database: %s", err))
|
||||
}
|
||||
}()
|
||||
|
||||
err = jsonrpc.Connect(cfg)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error connecting to servers: %s", err))
|
||||
}
|
||||
defer jsonrpc.Close()
|
||||
|
||||
shutdownServer := server.Start(cfg.HTTPListen)
|
||||
defer shutdownServer()
|
||||
|
||||
doneChan := make(chan struct{}, 1)
|
||||
spawn(func() {
|
||||
err := startSync(doneChan)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
|
||||
interrupt := signal.InterruptListener()
|
||||
<-interrupt
|
||||
|
||||
// Gracefully stop syncing
|
||||
doneChan <- struct{}{}
|
||||
}
|
||||
1
apiserver/migrations/000001_create_blocks_table.down.sql
Normal file
1
apiserver/migrations/000001_create_blocks_table.down.sql
Normal file
@@ -0,0 +1 @@
|
||||
DROP TABLE `blocks`;
|
||||
23
apiserver/migrations/000001_create_blocks_table.up.sql
Normal file
23
apiserver/migrations/000001_create_blocks_table.up.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
CREATE TABLE `blocks`
|
||||
(
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`block_hash` CHAR(64) NOT NULL,
|
||||
`accepting_block_id` BIGINT UNSIGNED NULL,
|
||||
`version` INT NOT NULL,
|
||||
`hash_merkle_root` CHAR(64) NOT NULL,
|
||||
`accepted_id_merkle_root` CHAR(64) NOT NULL,
|
||||
`utxo_commitment` CHAR(64) NOT NULL,
|
||||
`timestamp` DATETIME NOT NULL,
|
||||
`bits` INT UNSIGNED NOT NULL,
|
||||
`nonce` BIGINT UNSIGNED NOT NULL,
|
||||
`blue_score` BIGINT UNSIGNED NOT NULL,
|
||||
`is_chain_block` TINYINT NOT NULL,
|
||||
`mass` BIGINT NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE INDEX `idx_blocks_block_hash` (`block_hash`),
|
||||
INDEX `idx_blocks_timestamp` (`timestamp`),
|
||||
INDEX `idx_blocks_is_chain_block` (`is_chain_block`),
|
||||
CONSTRAINT `fk_blocks_accepting_block_id`
|
||||
FOREIGN KEY (`accepting_block_id`)
|
||||
REFERENCES `blocks` (`id`)
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE `parent_blocks`;
|
||||
@@ -0,0 +1,12 @@
|
||||
CREATE TABLE `parent_blocks`
|
||||
(
|
||||
`block_id` BIGINT UNSIGNED NOT NULL,
|
||||
`parent_block_id` BIGINT UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (`block_id`, `parent_block_id`),
|
||||
CONSTRAINT `fk_parent_blocks_block_id`
|
||||
FOREIGN KEY (`block_id`)
|
||||
REFERENCES `blocks` (`id`),
|
||||
CONSTRAINT `fk_parent_blocks_parent_block_id`
|
||||
FOREIGN KEY (`parent_block_id`)
|
||||
REFERENCES `blocks` (`id`)
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE `raw_blocks`;
|
||||
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE `raw_blocks`
|
||||
(
|
||||
`block_id` BIGINT UNSIGNED NOT NULL,
|
||||
`block_data` BLOB NOT NULL,
|
||||
PRIMARY KEY (`block_id`),
|
||||
CONSTRAINT `fk_raw_blocks_block_id`
|
||||
FOREIGN KEY (`block_id`)
|
||||
REFERENCES `blocks` (`id`)
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE `subnetworks`;
|
||||
@@ -0,0 +1,8 @@
|
||||
CREATE TABLE `subnetworks`
|
||||
(
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`subnetwork_id` CHAR(64) NOT NULL,
|
||||
`gas_limit` BIGINT UNSIGNED NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE INDEX `idx_subnetworks_subnetwork_id` (`subnetwork_id`)
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE `transactions`;
|
||||
19
apiserver/migrations/000005_create_transactions_table.up.sql
Normal file
19
apiserver/migrations/000005_create_transactions_table.up.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
CREATE TABLE `transactions`
|
||||
(
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`accepting_block_id` BIGINT UNSIGNED NULL,
|
||||
`transaction_hash` CHAR(64) NOT NULL,
|
||||
`transaction_id` CHAR(64) NOT NULL,
|
||||
`lock_time` BIGINT UNSIGNED NOT NULL,
|
||||
`subnetwork_id` BIGINT UNSIGNED NOT NULL,
|
||||
`gas` BIGINT UNSIGNED NOT NULL,
|
||||
`payload_hash` CHAR(64) NOT NULL,
|
||||
`payload` BLOB NOT NULL,
|
||||
`mass` BIGINT NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE INDEX `idx_transactions_transaction_hash` (`transaction_hash`),
|
||||
INDEX `idx_transactions_transaction_id` (`transaction_id`),
|
||||
CONSTRAINT `fk_transactions_accepting_block_id`
|
||||
FOREIGN KEY (`accepting_block_id`)
|
||||
REFERENCES `blocks` (`id`)
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE `transactions_to_blocks`;
|
||||
@@ -0,0 +1,14 @@
|
||||
CREATE TABLE `transactions_to_blocks`
|
||||
(
|
||||
`transaction_id` BIGINT UNSIGNED NOT NULL,
|
||||
`block_id` BIGINT UNSIGNED NOT NULL,
|
||||
`index` INT UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (`transaction_id`, `block_id`),
|
||||
INDEX `idx_transactions_to_blocks_index` (`index`),
|
||||
CONSTRAINT `fk_transactions_to_blocks_block_id`
|
||||
FOREIGN KEY (`block_id`)
|
||||
REFERENCES `blocks` (`id`),
|
||||
CONSTRAINT `fk_transactions_to_blocks_transaction_id`
|
||||
FOREIGN KEY (`transaction_id`)
|
||||
REFERENCES `transactions` (`id`)
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE `addresses`;
|
||||
@@ -0,0 +1,7 @@
|
||||
CREATE TABLE `addresses`
|
||||
(
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`address` CHAR(50) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE INDEX `idx_addresses_address` (`address`)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE `transaction_outputs`;
|
||||
@@ -0,0 +1,18 @@
|
||||
CREATE TABLE `transaction_outputs`
|
||||
(
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`transaction_id` BIGINT UNSIGNED NOT NULL,
|
||||
`index` INT UNSIGNED NOT NULL,
|
||||
`value` BIGINT UNSIGNED NOT NULL,
|
||||
`script_pub_key` BLOB NOT NULL,
|
||||
`is_spent` TINYINT NOT NULL,
|
||||
`address_id` BIGINT UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_transaction_outputs_transaction_id` (`transaction_id`),
|
||||
CONSTRAINT `fk_transaction_outputs_transaction_id`
|
||||
FOREIGN KEY (`transaction_id`)
|
||||
REFERENCES `transactions` (`id`),
|
||||
CONSTRAINT `fk_transaction_outputs_address_id`
|
||||
FOREIGN KEY (`address_id`)
|
||||
REFERENCES `addresses` (`id`)
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE `transaction_inputs`;
|
||||
@@ -0,0 +1,18 @@
|
||||
CREATE TABLE `transaction_inputs`
|
||||
(
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`transaction_id` BIGINT UNSIGNED NULL,
|
||||
`previous_transaction_output_id` BIGINT UNSIGNED NOT NULL,
|
||||
`index` INT UNSIGNED NOT NULL,
|
||||
`signature_script` BLOB NOT NULL,
|
||||
`sequence` BIGINT UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_transaction_inputs_transaction_id` (`transaction_id`),
|
||||
INDEX `idx_transaction_inputs_previous_transaction_output_id` (`previous_transaction_output_id`),
|
||||
CONSTRAINT `fk_transaction_inputs_transaction_id`
|
||||
FOREIGN KEY (`transaction_id`)
|
||||
REFERENCES `transactions` (`id`),
|
||||
CONSTRAINT `fk_transaction_inputs_previous_transaction_output_id`
|
||||
FOREIGN KEY (`previous_transaction_output_id`)
|
||||
REFERENCES `transaction_outputs` (`id`)
|
||||
);
|
||||
111
apiserver/models/models.go
Normal file
111
apiserver/models/models.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Block is the gorm model for the 'blocks' table
|
||||
type Block struct {
|
||||
ID uint64 `gorm:"primary_key"`
|
||||
BlockHash string
|
||||
AcceptingBlockID *uint64
|
||||
AcceptingBlock *Block
|
||||
Version int32
|
||||
HashMerkleRoot string
|
||||
AcceptedIDMerkleRoot string
|
||||
UTXOCommitment string
|
||||
Timestamp time.Time
|
||||
Bits uint32
|
||||
Nonce uint64
|
||||
BlueScore uint64
|
||||
IsChainBlock bool
|
||||
Mass uint64
|
||||
ParentBlocks []Block `gorm:"many2many:parent_blocks;"`
|
||||
}
|
||||
|
||||
// ParentBlock is the gorm model for the 'parent_blocks' table
|
||||
type ParentBlock struct {
|
||||
BlockID uint64
|
||||
Block Block
|
||||
ParentBlockID uint64
|
||||
ParentBlock Block
|
||||
}
|
||||
|
||||
// RawBlock is the gorm model for the 'raw_blocks' table
|
||||
type RawBlock struct {
|
||||
BlockID uint64
|
||||
Block Block
|
||||
BlockData []byte
|
||||
}
|
||||
|
||||
// Subnetwork is the gorm model for the 'subnetworks' table
|
||||
type Subnetwork struct {
|
||||
ID uint64 `gorm:"primary_key"`
|
||||
SubnetworkID string
|
||||
GasLimit *uint64
|
||||
}
|
||||
|
||||
// Transaction is the gorm model for the 'transactions' table
|
||||
type Transaction struct {
|
||||
ID uint64 `gorm:"primary_key"`
|
||||
AcceptingBlockID *uint64
|
||||
AcceptingBlock *Block
|
||||
TransactionHash string
|
||||
TransactionID string
|
||||
LockTime uint64
|
||||
SubnetworkID uint64
|
||||
Subnetwork Subnetwork
|
||||
Gas uint64
|
||||
PayloadHash string
|
||||
Payload []byte
|
||||
Mass uint64
|
||||
Blocks []Block `gorm:"many2many:transactions_to_blocks;"`
|
||||
TransactionOutputs []TransactionOutput
|
||||
TransactionInputs []TransactionInput
|
||||
}
|
||||
|
||||
// TransactionBlock is the gorm model for the 'transactions_to_blocks' table
|
||||
type TransactionBlock struct {
|
||||
TransactionID uint64
|
||||
Transaction Transaction
|
||||
BlockID uint64
|
||||
Block Block
|
||||
Index uint32
|
||||
}
|
||||
|
||||
// TableName returns the table name associated to the
|
||||
// TransactionBlock gorm model
|
||||
func (TransactionBlock) TableName() string {
|
||||
return "transactions_to_blocks"
|
||||
}
|
||||
|
||||
// TransactionOutput is the gorm model for the 'transaction_outputs' table
|
||||
type TransactionOutput struct {
|
||||
ID uint64 `gorm:"primary_key"`
|
||||
TransactionID uint64
|
||||
Transaction Transaction
|
||||
Index uint32
|
||||
Value uint64
|
||||
ScriptPubKey []byte
|
||||
IsSpent bool
|
||||
AddressID uint64
|
||||
Address Address
|
||||
}
|
||||
|
||||
// TransactionInput is the gorm model for the 'transaction_inputs' table
|
||||
type TransactionInput struct {
|
||||
ID uint64 `gorm:"primary_key"`
|
||||
TransactionID uint64
|
||||
Transaction Transaction
|
||||
PreviousTransactionOutputID uint64
|
||||
PreviousTransactionOutput TransactionOutput
|
||||
Index uint32
|
||||
SignatureScript []byte
|
||||
Sequence uint64
|
||||
}
|
||||
|
||||
// Address is the gorm model for the 'utxos' table
|
||||
type Address struct {
|
||||
ID uint64 `gorm:"primary_key"`
|
||||
Address string
|
||||
}
|
||||
9
apiserver/server/log.go
Normal file
9
apiserver/server/log.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package server
|
||||
|
||||
import "github.com/daglabs/btcd/util/panics"
|
||||
import "github.com/daglabs/btcd/apiserver/logger"
|
||||
|
||||
var (
|
||||
log = logger.BackendLog.Logger("REST")
|
||||
spawn = panics.GoroutineWrapperFunc(log, logger.BackendLog)
|
||||
)
|
||||
50
apiserver/server/middlewares.go
Normal file
50
apiserver/server/middlewares.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/daglabs/btcd/apiserver/utils"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
var nextRequestID uint64 = 1
|
||||
|
||||
func addRequestMetadataMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
rCtx := utils.ToAPIServerContext(r.Context()).SetRequestID(nextRequestID)
|
||||
r.WithContext(rCtx)
|
||||
nextRequestID++
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func loggingMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := utils.ToAPIServerContext(r.Context())
|
||||
ctx.Infof("Method: %s URI: %s", r.Method, r.RequestURI)
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func recoveryMiddleware(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := utils.ToAPIServerContext(r.Context())
|
||||
defer func() {
|
||||
recoveryErr := recover()
|
||||
if recoveryErr != nil {
|
||||
recoveryErrStr := fmt.Sprintf("%s", recoveryErr)
|
||||
log.Criticalf("Fatal error: %s", recoveryErrStr)
|
||||
log.Criticalf("Stack trace: %s", debug.Stack())
|
||||
sendErr(ctx, w, utils.NewInternalServerHandlerError(recoveryErrStr))
|
||||
}
|
||||
}()
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func setJSONMiddleware(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
244
apiserver/server/routes.go
Normal file
244
apiserver/server/routes.go
Normal file
@@ -0,0 +1,244 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/daglabs/btcd/apiserver/controllers"
|
||||
"github.com/daglabs/btcd/apiserver/utils"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
const (
|
||||
routeParamTxID = "txID"
|
||||
routeParamTxHash = "txHash"
|
||||
routeParamAddress = "address"
|
||||
routeParamBlockHash = "blockHash"
|
||||
)
|
||||
|
||||
const (
|
||||
queryParamSkip = "skip"
|
||||
queryParamLimit = "limit"
|
||||
queryParamOrder = "order"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultGetTransactionsLimit = 100
|
||||
defaultGetBlocksLimit = 25
|
||||
defaultGetBlocksOrder = controllers.OrderAscending
|
||||
)
|
||||
|
||||
type handlerFunc func(ctx *utils.APIServerContext, routeParams map[string]string, queryParams map[string]string, requestBody []byte) (
|
||||
interface{}, *utils.HandlerError)
|
||||
|
||||
func makeHandler(handler handlerFunc) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := utils.ToAPIServerContext(r.Context())
|
||||
|
||||
var requestBody []byte
|
||||
if r.Method == "POST" {
|
||||
var err error
|
||||
requestBody, err = ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
sendErr(ctx, w, utils.NewHandlerError(500, "Internal server error occured"))
|
||||
}
|
||||
}
|
||||
|
||||
flattenedQueryParams, hErr := flattenQueryParams(r.URL.Query())
|
||||
if hErr != nil {
|
||||
sendErr(ctx, w, hErr)
|
||||
return
|
||||
}
|
||||
|
||||
response, hErr := handler(ctx, mux.Vars(r), flattenedQueryParams, requestBody)
|
||||
if hErr != nil {
|
||||
sendErr(ctx, w, hErr)
|
||||
return
|
||||
}
|
||||
if response != nil {
|
||||
sendJSONResponse(w, response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func flattenQueryParams(queryParams map[string][]string) (map[string]string, *utils.HandlerError) {
|
||||
flattenedMap := make(map[string]string)
|
||||
for param, valuesSlice := range queryParams {
|
||||
if len(valuesSlice) > 1 {
|
||||
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter:"+
|
||||
" expected a single value but got multiple values", param))
|
||||
}
|
||||
flattenedMap[param] = valuesSlice[0]
|
||||
}
|
||||
return flattenedMap, nil
|
||||
}
|
||||
|
||||
type clientError struct {
|
||||
ErrorCode int `json:"errorCode"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
}
|
||||
|
||||
func sendErr(ctx *utils.APIServerContext, w http.ResponseWriter, hErr *utils.HandlerError) {
|
||||
errMsg := fmt.Sprintf("got error: %s", hErr)
|
||||
ctx.Warnf(errMsg)
|
||||
w.WriteHeader(hErr.Code)
|
||||
sendJSONResponse(w, &clientError{
|
||||
ErrorCode: hErr.Code,
|
||||
ErrorMessage: hErr.ClientMessage,
|
||||
})
|
||||
}
|
||||
|
||||
func sendJSONResponse(w http.ResponseWriter, response interface{}) {
|
||||
b, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err = fmt.Fprintf(w, string(b))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func mainHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string, _ []byte) (interface{}, *utils.HandlerError) {
|
||||
return struct {
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Message: "API server is running",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func addRoutes(router *mux.Router) {
|
||||
router.HandleFunc("/", makeHandler(mainHandler))
|
||||
|
||||
router.HandleFunc(
|
||||
fmt.Sprintf("/transaction/id/{%s}", routeParamTxID),
|
||||
makeHandler(getTransactionByIDHandler)).
|
||||
Methods("GET")
|
||||
|
||||
router.HandleFunc(
|
||||
fmt.Sprintf("/transaction/hash/{%s}", routeParamTxHash),
|
||||
makeHandler(getTransactionByHashHandler)).
|
||||
Methods("GET")
|
||||
|
||||
router.HandleFunc(
|
||||
fmt.Sprintf("/transactions/address/{%s}", routeParamAddress),
|
||||
makeHandler(getTransactionsByAddressHandler)).
|
||||
Methods("GET")
|
||||
|
||||
router.HandleFunc(
|
||||
fmt.Sprintf("/utxos/address/{%s}", routeParamAddress),
|
||||
makeHandler(getUTXOsByAddressHandler)).
|
||||
Methods("GET")
|
||||
|
||||
router.HandleFunc(
|
||||
fmt.Sprintf("/block/{%s}", routeParamBlockHash),
|
||||
makeHandler(getBlockByHashHandler)).
|
||||
Methods("GET")
|
||||
|
||||
router.HandleFunc(
|
||||
"/blocks",
|
||||
makeHandler(getBlocksHandler)).
|
||||
Methods("GET")
|
||||
|
||||
router.HandleFunc(
|
||||
"/fee-estimates",
|
||||
makeHandler(getFeeEstimatesHandler)).
|
||||
Methods("GET")
|
||||
|
||||
router.HandleFunc(
|
||||
"/transaction",
|
||||
makeHandler(postTransactionHandler)).
|
||||
Methods("POST")
|
||||
}
|
||||
|
||||
func convertQueryParamToInt(queryParams map[string]string, param string, defaultValue int) (int, *utils.HandlerError) {
|
||||
if _, ok := queryParams[param]; ok {
|
||||
intValue, err := strconv.Atoi(queryParams[param])
|
||||
if err != nil {
|
||||
return 0, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter: %s", param, err))
|
||||
}
|
||||
return intValue, nil
|
||||
}
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
func getTransactionByIDHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string,
|
||||
_ []byte) (interface{}, *utils.HandlerError) {
|
||||
|
||||
return controllers.GetTransactionByIDHandler(routeParams[routeParamTxID])
|
||||
}
|
||||
|
||||
func getTransactionByHashHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string,
|
||||
_ []byte) (interface{}, *utils.HandlerError) {
|
||||
|
||||
return controllers.GetTransactionByHashHandler(routeParams[routeParamTxHash])
|
||||
}
|
||||
|
||||
func getTransactionsByAddressHandler(_ *utils.APIServerContext, routeParams map[string]string, queryParams map[string]string,
|
||||
_ []byte) (interface{}, *utils.HandlerError) {
|
||||
|
||||
skip, hErr := convertQueryParamToInt(queryParams, queryParamSkip, 0)
|
||||
if hErr != nil {
|
||||
return nil, hErr
|
||||
}
|
||||
limit, hErr := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetTransactionsLimit)
|
||||
if hErr != nil {
|
||||
return nil, hErr
|
||||
}
|
||||
if _, ok := queryParams[queryParamLimit]; ok {
|
||||
var err error
|
||||
skip, err = strconv.Atoi(queryParams[queryParamLimit])
|
||||
if err != nil {
|
||||
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity,
|
||||
fmt.Sprintf("Couldn't parse the '%s' query parameter: %s", queryParamLimit, err))
|
||||
}
|
||||
}
|
||||
return controllers.GetTransactionsByAddressHandler(routeParams[routeParamAddress], uint64(skip), uint64(limit))
|
||||
}
|
||||
|
||||
func getUTXOsByAddressHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string,
|
||||
_ []byte) (interface{}, *utils.HandlerError) {
|
||||
|
||||
return controllers.GetUTXOsByAddressHandler(routeParams[routeParamAddress])
|
||||
}
|
||||
|
||||
func getBlockByHashHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string,
|
||||
_ []byte) (interface{}, *utils.HandlerError) {
|
||||
|
||||
return controllers.GetBlockByHashHandler(routeParams[routeParamBlockHash])
|
||||
}
|
||||
|
||||
func getFeeEstimatesHandler(_ *utils.APIServerContext, _ map[string]string, _ map[string]string,
|
||||
_ []byte) (interface{}, *utils.HandlerError) {
|
||||
|
||||
return controllers.GetFeeEstimatesHandler()
|
||||
}
|
||||
|
||||
func getBlocksHandler(_ *utils.APIServerContext, _ map[string]string, queryParams map[string]string,
|
||||
_ []byte) (interface{}, *utils.HandlerError) {
|
||||
|
||||
skip, hErr := convertQueryParamToInt(queryParams, queryParamSkip, 0)
|
||||
if hErr != nil {
|
||||
return nil, hErr
|
||||
}
|
||||
limit, hErr := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetBlocksLimit)
|
||||
if hErr != nil {
|
||||
return nil, hErr
|
||||
}
|
||||
order := defaultGetBlocksOrder
|
||||
if orderParamValue, ok := queryParams[queryParamOrder]; ok {
|
||||
if orderParamValue != controllers.OrderAscending && orderParamValue != controllers.OrderDescending {
|
||||
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("'%s' is not a valid value for the '%s' query parameter", orderParamValue, queryParamLimit))
|
||||
}
|
||||
order = orderParamValue
|
||||
}
|
||||
return controllers.GetBlocksHandler(order, uint64(skip), uint64(limit))
|
||||
}
|
||||
|
||||
func postTransactionHandler(_ *utils.APIServerContext, _ map[string]string, _ map[string]string,
|
||||
requestBody []byte) (interface{}, *utils.HandlerError) {
|
||||
return nil, controllers.PostTransaction(requestBody)
|
||||
}
|
||||
39
apiserver/server/server.go
Normal file
39
apiserver/server/server.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
const gracefulShutdownTimeout = 30 * time.Second
|
||||
|
||||
// Start starts the HTTP REST server and returns a
|
||||
// function to gracefully shutdown it.
|
||||
func Start(listenAddr string) func() {
|
||||
router := mux.NewRouter()
|
||||
router.Use(addRequestMetadataMiddleware)
|
||||
router.Use(recoveryMiddleware)
|
||||
router.Use(loggingMiddleware)
|
||||
router.Use(setJSONMiddleware)
|
||||
addRoutes(router)
|
||||
httpServer := &http.Server{
|
||||
Addr: listenAddr,
|
||||
Handler: handlers.CORS()(router),
|
||||
}
|
||||
spawn(func() {
|
||||
log.Errorf("%s", httpServer.ListenAndServe())
|
||||
})
|
||||
|
||||
return func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), gracefulShutdownTimeout)
|
||||
defer cancel()
|
||||
err := httpServer.Shutdown(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("Error shutting down HTTP server: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
877
apiserver/sync.go
Normal file
877
apiserver/sync.go
Normal file
@@ -0,0 +1,877 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/daglabs/btcd/apiserver/config"
|
||||
"github.com/daglabs/btcd/apiserver/database"
|
||||
"github.com/daglabs/btcd/apiserver/jsonrpc"
|
||||
"github.com/daglabs/btcd/apiserver/models"
|
||||
"github.com/daglabs/btcd/apiserver/utils"
|
||||
"github.com/daglabs/btcd/btcjson"
|
||||
"github.com/daglabs/btcd/txscript"
|
||||
"github.com/daglabs/btcd/util/daghash"
|
||||
"github.com/daglabs/btcd/util/subnetworkid"
|
||||
"github.com/jinzhu/gorm"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// startSync keeps the node and the API server in sync. On start, it downloads
|
||||
// all data that's missing from the API server, and once it's done it keeps
|
||||
// sync with the node via notifications.
|
||||
func startSync(doneChan chan struct{}) error {
|
||||
client, err := jsonrpc.GetClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Mass download missing data
|
||||
err = fetchInitialData(client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Keep the node and the API server in sync
|
||||
sync(client, doneChan)
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetchInitialData downloads all data that's currently missing from
|
||||
// the database.
|
||||
func fetchInitialData(client *jsonrpc.Client) error {
|
||||
err := syncBlocks(client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = syncSelectedParentChain(client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sync keeps the API server in sync with the node via notifications
|
||||
func sync(client *jsonrpc.Client, doneChan chan struct{}) {
|
||||
// ChainChangedMsgs must be processed in order and there may be times
|
||||
// when we may not be able to process them (e.g. appropriate
|
||||
// BlockAddedMsgs haven't arrived yet). As such, we pop messages from
|
||||
// client.OnChainChanged, make sure we're able to handle them, and
|
||||
// only then push them into nextChainChangedChan for them to be
|
||||
// actually handled.
|
||||
blockAddedMsgHandledChan := make(chan struct{})
|
||||
nextChainChangedChan := make(chan *jsonrpc.ChainChangedMsg)
|
||||
spawn(func() {
|
||||
for chainChanged := range client.OnChainChanged {
|
||||
for {
|
||||
<-blockAddedMsgHandledChan
|
||||
canHandle, err := canHandleChainChangedMsg(chainChanged)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if canHandle {
|
||||
break
|
||||
}
|
||||
}
|
||||
nextChainChangedChan <- chainChanged
|
||||
}
|
||||
})
|
||||
|
||||
// Handle client notifications until we're told to stop
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case blockAdded := <-client.OnBlockAdded:
|
||||
handleBlockAddedMsg(client, blockAdded)
|
||||
blockAddedMsgHandledChan <- struct{}{}
|
||||
case chainChanged := <-nextChainChangedChan:
|
||||
handleChainChangedMsg(chainChanged)
|
||||
case <-doneChan:
|
||||
log.Infof("startSync stopped")
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// syncBlocks attempts to download all DAG blocks starting with
|
||||
// the bluest block, and then inserts them into the database.
|
||||
func syncBlocks(client *jsonrpc.Client) error {
|
||||
// Start syncing from the bluest block hash. We use blue score to
|
||||
// simulate the "last" block we have because blue-block order is
|
||||
// the order that the node uses in the various JSONRPC calls.
|
||||
startHash, err := findHashOfBluestBlock(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var blocks []string
|
||||
var rawBlocks []btcjson.GetBlockVerboseResult
|
||||
for {
|
||||
blocksResult, err := client.GetBlocks(true, false, startHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(blocksResult.Hashes) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
rawBlocksResult, err := client.GetBlocks(true, true, startHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
startHash = &blocksResult.Hashes[len(blocksResult.Hashes)-1]
|
||||
blocks = append(blocks, blocksResult.Blocks...)
|
||||
rawBlocks = append(rawBlocks, rawBlocksResult.RawBlocks...)
|
||||
}
|
||||
|
||||
return addBlocks(client, blocks, rawBlocks)
|
||||
}
|
||||
|
||||
// syncSelectedParentChain attempts to download the selected parent
|
||||
// chain starting with the bluest chain-block, and then updates the
|
||||
// database accordingly.
|
||||
func syncSelectedParentChain(client *jsonrpc.Client) error {
|
||||
// Start syncing from the bluest chain-block hash. We use blue
|
||||
// score to simulate the "last" block we have because blue-block
|
||||
// order is the order that the node uses in the various JSONRPC
|
||||
// calls.
|
||||
startHash, err := findHashOfBluestBlock(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
chainFromBlockResult, err := client.GetChainFromBlock(false, startHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(chainFromBlockResult.AddedChainBlocks) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
startHash = &chainFromBlockResult.AddedChainBlocks[len(chainFromBlockResult.AddedChainBlocks)-1].Hash
|
||||
err = updateSelectedParentChain(chainFromBlockResult.RemovedChainBlockHashes,
|
||||
chainFromBlockResult.AddedChainBlocks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// findHashOfBluestBlock finds the block with the highest
|
||||
// blue score in the database. If the database is empty,
|
||||
// return nil.
|
||||
func findHashOfBluestBlock(mustBeChainBlock bool) (*string, error) {
|
||||
dbTx, err := database.DB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var block models.Block
|
||||
dbQuery := dbTx.Order("blue_score DESC")
|
||||
if mustBeChainBlock {
|
||||
dbQuery = dbQuery.Where(&models.Block{IsChainBlock: true})
|
||||
}
|
||||
dbResult := dbQuery.First(&block)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return nil, utils.NewErrorFromDBErrors("failed to find hash of bluest block: ", dbErrors)
|
||||
}
|
||||
if utils.IsDBRecordNotFoundError(dbErrors) {
|
||||
return nil, nil
|
||||
}
|
||||
return &block.BlockHash, nil
|
||||
}
|
||||
|
||||
// fetchBlock downloads the serialized block and raw block data of
|
||||
// the block with hash blockHash.
|
||||
func fetchBlock(client *jsonrpc.Client, blockHash *daghash.Hash) (
|
||||
block string, rawBlock *btcjson.GetBlockVerboseResult, err error) {
|
||||
msgBlock, err := client.GetBlock(blockHash, nil)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
writer := bytes.NewBuffer(make([]byte, 0, msgBlock.SerializeSize()))
|
||||
err = msgBlock.Serialize(writer)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
block = hex.EncodeToString(writer.Bytes())
|
||||
|
||||
rawBlock, err = client.GetBlockVerboseTx(blockHash, nil)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return block, rawBlock, nil
|
||||
}
|
||||
|
||||
// addBlocks inserts data in the given blocks and rawBlocks pairwise
|
||||
// into the database. See addBlock for further details.
|
||||
func addBlocks(client *jsonrpc.Client, blocks []string, rawBlocks []btcjson.GetBlockVerboseResult) error {
|
||||
for i, rawBlock := range rawBlocks {
|
||||
block := blocks[i]
|
||||
err := addBlock(client, block, rawBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func doesBlockExist(dbTx *gorm.DB, blockHash string) (bool, error) {
|
||||
var dbBlock models.Block
|
||||
dbResult := dbTx.
|
||||
Where(&models.Block{BlockHash: blockHash}).
|
||||
First(&dbBlock)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return false, utils.NewErrorFromDBErrors("failed to find block: ", dbErrors)
|
||||
}
|
||||
return !utils.IsDBRecordNotFoundError(dbErrors), nil
|
||||
}
|
||||
|
||||
// addBlocks inserts all the data that could be gleaned out of the serialized
|
||||
// block and raw block data into the database. This includes transactions,
|
||||
// subnetworks, and addresses.
|
||||
// Note that if this function may take a nil dbTx, in which case it would start
|
||||
// a database transaction by itself and commit it before returning.
|
||||
func addBlock(client *jsonrpc.Client, block string, rawBlock btcjson.GetBlockVerboseResult) error {
|
||||
db, err := database.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dbTx := db.Begin()
|
||||
|
||||
// Skip this block if it already exists.
|
||||
blockExists, err := doesBlockExist(dbTx, rawBlock.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if blockExists {
|
||||
dbTx.Commit()
|
||||
return nil
|
||||
}
|
||||
|
||||
dbBlock, err := insertBlock(dbTx, rawBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = insertBlockParents(dbTx, rawBlock, dbBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = insertBlockData(dbTx, block, dbBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, transaction := range rawBlock.RawTx {
|
||||
dbSubnetwork, err := insertSubnetwork(dbTx, &transaction, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dbTransaction, err := insertTransaction(dbTx, &transaction, dbSubnetwork)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = insertTransactionBlock(dbTx, dbBlock, dbTransaction, uint32(i))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = insertTransactionInputs(dbTx, &transaction, dbTransaction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = insertTransactionOutputs(dbTx, &transaction, dbTransaction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
dbTx.Commit()
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertBlock(dbTx *gorm.DB, rawBlock btcjson.GetBlockVerboseResult) (*models.Block, error) {
|
||||
bits, err := strconv.ParseUint(rawBlock.Bits, 16, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbBlock := models.Block{
|
||||
BlockHash: rawBlock.Hash,
|
||||
Version: rawBlock.Version,
|
||||
HashMerkleRoot: rawBlock.HashMerkleRoot,
|
||||
AcceptedIDMerkleRoot: rawBlock.AcceptedIDMerkleRoot,
|
||||
UTXOCommitment: rawBlock.UTXOCommitment,
|
||||
Timestamp: time.Unix(rawBlock.Time, 0),
|
||||
Bits: uint32(bits),
|
||||
Nonce: rawBlock.Nonce,
|
||||
BlueScore: rawBlock.BlueScore,
|
||||
IsChainBlock: false, // This must be false for updateSelectedParentChain to work properly
|
||||
Mass: rawBlock.Mass,
|
||||
}
|
||||
dbResult := dbTx.Create(&dbBlock)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return nil, utils.NewErrorFromDBErrors("failed to insert block: ", dbErrors)
|
||||
}
|
||||
return &dbBlock, nil
|
||||
}
|
||||
|
||||
func insertBlockParents(dbTx *gorm.DB, rawBlock btcjson.GetBlockVerboseResult, dbBlock *models.Block) error {
|
||||
// Exit early if this is the genesis block
|
||||
if len(rawBlock.ParentHashes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
dbWhereBlockIDsIn := make([]*models.Block, len(rawBlock.ParentHashes))
|
||||
for i, parentHash := range rawBlock.ParentHashes {
|
||||
dbWhereBlockIDsIn[i] = &models.Block{BlockHash: parentHash}
|
||||
}
|
||||
var dbParents []models.Block
|
||||
dbResult := dbTx.
|
||||
Where(dbWhereBlockIDsIn).
|
||||
First(&dbParents)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to find blocks: ", dbErrors)
|
||||
}
|
||||
if len(dbParents) != len(rawBlock.ParentHashes) {
|
||||
return fmt.Errorf("some parents are missing for block: %s", rawBlock.Hash)
|
||||
}
|
||||
|
||||
for _, dbParent := range dbParents {
|
||||
dbParentBlock := models.ParentBlock{
|
||||
BlockID: dbBlock.ID,
|
||||
ParentBlockID: dbParent.ID,
|
||||
}
|
||||
dbResult := dbTx.Create(&dbParentBlock)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to insert parentBlock: ", dbErrors)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertBlockData(dbTx *gorm.DB, block string, dbBlock *models.Block) error {
|
||||
blockData, err := hex.DecodeString(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dbRawBlock := models.RawBlock{
|
||||
BlockID: dbBlock.ID,
|
||||
BlockData: blockData,
|
||||
}
|
||||
dbResult := dbTx.Create(&dbRawBlock)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to insert rawBlock: ", dbErrors)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertSubnetwork(dbTx *gorm.DB, transaction *btcjson.TxRawResult, client *jsonrpc.Client) (*models.Subnetwork, error) {
|
||||
var dbSubnetwork models.Subnetwork
|
||||
dbResult := dbTx.
|
||||
Where(&models.Subnetwork{SubnetworkID: transaction.Subnetwork}).
|
||||
First(&dbSubnetwork)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return nil, utils.NewErrorFromDBErrors("failed to find subnetwork: ", dbErrors)
|
||||
}
|
||||
if utils.IsDBRecordNotFoundError(dbErrors) {
|
||||
subnetwork, err := client.GetSubnetwork(transaction.Subnetwork)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbSubnetwork = models.Subnetwork{
|
||||
SubnetworkID: transaction.Subnetwork,
|
||||
GasLimit: subnetwork.GasLimit,
|
||||
}
|
||||
dbResult := dbTx.Create(&dbSubnetwork)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return nil, utils.NewErrorFromDBErrors("failed to insert subnetwork: ", dbErrors)
|
||||
}
|
||||
}
|
||||
return &dbSubnetwork, nil
|
||||
}
|
||||
|
||||
func insertTransaction(dbTx *gorm.DB, transaction *btcjson.TxRawResult, dbSubnetwork *models.Subnetwork) (*models.Transaction, error) {
|
||||
var dbTransaction models.Transaction
|
||||
dbResult := dbTx.
|
||||
Where(&models.Transaction{TransactionID: transaction.TxID}).
|
||||
First(&dbTransaction)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return nil, utils.NewErrorFromDBErrors("failed to find transaction: ", dbErrors)
|
||||
}
|
||||
if utils.IsDBRecordNotFoundError(dbErrors) {
|
||||
payload, err := hex.DecodeString(transaction.Payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbTransaction = models.Transaction{
|
||||
TransactionHash: transaction.Hash,
|
||||
TransactionID: transaction.TxID,
|
||||
LockTime: transaction.LockTime,
|
||||
SubnetworkID: dbSubnetwork.ID,
|
||||
Gas: transaction.Gas,
|
||||
Mass: transaction.Mass,
|
||||
PayloadHash: transaction.PayloadHash,
|
||||
Payload: payload,
|
||||
}
|
||||
dbResult := dbTx.Create(&dbTransaction)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return nil, utils.NewErrorFromDBErrors("failed to insert transaction: ", dbErrors)
|
||||
}
|
||||
}
|
||||
return &dbTransaction, nil
|
||||
}
|
||||
|
||||
func insertTransactionBlock(dbTx *gorm.DB, dbBlock *models.Block, dbTransaction *models.Transaction, index uint32) error {
|
||||
var dbTransactionBlock models.TransactionBlock
|
||||
dbResult := dbTx.
|
||||
Where(&models.TransactionBlock{TransactionID: dbTransaction.ID, BlockID: dbBlock.ID}).
|
||||
First(&dbTransactionBlock)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to find transactionBlock: ", dbErrors)
|
||||
}
|
||||
if utils.IsDBRecordNotFoundError(dbErrors) {
|
||||
dbTransactionBlock = models.TransactionBlock{
|
||||
TransactionID: dbTransaction.ID,
|
||||
BlockID: dbBlock.ID,
|
||||
Index: index,
|
||||
}
|
||||
dbResult := dbTx.Create(&dbTransactionBlock)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to insert transactionBlock: ", dbErrors)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertTransactionInputs(dbTx *gorm.DB, transaction *btcjson.TxRawResult, dbTransaction *models.Transaction) error {
|
||||
isCoinbase, err := isTransactionCoinbase(transaction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isCoinbase {
|
||||
for _, input := range transaction.Vin {
|
||||
err := insertTransactionInput(dbTx, dbTransaction, &input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isTransactionCoinbase(transaction *btcjson.TxRawResult) (bool, error) {
|
||||
subnetwork, err := subnetworkid.NewFromStr(transaction.Subnetwork)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return subnetwork.IsEqual(subnetworkid.SubnetworkIDCoinbase), nil
|
||||
}
|
||||
|
||||
func insertTransactionInput(dbTx *gorm.DB, dbTransaction *models.Transaction, input *btcjson.Vin) error {
|
||||
var dbPreviousTransactionOutput models.TransactionOutput
|
||||
dbResult := dbTx.
|
||||
Joins("LEFT JOIN `transactions` ON `transactions`.`id` = `transaction_outputs`.`transaction_id`").
|
||||
Where("`transactions`.`transactiond_id` = ? AND `transaction_outputs`.`index` = ?", input.TxID, input.Vout).
|
||||
First(&dbPreviousTransactionOutput)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to find previous transactionOutput: ", dbErrors)
|
||||
}
|
||||
if utils.IsDBRecordNotFoundError(dbErrors) {
|
||||
return fmt.Errorf("missing output transaction output for txID: %s and index: %d", input.TxID, input.Vout)
|
||||
}
|
||||
|
||||
var dbTransactionInputCount int
|
||||
dbResult = dbTx.
|
||||
Model(&models.TransactionInput{}).
|
||||
Where(&models.TransactionInput{TransactionID: dbTransaction.ID, PreviousTransactionOutputID: dbPreviousTransactionOutput.ID}).
|
||||
Count(&dbTransactionInputCount)
|
||||
dbErrors = dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to find transactionInput: ", dbErrors)
|
||||
}
|
||||
if dbTransactionInputCount == 0 {
|
||||
scriptSig, err := hex.DecodeString(input.ScriptSig.Hex)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
dbTransactionInput := models.TransactionInput{
|
||||
TransactionID: dbTransaction.ID,
|
||||
PreviousTransactionOutputID: dbPreviousTransactionOutput.ID,
|
||||
Index: input.Vout,
|
||||
SignatureScript: scriptSig,
|
||||
Sequence: input.Sequence,
|
||||
}
|
||||
dbResult := dbTx.Create(&dbTransactionInput)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to insert transactionInput: ", dbErrors)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertTransactionOutputs(dbTx *gorm.DB, transaction *btcjson.TxRawResult, dbTransaction *models.Transaction) error {
|
||||
for _, output := range transaction.Vout {
|
||||
scriptPubKey, err := hex.DecodeString(output.ScriptPubKey.Hex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dbAddress, err := insertAddress(dbTx, scriptPubKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = insertTransactionOutput(dbTx, dbTransaction, &output, scriptPubKey, dbAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertAddress(dbTx *gorm.DB, scriptPubKey []byte) (*models.Address, error) {
|
||||
_, addr, err := txscript.ExtractScriptPubKeyAddress(scriptPubKey, config.ActiveNetParams())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hexAddress := addr.EncodeAddress()
|
||||
|
||||
var dbAddress models.Address
|
||||
dbResult := dbTx.
|
||||
Where(&models.Address{Address: hexAddress}).
|
||||
First(&dbAddress)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return nil, utils.NewErrorFromDBErrors("failed to find address: ", dbErrors)
|
||||
}
|
||||
if utils.IsDBRecordNotFoundError(dbErrors) {
|
||||
dbAddress = models.Address{
|
||||
Address: hexAddress,
|
||||
}
|
||||
dbResult := dbTx.Create(&dbAddress)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return nil, utils.NewErrorFromDBErrors("failed to insert address: ", dbErrors)
|
||||
}
|
||||
}
|
||||
return &dbAddress, nil
|
||||
}
|
||||
|
||||
func insertTransactionOutput(dbTx *gorm.DB, dbTransaction *models.Transaction,
|
||||
output *btcjson.Vout, scriptPubKey []byte, dbAddress *models.Address) error {
|
||||
var dbTransactionOutputCount int
|
||||
dbResult := dbTx.
|
||||
Model(&models.TransactionOutput{}).
|
||||
Where(&models.TransactionOutput{TransactionID: dbTransaction.ID, Index: output.N}).
|
||||
Count(&dbTransactionOutputCount)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to find transactionOutput: ", dbErrors)
|
||||
}
|
||||
if dbTransactionOutputCount == 0 {
|
||||
dbTransactionOutput := models.TransactionOutput{
|
||||
TransactionID: dbTransaction.ID,
|
||||
Index: output.N,
|
||||
Value: output.Value,
|
||||
IsSpent: false, // This must be false for updateSelectedParentChain to work properly
|
||||
ScriptPubKey: scriptPubKey,
|
||||
AddressID: dbAddress.ID,
|
||||
}
|
||||
dbResult := dbTx.Create(&dbTransactionOutput)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to insert transactionOutput: ", dbErrors)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateSelectedParentChain updates the database to reflect the current selected
|
||||
// parent chain. First it "unaccepts" all removedChainHashes and then it "accepts"
|
||||
// all addChainBlocks.
|
||||
// Note that if this function may take a nil dbTx, in which case it would start
|
||||
// a database transaction by itself and commit it before returning.
|
||||
func updateSelectedParentChain(removedChainHashes []string, addedChainBlocks []btcjson.ChainBlock) error {
|
||||
db, err := database.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dbTx := db.Begin()
|
||||
|
||||
for _, removedHash := range removedChainHashes {
|
||||
err := updateRemovedChainHashes(dbTx, removedHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, addedBlock := range addedChainBlocks {
|
||||
err := updateAddedChainBlocks(dbTx, &addedBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
dbTx.Commit()
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateRemovedChainHashes "unaccepts" the block of the given removedHash.
|
||||
// That is to say, it marks it as not in the selected parent chain in the
|
||||
// following ways:
|
||||
// * All its TransactionInputs.PreviousTransactionOutputs are set IsSpent = false
|
||||
// * All its Transactions are set AcceptingBlockID = nil
|
||||
// * The block is set IsChainBlock = false
|
||||
// This function will return an error if any of the above are in an unexpected state
|
||||
func updateRemovedChainHashes(dbTx *gorm.DB, removedHash string) error {
|
||||
var dbBlock models.Block
|
||||
dbResult := dbTx.
|
||||
Where(&models.Block{BlockHash: removedHash}).
|
||||
First(&dbBlock)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to find block: ", dbErrors)
|
||||
}
|
||||
if utils.IsDBRecordNotFoundError(dbErrors) {
|
||||
return fmt.Errorf("missing block for hash: %s", removedHash)
|
||||
}
|
||||
if !dbBlock.IsChainBlock {
|
||||
return fmt.Errorf("block erroneously marked as not a chain block: %s", removedHash)
|
||||
}
|
||||
|
||||
var dbTransactions []models.Transaction
|
||||
dbResult = dbTx.
|
||||
Where(&models.Transaction{AcceptingBlockID: &dbBlock.ID}).
|
||||
Preload("TransactionInputs.PreviousTransactionOutput").
|
||||
Find(&dbTransactions)
|
||||
dbErrors = dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to find transactions: ", dbErrors)
|
||||
}
|
||||
for _, dbTransaction := range dbTransactions {
|
||||
for _, dbTransactionInput := range dbTransaction.TransactionInputs {
|
||||
dbPreviousTransactionOutput := dbTransactionInput.PreviousTransactionOutput
|
||||
if !dbPreviousTransactionOutput.IsSpent {
|
||||
return fmt.Errorf("cannot de-spend an unspent transaction output: %s index: %d",
|
||||
dbTransaction.TransactionID, dbTransactionInput.Index)
|
||||
}
|
||||
|
||||
dbPreviousTransactionOutput.IsSpent = false
|
||||
dbResult = dbTx.Save(&dbPreviousTransactionOutput)
|
||||
dbErrors = dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to update transactionOutput: ", dbErrors)
|
||||
}
|
||||
}
|
||||
|
||||
dbTransaction.AcceptingBlockID = nil
|
||||
dbResult := dbTx.Save(&dbTransaction)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to update transaction: ", dbErrors)
|
||||
}
|
||||
}
|
||||
|
||||
dbBlock.IsChainBlock = false
|
||||
dbResult = dbTx.Save(&dbBlock)
|
||||
dbErrors = dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to update block: ", dbErrors)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateAddedChainBlocks "accepts" the given addedBlock. That is to say,
|
||||
// it marks it as in the selected parent chain in the following ways:
|
||||
// * All its TransactionInputs.PreviousTransactionOutputs are set IsSpent = true
|
||||
// * All its Transactions are set AcceptingBlockID = addedBlock
|
||||
// * The block is set IsChainBlock = true
|
||||
// This function will return an error if any of the above are in an unexpected state
|
||||
func updateAddedChainBlocks(dbTx *gorm.DB, addedBlock *btcjson.ChainBlock) error {
|
||||
for _, acceptedBlock := range addedBlock.AcceptedBlocks {
|
||||
var dbAccepedBlock models.Block
|
||||
dbResult := dbTx.
|
||||
Where(&models.Block{BlockHash: acceptedBlock.Hash}).
|
||||
First(&dbAccepedBlock)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to find block: ", dbErrors)
|
||||
}
|
||||
if utils.IsDBRecordNotFoundError(dbErrors) {
|
||||
return fmt.Errorf("missing block for hash: %s", acceptedBlock.Hash)
|
||||
}
|
||||
if dbAccepedBlock.IsChainBlock {
|
||||
return fmt.Errorf("block erroneously marked as a chain block: %s", acceptedBlock.Hash)
|
||||
}
|
||||
|
||||
dbWhereTransactionIDsIn := make([]*models.Transaction, len(acceptedBlock.AcceptedTxIDs))
|
||||
for i, acceptedTxID := range acceptedBlock.AcceptedTxIDs {
|
||||
dbWhereTransactionIDsIn[i] = &models.Transaction{TransactionID: acceptedTxID}
|
||||
}
|
||||
var dbAcceptedTransactions []models.Transaction
|
||||
dbResult = dbTx.
|
||||
Where(dbWhereTransactionIDsIn).
|
||||
Preload("TransactionInputs.PreviousTransactionOutput").
|
||||
First(&dbAcceptedTransactions)
|
||||
dbErrors = dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to find transactions: ", dbErrors)
|
||||
}
|
||||
if len(dbAcceptedTransactions) != len(acceptedBlock.AcceptedTxIDs) {
|
||||
return fmt.Errorf("some transaction are missing for block: %s", acceptedBlock.Hash)
|
||||
}
|
||||
|
||||
for _, dbAcceptedTransaction := range dbAcceptedTransactions {
|
||||
for _, dbTransactionInput := range dbAcceptedTransaction.TransactionInputs {
|
||||
dbPreviousTransactionOutput := dbTransactionInput.PreviousTransactionOutput
|
||||
if dbPreviousTransactionOutput.IsSpent {
|
||||
return fmt.Errorf("cannot spend an already spent transaction output: %s index: %d",
|
||||
dbAcceptedTransaction.TransactionID, dbTransactionInput.Index)
|
||||
}
|
||||
|
||||
dbPreviousTransactionOutput.IsSpent = true
|
||||
dbResult = dbTx.Save(&dbPreviousTransactionOutput)
|
||||
dbErrors = dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to update transactionOutput: ", dbErrors)
|
||||
}
|
||||
}
|
||||
|
||||
dbAcceptedTransaction.AcceptingBlockID = &dbAccepedBlock.ID
|
||||
dbResult = dbTx.Save(&dbAcceptedTransaction)
|
||||
dbErrors = dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to update transaction: ", dbErrors)
|
||||
}
|
||||
}
|
||||
|
||||
dbAccepedBlock.IsChainBlock = true
|
||||
dbResult = dbTx.Save(&dbAccepedBlock)
|
||||
dbErrors = dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return utils.NewErrorFromDBErrors("failed to update block: ", dbErrors)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleBlockAddedMsg handles onBlockAdded messages
|
||||
func handleBlockAddedMsg(client *jsonrpc.Client, blockAdded *jsonrpc.BlockAddedMsg) {
|
||||
hash := blockAdded.Header.BlockHash()
|
||||
block, rawBlock, err := fetchBlock(client, hash)
|
||||
if err != nil {
|
||||
log.Warnf("Could not fetch block %s: %s", hash, err)
|
||||
return
|
||||
}
|
||||
err = addBlock(client, block, *rawBlock)
|
||||
if err != nil {
|
||||
log.Warnf("Could not insert block %s: %s", hash, err)
|
||||
return
|
||||
}
|
||||
log.Infof("Added block %s", hash)
|
||||
}
|
||||
|
||||
// canHandleChainChangedMsg checks whether we have all the necessary data
|
||||
// to successfully handle a ChainChangedMsg.
|
||||
func canHandleChainChangedMsg(chainChanged *jsonrpc.ChainChangedMsg) (bool, error) {
|
||||
dbTx, err := database.DB()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Collect all unique referenced block hashes
|
||||
hashes := make(map[string]struct{})
|
||||
for _, removedHash := range chainChanged.RemovedChainBlockHashes {
|
||||
hashes[removedHash.String()] = struct{}{}
|
||||
}
|
||||
for _, addedBlock := range chainChanged.AddedChainBlocks {
|
||||
hashes[addedBlock.Hash.String()] = struct{}{}
|
||||
for _, acceptedBlock := range addedBlock.AcceptedBlocks {
|
||||
hashes[acceptedBlock.Hash.String()] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that all the hashes exist in the database
|
||||
dbWhereBlockHashesIn := make([]*models.Block, len(hashes))
|
||||
i := 0
|
||||
for hash := range hashes {
|
||||
dbWhereBlockHashesIn[i] = &models.Block{BlockHash: hash}
|
||||
i++
|
||||
}
|
||||
var dbBlocksCount int
|
||||
dbResult := dbTx.
|
||||
Where(dbWhereBlockHashesIn).
|
||||
Count(&dbBlocksCount)
|
||||
dbErrors := dbResult.GetErrors()
|
||||
if utils.HasDBError(dbErrors) {
|
||||
return false, utils.NewErrorFromDBErrors("failed to find block count: ", dbErrors)
|
||||
}
|
||||
if len(hashes) != dbBlocksCount {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// handleChainChangedMsg handles onChainChanged messages
|
||||
func handleChainChangedMsg(chainChanged *jsonrpc.ChainChangedMsg) {
|
||||
// Convert the data in chainChanged to something we can feed into
|
||||
// updateSelectedParentChain
|
||||
removedHashes, addedBlocks := convertChainChangedMsg(chainChanged)
|
||||
|
||||
err := updateSelectedParentChain(removedHashes, addedBlocks)
|
||||
if err != nil {
|
||||
log.Warnf("Could not update selected parent chain: %s", err)
|
||||
return
|
||||
}
|
||||
log.Infof("Chain changed: removed &d blocks and added %d block",
|
||||
len(removedHashes), len(addedBlocks))
|
||||
}
|
||||
|
||||
func convertChainChangedMsg(chainChanged *jsonrpc.ChainChangedMsg) (
|
||||
removedHashes []string, addedBlocks []btcjson.ChainBlock) {
|
||||
|
||||
removedHashes = make([]string, len(chainChanged.RemovedChainBlockHashes))
|
||||
for i, hash := range chainChanged.RemovedChainBlockHashes {
|
||||
removedHashes[i] = hash.String()
|
||||
}
|
||||
|
||||
addedBlocks = make([]btcjson.ChainBlock, len(chainChanged.AddedChainBlocks))
|
||||
for i, addedBlock := range chainChanged.AddedChainBlocks {
|
||||
acceptedBlocks := make([]btcjson.AcceptedBlock, len(addedBlock.AcceptedBlocks))
|
||||
for j, acceptedBlock := range addedBlock.AcceptedBlocks {
|
||||
acceptedTxIDs := make([]string, len(acceptedBlock.AcceptedTxIDs))
|
||||
for k, acceptedTxID := range acceptedBlock.AcceptedTxIDs {
|
||||
acceptedTxIDs[k] = acceptedTxID.String()
|
||||
}
|
||||
acceptedBlocks[j] = btcjson.AcceptedBlock{
|
||||
Hash: acceptedBlock.Hash.String(),
|
||||
AcceptedTxIDs: acceptedTxIDs,
|
||||
}
|
||||
}
|
||||
addedBlocks[i] = btcjson.ChainBlock{
|
||||
Hash: addedBlock.Hash.String(),
|
||||
AcceptedBlocks: acceptedBlocks,
|
||||
}
|
||||
}
|
||||
|
||||
return removedHashes, addedBlocks
|
||||
}
|
||||
79
apiserver/utils/context.go
Normal file
79
apiserver/utils/context.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const (
|
||||
contextKeyRequestID contextKey = "REQUEST_ID"
|
||||
)
|
||||
|
||||
// APIServerContext is a context.Context wrapper that
|
||||
// enables custom logs with request ID.
|
||||
type APIServerContext struct {
|
||||
context.Context
|
||||
}
|
||||
|
||||
// ToAPIServerContext takes a context.Context instance
|
||||
// and converts it to *ApiServerContext.
|
||||
func ToAPIServerContext(ctx context.Context) *APIServerContext {
|
||||
if asCtx, ok := ctx.(*APIServerContext); ok {
|
||||
return asCtx
|
||||
}
|
||||
return &APIServerContext{Context: ctx}
|
||||
}
|
||||
|
||||
// SetRequestID associates a request ID for the context.
|
||||
func (ctx *APIServerContext) SetRequestID(requestID uint64) context.Context {
|
||||
context.WithValue(ctx, contextKeyRequestID, requestID)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (ctx *APIServerContext) requestID() uint64 {
|
||||
id := ctx.Value(contextKeyRequestID)
|
||||
uint64ID, _ := id.(uint64)
|
||||
return uint64ID
|
||||
}
|
||||
|
||||
func (ctx *APIServerContext) getLogString(format string, params ...interface{}) string {
|
||||
return fmt.Sprintf("RID %d: ", ctx.requestID()) + fmt.Sprintf(format, params...)
|
||||
}
|
||||
|
||||
// Tracef writes a customized formatted context
|
||||
// related log with log level 'Trace'.
|
||||
func (ctx *APIServerContext) Tracef(format string, params ...interface{}) {
|
||||
log.Trace(ctx.getLogString(format, params...))
|
||||
}
|
||||
|
||||
// Debugf writes a customized formatted context
|
||||
// related log with log level 'Debug'.
|
||||
func (ctx *APIServerContext) Debugf(format string, params ...interface{}) {
|
||||
log.Debug(ctx.getLogString(format, params...))
|
||||
}
|
||||
|
||||
// Infof writes a customized formatted context
|
||||
// related log with log level 'Info'.
|
||||
func (ctx *APIServerContext) Infof(format string, params ...interface{}) {
|
||||
log.Info(ctx.getLogString(format, params...))
|
||||
}
|
||||
|
||||
// Warnf writes a customized formatted context
|
||||
// related log with log level 'Warn'.
|
||||
func (ctx *APIServerContext) Warnf(format string, params ...interface{}) {
|
||||
log.Warn(ctx.getLogString(format, params...))
|
||||
}
|
||||
|
||||
// Errorf writes a customized formatted context
|
||||
// related log with log level 'Error'.
|
||||
func (ctx *APIServerContext) Errorf(format string, params ...interface{}) {
|
||||
log.Error(ctx.getLogString(format, params...))
|
||||
}
|
||||
|
||||
// Criticalf writes a customized formatted context
|
||||
// related log with log level 'Critical'.
|
||||
func (ctx *APIServerContext) Criticalf(format string, params ...interface{}) {
|
||||
log.Criticalf(ctx.getLogString(format, params...))
|
||||
}
|
||||
74
apiserver/utils/error.go
Normal file
74
apiserver/utils/error.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jinzhu/gorm"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// HandlerError is an error returned from
|
||||
// a rest route handler or a middleware.
|
||||
type HandlerError struct {
|
||||
Code int
|
||||
Message string
|
||||
ClientMessage string
|
||||
}
|
||||
|
||||
func (hErr *HandlerError) Error() string {
|
||||
return hErr.Message
|
||||
}
|
||||
|
||||
// NewHandlerError returns a HandlerError with the given code and message.
|
||||
func NewHandlerError(code int, message string) *HandlerError {
|
||||
return &HandlerError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
ClientMessage: message,
|
||||
}
|
||||
}
|
||||
|
||||
// NewHandlerErrorWithCustomClientMessage returns a HandlerError with
|
||||
// the given code, message and client error message.
|
||||
func NewHandlerErrorWithCustomClientMessage(code int, message, clientMessage string) *HandlerError {
|
||||
return &HandlerError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
ClientMessage: clientMessage,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInternalServerHandlerError returns a HandlerError with
|
||||
// the given message, and the http.StatusInternalServerError
|
||||
// status text as client message.
|
||||
func NewInternalServerHandlerError(message string) *HandlerError {
|
||||
return NewHandlerErrorWithCustomClientMessage(http.StatusInternalServerError, message, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
// NewErrorFromDBErrors takes a slice of database errors and a prefix, and
|
||||
// returns an error with all of the database errors formatted to one string with
|
||||
// the given prefix
|
||||
func NewErrorFromDBErrors(prefix string, dbErrors []error) error {
|
||||
dbErrorsStrings := make([]string, len(dbErrors))
|
||||
for i, dbErr := range dbErrors {
|
||||
dbErrorsStrings[i] = fmt.Sprintf("\"%s\"", dbErr)
|
||||
}
|
||||
return fmt.Errorf("%s [%s]", prefix, strings.Join(dbErrorsStrings, ","))
|
||||
}
|
||||
|
||||
// NewHandlerErrorFromDBErrors takes a slice of database errors and a prefix, and
|
||||
// returns an HandlerError with error code http.StatusInternalServerError with
|
||||
// all of the database errors formatted to one string with the given prefix
|
||||
func NewHandlerErrorFromDBErrors(prefix string, dbErrors []error) *HandlerError {
|
||||
return NewInternalServerHandlerError(NewErrorFromDBErrors(prefix, dbErrors).Error())
|
||||
}
|
||||
|
||||
// IsDBRecordNotFoundError returns true if the given dbErrors contains only a RecordNotFound error
|
||||
func IsDBRecordNotFoundError(dbErrors []error) bool {
|
||||
return len(dbErrors) == 1 && gorm.IsRecordNotFoundError(dbErrors[0])
|
||||
}
|
||||
|
||||
// HasDBError returns true if the given dbErrors contain any errors that aren't RecordNotFound
|
||||
func HasDBError(dbErrors []error) bool {
|
||||
return !IsDBRecordNotFoundError(dbErrors) && len(dbErrors) > 0
|
||||
}
|
||||
9
apiserver/utils/log.go
Normal file
9
apiserver/utils/log.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package utils
|
||||
|
||||
import "github.com/daglabs/btcd/util/panics"
|
||||
import "github.com/daglabs/btcd/apiserver/logger"
|
||||
|
||||
var (
|
||||
log = logger.BackendLog.Logger("UTIL")
|
||||
spawn = panics.GoroutineWrapperFunc(log, logger.BackendLog)
|
||||
)
|
||||
248
app/app.go
248
app/app.go
@@ -1,248 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag/indexers"
|
||||
"github.com/kaspanet/kaspad/domain/mempool"
|
||||
"github.com/kaspanet/kaspad/domain/mining"
|
||||
"github.com/kaspanet/kaspad/domain/txscript"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/db/dbaccess"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/dnsseed"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/rpc"
|
||||
"github.com/kaspanet/kaspad/infrastructure/os/signal"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
|
||||
// App is a wrapper for all the kaspad services
|
||||
type App struct {
|
||||
cfg *config.Config
|
||||
rpcServer *rpc.Server
|
||||
addressManager *addressmanager.AddressManager
|
||||
protocolManager *protocol.Manager
|
||||
connectionManager *connmanager.ConnectionManager
|
||||
netAdapter *netadapter.NetAdapter
|
||||
|
||||
started, shutdown int32
|
||||
}
|
||||
|
||||
// Start launches all the kaspad services.
|
||||
func (a *App) Start() {
|
||||
// Already started?
|
||||
if atomic.AddInt32(&a.started, 1) != 1 {
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Starting kaspad")
|
||||
|
||||
err := a.protocolManager.Start()
|
||||
if err != nil {
|
||||
panics.Exit(log, fmt.Sprintf("Error starting the p2p protocol: %+v", err))
|
||||
}
|
||||
|
||||
a.maybeSeedFromDNS()
|
||||
|
||||
a.connectionManager.Start()
|
||||
|
||||
if !a.cfg.DisableRPC {
|
||||
a.rpcServer.Start()
|
||||
}
|
||||
}
|
||||
|
||||
// Stop gracefully shuts down all the kaspad services.
|
||||
func (a *App) Stop() {
|
||||
// Make sure this only happens once.
|
||||
if atomic.AddInt32(&a.shutdown, 1) != 1 {
|
||||
log.Infof("Kaspad is already in the process of shutting down")
|
||||
return
|
||||
}
|
||||
|
||||
log.Warnf("Kaspad shutting down")
|
||||
|
||||
a.connectionManager.Stop()
|
||||
|
||||
err := a.protocolManager.Stop()
|
||||
if err != nil {
|
||||
log.Errorf("Error stopping the p2p protocol: %+v", err)
|
||||
}
|
||||
|
||||
// Shutdown the RPC server if it's not disabled.
|
||||
if !a.cfg.DisableRPC {
|
||||
err := a.rpcServer.Stop()
|
||||
if err != nil {
|
||||
log.Errorf("Error stopping rpcServer: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = a.addressManager.Stop()
|
||||
if err != nil {
|
||||
log.Errorf("Error stopping address manager: %s", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// New returns a new App instance configured to listen on addr for the
|
||||
// kaspa network type specified by dagParams. Use start to begin accepting
|
||||
// connections from peers.
|
||||
func New(cfg *config.Config, databaseContext *dbaccess.DatabaseContext, interrupt <-chan struct{}) (*App, error) {
|
||||
indexManager, acceptanceIndex := setupIndexes(cfg)
|
||||
|
||||
sigCache := txscript.NewSigCache(cfg.SigCacheMaxSize)
|
||||
|
||||
// Create a new block DAG instance with the appropriate configuration.
|
||||
dag, err := setupDAG(cfg, databaseContext, interrupt, sigCache, indexManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txMempool := setupMempool(cfg, dag, sigCache)
|
||||
|
||||
netAdapter, err := netadapter.NewNetAdapter(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addressManager, err := addressmanager.New(cfg, databaseContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
connectionManager, err := connmanager.New(cfg, netAdapter, addressManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
protocolManager, err := protocol.NewManager(cfg, dag, netAdapter, addressManager, txMempool, connectionManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rpcServer, err := setupRPC(
|
||||
cfg, dag, txMempool, sigCache, acceptanceIndex, connectionManager, addressManager, protocolManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &App{
|
||||
cfg: cfg,
|
||||
rpcServer: rpcServer,
|
||||
protocolManager: protocolManager,
|
||||
connectionManager: connectionManager,
|
||||
netAdapter: netAdapter,
|
||||
addressManager: addressManager,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *App) maybeSeedFromDNS() {
|
||||
if !a.cfg.DisableDNSSeed {
|
||||
dnsseed.SeedFromDNS(a.cfg.NetParams(), a.cfg.DNSSeed, appmessage.SFNodeNetwork, false, nil,
|
||||
a.cfg.Lookup, func(addresses []*appmessage.NetAddress) {
|
||||
// Kaspad uses a lookup of the dns seeder here. Since seeder returns
|
||||
// IPs of nodes and not its own IP, we can not know real IP of
|
||||
// source. So we'll take first returned address as source.
|
||||
a.addressManager.AddAddresses(addresses, addresses[0], nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
func setupDAG(cfg *config.Config, databaseContext *dbaccess.DatabaseContext, interrupt <-chan struct{},
|
||||
sigCache *txscript.SigCache, indexManager blockdag.IndexManager) (*blockdag.BlockDAG, error) {
|
||||
|
||||
dag, err := blockdag.New(&blockdag.Config{
|
||||
Interrupt: interrupt,
|
||||
DatabaseContext: databaseContext,
|
||||
DAGParams: cfg.NetParams(),
|
||||
TimeSource: blockdag.NewTimeSource(),
|
||||
SigCache: sigCache,
|
||||
IndexManager: indexManager,
|
||||
SubnetworkID: cfg.SubnetworkID,
|
||||
})
|
||||
return dag, err
|
||||
}
|
||||
|
||||
func setupIndexes(cfg *config.Config) (blockdag.IndexManager, *indexers.AcceptanceIndex) {
|
||||
// Create indexes if needed.
|
||||
var indexes []indexers.Indexer
|
||||
var acceptanceIndex *indexers.AcceptanceIndex
|
||||
if cfg.AcceptanceIndex {
|
||||
log.Info("acceptance index is enabled")
|
||||
acceptanceIndex = indexers.NewAcceptanceIndex()
|
||||
indexes = append(indexes, acceptanceIndex)
|
||||
}
|
||||
|
||||
// Create an index manager if any of the optional indexes are enabled.
|
||||
if len(indexes) < 0 {
|
||||
return nil, nil
|
||||
}
|
||||
indexManager := indexers.NewManager(indexes)
|
||||
return indexManager, acceptanceIndex
|
||||
}
|
||||
|
||||
func setupMempool(cfg *config.Config, dag *blockdag.BlockDAG, sigCache *txscript.SigCache) *mempool.TxPool {
|
||||
mempoolConfig := mempool.Config{
|
||||
Policy: mempool.Policy{
|
||||
AcceptNonStd: cfg.RelayNonStd,
|
||||
MaxOrphanTxs: cfg.MaxOrphanTxs,
|
||||
MaxOrphanTxSize: config.DefaultMaxOrphanTxSize,
|
||||
MinRelayTxFee: cfg.MinRelayTxFee,
|
||||
MaxTxVersion: 1,
|
||||
},
|
||||
CalcSequenceLockNoLock: func(tx *util.Tx, utxoSet blockdag.UTXOSet) (*blockdag.SequenceLock, error) {
|
||||
return dag.CalcSequenceLockNoLock(tx, utxoSet)
|
||||
},
|
||||
SigCache: sigCache,
|
||||
DAG: dag,
|
||||
}
|
||||
|
||||
return mempool.New(&mempoolConfig)
|
||||
}
|
||||
|
||||
func setupRPC(cfg *config.Config,
|
||||
dag *blockdag.BlockDAG,
|
||||
txMempool *mempool.TxPool,
|
||||
sigCache *txscript.SigCache,
|
||||
acceptanceIndex *indexers.AcceptanceIndex,
|
||||
connectionManager *connmanager.ConnectionManager,
|
||||
addressManager *addressmanager.AddressManager,
|
||||
protocolManager *protocol.Manager) (*rpc.Server, error) {
|
||||
|
||||
if !cfg.DisableRPC {
|
||||
policy := mining.Policy{
|
||||
BlockMaxMass: cfg.BlockMaxMass,
|
||||
}
|
||||
blockTemplateGenerator := mining.NewBlkTmplGenerator(&policy, txMempool, dag, sigCache)
|
||||
|
||||
rpcServer, err := rpc.NewRPCServer(cfg, dag, txMempool, acceptanceIndex, blockTemplateGenerator,
|
||||
connectionManager, addressManager, protocolManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Signal process shutdown when the RPC server requests it.
|
||||
spawn("setupRPC-handleShutdownRequest", func() {
|
||||
<-rpcServer.RequestedProcessShutdown()
|
||||
signal.ShutdownRequestChannel <- struct{}{}
|
||||
})
|
||||
|
||||
return rpcServer, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// P2PNodeID returns the network ID associated with this App
|
||||
func (a *App) P2PNodeID() *id.ID {
|
||||
return a.netAdapter.ID()
|
||||
}
|
||||
|
||||
// AddressManager returns the AddressManager associated with this App
|
||||
func (a *App) AddressManager() *addressmanager.AddressManager {
|
||||
return a.addressManager
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
wire
|
||||
====
|
||||
|
||||
[](https://choosealicense.com/licenses/isc/)
|
||||
[](http://godoc.org/github.com/kaspanet/kaspad/wire)
|
||||
=======
|
||||
|
||||
Package wire implements the kaspa wire protocol.
|
||||
|
||||
## Kaspa Message Overview
|
||||
|
||||
The kaspa protocol consists of exchanging messages between peers. Each message
|
||||
is preceded by a header which identifies information about it such as which
|
||||
kaspa network it is a part of, its type, how big it is, and a checksum to
|
||||
verify validity. All encoding and decoding of message headers is handled by this
|
||||
package.
|
||||
|
||||
To accomplish this, there is a generic interface for kaspa messages named
|
||||
`Message` which allows messages of any type to be read, written, or passed
|
||||
around through channels, functions, etc. In addition, concrete implementations
|
||||
of most all kaspa messages are provided. All of the details of marshalling and
|
||||
unmarshalling to and from the wire using kaspa encoding are handled so the
|
||||
caller doesn't have to concern themselves with the specifics.
|
||||
|
||||
## Reading Messages Example
|
||||
|
||||
In order to unmarshal kaspa messages from the wire, use the `ReadMessage`
|
||||
function. It accepts any `io.Reader`, but typically this will be a `net.Conn`
|
||||
to a remote node running a kaspa peer. Example syntax is:
|
||||
|
||||
```Go
|
||||
// Use the most recent protocol version supported by the package and the
|
||||
// main kaspa network.
|
||||
pver := wire.ProtocolVersion
|
||||
kaspanet := wire.Mainnet
|
||||
|
||||
// Reads and validates the next kaspa message from conn using the
|
||||
// protocol version pver and the kaspa network kaspanet. The returns
|
||||
// are a appmessage.Message, a []byte which contains the unmarshalled
|
||||
// raw payload, and a possible error.
|
||||
msg, rawPayload, err := wire.ReadMessage(conn, pver, kaspanet)
|
||||
if err != nil {
|
||||
// Log and handle the error
|
||||
}
|
||||
```
|
||||
|
||||
See the package documentation for details on determining the message type.
|
||||
|
||||
## Writing Messages Example
|
||||
|
||||
In order to marshal kaspa messages to the wire, use the `WriteMessage`
|
||||
function. It accepts any `io.Writer`, but typically this will be a `net.Conn`
|
||||
to a remote node running a kaspa peer. Example syntax to request addresses
|
||||
from a remote peer is:
|
||||
|
||||
```Go
|
||||
// Use the most recent protocol version supported by the package and the
|
||||
// main bitcoin network.
|
||||
pver := wire.ProtocolVersion
|
||||
kaspanet := wire.Mainnet
|
||||
|
||||
// Create a new getaddr kaspa message.
|
||||
msg := wire.NewMsgGetAddr()
|
||||
|
||||
// Writes a kaspa message msg to conn using the protocol version
|
||||
// pver, and the kaspa network kaspanet. The return is a possible
|
||||
// error.
|
||||
err := wire.WriteMessage(conn, msg, pver, kaspanet)
|
||||
if err != nil {
|
||||
// Log and handle the error
|
||||
}
|
||||
```
|
||||
@@ -1,24 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
import "time"
|
||||
|
||||
type baseMessage struct {
|
||||
messageNumber uint64
|
||||
receivedAt time.Time
|
||||
}
|
||||
|
||||
func (b *baseMessage) MessageNumber() uint64 {
|
||||
return b.messageNumber
|
||||
}
|
||||
|
||||
func (b *baseMessage) SetMessageNumber(messageNumber uint64) {
|
||||
b.messageNumber = messageNumber
|
||||
}
|
||||
|
||||
func (b *baseMessage) ReceivedAt() time.Time {
|
||||
return b.receivedAt
|
||||
}
|
||||
|
||||
func (b *baseMessage) SetReceivedAt(receivedAt time.Time) {
|
||||
b.receivedAt = receivedAt
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
Package appmessage implements the kaspa appmessage protocol.
|
||||
|
||||
At a high level, this package provides support for marshalling and unmarshalling
|
||||
supported kaspa messages to and from the appmessage. This package does not deal
|
||||
with the specifics of message handling such as what to do when a message is
|
||||
received. This provides the caller with a high level of flexibility.
|
||||
|
||||
Kaspa Message Overview
|
||||
|
||||
The kaspa protocol consists of exchanging messages between peers. Each
|
||||
message is preceded by a header which identifies information about it such as
|
||||
which kaspa network it is a part of, its type, how big it is, and a checksum
|
||||
to verify validity. All encoding and decoding of message headers is handled by
|
||||
this package.
|
||||
|
||||
To accomplish this, there is a generic interface for kaspa messages named
|
||||
Message which allows messages of any type to be read, written, or passed around
|
||||
through channels, functions, etc. In addition, concrete implementations of most
|
||||
of the currently supported kaspa messages are provided. For these supported
|
||||
messages, all of the details of marshalling and unmarshalling to and from the
|
||||
appmessage using kaspa encoding are handled so the caller doesn't have to concern
|
||||
themselves with the specifics.
|
||||
|
||||
Message Interaction
|
||||
|
||||
The following provides a quick summary of how the kaspa messages are intended
|
||||
to interact with one another. As stated above, these interactions are not
|
||||
directly handled by this package.
|
||||
|
||||
The initial handshake consists of two peers sending each other a version message
|
||||
(MsgVersion) followed by responding with a verack message (MsgVerAck). Both
|
||||
peers use the information in the version message (MsgVersion) to negotiate
|
||||
things such as protocol version and supported services with each other. Once
|
||||
the initial handshake is complete, the following chart indicates message
|
||||
interactions in no particular order.
|
||||
|
||||
Peer A Sends Peer B Responds
|
||||
----------------------------------------------------------------------------
|
||||
getaddr message (MsgRequestAddresses) addr message (MsgAddresses)
|
||||
getblockinvs message (MsgGetBlockInvs) inv message (MsgInv)
|
||||
inv message (MsgInv) getdata message (MsgGetData)
|
||||
getdata message (MsgGetData) block message (MsgBlock) -or-
|
||||
tx message (MsgTx) -or-
|
||||
notfound message (MsgNotFound)
|
||||
ping message (MsgPing) pong message (MsgPong)
|
||||
|
||||
Common Parameters
|
||||
|
||||
There are several common parameters that arise when using this package to read
|
||||
and write kaspa messages. The following sections provide a quick overview of
|
||||
these parameters so the next sections can build on them.
|
||||
|
||||
Protocol Version
|
||||
|
||||
The protocol version should be negotiated with the remote peer at a higher
|
||||
level than this package via the version (MsgVersion) message exchange, however,
|
||||
this package provides the appmessage.ProtocolVersion constant which indicates the
|
||||
latest protocol version this package supports and is typically the value to use
|
||||
for all outbound connections before a potentially lower protocol version is
|
||||
negotiated.
|
||||
|
||||
Kaspa Network
|
||||
|
||||
The kaspa network is a magic number which is used to identify the start of a
|
||||
message and which kaspa network the message applies to. This package provides
|
||||
the following constants:
|
||||
|
||||
appmessage.Mainnet
|
||||
appmessage.Testnet (Test network)
|
||||
appmessage.Simnet (Simulation test network)
|
||||
appmessage.Devnet (Development network)
|
||||
|
||||
Determining Message Type
|
||||
|
||||
As discussed in the kaspa message overview section, this package reads
|
||||
and writes kaspa messages using a generic interface named Message. In
|
||||
order to determine the actual concrete type of the message, use a type
|
||||
switch or type assertion. An example of a type switch follows:
|
||||
|
||||
// Assumes msg is already a valid concrete message such as one created
|
||||
// via NewMsgVersion or read via ReadMessage.
|
||||
switch msg := msg.(type) {
|
||||
case *appmessage.MsgVersion:
|
||||
// The message is a pointer to a MsgVersion struct.
|
||||
fmt.Printf("Protocol version: %d", msg.ProtocolVersion)
|
||||
case *appmessage.MsgBlock:
|
||||
// The message is a pointer to a MsgBlock struct.
|
||||
fmt.Printf("Number of tx in block: %d", msg.Header.TxnCount)
|
||||
}
|
||||
|
||||
Reading Messages
|
||||
|
||||
In order to unmarshall kaspa messages from the appmessage, use the ReadMessage
|
||||
function. It accepts any io.Reader, but typically this will be a net.Conn to
|
||||
a remote node running a kaspa peer. Example syntax is:
|
||||
|
||||
// Reads and validates the next kaspa message from conn using the
|
||||
// protocol version pver and the kaspa network kaspaNet. The returns
|
||||
// are a appmessage.Message, a []byte which contains the unmarshalled
|
||||
// raw payload, and a possible error.
|
||||
msg, rawPayload, err := appmessage.ReadMessage(conn, pver, kaspaNet)
|
||||
if err != nil {
|
||||
// Log and handle the error
|
||||
}
|
||||
|
||||
Writing Messages
|
||||
|
||||
In order to marshall kaspa messages to the appmessage, use the WriteMessage
|
||||
function. It accepts any io.Writer, but typically this will be a net.Conn to
|
||||
a remote node running a kaspa peer. Example syntax to request addresses
|
||||
from a remote peer is:
|
||||
|
||||
// Create a new getaddr kaspa message.
|
||||
msg := appmessage.NewMsgRequestAddresses()
|
||||
|
||||
// Writes a kaspa message msg to conn using the protocol version
|
||||
// pver, and the kaspa network kaspaNet. The return is a possible
|
||||
// error.
|
||||
err := appmessage.WriteMessage(conn, msg, pver, kaspaNet)
|
||||
if err != nil {
|
||||
// Log and handle the error
|
||||
}
|
||||
|
||||
Errors
|
||||
|
||||
Errors returned by this package are either the raw errors provided by underlying
|
||||
calls to read/write from streams such as io.EOF, io.ErrUnexpectedEOF, and
|
||||
io.ErrShortWrite, or of type appmessage.MessageError. This allows the caller to
|
||||
differentiate between general IO errors and malformed messages through type
|
||||
assertions.
|
||||
*/
|
||||
package appmessage
|
||||
@@ -1,89 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MaxMessagePayload is the maximum bytes a message can be regardless of other
|
||||
// individual limits imposed by messages themselves.
|
||||
const MaxMessagePayload = (1024 * 1024 * 32) // 32MB
|
||||
|
||||
// MessageCommand is a number in the header of a message that represents its type.
|
||||
type MessageCommand uint32
|
||||
|
||||
func (cmd MessageCommand) String() string {
|
||||
cmdString, ok := MessageCommandToString[cmd]
|
||||
if !ok {
|
||||
cmdString = "unknown command"
|
||||
}
|
||||
return fmt.Sprintf("%s [code %d]", cmdString, uint8(cmd))
|
||||
}
|
||||
|
||||
// Commands used in kaspa message headers which describe the type of message.
|
||||
const (
|
||||
CmdVersion MessageCommand = iota
|
||||
CmdVerAck
|
||||
CmdRequestAddresses
|
||||
CmdAddresses
|
||||
CmdRequestIBDBlocks
|
||||
CmdBlock
|
||||
CmdTx
|
||||
CmdPing
|
||||
CmdPong
|
||||
CmdRequestBlockLocator
|
||||
CmdBlockLocator
|
||||
CmdSelectedTip
|
||||
CmdRequestSelectedTip
|
||||
CmdInvRelayBlock
|
||||
CmdRequestRelayBlocks
|
||||
CmdInvTransaction
|
||||
CmdRequestTransactions
|
||||
CmdIBDBlock
|
||||
CmdRequestNextIBDBlocks
|
||||
CmdDoneIBDBlocks
|
||||
CmdTransactionNotFound
|
||||
CmdReject
|
||||
)
|
||||
|
||||
// MessageCommandToString maps all MessageCommands to their string representation
|
||||
var MessageCommandToString = map[MessageCommand]string{
|
||||
CmdVersion: "Version",
|
||||
CmdVerAck: "VerAck",
|
||||
CmdRequestAddresses: "RequestAddresses",
|
||||
CmdAddresses: "Addresses",
|
||||
CmdRequestIBDBlocks: "RequestBlocks",
|
||||
CmdBlock: "Block",
|
||||
CmdTx: "Tx",
|
||||
CmdPing: "Ping",
|
||||
CmdPong: "Pong",
|
||||
CmdRequestBlockLocator: "RequestBlockLocator",
|
||||
CmdBlockLocator: "BlockLocator",
|
||||
CmdSelectedTip: "SelectedTip",
|
||||
CmdRequestSelectedTip: "RequestSelectedTip",
|
||||
CmdInvRelayBlock: "InvRelayBlock",
|
||||
CmdRequestRelayBlocks: "RequestRelayBlocks",
|
||||
CmdInvTransaction: "InvTransaction",
|
||||
CmdRequestTransactions: "RequestTransactions",
|
||||
CmdIBDBlock: "IBDBlock",
|
||||
CmdRequestNextIBDBlocks: "RequestNextIBDBlocks",
|
||||
CmdDoneIBDBlocks: "DoneIBDBlocks",
|
||||
CmdTransactionNotFound: "TransactionNotFound",
|
||||
CmdReject: "Reject",
|
||||
}
|
||||
|
||||
// Message is an interface that describes a kaspa message. A type that
|
||||
// implements Message has complete control over the representation of its data
|
||||
// and may therefore contain additional or fewer fields than those which
|
||||
// are used directly in the protocol encoded message.
|
||||
type Message interface {
|
||||
Command() MessageCommand
|
||||
MessageNumber() uint64
|
||||
SetMessageNumber(index uint64)
|
||||
ReceivedAt() time.Time
|
||||
SetReceivedAt(receivedAt time.Time)
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
)
|
||||
|
||||
// MaxAddressesPerMsg is the maximum number of addresses that can be in a single
|
||||
// kaspa Addresses message (MsgAddresses).
|
||||
const MaxAddressesPerMsg = 1000
|
||||
|
||||
// MsgAddresses implements the Message interface and represents a kaspa
|
||||
// Addresses message. It is used to provide a list of known active peers on the
|
||||
// network. An active peer is considered one that has transmitted a message
|
||||
// within the last 3 hours. Nodes which have not transmitted in that time
|
||||
// frame should be forgotten. Each message is limited to a maximum number of
|
||||
// addresses, which is currently 1000. As a result, multiple messages must
|
||||
// be used to relay the full list.
|
||||
//
|
||||
// Use the AddAddress function to build up the list of known addresses when
|
||||
// sending an Addresses message to another peer.
|
||||
type MsgAddresses struct {
|
||||
baseMessage
|
||||
IncludeAllSubnetworks bool
|
||||
SubnetworkID *subnetworkid.SubnetworkID
|
||||
AddrList []*NetAddress
|
||||
}
|
||||
|
||||
// AddAddress adds a known active peer to the message.
|
||||
func (msg *MsgAddresses) AddAddress(na *NetAddress) error {
|
||||
if len(msg.AddrList)+1 > MaxAddressesPerMsg {
|
||||
str := fmt.Sprintf("too many addresses in message [max %d]",
|
||||
MaxAddressesPerMsg)
|
||||
return messageError("MsgAddresses.AddAddress", str)
|
||||
}
|
||||
|
||||
msg.AddrList = append(msg.AddrList, na)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddAddresses adds multiple known active peers to the message.
|
||||
func (msg *MsgAddresses) AddAddresses(netAddrs ...*NetAddress) error {
|
||||
for _, na := range netAddrs {
|
||||
err := msg.AddAddress(na)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearAddresses removes all addresses from the message.
|
||||
func (msg *MsgAddresses) ClearAddresses() {
|
||||
msg.AddrList = []*NetAddress{}
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgAddresses) Command() MessageCommand {
|
||||
return CmdAddresses
|
||||
}
|
||||
|
||||
// NewMsgAddresses returns a new kaspa Addresses message that conforms to the
|
||||
// Message interface. See MsgAddresses for details.
|
||||
func NewMsgAddresses(includeAllSubnetworks bool, subnetworkID *subnetworkid.SubnetworkID) *MsgAddresses {
|
||||
return &MsgAddresses{
|
||||
IncludeAllSubnetworks: includeAllSubnetworks,
|
||||
SubnetworkID: subnetworkID,
|
||||
AddrList: make([]*NetAddress, 0, MaxAddressesPerMsg),
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
// TestAddresses tests the MsgAddresses API.
|
||||
func TestAddresses(t *testing.T) {
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(3)
|
||||
msg := NewMsgAddresses(false, nil)
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgAddresses: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
}
|
||||
|
||||
// Ensure NetAddresses are added properly.
|
||||
tcpAddr := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 16111}
|
||||
na := NewNetAddress(tcpAddr, SFNodeNetwork)
|
||||
err := msg.AddAddress(na)
|
||||
if err != nil {
|
||||
t.Errorf("AddAddress: %v", err)
|
||||
}
|
||||
if msg.AddrList[0] != na {
|
||||
t.Errorf("AddAddress: wrong address added - got %v, want %v",
|
||||
spew.Sprint(msg.AddrList[0]), spew.Sprint(na))
|
||||
}
|
||||
|
||||
// Ensure the address list is cleared properly.
|
||||
msg.ClearAddresses()
|
||||
if len(msg.AddrList) != 0 {
|
||||
t.Errorf("ClearAddresses: address list is not empty - "+
|
||||
"got %v [%v], want %v", len(msg.AddrList),
|
||||
spew.Sprint(msg.AddrList[0]), 0)
|
||||
}
|
||||
|
||||
// Ensure adding more than the max allowed addresses per message returns
|
||||
// error.
|
||||
for i := 0; i < MaxAddressesPerMsg+1; i++ {
|
||||
err = msg.AddAddress(na)
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("AddAddress: expected error on too many addresses " +
|
||||
"not received")
|
||||
}
|
||||
err = msg.AddAddresses(na)
|
||||
if err == nil {
|
||||
t.Errorf("AddAddresses: expected error on too many addresses " +
|
||||
"not received")
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MaxBlockLocatorsPerMsg is the maximum number of block locator hashes allowed
|
||||
// per message.
|
||||
const MaxBlockLocatorsPerMsg = 500
|
||||
|
||||
// MsgBlockLocator implements the Message interface and represents a kaspa
|
||||
// locator message. It is used to find the blockLocator of a peer that is
|
||||
// syncing with you.
|
||||
type MsgBlockLocator struct {
|
||||
baseMessage
|
||||
BlockLocatorHashes []*daghash.Hash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgBlockLocator) Command() MessageCommand {
|
||||
return CmdBlockLocator
|
||||
}
|
||||
|
||||
// NewMsgBlockLocator returns a new kaspa locator message that conforms to
|
||||
// the Message interface. See MsgBlockLocator for details.
|
||||
func NewMsgBlockLocator(locatorHashes []*daghash.Hash) *MsgBlockLocator {
|
||||
return &MsgBlockLocator{
|
||||
BlockLocatorHashes: locatorHashes,
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// TestBlockLocator tests the MsgBlockLocator API.
|
||||
func TestBlockLocator(t *testing.T) {
|
||||
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||
locatorHash, err := daghash.NewHashFromStr(hashStr)
|
||||
if err != nil {
|
||||
t.Errorf("NewHashFromStr: %v", err)
|
||||
}
|
||||
|
||||
msg := NewMsgBlockLocator([]*daghash.Hash{locatorHash})
|
||||
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(10)
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgBlockLocator: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
}
|
||||
|
||||
// Ensure block locator hashes are added properly.
|
||||
if msg.BlockLocatorHashes[0] != locatorHash {
|
||||
t.Errorf("AddBlockLocatorHash: wrong block locator added - "+
|
||||
"got %v, want %v",
|
||||
spew.Sprint(msg.BlockLocatorHashes[0]),
|
||||
spew.Sprint(locatorHash))
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
// MsgDoneIBDBlocks implements the Message interface and represents a kaspa
|
||||
// DoneIBDBlocks message. It is used to notify the IBD syncing peer that the
|
||||
// syncer sent all the requested blocks.
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgDoneIBDBlocks struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgDoneIBDBlocks) Command() MessageCommand {
|
||||
return CmdDoneIBDBlocks
|
||||
}
|
||||
|
||||
// NewMsgDoneIBDBlocks returns a new kaspa DoneIBDBlocks message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgDoneIBDBlocks() *MsgDoneIBDBlocks {
|
||||
return &MsgDoneIBDBlocks{}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
// MsgIBDBlock implements the Message interface and represents a kaspa
|
||||
// ibdblock message. It is used to deliver block and transaction information in
|
||||
// response to a RequestIBDBlocks message (MsgRequestIBDBlocks).
|
||||
type MsgIBDBlock struct {
|
||||
baseMessage
|
||||
*MsgBlock
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgIBDBlock) Command() MessageCommand {
|
||||
return CmdIBDBlock
|
||||
}
|
||||
|
||||
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||
// receiver. This is part of the Message interface implementation.
|
||||
func (msg *MsgIBDBlock) MaxPayloadLength(pver uint32) uint32 {
|
||||
return MaxMessagePayload
|
||||
}
|
||||
|
||||
// NewMsgIBDBlock returns a new kaspa ibdblock message that conforms to the
|
||||
// Message interface. See MsgIBDBlock for details.
|
||||
func NewMsgIBDBlock(msgBlock *MsgBlock) *MsgIBDBlock {
|
||||
return &MsgIBDBlock{MsgBlock: msgBlock}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
// TestIBDBlock tests the MsgIBDBlock API.
|
||||
func TestIBDBlock(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
// Block 1 header.
|
||||
parentHashes := blockOne.Header.ParentHashes
|
||||
hashMerkleRoot := blockOne.Header.HashMerkleRoot
|
||||
acceptedIDMerkleRoot := blockOne.Header.AcceptedIDMerkleRoot
|
||||
utxoCommitment := blockOne.Header.UTXOCommitment
|
||||
bits := blockOne.Header.Bits
|
||||
nonce := blockOne.Header.Nonce
|
||||
bh := NewBlockHeader(1, parentHashes, hashMerkleRoot, acceptedIDMerkleRoot, utxoCommitment, bits, nonce)
|
||||
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(17)
|
||||
msg := NewMsgIBDBlock(NewMsgBlock(bh))
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgIBDBlock: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
}
|
||||
|
||||
// Ensure max payload is expected value for latest protocol version.
|
||||
wantPayload := uint32(1024 * 1024 * 32)
|
||||
maxPayload := msg.MaxPayloadLength(pver)
|
||||
if maxPayload != wantPayload {
|
||||
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||
"protocol version %d - got %v, want %v", pver,
|
||||
maxPayload, wantPayload)
|
||||
}
|
||||
|
||||
// Ensure we get the same block header data back out.
|
||||
if !reflect.DeepEqual(&msg.Header, bh) {
|
||||
t.Errorf("NewMsgIBDBlock: wrong block header - got %v, want %v",
|
||||
spew.Sdump(&msg.Header), spew.Sdump(bh))
|
||||
}
|
||||
|
||||
// Ensure transactions are added properly.
|
||||
tx := blockOne.Transactions[0].Copy()
|
||||
msg.AddTransaction(tx)
|
||||
if !reflect.DeepEqual(msg.Transactions, blockOne.Transactions) {
|
||||
t.Errorf("AddTransaction: wrong transactions - got %v, want %v",
|
||||
spew.Sdump(msg.Transactions),
|
||||
spew.Sdump(blockOne.Transactions))
|
||||
}
|
||||
|
||||
// Ensure transactions are properly cleared.
|
||||
msg.ClearTransactions()
|
||||
if len(msg.Transactions) != 0 {
|
||||
t.Errorf("ClearTransactions: wrong transactions - got %v, want %v",
|
||||
len(msg.Transactions), 0)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIBDBlockEncoding tests the MsgIBDBlock appmessage encode and decode for various numbers
|
||||
// of transaction inputs and outputs and protocol versions.
|
||||
func TestIBDBlockEncoding(t *testing.T) {
|
||||
tests := []struct {
|
||||
in *MsgIBDBlock // Message to encode
|
||||
out *MsgIBDBlock // Expected decoded message
|
||||
buf []byte // Encoded value
|
||||
txLocs []TxLoc // Expected transaction locations
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
}{
|
||||
// Latest protocol version.
|
||||
{
|
||||
&MsgIBDBlock{MsgBlock: &blockOne},
|
||||
&MsgIBDBlock{MsgBlock: &blockOne},
|
||||
blockOneBytes,
|
||||
blockOneTxLocs,
|
||||
ProtocolVersion,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode the message to appmessage format.
|
||||
var buf bytes.Buffer
|
||||
err := test.in.KaspaEncode(&buf, test.pver)
|
||||
if err != nil {
|
||||
t.Errorf("KaspaEncode #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("KaspaEncode #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode the message from appmessage format.
|
||||
var msg MsgIBDBlock
|
||||
msg.MsgBlock = new(MsgBlock)
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
err = msg.KaspaDecode(rbuf, test.pver)
|
||||
if err != nil {
|
||||
t.Errorf("KaspaDecode #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&msg, test.out) {
|
||||
t.Errorf("KaspaDecode #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&msg), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MsgInvRelayBlock implements the Message interface and represents a kaspa
|
||||
// block inventory message. It is used to notify the network about new block
|
||||
// by sending their hash, and let the receiving node decide if it needs it.
|
||||
type MsgInvRelayBlock struct {
|
||||
baseMessage
|
||||
Hash *daghash.Hash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgInvRelayBlock) Command() MessageCommand {
|
||||
return CmdInvRelayBlock
|
||||
}
|
||||
|
||||
// NewMsgInvBlock returns a new kaspa invrelblk message that conforms to
|
||||
// the Message interface. See MsgInvRelayBlock for details.
|
||||
func NewMsgInvBlock(hash *daghash.Hash) *MsgInvRelayBlock {
|
||||
return &MsgInvRelayBlock{
|
||||
Hash: hash,
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MaxInvPerTxInvMsg is the maximum number of hashes that can
|
||||
// be in a single CmdInvTransaction message.
|
||||
const MaxInvPerTxInvMsg = MaxInvPerMsg
|
||||
|
||||
// MsgInvTransaction implements the Message interface and represents a kaspa
|
||||
// TxInv message. It is used to notify the network about new transactions
|
||||
// by sending their ID, and let the receiving node decide if it needs it.
|
||||
type MsgInvTransaction struct {
|
||||
baseMessage
|
||||
TxIDs []*daghash.TxID
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgInvTransaction) Command() MessageCommand {
|
||||
return CmdInvTransaction
|
||||
}
|
||||
|
||||
// NewMsgInvTransaction returns a new kaspa TxInv message that conforms to
|
||||
// the Message interface. See MsgInvTransaction for details.
|
||||
func NewMsgInvTransaction(ids []*daghash.TxID) *MsgInvTransaction {
|
||||
return &MsgInvTransaction{
|
||||
TxIDs: ids,
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
// MsgPing implements the Message interface and represents a kaspa ping
|
||||
// message.
|
||||
//
|
||||
// For versions BIP0031Version and earlier, it is used primarily to confirm
|
||||
// that a connection is still valid. A transmission error is typically
|
||||
// interpreted as a closed connection and that the peer should be removed.
|
||||
// For versions AFTER BIP0031Version it contains an identifier which can be
|
||||
// returned in the pong message to determine network timing.
|
||||
//
|
||||
// The payload for this message just consists of a nonce used for identifying
|
||||
// it later.
|
||||
type MsgPing struct {
|
||||
baseMessage
|
||||
// Unique value associated with message that is used to identify
|
||||
// specific ping message.
|
||||
Nonce uint64
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgPing) Command() MessageCommand {
|
||||
return CmdPing
|
||||
}
|
||||
|
||||
// NewMsgPing returns a new kaspa ping message that conforms to the Message
|
||||
// interface. See MsgPing for details.
|
||||
func NewMsgPing(nonce uint64) *MsgPing {
|
||||
return &MsgPing{
|
||||
Nonce: nonce,
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/random"
|
||||
)
|
||||
|
||||
// TestPing tests the MsgPing API against the latest protocol version.
|
||||
func TestPing(t *testing.T) {
|
||||
// Ensure we get the same nonce back out.
|
||||
nonce, err := random.Uint64()
|
||||
if err != nil {
|
||||
t.Errorf("random.Uint64: Error generating nonce: %v", err)
|
||||
}
|
||||
msg := NewMsgPing(nonce)
|
||||
if msg.Nonce != nonce {
|
||||
t.Errorf("NewMsgPing: wrong nonce - got %v, want %v",
|
||||
msg.Nonce, nonce)
|
||||
}
|
||||
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(7)
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgPing: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
// MsgPong implements the Message interface and represents a kaspa pong
|
||||
// message which is used primarily to confirm that a connection is still valid
|
||||
// in response to a kaspa ping message (MsgPing).
|
||||
//
|
||||
// This message was not added until protocol versions AFTER BIP0031Version.
|
||||
type MsgPong struct {
|
||||
baseMessage
|
||||
// Unique value associated with message that is used to identify
|
||||
// specific ping message.
|
||||
Nonce uint64
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgPong) Command() MessageCommand {
|
||||
return CmdPong
|
||||
}
|
||||
|
||||
// NewMsgPong returns a new kaspa pong message that conforms to the Message
|
||||
// interface. See MsgPong for details.
|
||||
func NewMsgPong(nonce uint64) *MsgPong {
|
||||
return &MsgPong{
|
||||
Nonce: nonce,
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/random"
|
||||
)
|
||||
|
||||
// TestPongLatest tests the MsgPong API against the latest protocol version.
|
||||
func TestPongLatest(t *testing.T) {
|
||||
nonce, err := random.Uint64()
|
||||
if err != nil {
|
||||
t.Errorf("random.Uint64: error generating nonce: %v", err)
|
||||
}
|
||||
msg := NewMsgPong(nonce)
|
||||
if msg.Nonce != nonce {
|
||||
t.Errorf("NewMsgPong: wrong nonce - got %v, want %v",
|
||||
msg.Nonce, nonce)
|
||||
}
|
||||
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(8)
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgPong: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
// MsgReject implements the Message interface and represents a kaspa
|
||||
// Reject message. It is used to notify peers why they are banned.
|
||||
type MsgReject struct {
|
||||
baseMessage
|
||||
Reason string
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgReject) Command() MessageCommand {
|
||||
return CmdReject
|
||||
}
|
||||
|
||||
// NewMsgReject returns a new kaspa Reject message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgReject(reason string) *MsgReject {
|
||||
return &MsgReject{
|
||||
Reason: reason,
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
)
|
||||
|
||||
// MsgRequestAddresses implements the Message interface and represents a kaspa
|
||||
// RequestAddresses message. It is used to request a list of known active peers on the
|
||||
// network from a peer to help identify potential nodes. The list is returned
|
||||
// via one or more addr messages (MsgAddresses).
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgRequestAddresses struct {
|
||||
baseMessage
|
||||
IncludeAllSubnetworks bool
|
||||
SubnetworkID *subnetworkid.SubnetworkID
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgRequestAddresses) Command() MessageCommand {
|
||||
return CmdRequestAddresses
|
||||
}
|
||||
|
||||
// NewMsgRequestAddresses returns a new kaspa RequestAddresses message that conforms to the
|
||||
// Message interface. See MsgRequestAddresses for details.
|
||||
func NewMsgRequestAddresses(includeAllSubnetworks bool, subnetworkID *subnetworkid.SubnetworkID) *MsgRequestAddresses {
|
||||
return &MsgRequestAddresses{
|
||||
IncludeAllSubnetworks: includeAllSubnetworks,
|
||||
SubnetworkID: subnetworkID,
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestRequestAddresses tests the MsgRequestAddresses API.
|
||||
func TestRequestAddresses(t *testing.T) {
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(2)
|
||||
msg := NewMsgRequestAddresses(false, nil)
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgRequestAddresses: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MsgRequestBlockLocator implements the Message interface and represents a kaspa
|
||||
// RequestBlockLocator message. It is used to request a block locator between high
|
||||
// and low hash.
|
||||
// The locator is returned via a locator message (MsgBlockLocator).
|
||||
type MsgRequestBlockLocator struct {
|
||||
baseMessage
|
||||
HighHash *daghash.Hash
|
||||
LowHash *daghash.Hash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgRequestBlockLocator) Command() MessageCommand {
|
||||
return CmdRequestBlockLocator
|
||||
}
|
||||
|
||||
// NewMsgRequestBlockLocator returns a new RequestBlockLocator message that conforms to the
|
||||
// Message interface using the passed parameters and defaults for the remaining
|
||||
// fields.
|
||||
func NewMsgRequestBlockLocator(highHash, lowHash *daghash.Hash) *MsgRequestBlockLocator {
|
||||
return &MsgRequestBlockLocator{
|
||||
HighHash: highHash,
|
||||
LowHash: lowHash,
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// TestRequestBlockLocator tests the MsgRequestBlockLocator API.
|
||||
func TestRequestBlockLocator(t *testing.T) {
|
||||
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||
highHash, err := daghash.NewHashFromStr(hashStr)
|
||||
if err != nil {
|
||||
t.Errorf("NewHashFromStr: %v", err)
|
||||
}
|
||||
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(9)
|
||||
msg := NewMsgRequestBlockLocator(highHash, &daghash.ZeroHash)
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgRequestBlockLocator: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MsgRequestIBDBlocks implements the Message interface and represents a kaspa
|
||||
// RequestIBDBlocks message. It is used to request a list of blocks starting after the
|
||||
// low hash and until the high hash.
|
||||
type MsgRequestIBDBlocks struct {
|
||||
baseMessage
|
||||
LowHash *daghash.Hash
|
||||
HighHash *daghash.Hash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgRequestIBDBlocks) Command() MessageCommand {
|
||||
return CmdRequestIBDBlocks
|
||||
}
|
||||
|
||||
// NewMsgRequstIBDBlocks returns a new kaspa RequestIBDBlocks message that conforms to the
|
||||
// Message interface using the passed parameters and defaults for the remaining
|
||||
// fields.
|
||||
func NewMsgRequstIBDBlocks(lowHash, highHash *daghash.Hash) *MsgRequestIBDBlocks {
|
||||
return &MsgRequestIBDBlocks{
|
||||
LowHash: lowHash,
|
||||
HighHash: highHash,
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// TestRequstIBDBlocks tests the MsgRequestIBDBlocks API.
|
||||
func TestRequstIBDBlocks(t *testing.T) {
|
||||
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||
lowHash, err := daghash.NewHashFromStr(hashStr)
|
||||
if err != nil {
|
||||
t.Errorf("NewHashFromStr: %v", err)
|
||||
}
|
||||
|
||||
hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||
highHash, err := daghash.NewHashFromStr(hashStr)
|
||||
if err != nil {
|
||||
t.Errorf("NewHashFromStr: %v", err)
|
||||
}
|
||||
|
||||
// Ensure we get the same data back out.
|
||||
msg := NewMsgRequstIBDBlocks(lowHash, highHash)
|
||||
if !msg.HighHash.IsEqual(highHash) {
|
||||
t.Errorf("NewMsgRequstIBDBlocks: wrong high hash - got %v, want %v",
|
||||
msg.HighHash, highHash)
|
||||
}
|
||||
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(4)
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgRequstIBDBlocks: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
// MsgRequestNextIBDBlocks implements the Message interface and represents a kaspa
|
||||
// RequestNextIBDBlocks message. It is used to notify the IBD syncer peer to send
|
||||
// more blocks.
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgRequestNextIBDBlocks struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgRequestNextIBDBlocks) Command() MessageCommand {
|
||||
return CmdRequestNextIBDBlocks
|
||||
}
|
||||
|
||||
// NewMsgRequestNextIBDBlocks returns a new kaspa RequestNextIBDBlocks message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgRequestNextIBDBlocks() *MsgRequestNextIBDBlocks {
|
||||
return &MsgRequestNextIBDBlocks{}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MsgRequestRelayBlocksHashes is the maximum number of hashes that can
|
||||
// be in a single RequestRelayBlocks message.
|
||||
const MsgRequestRelayBlocksHashes = MaxInvPerMsg
|
||||
|
||||
// MsgRequestRelayBlocks implements the Message interface and represents a kaspa
|
||||
// RequestRelayBlocks message. It is used to request blocks as part of the block
|
||||
// relay protocol.
|
||||
type MsgRequestRelayBlocks struct {
|
||||
baseMessage
|
||||
Hashes []*daghash.Hash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgRequestRelayBlocks) Command() MessageCommand {
|
||||
return CmdRequestRelayBlocks
|
||||
}
|
||||
|
||||
// NewMsgRequestRelayBlocks returns a new kaspa RequestRelayBlocks message that conforms to
|
||||
// the Message interface. See MsgRequestRelayBlocks for details.
|
||||
func NewMsgRequestRelayBlocks(hashes []*daghash.Hash) *MsgRequestRelayBlocks {
|
||||
return &MsgRequestRelayBlocks{
|
||||
Hashes: hashes,
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
// MsgRequestSelectedTip implements the Message interface and represents a kaspa
|
||||
// RequestSelectedTip message. It is used to request the selected tip of another peer.
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgRequestSelectedTip struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgRequestSelectedTip) Command() MessageCommand {
|
||||
return CmdRequestSelectedTip
|
||||
}
|
||||
|
||||
// NewMsgRequestSelectedTip returns a new kaspa RequestSelectedTip message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgRequestSelectedTip() *MsgRequestSelectedTip {
|
||||
return &MsgRequestSelectedTip{}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestRequestSelectedTip tests the MsgRequestSelectedTip API.
|
||||
func TestRequestSelectedTip(t *testing.T) {
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(12)
|
||||
msg := NewMsgRequestSelectedTip()
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgRequestSelectedTip: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MaxInvPerRequestTransactionsMsg is the maximum number of hashes that can
|
||||
// be in a single CmdInvTransaction message.
|
||||
const MaxInvPerRequestTransactionsMsg = MaxInvPerMsg
|
||||
|
||||
// MsgRequestTransactions implements the Message interface and represents a kaspa
|
||||
// RequestTransactions message. It is used to request transactions as part of the
|
||||
// transactions relay protocol.
|
||||
type MsgRequestTransactions struct {
|
||||
baseMessage
|
||||
IDs []*daghash.TxID
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgRequestTransactions) Command() MessageCommand {
|
||||
return CmdRequestTransactions
|
||||
}
|
||||
|
||||
// NewMsgRequestTransactions returns a new kaspa RequestTransactions message that conforms to
|
||||
// the Message interface. See MsgRequestTransactions for details.
|
||||
func NewMsgRequestTransactions(ids []*daghash.TxID) *MsgRequestTransactions {
|
||||
return &MsgRequestTransactions{
|
||||
IDs: ids,
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MsgSelectedTip implements the Message interface and represents a kaspa
|
||||
// selectedtip message. It is used to answer getseltip messages and tell
|
||||
// the asking peer what is the selected tip of this peer.
|
||||
type MsgSelectedTip struct {
|
||||
baseMessage
|
||||
// The selected tip hash of the generator of the message.
|
||||
SelectedTipHash *daghash.Hash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgSelectedTip) Command() MessageCommand {
|
||||
return CmdSelectedTip
|
||||
}
|
||||
|
||||
// NewMsgSelectedTip returns a new kaspa selectedtip message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgSelectedTip(selectedTipHash *daghash.Hash) *MsgSelectedTip {
|
||||
return &MsgSelectedTip{
|
||||
SelectedTipHash: selectedTipHash,
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestSelectedTip tests the MsgSelectedTip API.
|
||||
func TestSelectedTip(t *testing.T) {
|
||||
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(11)
|
||||
msg := NewMsgSelectedTip(&daghash.ZeroHash)
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgSelectedTip: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MsgTransactionNotFound defines a kaspa TransactionNotFound message which is sent in response to
|
||||
// a RequestTransactions message if any of the requested data in not available on the peer.
|
||||
type MsgTransactionNotFound struct {
|
||||
baseMessage
|
||||
ID *daghash.TxID
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgTransactionNotFound) Command() MessageCommand {
|
||||
return CmdTransactionNotFound
|
||||
}
|
||||
|
||||
// NewMsgTransactionNotFound returns a new kaspa transactionsnotfound message that conforms to the
|
||||
// Message interface. See MsgTransactionNotFound for details.
|
||||
func NewMsgTransactionNotFound(id *daghash.TxID) *MsgTransactionNotFound {
|
||||
return &MsgTransactionNotFound{
|
||||
ID: id,
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
// MsgVerAck defines a kaspa verack message which is used for a peer to
|
||||
// acknowledge a version message (MsgVersion) after it has used the information
|
||||
// to negotiate parameters. It implements the Message interface.
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgVerAck struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgVerAck) Command() MessageCommand {
|
||||
return CmdVerAck
|
||||
}
|
||||
|
||||
// NewMsgVerAck returns a new kaspa verack message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgVerAck() *MsgVerAck {
|
||||
return &MsgVerAck{}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestVerAck tests the MsgVerAck API.
|
||||
func TestVerAck(t *testing.T) {
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(1)
|
||||
msg := NewMsgVerAck()
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgVerAck: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/version"
|
||||
"strings"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
)
|
||||
|
||||
// MaxUserAgentLen is the maximum allowed length for the user agent field in a
|
||||
// version message (MsgVersion).
|
||||
const MaxUserAgentLen = 256
|
||||
|
||||
// DefaultUserAgent for appmessage in the stack
|
||||
var DefaultUserAgent = fmt.Sprintf("/kaspad:%s/", version.Version())
|
||||
|
||||
// MsgVersion implements the Message interface and represents a kaspa version
|
||||
// message. It is used for a peer to advertise itself as soon as an outbound
|
||||
// connection is made. The remote peer then uses this information along with
|
||||
// its own to negotiate. The remote peer must then respond with a version
|
||||
// message of its own containing the negotiated values followed by a verack
|
||||
// message (MsgVerAck). This exchange must take place before any further
|
||||
// communication is allowed to proceed.
|
||||
type MsgVersion struct {
|
||||
baseMessage
|
||||
// Version of the protocol the node is using.
|
||||
ProtocolVersion uint32
|
||||
|
||||
// The peer's network (mainnet, testnet, etc.)
|
||||
Network string
|
||||
|
||||
// Bitfield which identifies the enabled services.
|
||||
Services ServiceFlag
|
||||
|
||||
// Time the message was generated. This is encoded as an int64 on the appmessage.
|
||||
Timestamp mstime.Time
|
||||
|
||||
// Address of the local peer.
|
||||
Address *NetAddress
|
||||
|
||||
// The peer unique ID
|
||||
ID *id.ID
|
||||
|
||||
// The user agent that generated messsage. This is a encoded as a varString
|
||||
// on the appmessage. This has a max length of MaxUserAgentLen.
|
||||
UserAgent string
|
||||
|
||||
// The selected tip hash of the generator of the version message.
|
||||
SelectedTipHash *daghash.Hash
|
||||
|
||||
// Don't announce transactions to peer.
|
||||
DisableRelayTx bool
|
||||
|
||||
// The subnetwork of the generator of the version message. Should be nil in full nodes
|
||||
SubnetworkID *subnetworkid.SubnetworkID
|
||||
}
|
||||
|
||||
// HasService returns whether the specified service is supported by the peer
|
||||
// that generated the message.
|
||||
func (msg *MsgVersion) HasService(service ServiceFlag) bool {
|
||||
return msg.Services&service == service
|
||||
}
|
||||
|
||||
// AddService adds service as a supported service by the peer generating the
|
||||
// message.
|
||||
func (msg *MsgVersion) AddService(service ServiceFlag) {
|
||||
msg.Services |= service
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgVersion) Command() MessageCommand {
|
||||
return CmdVersion
|
||||
}
|
||||
|
||||
// NewMsgVersion returns a new kaspa version message that conforms to the
|
||||
// Message interface using the passed parameters and defaults for the remaining
|
||||
// fields.
|
||||
func NewMsgVersion(addr *NetAddress, id *id.ID, network string,
|
||||
selectedTipHash *daghash.Hash, subnetworkID *subnetworkid.SubnetworkID) *MsgVersion {
|
||||
|
||||
// Limit the timestamp to one millisecond precision since the protocol
|
||||
// doesn't support better.
|
||||
return &MsgVersion{
|
||||
ProtocolVersion: ProtocolVersion,
|
||||
Network: network,
|
||||
Services: 0,
|
||||
Timestamp: mstime.Now(),
|
||||
Address: addr,
|
||||
ID: id,
|
||||
UserAgent: DefaultUserAgent,
|
||||
SelectedTipHash: selectedTipHash,
|
||||
DisableRelayTx: false,
|
||||
SubnetworkID: subnetworkID,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateUserAgent checks userAgent length against MaxUserAgentLen
|
||||
func ValidateUserAgent(userAgent string) error {
|
||||
if len(userAgent) > MaxUserAgentLen {
|
||||
str := fmt.Sprintf("user agent too long [len %d, max %d]",
|
||||
len(userAgent), MaxUserAgentLen)
|
||||
return messageError("MsgVersion", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddUserAgent adds a user agent to the user agent string for the version
|
||||
// message. The version string is not defined to any strict format, although
|
||||
// it is recommended to use the form "major.minor.revision" e.g. "2.6.41".
|
||||
func (msg *MsgVersion) AddUserAgent(name string, version string,
|
||||
comments ...string) {
|
||||
|
||||
newUserAgent := fmt.Sprintf("%s:%s", name, version)
|
||||
if len(comments) != 0 {
|
||||
newUserAgent = fmt.Sprintf("%s(%s)", newUserAgent,
|
||||
strings.Join(comments, "; "))
|
||||
}
|
||||
newUserAgent = fmt.Sprintf("%s%s/", msg.UserAgent, newUserAgent)
|
||||
msg.UserAgent = newUserAgent
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestVersion tests the MsgVersion API.
|
||||
func TestVersion(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
// Create version message data.
|
||||
selectedTipHash := &daghash.Hash{12, 34}
|
||||
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 16111}
|
||||
me := NewNetAddress(tcpAddrMe, SFNodeNetwork)
|
||||
generatedID, err := id.GenerateID()
|
||||
if err != nil {
|
||||
t.Fatalf("id.GenerateID: %s", err)
|
||||
}
|
||||
|
||||
// Ensure we get the correct data back out.
|
||||
msg := NewMsgVersion(me, generatedID, "mainnet", selectedTipHash, nil)
|
||||
if msg.ProtocolVersion != pver {
|
||||
t.Errorf("NewMsgVersion: wrong protocol version - got %v, want %v",
|
||||
msg.ProtocolVersion, pver)
|
||||
}
|
||||
if !reflect.DeepEqual(msg.Address, me) {
|
||||
t.Errorf("NewMsgVersion: wrong me address - got %v, want %v",
|
||||
spew.Sdump(&msg.Address), spew.Sdump(me))
|
||||
}
|
||||
if msg.ID.String() != generatedID.String() {
|
||||
t.Errorf("NewMsgVersion: wrong nonce - got %s, want %s",
|
||||
msg.ID, generatedID)
|
||||
}
|
||||
if msg.UserAgent != DefaultUserAgent {
|
||||
t.Errorf("NewMsgVersion: wrong user agent - got %v, want %v",
|
||||
msg.UserAgent, DefaultUserAgent)
|
||||
}
|
||||
if !msg.SelectedTipHash.IsEqual(selectedTipHash) {
|
||||
t.Errorf("NewMsgVersion: wrong selected tip hash - got %s, want %s",
|
||||
msg.SelectedTipHash, selectedTipHash)
|
||||
}
|
||||
if msg.DisableRelayTx {
|
||||
t.Errorf("NewMsgVersion: disable relay tx is not false by "+
|
||||
"default - got %v, want %v", msg.DisableRelayTx, false)
|
||||
}
|
||||
|
||||
msg.AddUserAgent("myclient", "1.2.3", "optional", "comments")
|
||||
customUserAgent := DefaultUserAgent + "myclient:1.2.3(optional; comments)/"
|
||||
if msg.UserAgent != customUserAgent {
|
||||
t.Errorf("AddUserAgent: wrong user agent - got %s, want %s",
|
||||
msg.UserAgent, customUserAgent)
|
||||
}
|
||||
|
||||
msg.AddUserAgent("mygui", "3.4.5")
|
||||
customUserAgent += "mygui:3.4.5/"
|
||||
if msg.UserAgent != customUserAgent {
|
||||
t.Errorf("AddUserAgent: wrong user agent - got %s, want %s",
|
||||
msg.UserAgent, customUserAgent)
|
||||
}
|
||||
|
||||
// Version message should not have any services set by default.
|
||||
if msg.Services != 0 {
|
||||
t.Errorf("NewMsgVersion: wrong default services - got %v, want %v",
|
||||
msg.Services, 0)
|
||||
|
||||
}
|
||||
if msg.HasService(SFNodeNetwork) {
|
||||
t.Errorf("HasService: SFNodeNetwork service is set")
|
||||
}
|
||||
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(0)
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgVersion: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
}
|
||||
|
||||
// Ensure adding the full service node flag works.
|
||||
msg.AddService(SFNodeNetwork)
|
||||
if msg.Services != SFNodeNetwork {
|
||||
t.Errorf("AddService: wrong services - got %v, want %v",
|
||||
msg.Services, SFNodeNetwork)
|
||||
}
|
||||
if !msg.HasService(SFNodeNetwork) {
|
||||
t.Errorf("HasService: SFNodeNetwork service not set")
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"net"
|
||||
)
|
||||
|
||||
// NetAddress defines information about a peer on the network including the time
|
||||
// it was last seen, the services it supports, its IP address, and port.
|
||||
type NetAddress struct {
|
||||
// Last time the address was seen.
|
||||
Timestamp mstime.Time
|
||||
|
||||
// Bitfield which identifies the services supported by the address.
|
||||
Services ServiceFlag
|
||||
|
||||
// IP address of the peer.
|
||||
IP net.IP
|
||||
|
||||
// Port the peer is using. This is encoded in big endian on the appmessage
|
||||
// which differs from most everything else.
|
||||
Port uint16
|
||||
}
|
||||
|
||||
// HasService returns whether the specified service is supported by the address.
|
||||
func (na *NetAddress) HasService(service ServiceFlag) bool {
|
||||
return na.Services&service == service
|
||||
}
|
||||
|
||||
// AddService adds service as a supported service by the peer generating the
|
||||
// message.
|
||||
func (na *NetAddress) AddService(service ServiceFlag) {
|
||||
na.Services |= service
|
||||
}
|
||||
|
||||
// TCPAddress converts the NetAddress to *net.TCPAddr
|
||||
func (na *NetAddress) TCPAddress() *net.TCPAddr {
|
||||
return &net.TCPAddr{
|
||||
IP: na.IP,
|
||||
Port: int(na.Port),
|
||||
}
|
||||
}
|
||||
|
||||
// NewNetAddressIPPort returns a new NetAddress using the provided IP, port, and
|
||||
// supported services with defaults for the remaining fields.
|
||||
func NewNetAddressIPPort(ip net.IP, port uint16, services ServiceFlag) *NetAddress {
|
||||
return NewNetAddressTimestamp(mstime.Now(), services, ip, port)
|
||||
}
|
||||
|
||||
// NewNetAddressTimestamp returns a new NetAddress using the provided
|
||||
// timestamp, IP, port, and supported services. The timestamp is rounded to
|
||||
// single millisecond precision.
|
||||
func NewNetAddressTimestamp(
|
||||
timestamp mstime.Time, services ServiceFlag, ip net.IP, port uint16) *NetAddress {
|
||||
// Limit the timestamp to one millisecond precision since the protocol
|
||||
// doesn't support better.
|
||||
na := NetAddress{
|
||||
Timestamp: timestamp,
|
||||
Services: services,
|
||||
IP: ip,
|
||||
Port: port,
|
||||
}
|
||||
return &na
|
||||
}
|
||||
|
||||
// NewNetAddress returns a new NetAddress using the provided TCP address and
|
||||
// supported services with defaults for the remaining fields.
|
||||
func NewNetAddress(addr *net.TCPAddr, services ServiceFlag) *NetAddress {
|
||||
return NewNetAddressIPPort(addr.IP, uint16(addr.Port), services)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestNetAddress tests the NetAddress API.
|
||||
func TestNetAddress(t *testing.T) {
|
||||
ip := net.ParseIP("127.0.0.1")
|
||||
port := 16111
|
||||
|
||||
// Test NewNetAddress.
|
||||
na := NewNetAddress(&net.TCPAddr{IP: ip, Port: port}, 0)
|
||||
|
||||
// Ensure we get the same ip, port, and services back out.
|
||||
if !na.IP.Equal(ip) {
|
||||
t.Errorf("NetNetAddress: wrong ip - got %v, want %v", na.IP, ip)
|
||||
}
|
||||
if na.Port != uint16(port) {
|
||||
t.Errorf("NetNetAddress: wrong port - got %v, want %v", na.Port,
|
||||
port)
|
||||
}
|
||||
if na.Services != 0 {
|
||||
t.Errorf("NetNetAddress: wrong services - got %v, want %v",
|
||||
na.Services, 0)
|
||||
}
|
||||
if na.HasService(SFNodeNetwork) {
|
||||
t.Errorf("HasService: SFNodeNetwork service is set")
|
||||
}
|
||||
|
||||
// Ensure adding the full service node flag works.
|
||||
na.AddService(SFNodeNetwork)
|
||||
if na.Services != SFNodeNetwork {
|
||||
t.Errorf("AddService: wrong services - got %v, want %v",
|
||||
na.Services, SFNodeNetwork)
|
||||
}
|
||||
if !na.HasService(SFNodeNetwork) {
|
||||
t.Errorf("HasService: SFNodeNetwork service not set")
|
||||
}
|
||||
}
|
||||
14
app/log.go
14
app/log.go
@@ -1,14 +0,0 @@
|
||||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Copyright (c) 2017 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
|
||||
var log, _ = logger.Get(logger.SubsystemTags.KASD)
|
||||
var spawn = panics.GoroutineWrapperFunc(log)
|
||||
@@ -1,64 +0,0 @@
|
||||
// Copyright (c) 2015-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blocklogger
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
)
|
||||
|
||||
var (
|
||||
receivedLogBlocks int64
|
||||
receivedLogTx int64
|
||||
lastBlockLogTime = mstime.Now()
|
||||
mtx sync.Mutex
|
||||
)
|
||||
|
||||
// LogBlock logs a new block blue score 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 LogBlock(block *util.Block) error {
|
||||
mtx.Lock()
|
||||
defer mtx.Unlock()
|
||||
|
||||
receivedLogBlocks++
|
||||
receivedLogTx += int64(len(block.MsgBlock().Transactions))
|
||||
|
||||
now := mstime.Now()
|
||||
duration := now.Sub(lastBlockLogTime)
|
||||
if duration < time.Second*10 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Truncate the duration to 10s of milliseconds.
|
||||
tDuration := duration.Round(10 * time.Millisecond)
|
||||
|
||||
// Log information about new block blue score.
|
||||
blockStr := "blocks"
|
||||
if receivedLogBlocks == 1 {
|
||||
blockStr = "block"
|
||||
}
|
||||
txStr := "transactions"
|
||||
if receivedLogTx == 1 {
|
||||
txStr = "transaction"
|
||||
}
|
||||
|
||||
blueScore, err := block.BlueScore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Processed %d %s in the last %s (%d %s, blue score %d, %s)",
|
||||
receivedLogBlocks, blockStr, tDuration, receivedLogTx,
|
||||
txStr, blueScore, block.MsgBlock().Header.Timestamp)
|
||||
|
||||
receivedLogBlocks = 0
|
||||
receivedLogTx = 0
|
||||
lastBlockLogTime = now
|
||||
return nil
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// Copyright (c) 2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blocklogger
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
)
|
||||
|
||||
var log, _ = logger.Get(logger.SubsystemTags.PROT)
|
||||
@@ -1,14 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// DefaultTimeout is the default duration to wait for enqueuing/dequeuing
|
||||
// to/from routes.
|
||||
const DefaultTimeout = 30 * time.Second
|
||||
|
||||
// ErrPeerWithSameIDExists signifies that a peer with the same ID already exist.
|
||||
var ErrPeerWithSameIDExists = errors.New("ready peer with the same ID already exists")
|
||||
@@ -1,10 +0,0 @@
|
||||
package flowcontext
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
)
|
||||
|
||||
// AddressManager returns the address manager associated to the flow context.
|
||||
func (f *FlowContext) AddressManager() *addressmanager.AddressManager {
|
||||
return f.addressManager
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user