From dd53acfb64eb6d0de041747c6ca59e43bf51e1a3 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 29 Jul 2016 12:17:37 +0200 Subject: [PATCH 1/5] Testing: add a UnitTestServices class which implements ServiceHub. This is a lighter weight version of MockServices, and is intended to assist in the testing of core contract code, vs MockServices which can go on to be used only from the node module. --- .../node/services/testing/MockServices.kt | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/core/src/main/kotlin/com/r3corda/core/node/services/testing/MockServices.kt b/core/src/main/kotlin/com/r3corda/core/node/services/testing/MockServices.kt index d69331f1e1..e26671715a 100644 --- a/core/src/main/kotlin/com/r3corda/core/node/services/testing/MockServices.kt +++ b/core/src/main/kotlin/com/r3corda/core/node/services/testing/MockServices.kt @@ -1,13 +1,20 @@ package com.r3corda.core.node.services.testing +import com.google.common.util.concurrent.ListenableFuture import com.r3corda.core.contracts.Attachment import com.r3corda.core.contracts.SignedTransaction import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.generateKeyPair import com.r3corda.core.crypto.sha256 +import com.r3corda.core.messaging.MessagingService +import com.r3corda.core.node.ServiceHub import com.r3corda.core.node.services.* +import com.r3corda.core.protocols.ProtocolLogic import com.r3corda.core.serialization.SingletonSerializeAsToken +import com.r3corda.core.testing.DUMMY_NOTARY +import com.r3corda.core.testing.MEGA_CORP +import com.r3corda.core.testing.MINI_CORP import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.File @@ -15,10 +22,40 @@ import java.io.InputStream import java.security.KeyPair import java.security.PrivateKey import java.security.PublicKey +import java.time.Clock import java.util.* import java.util.jar.JarInputStream import javax.annotation.concurrent.ThreadSafe +// TODO: We need a single, rationalised unit testing environment that is usable for everything. Fix this! +// That means it probably shouldn't be in the 'core' module, which lacks enough code to create a realistic test env. + +/** + * A singleton utility that only provides a mock identity, key and storage service. However, this is sufficient for + * building chains of transactions and verifying them. It isn't sufficient for testing protocols however. + */ +open class UnitTestServices(val key: KeyPair = generateKeyPair()) : ServiceHub { + override fun invokeProtocolAsync(logicType: Class>, vararg args: Any?): ListenableFuture { + throw UnsupportedOperationException("not implemented") + } + + override fun recordTransactions(txs: Iterable) { + for (stx in txs) { + storageService.validatedTransactions.addTransaction(stx) + } + } + + override val storageService: TxWritableStorageService = MockStorageService(myLegalIdentityKey = key) + override val identityService: MockIdentityService = MockIdentityService(listOf(MEGA_CORP, MINI_CORP, DUMMY_NOTARY)) + override val keyManagementService: MockKeyManagementService = MockKeyManagementService(key) + + override val walletService: WalletService get() = throw UnsupportedOperationException() + override val networkService: MessagingService get() = throw UnsupportedOperationException() + override val networkMapCache: NetworkMapCache get() = throw UnsupportedOperationException() + override val clock: Clock get() = throw UnsupportedOperationException() + override val schedulerService: SchedulerService get() = throw UnsupportedOperationException() +} + @ThreadSafe class MockIdentityService(val identities: List) : IdentityService, SingletonSerializeAsToken() { private val keyToParties: Map From ba05b90b8f059210579156392dbcd6933042bce6 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 29 Jul 2016 13:10:58 +0200 Subject: [PATCH 2/5] Testing: change fillWithTestCash to issue cash under the DUMMY_CASH_ISSUER identity and adjust code that uses it. Introduce some code that'll prove useful later in WalletWithCashTest. This change reduces the testing confusion that can occur when cash is issued by one of the parties in a transaction rather than e.g. a neutral third party like a central bank. --- .../r3corda/contracts/testing/WalletFiller.kt | 29 +++++------- .../messaging/TwoPartyTradeProtocolTests.kt | 12 ++--- .../node/services/WalletWithCashTest.kt | 47 ++++++++----------- .../r3corda/core/testing/TraderDemoTest.kt | 3 -- .../kotlin/com/r3corda/demos/TraderDemo.kt | 19 ++++---- 5 files changed, 43 insertions(+), 67 deletions(-) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/testing/WalletFiller.kt b/contracts/src/main/kotlin/com/r3corda/contracts/testing/WalletFiller.kt index eed49f7d4f..e59f8bf086 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/testing/WalletFiller.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/testing/WalletFiller.kt @@ -2,6 +2,8 @@ package com.r3corda.contracts.testing import com.r3corda.contracts.asset.Cash +import com.r3corda.contracts.asset.DUMMY_CASH_ISSUER +import com.r3corda.contracts.asset.DUMMY_CASH_ISSUER_KEY import com.r3corda.core.contracts.Amount import com.r3corda.core.contracts.Issued import com.r3corda.core.contracts.SignedTransaction @@ -11,16 +13,14 @@ import com.r3corda.core.node.ServiceHub import com.r3corda.core.node.services.Wallet import com.r3corda.core.serialization.OpaqueBytes import com.r3corda.core.testing.DUMMY_NOTARY +import java.security.PublicKey import java.util.* /** * Creates a random set of between (by default) 3 and 10 cash states that add up to the given amount and adds them - * to the wallet. This is intended for unit tests. - * - * The cash is self issued with the current nodes identity, as fetched from the storage service. Thus it - * would not be trusted by any sensible market participant and is effectively an IOU. If it had been issued by - * the central bank, well ... that'd be a different story altogether. + * to the wallet. This is intended for unit tests. The cash is issued by [DUMMY_CASH_ISSUER] and owned by the legal + * identity key from the storage service. * * The service hub needs to provide at least a key management service and a storage service. * @@ -31,23 +31,18 @@ fun ServiceHub.fillWithSomeTestCash(howMuch: Amount, atLeastThisManyStates: Int = 3, atMostThisManyStates: Int = 10, rng: Random = Random(), - ref: OpaqueBytes = OpaqueBytes(ByteArray(1, { 0 }))): Wallet { + ref: OpaqueBytes = OpaqueBytes(ByteArray(1, { 1 })), + ownedBy: PublicKey? = null): Wallet { val amounts = calculateRandomlySizedAmounts(howMuch, atLeastThisManyStates, atMostThisManyStates, rng) - val myIdentity = storageService.myLegalIdentity - val myKey = storageService.myLegalIdentityKey + val myKey: PublicKey = ownedBy ?: storageService.myLegalIdentityKey.public // We will allocate one state to one transaction, for simplicities sake. val cash = Cash() val transactions: List = amounts.map { pennies -> - // This line is what makes the cash self issued. We just use zero as our deposit reference: we don't need - // this field as there's no other database or source of truth we need to sync with. - val depositRef = myIdentity.ref(ref) - val issuance = TransactionType.General.Builder() - val freshKey = keyManagementService.freshKey() - cash.generateIssue(issuance, Amount(pennies, Issued(depositRef, howMuch.token)), freshKey.public, notary) - issuance.signWith(myKey) + cash.generateIssue(issuance, Amount(pennies, Issued(DUMMY_CASH_ISSUER.copy(reference = ref), howMuch.token)), myKey, notary) + issuance.signWith(DUMMY_CASH_ISSUER_KEY) return@map issuance.toSignedTransaction(true) } @@ -77,8 +72,8 @@ private fun calculateRandomlySizedAmounts(howMuch: Amount, min: Int, m // Handle inexact rounding. amounts[i] = howMuch.quantity - filledSoFar } - check(amounts[i] >= 0) { amounts[i] } + check(amounts[i] >= 0) { "${amounts[i]} : $filledSoFar : $howMuch" } } check(amounts.sum() == howMuch.quantity) return amounts -} +} \ No newline at end of file diff --git a/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt b/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt index aec019bb17..520c7d339c 100644 --- a/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt +++ b/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt @@ -2,10 +2,7 @@ package com.r3corda.node.messaging import com.google.common.util.concurrent.ListenableFuture import com.r3corda.contracts.CommercialPaper -import com.r3corda.contracts.asset.CASH -import com.r3corda.contracts.asset.Cash -import com.r3corda.contracts.asset.`issued by` -import com.r3corda.contracts.asset.`owned by` +import com.r3corda.contracts.asset.* import com.r3corda.contracts.testing.fillWithSomeTestCash import com.r3corda.core.contracts.* import com.r3corda.core.crypto.Party @@ -93,9 +90,8 @@ class TwoPartyTradeProtocolTests { val bobNode = net.createPartyNode(notaryNode.info, BOB.name, BOB_KEY) bobNode.services.fillWithSomeTestCash(2000.DOLLARS) - val issuer = bobNode.services.storageService.myLegalIdentity.ref(0) val alicesFakePaper = fillUpForSeller(false, aliceNode.storage.myLegalIdentity.owningKey, - 1200.DOLLARS `issued by` issuer, notaryNode.info.identity, null).second + 1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, notaryNode.info.identity, null).second insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey, notaryNode.storage.myLegalIdentityKey) @@ -134,7 +130,6 @@ class TwoPartyTradeProtocolTests { @Test fun `shutdown and restore`() { - ledger { val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) val aliceNode = net.createPartyNode(notaryNode.info, ALICE.name, ALICE_KEY) @@ -142,13 +137,12 @@ class TwoPartyTradeProtocolTests { val bobAddr = bobNode.net.myAddress as InMemoryMessagingNetwork.Handle val networkMapAddr = notaryNode.info - val issuer = bobNode.services.storageService.myLegalIdentity.ref(0) net.runNetwork() // Clear network map registration messages bobNode.services.fillWithSomeTestCash(2000.DOLLARS) val alicesFakePaper = fillUpForSeller(false, aliceNode.storage.myLegalIdentity.owningKey, - 1200.DOLLARS `issued by` issuer, notaryNode.info.identity, null).second + 1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, notaryNode.info.identity, null).second insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey) val buyerSessionID = random63BitValue() diff --git a/node/src/test/kotlin/com/r3corda/node/services/WalletWithCashTest.kt b/node/src/test/kotlin/com/r3corda/node/services/WalletWithCashTest.kt index 8f8cd5ba3b..290f0988cb 100644 --- a/node/src/test/kotlin/com/r3corda/node/services/WalletWithCashTest.kt +++ b/node/src/test/kotlin/com/r3corda/node/services/WalletWithCashTest.kt @@ -1,14 +1,14 @@ package com.r3corda.node.services import com.r3corda.contracts.asset.Cash +import com.r3corda.contracts.asset.DUMMY_CASH_ISSUER import com.r3corda.contracts.asset.cashBalances import com.r3corda.contracts.testing.fillWithSomeTestCash import com.r3corda.core.contracts.* import com.r3corda.core.crypto.SecureHash -import com.r3corda.core.node.ServiceHub -import com.r3corda.core.node.services.testing.MockKeyManagementService +import com.r3corda.core.node.services.WalletService import com.r3corda.core.node.services.testing.MockStorageService -import com.r3corda.core.serialization.OpaqueBytes +import com.r3corda.core.node.services.testing.UnitTestServices import com.r3corda.core.testing.* import com.r3corda.core.utilities.BriefLogFormatter import com.r3corda.node.services.wallet.NodeWalletService @@ -23,11 +23,22 @@ import kotlin.test.assertNull // TODO: Move this to the cash contract tests once mock services are further split up. class WalletWithCashTest { - val kms = MockKeyManagementService(ALICE_KEY) + lateinit var services: UnitTestServices + val wallet: WalletService get() = services.walletService @Before fun setUp() { BriefLogFormatter.loggingOn(NodeWalletService::class) + services = object : UnitTestServices() { + override val walletService: WalletService = NodeWalletService(this) + + override fun recordTransactions(txs: Iterable) { + for (stx in txs) { + storageService.validatedTransactions.addTransaction(stx) + walletService.notify(stx.tx) + } + } + } } @After @@ -35,37 +46,24 @@ class WalletWithCashTest { BriefLogFormatter.loggingOff(NodeWalletService::class) } - fun make(): Pair { - val services = MockServices(keyManagement = kms) - return Pair(services.walletService as NodeWalletService, services) - } - @Test fun splits() { - val (wallet, services) = make() - val ref = OpaqueBytes(ByteArray(1, {0})) - - kms.nextKeys += Array(3) { ALICE_KEY } // Fix the PRNG so that we get the same splits every time. - services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L), ref) + services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L)) val w = wallet.currentWallet assertEquals(3, w.states.size) val state = w.states[0].state.data as Cash.State - val myIdentity = services.storageService.myLegalIdentity - val myPartyRef = myIdentity.ref(ref) - assertEquals(29.01.DOLLARS `issued by` myPartyRef, state.amount) - assertEquals(ALICE_PUBKEY, state.owner) + assertEquals(29.01.DOLLARS `issued by` DUMMY_CASH_ISSUER, state.amount) + assertEquals(services.key.public, state.owner) - assertEquals(35.38.DOLLARS `issued by` myPartyRef, (w.states[2].state.data as Cash.State).amount) - assertEquals(35.61.DOLLARS `issued by` myPartyRef, (w.states[1].state.data as Cash.State).amount) + assertEquals(35.38.DOLLARS `issued by` DUMMY_CASH_ISSUER, (w.states[2].state.data as Cash.State).amount) + assertEquals(35.61.DOLLARS `issued by` DUMMY_CASH_ISSUER, (w.states[1].state.data as Cash.State).amount) } @Test fun basics() { - val (wallet, services) = make() - // A tx that sends us money. val freshKey = services.keyManagementService.freshKey() val usefulTX = TransactionType.General.Builder().apply { @@ -102,10 +100,7 @@ class WalletWithCashTest { @Test fun branchingLinearStatesFails() { - val (wallet, services) = make() - val freshKey = services.keyManagementService.freshKey() - val thread = SecureHash.sha256("thread") // Issue a linear state @@ -131,8 +126,6 @@ class WalletWithCashTest { @Test fun sequencingLinearStatesWorks() { - val (wallet, services) = make() - val freshKey = services.keyManagementService.freshKey() val thread = SecureHash.sha256("thread") diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt index cd8e15a2b0..c917aa80e4 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt @@ -6,9 +6,6 @@ import com.r3corda.core.testing.utilities.TestTimestamp import com.r3corda.core.testing.utilities.assertExitOrKill import com.r3corda.core.testing.utilities.spawn import org.junit.Test -import java.nio.file.Paths -import java.text.SimpleDateFormat -import java.util.* import kotlin.test.assertEquals class TraderDemoTest { diff --git a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt index c98449523b..ed6c6509c2 100644 --- a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt @@ -136,7 +136,6 @@ fun runTraderDemo(args: Array): Int { // One of the two servers needs to run the network map and notary services. In such a trivial two-node network // the map is not very helpful, but we need one anyway. So just make the buyer side run the network map as it's // the side that sticks around waiting for the seller. - var cashIssuer: Party? = null val networkMapId = if (role == Role.BUYER) { advertisedServices = setOf(NetworkMapService.Type, SimpleNotaryService.Type) null @@ -148,7 +147,6 @@ fun runTraderDemo(args: Array): Int { val path = Paths.get(baseDirectory, Role.BUYER.name.toLowerCase(), "identity-public") val party = Files.readAllBytes(path).deserialize() advertisedServices = emptySet() - cashIssuer = party NodeInfo(ArtemisMessagingService.makeRecipient(theirNetAddr), party, setOf(NetworkMapService.Type)) } @@ -157,18 +155,15 @@ fun runTraderDemo(args: Array): Int { Node(directory, myNetAddr, apiNetAddr, config, networkMapId, advertisedServices).setup().start() } - // TODO: Replace with a separate trusted cash issuer - if (cashIssuer == null) { - cashIssuer = node.services.storageService.myLegalIdentity - } - // What happens next depends on the role. The buyer sits around waiting for a trade to start. The seller role // will contact the buyer and actually make something happen. val amount = 1000.DOLLARS if (role == Role.BUYER) { runBuyer(node, amount) } else { - runSeller(node, amount, cashIssuer) + node.networkMapRegistrationFuture.get() + val party = node.netMapCache.getNodeByLegalName("Bank A")?.identity ?: throw IllegalStateException("Cannot find other node?!") + runSeller(node, amount, party) } return 0 @@ -213,7 +208,9 @@ private fun runBuyer(node: Node, amount: Amount) { // Self issue some cash. // // TODO: At some point this demo should be extended to have a central bank node. - node.services.fillWithSomeTestCash(3000.DOLLARS, node.info.identity) + node.services.fillWithSomeTestCash(3000.DOLLARS, + notary = node.info.identity, // In this demo, the buyer and notary are the same. + ownedBy = node.services.keyManagementService.freshKey().public) // Wait around until a node asks to start a trade with us. In a real system, this part would happen out of band // via some other system like an exchange or maybe even a manual messaging system like Bloomberg. But for the @@ -250,7 +247,7 @@ private class TraderDemoProtocolBuyer(val otherSide: Party, progressTracker.currentStep = STARTING_BUY send(otherSide, 0, sessionID) - val notary = serviceHub.networkMapCache.notaryNodes[0] + val notary: NodeInfo = serviceHub.networkMapCache.notaryNodes[0] val buyer = TwoPartyTradeProtocol.Buyer( otherSide, notary.identity, @@ -323,7 +320,7 @@ private class TraderDemoProtocolSeller(val otherSide: Party, progressTracker.currentStep = SELF_ISSUING - val notary = serviceHub.networkMapCache.notaryNodes[0] + val notary: NodeInfo = serviceHub.networkMapCache.notaryNodes[0] val cpOwnerKey = serviceHub.keyManagementService.freshKey() val commercialPaper = selfIssueSomeCommercialPaper(cpOwnerKey.public, notary) From 1c3379f5087561018872cc2b2852842915b92c25 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 29 Jul 2016 14:48:25 +0200 Subject: [PATCH 3/5] Testing: make the ledger DSL take a ServiceHub rather than individual services. It defaults to a fresh UnitTestServices(). Also clear up a few other areas. --- .../com/r3corda/core/testing/CoreTestUtils.kt | 17 ++++----- .../core/testing/LedgerDSLInterpreter.kt | 6 ++-- .../com/r3corda/core/testing/TestDSL.kt | 36 ++++++++++--------- .../messaging/TwoPartyTradeProtocolTests.kt | 4 +-- .../node/visualiser/GroupToGraphConversion.kt | 8 ++--- 5 files changed, 35 insertions(+), 36 deletions(-) diff --git a/core/src/main/kotlin/com/r3corda/core/testing/CoreTestUtils.kt b/core/src/main/kotlin/com/r3corda/core/testing/CoreTestUtils.kt index 1a80c1a024..600dce87c6 100644 --- a/core/src/main/kotlin/com/r3corda/core/testing/CoreTestUtils.kt +++ b/core/src/main/kotlin/com/r3corda/core/testing/CoreTestUtils.kt @@ -4,14 +4,12 @@ package com.r3corda.core.testing import com.google.common.base.Throwables import com.google.common.net.HostAndPort -import com.r3corda.core.contracts.Attachment import com.r3corda.core.contracts.StateRef import com.r3corda.core.contracts.TransactionBuilder import com.r3corda.core.crypto.* -import com.r3corda.core.node.services.IdentityService -import com.r3corda.core.node.services.StorageService +import com.r3corda.core.node.ServiceHub import com.r3corda.core.node.services.testing.MockIdentityService -import com.r3corda.core.node.services.testing.MockStorageService +import com.r3corda.core.node.services.testing.UnitTestServices import java.math.BigInteger import java.net.ServerSocket import java.security.KeyPair @@ -95,17 +93,14 @@ fun freeLocalHostAndPort(): HostAndPort { } /** - * Creates and tests a ledger built by the passed in dsl. - * @param identityService: The [IdentityService] to be used while building the ledger. - * @param storageService: The [StorageService] to be used for storing e.g. [Attachment]s. - * @param dsl: The dsl building the ledger. + * Creates and tests a ledger built by the passed in dsl. The provided services can be customised, otherwise a default + * of a freshly built [UnitTestServices] is used. */ @JvmOverloads fun ledger( - identityService: IdentityService = MOCK_IDENTITY_SERVICE, - storageService: StorageService = MockStorageService(), + services: ServiceHub = UnitTestServices(), dsl: LedgerDSL.() -> Unit ): LedgerDSL { - val ledgerDsl = LedgerDSL(TestLedgerDSLInterpreter(identityService, storageService)) + val ledgerDsl = LedgerDSL(TestLedgerDSLInterpreter(services)) dsl(ledgerDsl) return ledgerDsl } diff --git a/core/src/main/kotlin/com/r3corda/core/testing/LedgerDSLInterpreter.kt b/core/src/main/kotlin/com/r3corda/core/testing/LedgerDSLInterpreter.kt index 3fc960871f..280d515ec3 100644 --- a/core/src/main/kotlin/com/r3corda/core/testing/LedgerDSLInterpreter.kt +++ b/core/src/main/kotlin/com/r3corda/core/testing/LedgerDSLInterpreter.kt @@ -40,11 +40,13 @@ interface Verifies { val exceptionMessage = exception.message if (exceptionMessage == null) { throw AssertionError( - "Expected exception containing '$expectedMessage' but raised exception had no message" + "Expected exception containing '$expectedMessage' but raised exception had no message", + exception ) } else if (!exceptionMessage.toLowerCase().contains(expectedMessage.toLowerCase())) { throw AssertionError( - "Expected exception containing '$expectedMessage' but raised exception was '$exception'" + "Expected exception containing '$expectedMessage' but raised exception was '$exception'", + exception ) } } diff --git a/core/src/main/kotlin/com/r3corda/core/testing/TestDSL.kt b/core/src/main/kotlin/com/r3corda/core/testing/TestDSL.kt index 4e0fc5a048..e3f18ea53d 100644 --- a/core/src/main/kotlin/com/r3corda/core/testing/TestDSL.kt +++ b/core/src/main/kotlin/com/r3corda/core/testing/TestDSL.kt @@ -5,8 +5,7 @@ import com.r3corda.core.crypto.DigitalSignature import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.signWithECDSA -import com.r3corda.core.node.services.IdentityService -import com.r3corda.core.node.services.StorageService +import com.r3corda.core.node.ServiceHub import com.r3corda.core.serialization.serialize import java.io.InputStream import java.security.KeyPair @@ -95,6 +94,10 @@ data class TestTransactionDSLInterpreter private constructor( transactionBuilder: TransactionBuilder ) : this(ledgerInterpreter, transactionBuilder, HashMap()) + val services = object : ServiceHub by ledgerInterpreter.services { + override fun loadState(stateRef: StateRef) = ledgerInterpreter.resolveStateRef(stateRef) + } + private fun copy(): TestTransactionDSLInterpreter = TestTransactionDSLInterpreter( ledgerInterpreter = ledgerInterpreter, @@ -141,18 +144,15 @@ data class TestTransactionDSLInterpreter private constructor( } data class TestLedgerDSLInterpreter private constructor ( - private val identityService: IdentityService, - private val storageService: StorageService, + val services: ServiceHub, internal val labelToOutputStateAndRefs: HashMap> = HashMap(), - private val transactionWithLocations: HashMap = HashMap(), + private val transactionWithLocations: HashMap = LinkedHashMap(), private val nonVerifiedTransactionWithLocations: HashMap = HashMap() ) : LedgerDSLInterpreter { val wireTransactions: List get() = transactionWithLocations.values.map { it.transaction } // We specify [labelToOutputStateAndRefs] just so that Kotlin picks the primary constructor instead of cycling - constructor(identityService: IdentityService, storageService: StorageService) : this( - identityService, storageService, labelToOutputStateAndRefs = HashMap() - ) + constructor(services: ServiceHub) : this(services, labelToOutputStateAndRefs = HashMap()) companion object { private fun getCallerLocation(): String? { @@ -179,8 +179,7 @@ data class TestLedgerDSLInterpreter private constructor ( internal fun copy(): TestLedgerDSLInterpreter = TestLedgerDSLInterpreter( - identityService, - storageService, + services, labelToOutputStateAndRefs = HashMap(labelToOutputStateAndRefs), transactionWithLocations = HashMap(transactionWithLocations), nonVerifiedTransactionWithLocations = HashMap(nonVerifiedTransactionWithLocations) @@ -189,7 +188,7 @@ data class TestLedgerDSLInterpreter private constructor ( internal fun resolveWireTransaction(wireTransaction: WireTransaction): TransactionForVerification { return wireTransaction.run { val authenticatedCommands = commands.map { - AuthenticatedObject(it.signers, it.signers.mapNotNull { identityService.partyFromKey(it) }, it.value) + AuthenticatedObject(it.signers, it.signers.mapNotNull { services.identityService.partyFromKey(it) }, it.value) } val resolvedInputStates = inputs.map { resolveStateRef(it) } val resolvedAttachments = attachments.map { resolveAttachment(it) } @@ -220,7 +219,7 @@ data class TestLedgerDSLInterpreter private constructor ( } internal fun resolveAttachment(attachmentId: SecureHash): Attachment = - storageService.attachments.openAttachment(attachmentId) ?: throw AttachmentResolutionException(attachmentId) + services.storageService.attachments.openAttachment(attachmentId) ?: throw AttachmentResolutionException(attachmentId) private fun interpretTransactionDsl( transactionBuilder: TransactionBuilder, @@ -233,10 +232,10 @@ data class TestLedgerDSLInterpreter private constructor ( fun toTransactionGroup(): TransactionGroup { val ledgerTransactions = transactionWithLocations.map { - it.value.transaction.toLedgerTransaction(identityService, storageService.attachments) + it.value.transaction.toLedgerTransaction(services.identityService, services.storageService.attachments) } val nonVerifiedLedgerTransactions = nonVerifiedTransactionWithLocations.map { - it.value.transaction.toLedgerTransaction(identityService, storageService.attachments) + it.value.transaction.toLedgerTransaction(services.identityService, services.storageService.attachments) } return TransactionGroup(ledgerTransactions.toSet(), nonVerifiedLedgerTransactions.toSet()) } @@ -295,7 +294,7 @@ data class TestLedgerDSLInterpreter private constructor ( dsl(LedgerDSL(copy())) override fun attachment(attachment: InputStream): SecureHash { - return storageService.attachments.importAttachment(attachment) + return services.storageService.attachments.importAttachment(attachment) } override fun verifies(): EnforceVerifyOrFail { @@ -322,6 +321,9 @@ data class TestLedgerDSLInterpreter private constructor ( return stateAndRef as StateAndRef } } + + val transactionsToVerify: List get() = transactionWithLocations.values.map { it.transaction } + val transactionsUnverified: List get() = nonVerifiedTransactionWithLocations.values.map { it.transaction } } /** @@ -330,7 +332,7 @@ data class TestLedgerDSLInterpreter private constructor ( * @param extraKeys extra keys to sign transactions with. * @return List of [SignedTransaction]s. */ -fun signAll(transactionsToSign: List, extraKeys: Array) = transactionsToSign.map { wtx -> +fun signAll(transactionsToSign: List, extraKeys: List) = transactionsToSign.map { wtx -> val allPubKeys = wtx.signers.toMutableSet() val bits = wtx.serialize() require(bits == wtx.serialized) @@ -350,4 +352,4 @@ fun signAll(transactionsToSign: List, extraKeys: Array.signAll( - vararg extraKeys: KeyPair) = signAll(this.interpreter.wireTransactions, extraKeys) + vararg extraKeys: KeyPair) = signAll(this.interpreter.wireTransactions, extraKeys.toList()) diff --git a/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt b/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt index 520c7d339c..0beef77c22 100644 --- a/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt +++ b/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt @@ -246,7 +246,7 @@ class TwoPartyTradeProtocolTests { val aliceNode = makeNodeWithTracking(notaryNode.info, ALICE.name, ALICE_KEY) val bobNode = makeNodeWithTracking(notaryNode.info, BOB.name, BOB_KEY) - ledger(storageService = aliceNode.storage) { + ledger(aliceNode.services) { // Insert a prospectus type attachment into the commercial paper transaction. val stream = ByteArrayOutputStream() @@ -413,7 +413,7 @@ class TwoPartyTradeProtocolTests { wtxToSign: List, services: ServiceHub, vararg extraKeys: KeyPair): Map { - val signed: List = signAll(wtxToSign, extraKeys) + val signed: List = signAll(wtxToSign, extraKeys.toList()) services.recordTransactions(signed) val validatedTransactions = services.storageService.validatedTransactions if (validatedTransactions is RecordingTransactionStorage) { diff --git a/node/src/test/kotlin/com/r3corda/node/visualiser/GroupToGraphConversion.kt b/node/src/test/kotlin/com/r3corda/node/visualiser/GroupToGraphConversion.kt index 4dc34b2f5d..51ce731508 100644 --- a/node/src/test/kotlin/com/r3corda/node/visualiser/GroupToGraphConversion.kt +++ b/node/src/test/kotlin/com/r3corda/node/visualiser/GroupToGraphConversion.kt @@ -17,13 +17,13 @@ class GraphVisualiser(val dsl: LedgerDSL("tx$txIndex") - if (tx !in tg.nonVerifiedRoots) + if (tx !in testLedger.transactionsUnverified) txNode.label = dsl.interpreter.transactionName(tx.id).let { it ?: "TX[${tx.id.prefixChars()}]" } txNode.styleClass = "tx" @@ -48,7 +48,7 @@ class GraphVisualiser(val dsl: LedgerDSL("tx$txIndex-in$inputIndex", ref.toString(), "tx$txIndex", true) edge.weight = 1.2 From 301d787e452be8c222f60ac060e7c0ad896f7760 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 29 Jul 2016 15:43:24 +0200 Subject: [PATCH 4/5] Fix bugs in the trade finance contracts and tests, where txns weren't being properly verified and thus the lack of timestamping wasn't detected. --- .../com/r3corda/contracts/BillOfLadingAgreement.kt | 9 +++++++-- .../src/main/kotlin/com/r3corda/contracts/LOC.kt | 7 ++++++- .../com/r3corda/contracts/BillOfLadingAgreementTests.kt | 3 ++- .../src/test/kotlin/com/r3corda/contracts/LOCTests.kt | 1 + 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/experimental/src/main/kotlin/com/r3corda/contracts/BillOfLadingAgreement.kt b/experimental/src/main/kotlin/com/r3corda/contracts/BillOfLadingAgreement.kt index 4bf0cad38c..c6839ca306 100644 --- a/experimental/src/main/kotlin/com/r3corda/contracts/BillOfLadingAgreement.kt +++ b/experimental/src/main/kotlin/com/r3corda/contracts/BillOfLadingAgreement.kt @@ -3,7 +3,10 @@ package com.r3corda.contracts import com.r3corda.core.contracts.* import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.SecureHash +import com.r3corda.core.days +import com.r3corda.core.testing.DUMMY_NOTARY import java.security.PublicKey +import java.time.Instant import java.time.LocalDate ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -109,9 +112,11 @@ class BillOfLadingAgreement : Contract { /** * Returns a transaction that issues a Bill of Lading Agreement */ - fun generateIssue(owner: PublicKey, beneficiary: Party, props: BillOfLadingProperties, notary: Party? = null): TransactionBuilder { + fun generateIssue(owner: PublicKey, beneficiary: Party, props: BillOfLadingProperties, notary: Party = DUMMY_NOTARY): TransactionBuilder { val state = State(owner, beneficiary, props) - return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.IssueBL(), props.carrierOwner.owningKey)) + val builder = TransactionType.General.Builder(notary = notary) + builder.setTime(Instant.now(), notary, 1.days) + return builder.withItems(state, Command(Commands.IssueBL(), props.carrierOwner.owningKey)) } /** diff --git a/experimental/src/main/kotlin/com/r3corda/contracts/LOC.kt b/experimental/src/main/kotlin/com/r3corda/contracts/LOC.kt index 846b0fd5b7..7fc58657f0 100644 --- a/experimental/src/main/kotlin/com/r3corda/contracts/LOC.kt +++ b/experimental/src/main/kotlin/com/r3corda/contracts/LOC.kt @@ -4,7 +4,10 @@ import com.r3corda.contracts.asset.sumCashBy import com.r3corda.core.contracts.* import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.SecureHash +import com.r3corda.core.days +import com.r3corda.core.testing.DUMMY_NOTARY import java.security.PublicKey +import java.time.Instant import java.time.LocalDate import java.time.Period import java.time.ZoneOffset @@ -142,7 +145,9 @@ class LOC : Contract { fun generateIssue(beneficiaryPaid: Boolean, issued: Boolean, terminated: Boolean, props: LOCProperties, notary: Party): TransactionBuilder { val state = State(beneficiaryPaid, issued, terminated, props) - return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Issuance(), props.issuingbank.owningKey)) + val builder = TransactionType.General.Builder(notary = notary) + builder.setTime(Instant.now(), notary, 1.days) + return builder.withItems(state, Command(Commands.Issuance(), props.issuingbank.owningKey)) } diff --git a/experimental/src/test/kotlin/com/r3corda/contracts/BillOfLadingAgreementTests.kt b/experimental/src/test/kotlin/com/r3corda/contracts/BillOfLadingAgreementTests.kt index 200d32ff64..1b81c75cde 100644 --- a/experimental/src/test/kotlin/com/r3corda/contracts/BillOfLadingAgreementTests.kt +++ b/experimental/src/test/kotlin/com/r3corda/contracts/BillOfLadingAgreementTests.kt @@ -48,8 +48,9 @@ class BillOfLadingAgreementTests { @Test fun issueGenerationMethod() { - val ptx = BillOfLadingAgreement().generateIssue(Bill.owner, Bill.beneficiary,Bill.props, notary = DUMMY_NOTARY).apply { + val ptx = BillOfLadingAgreement().generateIssue(Bill.owner, Bill.beneficiary,Bill.props).apply { signWith(ALICE_KEY) + signWith(DUMMY_NOTARY_KEY) } val stx = ptx.toSignedTransaction() stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE,attachments) diff --git a/experimental/src/test/kotlin/com/r3corda/contracts/LOCTests.kt b/experimental/src/test/kotlin/com/r3corda/contracts/LOCTests.kt index e8a5a31087..3c1f7d8c78 100644 --- a/experimental/src/test/kotlin/com/r3corda/contracts/LOCTests.kt +++ b/experimental/src/test/kotlin/com/r3corda/contracts/LOCTests.kt @@ -120,6 +120,7 @@ class LOCTests { fun issueSignedByBank() { val ptx = LOC().generateIssue(LOCstate.beneficiaryPaid, LOCstate.issued, LOCstate.terminated, LOCstate.props, DUMMY_NOTARY).apply { signWith(MEGA_CORP_KEY) + signWith(DUMMY_NOTARY_KEY) } val stx = ptx.toSignedTransaction() stx.verify() From c2aee2d4e8dc00097f101a5cbaaefff7c22f782e Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Tue, 2 Aug 2016 17:01:17 +0200 Subject: [PATCH 5/5] Minor: Replace MockServices with a new class that's in core instead of node, old class becomes MockServiceHubInternal. --- .../com/r3corda/core/node/services/testing/MockServices.kt | 2 +- .../main/kotlin/com/r3corda/core/testing/CoreTestUtils.kt | 6 +++--- .../kotlin/com/r3corda/contracts/BillOfLadingAgreement.kt | 3 +-- .../com/r3corda/contracts/BillOfLadingAgreementTests.kt | 6 +++--- .../services/{MockServices.kt => MockServiceHubInternal.kt} | 2 +- .../com/r3corda/node/services/NodeSchedulerServiceTest.kt | 2 +- .../kotlin/com/r3corda/node/services/WalletWithCashTest.kt | 6 +++--- .../node/services/statemachine/StateMachineManagerTests.kt | 4 ++-- 8 files changed, 15 insertions(+), 16 deletions(-) rename node/src/test/kotlin/com/r3corda/node/services/{MockServices.kt => MockServiceHubInternal.kt} (99%) diff --git a/core/src/main/kotlin/com/r3corda/core/node/services/testing/MockServices.kt b/core/src/main/kotlin/com/r3corda/core/node/services/testing/MockServices.kt index e26671715a..477d55fce0 100644 --- a/core/src/main/kotlin/com/r3corda/core/node/services/testing/MockServices.kt +++ b/core/src/main/kotlin/com/r3corda/core/node/services/testing/MockServices.kt @@ -34,7 +34,7 @@ import javax.annotation.concurrent.ThreadSafe * A singleton utility that only provides a mock identity, key and storage service. However, this is sufficient for * building chains of transactions and verifying them. It isn't sufficient for testing protocols however. */ -open class UnitTestServices(val key: KeyPair = generateKeyPair()) : ServiceHub { +open class MockServices(val key: KeyPair = generateKeyPair()) : ServiceHub { override fun invokeProtocolAsync(logicType: Class>, vararg args: Any?): ListenableFuture { throw UnsupportedOperationException("not implemented") } diff --git a/core/src/main/kotlin/com/r3corda/core/testing/CoreTestUtils.kt b/core/src/main/kotlin/com/r3corda/core/testing/CoreTestUtils.kt index 600dce87c6..952484a335 100644 --- a/core/src/main/kotlin/com/r3corda/core/testing/CoreTestUtils.kt +++ b/core/src/main/kotlin/com/r3corda/core/testing/CoreTestUtils.kt @@ -9,7 +9,7 @@ import com.r3corda.core.contracts.TransactionBuilder import com.r3corda.core.crypto.* import com.r3corda.core.node.ServiceHub import com.r3corda.core.node.services.testing.MockIdentityService -import com.r3corda.core.node.services.testing.UnitTestServices +import com.r3corda.core.node.services.testing.MockServices import java.math.BigInteger import java.net.ServerSocket import java.security.KeyPair @@ -94,10 +94,10 @@ fun freeLocalHostAndPort(): HostAndPort { /** * Creates and tests a ledger built by the passed in dsl. The provided services can be customised, otherwise a default - * of a freshly built [UnitTestServices] is used. + * of a freshly built [MockServices] is used. */ @JvmOverloads fun ledger( - services: ServiceHub = UnitTestServices(), + services: ServiceHub = MockServices(), dsl: LedgerDSL.() -> Unit ): LedgerDSL { val ledgerDsl = LedgerDSL(TestLedgerDSLInterpreter(services)) diff --git a/experimental/src/main/kotlin/com/r3corda/contracts/BillOfLadingAgreement.kt b/experimental/src/main/kotlin/com/r3corda/contracts/BillOfLadingAgreement.kt index c6839ca306..59db2d87f5 100644 --- a/experimental/src/main/kotlin/com/r3corda/contracts/BillOfLadingAgreement.kt +++ b/experimental/src/main/kotlin/com/r3corda/contracts/BillOfLadingAgreement.kt @@ -4,7 +4,6 @@ import com.r3corda.core.contracts.* import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.SecureHash import com.r3corda.core.days -import com.r3corda.core.testing.DUMMY_NOTARY import java.security.PublicKey import java.time.Instant import java.time.LocalDate @@ -112,7 +111,7 @@ class BillOfLadingAgreement : Contract { /** * Returns a transaction that issues a Bill of Lading Agreement */ - fun generateIssue(owner: PublicKey, beneficiary: Party, props: BillOfLadingProperties, notary: Party = DUMMY_NOTARY): TransactionBuilder { + fun generateIssue(owner: PublicKey, beneficiary: Party, props: BillOfLadingProperties, notary: Party): TransactionBuilder { val state = State(owner, beneficiary, props) val builder = TransactionType.General.Builder(notary = notary) builder.setTime(Instant.now(), notary, 1.days) diff --git a/experimental/src/test/kotlin/com/r3corda/contracts/BillOfLadingAgreementTests.kt b/experimental/src/test/kotlin/com/r3corda/contracts/BillOfLadingAgreementTests.kt index 1b81c75cde..4c0c169302 100644 --- a/experimental/src/test/kotlin/com/r3corda/contracts/BillOfLadingAgreementTests.kt +++ b/experimental/src/test/kotlin/com/r3corda/contracts/BillOfLadingAgreementTests.kt @@ -48,7 +48,7 @@ class BillOfLadingAgreementTests { @Test fun issueGenerationMethod() { - val ptx = BillOfLadingAgreement().generateIssue(Bill.owner, Bill.beneficiary,Bill.props).apply { + val ptx = BillOfLadingAgreement().generateIssue(Bill.owner, Bill.beneficiary, Bill.props, DUMMY_NOTARY).apply { signWith(ALICE_KEY) signWith(DUMMY_NOTARY_KEY) } @@ -58,14 +58,14 @@ class BillOfLadingAgreementTests { @Test(expected = IllegalStateException::class) fun issueGenerationMethod_Unsigned() { - val ptx = BillOfLadingAgreement().generateIssue(Bill.owner, Bill.beneficiary, Bill.props) + val ptx = BillOfLadingAgreement().generateIssue(Bill.owner, Bill.beneficiary, Bill.props, DUMMY_NOTARY) val stx = ptx.toSignedTransaction() stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE,attachments) } @Test(expected = IllegalStateException::class) fun issueGenerationMethod_KeyMismatch() { - val ptx = BillOfLadingAgreement().generateIssue(Bill.owner, Bill.beneficiary, Bill.props).apply { + val ptx = BillOfLadingAgreement().generateIssue(Bill.owner, Bill.beneficiary, Bill.props, DUMMY_NOTARY).apply { signWith(BOB_KEY) } val stx = ptx.toSignedTransaction() diff --git a/node/src/test/kotlin/com/r3corda/node/services/MockServices.kt b/node/src/test/kotlin/com/r3corda/node/services/MockServiceHubInternal.kt similarity index 99% rename from node/src/test/kotlin/com/r3corda/node/services/MockServices.kt rename to node/src/test/kotlin/com/r3corda/node/services/MockServiceHubInternal.kt index 4bf957f5d4..251d3652c2 100644 --- a/node/src/test/kotlin/com/r3corda/node/services/MockServices.kt +++ b/node/src/test/kotlin/com/r3corda/node/services/MockServiceHubInternal.kt @@ -19,7 +19,7 @@ import com.r3corda.node.services.statemachine.StateMachineManager import com.r3corda.node.services.wallet.NodeWalletService import java.time.Clock -open class MockServices( +open class MockServiceHubInternal( customWallet: WalletService? = null, val keyManagement: KeyManagementService? = null, val net: MessagingServiceInternal? = null, diff --git a/node/src/test/kotlin/com/r3corda/node/services/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/com/r3corda/node/services/NodeSchedulerServiceTest.kt index c2f20ec8b3..1b298893e9 100644 --- a/node/src/test/kotlin/com/r3corda/node/services/NodeSchedulerServiceTest.kt +++ b/node/src/test/kotlin/com/r3corda/node/services/NodeSchedulerServiceTest.kt @@ -60,7 +60,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { init { val kms = MockKeyManagementService(ALICE_KEY) val mockMessagingService = InMemoryMessagingNetwork(false).InMemoryMessaging(false, InMemoryMessagingNetwork.Handle(0, "None")) - val mockServices = object : MockServices(overrideClock = testClock, keyManagement = kms, net = mockMessagingService), TestReference { + val mockServices = object : MockServiceHubInternal(overrideClock = testClock, keyManagement = kms, net = mockMessagingService), TestReference { override val testReference = this@NodeSchedulerServiceTest } services = mockServices diff --git a/node/src/test/kotlin/com/r3corda/node/services/WalletWithCashTest.kt b/node/src/test/kotlin/com/r3corda/node/services/WalletWithCashTest.kt index 290f0988cb..4582b89fa3 100644 --- a/node/src/test/kotlin/com/r3corda/node/services/WalletWithCashTest.kt +++ b/node/src/test/kotlin/com/r3corda/node/services/WalletWithCashTest.kt @@ -8,7 +8,7 @@ import com.r3corda.core.contracts.* import com.r3corda.core.crypto.SecureHash import com.r3corda.core.node.services.WalletService import com.r3corda.core.node.services.testing.MockStorageService -import com.r3corda.core.node.services.testing.UnitTestServices +import com.r3corda.core.node.services.testing.MockServices import com.r3corda.core.testing.* import com.r3corda.core.utilities.BriefLogFormatter import com.r3corda.node.services.wallet.NodeWalletService @@ -23,13 +23,13 @@ import kotlin.test.assertNull // TODO: Move this to the cash contract tests once mock services are further split up. class WalletWithCashTest { - lateinit var services: UnitTestServices + lateinit var services: MockServices val wallet: WalletService get() = services.walletService @Before fun setUp() { BriefLogFormatter.loggingOn(NodeWalletService::class) - services = object : UnitTestServices() { + services = object : MockServices() { override val walletService: WalletService = NodeWalletService(this) override fun recordTransactions(txs: Iterable) { diff --git a/node/src/test/kotlin/com/r3corda/node/services/statemachine/StateMachineManagerTests.kt b/node/src/test/kotlin/com/r3corda/node/services/statemachine/StateMachineManagerTests.kt index 4fd25015bd..8c9ab91a95 100644 --- a/node/src/test/kotlin/com/r3corda/node/services/statemachine/StateMachineManagerTests.kt +++ b/node/src/test/kotlin/com/r3corda/node/services/statemachine/StateMachineManagerTests.kt @@ -4,7 +4,7 @@ import co.paralleluniverse.fibers.Fiber import co.paralleluniverse.fibers.Suspendable import com.r3corda.core.messaging.MessagingService import com.r3corda.core.protocols.ProtocolLogic -import com.r3corda.node.services.MockServices +import com.r3corda.node.services.MockServiceHubInternal import com.r3corda.node.services.api.Checkpoint import com.r3corda.node.services.api.CheckpointStorage import com.r3corda.node.services.api.MessagingServiceInternal @@ -45,7 +45,7 @@ class StateMachineManagerTests { assertThat(protocol.lazyTime).isNotNull() } - private fun createManager() = StateMachineManager(object : MockServices() { + private fun createManager() = StateMachineManager(object : MockServiceHubInternal() { override val networkService: MessagingServiceInternal get() = network }, emptyList(), checkpointStorage, AffinityExecutor.SAME_THREAD)