diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a353ebf..4c97105 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,28 +1,24 @@ -# This is a basic workflow to help you get started with Actions - name: CI -# Controls when the workflow will run on: - # Triggers the workflow on push or pull request events but only for the master branch push: branches: [ master ] pull_request: branches: [ master ] - # Allows you to run this workflow manually from the Actions tab workflow_dispatch: -# A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: - # This workflow contains a single job called "build" build: - # The type of runner that the job will run on runs-on: ubuntu-latest + env: + # specify versions to test + INT_NODECORE_VERSION: "0.4.13-rc.14" + INT_APM_VERSION: "0.4.13-rc.13" + INT_BTCSQ_VERSION: "master-47363b0" + INT_LOG_LEVEL: "DEBUG" - # Steps represent a sequence of tasks that will be executed as part of the job steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 - uses: actions/setup-java@v2 with: diff --git a/.gitignore b/.gitignore index 72c3cef..e061937 100644 --- a/.gitignore +++ b/.gitignore @@ -77,4 +77,6 @@ scripts/certs/ certs/ *.iml -*.prefs \ No newline at end of file +*.prefs + +reports diff --git a/README.md b/README.md new file mode 100644 index 0000000..1656efe --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# pop-integration-int + +This repository contains e2e test framework for POP protocol. + +To run all tests: `./gradlew build test` + +Each test uses testcontainers to start docker images of NodeCore, APM, BTCSQ (and other stuff) to test everything end-to-end. + +## Environment variables + +Tests will use either default versions (see [BaseIntegrationTest.kt](./src/main/kotlin/testframework/BaseIntegrationTest.kt)) or specified in following environment variables: + +- INT_NODECORE_VERSION - veriblock version (https://hub.docker.com/r/veriblock/nodecore) +- INT_APM_VERSION - APM version (https://hub.docker.com/r/veriblock/altchain-pop-miner) +- INT_BTCSQ_VERSION - BTCSQ version (https://hub.docker.com/r/veriblock/btcsq) +- INT_LOG_LEVEL: "DEBUG" + +Example: [ci.yml](./.github/workflows/main.yml) + +## Development guide + +Each service (APM/NC/BTCSQ) is organized into "wrapper" - [./src/main/kotlin/testframework/wrapper](./src/main/kotlin/testframework/wrapper). +Each wrapper is expected to provide all necessary tools to start / stop and control wrapped tool via API (HTTP/GRPC...). + +Every test defines a "topology" - a set of "wrappers" to run, their relations (for example, APM should be added after NC and BTCSQ). + +[ExampleTest.kt](./src/test/kotlin/functional/ExampleTest.kt) \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index d411479..b23fc41 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,12 @@ -import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL -import org.gradle.api.tasks.testing.logging.TestExceptionFormat.SHORT import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import com.adarshr.gradle.testlogger.TestLoggerExtension +import com.adarshr.gradle.testlogger.TestLoggerPlugin +import com.adarshr.gradle.testlogger.theme.ThemeType plugins { + idea kotlin("jvm") version "1.5.31" + id("com.adarshr.test-logger") version "3.1.0" } group = "org.veriblock" @@ -14,32 +17,32 @@ repositories { maven("https://jitpack.io") } -val kotestVersion = "4.6.3" +val kotestVersion = "5.0.1" val coroutinesVersion = "1.5.2-native-mt" -val log4jVersion= "2.14.1" -val ktorVersion = "1.6.4" +val log4jVersion = "2.16.0" +val ktorVersion = "1.6.0" dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutinesVersion") - implementation("io.github.microutils:kotlin-logging:2.0.11") + implementation("io.github.microutils:kotlin-logging:2.1.16") implementation("org.apache.logging.log4j:log4j-api:$log4jVersion") implementation("org.apache.logging.log4j:log4j-core:$log4jVersion") implementation("org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion") implementation("com.fasterxml.jackson.core:jackson-databind:2.13.0") - implementation("com.google.code.gson:gson:2.8.8") + implementation("com.google.code.gson:gson:2.8.9") implementation("io.ktor:ktor-client:$ktorVersion") implementation("io.ktor:ktor-client-core-jvm:$ktorVersion") implementation("io.ktor:ktor-client-cio:$ktorVersion") implementation("io.ktor:ktor-client-auth:$ktorVersion") implementation("io.ktor:ktor-client-gson:$ktorVersion") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.3.0") - implementation("org.testcontainers:testcontainers:1.16.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.3.1") + implementation("org.testcontainers:testcontainers:1.16.2") implementation("com.google.guava:guava:31.0.1-jre") implementation("com.github.veriblock.nodecore:nodecore-grpc:v0.4.13-rc.2") implementation("com.github.veriblock.nodecore:veriblock-extensions:v0.4.13-rc.2") implementation("com.github.veriblock.nodecore:vpm-mock:v0.4.13-rc.2") - implementation("io.github.microutils:kotlin-logging:2.0.11") + implementation("io.github.microutils:kotlin-logging:2.1.16") testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion") testImplementation("io.kotest:kotest-assertions-core:$kotestVersion") testImplementation(kotlin("test")) @@ -48,8 +51,6 @@ dependencies { tasks.test { useJUnitPlatform() maxParallelForks = 1 - testLogging.showStandardStreams = true - testLogging.exceptionFormat = SHORT } tasks.withType { @@ -62,4 +63,44 @@ tasks.withType { tasks.withType { useJUnitPlatform() + + reports { + html.required.set(true) + junitXml.required.set(true) + junitXml.apply { + isOutputPerTestCase = true // defaults to false + mergeReruns.set(true) // defaults to false + } + } +} + +plugins.withType { + configure { + theme = ThemeType.MOCHA + showExceptions = true + showStackTraces = true + showFullStackTraces = false + showCauses = true + slowThreshold = 20000 + showSummary = true + showSimpleNames = false + showPassed = true + showSkipped = true + showFailed = true + showStandardStreams = true + showPassedStandardStreams = false + showSkippedStandardStreams = false + showFailedStandardStreams = true + logLevel = LogLevel.LIFECYCLE + } +} + +reporting.baseDir = file("$buildDir/reports") +project.setProperty("testResultsDirName", "$buildDir/test-results") + +tasks.register("showDirs") { + doLast { + logger.quiet(rootDir.toPath().relativize((project.properties["reportsDir"] as File).toPath()).toString()) + logger.quiet(rootDir.toPath().relativize((project.properties["testResultsDir"] as File).toPath()).toString()) + } } diff --git a/gradle.properties b/gradle.properties index 7fc6f1f..a3293d9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,2 @@ kotlin.code.style=official + diff --git a/src/main/kotlin/nodecore/api/FaucetApi.kt b/src/main/kotlin/nodecore/api/FaucetApi.kt deleted file mode 100644 index 618add8..0000000 --- a/src/main/kotlin/nodecore/api/FaucetApi.kt +++ /dev/null @@ -1,20 +0,0 @@ -package nodecore.api - -import io.ktor.client.request.get -import io.ktor.client.request.parameter - -//private val apiConfig = config.extract("faucetApi") -// ?: HttpApiConfig("http://95.217.67.120/alt-integration/api/v1.0/faucet") - -private val httpClient = createHttpClient() - -object FaucetApi { - suspend fun getCoins(address: String): FaucetResponse = httpClient.get("http://95.217.67.120/alt-integration/api/v1.0/faucet") { // TODO: config URL - parameter("address", address) - }.handle() -} - -data class FaucetResponse( - val success: Boolean, - val txIds: List -) diff --git a/src/main/kotlin/nodecore/api/NodeCoreApi.kt b/src/main/kotlin/nodecore/api/NodeCoreApi.kt deleted file mode 100644 index 07e3475..0000000 --- a/src/main/kotlin/nodecore/api/NodeCoreApi.kt +++ /dev/null @@ -1,157 +0,0 @@ -package nodecore.api - -import io.ktor.client.request.post -import kotlinx.coroutines.delay -import org.slf4j.LoggerFactory -import java.util.Collections.EMPTY_MAP -import kotlin.math.absoluteValue -import kotlin.math.roundToInt - -//private val apiConfig = config.extract("nodeCoreApi") -// ?: HttpApiConfig("http://localhost:10600/api") - -private val httpClient = createHttpClient() - -private val logger = LoggerFactory.getLogger("NodeCoreApi") - -object NodeCoreApi { - suspend fun getInfo(): VbkInfo = performRequest( - method = "getinfo" - ) - - suspend fun getStateInfo(): StateInfo = performRequest( - method = "getstateinfo" - ) - - suspend fun getLastBlock(): BlockHeaderContainer = performRequest( - method = "getlastblock" - ) - - suspend fun getLastBitcoinBlockAtVeriBlockBlock(vbkHash: String): BtcBlockData = performRequest( - method = "getlastbitcoinblockatveriblockblock", - params = mapOf( - "vbkBlockHash" to vbkHash - ) - ) - - suspend fun getNewAddress(count: Int = 1): GetNewAddressReply = performRequest( - method = "getnewaddress", - params = mapOf( - "count" to count - ) - ) - - suspend fun getBlocksByHeight(searchLength: Int, heights: List): GetBlocksReply = performRequest( - method = "getblocks", - params = mapOf( - "searchLength" to searchLength, - "filters" to heights.map { - mapOf("index" to it) - } - ) - ) - - suspend fun getBlocksByHash(searchLength: Int, hashes: List): GetBlocksReply = performRequest( - method = "getblocks", - params = mapOf( - "searchLength" to searchLength, - "filters" to listOf(hashes.map { - mapOf("hash" to it) - }) - ) - ) - - suspend fun getTransaction(txId: String): GetTransactionsReply = performRequest( - method = "gettransactions", - params = mapOf( - "searchLength" to 0, - "ids" to listOf(txId) - ) - ) - - suspend fun sendCoins(sourceAddress: String, amounts: List): SendCoinsReply = performRequest( - method = "sendcoins", - params = mapOf( - "sourceAddress" to sourceAddress, - "amounts" to amounts - ) - ) - - suspend fun getPendingTransactions(): GetPendingTransactionsReply = performRequest( - method = "getpendingtransactions" - ) - - suspend fun checkConnection() { - while (true) { - try { - getInfo() - break - } catch (e: Exception) { - logger.warn("NodeCore not available yet, trying again in 10s...") - delay(10_000L) - } - } - } - - suspend fun checkSyncStatus() { - var previousState = 0 - var previousSpeed = 0.0 - while (true) { - try { - val stateInfo = getStateInfo() - val syncState = stateInfo.networkHeight - stateInfo.localBlockchainHeight - if (syncState.absoluteValue >= 5) { - val syncSummary = if (previousState > 0) { - val increment = previousState - syncState - val speed = increment / 5.0 - val fixedSpeed = if (previousSpeed > 0) { - (speed + previousSpeed) / 2.0 - } else { - speed - } - previousSpeed = fixedSpeed - val remainingTimeSeconds = (syncState / fixedSpeed).roundToInt() - val remainingTime = if (remainingTimeSeconds >= 60) { - "${remainingTimeSeconds / 60}m ${remainingTimeSeconds % 60}s" - } else { - "${remainingTimeSeconds}s" - } - " Download Speed=${String.format("%.2f", fixedSpeed)}Bk/s Remaining Time=$remainingTime" - } else { - "" - } - logger.warn("Waiting for NodeCore to synchronize. $syncState blocks left (LocalHeight=${stateInfo.localBlockchainHeight} NetworkHeight=${stateInfo.networkHeight}$syncSummary)") - previousState = syncState - - delay(5_000L) - continue - } - - logger.info("NodeCore is synchronized.. continuing.") - break - } catch (e: Exception) { - logger.warn("NodeCore not available, trying again in 5s...") - delay(5_000L) - } - } - } - - suspend fun waitUntilBlock(blockHeight: Int) { - while (true) { - val info = getInfo() - val tip = info.lastBlock.number - logger.info("Current Tip: $tip") - if (tip >= blockHeight) { - return - } - delay(30_0000) - } - } -} - -private suspend inline fun performRequest( - method: String, - params: Any? = EMPTY_MAP -): T = httpClient.post("http://localhost:10600/api") { // TODO: config URL - body = JsonRpcRequestBody(method, params).toJson() -}.handle() diff --git a/src/main/kotlin/nodecore/api/SyncNodeCoreApi.kt b/src/main/kotlin/nodecore/api/SyncNodeCoreApi.kt deleted file mode 100644 index 943b9dc..0000000 --- a/src/main/kotlin/nodecore/api/SyncNodeCoreApi.kt +++ /dev/null @@ -1,35 +0,0 @@ -package nodecore.api - -import io.ktor.client.request.post -import kotlinx.coroutines.runBlocking -//import nodecore.config - -//private val apiConfig = config.extract("nodeCoreApi") -// ?: HttpApiConfig("http://localhost:10600/api") - -private val httpClient = createHttpClient() - -object SyncNodeCoreApi { - fun getInfo(): VbkInfo = runBlocking { - httpClient.post("http://localhost:10600/api") { // TODO: config URL - body = JsonRpcRequestBody("getinfo").toJson() - }.handle() - } - - fun getLastBlock(): BlockHeaderContainer = runBlocking { - httpClient.post("http://localhost:10600/api") { // TODO: config URL - body = JsonRpcRequestBody("getlastblock").toJson() - }.handle() - } - - fun getLastBitcoinBlockAtVeriBlockBlock(vbkHash: String): BtcBlockData = runBlocking { - httpClient.post("http://localhost:10600/api") { // TODO: config URL - body = JsonRpcRequestBody( - "getlastbitcoinblockatveriblockblock", - mapOf( - "vbkBlockHash" to vbkHash - ) - ).toJson() - }.handle() - } -} diff --git a/src/main/kotlin/nodecore/testframework/wrapper/nodecore/Entities.kt b/src/main/kotlin/nodecore/testframework/wrapper/nodecore/Entities.kt deleted file mode 100644 index 320854b..0000000 --- a/src/main/kotlin/nodecore/testframework/wrapper/nodecore/Entities.kt +++ /dev/null @@ -1,83 +0,0 @@ -package nodecore.testframework.wrapper.nodecore - -import kotlinx.serialization.Serializable -import nodecore.api.VbkBlockData - -@Serializable -data class Result( - val error: Boolean, - val code: String, - val message: String, - val details: String -) { - override fun toString(): String { - return "[$code] $message: $details" - } -} - -@Serializable -data class ProtocolReply( - val success: Boolean, - val results: List -) - -@Serializable -data class VbkInfoAddress ( - val address: String -) - -@Serializable -data class VbkInfo( - val lastBlock: VbkBlockData, - val defaultAddress: VbkInfoAddress -) - -@Serializable -data class NodeRequest( - val endpoint: List -) - -@Serializable -data class Endpoint( - val address: String, - val port: Int -) - -@Serializable -data class GenerateBlocksReply( - val result: Result?, - val hash: List -) - -@Serializable -data class BlockHeader( - val header: String, - val hash: String -) - -@Serializable -data class GetLastBlockReply( - val header: BlockHeader -) - -@Serializable -data class GetLastBitcoinBlockReply( - val header: String, - val height: Int, - val hash: String -) - -@Serializable -data class BitcoinBlockHeader( - val header: String -) - -@Serializable -data class SubmitPopRequest( - val endorsedBlockHeader: String, - val bitcoinTransaction: String, - val bitcoinMerklePathToRoot: String, - val bitcoinBlockHeaderOfProof: BitcoinBlockHeader, - val contextBitcoinBlockHeaders: List, - val address: String -) diff --git a/src/main/kotlin/nodecore/testframework/BaseIntegrationTest.kt b/src/main/kotlin/testframework/BaseIntegrationTest.kt similarity index 52% rename from src/main/kotlin/nodecore/testframework/BaseIntegrationTest.kt rename to src/main/kotlin/testframework/BaseIntegrationTest.kt index 3989c87..61e5f7b 100644 --- a/src/main/kotlin/nodecore/testframework/BaseIntegrationTest.kt +++ b/src/main/kotlin/testframework/BaseIntegrationTest.kt @@ -1,15 +1,18 @@ -package nodecore.testframework +package testframework +import java.io.File import kotlinx.coroutines.* -import nodecore.testframework.wrapper.apm.ApmSettings -import nodecore.testframework.wrapper.apm.TestAPM -import nodecore.testframework.wrapper.nodecore.NodecoreSettings -import nodecore.testframework.wrapper.nodecore.TestNodecore -import nodecore.testframework.wrapper.vbtc.TestVBTC -import nodecore.testframework.wrapper.vbtc.VBtcSettings -import org.junit.ComparisonFailure +import testframework.wrapper.apm.ApmSettings +import testframework.wrapper.apm.TestAPM +import testframework.wrapper.btcsq.BtcsqSettings +import testframework.wrapper.nodecore.NodecoreSettings +import testframework.wrapper.nodecore.TestNodecore import org.slf4j.LoggerFactory -import java.io.File +import testframework.wrapper.btcsq.TestBtcsq + +val nodecoreVersion = System.getenv("INT_NODECORE_VERSION") ?: "0.4.13-rc.14" +val apmVersion = System.getenv("INT_APM_VERSION") ?: "0.4.13-rc.13" +val btcsqVersion = System.getenv("INT_BTCSQ_VERSION") ?: "master-47363b0" enum class TestStatus(val state: String) { PASSED("PASSED"), @@ -24,8 +27,8 @@ abstract class BaseIntegrationTest { // all instances of Altchain POP miners val apms = ArrayList() /* empty by default */ val nodecores = ArrayList() /* empty by default */ - val vbtcs = ArrayList() /* empty by default */ - val logger = LoggerFactory.getLogger("BaseIntegrationTest") + val btcsqs = ArrayList() /* empty by default */ + val logger = LoggerFactory.getLogger("Test") var baseNodecoreRpcPort = (23300) var baseNodecoreP2pPort = (23200) var baseNodecoreHttpPort = (23100) @@ -35,8 +38,8 @@ abstract class BaseIntegrationTest { var baseBtcRpcPort = (25200) var baseBtcZmqPort = (25300) val baseDir: File = createTempDir( - prefix = "veriblock_${System.currentTimeMillis()}_", - ) + prefix = "veriblock_${System.currentTimeMillis()}_", + ) var status: TestStatus = TestStatus.FAILED @@ -50,52 +53,60 @@ abstract class BaseIntegrationTest { // override this function to define network services abstract suspend fun setup() - fun addNodecore(version: String = "0.4.13-rc.12"): TestNodecore { - val ncSettings = NodecoreSettings( - peerPort = baseNodecoreP2pPort++, - rpcPort = baseNodecoreRpcPort++, - httpPort = baseNodecoreHttpPort++, - baseDir = baseDir, - index = nodecores.size, - progpowTime = progpowStartupTime, - network = "regtest" - ) + fun addNodecore(version: String = nodecoreVersion): TestNodecore { + val ncSettings = + NodecoreSettings( + peerPort = baseNodecoreP2pPort++, + rpcPort = baseNodecoreRpcPort++, + httpPort = baseNodecoreHttpPort++, + baseDir = baseDir, + index = nodecores.size, + progpowTime = progpowStartupTime, + network = "regtest" + ) val nc = TestNodecore(ncSettings, version) nodecores.add(nc) - logger.info("Setting up ${nc.name} with network=${ncSettings.network}") + logger.info("Setting up ${nc.name}:${nodecoreVersion} with network=${ncSettings.network}") return nc } - fun addAPM(node: TestNodecore, btcaltchains: List = emptyList(), version: String = "0.4.13-rc.11"): TestAPM { - val apmSettings = ApmSettings( - index = apms.size, - p2pPort = baseApmP2pPort++, - httpPort = baseApmHttpPort++, - nodecore = node, - baseDir = baseDir, - btcaltchains = btcaltchains - ) + fun addAPM( + node: TestNodecore, + btcaltchains: List = emptyList(), + version: String = apmVersion + ): TestAPM { + val apmSettings = + ApmSettings( + index = apms.size, + p2pPort = baseApmP2pPort++, + httpPort = baseApmHttpPort++, + nodecore = node, + baseDir = baseDir, + btcaltchains = btcaltchains + ) val apm = TestAPM(apmSettings, version) apms.add(apm) - logger.info("Setting up ${apm.name} connected to ${node.name} and BTC plugins: ${btcaltchains.joinToString { it.name() }}") + logger.info( + "Setting up ${apm.name}:${apmVersion} connected to ${node.name} and BTC plugins: ${btcaltchains.joinToString { it.name() }}" + ) return apm } - fun addVBTC(version: String = "master-734a3a0"): TestVBTC { - val settings = VBtcSettings( + fun addBtcsq(version: String = btcsqVersion): TestBtcsq { + val settings = BtcsqSettings( p2pPort = baseBtcP2pPort++, rpcPort = baseBtcRpcPort++, zmqPort = baseBtcZmqPort++, - index = vbtcs.size, + index = btcsqs.size, baseDir = baseDir ) - val vbtc = TestVBTC(settings, version) - vbtcs.add(vbtc) - logger.info("Setting up ${vbtc.name}") - return vbtc + val node = TestBtcsq(settings, version) + btcsqs.add(node) + logger.info("Setting up ${node.name}:${btcsqVersion}") + return node } // entry point for every test @@ -112,22 +123,28 @@ abstract class BaseIntegrationTest { status = TestStatus.PASSED willCleanup = shouldCleanup exitCode = 0 - } catch (e: ComparisonFailure) { + } catch (e: AssertionError) { logger.error("ASSERTION FAILED") e.printStackTrace() - status = TestStatus.FAILED - exitCode = 1 + fail(e) } catch (e: Exception) { logger.error("UNHANDLED EXCEPTION") - e.printStackTrace() - status = TestStatus.FAILED - exitCode = 1 + fail(e) } finally { - return shutdown() + shutdown() } + + return 0 + } + + private fun fail(reason: Exception) { + throw RuntimeException("Test failed: \n${reason}") + } + private fun fail(reason: Error) { + throw RuntimeException("Test failed: \n${reason}") } - private fun shutdown(): Int { + private fun shutdown() { logger.info("Test ${status.state}!") logger.info("Logs are available in ${baseDir.absolutePath}") logger.info("Shutting down environment...") @@ -141,16 +158,13 @@ abstract class BaseIntegrationTest { runBlocking { apms.map { async { it.close() } } + - nodecores.map { async { it.close() } } + - vbtcs.map { async { it.close() } } - .awaitAll() + nodecores.map { async { it.close() } } + + btcsqs.map { async { it.close() } }.awaitAll() } nodecores.clear() apms.clear() - vbtcs.clear() - - return exitCode + btcsqs.clear() } suspend fun syncAllApms(apms: List, timeout: Long = 60_000 /*ms*/) { @@ -158,59 +172,53 @@ abstract class BaseIntegrationTest { try { waitUntil(timeout = timeout) { - statuses = apms - .map { it.http.getMinerInfo().status.isReady } - - // if all getInfo returned same block, - // then we consider syncAll succeeded + statuses = apms.map { it.http.getMinerInfo().status.isReady } return@waitUntil statuses.all { it } } } catch (e: TimeoutCancellationException) { - logger.error("syncBlocks failed: ${statuses.joinToString { "\n" }}") + logger.error("syncAllApms failed: ${statuses.joinToString { "\n" }}") throw e } } suspend fun syncAllNodecores(nodecores: List, timeout: Long = 60_000 /*ms*/) { - syncBlocks(nodecores, timeout) - syncMempools(nodecores, timeout) + syncNodecoreBlocks(nodecores, timeout) + syncNodecoreMempools(nodecores, timeout) } - - suspend fun syncBlocks(nodecores: List, timeout: Long = 60_000 /*ms*/) { + + suspend fun syncNodecoreBlocks(nodecores: List, timeout: Long = 60_000 /*ms*/) { var hashes: List = emptyList() - + try { waitUntil(timeout = timeout) { - hashes = nodecores - .map { it.http.getInfo().lastBlock.hash } - + hashes = nodecores.map { it.http.getInfo().lastBlock.hash } + // if all getInfo returned same block, // then we consider syncAll succeeded return@waitUntil hashes.toSet().size == 1 } } catch (e: TimeoutCancellationException) { - logger.error("syncBlocks failed: ${hashes.joinToString { "\n" }}") + logger.error("syncNodecoreBlocks failed: ${hashes.joinToString { "\n" }}") throw e } } - - suspend fun syncMempools(nodecores: List, timeout: Long = 60_000 /*ms*/) { + + suspend fun syncNodecoreMempools(nodecores: List, timeout: Long = 60_000 /*ms*/) { var transactions: List> = emptyList() - + try { waitUntil(timeout = timeout) { - transactions = nodecores - .map { - it.http.getPendingTransactions() - .transactions.map { it.txId }.sorted() + transactions = + nodecores.map { + it.http.getPendingTransactions().transactions.map { it.txId }.sorted() } - + // if all getInfo returned same block, // then we consider syncAll succeeded return@waitUntil transactions.toSet().size == 1 } } catch (e: TimeoutCancellationException) { - logger.error("syncMempools failed: ${transactions.joinToString { "\n" }}") + logger.error("syncNodecoreMempools failed: ${transactions.joinToString { "\n" }}") throw e } } diff --git a/src/main/kotlin/nodecore/testframework/BaseJsonRpcApi.kt b/src/main/kotlin/testframework/BaseJsonRpcApi.kt similarity index 68% rename from src/main/kotlin/nodecore/testframework/BaseJsonRpcApi.kt rename to src/main/kotlin/testframework/BaseJsonRpcApi.kt index e8a8324..d31c5ba 100644 --- a/src/main/kotlin/nodecore/testframework/BaseJsonRpcApi.kt +++ b/src/main/kotlin/testframework/BaseJsonRpcApi.kt @@ -1,10 +1,19 @@ -package nodecore.testframework +package testframework +import testframework.util.JsonRpcRequestBody +import testframework.util.RpcResponse +import testframework.util.handle +import testframework.util.toJson import io.ktor.client.request.* -import nodecore.api.* import org.slf4j.LoggerFactory +import testframework.util.HttpApiConfig +import testframework.util.HttpAuthConfig +import testframework.util.createHttpClient import java.util.* +/** + * All JSONRPC Apis should extend from this class. + */ open class BaseJsonRpcApi( val name: String, host: String, @@ -12,8 +21,9 @@ open class BaseJsonRpcApi( suffix: String = "", username: String = "", password: String = "", - timeoutMillis: Long = 0 + timeoutMillis: Long = 10_000L ) { + val logger = LoggerFactory.getLogger(name) protected val apiConfig = HttpApiConfig("http://${host}:${port}/${suffix}") protected val httpClient = createHttpClient(HttpAuthConfig(username, password), timeoutMillis = timeoutMillis) @@ -29,8 +39,4 @@ open class BaseJsonRpcApi( logger.debug("$name --jsonrpc--> $response") return response.handle() }.handle() - - companion object { - val logger = LoggerFactory.getLogger("BaseJsonRpcApi") - } } diff --git a/src/main/kotlin/nodecore/testframework/BtcPluginInterface.kt b/src/main/kotlin/testframework/BtcPluginInterface.kt similarity index 83% rename from src/main/kotlin/nodecore/testframework/BtcPluginInterface.kt rename to src/main/kotlin/testframework/BtcPluginInterface.kt index ef40497..7617127 100644 --- a/src/main/kotlin/nodecore/testframework/BtcPluginInterface.kt +++ b/src/main/kotlin/testframework/BtcPluginInterface.kt @@ -1,4 +1,4 @@ -package nodecore.testframework +package testframework interface BtcPluginInterface { fun name(): String @@ -6,6 +6,7 @@ interface BtcPluginInterface { fun username(): String fun password(): String fun id(): Long + fun host(): String fun port(): Int fun network(): String fun payoutDelay(): Int diff --git a/src/main/kotlin/nodecore/testframework/Exec.kt b/src/main/kotlin/testframework/Exec.kt similarity index 97% rename from src/main/kotlin/nodecore/testframework/Exec.kt rename to src/main/kotlin/testframework/Exec.kt index faa8de4..50761e3 100644 --- a/src/main/kotlin/nodecore/testframework/Exec.kt +++ b/src/main/kotlin/testframework/Exec.kt @@ -1,4 +1,4 @@ -package nodecore.testframework +package testframework import java.io.File import java.io.IOException diff --git a/src/main/kotlin/nodecore/testframework/ProcessManager.kt b/src/main/kotlin/testframework/ProcessManager.kt similarity index 98% rename from src/main/kotlin/nodecore/testframework/ProcessManager.kt rename to src/main/kotlin/testframework/ProcessManager.kt index 03611ca..d2010c2 100644 --- a/src/main/kotlin/nodecore/testframework/ProcessManager.kt +++ b/src/main/kotlin/testframework/ProcessManager.kt @@ -1,4 +1,4 @@ -package nodecore.testframework +package testframework import kotlinx.coroutines.runBlocking import org.slf4j.LoggerFactory diff --git a/src/main/kotlin/nodecore/testframework/StdStreamLogger.kt b/src/main/kotlin/testframework/StdStreamLogger.kt similarity index 56% rename from src/main/kotlin/nodecore/testframework/StdStreamLogger.kt rename to src/main/kotlin/testframework/StdStreamLogger.kt index 8a9d0ce..0ae9b88 100644 --- a/src/main/kotlin/nodecore/testframework/StdStreamLogger.kt +++ b/src/main/kotlin/testframework/StdStreamLogger.kt @@ -1,5 +1,6 @@ -package nodecore.testframework +package testframework +import org.slf4j.Logger import org.testcontainers.containers.output.OutputFrame import java.io.File import java.io.PrintWriter @@ -11,7 +12,6 @@ class StdStreamLogger( private val stderr = File(datadir, "stderr") private val stdoutwriter: PrintWriter private val stderrwriter: PrintWriter - var useConsole = false init { if (!datadir.exists()) { @@ -27,17 +27,28 @@ class StdStreamLogger( stderrwriter = stderr.printWriter() } - fun forward(): (OutputFrame) -> Unit = { + fun forward(logger: Logger, addEndl: Boolean = false): (OutputFrame) -> Unit = { when (it.type) { OutputFrame.OutputType.STDOUT -> { - if(useConsole) print(it.utf8String) - stdoutwriter.print(it.utf8String) - stdoutwriter.print('\n') + if (addEndl) { + logger.debug(it.utf8String) + stdoutwriter.println(it.utf8String) + } else { + logger.debug(it.utf8String.dropLast(1)) + stdoutwriter.print(it.utf8String) + } + stdoutwriter.flush() } OutputFrame.OutputType.STDERR -> { - if(useConsole) System.err.print(it.utf8String) - stderrwriter.print(it.utf8String) + if (addEndl) { + logger.error(it.utf8String) + stdoutwriter.println(it.utf8String) + } else { + logger.error(it.utf8String.dropLast(1)) + stdoutwriter.print(it.utf8String) + } + stderrwriter.flush() } else -> Unit diff --git a/src/main/kotlin/nodecore/testframework/Util.kt b/src/main/kotlin/testframework/Util.kt similarity index 83% rename from src/main/kotlin/nodecore/testframework/Util.kt rename to src/main/kotlin/testframework/Util.kt index 3537894..c2e4e5d 100644 --- a/src/main/kotlin/nodecore/testframework/Util.kt +++ b/src/main/kotlin/testframework/Util.kt @@ -1,23 +1,24 @@ -package nodecore.testframework +package testframework import com.google.protobuf.ByteString import kotlinx.coroutines.delay import nodecore.api.grpc.RpcEvent import nodecore.api.grpc.utilities.ByteStringUtility -import nodecore.testframework.wrapper.apm.TestAPM -import nodecore.testframework.wrapper.nodecore.BitcoinBlockHeader -import nodecore.testframework.wrapper.nodecore.Endpoint -import nodecore.testframework.wrapper.nodecore.SubmitPopRequest -import nodecore.testframework.wrapper.nodecore.TestNodecore import org.testcontainers.containers.GenericContainer import org.testcontainers.utility.DockerImageName import org.veriblock.core.wallet.AddressKeyGenerator import org.veriblock.sdk.models.Address import org.veriblock.sdk.models.VeriBlockPopTransaction +import testframework.wrapper.apm.TestAPM +import testframework.wrapper.nodecore.BitcoinBlockHeader +import testframework.wrapper.nodecore.Endpoint +import testframework.wrapper.nodecore.SubmitPopRequest +import testframework.wrapper.nodecore.TestNodecore import java.net.ServerSocket import java.security.KeyPair +import java.security.SecureRandom import java.util.concurrent.TimeoutException - +import kotlin.random.Random fun isPortAvailable(port: Int): Boolean { try { @@ -31,11 +32,18 @@ fun isPortAvailable(port: Int): Boolean { return false } +fun getNextAvailablePort(basePort: Int): Int { + var port = basePort + while (!isPortAvailable(port++)) { + } + return port +} + suspend fun connectNodes(a: TestNodecore, b: TestNodecore) { a.http.addNode( listOf( Endpoint( - address = "127.0.0.1", + address = b.getAddress(), port = b.settings.peerPort ) ) @@ -62,7 +70,13 @@ fun buildMessage( .build() // sleep until the predicate resolves to be True -suspend fun waitUntil(attempts: Long = Long.MAX_VALUE, timeout: Long = 60_000L, delay: Long = 1_000L, predicate: suspend () -> Boolean) { +suspend fun waitUntil( + attempts: Long = Long.MAX_VALUE, + timeout: Long = 60_000L, + delay: Long = 1_000L, + message: String = "", + predicate: suspend () -> Boolean +) { var attempt = 0 val timeEnd = System.currentTimeMillis() + timeout while (attempt++ < attempts && System.currentTimeMillis() < timeEnd) { @@ -74,7 +88,9 @@ suspend fun waitUntil(attempts: Long = Long.MAX_VALUE, timeout: Long = 60_000L, } // print the cause of failure - val s: StringBuilder = StringBuilder().append("waitUntil failed! ") + val s: StringBuilder = StringBuilder().append("waitUntil failed!\n") + s.append(message) + s.append("\n") if (attempt >= attempts) { s.append("Predicate not true after $attempt attempts") } else if (System.currentTimeMillis() >= timeEnd) { diff --git a/src/main/kotlin/nodecore/api/HttpClient.kt b/src/main/kotlin/testframework/util/HttpClient.kt similarity index 71% rename from src/main/kotlin/nodecore/api/HttpClient.kt rename to src/main/kotlin/testframework/util/HttpClient.kt index 6428e80..74fa565 100644 --- a/src/main/kotlin/nodecore/api/HttpClient.kt +++ b/src/main/kotlin/testframework/util/HttpClient.kt @@ -1,10 +1,11 @@ -package nodecore.api// VeriBlock Blockchain Project +// VeriBlock Blockchain Project // Copyright 2017-2018 VeriBlock, Inc // Copyright 2018-2019 Xenios SEZC // All rights reserved. // https://www.veriblock.org // Distributed under the MIT software license, see the accompanying // file LICENSE or http://www.opensource.org/licenses/mit-license.php. +package testframework.util import com.google.gson.Gson import com.google.gson.JsonElement @@ -12,7 +13,7 @@ import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO import io.ktor.client.features.* import io.ktor.client.features.auth.Auth -import io.ktor.client.features.auth.providers.basic +import io.ktor.client.features.auth.providers.* import io.ktor.client.features.json.JsonFeature import io.ktor.http.ContentType import java.lang.reflect.Type @@ -31,7 +32,11 @@ class HttpAuthConfig( val password: String ) -fun createHttpClient(authConfig: HttpAuthConfig? = null, contentTypes: List? = null, timeoutMillis: Long = 0) = HttpClient(CIO) { +fun createHttpClient( + authConfig: HttpAuthConfig? = null, + contentTypes: List? = null, + timeoutMillis: Long = 0 +) = HttpClient(CIO) { install(JsonFeature) { if (contentTypes != null) { acceptContentTypes = contentTypes @@ -40,8 +45,12 @@ fun createHttpClient(authConfig: HttpAuthConfig? = null, contentTypes: List $ret") return ret } - - - companion object { - val logger = LoggerFactory.getLogger("ApmHttpApi") - } } diff --git a/src/main/kotlin/nodecore/testframework/wrapper/apm/Entities.kt b/src/main/kotlin/testframework/wrapper/apm/Entities.kt similarity index 97% rename from src/main/kotlin/nodecore/testframework/wrapper/apm/Entities.kt rename to src/main/kotlin/testframework/wrapper/apm/Entities.kt index 285b9c2..011dc43 100644 --- a/src/main/kotlin/nodecore/testframework/wrapper/apm/Entities.kt +++ b/src/main/kotlin/testframework/wrapper/apm/Entities.kt @@ -1,4 +1,4 @@ -package nodecore.testframework.wrapper.apm +package testframework.wrapper.apm import kotlinx.serialization.Serializable diff --git a/src/main/kotlin/nodecore/testframework/wrapper/apm/TestAPM.kt b/src/main/kotlin/testframework/wrapper/apm/TestAPM.kt similarity index 72% rename from src/main/kotlin/nodecore/testframework/wrapper/apm/TestAPM.kt rename to src/main/kotlin/testframework/wrapper/apm/TestAPM.kt index 2fa31e8..a188545 100644 --- a/src/main/kotlin/nodecore/testframework/wrapper/apm/TestAPM.kt +++ b/src/main/kotlin/testframework/wrapper/apm/TestAPM.kt @@ -1,13 +1,14 @@ -package nodecore.testframework.wrapper.apm +package testframework.wrapper.apm import kotlinx.coroutines.runBlocking -import nodecore.testframework.StdStreamLogger -import nodecore.testframework.BtcPluginInterface -import nodecore.testframework.KGenericContainer -import nodecore.testframework.waitUntil -import nodecore.testframework.wrapper.nodecore.TestNodecore +import testframework.StdStreamLogger +import testframework.BtcPluginInterface +import testframework.KGenericContainer +import testframework.waitUntil +import testframework.wrapper.nodecore.TestNodecore import org.slf4j.LoggerFactory import org.testcontainers.containers.BindMode +import org.testcontainers.containers.wait.strategy.Wait import java.io.Closeable import java.io.File @@ -31,16 +32,29 @@ class TestAPM( val datadir = File(settings.baseDir, name) val stdlog = StdStreamLogger(datadir) val container = KGenericContainer("veriblock/altchain-pop-miner:$version") - .withNetworkMode("host") .withNetworkAliases(name) .withFileSystemBind(datadir.absolutePath, "/data", BindMode.READ_WRITE) .withCreateContainerCmdModifier { it.withTty(true) } .withEnv("APM_LOG_LEVEL", "DEBUG") - .withEnv("APM_CONSOLE_LOG_LEVEL", "DEBUG") + .withEnv("APM_CONSOLE_LOG_LEVEL", "INFO") + .waitingFor(Wait.forLogMessage(".*Starting miner.*", 1)) + fun getAddress(): String { + // we can take IP only on running containers + assert(container.isRunning) + return container + .containerInfo + .networkSettings + .networks + .entries + .first() + .value + .ipAddress!! + } + val applicationConf = File(datadir, "application.conf") - val http = ApmHttpApi(name, container.host, settings.httpPort) + lateinit var http: ApmHttpApi val vbkAddress by lazy { runBlocking { http.getMinerInfo().vbkAddress @@ -55,7 +69,7 @@ class TestAPM( pluginKey: btc id: ${it.id()} name: "${it.name()}" - host: "http://127.0.0.1:${it.port()}" + host: "http://${it.host()}:${it.port()}" auth: { username: "${it.username()}" password: "${it.password()}" @@ -78,9 +92,10 @@ class TestAPM( feePerByte: 1000 maxFee: 10000000 api { + host: 0.0.0.0 port: ${settings.httpPort} } - connectDirectlyTo: ["127.0.0.1:${settings.nodecore.settings.peerPort}"] + connectDirectlyTo: ["${settings.nodecore.getAddress()}:${settings.nodecore.settings.peerPort}"] network: ${settings.nodecore.settings.network} dataDir: /data progPowGenesis: true @@ -95,7 +110,10 @@ class TestAPM( suspend fun start() { container.start() - container.followOutput(stdlog.forward()) + container.followOutput(stdlog.forward(logger, true)) + logger.info("IP: ${getAddress()}") + http = ApmHttpApi(name, getAddress(), settings.httpPort) + waitForHttpApiAvailability() } @@ -109,7 +127,7 @@ class TestAPM( } private suspend fun waitForHttpApiAvailability() { - waitUntil(timeout = 30_000L, delay = 2_000L) { + waitUntil(timeout = 60_000L, delay = 5_000L) { try { http.getMinerInfo() return@waitUntil true diff --git a/src/main/kotlin/nodecore/testframework/wrapper/vbtc/VBTCApi.kt b/src/main/kotlin/testframework/wrapper/btcsq/BtcsqApi.kt similarity index 89% rename from src/main/kotlin/nodecore/testframework/wrapper/vbtc/VBTCApi.kt rename to src/main/kotlin/testframework/wrapper/btcsq/BtcsqApi.kt index 708996b..4ad1ba6 100644 --- a/src/main/kotlin/nodecore/testframework/wrapper/vbtc/VBTCApi.kt +++ b/src/main/kotlin/testframework/wrapper/btcsq/BtcsqApi.kt @@ -1,8 +1,8 @@ -package nodecore.testframework.wrapper.vbtc +package testframework.wrapper.btcsq -import nodecore.testframework.BaseJsonRpcApi +import testframework.BaseJsonRpcApi -class VBTCApi( +class BtcsqApi( name: String, host: String, port: Int, diff --git a/src/main/kotlin/nodecore/testframework/wrapper/vbtc/VBtcSettings.kt b/src/main/kotlin/testframework/wrapper/btcsq/BtcsqSettings.kt similarity index 84% rename from src/main/kotlin/nodecore/testframework/wrapper/vbtc/VBtcSettings.kt rename to src/main/kotlin/testframework/wrapper/btcsq/BtcsqSettings.kt index feb1edc..bee280c 100644 --- a/src/main/kotlin/nodecore/testframework/wrapper/vbtc/VBtcSettings.kt +++ b/src/main/kotlin/testframework/wrapper/btcsq/BtcsqSettings.kt @@ -1,8 +1,8 @@ -package nodecore.testframework.wrapper.vbtc +package testframework.wrapper.btcsq import java.io.File -class VBtcSettings( +class BtcsqSettings( val p2pPort: Int, val rpcPort: Int, val zmqPort: Int, diff --git a/src/main/kotlin/nodecore/testframework/wrapper/vbtc/Entities.kt b/src/main/kotlin/testframework/wrapper/btcsq/Entities.kt similarity index 96% rename from src/main/kotlin/nodecore/testframework/wrapper/vbtc/Entities.kt rename to src/main/kotlin/testframework/wrapper/btcsq/Entities.kt index 3a71aeb..d9462c9 100644 --- a/src/main/kotlin/nodecore/testframework/wrapper/vbtc/Entities.kt +++ b/src/main/kotlin/testframework/wrapper/btcsq/Entities.kt @@ -1,4 +1,4 @@ -package nodecore.testframework.wrapper.vbtc +package testframework.wrapper.btcsq import kotlinx.serialization.Serializable diff --git a/src/main/kotlin/nodecore/testframework/wrapper/vbtc/TestVBTC.kt b/src/main/kotlin/testframework/wrapper/btcsq/TestBtcsq.kt similarity index 70% rename from src/main/kotlin/nodecore/testframework/wrapper/vbtc/TestVBTC.kt rename to src/main/kotlin/testframework/wrapper/btcsq/TestBtcsq.kt index f14d485..ec8a714 100644 --- a/src/main/kotlin/nodecore/testframework/wrapper/vbtc/TestVBTC.kt +++ b/src/main/kotlin/testframework/wrapper/btcsq/TestBtcsq.kt @@ -1,17 +1,18 @@ -package nodecore.testframework.wrapper.vbtc +package testframework.wrapper.btcsq import kotlinx.coroutines.runBlocking -import nodecore.testframework.StdStreamLogger -import nodecore.testframework.BtcPluginInterface -import nodecore.testframework.KGenericContainer -import nodecore.testframework.waitUntil +import testframework.StdStreamLogger +import testframework.BtcPluginInterface +import testframework.KGenericContainer +import testframework.waitUntil import org.slf4j.LoggerFactory import org.testcontainers.containers.BindMode +import org.testcontainers.containers.wait.strategy.Wait import java.io.Closeable import java.io.File -class TestVBTC( - val settings: VBtcSettings, +class TestBtcsq( + val settings: BtcsqSettings, version: String ): BtcPluginInterface, Closeable, AutoCloseable { @@ -21,20 +22,26 @@ class TestVBTC( val stdlog = StdStreamLogger(datadir) val container = KGenericContainer("veriblock/btcsq:$version") .withNetworkAliases(name) - .withNetworkMode("host") - .withFileSystemBind(datadir.absolutePath, "/home/vbitcoin/.vbitcoin", BindMode.READ_WRITE) + .withFileSystemBind(datadir.absolutePath, "/home/btcsq/.btcsq", BindMode.READ_WRITE) .withCommand("btcsqd") + .waitingFor(Wait.forLogMessage(".*tree best height =.*", 3)) + + fun getAddress(): String { + // we can take IP only on running containers + assert(container.isRunning) + return container + .containerInfo + .networkSettings + .networks + .entries + .first() + .value + .ipAddress!! + } val conf = File(datadir, "btcsq.conf") - val rpc = VBTCApi( - name, - container.host, - settings.rpcPort, - settings.username, - settings.password, - 60000 - ) + lateinit var rpc: BtcsqApi init { @@ -68,6 +75,9 @@ class TestVBTC( rpcpassword=${settings.password} poplogverbosity=info + debug=1 + debugexclude=leveldb + debugexclude=libevent [regtest] port=${settings.p2pPort} @@ -79,7 +89,18 @@ class TestVBTC( suspend fun start() { container.start() - container.followOutput(stdlog.forward()) + container.followOutput(stdlog.forward(logger)) + logger.info("IP: ${getAddress()}") + + rpc = BtcsqApi( + name, + getAddress(), + settings.rpcPort, + settings.username, + settings.password, + 60000 + ) + waitForRpcAvailability() } @@ -118,7 +139,8 @@ class TestVBTC( } override fun username(): String = settings.username override fun password(): String = settings.password - override fun id(): Long = 0x3ae6ca000026ff + override fun id(): Long = 0x3ae6ca26ff + override fun host(): String = getAddress() override fun port(): Int = settings.rpcPort override fun network(): String = settings.bitcoinNetwork override fun payoutDelay(): Int = 150 diff --git a/src/main/kotlin/nodecore/api/Proto.kt b/src/main/kotlin/testframework/wrapper/nodecore/Entities.kt similarity index 74% rename from src/main/kotlin/nodecore/api/Proto.kt rename to src/main/kotlin/testframework/wrapper/nodecore/Entities.kt index d962caa..68b8693 100644 --- a/src/main/kotlin/nodecore/api/Proto.kt +++ b/src/main/kotlin/testframework/wrapper/nodecore/Entities.kt @@ -1,6 +1,7 @@ -package nodecore.api +package testframework.wrapper.nodecore import kotlinx.serialization.Serializable +import nodecore.api.grpc.RpcNodeInfo @Serializable data class Result( @@ -20,9 +21,60 @@ data class ProtocolReply( val results: List ) +@Serializable +data class NodeInfo( + val address: String = "", + val application: String = "MiniNode", + val platform: String = "e2eTest", + val startTimestamp: Int = 1552064237, + val id: String = "Test", + val port: Int = 12345, + // mainnet, regtest, alphanet == 3 + // testnet, testnet_progpow == 2 + val protocolVersion: Int = 3, + val share: Boolean = false, + val capabilities: Long = 0 +) { + fun toProto(): RpcNodeInfo { + return RpcNodeInfo.newBuilder() + .setApplication(application) + .setPlatform(platform) + .setStartTimestamp(startTimestamp) + .setId(id) + .setPort(port) + .setShare(share) + .setProtocolVersion(protocolVersion) + .setCapabilities(capabilities) + .build() + } +} + +@Serializable +data class NodeHeight( + val peer: String, + val height: Int +) + +@Serializable +data class GetPeerInfoReply( + val success: Boolean, + val results: List, + val endpoints: List, + val connectedNodes: List, + val disconnectedNodes: List, + val candidateNodes: List, + val nodeHeights: List +) + +@Serializable +data class VbkInfoAddress( + val address: String +) + @Serializable data class VbkInfo( - val lastBlock: VbkBlockData + val lastBlock: VbkBlockData, + val defaultAddress: VbkInfoAddress ) @Serializable @@ -43,16 +95,65 @@ data class GenerateBlocksReply( ) @Serializable -data class BlockHeaderContainer( +data class BlockHeader( + val header: String, + val hash: String +) + +@Serializable +data class GetLastBlockReply( val header: BlockHeader ) @Serializable -data class BlockHeader( - val hash: String, +data class GetLastBitcoinBlockReply( + val header: String, + val height: Int, + val hash: String +) + +@Serializable +data class BitcoinBlockHeader( val header: String ) +@Serializable +data class SubmitPopRequest( + val endorsedBlockHeader: String, + val bitcoinTransaction: String, + val bitcoinMerklePathToRoot: String, + val bitcoinBlockHeaderOfProof: BitcoinBlockHeader, + val contextBitcoinBlockHeaders: List, + val address: String +) + +@Serializable +data class SendCoinsRequest( + val amounts: List, + val sourceAddress: String? = null, + val takeFeeFromOutputs: Boolean = false +) + +@Serializable +data class SendCoinsReply( + val success: Boolean, + val results: List, + val txIds: List +) + +@Serializable +data class GetPendingTransactionsReply( + val success: Boolean, + val results: List, + val transactions: List +) + + +@Serializable +data class BlockHeaderContainer( + val header: BlockHeader +) + @Serializable data class VbkBlockData( val hash: String, @@ -192,11 +293,6 @@ data class Output( val amount: Long ) -@Serializable -data class BitcoinBlockHeader( - val header: String -) - @Serializable data class SignedTransaction( val signature: String, @@ -271,23 +367,3 @@ data class TransactionInfo( val bitcoinConfirmations: Int ) -@Serializable -data class SendCoinsRequest( - val amounts: List, - val sourceAddress: String? = null, - val takeFeeFromOutputs: Boolean = false -) - -@Serializable -data class SendCoinsReply( - val success: Boolean, - val results: List, - val txIds: List -) - -@Serializable -data class GetPendingTransactionsReply( - val success: Boolean, - val results: List, - val transactions: List -) diff --git a/src/main/kotlin/nodecore/testframework/wrapper/nodecore/MiniNode.kt b/src/main/kotlin/testframework/wrapper/nodecore/MiniNode.kt similarity index 75% rename from src/main/kotlin/nodecore/testframework/wrapper/nodecore/MiniNode.kt rename to src/main/kotlin/testframework/wrapper/nodecore/MiniNode.kt index b6dc270..46e2792 100644 --- a/src/main/kotlin/nodecore/testframework/wrapper/nodecore/MiniNode.kt +++ b/src/main/kotlin/testframework/wrapper/nodecore/MiniNode.kt @@ -1,4 +1,4 @@ -package nodecore.testframework.wrapper.nodecore +package testframework.wrapper.nodecore import com.google.common.util.concurrent.ThreadFactoryBuilder import io.ktor.network.selector.* @@ -10,8 +10,9 @@ import kotlinx.coroutines.channels.ClosedReceiveChannelException import nodecore.api.grpc.RpcAnnounce import nodecore.api.grpc.RpcEvent import nodecore.api.grpc.RpcNodeInfo -import nodecore.testframework.buildMessage +import testframework.buildMessage import org.slf4j.LoggerFactory +import testframework.waitUntil import java.io.Closeable import java.io.EOFException import java.io.IOException @@ -54,13 +55,16 @@ private class PeerSocket( start() } - fun write(message: RpcEvent) { + fun write(message: RpcEvent, failOnError: Boolean = true) { logger.debug("$peerName <--p2p-- ${message.resultsCase.name}") try { if (!writeQueue.offer(message)) { logger.warn( "Not writing event ${message.resultsCase.name} to peer $peerName because write queue is full." ) + if (failOnError) { + throw RuntimeException("$peerName: Write queue is full") + } } } catch (e: InterruptedException) { logger.warn("Output stream thread shutting down for peer $peerName") @@ -147,36 +151,14 @@ private class PeerSocket( } -data class NodeMetadata( - var application: String = "", - var platform: String = "", - var startTimestamp: Int = 1552064237, - var id: String = "Test", - var port: Int = 12345, - // mainnet, regtest, alphanet == 3 - // testnet, testnet_progpow == 2 - var protocolVersion: Int = 3 -) { - fun toProto(): RpcNodeInfo { - return RpcNodeInfo.newBuilder() - .setApplication(application) - .setPlatform(platform) - .setStartTimestamp(startTimestamp) - .setId(id) - .setPort(port) - .setShare(false) - .setProtocolVersion(protocolVersion) - .build() - } -} - - open class MiniNode : Closeable, AutoCloseable { // connected to this node private var socket: PeerSocket? = null val stats: HashMap = HashMap() val identity = AtomicLong(0) - val metadata: NodeMetadata = NodeMetadata() + val metadata: NodeInfo = NodeInfo() + + private var nodeAnnouncedBack = AtomicBoolean(false) fun nextMessageId(): String { return identity.incrementAndGet().toString() @@ -186,38 +168,54 @@ open class MiniNode : Closeable, AutoCloseable { return socket?.peerName } - suspend fun connect(p: TestNodecore, shouldAnnounce: Boolean = true) { + suspend fun connect(p: TestNodecore) { if (socket != null) { - logger.warn("Already connected to ${peerName()}, disconnect first") + logger.warn("Already connected to ${p.name}, disconnect first") return } - val address = NetworkAddress("127.0.0.1", p.settings.peerPort) - logger.debug("Connecting to node${p.settings.index}") + waitUntil(attempts = 5, delay = 2_000L, message = "Can not connect to ${p.name}") { + connectOnce(p) + } + } + + suspend fun disconnect() { + if(this.socket?.isRunning() == true) { + this.socket?.socket?.close() + } + } + + private suspend fun connectOnce(p: TestNodecore): Boolean { try { - val socket = PeerSocket( - p, - aSocket(selectorManager) - .tcp() - .connect(address) - ) { - handleMessage(it) + val address = NetworkAddress(p.getAddress(), p.settings.peerPort) + logger.debug("Connecting to ${p.name}") + + val socket = aSocket(selectorManager) + .tcp() + .connect(address) + + val peer = PeerSocket(p, socket) { + handleMessage(it, p.name) } - if (shouldAnnounce) { - val announceMsg = buildMessage(nextMessageId()) { - announce = RpcAnnounce.newBuilder() - .setReply(false) - .setNodeInfo(metadata.toProto()) - .build() - } + val announceMsg = buildMessage("0") { + announce = RpcAnnounce.newBuilder() + .setReply(false) + .setNodeInfo(metadata.toProto()) + .build() + } + + peer.write(announceMsg) - socket.write(announceMsg) + // wait for ANNOUNCE from a node + waitUntil(timeout = 5_000L, message = "${p.name} did not ANNOUNCE back") { + nodeAnnouncedBack.get() } - this.socket = socket + this.socket = peer + return true } catch (e: Exception) { - logger.error("Unable to connect to ${peerName()}") - throw e + logger.error("Unable to connect to ${p.name}: $e") + return false } } @@ -242,17 +240,23 @@ open class MiniNode : Closeable, AutoCloseable { socket.write(e) } - private suspend fun handleMessage(buf: ByteArray) { + private suspend fun handleMessage(buf: ByteArray, name: String) { try { val event = RpcEvent.parseFrom(buf) - logger.debug("${peerName()} --p2p--> ${event.resultsCase.name}") + logger.debug("$name --p2p--> ${event.resultsCase.name}") // store stats about received msgs val count = stats.getOrDefault(event.resultsCase.name, 0L) stats[event.resultsCase.name] = count + 1 + + // if this is announce back, then release "connect" + if (event.hasAnnounce()) { + this.nodeAnnouncedBack.set(true) + } + // let user handle msg onEvent(event) } catch (e: Exception) { - logger.error("${peerName()} misbehaved! Can't parse Event of size ${buf.size}.") + logger.error("$name misbehaved! Can't parse Event of size ${buf.size}.") close() return } diff --git a/src/main/kotlin/nodecore/testframework/wrapper/nodecore/NodeHttpApi.kt b/src/main/kotlin/testframework/wrapper/nodecore/NodeHttpApi.kt similarity index 78% rename from src/main/kotlin/nodecore/testframework/wrapper/nodecore/NodeHttpApi.kt rename to src/main/kotlin/testframework/wrapper/nodecore/NodeHttpApi.kt index 53480b6..221a05a 100644 --- a/src/main/kotlin/nodecore/testframework/wrapper/nodecore/NodeHttpApi.kt +++ b/src/main/kotlin/testframework/wrapper/nodecore/NodeHttpApi.kt @@ -1,13 +1,8 @@ -package nodecore.testframework.wrapper.nodecore +package testframework.wrapper.nodecore -import nodecore.api.GetPendingTransactionsReply -import nodecore.api.SendCoinsReply -import nodecore.api.SendCoinsRequest -import nodecore.api.grpc.RpcGetPendingTransactionsReply -import nodecore.api.grpc.RpcSendCoinsReply -import nodecore.api.grpc.RpcSendCoinsRequest -import nodecore.testframework.BaseJsonRpcApi -import nodecore.testframework.toRequest +import nodecore.api.grpc.RpcGetPeerInfoReply +import testframework.BaseJsonRpcApi +import testframework.toRequest import org.veriblock.sdk.models.Address import org.veriblock.sdk.models.VeriBlockPopTransaction @@ -26,8 +21,8 @@ class NodeHttpApi( method = "getinfo" ) - suspend fun getStateInfo(): VbkInfo = performRequest( - method = "getstateinfo" + suspend fun getPeerInfo(): GetPeerInfoReply = performRequest( + method = "getpeerinfo" ) suspend fun addNode(e: List): ProtocolReply = performRequest( diff --git a/src/main/kotlin/nodecore/testframework/wrapper/nodecore/TestNodecore.kt b/src/main/kotlin/testframework/wrapper/nodecore/TestNodecore.kt similarity index 68% rename from src/main/kotlin/nodecore/testframework/wrapper/nodecore/TestNodecore.kt rename to src/main/kotlin/testframework/wrapper/nodecore/TestNodecore.kt index fef64a5..0cef608 100644 --- a/src/main/kotlin/nodecore/testframework/wrapper/nodecore/TestNodecore.kt +++ b/src/main/kotlin/testframework/wrapper/nodecore/TestNodecore.kt @@ -1,14 +1,17 @@ -package nodecore.testframework.wrapper.nodecore +package testframework.wrapper.nodecore import io.grpc.Deadline +import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder import kotlinx.coroutines.delay import kotlinx.coroutines.withTimeout import nodecore.api.grpc.AdminGrpc -import nodecore.testframework.StdStreamLogger -import nodecore.testframework.KGenericContainer import org.slf4j.LoggerFactory import org.testcontainers.containers.BindMode +import org.testcontainers.containers.wait.strategy.Wait +import org.testcontainers.containers.wait.strategy.WaitStrategy +import testframework.KGenericContainer +import testframework.StdStreamLogger import java.io.Closeable import java.io.File import java.util.concurrent.TimeUnit @@ -27,8 +30,7 @@ class NodecoreSettings( class TestNodecore( val settings: NodecoreSettings, version: String, -) : Closeable, AutoCloseable -{ +) : Closeable, AutoCloseable { val name = "nodecore${settings.index}" private val logger = LoggerFactory.getLogger(name) val datadir = File(settings.baseDir, name) @@ -38,14 +40,28 @@ class TestNodecore( val container = KGenericContainer("veriblock/nodecore:$version") .withNetworkAliases(name) - .withNetworkMode("host") .withFileSystemBind(datadir.absolutePath, "/data", BindMode.READ_WRITE) .withEnv("NODECORE_LOG_LEVEL", "DEBUG") .withEnv("NODECORE_CONSOLE_LOG_LEVEL", "DEBUG") + .waitingFor(Wait.forLogMessage(".*NodeCore operating state is now: Running.*", 1)) + + fun getAddress(): String { + // we can take IP only on running containers + assert(container.isRunning) + return container + .containerInfo + .networkSettings + .networks + .entries + .first() + .value + .ipAddress!! + } // Accessor for Admin HTTP API - val http = NodeHttpApi(name, "127.0.0.1", settings.httpPort) - val rpc: AdminGrpc.AdminBlockingStub + lateinit var http: NodeHttpApi + lateinit var rpc: AdminGrpc.AdminBlockingStub + private lateinit var channel: ManagedChannel init { datadir.mkdirs() @@ -55,31 +71,20 @@ class TestNodecore( nodecoreProperties.setReadable(true, false) nodecoreProperties.setWritable(true, false) - // setup RPC channel - rpc = AdminGrpc - .newBlockingStub( - ManagedChannelBuilder - .forAddress("127.0.0.1", settings.rpcPort) - .usePlaintext() - .build() - ) - .withMaxInboundMessageSize(20 * 1024 * 1024) - .withMaxOutboundMessageSize(20 * 1024 * 1024) - .withDeadline(Deadline.after(rpcTimeout, TimeUnit.MILLISECONDS)) - // write nodecode.properties nodecoreProperties .writeText( """ + bfi.enabled=false network=${settings.network} peer.bootstrap.enabled=false - peer.bind.address=127.0.0.1 + peer.bind.address=0.0.0.0 peer.bind.port=${settings.peerPort} peer.share.platform=true peer.share.myAddress=true - rpc.bind.address=127.0.0.1 + rpc.bind.address=0.0.0.0 rpc.bind.port=${settings.rpcPort} - http.api.bind.address=127.0.0.1 + http.api.bind.address=0.0.0.0 http.api.bind.port=${settings.httpPort} regtest.progpow.height=${settings.progpowHeight} regtest.progpow.start.time=${settings.progpowTime} @@ -89,7 +94,22 @@ class TestNodecore( suspend fun start() { container.start() - container.followOutput(stdlog.forward()) + container.followOutput(stdlog.forward(logger)) + logger.info("IP: ${getAddress()}") + + channel = ManagedChannelBuilder + .forAddress(getAddress(), settings.rpcPort) + .usePlaintext() + .build() + + // setup RPC channel + rpc = AdminGrpc + .newBlockingStub(channel) + .withMaxInboundMessageSize(20 * 1024 * 1024) + .withMaxOutboundMessageSize(20 * 1024 * 1024) + .withDeadline(Deadline.after(rpcTimeout, TimeUnit.MILLISECONDS)) + + http = NodeHttpApi(name, getAddress(), settings.httpPort) waitForRpcConnection() } @@ -100,6 +120,10 @@ class TestNodecore( } fun stop() { + // don't forget to shutdown channel + if (!channel.isShutdown) { + channel.shutdownNow() + } container.stop() } diff --git a/src/main/resources/junit-platform.properties b/src/main/resources/junit-platform.properties new file mode 100644 index 0000000..2dd4834 --- /dev/null +++ b/src/main/resources/junit-platform.properties @@ -0,0 +1 @@ +#junit.jupiter.testinstance.lifecycle.default = per_class \ No newline at end of file diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index af739ea..dd626a4 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -1,13 +1,13 @@ - + - + diff --git a/src/test/kotlin/functional/ExampleApmTest.kt b/src/test/kotlin/functional/ExampleApmTest.kt index a833143..369a376 100644 --- a/src/test/kotlin/functional/ExampleApmTest.kt +++ b/src/test/kotlin/functional/ExampleApmTest.kt @@ -2,11 +2,12 @@ package functional import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.runBlocking -import nodecore.testframework.BaseIntegrationTest -import nodecore.testframework.BtcPluginInterface +import org.junit.jupiter.api.TestInstance +import testframework.BaseIntegrationTest +import testframework.BtcPluginInterface import kotlin.test.Test -internal class ExampleApmTest : BaseIntegrationTest() { +class ExampleApmTest : BaseIntegrationTest() { override suspend fun runTest() { logger.info("Running ExampleApmTest test!") @@ -18,10 +19,10 @@ internal class ExampleApmTest : BaseIntegrationTest() { val nodecore = addNodecore() nodecore.start() - val vbtc = addVBTC() + val vbtc = addBtcsq() vbtc.start() - val apm = addAPM(nodecore, List(1){vbtc}) + val apm = addAPM(nodecore, listOf(vbtc)) apm.start() } diff --git a/src/test/kotlin/functional/ExampleTest.kt b/src/test/kotlin/functional/ExampleTest.kt index bee5e37..45cf283 100644 --- a/src/test/kotlin/functional/ExampleTest.kt +++ b/src/test/kotlin/functional/ExampleTest.kt @@ -7,10 +7,11 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.runBlocking import nodecore.api.grpc.RpcEvent -import nodecore.testframework.BaseIntegrationTest -import nodecore.testframework.wrapper.nodecore.MiniNode -import nodecore.testframework.connectNodes -import nodecore.testframework.randomAddress +import org.junit.jupiter.api.TestInstance +import testframework.BaseIntegrationTest +import testframework.wrapper.nodecore.MiniNode +import testframework.connectNodes +import testframework.randomAddress import kotlin.test.Test import org.veriblock.core.utilities.createLogger @@ -21,7 +22,7 @@ private class ExampleMiniNode : MiniNode() { } } -internal class ExampleTest : BaseIntegrationTest() { +class ExampleTest : BaseIntegrationTest() { override suspend fun setup() = coroutineScope { addNodecore() addNodecore() diff --git a/src/test/kotlin/functional/LedgerProofTest.kt b/src/test/kotlin/functional/LedgerProofTest.kt index b0a8b71..acfd744 100644 --- a/src/test/kotlin/functional/LedgerProofTest.kt +++ b/src/test/kotlin/functional/LedgerProofTest.kt @@ -1,6 +1,7 @@ package functional import com.google.protobuf.ByteString +import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.shouldBe import kotlinx.coroutines.* import nodecore.api.grpc.RpcEvent @@ -8,11 +9,13 @@ import nodecore.api.grpc.RpcLedgerProofReply import nodecore.api.grpc.RpcLedgerProofReply.* import nodecore.api.grpc.RpcLedgerProofRequest import nodecore.api.grpc.utilities.ByteStringAddressUtility -import nodecore.testframework.* -import nodecore.testframework.wrapper.nodecore.MiniNode +import org.junit.jupiter.api.TestInstance +import testframework.* +import testframework.wrapper.nodecore.MiniNode import kotlin.test.Test import org.veriblock.extensions.ledger.LedgerProofWithContext import org.veriblock.sdk.models.Address +import kotlin.test.fail private class LedgerProofVerifier : MiniNode() { var reply: RpcLedgerProofReply? = null @@ -24,15 +27,15 @@ private class LedgerProofVerifier : MiniNode() { } } -internal class LedgerProofTest : BaseIntegrationTest() { +class LedgerProofTest : BaseIntegrationTest() { // exists, has VBK - val addr1 = randomAddress() + private val addr1 = randomAddress() // does not exist, has no VBK - val addr2 = randomAddress() - val badAddr = "Not An Address" + private val addr2 = randomAddress() + private val badAddr = "Not An Address" - fun addr2bytes(addr: Address): ByteString { + private fun addr2bytes(addr: Address): ByteString { return ByteStringAddressUtility.createProperByteStringAutomatically(addr.toString()) } @@ -41,14 +44,26 @@ internal class LedgerProofTest : BaseIntegrationTest() { nc.start() } + private suspend fun ensureMiniNodeIsConnected() { + val info = nodecores[0].http.getPeerInfo() + if (info.connectedNodes.size != 1) { + logger.error(info.toString()) + fail() + } + } + override suspend fun runTest() { logger.info("Running LedgerProof test!") + delay(5_000) val n = LedgerProofVerifier() n.connect(nodecores[0]) + ensureMiniNodeIsConnected() nodecores[0].http.generateBlocks(100, addr1.toString()) + ensureMiniNodeIsConnected() + val req = RpcEvent.newBuilder() .setLedgerProofRequest( RpcLedgerProofRequest.newBuilder() @@ -60,8 +75,11 @@ internal class LedgerProofTest : BaseIntegrationTest() { .build() n.sendEvent(req) + + ensureMiniNodeIsConnected() + // wait until reply is received - waitUntil { n.reply != null } + waitUntil(message = "Did not get reply for LedgerProofRequest") { n.reply != null } val reply = n.reply!! logger.debug(reply.toString()) @@ -74,10 +92,10 @@ internal class LedgerProofTest : BaseIntegrationTest() { checkProofOfNonExistence(list[1]) } - fun checkProofOfExistence(e: LedgerProofResult) { + private fun checkProofOfExistence(e: LedgerProofResult) { e.result shouldBe Status.ADDRESS_EXISTS - // throws is proof is invalid + // throws if proof is invalid val proof = LedgerProofWithContext.parseFrom( e.ledgerProofWithContext ) @@ -85,7 +103,7 @@ internal class LedgerProofTest : BaseIntegrationTest() { proof.ledgerAddress shouldBe addr1.toString() } - fun checkProofOfNonExistence(e: LedgerProofResult) { + private fun checkProofOfNonExistence(e: LedgerProofResult) { e.result shouldBe Status.ADDRESS_DOES_NOT_EXIST // throws if proof is invalid @@ -96,11 +114,6 @@ internal class LedgerProofTest : BaseIntegrationTest() { proof.ledgerAddress shouldBe addr2.toString() } - fun checkInvalidAddr(e: LedgerProofResult) { - e.address.toString("UTF-8") shouldBe badAddr - e.result shouldBe Status.ADDRESS_IS_INVALID - } - @Test fun run(): Unit = runBlocking { LedgerProofTest().main() diff --git a/src/test/kotlin/functional/PopMiningTest.kt b/src/test/kotlin/functional/PopMiningTest.kt index 21c0e78..7e5e2a7 100644 --- a/src/test/kotlin/functional/PopMiningTest.kt +++ b/src/test/kotlin/functional/PopMiningTest.kt @@ -2,10 +2,11 @@ package functional import io.kotest.matchers.shouldBe import kotlinx.coroutines.* -import nodecore.testframework.* -import nodecore.testframework.wrapper.apm.MineRequest -import nodecore.testframework.wrapper.nodecore.TestNodecore +import testframework.* +import testframework.wrapper.apm.MineRequest +import testframework.wrapper.nodecore.TestNodecore import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.junit.jupiter.api.TestInstance import kotlin.test.Test import org.nodecore.vpmmock.mockmining.VeriBlockPopMinerMock import org.veriblock.core.Context @@ -63,10 +64,12 @@ class PopMiningTest : BaseIntegrationTest() { connectNodes(nodecores[i + 1], nodecores[i]) } - val vbtc = addVBTC() + val vbtc = addBtcsq() vbtc.start() vbtc.mineUntilPopEnabled() + logger.info("BTCSQ is at height when POP is enabled") + val apm = addAPM(nodecores[0], listOf(vbtc)) apm.start() } @@ -75,8 +78,8 @@ class PopMiningTest : BaseIntegrationTest() { logger.info("Running PopMiningTest test!") logger.info("Generating 10 vBTC blocks") - val vbtcAddr = vbtcs[0].rpc.getNewAddress() - vbtcs[0].rpc.generateToAddress(10, vbtcAddr) + val vbtcAddr = btcsqs[0].rpc.getNewAddress() + btcsqs[0].rpc.generateToAddress(10, vbtcAddr) logger.info("Sending VBK to APM address ${apms[0].vbkAddress}") topUpApmWallet(apms[0], blocks = 10) @@ -85,14 +88,14 @@ class PopMiningTest : BaseIntegrationTest() { val ncAddress = Address(nodecores[0].http.getInfo().defaultAddress.address) logger.info("Generating VTBs...") - val TOTAL_VTBS = 10 - for (i in 1..TOTAL_VTBS) { + val totalVtbs = 10 + for (i in 1..totalVtbs) { nodecores[0].http.generateBlocks(1, ncAddress.toString()) endorseVbkTip(nodecores[0], address = ncAddress) } syncAllApms(apms) - val operation = apms[0].http.mine(MineRequest(chainSymbol = vbtcs[0].name, 210)) // TODO: height = popActvationHeight + mined vbtc blocks + val operation = apms[0].http.mine(MineRequest(chainSymbol = btcsqs[0].name, 210)) // TODO: height = popActvationHeight + mined vbtc blocks logger.info("waiting until APM submits endorsement TX") waitUntil(delay = 5000L) { @@ -116,17 +119,17 @@ class PopMiningTest : BaseIntegrationTest() { val lastBlockHeight = nodecores[0].http.getInfo().lastBlock.number; - logger.info("waiting until APM sends all lacking VBK context blocks, $TOTAL_VTBS VTBs and 1 ATV") + logger.info("waiting until APM sends all lacking VBK context blocks, $totalVtbs VTBs and 1 ATV") waitUntil(timeout = 120_000L, delay = 5000L) { - val popmp = vbtcs[0].rpc.getRawPopMempool() + val popmp = btcsqs[0].rpc.getRawPopMempool() // total number of VBK blocks in mempool must be `lastBlockHeight - 1` - popmp.vbkblocks.size != lastBlockHeight - 1 /* genesis */ && popmp.vtbs.size >= TOTAL_VTBS && popmp.atvs.isNotEmpty() + popmp.vbkblocks.size != lastBlockHeight - 1 /* genesis */ && popmp.vtbs.size >= totalVtbs && popmp.atvs.isNotEmpty() } - vbtcs[0].rpc.generateToAddress(1, address = vbtcAddr)[0] + btcsqs[0].rpc.generateToAddress(1, address = vbtcAddr)[0] // all pop-payloads mined, mempool is empty - val popmp = vbtcs[0].rpc.getRawPopMempool() + val popmp = btcsqs[0].rpc.getRawPopMempool() popmp.atvs.size shouldBe 0 popmp.vtbs.size shouldBe 0 popmp.vbkblocks.size shouldBe 0 diff --git a/src/test/kotlin/functional/TxLimitTest.kt b/src/test/kotlin/functional/TxLimitTest.kt index b63e049..787dcdd 100644 --- a/src/test/kotlin/functional/TxLimitTest.kt +++ b/src/test/kotlin/functional/TxLimitTest.kt @@ -5,11 +5,12 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.runBlocking -import nodecore.testframework.BaseIntegrationTest -import nodecore.testframework.connectNodes -import nodecore.testframework.topUpApmWallet -import nodecore.testframework.waitUntil -import nodecore.testframework.wrapper.apm.MineRequest +import org.junit.jupiter.api.TestInstance +import testframework.BaseIntegrationTest +import testframework.connectNodes +import testframework.topUpApmWallet +import testframework.waitUntil +import testframework.wrapper.apm.MineRequest import kotlin.test.Test class TxLimitTest : BaseIntegrationTest() { @@ -29,7 +30,7 @@ class TxLimitTest : BaseIntegrationTest() { connectNodes(nodecores[i + 1], nodecores[i]) } - val vbtc = addVBTC() + val vbtc = addBtcsq() vbtc.start() vbtc.mineUntilPopEnabled() @@ -46,7 +47,7 @@ class TxLimitTest : BaseIntegrationTest() { val TX_LIMIT = 900 logger.info("Create ${TX_LIMIT} transactions") for (i in 1..TX_LIMIT) { - apms[0].http.mine(MineRequest(chainSymbol = vbtcs[0].name, 200)) + apms[0].http.mine(MineRequest(chainSymbol = btcsqs[0].name, 200)) } waitUntil { @@ -57,7 +58,7 @@ class TxLimitTest : BaseIntegrationTest() { logger.info("Create 901 transaction") try { - apms[0].http.mine(MineRequest(chainSymbol = vbtcs[0].name, 200)) + apms[0].http.mine(MineRequest(chainSymbol = btcsqs[0].name, 200)) throw NoErrorException("Error was not returned") } catch(e: NoErrorException) { logger.error(e.message) diff --git a/src/test/kotlin/functional/nodecore/NodeCoreMempoolSyncTest.kt b/src/test/kotlin/functional/nodecore/NodeCoreMempoolSyncTest.kt index d7768c3..1bfb657 100644 --- a/src/test/kotlin/functional/nodecore/NodeCoreMempoolSyncTest.kt +++ b/src/test/kotlin/functional/nodecore/NodeCoreMempoolSyncTest.kt @@ -1,4 +1,4 @@ -package functional +package functional.nodecore import io.kotest.matchers.collections.shouldNotBeEmpty import io.kotest.matchers.shouldBe @@ -7,11 +7,12 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.runBlocking -import nodecore.testframework.BaseIntegrationTest -import nodecore.testframework.connectNodes +import org.junit.jupiter.api.TestInstance +import testframework.BaseIntegrationTest +import testframework.connectNodes +import testframework.wrapper.nodecore.Output +import testframework.wrapper.nodecore.SendCoinsRequest import kotlin.test.Test -import nodecore.api.Output -import nodecore.api.SendCoinsRequest class NodeCoreMempoolSyncTest : BaseIntegrationTest() { override suspend fun setup() = coroutineScope { @@ -80,8 +81,9 @@ class NodeCoreMempoolSyncTest : BaseIntegrationTest() { nodecores[0].http.getPendingTransactions().transactions[0].txId shouldBe txId // Add a new node and connect it to the network. It should get the pending transactions too - addNodecore().start() - connectNodes(nodecores[1], nodecores[2]) + val nc = addNodecore() + nc.start() + connectNodes(nodecores[1], nc) syncAllNodecores(nodecores) }