mirror of
https://github.com/corda/corda.git
synced 2024-12-22 22:32:26 +00:00
Merged in mike-resolvetx-unit-tests (pull request #279)
Add unit tests for the tx resolution protocol
This commit is contained in:
commit
2bc757a8a7
@ -18,6 +18,9 @@ repositories {
|
|||||||
maven {
|
maven {
|
||||||
url 'http://oss.sonatype.org/content/repositories/snapshots'
|
url 'http://oss.sonatype.org/content/repositories/snapshots'
|
||||||
}
|
}
|
||||||
|
maven {
|
||||||
|
url 'https://dl.bintray.com/kotlin/exposed'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@ -35,6 +38,9 @@ dependencies {
|
|||||||
// Guava: Google test library (collections test suite)
|
// Guava: Google test library (collections test suite)
|
||||||
testCompile "com.google.guava:guava-testlib:19.0"
|
testCompile "com.google.guava:guava-testlib:19.0"
|
||||||
|
|
||||||
|
// Bring in the MockNode infrastructure for writing protocol unit tests.
|
||||||
|
testCompile project(":node")
|
||||||
|
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.r3corda.core
|
package com.r3corda.core
|
||||||
|
|
||||||
|
import com.google.common.base.Throwables
|
||||||
import com.google.common.io.ByteStreams
|
import com.google.common.io.ByteStreams
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.google.common.util.concurrent.MoreExecutors
|
import com.google.common.util.concurrent.MoreExecutors
|
||||||
@ -215,3 +216,5 @@ fun extractZipFile(zipPath: Path, toPath: Path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Generic csv printing utility for clases.
|
// TODO: Generic csv printing utility for clases.
|
||||||
|
|
||||||
|
val Throwable.rootCause: Throwable get() = Throwables.getRootCause(this)
|
@ -46,8 +46,21 @@ class DummyContract : Contract {
|
|||||||
// The "empty contract"
|
// The "empty contract"
|
||||||
override val legalContractReference: SecureHash = SecureHash.sha256("")
|
override val legalContractReference: SecureHash = SecureHash.sha256("")
|
||||||
|
|
||||||
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
|
companion object {
|
||||||
val state = SingleOwnerState(magicNumber, owner.party.owningKey)
|
@JvmStatic
|
||||||
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Create(), owner.party.owningKey))
|
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
|
||||||
|
val state = SingleOwnerState(magicNumber, owner.party.owningKey)
|
||||||
|
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Create(), owner.party.owningKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun move(prior: StateAndRef<DummyContract.SingleOwnerState>, newOwner: PublicKey): TransactionBuilder {
|
||||||
|
val priorState = prior.state.data
|
||||||
|
val (cmd, state) = priorState.withNewOwner(newOwner)
|
||||||
|
return TransactionType.General.Builder(notary = prior.state.notary).withItems(
|
||||||
|
/* INPUT */ prior,
|
||||||
|
/* COMMAND */ Command(cmd, priorState.owner),
|
||||||
|
/* OUTPUT */ state
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -81,6 +81,7 @@ open class TransactionBuilder(
|
|||||||
is TransactionState<*> -> addOutputState(t)
|
is TransactionState<*> -> addOutputState(t)
|
||||||
is ContractState -> addOutputState(t)
|
is ContractState -> addOutputState(t)
|
||||||
is Command -> addCommand(t)
|
is Command -> addCommand(t)
|
||||||
|
is CommandData -> throw IllegalArgumentException("You passed an instance of CommandData, but that lacks the pubkey. You need to wrap it in a Command object first.")
|
||||||
else -> throw IllegalArgumentException("Wrong argument type: ${t.javaClass}")
|
else -> throw IllegalArgumentException("Wrong argument type: ${t.javaClass}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,8 +121,9 @@ open class TransactionBuilder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Adds the signature directly to the transaction, without checking it for validity. */
|
/** Adds the signature directly to the transaction, without checking it for validity. */
|
||||||
fun addSignatureUnchecked(sig: DigitalSignature.WithKey) {
|
fun addSignatureUnchecked(sig: DigitalSignature.WithKey): TransactionBuilder {
|
||||||
currentSigs.add(sig)
|
currentSigs.add(sig)
|
||||||
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments),
|
fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.r3corda.core.serialization
|
package com.r3corda.core.serialization
|
||||||
|
|
||||||
import com.google.common.io.BaseEncoding
|
import com.google.common.io.BaseEncoding
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,6 +28,9 @@ open class OpaqueBytes(val bits: ByteArray) {
|
|||||||
override fun toString() = "[" + BaseEncoding.base16().encode(bits) + "]"
|
override fun toString() = "[" + BaseEncoding.base16().encode(bits) + "]"
|
||||||
|
|
||||||
val size: Int get() = bits.size
|
val size: Int get() = bits.size
|
||||||
|
|
||||||
|
/** Returns a [ByteArrayInputStream] of the bytes */
|
||||||
|
fun open() = ByteArrayInputStream(bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ByteArray.opaque(): OpaqueBytes = OpaqueBytes(this)
|
fun ByteArray.opaque(): OpaqueBytes = OpaqueBytes(this)
|
||||||
|
@ -41,6 +41,10 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
|
|||||||
private var stx: SignedTransaction? = null
|
private var stx: SignedTransaction? = null
|
||||||
private var wtx: WireTransaction? = null
|
private var wtx: WireTransaction? = null
|
||||||
|
|
||||||
|
// TODO: Figure out a more appropriate DOS limit here, 5000 is simply a very bad guess.
|
||||||
|
/** The maximum number of transactions this protocol will try to download before bailing out. */
|
||||||
|
var transactionCountLimit = 5000
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve the full history of a transaction and verify it with its dependencies.
|
* Resolve the full history of a transaction and verify it with its dependencies.
|
||||||
*/
|
*/
|
||||||
@ -65,11 +69,11 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
|
|||||||
// redundantly next time we attempt verification.
|
// redundantly next time we attempt verification.
|
||||||
val result = ArrayList<LedgerTransaction>()
|
val result = ArrayList<LedgerTransaction>()
|
||||||
|
|
||||||
for (tx in newTxns) {
|
for (stx in newTxns) {
|
||||||
// Resolve to a LedgerTransaction and then run all contracts.
|
// Resolve to a LedgerTransaction and then run all contracts.
|
||||||
val ltx = tx.toLedgerTransaction(serviceHub)
|
val ltx = stx.toLedgerTransaction(serviceHub)
|
||||||
ltx.verify()
|
ltx.verify()
|
||||||
serviceHub.recordTransactions(tx)
|
serviceHub.recordTransactions(stx)
|
||||||
result += ltx
|
result += ltx
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,6 +118,8 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
|
|||||||
nextRequests.addAll(depsToCheck)
|
nextRequests.addAll(depsToCheck)
|
||||||
val resultQ = LinkedHashMap<SecureHash, SignedTransaction>()
|
val resultQ = LinkedHashMap<SecureHash, SignedTransaction>()
|
||||||
|
|
||||||
|
val limit = transactionCountLimit
|
||||||
|
check(limit > 0) { "$limit is not a valid count limit" }
|
||||||
var limitCounter = 0
|
var limitCounter = 0
|
||||||
while (nextRequests.isNotEmpty()) {
|
while (nextRequests.isNotEmpty()) {
|
||||||
// Don't re-download the same tx when we haven't verified it yet but it's referenced multiple times in the
|
// Don't re-download the same tx when we haven't verified it yet but it's referenced multiple times in the
|
||||||
@ -136,10 +142,8 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
|
|||||||
val inputHashes = downloads.flatMap { it.tx.inputs }.map { it.txhash }
|
val inputHashes = downloads.flatMap { it.tx.inputs }.map { it.txhash }
|
||||||
nextRequests.addAll(inputHashes)
|
nextRequests.addAll(inputHashes)
|
||||||
|
|
||||||
// TODO: Figure out a more appropriate DOS limit here, 5000 is simply a very bad guess.
|
|
||||||
// TODO: Unit test the DoS limit.
|
|
||||||
limitCounter = limitCounter checkedAdd nextRequests.size
|
limitCounter = limitCounter checkedAdd nextRequests.size
|
||||||
if (limitCounter > 5000)
|
if (limitCounter > limit)
|
||||||
throw ExcessivelyLargeTransactionGraph()
|
throw ExcessivelyLargeTransactionGraph()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,6 +160,7 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
|
|||||||
val missingAttachments = downloads.flatMap { wtx ->
|
val missingAttachments = downloads.flatMap { wtx ->
|
||||||
wtx.attachments.filter { serviceHub.storageService.attachments.openAttachment(it) == null }
|
wtx.attachments.filter { serviceHub.storageService.attachments.openAttachment(it) == null }
|
||||||
}
|
}
|
||||||
subProtocol(FetchAttachmentsProtocol(missingAttachments.toSet(), otherSide))
|
if (missingAttachments.isNotEmpty())
|
||||||
|
subProtocol(FetchAttachmentsProtocol(missingAttachments.toSet(), otherSide))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,128 @@
|
|||||||
|
package com.r3corda.core.protocols
|
||||||
|
|
||||||
|
import com.r3corda.core.contracts.DummyContract
|
||||||
|
import com.r3corda.core.contracts.SignedTransaction
|
||||||
|
import com.r3corda.core.crypto.NullSignature
|
||||||
|
import com.r3corda.core.crypto.Party
|
||||||
|
import com.r3corda.core.crypto.SecureHash
|
||||||
|
import com.r3corda.core.serialization.opaque
|
||||||
|
import com.r3corda.core.testing.*
|
||||||
|
import com.r3corda.node.internal.testing.MockNetwork
|
||||||
|
import com.r3corda.protocols.ResolveTransactionsProtocol
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import java.security.SignatureException
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
import kotlin.test.assertNull
|
||||||
|
|
||||||
|
class ResolveTransactionsProtocolTest {
|
||||||
|
lateinit var net: MockNetwork
|
||||||
|
lateinit var a: MockNetwork.MockNode
|
||||||
|
lateinit var b: MockNetwork.MockNode
|
||||||
|
lateinit var notary: Party
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
net = MockNetwork()
|
||||||
|
val nodes = net.createSomeNodes()
|
||||||
|
a = nodes.partyNodes[0]
|
||||||
|
b = nodes.partyNodes[1]
|
||||||
|
notary = nodes.notaryNode.info.identity
|
||||||
|
net.runNetwork()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
net.stopNodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `resolve from two hashes`() {
|
||||||
|
val (stx1, stx2) = makeTransactions()
|
||||||
|
val p = ResolveTransactionsProtocol(setOf(stx2.id), a.info.identity)
|
||||||
|
val future = b.services.startProtocol("resolve", p)
|
||||||
|
net.runNetwork()
|
||||||
|
val results = future.get()
|
||||||
|
assertEquals(listOf(stx1.id, stx2.id), results.map { it.id })
|
||||||
|
assertEquals(stx1, b.storage.validatedTransactions.getTransaction(stx1.id))
|
||||||
|
assertEquals(stx2, b.storage.validatedTransactions.getTransaction(stx2.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `dependency with an error`() {
|
||||||
|
val stx = makeTransactions(signFirstTX = false).second
|
||||||
|
val p = ResolveTransactionsProtocol(setOf(stx.id), a.info.identity)
|
||||||
|
val future = b.services.startProtocol("resolve", p)
|
||||||
|
net.runNetwork()
|
||||||
|
assertFailsWith(SignatureException::class) {
|
||||||
|
rootCauseExceptions { future.get() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `resolve from a signed transaction`() {
|
||||||
|
val (stx1, stx2) = makeTransactions()
|
||||||
|
val p = ResolveTransactionsProtocol(stx2, a.info.identity)
|
||||||
|
val future = b.services.startProtocol("resolve", p)
|
||||||
|
net.runNetwork()
|
||||||
|
future.get()
|
||||||
|
assertEquals(stx1, b.storage.validatedTransactions.getTransaction(stx1.id))
|
||||||
|
// But stx2 wasn't inserted, just stx1.
|
||||||
|
assertNull(b.storage.validatedTransactions.getTransaction(stx2.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `denial of service check`() {
|
||||||
|
// Chain lots of txns together.
|
||||||
|
val stx2 = makeTransactions().second
|
||||||
|
val count = 50
|
||||||
|
var cursor = stx2
|
||||||
|
repeat(count) {
|
||||||
|
val stx = DummyContract.move(cursor.tx.outRef(0), MINI_CORP_PUBKEY)
|
||||||
|
.addSignatureUnchecked(NullSignature)
|
||||||
|
.toSignedTransaction(false)
|
||||||
|
a.services.recordTransactions(stx)
|
||||||
|
cursor = stx
|
||||||
|
}
|
||||||
|
val p = ResolveTransactionsProtocol(setOf(cursor.id), a.info.identity)
|
||||||
|
p.transactionCountLimit = 40
|
||||||
|
val future = b.services.startProtocol("resolve", p)
|
||||||
|
net.runNetwork()
|
||||||
|
assertFailsWith<ResolveTransactionsProtocol.ExcessivelyLargeTransactionGraph> {
|
||||||
|
rootCauseExceptions { future.get() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun attachment() {
|
||||||
|
val id = a.services.storageService.attachments.importAttachment("Some test file".toByteArray().opaque().open())
|
||||||
|
val stx2 = makeTransactions(withAttachment = id).second
|
||||||
|
val p = ResolveTransactionsProtocol(stx2, a.info.identity)
|
||||||
|
val future = b.services.startProtocol("resolve", p)
|
||||||
|
net.runNetwork()
|
||||||
|
future.get()
|
||||||
|
assertNotNull(b.services.storageService.attachments.openAttachment(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeTransactions(signFirstTX: Boolean = true, withAttachment: SecureHash? = null): Pair<SignedTransaction, SignedTransaction> {
|
||||||
|
// Make a chain of custody of dummy states and insert into node A.
|
||||||
|
val dummy1: SignedTransaction = DummyContract.generateInitial(MEGA_CORP.ref(1), 0, notary).let {
|
||||||
|
if (withAttachment != null)
|
||||||
|
it.addAttachment(withAttachment)
|
||||||
|
if (signFirstTX)
|
||||||
|
it.signWith(MEGA_CORP_KEY)
|
||||||
|
it.signWith(DUMMY_NOTARY_KEY)
|
||||||
|
it.toSignedTransaction(false)
|
||||||
|
}
|
||||||
|
val dummy2: SignedTransaction = DummyContract.move(dummy1.tx.outRef(0), MINI_CORP_PUBKEY).let {
|
||||||
|
it.signWith(MEGA_CORP_KEY)
|
||||||
|
it.signWith(DUMMY_NOTARY_KEY)
|
||||||
|
it.toSignedTransaction()
|
||||||
|
}
|
||||||
|
a.services.recordTransactions(dummy1, dummy2)
|
||||||
|
return Pair(dummy1, dummy2)
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +0,0 @@
|
|||||||
package com.r3corda.protocols
|
|
||||||
|
|
||||||
class ResolveTransactionsProtocolTest {
|
|
||||||
}
|
|
@ -559,4 +559,106 @@ the sub-protocol to have the tracker it will use be passed in as a parameter. Th
|
|||||||
and linked ahead of time.
|
and linked ahead of time.
|
||||||
|
|
||||||
In future, the progress tracking framework will become a vital part of how exceptions, errors, and other faults are
|
In future, the progress tracking framework will become a vital part of how exceptions, errors, and other faults are
|
||||||
surfaced to human operators for investigation and resolution.
|
surfaced to human operators for investigation and resolution.
|
||||||
|
|
||||||
|
Unit testing
|
||||||
|
------------
|
||||||
|
|
||||||
|
A protocol can be a fairly complex thing that interacts with many services and other parties over the network. That
|
||||||
|
means unit testing one requires some infrastructure to provide lightweight mock implementations. The MockNetwork
|
||||||
|
provides this testing infrastructure layer; you can find this class in the node module
|
||||||
|
|
||||||
|
A good example to examine for learning how to unit test protocols is the ``ResolveTransactionsProtocol`` tests. This
|
||||||
|
protocol takes care of downloading and verifying transaction graphs, with all the needed dependencies. We start
|
||||||
|
with this basic skeleton:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
class ResolveTransactionsProtocolTest {
|
||||||
|
lateinit var net: MockNetwork
|
||||||
|
lateinit var a: MockNetwork.MockNode
|
||||||
|
lateinit var b: MockNetwork.MockNode
|
||||||
|
lateinit var notary: Party
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
net = MockNetwork()
|
||||||
|
val nodes = net.createSomeNodes()
|
||||||
|
a = nodes.partyNodes[0]
|
||||||
|
b = nodes.partyNodes[1]
|
||||||
|
notary = nodes.notaryNode.info.identity
|
||||||
|
net.runNetwork()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
net.stopNodes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
We create a mock network in our ``@Before`` setup method and create a couple of nodes. We also record the identity
|
||||||
|
of the notary in our test network, which will come in handy later. We also tidy up when we're done.
|
||||||
|
|
||||||
|
Next, we write a test case:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun resolveFromTwoHashes() {
|
||||||
|
val (stx1, stx2) = makeTransactions()
|
||||||
|
val p = ResolveTransactionsProtocol(setOf(stx2.id), a.info.identity)
|
||||||
|
val future = b.services.startProtocol("resolve", p)
|
||||||
|
net.runNetwork()
|
||||||
|
val results = future.get()
|
||||||
|
assertEquals(listOf(stx1.id, stx2.id), results.map { it.id })
|
||||||
|
assertEquals(stx1, b.storage.validatedTransactions.getTransaction(stx1.id))
|
||||||
|
assertEquals(stx2, b.storage.validatedTransactions.getTransaction(stx2.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
We'll take a look at the ``makeTransactions`` function in a moment. For now, it's enough to know that it returns two
|
||||||
|
``SignedTransaction`` objects, the second of which spends the first. Both transactions are known by node A
|
||||||
|
but not node B.
|
||||||
|
|
||||||
|
The test logic is simple enough: we create the protocol, giving it node A's identity as the target to talk to.
|
||||||
|
Then we start it on node B and use the ``net.runNetwork()`` method to bounce messages around until things have
|
||||||
|
settled (i.e. there are no more messages waiting to be delivered). All this is done using an in memory message
|
||||||
|
routing implementation that is fast to initialise and use. Finally, we obtain the result of the protocol and do
|
||||||
|
some tests on it. We also check the contents of node B's database to see that the protocol had the intended effect
|
||||||
|
on the node's persistent state.
|
||||||
|
|
||||||
|
Here's what ``makeTransactions`` looks like:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
private fun makeTransactions(): Pair<SignedTransaction, SignedTransaction> {
|
||||||
|
// Make a chain of custody of dummy states and insert into node A.
|
||||||
|
val dummy1: SignedTransaction = DummyContract.generateInitial(MEGA_CORP.ref(1), 0, notary).let {
|
||||||
|
it.signWith(MEGA_CORP_KEY)
|
||||||
|
it.signWith(DUMMY_NOTARY_KEY)
|
||||||
|
it.toSignedTransaction(false)
|
||||||
|
}
|
||||||
|
val dummy2: SignedTransaction = DummyContract.move(dummy1.tx.outRef(0), MINI_CORP_PUBKEY).let {
|
||||||
|
it.signWith(MEGA_CORP_KEY)
|
||||||
|
it.signWith(DUMMY_NOTARY_KEY)
|
||||||
|
it.toSignedTransaction()
|
||||||
|
}
|
||||||
|
a.services.recordTransactions(dummy1, dummy2)
|
||||||
|
return Pair(dummy1, dummy2)
|
||||||
|
}
|
||||||
|
|
||||||
|
We're using the ``DummyContract``, a simple test smart contract which stores a single number in its states, along
|
||||||
|
with ownership and issuer information. You can issue such states, exit them and re-assign ownership (move them).
|
||||||
|
It doesn't do anything else. This code simply creates a transaction that issues a dummy state (the issuer is
|
||||||
|
``MEGA_CORP``, a pre-defined unit test identity), signs it with the test notary and MegaCorp keys and then
|
||||||
|
converts the builder to the final ``SignedTransaction``. It then does so again, but this time instead of issuing
|
||||||
|
it re-assigns ownership instead. The chain of two transactions is finally committed to node A by sending them
|
||||||
|
directly to the ``a.services.recordTransaction`` method (note that this method doesn't check the transactions are
|
||||||
|
valid).
|
||||||
|
|
||||||
|
And that's it: you can explore the documentation for the `MockNode API <api/com.r3corda.node.internal.testing/-mock-network/index.html>`_ here.
|
||||||
|
@ -11,6 +11,7 @@ import com.r3corda.core.node.services.ServiceType
|
|||||||
import com.r3corda.core.node.services.WalletService
|
import com.r3corda.core.node.services.WalletService
|
||||||
import com.r3corda.core.node.services.testing.MockIdentityService
|
import com.r3corda.core.node.services.testing.MockIdentityService
|
||||||
import com.r3corda.core.node.services.testing.makeTestDataSourceProperties
|
import com.r3corda.core.node.services.testing.makeTestDataSourceProperties
|
||||||
|
import com.r3corda.core.testing.DUMMY_NOTARY_KEY
|
||||||
import com.r3corda.core.testing.InMemoryWalletService
|
import com.r3corda.core.testing.InMemoryWalletService
|
||||||
import com.r3corda.core.utilities.loggerFor
|
import com.r3corda.core.utilities.loggerFor
|
||||||
import com.r3corda.node.internal.AbstractNode
|
import com.r3corda.node.internal.AbstractNode
|
||||||
@ -160,6 +161,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Move this to using createSomeNodes which doesn't conflate network services with network users.
|
||||||
/**
|
/**
|
||||||
* Sets up a two node network, in which the first node runs network map and notary services and the other
|
* Sets up a two node network, in which the first node runs network map and notary services and the other
|
||||||
* doesn't.
|
* doesn't.
|
||||||
@ -172,8 +174,35 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createNotaryNode(legalName: String? = null, keyPair: KeyPair? = null) = createNode(null, -1, defaultFactory, true, legalName, keyPair, false, NetworkMapService.Type, SimpleNotaryService.Type)
|
/**
|
||||||
fun createPartyNode(networkMapAddr: NodeInfo, legalName: String? = null, keyPair: KeyPair? = null) = createNode(networkMapAddr, -1, defaultFactory, true, legalName, keyPair)
|
* A bundle that separates the generic user nodes and service-providing nodes. A real network might not be so
|
||||||
|
* clearly separated, but this is convenient for testing.
|
||||||
|
*/
|
||||||
|
data class BasketOfNodes(val partyNodes: List<MockNode>, val notaryNode: MockNode, val mapNode: MockNode)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up a network with the requested number of nodes (defaulting to two), with one or more service nodes that
|
||||||
|
* run a notary, network map, any oracles etc. Can't be combined with [createTwoNodes].
|
||||||
|
*/
|
||||||
|
fun createSomeNodes(numPartyNodes: Int = 2, nodeFactory: Factory = defaultFactory, notaryKeyPair: KeyPair? = DUMMY_NOTARY_KEY): BasketOfNodes {
|
||||||
|
require(nodes.isEmpty())
|
||||||
|
val mapNode = createNode(null, nodeFactory = nodeFactory, advertisedServices = NetworkMapService.Type)
|
||||||
|
val notaryNode = createNode(mapNode.info, nodeFactory = nodeFactory, keyPair = notaryKeyPair,
|
||||||
|
advertisedServices = SimpleNotaryService.Type)
|
||||||
|
val nodes = ArrayList<MockNode>()
|
||||||
|
repeat(numPartyNodes) {
|
||||||
|
nodes += createPartyNode(mapNode.info)
|
||||||
|
}
|
||||||
|
return BasketOfNodes(nodes, notaryNode, mapNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createNotaryNode(legalName: String? = null, keyPair: KeyPair? = null): MockNode {
|
||||||
|
return createNode(null, -1, defaultFactory, true, legalName, keyPair, false, NetworkMapService.Type, SimpleNotaryService.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createPartyNode(networkMapAddr: NodeInfo, legalName: String? = null, keyPair: KeyPair? = null): MockNode {
|
||||||
|
return createNode(networkMapAddr, -1, defaultFactory, true, legalName, keyPair)
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("unused") // This is used from the network visualiser tool.
|
@Suppress("unused") // This is used from the network visualiser tool.
|
||||||
fun addressToNode(address: SingleMessageRecipient): MockNode = nodes.single { it.net.myAddress == address }
|
fun addressToNode(address: SingleMessageRecipient): MockNode = nodes.single { it.net.myAddress == address }
|
||||||
|
@ -96,7 +96,7 @@ class NotaryChangeTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun issueState(node: AbstractNode): StateAndRef<*> {
|
fun issueState(node: AbstractNode): StateAndRef<*> {
|
||||||
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), DUMMY_NOTARY)
|
val tx = DummyContract.generateInitial(node.info.identity.ref(0), Random().nextInt(), DUMMY_NOTARY)
|
||||||
tx.signWith(node.storage.myLegalIdentityKey)
|
tx.signWith(node.storage.myLegalIdentityKey)
|
||||||
tx.signWith(DUMMY_NOTARY_KEY)
|
tx.signWith(DUMMY_NOTARY_KEY)
|
||||||
val stx = tx.toSignedTransaction()
|
val stx = tx.toSignedTransaction()
|
||||||
@ -119,7 +119,7 @@ fun issueMultiPartyState(nodeA: AbstractNode, nodeB: AbstractNode): StateAndRef<
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun issueInvalidState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateAndRef<*> {
|
fun issueInvalidState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateAndRef<*> {
|
||||||
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary)
|
val tx = DummyContract.generateInitial(node.info.identity.ref(0), Random().nextInt(), notary)
|
||||||
tx.setTime(Instant.now(), 30.seconds)
|
tx.setTime(Instant.now(), 30.seconds)
|
||||||
tx.signWith(node.storage.myLegalIdentityKey)
|
tx.signWith(node.storage.myLegalIdentityKey)
|
||||||
val stx = tx.toSignedTransaction(false)
|
val stx = tx.toSignedTransaction(false)
|
||||||
|
Loading…
Reference in New Issue
Block a user