Merged in mike-ledgertx-refactoring-part-2 (pull request #260)

More simplifications to unit testing, introduce UnitTestServices
This commit is contained in:
Mike Hearn 2016-08-04 12:54:28 +02:00
commit 918de94a22
17 changed files with 136 additions and 113 deletions

View File

@ -2,6 +2,8 @@
package com.r3corda.contracts.testing package com.r3corda.contracts.testing
import com.r3corda.contracts.asset.Cash 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.Amount
import com.r3corda.core.contracts.Issued import com.r3corda.core.contracts.Issued
import com.r3corda.core.contracts.SignedTransaction 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.node.services.Wallet
import com.r3corda.core.serialization.OpaqueBytes import com.r3corda.core.serialization.OpaqueBytes
import com.r3corda.core.testing.DUMMY_NOTARY import com.r3corda.core.testing.DUMMY_NOTARY
import java.security.PublicKey
import java.util.* 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 * 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. * 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 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.
* *
* The service hub needs to provide at least a key management service and a 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, atLeastThisManyStates: Int = 3,
atMostThisManyStates: Int = 10, atMostThisManyStates: Int = 10,
rng: Random = Random(), 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 amounts = calculateRandomlySizedAmounts(howMuch, atLeastThisManyStates, atMostThisManyStates, rng)
val myIdentity = storageService.myLegalIdentity val myKey: PublicKey = ownedBy ?: storageService.myLegalIdentityKey.public
val myKey = storageService.myLegalIdentityKey
// We will allocate one state to one transaction, for simplicities sake. // We will allocate one state to one transaction, for simplicities sake.
val cash = Cash() val cash = Cash()
val transactions: List<SignedTransaction> = amounts.map { pennies -> 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 issuance = TransactionType.General.Builder()
val freshKey = keyManagementService.freshKey() cash.generateIssue(issuance, Amount(pennies, Issued(DUMMY_CASH_ISSUER.copy(reference = ref), howMuch.token)), myKey, notary)
cash.generateIssue(issuance, Amount(pennies, Issued(depositRef, howMuch.token)), freshKey.public, notary) issuance.signWith(DUMMY_CASH_ISSUER_KEY)
issuance.signWith(myKey)
return@map issuance.toSignedTransaction(true) return@map issuance.toSignedTransaction(true)
} }
@ -77,7 +72,7 @@ private fun calculateRandomlySizedAmounts(howMuch: Amount<Currency>, min: Int, m
// Handle inexact rounding. // Handle inexact rounding.
amounts[i] = howMuch.quantity - filledSoFar amounts[i] = howMuch.quantity - filledSoFar
} }
check(amounts[i] >= 0) { amounts[i] } check(amounts[i] >= 0) { "${amounts[i]} : $filledSoFar : $howMuch" }
} }
check(amounts.sum() == howMuch.quantity) check(amounts.sum() == howMuch.quantity)
return amounts return amounts

View File

@ -1,13 +1,20 @@
package com.r3corda.core.node.services.testing 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.Attachment
import com.r3corda.core.contracts.SignedTransaction import com.r3corda.core.contracts.SignedTransaction
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.crypto.generateKeyPair import com.r3corda.core.crypto.generateKeyPair
import com.r3corda.core.crypto.sha256 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.node.services.*
import com.r3corda.core.protocols.ProtocolLogic
import com.r3corda.core.serialization.SingletonSerializeAsToken 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.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
@ -15,10 +22,40 @@ import java.io.InputStream
import java.security.KeyPair import java.security.KeyPair
import java.security.PrivateKey import java.security.PrivateKey
import java.security.PublicKey import java.security.PublicKey
import java.time.Clock
import java.util.* import java.util.*
import java.util.jar.JarInputStream import java.util.jar.JarInputStream
import javax.annotation.concurrent.ThreadSafe 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 MockServices(val key: KeyPair = generateKeyPair()) : ServiceHub {
override fun <T : Any> invokeProtocolAsync(logicType: Class<out ProtocolLogic<T>>, vararg args: Any?): ListenableFuture<T> {
throw UnsupportedOperationException("not implemented")
}
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
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 @ThreadSafe
class MockIdentityService(val identities: List<Party>) : IdentityService, SingletonSerializeAsToken() { class MockIdentityService(val identities: List<Party>) : IdentityService, SingletonSerializeAsToken() {
private val keyToParties: Map<PublicKey, Party> private val keyToParties: Map<PublicKey, Party>

View File

@ -4,14 +4,12 @@ package com.r3corda.core.testing
import com.google.common.base.Throwables import com.google.common.base.Throwables
import com.google.common.net.HostAndPort import com.google.common.net.HostAndPort
import com.r3corda.core.contracts.Attachment
import com.r3corda.core.contracts.StateRef import com.r3corda.core.contracts.StateRef
import com.r3corda.core.contracts.TransactionBuilder import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.crypto.* import com.r3corda.core.crypto.*
import com.r3corda.core.node.services.IdentityService import com.r3corda.core.node.ServiceHub
import com.r3corda.core.node.services.StorageService
import com.r3corda.core.node.services.testing.MockIdentityService import com.r3corda.core.node.services.testing.MockIdentityService
import com.r3corda.core.node.services.testing.MockStorageService import com.r3corda.core.node.services.testing.MockServices
import java.math.BigInteger import java.math.BigInteger
import java.net.ServerSocket import java.net.ServerSocket
import java.security.KeyPair import java.security.KeyPair
@ -95,17 +93,14 @@ fun freeLocalHostAndPort(): HostAndPort {
} }
/** /**
* Creates and tests a ledger built by the passed in dsl. * Creates and tests a ledger built by the passed in dsl. The provided services can be customised, otherwise a default
* @param identityService: The [IdentityService] to be used while building the ledger. * of a freshly built [MockServices] is used.
* @param storageService: The [StorageService] to be used for storing e.g. [Attachment]s.
* @param dsl: The dsl building the ledger.
*/ */
@JvmOverloads fun ledger( @JvmOverloads fun ledger(
identityService: IdentityService = MOCK_IDENTITY_SERVICE, services: ServiceHub = MockServices(),
storageService: StorageService = MockStorageService(),
dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
): LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> { ): LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
val ledgerDsl = LedgerDSL(TestLedgerDSLInterpreter(identityService, storageService)) val ledgerDsl = LedgerDSL(TestLedgerDSLInterpreter(services))
dsl(ledgerDsl) dsl(ledgerDsl)
return ledgerDsl return ledgerDsl
} }

View File

@ -40,11 +40,13 @@ interface Verifies {
val exceptionMessage = exception.message val exceptionMessage = exception.message
if (exceptionMessage == null) { if (exceptionMessage == null) {
throw AssertionError( 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())) { } else if (!exceptionMessage.toLowerCase().contains(expectedMessage.toLowerCase())) {
throw AssertionError( throw AssertionError(
"Expected exception containing '$expectedMessage' but raised exception was '$exception'" "Expected exception containing '$expectedMessage' but raised exception was '$exception'",
exception
) )
} }
} }

View File

@ -5,8 +5,7 @@ import com.r3corda.core.crypto.DigitalSignature
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.crypto.signWithECDSA import com.r3corda.core.crypto.signWithECDSA
import com.r3corda.core.node.services.IdentityService import com.r3corda.core.node.ServiceHub
import com.r3corda.core.node.services.StorageService
import com.r3corda.core.serialization.serialize import com.r3corda.core.serialization.serialize
import java.io.InputStream import java.io.InputStream
import java.security.KeyPair import java.security.KeyPair
@ -95,6 +94,10 @@ data class TestTransactionDSLInterpreter private constructor(
transactionBuilder: TransactionBuilder transactionBuilder: TransactionBuilder
) : this(ledgerInterpreter, transactionBuilder, HashMap()) ) : this(ledgerInterpreter, transactionBuilder, HashMap())
val services = object : ServiceHub by ledgerInterpreter.services {
override fun loadState(stateRef: StateRef) = ledgerInterpreter.resolveStateRef<ContractState>(stateRef)
}
private fun copy(): TestTransactionDSLInterpreter = private fun copy(): TestTransactionDSLInterpreter =
TestTransactionDSLInterpreter( TestTransactionDSLInterpreter(
ledgerInterpreter = ledgerInterpreter, ledgerInterpreter = ledgerInterpreter,
@ -141,18 +144,15 @@ data class TestTransactionDSLInterpreter private constructor(
} }
data class TestLedgerDSLInterpreter private constructor ( data class TestLedgerDSLInterpreter private constructor (
private val identityService: IdentityService, val services: ServiceHub,
private val storageService: StorageService,
internal val labelToOutputStateAndRefs: HashMap<String, StateAndRef<ContractState>> = HashMap(), internal val labelToOutputStateAndRefs: HashMap<String, StateAndRef<ContractState>> = HashMap(),
private val transactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap(), private val transactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = LinkedHashMap(),
private val nonVerifiedTransactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap() private val nonVerifiedTransactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap()
) : LedgerDSLInterpreter<TestTransactionDSLInterpreter> { ) : LedgerDSLInterpreter<TestTransactionDSLInterpreter> {
val wireTransactions: List<WireTransaction> get() = transactionWithLocations.values.map { it.transaction } val wireTransactions: List<WireTransaction> get() = transactionWithLocations.values.map { it.transaction }
// We specify [labelToOutputStateAndRefs] just so that Kotlin picks the primary constructor instead of cycling // We specify [labelToOutputStateAndRefs] just so that Kotlin picks the primary constructor instead of cycling
constructor(identityService: IdentityService, storageService: StorageService) : this( constructor(services: ServiceHub) : this(services, labelToOutputStateAndRefs = HashMap())
identityService, storageService, labelToOutputStateAndRefs = HashMap()
)
companion object { companion object {
private fun getCallerLocation(): String? { private fun getCallerLocation(): String? {
@ -179,8 +179,7 @@ data class TestLedgerDSLInterpreter private constructor (
internal fun copy(): TestLedgerDSLInterpreter = internal fun copy(): TestLedgerDSLInterpreter =
TestLedgerDSLInterpreter( TestLedgerDSLInterpreter(
identityService, services,
storageService,
labelToOutputStateAndRefs = HashMap(labelToOutputStateAndRefs), labelToOutputStateAndRefs = HashMap(labelToOutputStateAndRefs),
transactionWithLocations = HashMap(transactionWithLocations), transactionWithLocations = HashMap(transactionWithLocations),
nonVerifiedTransactionWithLocations = HashMap(nonVerifiedTransactionWithLocations) nonVerifiedTransactionWithLocations = HashMap(nonVerifiedTransactionWithLocations)
@ -189,7 +188,7 @@ data class TestLedgerDSLInterpreter private constructor (
internal fun resolveWireTransaction(wireTransaction: WireTransaction): TransactionForVerification { internal fun resolveWireTransaction(wireTransaction: WireTransaction): TransactionForVerification {
return wireTransaction.run { return wireTransaction.run {
val authenticatedCommands = commands.map { 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<ContractState>(it) } val resolvedInputStates = inputs.map { resolveStateRef<ContractState>(it) }
val resolvedAttachments = attachments.map { resolveAttachment(it) } val resolvedAttachments = attachments.map { resolveAttachment(it) }
@ -220,7 +219,7 @@ data class TestLedgerDSLInterpreter private constructor (
} }
internal fun resolveAttachment(attachmentId: SecureHash): Attachment = internal fun resolveAttachment(attachmentId: SecureHash): Attachment =
storageService.attachments.openAttachment(attachmentId) ?: throw AttachmentResolutionException(attachmentId) services.storageService.attachments.openAttachment(attachmentId) ?: throw AttachmentResolutionException(attachmentId)
private fun <R> interpretTransactionDsl( private fun <R> interpretTransactionDsl(
transactionBuilder: TransactionBuilder, transactionBuilder: TransactionBuilder,
@ -233,10 +232,10 @@ data class TestLedgerDSLInterpreter private constructor (
fun toTransactionGroup(): TransactionGroup { fun toTransactionGroup(): TransactionGroup {
val ledgerTransactions = transactionWithLocations.map { val ledgerTransactions = transactionWithLocations.map {
it.value.transaction.toLedgerTransaction(identityService, storageService.attachments) it.value.transaction.toLedgerTransaction(services.identityService, services.storageService.attachments)
} }
val nonVerifiedLedgerTransactions = nonVerifiedTransactionWithLocations.map { 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()) return TransactionGroup(ledgerTransactions.toSet(), nonVerifiedLedgerTransactions.toSet())
} }
@ -295,7 +294,7 @@ data class TestLedgerDSLInterpreter private constructor (
dsl(LedgerDSL(copy())) dsl(LedgerDSL(copy()))
override fun attachment(attachment: InputStream): SecureHash { override fun attachment(attachment: InputStream): SecureHash {
return storageService.attachments.importAttachment(attachment) return services.storageService.attachments.importAttachment(attachment)
} }
override fun verifies(): EnforceVerifyOrFail { override fun verifies(): EnforceVerifyOrFail {
@ -322,6 +321,9 @@ data class TestLedgerDSLInterpreter private constructor (
return stateAndRef as StateAndRef<S> return stateAndRef as StateAndRef<S>
} }
} }
val transactionsToVerify: List<WireTransaction> get() = transactionWithLocations.values.map { it.transaction }
val transactionsUnverified: List<WireTransaction> get() = nonVerifiedTransactionWithLocations.values.map { it.transaction }
} }
/** /**
@ -330,7 +332,7 @@ data class TestLedgerDSLInterpreter private constructor (
* @param extraKeys extra keys to sign transactions with. * @param extraKeys extra keys to sign transactions with.
* @return List of [SignedTransaction]s. * @return List of [SignedTransaction]s.
*/ */
fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: Array<out KeyPair>) = transactionsToSign.map { wtx -> fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: List<KeyPair>) = transactionsToSign.map { wtx ->
val allPubKeys = wtx.signers.toMutableSet() val allPubKeys = wtx.signers.toMutableSet()
val bits = wtx.serialize() val bits = wtx.serialize()
require(bits == wtx.serialized) require(bits == wtx.serialized)
@ -350,4 +352,4 @@ fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: Array<out KeyP
* @return List of [SignedTransaction]s. * @return List of [SignedTransaction]s.
*/ */
fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.signAll( fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.signAll(
vararg extraKeys: KeyPair) = signAll(this.interpreter.wireTransactions, extraKeys) vararg extraKeys: KeyPair) = signAll(this.interpreter.wireTransactions, extraKeys.toList())

View File

@ -3,7 +3,9 @@ package com.r3corda.contracts
import com.r3corda.core.contracts.* import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.days
import java.security.PublicKey import java.security.PublicKey
import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -109,9 +111,11 @@ class BillOfLadingAgreement : Contract {
/** /**
* Returns a transaction that issues a Bill of Lading Agreement * 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): TransactionBuilder {
val state = State(owner, beneficiary, props) 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))
} }
/** /**

View File

@ -4,7 +4,10 @@ import com.r3corda.contracts.asset.sumCashBy
import com.r3corda.core.contracts.* import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.days
import com.r3corda.core.testing.DUMMY_NOTARY
import java.security.PublicKey import java.security.PublicKey
import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.time.Period import java.time.Period
import java.time.ZoneOffset import java.time.ZoneOffset
@ -142,7 +145,9 @@ class LOC : Contract {
fun generateIssue(beneficiaryPaid: Boolean, issued: Boolean, terminated: Boolean, props: LOCProperties, notary: Party): TransactionBuilder { fun generateIssue(beneficiaryPaid: Boolean, issued: Boolean, terminated: Boolean, props: LOCProperties, notary: Party): TransactionBuilder {
val state = State(beneficiaryPaid, issued, terminated, props) 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))
} }

View File

@ -48,8 +48,9 @@ class BillOfLadingAgreementTests {
@Test @Test
fun issueGenerationMethod() { 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, DUMMY_NOTARY).apply {
signWith(ALICE_KEY) signWith(ALICE_KEY)
signWith(DUMMY_NOTARY_KEY)
} }
val stx = ptx.toSignedTransaction() val stx = ptx.toSignedTransaction()
stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE,attachments) stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE,attachments)
@ -57,14 +58,14 @@ class BillOfLadingAgreementTests {
@Test(expected = IllegalStateException::class) @Test(expected = IllegalStateException::class)
fun issueGenerationMethod_Unsigned() { 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() val stx = ptx.toSignedTransaction()
stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE,attachments) stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE,attachments)
} }
@Test(expected = IllegalStateException::class) @Test(expected = IllegalStateException::class)
fun issueGenerationMethod_KeyMismatch() { 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) signWith(BOB_KEY)
} }
val stx = ptx.toSignedTransaction() val stx = ptx.toSignedTransaction()

View File

@ -120,6 +120,7 @@ class LOCTests {
fun issueSignedByBank() { fun issueSignedByBank() {
val ptx = LOC().generateIssue(LOCstate.beneficiaryPaid, LOCstate.issued, LOCstate.terminated, LOCstate.props, DUMMY_NOTARY).apply { val ptx = LOC().generateIssue(LOCstate.beneficiaryPaid, LOCstate.issued, LOCstate.terminated, LOCstate.props, DUMMY_NOTARY).apply {
signWith(MEGA_CORP_KEY) signWith(MEGA_CORP_KEY)
signWith(DUMMY_NOTARY_KEY)
} }
val stx = ptx.toSignedTransaction() val stx = ptx.toSignedTransaction()
stx.verify() stx.verify()

View File

@ -2,10 +2,7 @@ package com.r3corda.node.messaging
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import com.r3corda.contracts.CommercialPaper import com.r3corda.contracts.CommercialPaper
import com.r3corda.contracts.asset.CASH import com.r3corda.contracts.asset.*
import com.r3corda.contracts.asset.Cash
import com.r3corda.contracts.asset.`issued by`
import com.r3corda.contracts.asset.`owned by`
import com.r3corda.contracts.testing.fillWithSomeTestCash import com.r3corda.contracts.testing.fillWithSomeTestCash
import com.r3corda.core.contracts.* import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
@ -93,9 +90,8 @@ class TwoPartyTradeProtocolTests {
val bobNode = net.createPartyNode(notaryNode.info, BOB.name, BOB_KEY) val bobNode = net.createPartyNode(notaryNode.info, BOB.name, BOB_KEY)
bobNode.services.fillWithSomeTestCash(2000.DOLLARS) bobNode.services.fillWithSomeTestCash(2000.DOLLARS)
val issuer = bobNode.services.storageService.myLegalIdentity.ref(0)
val alicesFakePaper = fillUpForSeller(false, aliceNode.storage.myLegalIdentity.owningKey, 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) insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey, notaryNode.storage.myLegalIdentityKey)
@ -134,7 +130,6 @@ class TwoPartyTradeProtocolTests {
@Test @Test
fun `shutdown and restore`() { fun `shutdown and restore`() {
ledger { ledger {
val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val aliceNode = net.createPartyNode(notaryNode.info, ALICE.name, ALICE_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 bobAddr = bobNode.net.myAddress as InMemoryMessagingNetwork.Handle
val networkMapAddr = notaryNode.info val networkMapAddr = notaryNode.info
val issuer = bobNode.services.storageService.myLegalIdentity.ref(0)
net.runNetwork() // Clear network map registration messages net.runNetwork() // Clear network map registration messages
bobNode.services.fillWithSomeTestCash(2000.DOLLARS) bobNode.services.fillWithSomeTestCash(2000.DOLLARS)
val alicesFakePaper = fillUpForSeller(false, aliceNode.storage.myLegalIdentity.owningKey, 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) insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey)
val buyerSessionID = random63BitValue() val buyerSessionID = random63BitValue()
@ -252,7 +246,7 @@ class TwoPartyTradeProtocolTests {
val aliceNode = makeNodeWithTracking(notaryNode.info, ALICE.name, ALICE_KEY) val aliceNode = makeNodeWithTracking(notaryNode.info, ALICE.name, ALICE_KEY)
val bobNode = makeNodeWithTracking(notaryNode.info, BOB.name, BOB_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. // Insert a prospectus type attachment into the commercial paper transaction.
val stream = ByteArrayOutputStream() val stream = ByteArrayOutputStream()
@ -419,7 +413,7 @@ class TwoPartyTradeProtocolTests {
wtxToSign: List<WireTransaction>, wtxToSign: List<WireTransaction>,
services: ServiceHub, services: ServiceHub,
vararg extraKeys: KeyPair): Map<SecureHash, SignedTransaction> { vararg extraKeys: KeyPair): Map<SecureHash, SignedTransaction> {
val signed: List<SignedTransaction> = signAll(wtxToSign, extraKeys) val signed: List<SignedTransaction> = signAll(wtxToSign, extraKeys.toList())
services.recordTransactions(signed) services.recordTransactions(signed)
val validatedTransactions = services.storageService.validatedTransactions val validatedTransactions = services.storageService.validatedTransactions
if (validatedTransactions is RecordingTransactionStorage) { if (validatedTransactions is RecordingTransactionStorage) {

View File

@ -19,7 +19,7 @@ import com.r3corda.node.services.statemachine.StateMachineManager
import com.r3corda.node.services.wallet.NodeWalletService import com.r3corda.node.services.wallet.NodeWalletService
import java.time.Clock import java.time.Clock
open class MockServices( open class MockServiceHubInternal(
customWallet: WalletService? = null, customWallet: WalletService? = null,
val keyManagement: KeyManagementService? = null, val keyManagement: KeyManagementService? = null,
val net: MessagingServiceInternal? = null, val net: MessagingServiceInternal? = null,

View File

@ -60,7 +60,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
init { init {
val kms = MockKeyManagementService(ALICE_KEY) val kms = MockKeyManagementService(ALICE_KEY)
val mockMessagingService = InMemoryMessagingNetwork(false).InMemoryMessaging(false, InMemoryMessagingNetwork.Handle(0, "None")) 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 override val testReference = this@NodeSchedulerServiceTest
} }
services = mockServices services = mockServices

View File

@ -1,14 +1,14 @@
package com.r3corda.node.services package com.r3corda.node.services
import com.r3corda.contracts.asset.Cash import com.r3corda.contracts.asset.Cash
import com.r3corda.contracts.asset.DUMMY_CASH_ISSUER
import com.r3corda.contracts.asset.cashBalances import com.r3corda.contracts.asset.cashBalances
import com.r3corda.contracts.testing.fillWithSomeTestCash import com.r3corda.contracts.testing.fillWithSomeTestCash
import com.r3corda.core.contracts.* import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.node.ServiceHub import com.r3corda.core.node.services.WalletService
import com.r3corda.core.node.services.testing.MockKeyManagementService
import com.r3corda.core.node.services.testing.MockStorageService import com.r3corda.core.node.services.testing.MockStorageService
import com.r3corda.core.serialization.OpaqueBytes import com.r3corda.core.node.services.testing.MockServices
import com.r3corda.core.testing.* import com.r3corda.core.testing.*
import com.r3corda.core.utilities.BriefLogFormatter import com.r3corda.core.utilities.BriefLogFormatter
import com.r3corda.node.services.wallet.NodeWalletService 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. // TODO: Move this to the cash contract tests once mock services are further split up.
class WalletWithCashTest { class WalletWithCashTest {
val kms = MockKeyManagementService(ALICE_KEY) lateinit var services: MockServices
val wallet: WalletService get() = services.walletService
@Before @Before
fun setUp() { fun setUp() {
BriefLogFormatter.loggingOn(NodeWalletService::class) BriefLogFormatter.loggingOn(NodeWalletService::class)
services = object : MockServices() {
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 @After
@ -35,37 +46,24 @@ class WalletWithCashTest {
BriefLogFormatter.loggingOff(NodeWalletService::class) BriefLogFormatter.loggingOff(NodeWalletService::class)
} }
fun make(): Pair<NodeWalletService, ServiceHub> {
val services = MockServices(keyManagement = kms)
return Pair(services.walletService as NodeWalletService, services)
}
@Test @Test
fun splits() { 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. // 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 val w = wallet.currentWallet
assertEquals(3, w.states.size) assertEquals(3, w.states.size)
val state = w.states[0].state.data as Cash.State val state = w.states[0].state.data as Cash.State
val myIdentity = services.storageService.myLegalIdentity assertEquals(29.01.DOLLARS `issued by` DUMMY_CASH_ISSUER, state.amount)
val myPartyRef = myIdentity.ref(ref) assertEquals(services.key.public, state.owner)
assertEquals(29.01.DOLLARS `issued by` myPartyRef, state.amount)
assertEquals(ALICE_PUBKEY, state.owner)
assertEquals(35.38.DOLLARS `issued by` myPartyRef, (w.states[2].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` myPartyRef, (w.states[1].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 @Test
fun basics() { fun basics() {
val (wallet, services) = make()
// A tx that sends us money. // A tx that sends us money.
val freshKey = services.keyManagementService.freshKey() val freshKey = services.keyManagementService.freshKey()
val usefulTX = TransactionType.General.Builder().apply { val usefulTX = TransactionType.General.Builder().apply {
@ -102,10 +100,7 @@ class WalletWithCashTest {
@Test @Test
fun branchingLinearStatesFails() { fun branchingLinearStatesFails() {
val (wallet, services) = make()
val freshKey = services.keyManagementService.freshKey() val freshKey = services.keyManagementService.freshKey()
val thread = SecureHash.sha256("thread") val thread = SecureHash.sha256("thread")
// Issue a linear state // Issue a linear state
@ -131,8 +126,6 @@ class WalletWithCashTest {
@Test @Test
fun sequencingLinearStatesWorks() { fun sequencingLinearStatesWorks() {
val (wallet, services) = make()
val freshKey = services.keyManagementService.freshKey() val freshKey = services.keyManagementService.freshKey()
val thread = SecureHash.sha256("thread") val thread = SecureHash.sha256("thread")

View File

@ -4,7 +4,7 @@ import co.paralleluniverse.fibers.Fiber
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import com.r3corda.core.messaging.MessagingService import com.r3corda.core.messaging.MessagingService
import com.r3corda.core.protocols.ProtocolLogic 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.Checkpoint
import com.r3corda.node.services.api.CheckpointStorage import com.r3corda.node.services.api.CheckpointStorage
import com.r3corda.node.services.api.MessagingServiceInternal import com.r3corda.node.services.api.MessagingServiceInternal
@ -45,7 +45,7 @@ class StateMachineManagerTests {
assertThat(protocol.lazyTime).isNotNull() assertThat(protocol.lazyTime).isNotNull()
} }
private fun createManager() = StateMachineManager(object : MockServices() { private fun createManager() = StateMachineManager(object : MockServiceHubInternal() {
override val networkService: MessagingServiceInternal get() = network override val networkService: MessagingServiceInternal get() = network
}, emptyList(), checkpointStorage, AffinityExecutor.SAME_THREAD) }, emptyList(), checkpointStorage, AffinityExecutor.SAME_THREAD)

View File

@ -17,13 +17,13 @@ class GraphVisualiser(val dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedg
} }
fun convert(): SingleGraph { fun convert(): SingleGraph {
val tg = dsl.interpreter.toTransactionGroup() val testLedger: TestLedgerDSLInterpreter = dsl.interpreter
val graph = createGraph("Transaction group", css) val graph = createGraph("Transaction group", css)
// Map all the transactions, including the bogus non-verified ones (with no inputs) to graph nodes. // Map all the transactions, including the bogus non-verified ones (with no inputs) to graph nodes.
for ((txIndex, tx) in (tg.transactions + tg.nonVerifiedRoots).withIndex()) { for ((txIndex, tx) in (testLedger.transactionsToVerify + testLedger.transactionsUnverified).withIndex()) {
val txNode = graph.addNode<Node>("tx$txIndex") val txNode = graph.addNode<Node>("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.label = dsl.interpreter.transactionName(tx.id).let { it ?: "TX[${tx.id.prefixChars()}]" }
txNode.styleClass = "tx" txNode.styleClass = "tx"
@ -48,7 +48,7 @@ class GraphVisualiser(val dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedg
} }
} }
// And now all states and transactions were mapped to graph nodes, hook up the input edges. // And now all states and transactions were mapped to graph nodes, hook up the input edges.
for ((txIndex, tx) in tg.transactions.withIndex()) { for ((txIndex, tx) in testLedger.transactionsToVerify.withIndex()) {
for ((inputIndex, ref) in tx.inputs.withIndex()) { for ((inputIndex, ref) in tx.inputs.withIndex()) {
val edge = graph.addEdge<Edge>("tx$txIndex-in$inputIndex", ref.toString(), "tx$txIndex", true) val edge = graph.addEdge<Edge>("tx$txIndex-in$inputIndex", ref.toString(), "tx$txIndex", true)
edge.weight = 1.2 edge.weight = 1.2

View File

@ -6,9 +6,6 @@ import com.r3corda.core.testing.utilities.TestTimestamp
import com.r3corda.core.testing.utilities.assertExitOrKill import com.r3corda.core.testing.utilities.assertExitOrKill
import com.r3corda.core.testing.utilities.spawn import com.r3corda.core.testing.utilities.spawn
import org.junit.Test import org.junit.Test
import java.nio.file.Paths
import java.text.SimpleDateFormat
import java.util.*
import kotlin.test.assertEquals import kotlin.test.assertEquals
class TraderDemoTest { class TraderDemoTest {

View File

@ -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 // 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 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. // the side that sticks around waiting for the seller.
var cashIssuer: Party? = null
val networkMapId = if (role == Role.BUYER) { val networkMapId = if (role == Role.BUYER) {
advertisedServices = setOf(NetworkMapService.Type, SimpleNotaryService.Type) advertisedServices = setOf(NetworkMapService.Type, SimpleNotaryService.Type)
null null
@ -148,7 +147,6 @@ fun runTraderDemo(args: Array<String>): Int {
val path = Paths.get(baseDirectory, Role.BUYER.name.toLowerCase(), "identity-public") val path = Paths.get(baseDirectory, Role.BUYER.name.toLowerCase(), "identity-public")
val party = Files.readAllBytes(path).deserialize<Party>() val party = Files.readAllBytes(path).deserialize<Party>()
advertisedServices = emptySet() advertisedServices = emptySet()
cashIssuer = party
NodeInfo(ArtemisMessagingService.makeRecipient(theirNetAddr), party, setOf(NetworkMapService.Type)) 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() 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 // 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. // will contact the buyer and actually make something happen.
val amount = 1000.DOLLARS val amount = 1000.DOLLARS
if (role == Role.BUYER) { if (role == Role.BUYER) {
runBuyer(node, amount) runBuyer(node, amount)
} else { } 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 return 0
@ -213,7 +208,9 @@ private fun runBuyer(node: Node, amount: Amount<Currency>) {
// Self issue some cash. // Self issue some cash.
// //
// TODO: At some point this demo should be extended to have a central bank node. // 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 // 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 // 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 progressTracker.currentStep = STARTING_BUY
send(otherSide, 0, sessionID) send(otherSide, 0, sessionID)
val notary = serviceHub.networkMapCache.notaryNodes[0] val notary: NodeInfo = serviceHub.networkMapCache.notaryNodes[0]
val buyer = TwoPartyTradeProtocol.Buyer( val buyer = TwoPartyTradeProtocol.Buyer(
otherSide, otherSide,
notary.identity, notary.identity,
@ -323,7 +320,7 @@ private class TraderDemoProtocolSeller(val otherSide: Party,
progressTracker.currentStep = SELF_ISSUING progressTracker.currentStep = SELF_ISSUING
val notary = serviceHub.networkMapCache.notaryNodes[0] val notary: NodeInfo = serviceHub.networkMapCache.notaryNodes[0]
val cpOwnerKey = serviceHub.keyManagementService.freshKey() val cpOwnerKey = serviceHub.keyManagementService.freshKey()
val commercialPaper = selfIssueSomeCommercialPaper(cpOwnerKey.public, notary) val commercialPaper = selfIssueSomeCommercialPaper(cpOwnerKey.public, notary)