mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-13 11:35:40 +01:00
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:
parent
5ed0f6d35b
commit
02560ebb5f
33 changed files with 634 additions and 369 deletions
12
build.sbt
12
build.sbt
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 block’s 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,
|
||||
|
|
|
@ -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>
|
||||
|
|
22
node/LICENSE
22
node/LICENSE
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
|
@ -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).
|
||||
|
|
@ -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.
|
||||
|
|
@ -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")
|
||||
)
|
||||
}
|
99
node/src/main/resources/common-logback.xml
Normal file
99
node/src/main/resources/common-logback.xml
Normal 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>
|
7
node/src/main/resources/logback.xml
Normal file
7
node/src/main/resources/logback.xml
Normal 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>
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
80
node/src/main/scala/org/bitcoins/node/SpvNodeMain.scala
Normal file
80
node/src/main/scala/org/bitcoins/node/SpvNodeMain.scala
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 =>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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] = {
|
||||
|
|
Loading…
Add table
Reference in a new issue