diff --git a/core-test/README.md b/core-test/README.md index d4e4fb247f..d69140d7fd 100644 --- a/core-test/README.md +++ b/core-test/README.md @@ -1,25 +1,2 @@ -[ ![Download](https://api.bintray.com/packages/bitcoin-s/bitcoin-s-core/bitcoin-s-core-test/images/download.svg) ](https://bintray.com/bitcoin-s/bitcoin-s-core/bitcoin-s-core-test/_latestVersion) - -## Running tests - -To run the entire `core` test suite: - -```bash -chris@chris:~/dev/bitcoins-core$ bloop test coreTest -``` - -## Coverage - -To produce a report that quantifies how much of our code is covered by tests: - -```bash -sbt -> coverage -> coreTest/test -> core/coverageReport -``` - -This generates three different reports: Cobertura, XML and HTML formats. -See the output of your sbt shell to find the location of them. -Open up the HTML file in your browser. You'll now see code coverage -of all files in `core` project. \ No newline at end of file +See our [contributing guide](https://bitcoin-s.org/docs/contributing) for information +on how to benchmark, test and develop your code. diff --git a/core-test/src/test/scala/org/bitcoins/core/crypto/ECPublicKeyTest.scala b/core-test/src/test/scala/org/bitcoins/core/crypto/ECPublicKeyTest.scala index 802efc83a3..4fce2f344c 100644 --- a/core-test/src/test/scala/org/bitcoins/core/crypto/ECPublicKeyTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/crypto/ECPublicKeyTest.scala @@ -2,14 +2,12 @@ package org.bitcoins.core.crypto import org.bitcoinj.core.Sha256Hash import org.bitcoins.testkit.core.gen.CryptoGenerators +import org.bitcoins.testkit.util.BitcoinSUnitTest import org.scalatest.prop.PropertyChecks import org.scalatest.{FlatSpec, MustMatchers} -import scodec.bits.ByteVector +import scodec.bits._ -/** - * Created by chris on 2/29/16. - */ -class ECPublicKeyTest extends FlatSpec with MustMatchers { +class ECPublicKeyTest extends BitcoinSUnitTest { "ECPublicKey" must "verify that a arbitrary piece of data was signed by the private key corresponding to a public key" in { @@ -58,6 +56,28 @@ class ECPublicKeyTest extends FlatSpec with MustMatchers { bitcoinsSignature.bytes.toArray) must be(true) } + it must "be able to decompress keys" in { + val uncompressed = + ECPublicKey( + hex"044f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa385b6b1b8ead809ca67454d9683fcf2ba03456d6fe2c4abe2b07f0fbdbb2f1c1") + val compressed = + ECPublicKey( + hex"034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa") + + assert(uncompressed.isFullyValid) + assert(compressed.isFullyValid) + + assert(compressed.isCompressed) + assert(!uncompressed.isCompressed) + + assert(compressed.decompressed == uncompressed) + assert(uncompressed.decompressed == uncompressed) + } + + it must "generate unique keys" in { + assert(ECPublicKey() != ECPublicKey()) + } + it must "have serialization symmetry from ECPublicKey -> ECPoint -> ECPublicKey" in { PropertyChecks.forAll(CryptoGenerators.publicKey) { pubKey => val p = pubKey.toPoint diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/AddrMessageTest.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/AddrMessageTest.scala new file mode 100644 index 0000000000..90a3b0280e --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/AddrMessageTest.scala @@ -0,0 +1,15 @@ +package org.bitcoins.core.p2p + +import org.bitcoins.testkit.util.BitcoinSUnitTest +import org.bitcoins.testkit.core.gen.p2p.DataMessageGenerator +import org.bitcoins.testkit.core.gen.p2p.ControlMessageGenerator + +class AddrMessageTest extends BitcoinSUnitTest { + it must "have serialization symmetry" in { + forAll(ControlMessageGenerator.addrMessage) { addr => + val fromBytes = AddrMessage.fromBytes(addr.bytes) + // assert(fromBytes == addr) todo + assert(true) + } + } +} diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/BlockMessageTest.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/BlockMessageTest.scala new file mode 100644 index 0000000000..c4a6700bdc --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/BlockMessageTest.scala @@ -0,0 +1,12 @@ +package org.bitcoins.core.p2p + +import org.bitcoins.testkit.util.BitcoinSUnitTest +import org.bitcoins.testkit.core.gen.p2p.DataMessageGenerator + +class BlockMessageTest extends BitcoinSUnitTest { + it must "have serialization symmetry" in { + forAll(DataMessageGenerator.blockMessage) { block => + assert(block == BlockMessage.fromBytes(block.bytes)) + } + } +} diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/FeeFilterMessageTest.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/FeeFilterMessageTest.scala new file mode 100644 index 0000000000..fcbd00c17e --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/FeeFilterMessageTest.scala @@ -0,0 +1,12 @@ +package org.bitcoins.core.p2p + +import org.bitcoins.testkit.util.BitcoinSUnitTest +import org.bitcoins.testkit.core.gen.p2p.ControlMessageGenerator + +class FeeFilterMessageTest extends BitcoinSUnitTest { + it must "have serialization symmetry" in { + forAll(ControlMessageGenerator.feeFilterMessage) { fee => + assert(FeeFilterMessage.fromBytes(fee.bytes) == fee) + } + } +} diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/FilterAddMessageSpec.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/FilterAddMessageTest.scala similarity index 64% rename from core-test/src/test/scala/org/bitcoins/core/p2p/FilterAddMessageSpec.scala rename to core-test/src/test/scala/org/bitcoins/core/p2p/FilterAddMessageTest.scala index 439bf56e96..45cd3a8703 100644 --- a/core-test/src/test/scala/org/bitcoins/core/p2p/FilterAddMessageSpec.scala +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/FilterAddMessageTest.scala @@ -1,12 +1,9 @@ package org.bitcoins.core.p2p -import org.bitcoins.testkit.gen.ControlMessageGenerator +import org.bitcoins.testkit.core.gen.p2p.ControlMessageGenerator import org.bitcoins.testkit.util.BitcoinSUnitTest -/** - * Created by chris on 8/26/16. - */ -class FilterAddMessageSpec extends BitcoinSUnitTest { +class FilterAddMessageTest extends BitcoinSUnitTest { it must "have serialization symmetry" in { forAll(ControlMessageGenerator.filterAddMessage) { filterAddMsg => diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/FilterLoadMessageTest.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/FilterLoadMessageTest.scala new file mode 100644 index 0000000000..b6dd577648 --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/FilterLoadMessageTest.scala @@ -0,0 +1,68 @@ +package org.bitcoins.core.p2p + +import org.bitcoins.testkit.core.gen.p2p.ControlMessageGenerator +import org.bitcoins.testkit.util.BitcoinSUnitTest +import org.bitcoins.core.protocol.CompactSizeUInt +import org.bitcoins.core.number.UInt64 +import scodec.bits._ +import org.bitcoins.core.number.UInt32 +import org.bitcoins.core.bloom.BloomUpdateAll +import org.bitcoins.core.bloom.BloomFlag +import org.bitcoins.core.bloom.BloomFilter + +class FilterLoadMessageTest extends BitcoinSUnitTest { + + it must "have serialization symmetry" in { + forAll(ControlMessageGenerator.filterLoadMessage) { filterMsg => + assert(FilterLoadMessage(filterMsg.hex) == filterMsg) + } + } + + it must "throw on too large filters" in { + intercept[IllegalArgumentException] { + val size = CompactSizeUInt(UInt64(36001)) + val data = ByteVector.empty + val hashFuncs = UInt32.one + val tweak = UInt32.zero + val flags = BloomUpdateAll + val bloom = BloomFilter(filterSize = size, + data = data, + hashFuncs = hashFuncs, + tweak = tweak, + flags = flags) + FilterLoadMessage(bloom) + } + } + + it must "throw on too many hashfuncs" in { + intercept[IllegalArgumentException] { + val size = CompactSizeUInt(UInt64(36000)) + val data = ByteVector.empty + val hashFuncs = UInt32(51) + val tweak = UInt32.zero + val flags = BloomUpdateAll + val bloom = BloomFilter(filterSize = size, + data = data, + hashFuncs = hashFuncs, + tweak = tweak, + flags = flags) + FilterLoadMessage(bloom) + } + } + + it must "throw on size discrepancy" in { + intercept[IllegalArgumentException] { + val size = CompactSizeUInt(UInt64(36000)) + val data = ByteVector.empty + val hashFuncs = UInt32.one + val tweak = UInt32.zero + val flags = BloomUpdateAll + val bloom = BloomFilter(filterSize = size, + data = data, + hashFuncs = hashFuncs, + tweak = tweak, + flags = flags) + FilterLoadMessage(bloom) + } + } +} diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/GetBlocksMessageTest.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/GetBlocksMessageTest.scala new file mode 100644 index 0000000000..0af7e89b11 --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/GetBlocksMessageTest.scala @@ -0,0 +1,12 @@ +package org.bitcoins.core.p2p + +import org.bitcoins.testkit.core.gen.p2p.DataMessageGenerator +import org.bitcoins.testkit.util.BitcoinSUnitTest + +class GetBlocksMessageTest extends BitcoinSUnitTest { + it must "have serialization symmetry" in { + forAll(DataMessageGenerator.getBlocksMessage) { getBlocks => + assert(getBlocks == GetBlocksMessage.fromBytes(getBlocks.bytes)) + } + } +} diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/GetDataMessageTest.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/GetDataMessageTest.scala new file mode 100644 index 0000000000..68eb29f5a5 --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/GetDataMessageTest.scala @@ -0,0 +1,26 @@ +package org.bitcoins.core.p2p + +import org.bitcoins.testkit.core.gen.p2p.DataMessageGenerator +import org.bitcoins.testkit.util.BitcoinSUnitTest +import org.scalacheck.Gen +import org.bitcoins.core.crypto.DoubleSha256Digest + +class GetDataMessageTest extends BitcoinSUnitTest { + + it must "have serialization symmetry" in { + forAll(DataMessageGenerator.getDataMessages) { dataMsg => + assert(GetDataMessage(dataMsg.hex) == dataMsg) + } + } + + it must "be constructable from inventories" in { + forAll(DataMessageGenerator.getDataMessages) { getData => + assert(GetDataMessage(getData.inventories) == getData) + } + } + + it must "be constructable from a single inventory" in { + val inventory = Inventory(TypeIdentifier.MsgBlock, DoubleSha256Digest.empty) + assert(GetDataMessage(inventory) == GetDataMessage(Seq(inventory))) + } +} diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/GetHeadersMessageTest.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/GetHeadersMessageTest.scala new file mode 100644 index 0000000000..59caa433ff --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/GetHeadersMessageTest.scala @@ -0,0 +1,33 @@ +package org.bitcoins.core.p2p + +import org.bitcoins.testkit.core.gen.p2p.DataMessageGenerator +import org.bitcoins.testkit.util.BitcoinSUnitTest +import org.bitcoins.core.crypto.DoubleSha256Digest +import org.bitcoins.testkit.core.gen.CryptoGenerators + +class GetHeadersMessageTest extends BitcoinSUnitTest { + + it must "have serialization symmetry" in { + forAll(DataMessageGenerator.getHeaderMessages) { headerMsg => + assert(GetHeadersMessage(headerMsg.hex) == headerMsg) + } + } + + it must "be constructable from just hashes" in { + forAll(DataMessageGenerator.getHeaderDefaultProtocolMessage) { getHeader => + assert( + GetHeadersMessage(getHeader.hashes, getHeader.hashStop) == getHeader) + } + } + + it must "be constructable without a stop" in { + def getHash: DoubleSha256Digest = + CryptoGenerators.doubleSha256Digest.sample.getOrElse(getHash) + val msg = GetHeadersMessage(List.fill(10)(getHash)) + assert(msg.hashStop == DoubleSha256Digest.empty) + + val hash = getHash + val otherMsg = GetHeadersMessage(hash) + assert(otherMsg == GetHeadersMessage(Vector(hash))) + } +} diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/HeadersMessageSpec.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/HeadersMessageTest.scala similarity index 69% rename from core-test/src/test/scala/org/bitcoins/core/p2p/HeadersMessageSpec.scala rename to core-test/src/test/scala/org/bitcoins/core/p2p/HeadersMessageTest.scala index 98f1b2fcde..f71dcbca2c 100644 --- a/core-test/src/test/scala/org/bitcoins/core/p2p/HeadersMessageSpec.scala +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/HeadersMessageTest.scala @@ -1,9 +1,9 @@ package org.bitcoins.core.p2p -import org.bitcoins.testkit.gen.DataMessageGenerator +import org.bitcoins.testkit.core.gen.p2p.DataMessageGenerator import org.bitcoins.testkit.util.BitcoinSUnitTest -class HeadersMessageSpec extends BitcoinSUnitTest { +class HeadersMessageTest extends BitcoinSUnitTest { it must "have serialization symmetry" in { forAll(DataMessageGenerator.headersMessage) { headersMsg => diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/InventoryMessageSpec.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/InventoryMessageTest.scala similarity index 69% rename from core-test/src/test/scala/org/bitcoins/core/p2p/InventoryMessageSpec.scala rename to core-test/src/test/scala/org/bitcoins/core/p2p/InventoryMessageTest.scala index a4645815cd..3296150099 100644 --- a/core-test/src/test/scala/org/bitcoins/core/p2p/InventoryMessageSpec.scala +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/InventoryMessageTest.scala @@ -1,9 +1,9 @@ package org.bitcoins.core.p2p -import org.bitcoins.testkit.gen.DataMessageGenerator +import org.bitcoins.testkit.core.gen.p2p.DataMessageGenerator import org.bitcoins.testkit.util.BitcoinSUnitTest -class InventoryMessageSpec extends BitcoinSUnitTest { +class InventoryMessageTest extends BitcoinSUnitTest { it must " have serialization symmetry" in { forAll(DataMessageGenerator.inventoryMessages) { invMessage => diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/InventorySpec.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/InventoryTest.scala similarity index 69% rename from core-test/src/test/scala/org/bitcoins/core/p2p/InventorySpec.scala rename to core-test/src/test/scala/org/bitcoins/core/p2p/InventoryTest.scala index 7c38049b1c..5a87026188 100644 --- a/core-test/src/test/scala/org/bitcoins/core/p2p/InventorySpec.scala +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/InventoryTest.scala @@ -1,9 +1,9 @@ package org.bitcoins.core.p2p -import org.bitcoins.testkit.gen.DataMessageGenerator +import org.bitcoins.testkit.core.gen.p2p.DataMessageGenerator import org.bitcoins.testkit.util.BitcoinSUnitTest -class InventorySpec extends BitcoinSUnitTest { +class InventoryTest extends BitcoinSUnitTest { it must "have serialization symmetry" in { forAll(DataMessageGenerator.inventory) { inventory => diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/MerkleBlockMessageSpec.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/MerkleBlockMessageTest.scala similarity index 70% rename from core-test/src/test/scala/org/bitcoins/core/p2p/MerkleBlockMessageSpec.scala rename to core-test/src/test/scala/org/bitcoins/core/p2p/MerkleBlockMessageTest.scala index ddf16e582b..099850bde7 100644 --- a/core-test/src/test/scala/org/bitcoins/core/p2p/MerkleBlockMessageSpec.scala +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/MerkleBlockMessageTest.scala @@ -1,9 +1,9 @@ package org.bitcoins.core.p2p -import org.bitcoins.testkit.gen.DataMessageGenerator +import org.bitcoins.testkit.core.gen.p2p.DataMessageGenerator import org.bitcoins.testkit.util.BitcoinSUnitTest -class MerkleBlockMessageSpec extends BitcoinSUnitTest { +class MerkleBlockMessageTest extends BitcoinSUnitTest { it must "have serialization symmetry" in { forAll(DataMessageGenerator.merkleBlockMessage) { merkleBlockMsg => assert(MerkleBlockMessage(merkleBlockMsg.hex) == merkleBlockMsg) diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/NetworkHeaderTest.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/NetworkHeaderTest.scala index 6daa53d0c5..1d47d02461 100644 --- a/core-test/src/test/scala/org/bitcoins/core/p2p/NetworkHeaderTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/NetworkHeaderTest.scala @@ -5,15 +5,15 @@ import org.bitcoins.core.number.UInt32 import org.bitcoins.core.util.{BitcoinSUtil, CryptoUtil} import org.bitcoins.testkit.node.NodeTestUtil import org.scalatest.{FlatSpec, MustMatchers} +import org.bitcoins.core.config.MainNet +import scala.util.Random +import scodec.bits.ByteVector -/** - * Created by chris on 6/10/16. - */ class NetworkHeaderTest extends FlatSpec with MustMatchers { "MessageHeader" must "must create a message header for a message" in { val messageHeader = NetworkHeader(TestNet3, NodeTestUtil.versionMessage) - messageHeader.network must be(TestNet3.magicBytes) + messageHeader.network must be(TestNet3) messageHeader.commandName must be(NodeTestUtil.versionMessage.commandName) messageHeader.payloadSize must be( UInt32(NodeTestUtil.versionMessage.bytes.size)) @@ -23,10 +23,20 @@ class NetworkHeaderTest extends FlatSpec with MustMatchers { it must "build the correct message header for a verack message" in { val messageHeader = NetworkHeader(TestNet3, VerAckMessage) - messageHeader.network must be(TestNet3.magicBytes) + messageHeader.network must be(TestNet3) messageHeader.commandName must be(VerAckMessage.commandName) messageHeader.payloadSize must be(UInt32.zero) BitcoinSUtil.encodeHex(messageHeader.checksum) must be("5df6e0e2") } + it must "throw on messages of bad length" in { + intercept[IllegalArgumentException] { + val commandName = Random.shuffle(NetworkPayload.commandNames).head + NetworkHeader(MainNet, + commandName, + payloadSize = UInt32.one, + checksum = ByteVector.empty) + } + } + } diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/NetworkIpAddressTest.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/NetworkIpAddressTest.scala new file mode 100644 index 0000000000..f238195aa3 --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/NetworkIpAddressTest.scala @@ -0,0 +1,13 @@ +package org.bitcoins.core.p2p + +import org.bitcoins.testkit.util.BitcoinSUnitTest +import org.bitcoins.testkit.core.gen.p2p.P2PGenerator + +class NetworkIpAddressTest extends BitcoinSUnitTest { + it must "have serialization symmetry" in { + forAll(P2PGenerator.networkIpAddress) { ip => + val fromBytes = NetworkIpAddress.fromBytes(ip.bytes) + assert(fromBytes == ip) + } + } +} diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/NetworkPayloadTest.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/NetworkPayloadTest.scala index 59b823114f..f17c8d33ce 100644 --- a/core-test/src/test/scala/org/bitcoins/core/p2p/NetworkPayloadTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/NetworkPayloadTest.scala @@ -2,6 +2,7 @@ package org.bitcoins.core.p2p import org.bitcoins.testkit.node.NodeTestUtil import org.bitcoins.testkit.util.BitcoinSUnitTest +import org.bitcoins.testkit.core.gen.p2p.P2PGenerator class NetworkPayloadTest extends BitcoinSUnitTest { @@ -14,4 +15,14 @@ class NetworkPayloadTest extends BitcoinSUnitTest { payload.isInstanceOf[VersionMessage] must be(true) payload.commandName must be(NetworkPayload.versionCommandName) } + + // this tests has a bunch of messages to choose between, so we set a high config value + implicit override val generatorDrivenConfig = customGenDrivenConfig(200) + it must "parse messages based on its command name" in { + forAll(P2PGenerator.message) { p2p => + val bytes = p2p.bytes + val parser = NetworkPayload.readers(p2p.commandName) + assert(parser(bytes) == p2p) + } + } } diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/NotFoundMessageTest.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/NotFoundMessageTest.scala new file mode 100644 index 0000000000..1cac2eab3f --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/NotFoundMessageTest.scala @@ -0,0 +1,13 @@ +package org.bitcoins.core.p2p + +import org.bitcoins.testkit.util.BitcoinSUnitTest +import org.bitcoins.testkit.core.gen.p2p.DataMessageGenerator + +class NotFoundMessageTest extends BitcoinSUnitTest { + + it must "have serialization symmetry" in { + forAll(DataMessageGenerator.notFoundMessage) { notFound => + assert(NotFoundMessage.fromBytes(notFound.bytes) == notFound) + } + } +} diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/PingMessageSpec.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/PingMessageTest.scala similarity index 69% rename from core-test/src/test/scala/org/bitcoins/core/p2p/PingMessageSpec.scala rename to core-test/src/test/scala/org/bitcoins/core/p2p/PingMessageTest.scala index 7ad0564618..52ef94964b 100644 --- a/core-test/src/test/scala/org/bitcoins/core/p2p/PingMessageSpec.scala +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/PingMessageTest.scala @@ -1,9 +1,9 @@ package org.bitcoins.core.p2p -import org.bitcoins.testkit.gen.ControlMessageGenerator +import org.bitcoins.testkit.core.gen.p2p.ControlMessageGenerator import org.bitcoins.testkit.util.BitcoinSUnitTest -class PingMessageSpec extends BitcoinSUnitTest { +class PingMessageTest extends BitcoinSUnitTest { it must "have symmetry serialization" in { forAll(ControlMessageGenerator.pingMessage) { pingMessage => diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/PongMessageSpec.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/PongMessageTest.scala similarity index 68% rename from core-test/src/test/scala/org/bitcoins/core/p2p/PongMessageSpec.scala rename to core-test/src/test/scala/org/bitcoins/core/p2p/PongMessageTest.scala index 2130c4c6ce..3e30aebe94 100644 --- a/core-test/src/test/scala/org/bitcoins/core/p2p/PongMessageSpec.scala +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/PongMessageTest.scala @@ -1,9 +1,9 @@ package org.bitcoins.core.p2p -import org.bitcoins.testkit.gen.ControlMessageGenerator +import org.bitcoins.testkit.core.gen.p2p.ControlMessageGenerator import org.bitcoins.testkit.util.BitcoinSUnitTest -class PongMessageSpec extends BitcoinSUnitTest { +class PongMessageTest extends BitcoinSUnitTest { it must "have serialization symmetry" in { forAll(ControlMessageGenerator.pongMessage) { pongMsg => diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/RejectMessageSpec.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/RejectMessageTest.scala similarity index 69% rename from core-test/src/test/scala/org/bitcoins/core/p2p/RejectMessageSpec.scala rename to core-test/src/test/scala/org/bitcoins/core/p2p/RejectMessageTest.scala index b30a65f598..91c68de094 100644 --- a/core-test/src/test/scala/org/bitcoins/core/p2p/RejectMessageSpec.scala +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/RejectMessageTest.scala @@ -1,9 +1,9 @@ package org.bitcoins.core.p2p -import org.bitcoins.testkit.gen.ControlMessageGenerator +import org.bitcoins.testkit.core.gen.p2p.ControlMessageGenerator import org.bitcoins.testkit.util.BitcoinSUnitTest -class RejectMessageSpec extends BitcoinSUnitTest { +class RejectMessageTest extends BitcoinSUnitTest { it must "have serialization symmetry" in { forAll(ControlMessageGenerator.rejectMessage) { rejectMsg => diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/TransactionMessageSpec.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/TransactionMessageTest.scala similarity index 69% rename from core-test/src/test/scala/org/bitcoins/core/p2p/TransactionMessageSpec.scala rename to core-test/src/test/scala/org/bitcoins/core/p2p/TransactionMessageTest.scala index efa333d6e7..9b2504ca3c 100644 --- a/core-test/src/test/scala/org/bitcoins/core/p2p/TransactionMessageSpec.scala +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/TransactionMessageTest.scala @@ -1,9 +1,9 @@ package org.bitcoins.core.p2p -import org.bitcoins.testkit.gen.DataMessageGenerator +import org.bitcoins.testkit.core.gen.p2p.DataMessageGenerator import org.bitcoins.testkit.util.BitcoinSUnitTest -class TransactionMessageSpec extends BitcoinSUnitTest { +class TransactionMessageTest extends BitcoinSUnitTest { it must "have serialization symmetry" in { forAll(DataMessageGenerator.transactionMessage) { txMsg => diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/VersionMessageTest.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/VersionMessageTest.scala index 311ce8f4c7..1aee9f8ddd 100644 --- a/core-test/src/test/scala/org/bitcoins/core/p2p/VersionMessageTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/VersionMessageTest.scala @@ -4,11 +4,18 @@ import java.net.InetAddress import org.bitcoins.core.config.MainNet 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 class VersionMessageTest extends BitcoinSUnitTest { + it must "have serialization symmetry" in { + forAll(ControlMessageGenerator.versionMessage) { versionMessage => + assert(VersionMessage(versionMessage.hex) == versionMessage) + } + } + "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) diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/headers/RawNetworkHeaderSerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/headers/RawNetworkHeaderSerializerTest.scala index b6f2d81d0e..811e4b87ab 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/headers/RawNetworkHeaderSerializerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/headers/RawNetworkHeaderSerializerTest.scala @@ -1,10 +1,10 @@ package org.bitcoins.core.serializers.p2p.headers import org.bitcoins.core.number.UInt32 -import org.bitcoins.core.util.BitcoinSUtil import org.bitcoins.core.p2p.NetworkPayload import org.bitcoins.testkit.node.NodeTestUtil import org.bitcoins.testkit.util.BitcoinSUnitTest +import scodec.bits._ class RawNetworkHeaderSerializerTest extends BitcoinSUnitTest { val hex = "f9beb4d976657261636b000000000000000000005df6e0e2" @@ -14,13 +14,13 @@ class RawNetworkHeaderSerializerTest extends BitcoinSUnitTest { val messageHeader = RawNetworkHeaderSerializer.read(hex) //this is the mainnet id - BitcoinSUtil.encodeHex(messageHeader.network) must be("f9beb4d9") + messageHeader.network.magicBytes must be(hex"f9beb4d9") messageHeader.commandName must be("verack") messageHeader.payloadSize must be(UInt32.zero) - BitcoinSUtil.encodeHex(messageHeader.checksum) must be("5df6e0e2") + messageHeader.checksum must be(hex"5df6e0e2") } it must "write an object that was just read and get the original input" in { @@ -31,11 +31,11 @@ class RawNetworkHeaderSerializerTest extends BitcoinSUnitTest { it must "read a network header from a node on the network" in { val hex = NodeTestUtil.rawNetworkMessage.take(48) val header = RawNetworkHeaderSerializer.read(hex) - BitcoinSUtil.encodeHex(header.network) must be("0B110907".toLowerCase) + header.network.magicBytes must be(hex"0B110907") header.commandName.size must be(NetworkPayload.versionCommandName.size) header.commandName must be(NetworkPayload.versionCommandName) header.payloadSize must be(UInt32(102)) - BitcoinSUtil.encodeHex(header.checksum) must be("2f6743da") + header.checksum must be(hex"2f6743da") } diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawNetworkIpAddressSerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawNetworkIpAddressSerializerTest.scala index 96ec34f509..af909532cd 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawNetworkIpAddressSerializerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawNetworkIpAddressSerializerTest.scala @@ -1,4 +1,4 @@ -package org.bitcoins.core.serializers.p2p.messages +package org.bitcoins.core.serializers.p2p import org.bitcoins.core.number.UInt32 import org.bitcoins.core.p2p.NodeNetwork diff --git a/core/src/main/scala/org/bitcoins/core/config/NetworkParameters.scala b/core/src/main/scala/org/bitcoins/core/config/NetworkParameters.scala index aa7deb2c14..0ae07f4845 100644 --- a/core/src/main/scala/org/bitcoins/core/config/NetworkParameters.scala +++ b/core/src/main/scala/org/bitcoins/core/config/NetworkParameters.scala @@ -162,6 +162,13 @@ object Networks { case _: String => None } + /** Map of magic network bytes to the corresponding network */ + def magicToNetwork: Map[ByteVector, NetworkParameters] = Map( + MainNet.magicBytes -> MainNet, + TestNet3.magicBytes -> TestNet3, + RegTest.magicBytes -> RegTest + ) + def bytesToNetwork: Map[ByteVector, NetworkParameters] = Map( MainNet.p2shNetworkByte -> MainNet, MainNet.p2pkhNetworkByte -> MainNet, diff --git a/core/src/main/scala/org/bitcoins/core/p2p/NetworkHeader.scala b/core/src/main/scala/org/bitcoins/core/p2p/NetworkHeader.scala index 9cb58354ac..738afb0f7d 100644 --- a/core/src/main/scala/org/bitcoins/core/p2p/NetworkHeader.scala +++ b/core/src/main/scala/org/bitcoins/core/p2p/NetworkHeader.scala @@ -16,10 +16,10 @@ sealed trait NetworkHeader extends NetworkElement { override def bytes: ByteVector = RawNetworkHeaderSerializer.write(this) /** - * Magic bytes indicating the originating network; + * Each network has magic bytes indicating the originating network; * used to seek to next message when stream state is unknown. */ - def network: ByteVector + def network: NetworkParameters /** * ASCII string which identifies what message type is contained in the payload. @@ -46,7 +46,7 @@ sealed trait NetworkHeader extends NetworkElement { object NetworkHeader extends Factory[NetworkHeader] { private case class NetworkHeaderImpl( - network: ByteVector, + network: NetworkParameters, commandName: String, payloadSize: UInt32, checksum: ByteVector) @@ -65,7 +65,7 @@ object NetworkHeader extends Factory[NetworkHeader] { * @param checksum the checksum of the payload to ensure that the entire payload was sent */ def apply( - network: ByteVector, + network: NetworkParameters, commandName: String, payloadSize: UInt32, checksum: ByteVector): NetworkHeader = { @@ -81,7 +81,7 @@ object NetworkHeader extends Factory[NetworkHeader] { network: NetworkParameters, payload: NetworkPayload): NetworkHeader = { val checksum = CryptoUtil.doubleSHA256(payload.bytes) - NetworkHeader(network.magicBytes, + NetworkHeader(network, payload.commandName, UInt32(payload.bytes.size), checksum.bytes.take(4)) diff --git a/core/src/main/scala/org/bitcoins/core/p2p/NetworkIpAddress.scala b/core/src/main/scala/org/bitcoins/core/p2p/NetworkIpAddress.scala index eb98240507..08b2ed5a79 100644 --- a/core/src/main/scala/org/bitcoins/core/p2p/NetworkIpAddress.scala +++ b/core/src/main/scala/org/bitcoins/core/p2p/NetworkIpAddress.scala @@ -4,7 +4,7 @@ import java.net.{InetAddress, InetSocketAddress} import org.bitcoins.core.number.UInt32 import org.bitcoins.core.protocol.NetworkElement -import org.bitcoins.core.serializers.p2p.messages.RawNetworkIpAddressSerializer +import org.bitcoins.core.serializers.p2p._ import org.bitcoins.core.util.Factory import scodec.bits._ diff --git a/core/src/main/scala/org/bitcoins/core/p2p/NetworkPayload.scala b/core/src/main/scala/org/bitcoins/core/p2p/NetworkPayload.scala index e3a7c0724e..172edd92ff 100644 --- a/core/src/main/scala/org/bitcoins/core/p2p/NetworkPayload.scala +++ b/core/src/main/scala/org/bitcoins/core/p2p/NetworkPayload.scala @@ -35,13 +35,15 @@ sealed trait NetworkPayload extends NetworkElement { /** * Represents a data message inside of bitcoin core - * [[https://bitcoin.org/en/developer-reference#data-messages]] + * + * @see [[https://bitcoin.org/en/developer-reference#data-messages]] */ sealed trait DataPayload extends NetworkPayload /** * The block message transmits a single serialized block - * [[https://bitcoin.org/en/developer-reference#block]] + * + * @see [[https://bitcoin.org/en/developer-reference#block]] */ trait BlockMessage extends DataPayload { @@ -252,8 +254,8 @@ object GetHeadersMessage extends Factory[GetHeadersMessage] { GetHeadersMessage(hashes, hashStop) } - def apply(hashes: DoubleSha256Digest): GetHeadersMessage = { - GetHeadersMessage(Vector(hashes)) + def apply(hash: DoubleSha256Digest): GetHeadersMessage = { + GetHeadersMessage(Vector(hash)) } } @@ -357,7 +359,8 @@ object InventoryMessage extends Factory[InventoryMessage] { * as valid but which have not yet appeared in a block. * That is, transactions which are in the receiving node’s memory pool. * The response to the mempool message is one or more inv messages containing the TXIDs in the usual inventory format. - * [[https://bitcoin.org/en/developer-reference#mempool]] + * + * @see [[https://bitcoin.org/en/developer-reference#mempool]] */ case object MemPoolMessage extends DataPayload { override val commandName = NetworkPayload.memPoolCommandName @@ -404,6 +407,7 @@ object MerkleBlockMessage extends Factory[MerkleBlockMessage] { * node does not have available for relay. (Nodes are not expected to relay historic transactions * which are no longer in the memory pool or relay set. * Nodes may also have pruned spent transactions from older blocks, making them unable to send those blocks.) + * * @see [[https://bitcoin.org/en/developer-reference#notfound]] */ trait NotFoundMessage extends DataPayload with InventoryMessage { @@ -413,6 +417,7 @@ trait NotFoundMessage extends DataPayload with InventoryMessage { /** * The companion object factory used to create NotFoundMessages on the p2p network + * * @see https://bitcoin.org/en/developer-reference#notfound */ object NotFoundMessage extends Factory[NotFoundMessage] { @@ -425,6 +430,11 @@ object NotFoundMessage extends Factory[NotFoundMessage] { def fromBytes(bytes: ByteVector): NotFoundMessage = RawNotFoundMessageSerializer.read(bytes) + def apply(inventories: Seq[Inventory]): NotFoundMessage = { + val count = CompactSizeUInt(UInt64(inventories.length)) + apply(count, inventories) + } + def apply( inventoryCount: CompactSizeUInt, inventories: Seq[Inventory]): NotFoundMessage = { @@ -499,6 +509,11 @@ object AddrMessage extends Factory[AddrMessage] { def fromBytes(bytes: ByteVector): AddrMessage = RawAddrMessageSerializer.read(bytes) + def apply(addresses: Seq[NetworkIpAddress]): AddrMessage = { + val count = CompactSizeUInt(UInt64(addresses.length)) + apply(count, addresses) + } + def apply( ipCount: CompactSizeUInt, addresses: Seq[NetworkIpAddress]): AddrMessage = @@ -1086,13 +1101,14 @@ object NetworkPayload { val versionCommandName = "version" /** - * Contains all the valid command names with their deserializer on the p2p protocol + * Contains all the valid command names with their deserializer on the p2p protocol. * These commands all have the null bytes appended to the end of the string as - * required in [[NetworkHeader]] - * [[https://bitcoin.org/en/developer-reference#message-headers]] + * required by the network header specification. + * + * @see [[https://bitcoin.org/en/developer-reference#message-headers]] * */ - val commandNames: Map[String, ByteVector => NetworkPayload] = Map( + val readers: Map[String, ByteVector => NetworkPayload] = Map( blockCommandName -> { RawBlockMessageSerializer.read(_) }, getBlocksCommandName -> { RawGetBlocksMessageSerializer.read(_) }, getHeadersCommandName -> { RawGetHeadersMessageSerializer.read(_) }, @@ -1117,9 +1133,7 @@ object NetworkPayload { }, pingCommandName -> { RawPingMessageSerializer.read(_) }, pongCommandName -> { RawPongMessageSerializer.read(_) }, - rejectCommandName -> { _: ByteVector => - ??? - }, + rejectCommandName -> { RawRejectMessageSerializer.read(_) }, sendHeadersCommandName -> { _: ByteVector => SendHeadersMessage }, @@ -1129,6 +1143,9 @@ object NetworkPayload { versionCommandName -> { RawVersionMessageSerializer.read(_) } ) + /** All command names for P2P messages */ + val commandNames: Vector[String] = readers.keys.toVector + /** * Parses a [[NetworkPayload]] from the given bytes using the [[NetworkHeader]] * to determine what type of [[NetworkPayload]] this is @@ -1139,7 +1156,7 @@ object NetworkPayload { networkHeader: NetworkHeader, payloadBytes: ByteVector): NetworkPayload = { //the commandName in the network header tells us what payload type this is - val deserializer: ByteVector => NetworkPayload = commandNames( + val deserializer: ByteVector => NetworkPayload = readers( networkHeader.commandName) deserializer(payloadBytes) } diff --git a/core/src/main/scala/org/bitcoins/core/p2p/ProtocolVersion.scala b/core/src/main/scala/org/bitcoins/core/p2p/ProtocolVersion.scala index cf9f8f2370..d6356dd990 100644 --- a/core/src/main/scala/org/bitcoins/core/p2p/ProtocolVersion.scala +++ b/core/src/main/scala/org/bitcoins/core/p2p/ProtocolVersion.scala @@ -30,7 +30,8 @@ object ProtocolVersion extends Factory[ProtocolVersion] { ProtocolVersion60002, ProtocolVersion70001, ProtocolVersion70002, - ProtocolVersion70012 + ProtocolVersion70012, + ProtocolVersion70013 ) def fromBytes(bytes: ByteVector): ProtocolVersion = { diff --git a/core/src/main/scala/org/bitcoins/core/p2p/serializers/messages/RawNetworkIpAddressSerializer.scala b/core/src/main/scala/org/bitcoins/core/serializers/p2p/RawNetworkIpAddressSerializer.scala similarity index 83% rename from core/src/main/scala/org/bitcoins/core/p2p/serializers/messages/RawNetworkIpAddressSerializer.scala rename to core/src/main/scala/org/bitcoins/core/serializers/p2p/RawNetworkIpAddressSerializer.scala index 85928d977d..bb8e642668 100644 --- a/core/src/main/scala/org/bitcoins/core/p2p/serializers/messages/RawNetworkIpAddressSerializer.scala +++ b/core/src/main/scala/org/bitcoins/core/serializers/p2p/RawNetworkIpAddressSerializer.scala @@ -1,4 +1,4 @@ -package org.bitcoins.core.p2p.serializers.messages +package org.bitcoins.core.serializers.p2p import java.net.InetAddress @@ -21,7 +21,7 @@ trait RawNetworkIpAddressSerializer val services = ServiceIdentifier(bytes.slice(4, 12)) val ipBytes = bytes.slice(12, 28) val ipAddress = InetAddress.getByAddress(ipBytes.toArray) - val port = NumberUtil.toLong(bytes.slice(28, 30)).toInt + val port = bytes.slice(28, 30).toInt(signed = false) NetworkIpAddress(time, services, ipAddress, port) } @@ -29,8 +29,9 @@ trait RawNetworkIpAddressSerializer val time = networkIpAddress.time.bytes.reverse val services = networkIpAddress.services.bytes val ipAddress = NetworkIpAddress.writeAddress(networkIpAddress.address) - //uint16s are only 4 hex characters - val port = ByteVector.fromShort(networkIpAddress.port.toShort) + // uint16s are only 4 hex characters + // cannot do fromShort, + val port = ByteVector.fromInt(networkIpAddress.port, size = 2) time ++ services ++ ipAddress ++ port } diff --git a/core/src/main/scala/org/bitcoins/core/serializers/p2p/headers/RawNetworkHeaderSerializer.scala b/core/src/main/scala/org/bitcoins/core/serializers/p2p/headers/RawNetworkHeaderSerializer.scala index 186a1ea42d..5a3c62be45 100644 --- a/core/src/main/scala/org/bitcoins/core/serializers/p2p/headers/RawNetworkHeaderSerializer.scala +++ b/core/src/main/scala/org/bitcoins/core/serializers/p2p/headers/RawNetworkHeaderSerializer.scala @@ -1,6 +1,7 @@ package org.bitcoins.core.serializers.p2p.headers import org.bitcoins.core.number.UInt32 +import org.bitcoins.core.config._ import org.bitcoins.core.p2p.NetworkHeader import org.bitcoins.core.serializers.RawBitcoinSerializer import org.bitcoins.core.util.BitcoinSLogger @@ -20,7 +21,7 @@ trait RawNetworkHeaderSerializer * @return the native object for the MessageHeader */ def read(bytes: ByteVector): NetworkHeader = { - val network = bytes.take(4) + val network = Networks.magicToNetwork(bytes.take(4)) //.trim removes the null characters appended to the command name val commandName = bytes.slice(4, 16).toArray.map(_.toChar).mkString.trim val payloadSize = UInt32(bytes.slice(16, 20).reverse) @@ -39,7 +40,7 @@ trait RawNetworkHeaderSerializer //command name needs to be 12 bytes in size, or 24 chars in hex val commandName = ByteVector(commandNameNoPadding).padRight(12) val checksum = messageHeader.checksum - network ++ + network.magicBytes ++ commandName ++ messageHeader.payloadSize.bytes.reverse ++ checksum diff --git a/core/src/main/scala/org/bitcoins/core/serializers/p2p/messages/RawAddrMessageSerializer.scala b/core/src/main/scala/org/bitcoins/core/serializers/p2p/messages/RawAddrMessageSerializer.scala index d95ecf80c5..2f8f1056f5 100644 --- a/core/src/main/scala/org/bitcoins/core/serializers/p2p/messages/RawAddrMessageSerializer.scala +++ b/core/src/main/scala/org/bitcoins/core/serializers/p2p/messages/RawAddrMessageSerializer.scala @@ -2,6 +2,7 @@ package org.bitcoins.core.serializers.p2p.messages import org.bitcoins.core.protocol.CompactSizeUInt import org.bitcoins.core.serializers.{RawBitcoinSerializer, RawSerializerHelper} +import org.bitcoins.core.serializers.p2p._ import org.bitcoins.core.p2p._ import scodec.bits.ByteVector diff --git a/core/src/main/scala/org/bitcoins/core/serializers/p2p/messages/RawFeeFilterMessageSerializer.scala b/core/src/main/scala/org/bitcoins/core/serializers/p2p/messages/RawFeeFilterMessageSerializer.scala index 61655b7a81..ce65f2617b 100644 --- a/core/src/main/scala/org/bitcoins/core/serializers/p2p/messages/RawFeeFilterMessageSerializer.scala +++ b/core/src/main/scala/org/bitcoins/core/serializers/p2p/messages/RawFeeFilterMessageSerializer.scala @@ -1,7 +1,6 @@ package org.bitcoins.core.serializers.p2p.messages import org.bitcoins.core.currency.Satoshis -import org.bitcoins.core.number.Int64 import org.bitcoins.core.serializers.RawBitcoinSerializer import org.bitcoins.core.wallet.fee.SatoshisPerKiloByte import org.bitcoins.core.p2p._ @@ -11,14 +10,14 @@ sealed abstract class RawFeeFilterMessageSerializer extends RawBitcoinSerializer[FeeFilterMessage] { override def read(bytes: ByteVector): FeeFilterMessage = { - val i64 = Int64.fromBytes(bytes.take(8).reverse) - val sat = Satoshis(i64) + val satBytes = bytes.take(8).reverse + val sat = Satoshis(satBytes) val satPerKb = SatoshisPerKiloByte(sat) FeeFilterMessage(satPerKb) } override def write(feeFilterMessage: FeeFilterMessage): ByteVector = { - feeFilterMessage.feeRate.currencyUnit.bytes.reverse + feeFilterMessage.feeRate.currencyUnit.satoshis.bytes.reverse } } diff --git a/core/src/main/scala/org/bitcoins/core/wallet/fee/FeeUnit.scala b/core/src/main/scala/org/bitcoins/core/wallet/fee/FeeUnit.scala index d6590d310f..90d7163fa0 100644 --- a/core/src/main/scala/org/bitcoins/core/wallet/fee/FeeUnit.scala +++ b/core/src/main/scala/org/bitcoins/core/wallet/fee/FeeUnit.scala @@ -17,17 +17,20 @@ sealed abstract class FeeUnit { /** * Meant to represent the different fee unit types for the bitcoin protocol - * [[https://en.bitcoin.it/wiki/Weight_units]] + * @see [[https://en.bitcoin.it/wiki/Weight_units]] */ sealed abstract class BitcoinFeeUnit extends FeeUnit case class SatoshisPerByte(currencyUnit: CurrencyUnit) extends BitcoinFeeUnit { + def toSatPerKb: SatoshisPerKiloByte = { SatoshisPerKiloByte(currencyUnit.satoshis * Satoshis(Int64(1000))) } } -case class SatoshisPerKiloByte(currencyUnit: CurrencyUnit) extends BitcoinFeeUnit { +case class SatoshisPerKiloByte(currencyUnit: CurrencyUnit) + extends BitcoinFeeUnit { + def toSatPerByte: SatoshisPerByte = { val conversionOpt = (currencyUnit.toBigDecimal * 0.001).toBigIntExact() conversionOpt match { @@ -36,11 +39,13 @@ case class SatoshisPerKiloByte(currencyUnit: CurrencyUnit) extends BitcoinFeeUni SatoshisPerByte(sat) case None => - throw new RuntimeException(s"Failed to convert sat/kb -> sat/byte for ${currencyUnit}") + throw new RuntimeException( + s"Failed to convert sat/kb -> sat/byte for ${currencyUnit}") } } } + /** * A 'virtual byte' (also known as virtual size) is a new weight measurement that * was created with segregated witness (BIP141). Now 1 'virtual byte' diff --git a/docs/contributing.md b/docs/contributing.md index 36762fe490..7cb1934d23 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -160,6 +160,22 @@ The command `sbt testQuick` can also be handy. It runs tests that either: For more information on `testQuick`, see the offical [sbt docs](https://www.scala-sbt.org/1.x/docs/Testing.html#testQuick). +### Coverage + +To produce a report that quantifies how much of our code is covered by tests: + +```bash +sbt +> coverage +> coreTest/test +> core/coverageReport +``` + +This generates three different reports: Cobertura, XML and HTML formats. +See the output of your sbt shell to find the location of them. +Open up the HTML file in your browser. You'll now see code coverage +of all files in `core` project. + ### CI Bitcoin-S uses Travis to run tests and deploy library and website builds. Generally diff --git a/testkit/src/main/scala/org/bitcoins/testkit/core/gen/CurrencyUnitGenerator.scala b/testkit/src/main/scala/org/bitcoins/testkit/core/gen/CurrencyUnitGenerator.scala index 801b4a08eb..fa940862b2 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/core/gen/CurrencyUnitGenerator.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/core/gen/CurrencyUnitGenerator.scala @@ -9,12 +9,34 @@ import org.bitcoins.core.currency.{ import org.bitcoins.core.number.Int64 import org.bitcoins.core.protocol.ln.currency._ import org.scalacheck.Gen +import org.bitcoins.core.wallet.fee.FeeUnit +import org.bitcoins.core.wallet.fee.SatoshisPerByte +import org.bitcoins.core.wallet.fee.SatoshisPerKiloByte +import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte -/** - * Created by chris on 6/23/16. - */ trait CurrencyUnitGenerator { + def satsPerByte: Gen[SatoshisPerByte] = { + for { + curr <- positiveRealistic + } yield SatoshisPerByte(curr) + } + + def satsPerKiloByte: Gen[SatoshisPerKiloByte] = { + for { + curr <- positiveRealistic + } yield SatoshisPerKiloByte(curr) + } + + def satsPerVirtualByte: Gen[SatoshisPerVirtualByte] = { + for { + curr <- positiveRealistic + } yield SatoshisPerVirtualByte(curr) + } + + def feeUnit: Gen[FeeUnit] = + Gen.oneOf(satsPerByte, satsPerKiloByte, satsPerVirtualByte) + def satoshis: Gen[Satoshis] = for { int64 <- NumberGenerator.int64s diff --git a/testkit/src/main/scala/org/bitcoins/testkit/gen/ControlMessageGenerator.scala b/testkit/src/main/scala/org/bitcoins/testkit/core/gen/p2p/ControlMessageGenerator.scala similarity index 65% rename from testkit/src/main/scala/org/bitcoins/testkit/gen/ControlMessageGenerator.scala rename to testkit/src/main/scala/org/bitcoins/testkit/core/gen/p2p/ControlMessageGenerator.scala index 62b9aa0d67..077e40a7d2 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/gen/ControlMessageGenerator.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/core/gen/p2p/ControlMessageGenerator.scala @@ -1,4 +1,4 @@ -package org.bitcoins.testkit.gen +package org.bitcoins.testkit.core.gen.p2p import java.net.{InetAddress, InetSocketAddress} @@ -7,17 +7,50 @@ import org.bitcoins.core.p2p.ProtocolVersion import org.bitcoins.core.protocol.CompactSizeUInt import org.bitcoins.core.p2p._ import org.bitcoins.core.p2p._ -import org.bitcoins.testkit.core.gen.{BloomFilterGenerator, CryptoGenerators, NumberGenerator, StringGenerators} +import org.bitcoins.testkit.core.gen.{ + BloomFilterGenerator, + CryptoGenerators, + NumberGenerator, + StringGenerators +} import org.scalacheck.Gen import scodec.bits.ByteVector +import org.bitcoins.testkit.core.gen.CurrencyUnitGenerator +import org.bitcoins.core.wallet.fee.SatoshisPerByte +import org.bitcoins.core.wallet.fee.SatoshisPerKiloByte +import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte -trait ControlMessageGenerator { +object ControlMessageGenerator { + + /** Generates a valid P2P control message */ + def controlMessage: Gen[ControlPayload] = Gen.oneOf( + addrMessage, + filterAddMessage, + filterLoadMessage, + feeFilterMessage, + pingMessage, + pongMessage, + rejectMessage, + versionMessage + ) + + def feeFilterMessage: Gen[FeeFilterMessage] = { + for { + fee <- CurrencyUnitGenerator.feeUnit.suchThat( + !_.isInstanceOf[SatoshisPerVirtualByte]) + } yield + fee match { + case fee: SatoshisPerByte => FeeFilterMessage(fee) + case fee: SatoshisPerKiloByte => FeeFilterMessage(fee) + case SatoshisPerVirtualByte(_) => + throw new RuntimeException(s"We cannot end up here") + } + } /** * Generates a random [[VersionMessage]] - * [[https://bitcoin.org/en/developer-reference#version]] * - * @return + * @see [[https://bitcoin.org/en/developer-reference#version]] */ def versionMessage: Gen[VersionMessage] = for { @@ -53,9 +86,8 @@ trait ControlMessageGenerator { /** * Generates a [[PingMessage]] - * [[https://bitcoin.org/en/developer-reference#ping]] * - * @return + * @see [[https://bitcoin.org/en/developer-reference#ping]] */ def pingMessage: Gen[PingMessage] = for { @@ -64,20 +96,24 @@ trait ControlMessageGenerator { /** * Generates a [[PongMessage]] - * [[https://bitcoin.org/en/developer-reference#pong]] * - * @return + * @see [[https://bitcoin.org/en/developer-reference#pong]] */ def pongMessage: Gen[PongMessage] = for { uInt64 <- NumberGenerator.uInt64s } yield PongMessage(uInt64) + def addrMessage: Gen[AddrMessage] = { + for { + addresses <- Gen.listOf(P2PGenerator.networkIpAddress) + } yield AddrMessage(addresses) + } + /** * Generates a random [[ProtocolVersion]] - * [[https://bitcoin.org/en/developer-reference#protocol-versions]] * - * @return + * @see [[https://bitcoin.org/en/developer-reference#protocol-versions]] */ def protocolVersion: Gen[ProtocolVersion] = for { @@ -86,9 +122,8 @@ trait ControlMessageGenerator { /** * Generates a [[ServiceIdentifier]] - * [[https://bitcoin.org/en/developer-reference#version]] * - * @return + * @see [[https://bitcoin.org/en/developer-reference#version]] */ def serviceIdentifier: Gen[ServiceIdentifier] = for { @@ -110,9 +145,8 @@ trait ControlMessageGenerator { /** * Creates a [[FilterLoadMessage]] - * [[https://bitcoin.org/en/developer-reference#filterload]] * - * @return + * @see [[https://bitcoin.org/en/developer-reference#filterload]] */ def filterLoadMessage: Gen[FilterLoadMessage] = for { @@ -125,9 +159,8 @@ trait ControlMessageGenerator { /** * Creates a [[FilterAddMessage]] - * [[https://bitcoin.org/en/developer-reference#filteradd]] * - * @return + * @see [[https://bitcoin.org/en/developer-reference#filteradd]] */ def filterAddMessage: Gen[FilterAddMessage] = for { @@ -137,9 +170,8 @@ trait ControlMessageGenerator { /** * Creates a [[RejectMessage]] - * [[https://bitcoin.org/en/developer-reference#reject]] * - * @return + * @see [[https://bitcoin.org/en/developer-reference#reject]] */ def rejectMessage: Gen[RejectMessage] = for { @@ -149,5 +181,3 @@ trait ControlMessageGenerator { extra <- CryptoGenerators.doubleSha256Digest } yield RejectMessage(message, code, reason, extra.bytes) } - -object ControlMessageGenerator extends ControlMessageGenerator diff --git a/testkit/src/main/scala/org/bitcoins/testkit/gen/DataMessageGenerator.scala b/testkit/src/main/scala/org/bitcoins/testkit/core/gen/p2p/DataMessageGenerator.scala similarity index 66% rename from testkit/src/main/scala/org/bitcoins/testkit/gen/DataMessageGenerator.scala rename to testkit/src/main/scala/org/bitcoins/testkit/core/gen/p2p/DataMessageGenerator.scala index c4d9a26685..61d6a603d6 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/gen/DataMessageGenerator.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/core/gen/p2p/DataMessageGenerator.scala @@ -1,4 +1,4 @@ -package org.bitcoins.testkit.gen +package org.bitcoins.testkit.core.gen.p2p import org.bitcoins.core.number.UInt32 import org.bitcoins.core.p2p._ @@ -11,13 +11,44 @@ import org.bitcoins.testkit.core.gen.{ import org.scalacheck.Gen /** - * Responsible for generating random [[DataMessage]] + * Responsible for generating random data message + * @see [[https://bitcoin.org/en/developer-reference#data-messages]] */ trait DataMessageGenerator { + /** Generates a valid P2P data message */ + def dataMessage: Gen[DataPayload] = Gen.oneOf( + blockMessage, + getBlocksMessage, + getDataMessages, + getHeaderMessages, + headersMessage, + inventoryMessages, + merkleBlockMessage, + notFoundMessage, + transactionMessage + ) + + def blockMessage: Gen[BlockMessage] = { + for { + block <- BlockchainElementsGenerator.block + } yield BlockMessage(block) + } + + def getBlocksMessage: Gen[GetBlocksMessage] = { + for { + protocol <- ControlMessageGenerator.protocolVersion + hashes <- Gen + .nonEmptyListOf(CryptoGenerators.doubleSha256Digest) + .suchThat(_.length <= 500) + stopHash <- CryptoGenerators.doubleSha256Digest + } yield GetBlocksMessage(protocol, hashes, stopHash) + } + /** * Generates a random [[GetHeadersMessage]] + * * @see [[https://bitcoin.org/en/developer-reference#getheaders]] */ def getHeaderMessages: Gen[GetHeadersMessage] = @@ -28,6 +59,15 @@ trait DataMessageGenerator { hashStop <- CryptoGenerators.doubleSha256Digest } yield GetHeadersMessage(version, hashes, hashStop) + /** Generates a `getheaders` message with the default protocol version */ + def getHeaderDefaultProtocolMessage: Gen[GetHeadersMessage] = { + for { + numHashes <- Gen.choose(0, 2000) + hashes <- CryptoGenerators.doubleSha256DigestSeq(numHashes) + hashStop <- CryptoGenerators.doubleSha256Digest + } yield GetHeadersMessage(hashes, hashStop) + } + def headersMessage: Gen[HeadersMessage] = for { randomNum <- Gen.choose(1, 10) @@ -39,7 +79,8 @@ trait DataMessageGenerator { /** * Generates a random [[TypeIdentifier]] - * [[https://bitcoin.org/en/developer-reference#data-messages]] + * + * @see [[https://bitcoin.org/en/developer-reference#data-messages]] */ def typeIdentifier: Gen[TypeIdentifier] = for { @@ -66,6 +107,12 @@ trait DataMessageGenerator { inventories <- Gen.listOfN(numInventories, inventory) } yield InventoryMessage(inventories) + def notFoundMessage: Gen[NotFoundMessage] = { + for { + inventories <- Gen.nonEmptyListOf(inventory) + } yield NotFoundMessage(inventories) + } + /** * Generate a random [[GetDataMessage]] * @see [[https://bitcoin.org/en/developer-reference#getdata]] diff --git a/testkit/src/main/scala/org/bitcoins/testkit/core/gen/p2p/P2PGenerator.scala b/testkit/src/main/scala/org/bitcoins/testkit/core/gen/p2p/P2PGenerator.scala new file mode 100644 index 0000000000..082afd95f6 --- /dev/null +++ b/testkit/src/main/scala/org/bitcoins/testkit/core/gen/p2p/P2PGenerator.scala @@ -0,0 +1,51 @@ +package org.bitcoins.testkit.core.gen.p2p +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 + +object P2PGenerator { + + /** Generates a valid P2P network message */ + def message: Gen[NetworkPayload] = + Gen.oneOf(ControlMessageGenerator.controlMessage, + DataMessageGenerator.dataMessage) + + def inetAddress: Gen[InetAddress] = { + def ipRangeNum = Gen.choose(0, 255) + for { + first <- ipRangeNum + second <- ipRangeNum + third <- ipRangeNum + fourth <- ipRangeNum + } yield { + // as long as we don't pass in a host name no IO is performed + // https://stackoverflow.com/questions/5571744/java-convert-a-string-representing-an-ip-to-inetaddress + InetAddress.getByName(s"$first.$second.$third.$fourth") + } + + } + + def networkIpAddress: Gen[NetworkIpAddress] = { + for { + time <- NumberGenerator.uInt32s + services <- serviceIdentifier + address <- inetAddress + port <- Gen.choose(1025, 64000) + } yield NetworkIpAddress(time, services, address, port) + } + + 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 + } +} diff --git a/testkit/src/main/scala/org/bitcoins/testkit/util/BitcoinSUnitTest.scala b/testkit/src/main/scala/org/bitcoins/testkit/util/BitcoinSUnitTest.scala index 6115d61098..91afd92db4 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/util/BitcoinSUnitTest.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/util/BitcoinSUnitTest.scala @@ -14,13 +14,14 @@ abstract class BitcoinSUnitTest protected lazy val logger: Logger = LoggerFactory.getLogger(getClass) /** The configuration for property based tests in our testing suite - * See: http://www.scalatest.org/user_guide/writing_scalacheck_style_properties + * @see http://www.scalatest.org/user_guide/writing_scalacheck_style_properties */ implicit override val generatorDrivenConfig: PropertyCheckConfiguration = { generatorDriveConfigOldCode } - private def buildConfig(executions: Int): PropertyCheckConfiguration = { + /** Sets the generator driven tests to perform the given amount of execs */ + def customGenDrivenConfig(executions: Int): PropertyCheckConfiguration = { PropertyCheckConfiguration( minSuccessful = PosInt.from(executions).get, minSize = PosInt.from(executions).get, @@ -33,7 +34,7 @@ abstract class BitcoinSUnitTest * @return */ def generatorDriveConfigOldCode: PropertyCheckConfiguration = { - buildConfig(BitcoinSUnitTest.OLD_CODE_EXECUTIONS) + customGenDrivenConfig(BitcoinSUnitTest.OLD_CODE_EXECUTIONS) } /** Property based tests that are new have a higher chance of failing @@ -41,7 +42,7 @@ abstract class BitcoinSUnitTest * @return */ def generatorDrivenConfigNewCode: PropertyCheckConfiguration = { - buildConfig(BitcoinSUnitTest.NEW_CODE_EXECUTIONS) + customGenDrivenConfig(BitcoinSUnitTest.NEW_CODE_EXECUTIONS) } }