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)