mirror of
https://github.com/corda/corda.git
synced 2025-04-08 11:54:44 +00:00
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.
This commit is contained in:
parent
dd53acfb64
commit
ba05b90b8f
@ -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<Currency>,
|
||||
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<SignedTransaction> = 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<Currency>, 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
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -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<SignedTransaction>) {
|
||||
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<NodeWalletService, ServiceHub> {
|
||||
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")
|
||||
|
@ -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 {
|
||||
|
@ -136,7 +136,6 @@ fun runTraderDemo(args: Array<String>): 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<String>): Int {
|
||||
val path = Paths.get(baseDirectory, Role.BUYER.name.toLowerCase(), "identity-public")
|
||||
val party = Files.readAllBytes(path).deserialize<Party>()
|
||||
advertisedServices = emptySet()
|
||||
cashIssuer = party
|
||||
NodeInfo(ArtemisMessagingService.makeRecipient(theirNetAddr), party, setOf(NetworkMapService.Type))
|
||||
}
|
||||
|
||||
@ -157,18 +155,15 @@ fun runTraderDemo(args: Array<String>): 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<Currency>) {
|
||||
// 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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user