Service identifier and node cleanup (#522)

* Log messages more granularely in node

* Add pong message

* Rework P2P service identifier

The old implementation didn't catch the notion
that service identifiers was a bit field where
multiple services could be set at the same time.

* Make Peer take InetSocketAddress

Previously it took NetworkIpAddress.
This doesn't make any sense, as we
need to know the services bitfield
to construct a NetworkIpAddress.

* Clean up logs and toString methods

* Clean up node README and main method

* Make all networks final case objects

* Skip publishing of wallet, node and chain

* Clean up logging of node and chain modules

* Add logging of max height in SpvNodeMain

* Clean up Scaladoc and toStrings

* Add logging configuration as symlinks in node

We'll have to figure out a more stable solution to
configure logging for the SPV node, but for the time
being this is an acceptable solution.

* Fix 2.11 compiler error
This commit is contained in:
Torkel Rogstad 2019-06-17 21:27:51 +02:00 committed by Chris Stewart
parent 5ed0f6d35b
commit 02560ebb5f
33 changed files with 634 additions and 369 deletions

View file

@ -276,7 +276,9 @@ lazy val chain = project
.settings(chainDbSettings: _*)
.settings(
name := "bitcoin-s-chain",
libraryDependencies ++= Deps.chain
libraryDependencies ++= Deps.chain,
// don't publish while such a heavy WIP
publish / skip := true
).dependsOn(core, dbCommons)
.enablePlugins(FlywayPlugin)
@ -366,7 +368,9 @@ lazy val node = {
.settings(nodeDbSettings: _*)
.settings(
name := "bitcoin-s-node",
libraryDependencies ++= Deps.node
libraryDependencies ++= Deps.node,
// don't publish while such a heavy WIP
publish / skip := true
)
.dependsOn(
core,
@ -452,7 +456,9 @@ lazy val wallet = project
.settings(walletDbSettings: _*)
.settings(
name := "bitcoin-s-wallet",
libraryDependencies ++= Deps.wallet
libraryDependencies ++= Deps.wallet,
// don't publish while such a heavy WIP
publish / skip := true
)
.dependsOn(core, dbCommons)
.enablePlugins(FlywayPlugin)

View file

@ -37,10 +37,11 @@ trait ChainApi {
}
/** Get's a [[org.bitcoins.chain.models.BlockHeaderDb]] from the chain's database */
def getHeader(hash: DoubleSha256DigestBE): Future[Option[BlockHeaderDb]]
def getHeader(hash: DoubleSha256DigestBE)(
implicit ec: ExecutionContext): Future[Option[BlockHeaderDb]]
/** Gets the number of blocks in the database */
def getBlockCount: Future[Long]
def getBlockCount(implicit ec: ExecutionContext): Future[Long]
/** Gets the hash of the block that is what we consider "best" */
def getBestBlockHash(

View file

@ -20,13 +20,24 @@ case class ChainHandler(
extends ChainApi
with BitcoinSLogger {
override def getBlockCount: Future[Long] = {
blockHeaderDAO.maxHeight
override def getBlockCount(implicit ec: ExecutionContext): Future[Long] = {
logger.debug(s"Querying for block count")
blockHeaderDAO.maxHeight.map { height =>
logger.debug(s"getBlockCount result: count=$height")
height
}
}
override def getHeader(
hash: DoubleSha256DigestBE): Future[Option[BlockHeaderDb]] = {
blockHeaderDAO.findByHash(hash)
override def getHeader(hash: DoubleSha256DigestBE)(
implicit ec: ExecutionContext): Future[Option[BlockHeaderDb]] = {
blockHeaderDAO.findByHash(hash).map { header =>
logger.debug(s"Looking for header by hash=$hash")
val resultStr = header
.map(h => s"height=${h.height}, hash=${h.hashBE}")
.getOrElse("None")
logger.debug(s"getHeader result: $resultStr")
header
}
}
override def processHeader(header: BlockHeader)(
@ -39,7 +50,11 @@ case class ChainHandler(
//now we have successfully connected the header, we need to insert
//it into the database
val createdF = blockHeaderDAO.create(updatedHeader)
createdF.map(_ => ChainHandler(blockHeaderDAO, chainConfig))
createdF.map { header =>
logger.debug(
s"Connected new header to blockchain, heigh=${header.height} hash=${header.hashBE}")
ChainHandler(blockHeaderDAO, chainConfig)
}
case BlockchainUpdate.Failed(_, _, reason) =>
val errMsg =
s"Failed to add header to chain, header=${header.hashBE.hex} reason=${reason}"
@ -61,13 +76,16 @@ case class ChainHandler(
*/
override def getBestBlockHash(
implicit ec: ExecutionContext): Future[DoubleSha256DigestBE] = {
logger.debug(s"Querying for best block hash")
//naive implementation, this is looking for the tip with the _most_ proof of work
//this does _not_ mean that it is on the chain that has the most work
//TODO: Enhance this in the future to return the "heaviest" header
//https://bitcoin.org/en/glossary/block-chain
blockHeaderDAO.chainTips.map { tips =>
val sorted = tips.sortBy(header => header.blockHeader.difficulty)
sorted.head.hashBE
val hash = sorted.head.hashBE
logger.debug(s"getBestBlockHash result: hash=$hash")
hash
}
}
}

View file

@ -1,6 +1,10 @@
package org.bitcoins.chain.validation
import org.bitcoins.chain.models.{BlockHeaderDAO, BlockHeaderDb, BlockHeaderDbHelper}
import org.bitcoins.chain.models.{
BlockHeaderDAO,
BlockHeaderDb,
BlockHeaderDbHelper
}
import org.bitcoins.chain.pow.Pow
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.blockchain.BlockHeader
@ -28,7 +32,7 @@ sealed abstract class TipValidation extends BitcoinSLogger {
blockHeaderDAO: BlockHeaderDAO)(
implicit ec: ExecutionContext): Future[TipUpdateResult] = {
val header = newPotentialTip
logger.info(
logger.trace(
s"Checking header=${header.hashBE.hex} to try to connect to currentTip=${currentTip.hashBE.hex} with height=${currentTip.height}")
val powCheckF = isBadPow(newPotentialTip = newPotentialTip,
@ -66,7 +70,7 @@ sealed abstract class TipValidation extends BitcoinSLogger {
currentTip: BlockHeaderDb)(implicit ec: ExecutionContext): Unit = {
connectTipResultF.map {
case TipUpdateResult.Success(tipDb) =>
logger.info(
logger.trace(
s"Successfully connected ${tipDb.hashBE.hex} with height=${tipDb.height} to block=${currentTip.hashBE.hex} with height=${currentTip.height}")
case bad: TipUpdateResult.Failure =>
@ -78,9 +82,11 @@ sealed abstract class TipValidation extends BitcoinSLogger {
()
}
/** Checks if [[header]] hashes to meet the POW requirements for this block (nBits)
* Mimics this
* @see [[https://github.com/bitcoin/bitcoin/blob/eb7daf4d600eeb631427c018a984a77a34aca66e/src/pow.cpp#L74]]
/** Checks if the given header hashes to meet the POW requirements for
* this block (determined by lookinng at the `nBits` field).
*
* @see [[https://github.com/bitcoin/bitcoin/blob/eb7daf4d600eeb631427c018a984a77a34aca66e/src/pow.cpp#L74 pow.cpp]]
* in Bitcoin Core
* */
def isBadNonce(header: BlockHeader): Boolean = {
//convert hash into a big integer

View file

@ -0,0 +1,40 @@
package org.bitcoins.core.p2p
import org.bitcoins.testkit.util.BitcoinSUnitTest
import org.bitcoins.testkit.core.gen.p2p.P2PGenerator
class ServiceIdentifierTest extends BitcoinSUnitTest {
it must "have serialization symmetry" in {
forAll(P2PGenerator.serviceIdentifier) { id =>
assert(ServiceIdentifier.fromBytes(id.bytes) == id)
}
}
it must "parse NODE_NONE" in {
assert(ServiceIdentifier.NODE_NONE.nodeNone)
}
it must "parse NODE_NETWORK" in {
assert(ServiceIdentifier.NODE_NETWORK.nodeNetwork)
}
it must "parse NODE_GET_UTXO" in {
assert(ServiceIdentifier.NODE_GET_UTXO.nodeGetUtxo)
}
it must "parse NODE_BLOOM" in {
assert(ServiceIdentifier.NODE_BLOOM.nodeBloom)
}
it must "parse NODE_WITNESS" in {
assert(ServiceIdentifier.NODE_WITNESS.nodeWitness)
}
it must "parse NODE_XTHIN" in {
assert(ServiceIdentifier.NODE_XTHIN.nodeXthin)
}
it must "parse NODE_NETWORK_LIMITED" in {
assert(ServiceIdentifier.NODE_NETWORK_LIMITED.nodeNetworkLimited)
}
}

View file

@ -7,6 +7,7 @@ import org.bitcoins.core.number.{Int32, UInt64}
import org.bitcoins.testkit.core.gen.p2p.ControlMessageGenerator
import org.bitcoins.testkit.util.BitcoinSUnitTest
import org.joda.time.DateTime
import scodec.bits._
class VersionMessageTest extends BitcoinSUnitTest {
@ -18,11 +19,11 @@ class VersionMessageTest extends BitcoinSUnitTest {
"VersionMessage" must "create a new version message to be sent to another node on the network" in {
val versionMessage = VersionMessage(MainNet, InetAddress.getLocalHost)
versionMessage.addressReceiveServices must be(UnnamedService)
assert(versionMessage.addressReceiveServices.nodeNone)
versionMessage.addressReceiveIpAddress must be(InetAddress.getLocalHost)
versionMessage.addressReceivePort must be(MainNet.port)
versionMessage.addressTransServices must be(NodeNetwork)
assert(versionMessage.addressTransServices.nodeNetwork)
versionMessage.addressTransIpAddress must be(InetAddress.getLocalHost)
versionMessage.addressTransPort must be(MainNet.port)
@ -30,4 +31,19 @@ class VersionMessageTest extends BitcoinSUnitTest {
versionMessage.startHeight must be(Int32.zero)
versionMessage.timestamp.toLong must be(DateTime.now.getMillis +- 1000)
}
it must "correctly deduce service flags" in {
// extracted from log dump of local bitcoind running 0.17.0.1
val msgBytes =
hex"7f1101000d040000000000004ea1035d0000000000000000000000000000000000000000000000000000000000000d04000000000000000000000000000000000000000000000000fa562b93b3113e02122f5361746f7368693a302e31372e302e312f6800000001"
val versionMessage = VersionMessage.fromBytes(msgBytes)
assert(versionMessage.services.nodeNetwork)
assert(!versionMessage.services.nodeGetUtxo)
assert(versionMessage.services.nodeBloom)
assert(versionMessage.services.nodeWitness)
assert(!versionMessage.services.nodeXthin)
assert(versionMessage.services.nodeNetworkLimited)
assert(!versionMessage.services.nodeNone)
}
}

View file

@ -1,7 +1,6 @@
package org.bitcoins.core.serializers.p2p
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.p2p.NodeNetwork
import org.scalatest.{FlatSpec, MustMatchers}
class RawNetworkIpAddressSerializerTest extends FlatSpec with MustMatchers {
@ -16,7 +15,7 @@ class RawNetworkIpAddressSerializerTest extends FlatSpec with MustMatchers {
"RawNetworkIpAddressSerializer" must "read a network ip address from a hex string" in {
val ipAddress = RawNetworkIpAddressSerializer.read(hex)
ipAddress.time must be(UInt32(1414012889))
ipAddress.services must be(NodeNetwork)
assert(ipAddress.services.nodeNetwork)
ipAddress.address.toString must be("/192.0.2.51")
ipAddress.port must be(8333)
}

View file

@ -1,20 +1,16 @@
package org.bitcoins.core.serializers.p2p.messages
import org.bitcoins.core.p2p.{NodeNetwork, UnnamedService}
import org.scalatest.{FlatSpec, MustMatchers}
/**
* Created by chris on 6/2/16.
*/
class RawServiceIdentifierSerializerTest extends FlatSpec with MustMatchers {
"RawServiceIdentifierSerializer" must "read a unnamed service identfier from a hex string" in {
val hex = "0000000000000000"
RawServiceIdentifierSerializer.read(hex) must be(UnnamedService)
assert(RawServiceIdentifierSerializer.read(hex).nodeNone)
}
it must "read a full node service identifier" in {
val hex = "0100000000000000"
RawServiceIdentifierSerializer.read(hex) must be(NodeNetwork)
assert(RawServiceIdentifierSerializer.read(hex).nodeNetwork)
}
it must "write a unnamed service identifier" in {

View file

@ -39,16 +39,16 @@ class RawVersionMessageSerializerTest extends FlatSpec with MustMatchers {
"RawVersionMessageSerializer" must "read a raw version message from the p2p network" in {
val versionMessage = RawVersionMessageSerializer.read(hex)
versionMessage.version must be(ProtocolVersion(protocolVersion))
versionMessage.services must be(NodeNetwork)
assert(versionMessage.services.nodeNetwork, versionMessage.services)
versionMessage.timestamp must be(Int64(1415483324))
versionMessage.addressReceiveServices must be(NodeNetwork)
assert(versionMessage.addressReceiveServices.nodeNetwork)
NetworkIpAddress
.writeAddress(versionMessage.addressReceiveIpAddress)
.toHex must be(receivingNodeIpAddress)
versionMessage.addressReceivePort must be(8333)
versionMessage.addressTransServices must be(NodeNetwork)
assert(versionMessage.addressTransServices.nodeNetwork)
NetworkIpAddress
.writeAddress(versionMessage.addressTransIpAddress)
.toHex must be(transNodeIpAddress)
@ -99,13 +99,13 @@ class RawVersionMessageSerializerTest extends FlatSpec with MustMatchers {
"7211010001000000000000003248e7fc9a64a6c2000000000000000000000000000000000000ffff0000000042a1000000000000000000000000000000000000ffff00000000a3eb8000000000000001564e635148775a38376252653979346d365041376c5832695641354966316a576a557963796b464f516571423052456a39326177614b79307a4d5264636b76454b71316a393769334d616c33456f37517867646a637056fe5b2bc900"
val versionMessage = RawVersionMessageSerializer.read(hex)
versionMessage.version must be(ProtocolVersion70002)
versionMessage.services must be(NodeNetwork)
assert(versionMessage.services.nodeNetwork)
versionMessage.timestamp must be(Int64(-4420735367386806222L))
versionMessage.addressReceiveIpAddress must be(
new InetSocketAddress(17057).getAddress)
versionMessage.addressReceiveServices must be(UnnamedService)
assert(versionMessage.addressReceiveServices.nodeNone)
versionMessage.addressReceivePort must be(17057)
versionMessage.addressTransServices must be(UnnamedService)
assert(versionMessage.addressTransServices.nodeNone)
versionMessage.addressTransIpAddress must be(
new InetSocketAddress(41963).getAddress)
versionMessage.addressTransPort must be(41963)

View file

@ -3,9 +3,6 @@ package org.bitcoins.core.config
import org.bitcoins.core.protocol.blockchain._
import scodec.bits.ByteVector
/**
* Created by chris on 7/27/15.
*/
sealed abstract class NetworkParameters {
/** The parameters of the blockchain we are connecting to */
@ -90,7 +87,7 @@ sealed abstract class MainNet extends BitcoinNetwork {
}
object MainNet extends MainNet
final case object MainNet extends MainNet
sealed abstract class TestNet3 extends BitcoinNetwork {
override def chainParams: TestNetChainParams.type = TestNetChainParams
@ -119,7 +116,7 @@ sealed abstract class TestNet3 extends BitcoinNetwork {
}
object TestNet3 extends TestNet3
final case object TestNet3 extends TestNet3
sealed abstract class RegTest extends BitcoinNetwork {
override def chainParams: RegTestNetChainParams.type = RegTestNetChainParams
@ -145,7 +142,7 @@ sealed abstract class RegTest extends BitcoinNetwork {
override def magicBytes = ByteVector(0xfa, 0xbf, 0xb5, 0xda)
}
object RegTest extends RegTest
final case object RegTest extends RegTest
// $COVERAGE-ON$
object Networks {

View file

@ -89,13 +89,15 @@ object NetworkIpAddress extends Factory[NetworkIpAddress] {
def fromBytes(bytes: ByteVector): NetworkIpAddress =
RawNetworkIpAddressSerializer.read(bytes)
def fromInetSocketAddress(socket: InetSocketAddress): NetworkIpAddress = {
def fromInetSocketAddress(
socket: InetSocketAddress,
services: ServiceIdentifier): NetworkIpAddress = {
//TODO: this might be wrong, read this time documentation above
val timestamp = UInt32(System.currentTimeMillis() / 1000)
NetworkIpAddress(
time = timestamp,
services = NodeNetwork,
services = services,
address = socket.getAddress,
port = socket.getPort
)

View file

@ -206,6 +206,17 @@ trait GetHeadersMessage extends DataPayload {
override def commandName = NetworkPayload.getHeadersCommandName
override def bytes: ByteVector = RawGetHeadersMessageSerializer.write(this)
override def toString(): String = {
val count = hashCount.toInt
val hashesStr = if (count > 5) {
hashes.take(5).mkString + "..."
} else {
hashes.mkString
}
s"GetHeadersMessage($version, hashCount=$count, hashes=$hashesStr, stop=$hashStop)"
}
}
object GetHeadersMessage extends Factory[GetHeadersMessage] {
@ -763,6 +774,8 @@ trait PongMessage extends ControlPayload {
override def bytes: ByteVector = RawPongMessageSerializer.write(this)
override def toString(): String = s"PongMessage(${nonce.toBigInt})"
}
object PongMessage extends Factory[PongMessage] {
@ -864,7 +877,7 @@ object RejectMessage extends Factory[RejectMessage] {
* using a headers message rather than an inv message.
* There is no payload in a sendheaders message. See the message header section for an example
* of a message without a payload.
* [[https://bitcoin.org/en/developer-reference#sendheaders]]
* @see [[https://bitcoin.org/en/developer-reference#sendheaders]]
*/
case object SendHeadersMessage extends ControlPayload {
override def commandName = NetworkPayload.sendHeadersCommandName
@ -876,7 +889,7 @@ case object SendHeadersMessage extends ControlPayload {
* informing the connecting node that it can begin to send other messages.
* The verack message has no payload; for an example of a message with no payload,
* see the message headers section.
* [[https://bitcoin.org/en/developer-reference#verack]]
* @see [[https://bitcoin.org/en/developer-reference#verack]]
*/
case object VerAckMessage extends ControlPayload {
override val commandName = NetworkPayload.verAckCommandName
@ -984,6 +997,11 @@ trait VersionMessage extends ControlPayload {
override def commandName = NetworkPayload.versionCommandName
override def bytes: ByteVector = RawVersionMessageSerializer.write(this)
// TODO addressTransServices, addressTransIpAddress and addressTransPort
// what do these fields mean?
override def toString(): String =
s"VersionMessage($version, $services, epoch=${timestamp.toLong}, receiverServices=$addressReceiveIpAddress, receiverAddress=$addressReceiveIpAddress, receiverPort=$addressReceivePort), userAgent=$userAgent, startHeight=${startHeight.toInt}, relay=$relay)"
}
/**
@ -1062,12 +1080,12 @@ object VersionMessage extends Factory[VersionMessage] {
val relay = false
VersionMessage(
version = ProtocolVersion.default,
services = UnnamedService,
services = ServiceIdentifier.NODE_NONE,
timestamp = Int64(java.time.Instant.now.toEpochMilli),
addressReceiveServices = UnnamedService,
addressReceiveServices = ServiceIdentifier.NODE_NONE,
addressReceiveIpAddress = receivingIpAddress,
addressReceivePort = network.port,
addressTransServices = NodeNetwork,
addressTransServices = ServiceIdentifier.NODE_NETWORK,
addressTransIpAddress = transmittingIpAddress,
addressTransPort = network.port,
nonce = nonce,

View file

@ -7,47 +7,158 @@ import org.bitcoins.core.util.Factory
import scodec.bits.ByteVector
/**
* Indicates the services that are provided by this spv node
* Indicates the services that are provided by a node on the P2P network
*
* @see [[https://bitcoin.org/en/developer-reference#version]]
* @see [[https://github.com/bitcoin/bitcoin/blob/master/src/protocol.h#L247 protocol.h]]
* in Bitcoin Core
*/
sealed trait ServiceIdentifier extends NetworkElement {
def num: UInt64
sealed abstract class ServiceIdentifier extends NetworkElement {
/** The underlying number backing the set of flags */
private[bitcoins] val num: UInt64
private val reversedBits = num.bytes.bits.reverse
override def bytes: ByteVector = RawServiceIdentifierSerializer.write(this)
}
/**
* This node is not a full node.
* It may not be able to provide any data except for the transactions it originates.
*/
case object UnnamedService extends ServiceIdentifier {
override val num = UInt64.zero
}
/**
* This node is not a full node.
* It may not be able to provide any data except for the transactions it originates.
*/
lazy val nodeNone: Boolean = num == UInt64.zero
/**
* This is a full node and can be asked for full blocks.
* It should implement all protocol features available in its self-reported protocol version.
*/
case object NodeNetwork extends ServiceIdentifier {
override val num = UInt64.one
/**
* This is a full node and can be asked for full blocks.
* It should implement all protocol features available in
* its self-reported protocol version.
*/
lazy val nodeNetwork: Boolean = reversedBits(0) // 1 << 0
/**
* This is a full node capable of responding to the
* `getutxo` protocol request. This is not supported
* by any currently-maintained Bitcoin node.
*
* @see [[https://github.com/bitcoin/bips/blob/master/bip-0064.mediawiki BIP64]]
* for details on how this is implemented.
*/
lazy val nodeGetUtxo: Boolean = reversedBits(1) // 1 << 1
/**
* This is a full node capable and willing to handle
* bloom-filtered connections.
*
* @see [[https://github.com/bitcoin/bips/blob/master/bip-0111.mediawiki BIP111]]
* for details
*/
lazy val nodeBloom: Boolean = reversedBits(2) // 1 << 2
/**
* This is a full node that can be asked for blocks
* and transactions including witness data.
*
* @see [[https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki BIP144]]]
* for details.
*/
lazy val nodeWitness: Boolean = reversedBits(3) // 1 << 3
/**
* This is a full node that supports Xtreme Thinblocks.
* This is not supported by any currently-maintained
* Bitcoin node.
*/
lazy val nodeXthin: Boolean = reversedBits(4) // 1 << 4
/**
* this means the same as `nodeNetwork` with the limitation of only
* serving the last 288 (2 days) blocks
*
* @see [[https://github.com/bitcoin/bips/blob/master/bip-0159.mediawiki BIP159]]
* for details on how this is implemented.
*/
lazy val nodeNetworkLimited: Boolean = reversedBits(10) // 1 << 10
override def toString: String = {
val innerText =
if (nodeNone) "none"
else
s"network=$nodeNetwork, getUtxo=$nodeGetUtxo, bloom=$nodeBloom, witness=$nodeWitness, xthin=$nodeXthin, networkLimited=$nodeNetworkLimited"
s"ServiceIdentifier($innerText)"
}
}
/**
* Designated type for any service that does not have value of 0 or 1
*/
sealed trait UnknownService extends ServiceIdentifier
sealed trait UnknownService extends ServiceIdentifier {
override def toString(): String = s"UnknownService(${num.toLong})"
}
object ServiceIdentifier extends Factory[ServiceIdentifier] {
private case class UnknownServiceImpl(num: UInt64) extends UnknownService
/**
* This node is not a full node.
* It may not be able to provide any data except for the transactions it originates.
*/
val NODE_NONE = ServiceIdentifier(UInt64.zero)
/**
* This is a full node and can be asked for full blocks.
* It should implement all protocol features available in its self-reported protocol version.
*/
val NODE_NETWORK: ServiceIdentifier = ServiceIdentifier(1 << 0)
/**
* This is a full node capable of responding to the
* `getutxo` protocol request. This is not supported
* by any currently-maintained Bitcoin node.
*
* @see [[https://github.com/bitcoin/bips/blob/master/bip-0064.mediawiki BIP64]]
* for details on how this is implemented.
*/
val NODE_GET_UTXO: ServiceIdentifier = ServiceIdentifier(1 << 1)
/**
* This is a full node capable and willing to handle
* bloom-filtered connections.
*
* @see [[https://github.com/bitcoin/bips/blob/master/bip-0111.mediawiki BIP111]]
* for details
*/
val NODE_BLOOM: ServiceIdentifier = ServiceIdentifier(1 << 2)
/**
* This is a full node that can be asked for blocks
* and transactions including witness data.
*
* @see [[https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki BIP144]]]
* for details.
*/
val NODE_WITNESS: ServiceIdentifier = ServiceIdentifier(1 << 3)
/**
* This is a full node that supports Xtreme Thinblocks.
* This is not supported by any currently-maintained
* Bitcoin node.
*/
val NODE_XTHIN: ServiceIdentifier = ServiceIdentifier(1 << 4)
/**
* This means the same as `NODE_NETWORK` with the limitation of only
* serving the last 288 (2 days) blocks
*
* @see [[https://github.com/bitcoin/bips/blob/master/bip-0159.mediawiki BIP159]]
* for details on how this is implemented.
*/
val NODE_NETWORK_LIMITED: ServiceIdentifier = ServiceIdentifier(1 << 10)
private case class ServiceIdentifierImpl(num: UInt64)
extends ServiceIdentifier
def fromBytes(bytes: ByteVector): ServiceIdentifier =
RawServiceIdentifierSerializer.read(bytes)
def apply(num: BigInt): ServiceIdentifier = ServiceIdentifier(UInt64(num))
def apply(uInt64: UInt64): ServiceIdentifier = uInt64 match {
case UInt64.zero => UnnamedService
case UInt64.one => NodeNetwork
case x: UInt64 => UnknownServiceImpl(x)
}
def apply(uInt64: UInt64): ServiceIdentifier = ServiceIdentifierImpl(uInt64)
}

View file

@ -8,16 +8,16 @@ import org.bitcoins.core.util.{CryptoUtil, Factory, NumberUtil}
import scodec.bits.ByteVector
/**
* Created by chris on 5/19/16.
* Nodes collect new transactions into a block, hash them into a hash tree,
* and scan through nonce values to make the block's hash satisfy proof-of-work
* requirements. When they solve the proof-of-work, they broadcast the block
* to everyone and the block is added to the block chain. The first transaction
* in the block is a special one that creates a new coin owned by the creator
* of the block.
* Bitcoin Developer reference link
* @see Bitcoin Developer reference:
* https://bitcoin.org/en/developer-reference#block-headers
* Bitcoin Core implementation:
*
* @see Bitcoin Core implementation:
* https://github.com/bitcoin/bitcoin/blob/master/src/primitives/block.h#L20
*/
sealed trait BlockHeader extends NetworkElement {
@ -25,7 +25,8 @@ sealed trait BlockHeader extends NetworkElement {
/**
* The block version number indicates which set of block validation rules to follow.
* See the list of block versions below.
* See BIP9 for more information on what version number signify
*
* @see BIP9 for more information on what version number signify
* https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki
*
* @return the version number for this block
@ -43,17 +44,18 @@ sealed trait BlockHeader extends NetworkElement {
/**
* Returns the big endian encoding of the previous block hash
* This is useful for using rpc and block exporers, but is NOT used in the protocol itself
* See this link for more info
*
* @see see this Stack Exchange question for more:
* [[https://bitcoin.stackexchange.com/questions/2063/why-does-the-bitcoin-protocol-use-the-little-endian-notation]]
* @return
*/
def previousBlockHashBE: DoubleSha256DigestBE = previousBlockHash.flip
/**
* A SHA256(SHA256()) hash in internal byte order.
* A `SHA256(SHA256())` hash in internal byte order.
* The merkle root is derived from the hashes of all transactions included in this block,
* ensuring that none of those transactions can be modified without modifying the header.
* https://bitcoin.org/en/developer-reference#merkle-trees
*
* @see https://bitcoin.org/en/developer-reference#merkle-trees
*
* @return the merkle root of the merkle tree
*/
@ -62,9 +64,9 @@ sealed trait BlockHeader extends NetworkElement {
/**
* Returns the merkle root hash in BIG ENDIAN format. This is not compatible with the bitcoin
* protocol but it is useful for rpc clients and block explorers
* See this link for more info
*
* @see this link for more info
* [[https://bitcoin.stackexchange.com/questions/2063/why-does-the-bitcoin-protocol-use-the-little-endian-notation]]
* @return
*/
def merkleRootHashBE: DoubleSha256DigestBE = merkleRootHash.flip
@ -79,10 +81,10 @@ sealed trait BlockHeader extends NetworkElement {
/**
* An encoded version of the target threshold this blocks header hash must be less than or equal to.
* See the nBits format described below.
*
* @see See the nBits format described below.
* https://bitcoin.org/en/developer-reference#target-nbits
*
* @return
*/
def nBits: UInt32
@ -130,7 +132,7 @@ sealed trait BlockHeader extends NetworkElement {
*/
object BlockHeader extends Factory[BlockHeader] {
private sealed case class BlockHeaderImpl(
sealed private case class BlockHeaderImpl(
version: Int32,
previousBlockHash: DoubleSha256Digest,
merkleRootHash: DoubleSha256Digest,

View file

@ -6,7 +6,8 @@
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/application.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{5}.%M\(%line\) - %msg%n</pattern>
<!-- UTC ISO8601 date format -->
<pattern>%date{"yyyy-MM-dd'T'HH:mm:ss,SSSXXX", UTC} %-5level %msg%n</pattern>
</encoder>
</appender>
@ -16,29 +17,65 @@
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE" />
<appender-ref ref="STDOUT" />
</root>
<!-- get rid of "Slf4jLogger started" messages -->
<logger name="akka.event.slf4j.Slf4jLogger" level="OFF" />
<!-- ╔═══════════════════════╗ -->
<!-- ║ Bitcoin-S logging ║-->
<!-- ╚═══════════════════════╝ -->
<!-- get rid of annoying warning messages from tests -->
<logger name="org.bitcoins.chain.validation" level="OFF" />
<!-- ╔═══════════════════╗ -->
<!-- ║ Configuration ║ -->
<!-- ╚═══════════════════╝ -->
<!-- inspect resolved DB connection -->
<logger name="org.bitcoins.db.SafeDatabase" level="INFO" />
<!-- inspect resolved config -->
<logger name="org.bitcoins.chain.config" level="INFO" />
<logger name="org.bitcoins.node.config" level="INFO" />
<logger name="org.bitcoins.wallet.config" level="INFO" />
<!-- ╔═════════════════╗ -->
<!-- ║ Node module ║ -->
<!-- ╚═════════════════╝ -->
<!-- See incoming message names and the peer it's sent from -->
<logger name="org.bitcoins.node.networking.peer.PeerMessageReceiver" level="INFO" />
<!-- inspect resolved db connection -->
<logger name="org.bitcoins.db.SafeDatabase" level="INFO" />
<!-- See outgoing message names and the peer it's sent to -->
<logger name="org.bitcoins.node.networking.peer.PeerMessageSender" level="INFO" />
<!-- Inspect handling of headers and inventory messages -->
<logger name="org.bitcoins.node.networking.peer.DataMessageHandler" level="INFO" />
<!-- inspect TCP details -->
<logger name="org.bitcoins.node.networking.Client" level="INFO" />
<!-- ╔════════════════════╗ -->
<!-- ║ Chain module ║ -->
<!-- ╚════════════════════╝ -->
<!-- See queries received by chain handler, as well as result of -->
<!-- connecting new block headers to chain -->
<logger name="org.bitcoins.chain.blockchain.ChainHandler" level="INFO" />
<!-- Set to TRACE to inspect details of chain vaidation -->
<logger name="org.bitcoins.chain.validation" level="INFO" />
<!-- ╔═════════════════════╗ -->
<!-- ║ Wallet module ║ -->
<!-- ╚═════════════════════╝ -->
<!-- ╔═══════════════════════════╗ -->
<!-- ║ Bitcoin-S logging end ║-->
<!-- ╚═══════════════════════════╝ -->
<!-- ╔═════════════════════════╗ -->
<!-- ║ External libraries ║ -->
<!-- ╚═════════════════════════╝ -->
<!-- see how long statements took to execute by setting to DEBUG -->
<logger name="slick.jdbc.JdbcBackend.benchmark" level="INFO"/>
@ -52,10 +89,11 @@
<!-- see what's returned by Slick -->
<logger name="slick.jdbc.StatementInvoker.result" level="INFO" />
<logger name="slick" level="INFO"/>
<!-- Get rid of messages like this:
Connection attempt failed. Backing off new connection
attempts for at least 800 milliseconds. -->
<logger name="akka.http.impl.engine.client.PoolGateway" level="OFF"/>
<!-- get rid of "Slf4jLogger started" messages -->
<logger name="akka.event.slf4j.Slf4jLogger" level="OFF" />
</included>

View file

@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 scalacoin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,16 +1,60 @@
# State of the world
# Bitcoin-S SPV node
Currently this project is a heavy WIP. The most important files are
This module is a Bitcoin SPV (simplified payment verification) node that peers
with a Bitcoin Core node over the P2P network. It syncs block headers and does
as much verification as possible with the data it has available.
- [`Client`](https://github.com/bitcoin-s/bitcoin-s-core/blob/node/node/src/main/scala/org/bitcoins/node/networking/Client.scala) - this handles all of the networking code. Currently this uses akka but the plan is to move away from akka in the future for compatability with other JVM based platforms
- [`PeerMessageReceiver`](https://github.com/bitcoin-s/bitcoin-s-core/blob/node/node/src/main/scala/org/bitcoins/node/networking/peer/PeerMessageReceiver.scala) - this handles messages we receive on the p2p network. The only messages that are currently handled are `VerackMessage` and `VersionMessage`. As this project get's built out this is where we need to add code for calling other subsystems that handle transactions, blocks, peer related information etc. All messages are algebraic data types, so we can easily pattern match on them and implement features in `PeerMessageReceiver.handleControlPayload` and `PeerMessageReceiver.handleDataPayload`
- [`PeerMessageReceiverState`](https://github.com/bitcoin-s/bitcoin-s-core/blob/node/node/src/main/scala/org/bitcoins/node/networking/peer/PeerMessageReceiverState.scala) - the states that our peer message receiver can be in. It transitions through these states during the connect/disconnect process with our peer.
- [`PeerMessageSender`](https://github.com/bitcoin-s/bitcoin-s-core/blob/node/node/src/main/scala/org/bitcoins/node/networking/peer/PeerMessageSender.scala) - this handles sending messages to our peer on the p2p network. Since we are lite client, we probably won't be sending a lot of messages to peers so this isn't that interesting.
- [`PeerHandler`](https://github.com/bitcoin-s/bitcoin-s-core/blob/node/node/src/main/scala/org/bitcoins/node/networking/peer/PeerHandler.scala) - this combines a `PeerMessageReceiver` and a `PeerMessageSender` into a pair.
- [`Peer`](https://github.com/bitcoin-s/bitcoin-s-core/blob/node/node/src/main/scala/org/bitcoins/node/models/Peer.scala) - The low level socket details need to connect to a peer
The node supports bloom filters, and provides optional callbacks that notify
consumers on events such as new blocks, filtered merkle blocks and transactions.
## Caveats:
There is still a lot of code commented out on the project, but the unit tests should pass for the ones that are not. Interesting unit tests are
1. This is a **heavy** work in progress, and should not be used for anything serious
yet
2. The node can only peer with one node on the P2P network right now, and that
node must be passed in on startup. Eventually we want to support peer discovery
through DNS seeds, as well as supporting multiple peers at the same time.
3. The majority of the P2P code was written in late 2017, and as a consequence does
not handle some of the newer P2P messages and functionality (including SegWit
related messages).
- [`ClientTest`](https://github.com/bitcoin-s/bitcoin-s-core/blob/node/node-test/src/test/scala/org/bitcoins/node/networking/ClientTest.scala) - currently tests that we can connect with peers
- [`PeerMessageHandlerTest`](https://github.com/bitcoin-s/bitcoin-s-core/blob/node/node-test/src/test/scala/org/bitcoins/node/networking/peer/PeerMessageHandlerTest.scala) - tests that we can get our node into the [`PeerMessageReceiverState.Normal`](https://github.com/bitcoin-s/bitcoin-s-core/blob/node/node/src/main/scala/org/bitcoins/node/networking/peer/PeerMessageReceiverState.scala#L150) state. This means we can send / receive arbitrary messages from our peer.
## Interesting files
Currently this project is a _heavy_ WIP. The most important files are
- [`Client`](src/main/scala/org/bitcoins/node/networking/Client.scala) - this handles
all of the networking code. Currently this uses Akka but the plan is to move away
from Akka in the future and use a networking library with a smaller classpath footprint.
- [`PeerMessageReceiver`](src/main/scala/org/bitcoins/node/networking/peer/PeerMessageReceiver.scala) - this handles messages we receive on the P2P network.
All messages are algebraic data types, so we can easily pattern match on them and
implement features in `PeerMessageReceiver.handleControlPayload` and
`PeerMessageReceiver.handleDataPayload`
- [`PeerMessageReceiverState`](src/main/scala/org/bitcoins/node/networking/peer/PeerMessageReceiverState.scala) - the states that our peer message receiver can be in.
It transitions through these states during the connect/disconnect process with our peer.
- [`PeerMessageSender`](src/main/scala/org/bitcoins/node/networking/peer/PeerMessageSender.scala) - this handles sending messages to our peer on the P2P network.
Since we are a light client, we probably won't be sending a lot of messages to peers
so this isn't that interesting.
- [`PeerHandler`](src/main/scala/org/bitcoins/node/networking/peer/PeerHandler.scala) - this combines a `PeerMessageReceiver` and a `PeerMessageSender` into a pair.
- [`Peer`](src/main/scala/org/bitcoins/node/models/Peer.scala) - The low level socket
details need to connect to a peer
## Interesting tests
There is still a lot of code commented out on the project, but the tests should
pass for the ones that are not. Interesting tests are
- [`ClientTest`](../node-test/src/test/scala/org/bitcoins/node/networking/ClientTest.scala) - currently tests that we can connect with peers
- [`PeerMessageHandlerTest`](../node-test/src/test/scala/org/bitcoins/node/networking/peer/PeerMessageHandlerTest.scala) - tests that we can get our node into the
[`PeerMessageReceiverState.Normal`](src/main/scala/org/bitcoins/node/networking/peer/PeerMessageReceiverState.scala)
state. This means we can send/receive arbitrary messages from our peer.
- [`SpvNodeTest`] - tests that we can peer with a `bitcoind` and sync a block header
## Main method
There's a main method available in
[`SpvNodeMain.scala`](src/main/scala/org/bitcoins/node/SpvNodeMain.scala). Currently
(June 17th, 2019) the node peers with a locally running `bitcoind`. It does not do
much interesting beyond that, although you can make it more interesting if you
modify the logging levels (look in
[common-logback.xml](../core/src/test/resources/common-logback.xml)) and pass in
some callbacks to the node on startup.

View file

@ -1,50 +0,0 @@
We use [Slick](http://slick.lightbend.com/) as our library for database bindings in bitcoins-spv-node. Slick offers numerous database bindings such as Postgres, MySQL, DB2 etc. Configuration for databases is specified inside of the [application.conf](https://github.com/bitcoin-s/bitcoin-s-spv-node/blob/master/src/main/resources/application.conf#L14-L63) file inside of src/main/resources. If you want to read more about the different configuration options for Slick the documentation is [here](http://slick.lightbend.com/doc/3.1.1/database.html).
Currently we have 4 databases that need to be created for using our application:
* bitcoins-spv-node
* bitcoins-spv-node-testnet3
* bitcoins-spv-node-regtest
* bitcoins-spv-node-unit-test
Note, that bitcoins-spv-node is for mainnet. Inside our codebase, we have a trait that represents a database binding called [DbConfig](https://github.com/bitcoin-s/bitcoin-s-spv-node/blob/master/src/main/scala/org/bitcoins/spvnode/constant/DbConfig.scala). This is passed around inside of our codebase to specify what database to use. It is best to just use [the configuration inside of Constants](https://github.com/bitcoin-s/bitcoin-s-spv-node/blob/master/src/main/scala/org/bitcoins/spvnode/constant/Constants.scala#L32) instead of passing around the objects inside of DbConfig. This eliminates the chance of having a situation where we have given the wrong database binding for the network we are currently on, for instance giving the TestNet3 database as an arguement when we are on mainnet.
Here is an example of creating a table in a database.
```scala
import org.bitcoins.node.constant.Constants
import org.bitcoins.node.models.BlockHeaderTable
import slick.driver.PostgresDriver.api._
import scala.concurrent.Await
import scala.concurrent.duration.DurationInt
object Main extends App {
override def main(args : Array[String]) = {
val table = TableQuery[BlockHeaderTable]
val db = Constants.database
//creates the table in the database
Await.result(db.run(table.schema.create),3.seconds)
db.close()
}
}
```
now if we wanted to drop that same table, we could use this snippet of code:
```scala
import org.bitcoins.node.constant.Constants
import org.bitcoins.node.models.BlockHeaderTable
import slick.driver.PostgresDriver.api._
import scala.concurrent.Await
import scala.concurrent.duration.DurationInt
object Main extends App {
override def main(args : Array[String]) = {
val table = TableQuery[BlockHeaderTable]
val db = Constants.database
//drops the table in the database
Await.result(db.run(table.schema.drop),3.seconds)
db.close()
}
}
```
If you want to see how a table is actually represented in Slick, you can look at how we model our BlockHeaderTable, which stores all headers on the network, [here](https://github.com/bitcoin-s/bitcoin-s-spv-node/blob/master/src/main/scala/org/bitcoins/spvnode/models/BlockHeaderTable.scala). For documentation on creating schemas for tables in Slick, you can look [here](http://slick.lightbend.com/doc/3.1.1/schemas.html).

View file

@ -1,61 +0,0 @@
### Syncing for the first time
To start syncing our block headers, we need to indicate a header to start at. Currently our library only supports syncing from the beginning of time, aka the genesis block. We have some logic inside of our storage mechanisms to handle the case of seeding the database with the genesis header, then each new row added must reference the previous row in the database (thus forming a blockchain).
Here is some example code to start syncing our spv node with all block headers on the network
```scala
package org.bitcoins.node
import akka.actor.ActorRef
import org.bitcoins.core.protocol.blockchain.TestNetChainParams
import org.bitcoins.node.constant.Constants
import org.bitcoins.node.models.BlockHeaderTable
import org.bitcoins.node.networking.sync.BlockHeaderSyncActor
import slick.driver.PostgresDriver.api._
import scala.concurrent.Await
import scala.concurrent.duration.DurationInt
/**
* Created by chris on 8/29/16.
*/
object Main extends App {
override def main(args : Array[String]) = {
//creates the 'block_headers' table, if it exists alread you can remove these 4 lines
val table = TableQuery[BlockHeaderTable]
val db = Constants.database
Await.result(Constants.database.run(table.schema.create),3.seconds)
db.close()
//create a BlockHeaderSyncActor
val blockHeaderSyncActor: ActorRef = BlockHeaderSyncActor(Constants.actorSystem, Constants.dbConfig, Constants.networkParameters)
val genesisBlockHash = TestNetChainParams.genesisBlock.blockHeader.hash
//indicates to start the header sync at the genesis hash
val startHeader = BlockHeaderSyncActor.StartHeaders(Seq(genesisBlockHash))
//send the block header sync actor a message indicating to start the sync
blockHeaderSyncActor ! startHeader
}
}
```
You will start receiving block headers from a node on the peer to peer network, and those headers will be saved in persistent storage, for more information persistent storage read the [database doc](https://github.com/Christewart/bitcoin-s-spv-node/blob/database_documentation/doc/database_setup.md).
### Syncing after being off for awhile
Another scenario users can have is that they have powered down their node for awhile, and want to sync all blockheaders from the network to get the current state of the blockchain. You can do this with this code
```scala
package org.bitcoins.node
import org.bitcoins.node.constant.Constants
import org.bitcoins.node.networking.sync.BlockHeaderSyncActor
object Main extends App {
override def main(args : Array[String]) = {
val blockHeaderSyncActor = BlockHeaderSyncActor(Constants.actorSystem, Constants.dbConfig, Constants.networkParameters)
blockHeaderSyncActor ! BlockHeaderSyncActor.StartAtLastSavedHeader
}
}
```
Once the sync is complete, your actor will receive a [BlockHeaderSyncActor.SuccessfulSyncReply](https://github.com/bitcoin-s/bitcoin-s-spv-node/blob/master/src/main/scala/org/bitcoins/spvnode/networking/sync/BlockHeaderSyncActor.scala#L244) message indicating the sync was successful.

View file

@ -1,36 +0,0 @@
import sbt._
import Keys._
object BitcoinSSpvNodeBuild extends Build {
val appName = "bitcoins-spv-node"
val appV = "0.0.1"
val scalaV = "2.11.7"
val organization = "org.bitcoins.node"
val slf4jV = "1.7.5"
val logbackV = "1.0.13"
val akkaV = "2.4.7"
val slickV = "3.1.1"
/* val appDependencies = Seq(
"org.scalatest" % "scalatest_2.11" % "2.2.0",
"com.typesafe.akka" %% "akka-actor" % akkaV withSources() withJavadoc(),
"com.typesafe.akka" %% "akka-testkit" % akkaV withSources() withJavadoc(),
"ch.qos.logback" % "logback-classic" % logbackV,
"joda-time" % "joda-time" % "2.9.4",
("com.typesafe.akka" %% "akka-slf4j" % akkaV withSources() withJavadoc()).exclude("org.slf4j", "slf4j-api"),
"com.typesafe.slick" %% "slick" % slickV withSources() withJavadoc(),
"com.typesafe.slick" %% "slick-hikaricp" % "3.1.1",
"org.postgresql" % "postgresql" % "9.4.1210"
)*/
lazy val root = Project(appName, file(".")).settings(
version := appV,
scalaVersion := scalaV,
resolvers += Resolver.sonatypeRepo("releases"),
libraryDependencies ++= appDependencies,
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature"),
mainClass := Some("org.bitcoins.node.Main"),
parallelExecution in Test := false,
//hints for testOptions config here: http://stackoverflow.com/questions/7237824/how-can-i-get-complete-stacktraces-for-exceptions-thrown-in-tests-when-using-sbt
testOptions in Test += Tests.Argument("-oF")
)
}

View file

@ -0,0 +1,99 @@
<included>
<!-- Stop output INFO at start -->
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/application.log</file>
<encoder>
<!-- UTC ISO8601 date format -->
<pattern>%date{"yyyy-MM-dd'T'HH:mm:ss,SSSXXX", UTC} %-5level %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %level %logger{0} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE" />
<appender-ref ref="STDOUT" />
</root>
<!-- ╔═══════════════════════╗ -->
<!-- ║ Bitcoin-S logging ║-->
<!-- ╚═══════════════════════╝ -->
<!-- ╔═══════════════════╗ -->
<!-- ║ Configuration ║ -->
<!-- ╚═══════════════════╝ -->
<!-- inspect resolved DB connection -->
<logger name="org.bitcoins.db.SafeDatabase" level="INFO" />
<!-- inspect resolved config -->
<logger name="org.bitcoins.chain.config" level="INFO" />
<logger name="org.bitcoins.node.config" level="INFO" />
<logger name="org.bitcoins.wallet.config" level="INFO" />
<!-- ╔═════════════════╗ -->
<!-- ║ Node module ║ -->
<!-- ╚═════════════════╝ -->
<!-- See incoming message names and the peer it's sent from -->
<logger name="org.bitcoins.node.networking.peer.PeerMessageReceiver" level="INFO" />
<!-- See outgoing message names and the peer it's sent to -->
<logger name="org.bitcoins.node.networking.peer.PeerMessageSender" level="INFO" />
<!-- Inspect handling of headers and inventory messages -->
<logger name="org.bitcoins.node.networking.peer.DataMessageHandler" level="INFO" />
<!-- inspect TCP details -->
<logger name="org.bitcoins.node.networking.Client" level="INFO" />
<!-- ╔════════════════════╗ -->
<!-- ║ Chain module ║ -->
<!-- ╚════════════════════╝ -->
<!-- See queries received by chain handler, as well as result of -->
<!-- connecting new block headers to chain -->
<logger name="org.bitcoins.chain.blockchain.ChainHandler" level="INFO" />
<!-- Set to TRACE to inspect details of chain vaidation -->
<logger name="org.bitcoins.chain.validation" level="INFO" />
<!-- ╔═════════════════════╗ -->
<!-- ║ Wallet module ║ -->
<!-- ╚═════════════════════╝ -->
<!-- ╔═══════════════════════════╗ -->
<!-- ║ Bitcoin-S logging end ║-->
<!-- ╚═══════════════════════════╝ -->
<!-- ╔═════════════════════════╗ -->
<!-- ║ External libraries ║ -->
<!-- ╚═════════════════════════╝ -->
<!-- see how long statements took to execute by setting to DEBUG -->
<logger name="slick.jdbc.JdbcBackend.benchmark" level="INFO"/>
<!-- see what statements are executed by setting to DEBUG -->
<logger name="slick.jdbc.JdbcBackend.statement" level="INFO"/>
<!-- see what slick is compiling to in sql -->
<logger name="slick.compiler" level="INFO"/>
<!-- see what's returned by Slick -->
<logger name="slick.jdbc.StatementInvoker.result" level="INFO" />
<!-- Get rid of messages like this:
Connection attempt failed. Backing off new connection
attempts for at least 800 milliseconds. -->
<logger name="akka.http.impl.engine.client.PoolGateway" level="OFF"/>
<!-- get rid of "Slf4jLogger started" messages -->
<logger name="akka.event.slf4j.Slf4jLogger" level="OFF" />
</included>

View file

@ -0,0 +1,7 @@
<configuration>
<!-- Although this file is on the core project classpath,
we exclude all XML files from being included in published
artifacts. This way users of Bitcoin-S won't get our config
pushed on them. -->
<include resource="common-logback.xml" />
</configuration>

View file

@ -1,55 +0,0 @@
package org.bitcoins.node
import java.net.{InetAddress, InetSocketAddress}
import org.bitcoins.chain.blockchain.ChainHandler
import org.bitcoins.chain.config.ChainAppConfig
import org.bitcoins.chain.models.{BlockHeaderDAO, BlockHeaderTable}
import org.bitcoins.core.p2p.NetworkIpAddress
import org.bitcoins.core.util.BitcoinSLogger
import org.bitcoins.node.config.NodeAppConfig
import org.bitcoins.node.constant.Constants
import org.bitcoins.node.models.Peer
import slick.jdbc.SQLiteProfile.api._
import scala.concurrent._
import scala.concurrent.duration._
import scala.util.{Failure, Success}
object Main extends App with BitcoinSLogger {
implicit val system = Constants.actorSystem
import system.dispatcher
implicit val appconfig = NodeAppConfig()
implicit val chainAppConfig = ChainAppConfig()
logger.info(s"Chain config: ${chainAppConfig.dbConfig.config}")
val bhDAO = BlockHeaderDAO()
val chainApi = ChainHandler(bhDAO, chainAppConfig)
val table = TableQuery[BlockHeaderTable]
logger.info(s"Creating block header table")
val chainInitF = chainAppConfig.initialize
Await.result(chainInitF, 3.seconds)
logger.info(s"Creating block header table: done")
val socket = new InetSocketAddress(InetAddress.getLoopbackAddress, 18333)
val nip = NetworkIpAddress.fromInetSocketAddress(socket)
val peer = Peer(nip)
logger.info(s"Starting spv node")
val spvNodeF = SpvNode(peer, chainApi, bloomFilter = ???).start()
logger.info(s"Starting SPV node sync")
spvNodeF.map { spvNode =>
spvNode.sync().onComplete {
case Failure(exception) =>
logger.error(s"Could not sync SPV node!")
exception.printStackTrace()
sys.exit(1)
case Success(_) =>
logger.info(s"Started syncing SPV node successfully")
}
}
}

View file

@ -62,7 +62,7 @@ case class SpvNode(
AsyncUtil.retryUntilSatisfied(peerMsgRecv.isInitialized)
isInitializedF.failed.foreach(err =>
logger.error(s"Failed to conenct with peer=$peer with err=${err}"))
logger.error(s"Failed to connect with peer=$peer with err=${err}"))
isInitializedF.map { _ =>
logger.info(s"Our peer=${peer} has been initialized")
@ -102,8 +102,14 @@ case class SpvNode(
* @return
*/
def sync(): Future[Unit] = {
chainApi.getBestBlockHash.map { hashBE: DoubleSha256DigestBE =>
peerMsgSender.sendGetHeadersMessage(hashBE.flip)
for {
hash <- chainApi.getBestBlockHash
header <- chainApi
.getHeader(hash)
.map(_.get) // .get is safe since this is an internal call
} yield {
peerMsgSender.sendGetHeadersMessage(hash.flip)
logger.info(s"Starting sync node, height=${header.height} hash=$hash")
}
}
}

View file

@ -0,0 +1,80 @@
package org.bitcoins.node
import org.bitcoins.chain.blockchain.ChainHandler
import org.bitcoins.chain.config.ChainAppConfig
import org.bitcoins.chain.models.BlockHeaderDAO
import org.bitcoins.core.util.BitcoinSLogger
import org.bitcoins.node.config.NodeAppConfig
import org.bitcoins.node.constant.Constants
import org.bitcoins.node.models.Peer
import scala.concurrent._
import scala.concurrent.duration._
import scala.util.{Failure, Success}
import org.bitcoins.rpc.config.BitcoindInstance
import java.net.InetSocketAddress
import org.bitcoins.core.bloom.BloomFilter
import org.bitcoins.core.bloom.BloomUpdateAll
object SpvNodeMain extends App with BitcoinSLogger {
implicit val system = Constants.actorSystem
import system.dispatcher
// TODO: Make BitcoinSAppConfig available in main sources
// somehow, use this here
implicit val nodeConf = NodeAppConfig()
implicit val chainConf = ChainAppConfig()
val bhDAO = BlockHeaderDAO()
val chainApi = ChainHandler(bhDAO, chainConf)
val _ = {
logger.info(s"Initializing chain and node")
val initF =
Future.sequence(List(nodeConf.initialize(), chainConf.initialize()))
Await.result(initF, 3.seconds)
logger.info(s"Initializing chain and node: done")
}
val peer = {
val bitcoind = BitcoindInstance.fromDatadir()
if (bitcoind.network != nodeConf.network) {
sys.error(
s"Node (${nodeConf.network}) and bitcoind (${bitcoind.network}) is running on different networks!")
}
logger.info(
s"Connecting to bitcoind running on ${bitcoind.network} at ${bitcoind.uri} ")
val socket =
new InetSocketAddress(bitcoind.uri.getHost(), bitcoind.p2pPort)
Peer(socket)
}
logger.info(s"Starting SPV node")
val emptyBloom =
BloomFilter(numElements = 1, falsePositiveRate = 1, flags = BloomUpdateAll)
val spvNodeF = SpvNode(peer, chainApi, emptyBloom).start()
val getHeight: Runnable = new Runnable {
def run: Unit =
spvNodeF
.flatMap(_.chainApi.getBlockCount)
.foreach(count => logger.debug(s"SPV block height: $count"))
}
val interval = 30.seconds
system.scheduler.schedule(interval, interval, getHeight)
spvNodeF.map { spvNode =>
spvNode.sync().onComplete {
case Failure(exception) =>
logger.error(s"Could not sync SPV node!", exception)
sys.exit(1)
case Success(_) =>
logger.info(s"Started syncing SPV node successfully")
}
}
}

View file

@ -2,34 +2,25 @@ package org.bitcoins.node.models
import java.net.InetSocketAddress
import org.bitcoins.core.p2p.NetworkIpAddress
import org.bitcoins.db.DbRowAutoInc
import org.bitcoins.rpc.config.BitcoindInstance
case class Peer(networkIpAddress: NetworkIpAddress, id: Option[Long] = None)
case class Peer(socket: InetSocketAddress, id: Option[Long] = None)
extends DbRowAutoInc[Peer] {
def socket: InetSocketAddress =
new InetSocketAddress(networkIpAddress.address, networkIpAddress.port)
override def copyWithId(id: Long): Peer = {
this.copy(id = Some(id))
}
override def toString(): String =
s"Peer(${networkIpAddress.address}:${networkIpAddress.port})"
s"Peer(${socket.getAddress()}:${socket.getPort()})"
}
object Peer {
def fromNetworkIpAddress(networkIpAddress: NetworkIpAddress): Peer = {
Peer(networkIpAddress)
}
def fromSocket(socket: InetSocketAddress): Peer = {
val nip = NetworkIpAddress.fromInetSocketAddress(socket = socket)
fromNetworkIpAddress(nip)
Peer(socket)
}
/**

View file

@ -37,13 +37,20 @@ class DataMessageHandler(callbacks: SpvNodeCallbacks)(
payload match {
case headersMsg: HeadersMessage =>
logger.trace(
s"Received headers message with ${headersMsg.count.toInt} headers")
val headers = headersMsg.headers
val chainApi: ChainApi =
ChainHandler(blockHeaderDAO, chainConfig = appConfig)
val chainApiF = chainApi.processHeaders(headers)
chainApiF.map { _ =>
val lastHash = headers.last.hash
chainApiF.map { newApi =>
val lastHeader = headers.last
val lastHash = lastHeader.hash
newApi.getBlockCount.map { count =>
logger.trace(
s"Processed headers, most recent has height=$count and hash=$lastHash.")
}
peerMsgSender.sendGetHeadersMessage(lastHash)
}
case msg: BlockMessage =>

View file

@ -65,8 +65,6 @@ class PeerMessageReceiver(
val _ = toState(newState)
logger.debug(s"new state ${internalState}")
logger.debug(s"isConnected=${isConnected}")
val peerMsgSender = PeerMessageSender(client, chainAppConfig.network)
peerMsgSender.sendVersionMessage()
@ -159,8 +157,8 @@ class PeerMessageReceiver(
payload match {
case versionMsg: VersionMessage =>
logger.debug(
s"Received version message from peer=${peerOpt.get} msg=${versionMsg}")
logger.trace(
s"Received versionMsg=${versionMsg}from peer=${peerOpt.get}")
internalState match {
case bad @ (_: Disconnected | _: Normal | Preconnection) =>
@ -181,8 +179,6 @@ class PeerMessageReceiver(
}
case VerAckMessage =>
logger.debug(s"Received verack message from peer=${peerOpt.get}")
internalState match {
case bad @ (_: Disconnected | _: Normal | Preconnection) =>
Failure(
@ -194,7 +190,8 @@ class PeerMessageReceiver(
Success(())
}
case _: PingMessage =>
case ping: PingMessage =>
sender.sendPong(ping)
Success(())
case SendHeadersMessage =>
//not implemented as of now
@ -214,6 +211,8 @@ class PeerMessageReceiver(
}
private def toState(state: PeerMessageReceiverState): Unit = {
logger.debug(
s"PeerMessageReceiver changing state, oldState=$internalState, newState=$state")
internalState = state
}
}

View file

@ -1,6 +1,5 @@
package org.bitcoins.node.networking.peer
import org.bitcoins.core.p2p.NetworkMessage
import org.bitcoins.core.util.BitcoinSLogger
import org.bitcoins.core.p2p.{VerAckMessage, VersionMessage}
import org.bitcoins.node.networking.Client
@ -141,6 +140,8 @@ object PeerMessageReceiverState {
verackMsgP = verackMsgP.success(verAckMessage)
)
}
override def toString: String = "Initializing"
}
/** This represents a [[org.bitcoins.node.networking.peer.PeerMessageReceiverState]]
@ -159,6 +160,8 @@ object PeerMessageReceiverState {
require(
isInitialized,
s"We cannot have a PeerMessageReceiverState.Normal if the Peer is not initialized")
override def toString: String = "Normal"
}
case class Disconnected(
@ -170,6 +173,9 @@ object PeerMessageReceiverState {
require(
isDisconnected,
"We cannot be in the disconnected state if a peer is not disconnected")
override def toString: String = "Disconnected"
}
def fresh(): PeerMessageReceiverState.Preconnection.type = {

View file

@ -28,6 +28,7 @@ class PeerMessageSender(client: Client)(implicit np: NetworkParameters)
/** Sends a [[org.bitcoins.node.messages.VersionMessage VersionMessage]] to our peer */
def sendVersionMessage(): Unit = {
val versionMsg = VersionMessage(client.peer.socket, np)
logger.trace(s"Sending versionMsg=$versionMsg to peer=${client.peer}")
sendMsg(versionMsg)
}
@ -36,8 +37,16 @@ class PeerMessageSender(client: Client)(implicit np: NetworkParameters)
sendMsg(verackMsg)
}
/** Responds to a ping message */
def sendPong(ping: PingMessage): Unit = {
val pong = PongMessage(ping.nonce)
logger.trace(s"Sending pong=$pong to peer=${client.peer}")
sendMsg(pong)
}
def sendGetHeadersMessage(lastHash: DoubleSha256Digest): Unit = {
val headersMsg = GetHeadersMessage(lastHash)
logger.trace(s"Sending getheaders=$headersMsg to peer=${client.peer}")
sendMsg(headersMsg)
}
@ -47,8 +56,7 @@ class PeerMessageSender(client: Client)(implicit np: NetworkParameters)
}
private[node] def sendMsg(msg: NetworkPayload): Unit = {
logger.debug(
s"PeerMessageSender sending to peer=${socket} msg=${msg.commandName}")
logger.debug(s"Sending msg=${msg.commandName} to peer=${socket}")
val newtworkMsg = NetworkMessage(np, msg)
client.actor ! newtworkMsg
}

View file

@ -3,8 +3,6 @@ import org.scalacheck.Gen
import org.bitcoins.core.p2p.NetworkIpAddress
import org.bitcoins.testkit.core.gen.NumberGenerator
import org.bitcoins.core.p2p.ServiceIdentifier
import org.bitcoins.core.p2p.NodeNetwork
import org.bitcoins.core.p2p.UnnamedService
import java.net.InetAddress
import org.bitcoins.core.p2p.NetworkPayload
@ -41,11 +39,7 @@ object P2PGenerator {
def serviceIdentifier: Gen[ServiceIdentifier] = {
for {
unknown <- NumberGenerator.uInt64.suchThat(u64 =>
u64 != NodeNetwork.num || u64 != UnnamedService.num)
service <- Gen.oneOf(NodeNetwork,
UnnamedService,
ServiceIdentifier(unknown))
} yield service
num <- NumberGenerator.uInt64
} yield ServiceIdentifier(num)
}
}

View file

@ -100,8 +100,7 @@ abstract class NodeTestUtil extends BitcoinSLogger {
* corresponds to [[org.bitcoins.rpc.client.common.BitcoindRpcClient]] */
def getBitcoindPeer(bitcoindRpcClient: BitcoindRpcClient): Peer = {
val socket = getBitcoindSocketAddress(bitcoindRpcClient)
val networkIpAddress = NetworkIpAddress.fromInetSocketAddress(socket)
Peer.fromNetworkIpAddress(networkIpAddress)
Peer(socket)
}
/** Checks if the given SPV node and bitcoind is synced */

View file

@ -95,8 +95,7 @@ trait NodeUnitTest
def createPeer(bitcoind: BitcoindRpcClient): Peer = {
val socket = peerSocketAddress(bitcoind)
val nip = NetworkIpAddress.fromInetSocketAddress(socket)
Peer(id = None, networkIpAddress = nip)
Peer(id = None, socket = socket)
}
def createSpvNode(bitcoind: BitcoindRpcClient): Future[SpvNode] = {