Merge branch 'master' into m4ksio_gradle_no_o_fix

# Conflicts:
#	gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy
This commit is contained in:
Maksymilian Pawlak 2017-10-19 16:02:53 +01:00
commit 3f04b45010
266 changed files with 5346 additions and 3316 deletions

View File

@ -1576,24 +1576,8 @@ public interface net.corda.core.node.services.KeyManagementService
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature sign(net.corda.core.crypto.SignableData, java.security.PublicKey) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature sign(net.corda.core.crypto.SignableData, java.security.PublicKey)
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.DigitalSignature$WithKey sign(byte[], java.security.PublicKey) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.DigitalSignature$WithKey sign(byte[], java.security.PublicKey)
## ##
public interface net.corda.core.node.services.NetworkMapCache public interface net.corda.core.node.services.NetworkMapCache extends net.corda.core.node.services.NetworkMapCacheBase
public abstract void clearNetworkMapCache()
@org.jetbrains.annotations.NotNull public abstract List getAllNodes()
@org.jetbrains.annotations.NotNull public abstract rx.Observable getChanged()
@org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByAddress(net.corda.core.utilities.NetworkHostAndPort)
@org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByLegalIdentity(net.corda.core.identity.AbstractParty) @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByLegalIdentity(net.corda.core.identity.AbstractParty)
@org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByLegalName(net.corda.core.identity.CordaX500Name)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture getNodeReady()
@org.jetbrains.annotations.NotNull public abstract List getNodesByLegalIdentityKey(java.security.PublicKey)
@org.jetbrains.annotations.NotNull public abstract List getNodesByLegalName(net.corda.core.identity.CordaX500Name)
@org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party getNotary(net.corda.core.identity.CordaX500Name)
@org.jetbrains.annotations.NotNull public abstract List getNotaryIdentities()
@org.jetbrains.annotations.Nullable public abstract net.corda.core.node.services.PartyInfo getPartyInfo(net.corda.core.identity.Party)
@org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party getPeerByLegalName(net.corda.core.identity.CordaX500Name)
@org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.PartyAndCertificate getPeerCertificateByLegalName(net.corda.core.identity.CordaX500Name)
public abstract boolean isNotary(net.corda.core.identity.Party)
public abstract boolean isValidatingNotary(net.corda.core.identity.Party)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track()
## ##
public abstract static class net.corda.core.node.services.NetworkMapCache$MapChange extends java.lang.Object public abstract static class net.corda.core.node.services.NetworkMapCache$MapChange extends java.lang.Object
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NodeInfo getNode() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NodeInfo getNode()
@ -1627,6 +1611,24 @@ public static final class net.corda.core.node.services.NetworkMapCache$MapChange
public int hashCode() public int hashCode()
public String toString() public String toString()
## ##
public interface net.corda.core.node.services.NetworkMapCacheBase
public abstract void clearNetworkMapCache()
@org.jetbrains.annotations.NotNull public abstract List getAllNodes()
@org.jetbrains.annotations.NotNull public abstract rx.Observable getChanged()
@org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByAddress(net.corda.core.utilities.NetworkHostAndPort)
@org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByLegalName(net.corda.core.identity.CordaX500Name)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture getNodeReady()
@org.jetbrains.annotations.NotNull public abstract List getNodesByLegalIdentityKey(java.security.PublicKey)
@org.jetbrains.annotations.NotNull public abstract List getNodesByLegalName(net.corda.core.identity.CordaX500Name)
@org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party getNotary(net.corda.core.identity.CordaX500Name)
@org.jetbrains.annotations.NotNull public abstract List getNotaryIdentities()
@org.jetbrains.annotations.Nullable public abstract net.corda.core.node.services.PartyInfo getPartyInfo(net.corda.core.identity.Party)
@org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party getPeerByLegalName(net.corda.core.identity.CordaX500Name)
@org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.PartyAndCertificate getPeerCertificateByLegalName(net.corda.core.identity.CordaX500Name)
public abstract boolean isNotary(net.corda.core.identity.Party)
public abstract boolean isValidatingNotary(net.corda.core.identity.Party)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track()
##
public abstract class net.corda.core.node.services.NotaryService extends net.corda.core.serialization.SingletonSerializeAsToken public abstract class net.corda.core.node.services.NotaryService extends net.corda.core.serialization.SingletonSerializeAsToken
public <init>() public <init>()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.FlowLogic createServiceFlow(net.corda.core.flows.FlowSession) @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.FlowLogic createServiceFlow(net.corda.core.flows.FlowSession)
@ -2435,6 +2437,17 @@ public final class net.corda.core.serialization.MissingAttachmentsException exte
public <init>(List) public <init>(List)
@org.jetbrains.annotations.NotNull public final List getIds() @org.jetbrains.annotations.NotNull public final List getIds()
## ##
public final class net.corda.core.serialization.ObjectWithCompatibleContext extends java.lang.Object
public <init>(Object, net.corda.core.serialization.SerializationContext)
@org.jetbrains.annotations.NotNull public final Object component1()
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext component2()
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.ObjectWithCompatibleContext copy(Object, net.corda.core.serialization.SerializationContext)
public boolean equals(Object)
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getContext()
@org.jetbrains.annotations.NotNull public final Object getObj()
public int hashCode()
public String toString()
##
public final class net.corda.core.serialization.SerializationAPIKt extends java.lang.Object public final class net.corda.core.serialization.SerializationAPIKt extends java.lang.Object
@org.jetbrains.annotations.NotNull public static final net.corda.core.serialization.SerializedBytes serialize(Object, net.corda.core.serialization.SerializationFactory, net.corda.core.serialization.SerializationContext) @org.jetbrains.annotations.NotNull public static final net.corda.core.serialization.SerializedBytes serialize(Object, net.corda.core.serialization.SerializationFactory, net.corda.core.serialization.SerializationContext)
## ##
@ -2476,6 +2489,7 @@ public abstract class net.corda.core.serialization.SerializationFactory extends
public <init>() public <init>()
public final Object asCurrent(kotlin.jvm.functions.Function1) public final Object asCurrent(kotlin.jvm.functions.Function1)
@org.jetbrains.annotations.NotNull public abstract Object deserialize(net.corda.core.utilities.ByteSequence, Class, net.corda.core.serialization.SerializationContext) @org.jetbrains.annotations.NotNull public abstract Object deserialize(net.corda.core.utilities.ByteSequence, Class, net.corda.core.serialization.SerializationContext)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.ObjectWithCompatibleContext deserializeWithCompatibleContext(net.corda.core.utilities.ByteSequence, Class, net.corda.core.serialization.SerializationContext)
@org.jetbrains.annotations.Nullable public final net.corda.core.serialization.SerializationContext getCurrentContext() @org.jetbrains.annotations.Nullable public final net.corda.core.serialization.SerializationContext getCurrentContext()
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getDefaultContext() @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getDefaultContext()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializedBytes serialize(Object, net.corda.core.serialization.SerializationContext) @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializedBytes serialize(Object, net.corda.core.serialization.SerializationContext)
@ -2923,7 +2937,7 @@ public static final class net.corda.core.utilities.NonEmptySet$iterator$1 extend
## ##
public class net.corda.core.utilities.OpaqueBytes extends net.corda.core.utilities.ByteSequence public class net.corda.core.utilities.OpaqueBytes extends net.corda.core.utilities.ByteSequence
public <init>(byte[]) public <init>(byte[])
@org.jetbrains.annotations.NotNull public byte[] getBytes() @org.jetbrains.annotations.NotNull public final byte[] getBytes()
public int getOffset() public int getOffset()
public int getSize() public int getSize()
public static final net.corda.core.utilities.OpaqueBytes$Companion Companion public static final net.corda.core.utilities.OpaqueBytes$Companion Companion

View File

@ -10,13 +10,14 @@ if [ ! -f $apiCurrent ]; then
exit -1 exit -1
fi fi
diffContents=`diff -u $apiCurrent $APIHOME/../build/api/api-corda-*.txt` # Remove the two header lines from the diff output.
echo "Diff contents:" diffContents=`diff -u $apiCurrent $APIHOME/../build/api/api-corda-*.txt | tail --lines=+3`
echo "Diff contents:"
echo "$diffContents" echo "$diffContents"
echo echo
# A removed line means that an API was either deleted or modified. # A removed line means that an API was either deleted or modified.
removals=$(echo "$diffContents" | grep "^-\s") removals=$(echo "$diffContents" | grep "^-")
removalCount=`grep -v "^$" <<EOF | wc -l removalCount=`grep -v "^$" <<EOF | wc -l
$removals $removals
EOF EOF
@ -29,7 +30,7 @@ if [ $removalCount -gt 0 ]; then
fi fi
# Adding new abstract methods could also break the API. # Adding new abstract methods could also break the API.
newAbstracts=$(echo "$diffContents" | grep "^+\s" | grep "\(public\|protected\) abstract") newAbstracts=$(echo "$diffContents" | grep "^+" | grep "\(public\|protected\) abstract")
abstractCount=`grep -v "^$" <<EOF | wc -l abstractCount=`grep -v "^$" <<EOF | wc -l
$newAbstracts $newAbstracts
EOF EOF

2
.idea/compiler.xml generated
View File

@ -60,6 +60,8 @@
<module name="jfx_integrationTest" target="1.8" /> <module name="jfx_integrationTest" target="1.8" />
<module name="jfx_main" target="1.8" /> <module name="jfx_main" target="1.8" />
<module name="jfx_test" target="1.8" /> <module name="jfx_test" target="1.8" />
<module name="kryo-hook_main" target="1.8" />
<module name="kryo-hook_test" target="1.8" />
<module name="loadtest_main" target="1.8" /> <module name="loadtest_main" target="1.8" />
<module name="loadtest_test" target="1.8" /> <module name="loadtest_test" target="1.8" />
<module name="mock_main" target="1.8" /> <module name="mock_main" target="1.8" />

View File

@ -4,7 +4,7 @@ buildscript {
file("$projectDir/constants.properties").withInputStream { constants.load(it) } file("$projectDir/constants.properties").withInputStream { constants.load(it) }
// Our version: bump this on release. // Our version: bump this on release.
ext.corda_release_version = "1.1-SNAPSHOT" ext.corda_release_version = "2.0-SNAPSHOT"
// Increment this on any release that changes public APIs anywhere in the Corda platform // Increment this on any release that changes public APIs anywhere in the Corda platform
// TODO This is going to be difficult until we have a clear separation throughout the code of what is public and what is internal // TODO This is going to be difficult until we have a clear separation throughout the code of what is public and what is internal
ext.corda_platform_version = 1 ext.corda_platform_version = 1
@ -231,7 +231,6 @@ tasks.withType(Test) {
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
directory "./build/nodes" directory "./build/nodes"
networkMap "O=Controller,OU=corda,L=London,C=GB"
node { node {
name "O=Controller,OU=corda,L=London,C=GB" name "O=Controller,OU=corda,L=London,C=GB"
notary = [validating : true] notary = [validating : true]
@ -294,12 +293,16 @@ artifactory {
publish { publish {
contextUrl = 'https://ci-artifactory.corda.r3cev.com/artifactory' contextUrl = 'https://ci-artifactory.corda.r3cev.com/artifactory'
repository { repository {
repoKey = 'corda-releases' repoKey = 'corda-dev'
username = 'teamcity' username = 'teamcity'
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD') password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
} }
defaults { defaults {
publications('corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'cordform-common', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver', 'corda-confidential-identities') // Root project applies the plugin (for this block) but does not need to be published
if(project != rootProject) {
publications(project.extensions.publish.name())
}
} }
} }
} }

View File

@ -28,11 +28,13 @@ import static kotlin.test.AssertionsKt.assertEquals;
import static net.corda.finance.Currencies.DOLLARS; import static net.corda.finance.Currencies.DOLLARS;
import static net.corda.finance.contracts.GetBalances.getCashBalance; import static net.corda.finance.contracts.GetBalances.getCashBalance;
import static net.corda.node.services.FlowPermissions.startFlowPermission; import static net.corda.node.services.FlowPermissions.startFlowPermission;
import static net.corda.testing.CoreTestUtils.setCordappPackages;
import static net.corda.testing.CoreTestUtils.unsetCordappPackages;
import static net.corda.testing.TestConstants.getALICE; import static net.corda.testing.TestConstants.getALICE;
public class CordaRPCJavaClientTest extends NodeBasedTest { public class CordaRPCJavaClientTest extends NodeBasedTest {
public CordaRPCJavaClientTest() {
super(Arrays.asList("net.corda.finance.contracts", CashSchemaV1.class.getPackage().getName()));
}
private List<String> perms = Arrays.asList(startFlowPermission(CashPaymentFlow.class), startFlowPermission(CashIssueFlow.class)); private List<String> perms = Arrays.asList(startFlowPermission(CashPaymentFlow.class), startFlowPermission(CashIssueFlow.class));
private Set<String> permSet = new HashSet<>(perms); private Set<String> permSet = new HashSet<>(perms);
private User rpcUser = new User("user1", "test", permSet); private User rpcUser = new User("user1", "test", permSet);
@ -49,17 +51,14 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
@Before @Before
public void setUp() throws ExecutionException, InterruptedException { public void setUp() throws ExecutionException, InterruptedException {
setCordappPackages("net.corda.finance.contracts");
CordaFuture<StartedNode<Node>> nodeFuture = startNotaryNode(getALICE().getName(), singletonList(rpcUser), true); CordaFuture<StartedNode<Node>> nodeFuture = startNotaryNode(getALICE().getName(), singletonList(rpcUser), true);
node = nodeFuture.get(); node = nodeFuture.get();
node.getInternals().registerCustomSchemas(Collections.singleton(CashSchemaV1.INSTANCE));
client = new CordaRPCClient(requireNonNull(node.getInternals().getConfiguration().getRpcAddress())); client = new CordaRPCClient(requireNonNull(node.getInternals().getConfiguration().getRpcAddress()));
} }
@After @After
public void done() throws IOException { public void done() throws IOException {
connection.close(); connection.close();
unsetCordappPackages();
} }
@Test @Test

View File

@ -23,7 +23,7 @@ import org.junit.rules.ExpectedException
@CordaSerializable @CordaSerializable
data class Packet(val x: () -> Long) data class Packet(val x: () -> Long)
class BlacklistKotlinClosureTest : NodeBasedTest() { class BlacklistKotlinClosureTest : NodeBasedTest(listOf("net.corda.client.rpc")) {
companion object { companion object {
@Suppress("UNUSED") val logger = loggerFor<BlacklistKotlinClosureTest>() @Suppress("UNUSED") val logger = loggerFor<BlacklistKotlinClosureTest>()
const val EVIL: Long = 666 const val EVIL: Long = 666
@ -66,7 +66,6 @@ class BlacklistKotlinClosureTest : NodeBasedTest() {
@Before @Before
fun setUp() { fun setUp() {
setCordappPackages("net.corda.client.rpc")
aliceNode = startNode(ALICE.name, rpcUsers = listOf(rpcUser)).getOrThrow() aliceNode = startNode(ALICE.name, rpcUsers = listOf(rpcUser)).getOrThrow()
bobNode = startNode(BOB.name, rpcUsers = listOf(rpcUser)).getOrThrow() bobNode = startNode(BOB.name, rpcUsers = listOf(rpcUser)).getOrThrow()
bobNode.registerInitiatedFlow(RemoteFlowC::class.java) bobNode.registerInitiatedFlow(RemoteFlowC::class.java)
@ -78,7 +77,6 @@ class BlacklistKotlinClosureTest : NodeBasedTest() {
connection?.close() connection?.close()
bobNode.internals.stop() bobNode.internals.stop()
aliceNode.internals.stop() aliceNode.internals.stop()
unsetCordappPackages()
} }
@Test @Test

View File

@ -2,6 +2,7 @@ package net.corda.client.rpc
import net.corda.core.crypto.random63BitValue import net.corda.core.crypto.random63BitValue
import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowInitiator
import net.corda.core.internal.packageName
import net.corda.core.messaging.FlowProgressHandle import net.corda.core.messaging.FlowProgressHandle
import net.corda.core.messaging.StateMachineUpdate import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
@ -23,8 +24,6 @@ import net.corda.nodeapi.User
import net.corda.testing.ALICE import net.corda.testing.ALICE
import net.corda.testing.chooseIdentity import net.corda.testing.chooseIdentity
import net.corda.testing.node.NodeBasedTest import net.corda.testing.node.NodeBasedTest
import net.corda.testing.setCordappPackages
import net.corda.testing.unsetCordappPackages
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.After import org.junit.After
@ -34,7 +33,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
class CordaRPCClientTest : NodeBasedTest() { class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", CashSchemaV1::class.packageName)) {
private val rpcUser = User("user1", "test", permissions = setOf( private val rpcUser = User("user1", "test", permissions = setOf(
startFlowPermission<CashIssueFlow>(), startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>() startFlowPermission<CashPaymentFlow>()
@ -49,16 +48,13 @@ class CordaRPCClientTest : NodeBasedTest() {
@Before @Before
fun setUp() { fun setUp() {
setCordappPackages("net.corda.finance.contracts")
node = startNotaryNode(ALICE.name, rpcUsers = listOf(rpcUser)).getOrThrow() node = startNotaryNode(ALICE.name, rpcUsers = listOf(rpcUser)).getOrThrow()
node.internals.registerCustomSchemas(setOf(CashSchemaV1))
client = CordaRPCClient(node.internals.configuration.rpcAddress!!) client = CordaRPCClient(node.internals.configuration.rpcAddress!!)
} }
@After @After
fun done() { fun done() {
connection?.close() connection?.close()
unsetCordappPackages()
} }
@Test @Test

View File

@ -92,6 +92,7 @@ class RPCStabilityTests {
startAndStop() startAndStop()
} }
val numberOfThreadsAfter = waitUntilNumberOfThreadsStable(executor) val numberOfThreadsAfter = waitUntilNumberOfThreadsStable(executor)
assertTrue(numberOfThreadsBefore >= numberOfThreadsAfter) assertTrue(numberOfThreadsBefore >= numberOfThreadsAfter)
executor.shutdownNow() executor.shutdownNow()
} }

View File

@ -74,9 +74,9 @@ public class StandaloneCordaRPCJavaClientTest {
} }
private void copyFinanceCordapp() { private void copyFinanceCordapp() {
Path pluginsDir = (factory.baseDirectory(notaryConfig).resolve("plugins")); Path cordappsDir = (factory.baseDirectory(notaryConfig).resolve("cordapps"));
try { try {
Files.createDirectories(pluginsDir); Files.createDirectories(cordappsDir);
} catch (IOException ex) { } catch (IOException ex) {
fail("Failed to create directories"); fail("Failed to create directories");
} }
@ -84,7 +84,7 @@ public class StandaloneCordaRPCJavaClientTest {
paths.forEach(file -> { paths.forEach(file -> {
if (file.toString().contains("corda-finance")) { if (file.toString().contains("corda-finance")) {
try { try {
Files.copy(file, pluginsDir.resolve(file.getFileName())); Files.copy(file, cordappsDir.resolve(file.getFileName()));
} catch (IOException ex) { } catch (IOException ex) {
fail("Failed to copy finance jar"); fail("Failed to copy finance jar");
} }

View File

@ -89,12 +89,12 @@ class StandaloneCordaRPClientTest {
} }
private fun copyFinanceCordapp() { private fun copyFinanceCordapp() {
val pluginsDir = (factory.baseDirectory(notaryConfig) / "plugins").createDirectories() val cordappsDir = (factory.baseDirectory(notaryConfig) / "cordapps").createDirectories()
// Find the finance jar file for the smoke tests of this module // Find the finance jar file for the smoke tests of this module
val financeJar = Paths.get("build", "resources", "smokeTest").list { val financeJar = Paths.get("build", "resources", "smokeTest").list {
it.filter { "corda-finance" in it.toString() }.toList().single() it.filter { "corda-finance" in it.toString() }.toList().single()
} }
financeJar.copyToDirectory(pluginsDir) financeJar.copyToDirectory(cordappsDir)
} }
@Test @Test

View File

@ -28,26 +28,24 @@ class IdentitySyncFlowTests {
@Before @Before
fun before() { fun before() {
setCordappPackages("net.corda.finance.contracts.asset")
// We run this in parallel threads to help catch any race conditions that may exist. // We run this in parallel threads to help catch any race conditions that may exist.
mockNet = MockNetwork(networkSendManuallyPumped = false, threadPerNode = true) mockNet = MockNetwork(networkSendManuallyPumped = false, threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset"))
} }
@After @After
fun cleanUp() { fun cleanUp() {
mockNet.stopNodes() mockNet.stopNodes()
unsetCordappPackages()
} }
@Test @Test
fun `sync confidential identities`() { fun `sync confidential identities`() {
// Set up values we'll need // Set up values we'll need
mockNet.createNotaryNode() val notaryNode = mockNet.createNotaryNode()
val aliceNode = mockNet.createPartyNode(ALICE.name) val aliceNode = mockNet.createPartyNode(ALICE_NAME)
val bobNode = mockNet.createPartyNode(BOB.name) val bobNode = mockNet.createPartyNode(BOB_NAME)
val alice: Party = aliceNode.services.myInfo.chooseIdentity() val alice: Party = aliceNode.info.singleIdentity()
val bob: Party = bobNode.services.myInfo.chooseIdentity() val bob: Party = bobNode.info.singleIdentity()
val notary = aliceNode.services.getDefaultNotary() val notary = notaryNode.services.getDefaultNotary()
bobNode.internals.registerInitiatedFlow(Receive::class.java) bobNode.internals.registerInitiatedFlow(Receive::class.java)
// Alice issues then pays some cash to a new confidential identity that Bob doesn't know about // Alice issues then pays some cash to a new confidential identity that Bob doesn't know about
@ -73,12 +71,12 @@ class IdentitySyncFlowTests {
fun `don't offer other's identities confidential identities`() { fun `don't offer other's identities confidential identities`() {
// Set up values we'll need // Set up values we'll need
val notaryNode = mockNet.createNotaryNode() val notaryNode = mockNet.createNotaryNode()
val aliceNode = mockNet.createPartyNode(ALICE.name) val aliceNode = mockNet.createPartyNode(ALICE_NAME)
val bobNode = mockNet.createPartyNode(BOB.name) val bobNode = mockNet.createPartyNode(BOB_NAME)
val charlieNode = mockNet.createPartyNode(CHARLIE.name) val charlieNode = mockNet.createPartyNode(CHARLIE_NAME)
val alice: Party = aliceNode.services.myInfo.chooseIdentity() val alice: Party = aliceNode.info.singleIdentity()
val bob: Party = bobNode.services.myInfo.chooseIdentity() val bob: Party = bobNode.info.singleIdentity()
val charlie: Party = charlieNode.services.myInfo.chooseIdentity() val charlie: Party = charlieNode.info.singleIdentity()
val notary = notaryNode.services.getDefaultNotary() val notary = notaryNode.services.getDefaultNotary()
bobNode.internals.registerInitiatedFlow(Receive::class.java) bobNode.internals.registerInitiatedFlow(Receive::class.java)

View File

@ -13,14 +13,14 @@ class SwapIdentitiesFlowTests {
@Test @Test
fun `issue key`() { fun `issue key`() {
// We run this in parallel threads to help catch any race conditions that may exist. // We run this in parallel threads to help catch any race conditions that may exist.
val mockNet = MockNetwork(false, true) val mockNet = MockNetwork(threadPerNode = true)
// Set up values we'll need // Set up values we'll need
val notaryNode = mockNet.createNotaryNode() val notaryNode = mockNet.createNotaryNode()
val aliceNode = mockNet.createPartyNode(ALICE.name) val aliceNode = mockNet.createPartyNode(ALICE.name)
val bobNode = mockNet.createPartyNode(BOB.name) val bobNode = mockNet.createPartyNode(BOB.name)
val alice: Party = aliceNode.services.myInfo.chooseIdentity() val alice = aliceNode.info.singleIdentity()
val bob: Party = bobNode.services.myInfo.chooseIdentity() val bob = bobNode.services.myInfo.singleIdentity()
// Run the flows // Run the flows
val requesterFlow = aliceNode.services.startFlow(SwapIdentitiesFlow(bob)) val requesterFlow = aliceNode.services.startFlow(SwapIdentitiesFlow(bob))
@ -53,13 +53,13 @@ class SwapIdentitiesFlowTests {
@Test @Test
fun `verifies identity name`() { fun `verifies identity name`() {
// We run this in parallel threads to help catch any race conditions that may exist. // We run this in parallel threads to help catch any race conditions that may exist.
val mockNet = MockNetwork(false, true) val mockNet = MockNetwork(threadPerNode = true)
// Set up values we'll need // Set up values we'll need
val notaryNode = mockNet.createNotaryNode(DUMMY_NOTARY.name) val notaryNode = mockNet.createNotaryNode(DUMMY_NOTARY.name)
val aliceNode = mockNet.createPartyNode(ALICE.name) val aliceNode = mockNet.createPartyNode(ALICE.name)
val bobNode = mockNet.createPartyNode(BOB.name) val bobNode = mockNet.createPartyNode(BOB.name)
val bob: Party = bobNode.services.myInfo.chooseIdentity() val bob: Party = bobNode.services.myInfo.singleIdentity()
val notBob = notaryNode.database.transaction { val notBob = notaryNode.database.transaction {
notaryNode.services.keyManagementService.freshKeyAndCert(notaryNode.services.myInfo.chooseIdentityAndCert(), false) notaryNode.services.keyManagementService.freshKeyAndCert(notaryNode.services.myInfo.chooseIdentityAndCert(), false)
} }
@ -78,13 +78,13 @@ class SwapIdentitiesFlowTests {
@Test @Test
fun `verifies signature`() { fun `verifies signature`() {
// We run this in parallel threads to help catch any race conditions that may exist. // We run this in parallel threads to help catch any race conditions that may exist.
val mockNet = MockNetwork(false, true) val mockNet = MockNetwork(threadPerNode = true)
// Set up values we'll need // Set up values we'll need
val notaryNode = mockNet.createNotaryNode(DUMMY_NOTARY.name) val notaryNode = mockNet.createNotaryNode(DUMMY_NOTARY.name)
val aliceNode = mockNet.createPartyNode(ALICE.name) val aliceNode = mockNet.createPartyNode(ALICE.name)
val bobNode = mockNet.createPartyNode(BOB.name) val bobNode = mockNet.createPartyNode(BOB.name)
val bob: Party = bobNode.services.myInfo.chooseIdentity() val bob: Party = bobNode.services.myInfo.singleIdentity()
// Check that the wrong signature is rejected // Check that the wrong signature is rejected
notaryNode.database.transaction { notaryNode.database.transaction {
notaryNode.services.keyManagementService.freshKeyAndCert(notaryNode.services.myInfo.chooseIdentityAndCert(), false) notaryNode.services.keyManagementService.freshKeyAndCert(notaryNode.services.myInfo.chooseIdentityAndCert(), false)

View File

@ -1,4 +1,4 @@
gradlePluginsVersion=2.0.1 gradlePluginsVersion=2.0.5
kotlinVersion=1.1.50 kotlinVersion=1.1.50
guavaVersion=21.0 guavaVersion=21.0
bouncycastleVersion=1.57 bouncycastleVersion=1.57

View File

@ -2,6 +2,7 @@ package net.corda.core.contracts
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.extractFile import net.corda.core.internal.extractFile
import net.corda.core.serialization.CordaSerializable
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
@ -17,6 +18,7 @@ import java.util.jar.JarInputStream
* - Legal documents * - Legal documents
* - Facts generated by oracles which might be reused a lot * - Facts generated by oracles which might be reused a lot
*/ */
@CordaSerializable
interface Attachment : NamedByHash { interface Attachment : NamedByHash {
fun open(): InputStream fun open(): InputStream
fun openAsJAR(): JarInputStream { fun openAsJAR(): JarInputStream {

View File

@ -10,5 +10,6 @@ enum class ComponentGroupEnum {
COMMANDS_GROUP, // ordinal = 2. COMMANDS_GROUP, // ordinal = 2.
ATTACHMENTS_GROUP, // ordinal = 3. ATTACHMENTS_GROUP, // ordinal = 3.
NOTARY_GROUP, // ordinal = 4. NOTARY_GROUP, // ordinal = 4.
TIMEWINDOW_GROUP // ordinal = 5. TIMEWINDOW_GROUP, // ordinal = 5.
SIGNERS_GROUP // ordinal = 6.
} }

View File

@ -64,6 +64,17 @@ abstract class TimeWindow {
*/ */
abstract val midpoint: Instant? abstract val midpoint: Instant?
/**
* Returns the duration between [fromTime] and [untilTime] if both are non-null. Otherwise returns null.
*/
val length: Duration? get() {
return if (fromTime == null || untilTime == null) {
null
} else {
Duration.between(fromTime, untilTime)
}
}
/** Returns true iff the given [instant] is within the time interval of this [TimeWindow]. */ /** Returns true iff the given [instant] is within the time interval of this [TimeWindow]. */
abstract operator fun contains(instant: Instant): Boolean abstract operator fun contains(instant: Instant): Boolean

View File

@ -19,7 +19,7 @@ sealed class TransactionVerificationException(val txId: SecureHash, message: Str
class ContractConstraintRejection(txId: SecureHash, contractClass: String) class ContractConstraintRejection(txId: SecureHash, contractClass: String)
: TransactionVerificationException(txId, "Contract constraints failed for $contractClass", null) : TransactionVerificationException(txId, "Contract constraints failed for $contractClass", null)
class MissingAttachmentRejection(txId: SecureHash, contractClass: String) class MissingAttachmentRejection(txId: SecureHash, val contractClass: String)
: TransactionVerificationException(txId, "Contract constraints failed, could not find attachment for: $contractClass", null) : TransactionVerificationException(txId, "Contract constraints failed, could not find attachment for: $contractClass", null)
class ContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable) class ContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable)

View File

@ -2,7 +2,10 @@ package net.corda.core.crypto
import net.corda.core.internal.X509EdDSAEngine import net.corda.core.internal.X509EdDSAEngine
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.i2p.crypto.eddsa.* import net.i2p.crypto.eddsa.EdDSAEngine
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
import net.i2p.crypto.eddsa.EdDSASecurityProvider
import net.i2p.crypto.eddsa.math.GroupElement import net.i2p.crypto.eddsa.math.GroupElement
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
@ -39,8 +42,6 @@ import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey
import org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec import org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec
import java.math.BigInteger import java.math.BigInteger
import java.security.* import java.security.*
import java.security.KeyFactory
import java.security.KeyPairGenerator
import java.security.spec.InvalidKeySpecException import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec import java.security.spec.X509EncodedKeySpec
@ -148,7 +149,7 @@ object Crypto {
"at the cost of larger key sizes and loss of compatibility." "at the cost of larger key sizes and loss of compatibility."
) )
/** Corda composite key type */ /** Corda composite key type. */
@JvmField @JvmField
val COMPOSITE_KEY = SignatureScheme( val COMPOSITE_KEY = SignatureScheme(
6, 6,
@ -823,7 +824,7 @@ object Crypto {
@JvmStatic @JvmStatic
fun deriveKeyPairFromEntropy(entropy: BigInteger): KeyPair = deriveKeyPairFromEntropy(DEFAULT_SIGNATURE_SCHEME, entropy) fun deriveKeyPairFromEntropy(entropy: BigInteger): KeyPair = deriveKeyPairFromEntropy(DEFAULT_SIGNATURE_SCHEME, entropy)
// custom key pair generator from entropy. // Custom key pair generator from entropy.
private fun deriveEdDSAKeyPairFromEntropy(entropy: BigInteger): KeyPair { private fun deriveEdDSAKeyPairFromEntropy(entropy: BigInteger): KeyPair {
val params = EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec val params = EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec
val bytes = entropy.toByteArray().copyOf(params.curve.field.getb() / 8) // Need to pad the entropy to the valid seed length. val bytes = entropy.toByteArray().copyOf(params.curve.field.getb() / 8) // Need to pad the entropy to the valid seed length.
@ -882,7 +883,7 @@ object Crypto {
} }
} }
// return true if EdDSA publicKey is point at infinity. // Return true if EdDSA publicKey is point at infinity.
// For EdDSA a custom function is required as it is not supported by the I2P implementation. // For EdDSA a custom function is required as it is not supported by the I2P implementation.
private fun isEdDSAPointAtInfinity(publicKey: EdDSAPublicKey): Boolean { private fun isEdDSAPointAtInfinity(publicKey: EdDSAPublicKey): Boolean {
return publicKey.a.toP3() == (EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec).curve.getZero(GroupElement.Representation.P3) return publicKey.a.toP3() == (EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec).curve.getZero(GroupElement.Representation.P3)
@ -894,7 +895,7 @@ object Crypto {
return signatureScheme.schemeCodeName in signatureSchemeMap return signatureScheme.schemeCodeName in signatureSchemeMap
} }
// validate a key, by checking its algorithmic params. // Validate a key, by checking its algorithmic params.
private fun validateKey(signatureScheme: SignatureScheme, key: Key): Boolean { private fun validateKey(signatureScheme: SignatureScheme, key: Key): Boolean {
return when (key) { return when (key) {
is PublicKey -> validatePublicKey(signatureScheme, key) is PublicKey -> validatePublicKey(signatureScheme, key)
@ -903,7 +904,7 @@ object Crypto {
} }
} }
// check if a public key satisfies algorithm specs (for ECC: key should lie on the curve and not being point-at-infinity). // Check if a public key satisfies algorithm specs (for ECC: key should lie on the curve and not being point-at-infinity).
private fun validatePublicKey(signatureScheme: SignatureScheme, key: PublicKey): Boolean { private fun validatePublicKey(signatureScheme: SignatureScheme, key: PublicKey): Boolean {
return when (key) { return when (key) {
is BCECPublicKey, is EdDSAPublicKey -> publicKeyOnCurve(signatureScheme, key) is BCECPublicKey, is EdDSAPublicKey -> publicKeyOnCurve(signatureScheme, key)
@ -912,7 +913,7 @@ object Crypto {
} }
} }
// check if a private key satisfies algorithm specs. // Check if a private key satisfies algorithm specs.
private fun validatePrivateKey(signatureScheme: SignatureScheme, key: PrivateKey): Boolean { private fun validatePrivateKey(signatureScheme: SignatureScheme, key: PrivateKey): Boolean {
return when (key) { return when (key) {
is BCECPrivateKey -> key.parameters == signatureScheme.algSpec is BCECPrivateKey -> key.parameters == signatureScheme.algSpec
@ -924,7 +925,6 @@ object Crypto {
/** /**
* Convert a public key to a supported implementation. * Convert a public key to a supported implementation.
*
* @param key a public key. * @param key a public key.
* @return a supported implementation of the input public key. * @return a supported implementation of the input public key.
* @throws IllegalArgumentException on not supported scheme or if the given key specification * @throws IllegalArgumentException on not supported scheme or if the given key specification

View File

@ -20,9 +20,19 @@ import java.security.*
* @throws InvalidKeyException if the private key is invalid. * @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key. * @throws SignatureException if signing is not possible due to malformed data or private key.
*/ */
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class) @Throws(InvalidKeyException::class, SignatureException::class)
fun PrivateKey.sign(bytesToSign: ByteArray): DigitalSignature = DigitalSignature(Crypto.doSign(this, bytesToSign)) fun PrivateKey.sign(bytesToSign: ByteArray): DigitalSignature = DigitalSignature(Crypto.doSign(this, bytesToSign))
/**
* Utility to simplify the act of signing a byte array and return a [DigitalSignature.WithKey] object.
* Note that there is no check if the public key matches with the signing private key.
* @param bytesToSign the data/message to be signed in [ByteArray] form (usually the Merkle root).
* @return the [DigitalSignature.WithKey] object on the input message [bytesToSign] and [publicKey].
* @throws IllegalArgumentException if the signature scheme is not supported for this private key.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(InvalidKeyException::class, SignatureException::class)
fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey) = DigitalSignature.WithKey(publicKey, this.sign(bytesToSign).bytes) fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey) = DigitalSignature.WithKey(publicKey, this.sign(bytesToSign).bytes)
/** /**
@ -33,10 +43,13 @@ fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey) = DigitalSigna
* @throws InvalidKeyException if the private key is invalid. * @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key. * @throws SignatureException if signing is not possible due to malformed data or private key.
*/ */
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class) @Throws(InvalidKeyException::class, SignatureException::class)
fun KeyPair.sign(bytesToSign: ByteArray) = private.sign(bytesToSign, public) fun KeyPair.sign(bytesToSign: ByteArray) = private.sign(bytesToSign, public)
/** Helper function to sign the bytes of [bytesToSign] with a key pair. */
@Throws(InvalidKeyException::class, SignatureException::class)
fun KeyPair.sign(bytesToSign: OpaqueBytes) = sign(bytesToSign.bytes) fun KeyPair.sign(bytesToSign: OpaqueBytes) = sign(bytesToSign.bytes)
/** /**
* Helper function for signing a [SignableData] object. * Helper function for signing a [SignableData] object.
* @param signableData the object to be signed. * @param signableData the object to be signed.
@ -56,8 +69,8 @@ fun KeyPair.sign(signableData: SignableData): TransactionSignature = Crypto.doSi
* @throws SignatureException if the signature is invalid (i.e. damaged), or does not match the key (incorrect). * @throws SignatureException if the signature is invalid (i.e. damaged), or does not match the key (incorrect).
* @throws IllegalArgumentException if the signature scheme is not supported or if any of the clear or signature data is empty. * @throws IllegalArgumentException if the signature scheme is not supported or if any of the clear or signature data is empty.
*/ */
// TODO: SignatureException should be used only for a damaged signature, as per `java.security.Signature.verify()`, // TODO: SignatureException should be used only for a damaged signature, as per `java.security.Signature.verify()`.
@Throws(SignatureException::class, IllegalArgumentException::class, InvalidKeyException::class) @Throws(SignatureException::class, InvalidKeyException::class)
fun PublicKey.verify(content: ByteArray, signature: DigitalSignature) = Crypto.doVerify(this, signature.bytes, content) fun PublicKey.verify(content: ByteArray, signature: DigitalSignature) = Crypto.doVerify(this, signature.bytes, content)
/** /**
@ -70,9 +83,10 @@ fun PublicKey.verify(content: ByteArray, signature: DigitalSignature) = Crypto.d
* signature). * signature).
* @throws SignatureException if the signature is invalid (i.e. damaged). * @throws SignatureException if the signature is invalid (i.e. damaged).
* @throws IllegalArgumentException if the signature scheme is not supported or if any of the clear or signature data is empty. * @throws IllegalArgumentException if the signature scheme is not supported or if any of the clear or signature data is empty.
* @throws IllegalStateException if this is a [CompositeKey], because verification of composite key signatures is not supported.
* @return whether the signature is correct for this key. * @return whether the signature is correct for this key.
*/ */
@Throws(IllegalStateException::class, SignatureException::class, IllegalArgumentException::class) @Throws(SignatureException::class, InvalidKeyException::class)
fun PublicKey.isValid(content: ByteArray, signature: DigitalSignature): Boolean { fun PublicKey.isValid(content: ByteArray, signature: DigitalSignature): Boolean {
if (this is CompositeKey) if (this is CompositeKey)
throw IllegalStateException("Verification of CompositeKey signatures currently not supported.") // TODO CompositeSignature verification. throw IllegalStateException("Verification of CompositeKey signatures currently not supported.") // TODO CompositeSignature verification.
@ -82,9 +96,12 @@ fun PublicKey.isValid(content: ByteArray, signature: DigitalSignature): Boolean
/** Render a public key to its hash (in Base58) of its serialised form using the DL prefix. */ /** Render a public key to its hash (in Base58) of its serialised form using the DL prefix. */
fun PublicKey.toStringShort(): String = "DL" + this.toSHA256Bytes().toBase58() fun PublicKey.toStringShort(): String = "DL" + this.toSHA256Bytes().toBase58()
/** Return a [Set] of the contained keys if this is a [CompositeKey]; otherwise, return a [Set] with a single element (this [PublicKey]). */
val PublicKey.keys: Set<PublicKey> get() = (this as? CompositeKey)?.leafKeys ?: setOf(this) val PublicKey.keys: Set<PublicKey> get() = (this as? CompositeKey)?.leafKeys ?: setOf(this)
/** Return true if [otherKey] fulfils the requirements of this [PublicKey]. */
fun PublicKey.isFulfilledBy(otherKey: PublicKey): Boolean = isFulfilledBy(setOf(otherKey)) fun PublicKey.isFulfilledBy(otherKey: PublicKey): Boolean = isFulfilledBy(setOf(otherKey))
/** Return true if [otherKeys] fulfil the requirements of this [PublicKey]. */
fun PublicKey.isFulfilledBy(otherKeys: Iterable<PublicKey>): Boolean = (this as? CompositeKey)?.isFulfilledBy(otherKeys) ?: (this in otherKeys) fun PublicKey.isFulfilledBy(otherKeys: Iterable<PublicKey>): Boolean = (this as? CompositeKey)?.isFulfilledBy(otherKeys) ?: (this in otherKeys)
/** Checks whether any of the given [keys] matches a leaf on the [CompositeKey] tree or a single [PublicKey]. */ /** Checks whether any of the given [keys] matches a leaf on the [CompositeKey] tree or a single [PublicKey]. */
@ -98,8 +115,9 @@ fun Iterable<TransactionSignature>.byKeys() = map { it.by }.toSet()
// Allow Kotlin destructuring: // Allow Kotlin destructuring:
// val (private, public) = keyPair // val (private, public) = keyPair
/* The [PrivateKey] of this [KeyPair] .*/
operator fun KeyPair.component1(): PrivateKey = this.private operator fun KeyPair.component1(): PrivateKey = this.private
/* The [PublicKey] of this [KeyPair] .*/
operator fun KeyPair.component2(): PublicKey = this.public operator fun KeyPair.component2(): PublicKey = this.public
/** A simple wrapper that will make it easier to swap out the EC algorithm we use in future. */ /** A simple wrapper that will make it easier to swap out the EC algorithm we use in future. */
@ -122,7 +140,7 @@ fun entropyToKeyPair(entropy: BigInteger): KeyPair = Crypto.deriveKeyPairFromEnt
* if this signatureData algorithm is unable to process the input data provided, etc. * if this signatureData algorithm is unable to process the input data provided, etc.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key or if any of the clear or signature data is empty. * @throws IllegalArgumentException if the signature scheme is not supported for this private key or if any of the clear or signature data is empty.
*/ */
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class) @Throws(InvalidKeyException::class, SignatureException::class)
fun PublicKey.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Crypto.doVerify(this, signatureData, clearData) fun PublicKey.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Crypto.doVerify(this, signatureData, clearData)
/** /**
@ -135,7 +153,7 @@ fun PublicKey.verify(signatureData: ByteArray, clearData: ByteArray): Boolean =
* if this signatureData algorithm is unable to process the input data provided, etc. * if this signatureData algorithm is unable to process the input data provided, etc.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key or if any of the clear or signature data is empty. * @throws IllegalArgumentException if the signature scheme is not supported for this private key or if any of the clear or signature data is empty.
*/ */
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class) @Throws(InvalidKeyException::class, SignatureException::class)
fun KeyPair.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Crypto.doVerify(this.public, signatureData, clearData) fun KeyPair.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Crypto.doVerify(this.public, signatureData, clearData)
/** /**

View File

@ -158,4 +158,41 @@ class PartialMerkleTree(val root: PartialTree) {
return false return false
return (verifyRoot == merkleRootHash) return (verifyRoot == merkleRootHash)
} }
/**
* Method to return the index of the input leaf in the partial Merkle tree structure.
* @param leaf the component hash to check.
* @return leaf-index of this component (starting from zero).
* @throws MerkleTreeException if the provided hash is not in the tree.
*/
@Throws(MerkleTreeException::class)
internal fun leafIndex(leaf: SecureHash): Int {
// Special handling if the tree consists of one node only.
if (root is PartialTree.IncludedLeaf && root.hash == leaf) return 0
val flagPath = mutableListOf<Boolean>()
if (!leafIndexHelper(leaf, this.root, flagPath)) throw MerkleTreeException("The provided hash $leaf is not in the tree.")
return indexFromFlagPath(flagPath)
}
// Helper function to compute the path. False means go to the left and True to the right.
// Because the path is updated recursively, the path is returned in reverse order.
private fun leafIndexHelper(leaf: SecureHash, node: PartialTree, path: MutableList<Boolean>): Boolean {
if (node is PartialTree.IncludedLeaf) {
return node.hash == leaf
} else if (node is PartialTree.Node) {
if (leafIndexHelper(leaf, node.left, path)) {
path.add(false)
return true
}
if (leafIndexHelper(leaf, node.right, path)) {
path.add(true)
return true
}
}
return false
}
// Return the leaf index from the path boolean list.
private fun indexFromFlagPath(pathList: List<Boolean>) =
pathList.mapIndexed { index, value -> if (value) (1 shl index) else 0 }.sum()
} }

View File

@ -19,39 +19,87 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
} }
} }
/**
* Convert the hash value to an uppercase hexadecimal [String].
*/
override fun toString(): String = bytes.toHexString() override fun toString(): String = bytes.toHexString()
/**
* Returns the first [prefixLen] hexadecimal digits of the [SecureHash] value.
* @param prefixLen The number of characters in the prefix.
*/
fun prefixChars(prefixLen: Int = 6) = toString().substring(0, prefixLen) fun prefixChars(prefixLen: Int = 6) = toString().substring(0, prefixLen)
/**
* Append a second hash value to this hash value, and then compute the SHA-256 hash of the result.
* @param other The hash to append to this one.
*/
fun hashConcat(other: SecureHash) = (this.bytes + other.bytes).sha256() fun hashConcat(other: SecureHash) = (this.bytes + other.bytes).sha256()
// Like static methods in Java, except the 'companion' is a singleton that can have state. // Like static methods in Java, except the 'companion' is a singleton that can have state.
companion object { companion object {
/**
* Converts a SHA-256 hash value represented as a hexadecimal [String] into a [SecureHash].
* @param str A sequence of 64 hexadecimal digits that represents a SHA-256 hash value.
* @throws IllegalArgumentException The input string does not contain 64 hexadecimal digits, or it contains incorrectly-encoded characters.
*/
@JvmStatic @JvmStatic
fun parse(str: String) = str.toUpperCase().parseAsHex().let { fun parse(str: String): SHA256 {
when (it.size) { return str.toUpperCase().parseAsHex().let {
32 -> SHA256(it) when (it.size) {
else -> throw IllegalArgumentException("Provided string is ${it.size} bytes not 32 bytes in hex: $str") 32 -> SHA256(it)
else -> throw IllegalArgumentException("Provided string is ${it.size} bytes not 32 bytes in hex: $str")
}
} }
} }
/**
* Computes the SHA-256 hash value of the [ByteArray].
* @param bytes The [ByteArray] to hash.
*/
@JvmStatic @JvmStatic
fun sha256(bytes: ByteArray) = SHA256(MessageDigest.getInstance("SHA-256").digest(bytes)) fun sha256(bytes: ByteArray) = SHA256(MessageDigest.getInstance("SHA-256").digest(bytes))
/**
* Computes the SHA-256 hash of the [ByteArray], and then computes the SHA-256 hash of the hash.
* @param bytes The [ByteArray] to hash.
*/
@JvmStatic @JvmStatic
fun sha256Twice(bytes: ByteArray) = sha256(sha256(bytes).bytes) fun sha256Twice(bytes: ByteArray) = sha256(sha256(bytes).bytes)
/**
* Computes the SHA-256 hash of the [String]'s UTF-8 byte contents.
* @param str [String] whose UTF-8 contents will be hashed.
*/
@JvmStatic @JvmStatic
fun sha256(str: String) = sha256(str.toByteArray()) fun sha256(str: String) = sha256(str.toByteArray())
/**
* Generates a random SHA-256 value.
*/
@JvmStatic @JvmStatic
fun randomSHA256() = sha256(newSecureRandom().generateSeed(32)) fun randomSHA256() = sha256(newSecureRandom().generateSeed(32))
/**
* A SHA-256 hash value consisting of 32 0x00 bytes.
*/
val zeroHash = SecureHash.SHA256(ByteArray(32, { 0.toByte() })) val zeroHash = SecureHash.SHA256(ByteArray(32, { 0.toByte() }))
/**
* A SHA-256 hash value consisting of 32 0xFF bytes.
*/
val allOnesHash = SecureHash.SHA256(ByteArray(32, { 255.toByte() })) val allOnesHash = SecureHash.SHA256(ByteArray(32, { 255.toByte() }))
} }
// In future, maybe SHA3, truncated hashes etc. // In future, maybe SHA3, truncated hashes etc.
} }
/**
* Compute the SHA-256 hash for the contents of the [ByteArray].
*/
fun ByteArray.sha256(): SecureHash.SHA256 = SecureHash.sha256(this) fun ByteArray.sha256(): SecureHash.SHA256 = SecureHash.sha256(this)
/**
* Compute the SHA-256 hash for the contents of the [OpaqueBytes].
*/
fun OpaqueBytes.sha256(): SecureHash.SHA256 = SecureHash.sha256(this.bytes) fun OpaqueBytes.sha256(): SecureHash.SHA256 = SecureHash.sha256(this.bytes)

View File

@ -1,6 +1,7 @@
package net.corda.core.flows package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import co.paralleluniverse.strands.Strand
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
@ -16,6 +17,8 @@ import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import org.slf4j.Logger import org.slf4j.Logger
import java.time.Duration
import java.time.Instant
/** /**
* A sub-class of [FlowLogic<T>] implements a flow using direct, straight line blocking code. Thus you * A sub-class of [FlowLogic<T>] implements a flow using direct, straight line blocking code. Thus you
@ -43,6 +46,34 @@ abstract class FlowLogic<out T> {
/** This is where you should log things to. */ /** This is where you should log things to. */
val logger: Logger get() = stateMachine.logger val logger: Logger get() = stateMachine.logger
companion object {
/**
* Return the outermost [FlowLogic] instance, or null if not in a flow.
*/
@JvmStatic
val currentTopLevel: FlowLogic<*>? get() = (Strand.currentStrand() as? FlowStateMachine<*>)?.logic
/**
* If on a flow, suspends the flow and only wakes it up after at least [duration] time has passed. Otherwise,
* just sleep for [duration]. This sleep function is not designed to aid scheduling, for which you should
* consider using [SchedulableState]. It is designed to aid with managing contention for which you have not
* managed via another means.
*
* Warning: long sleeps and in general long running flows are highly discouraged, as there is currently no
* support for flow migration! This method will throw an exception if you attempt to sleep for longer than
* 5 minutes.
*/
@Suspendable
@JvmStatic
@Throws(FlowException::class)
fun sleep(duration: Duration) {
if (duration.compareTo(Duration.ofMinutes(5)) > 0) {
throw FlowException("Attempt to sleep for longer than 5 minutes is not supported. Consider using SchedulableState.")
}
(Strand.currentStrand() as? FlowStateMachine<*>)?.sleepUntil(Instant.now() + duration) ?: Strand.sleep(duration.toMillis())
}
}
/** /**
* Returns a wrapped [java.util.UUID] object that identifies this state machine run (i.e. subflows have the same * Returns a wrapped [java.util.UUID] object that identifies this state machine run (i.e. subflows have the same
* identifier as their parents). * identifier as their parents).

View File

@ -10,6 +10,7 @@ import net.corda.core.node.ServiceHub
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.UntrustworthyData
import org.slf4j.Logger import org.slf4j.Logger
import java.time.Instant
/** This is an internal interface that is implemented by code in the node module. You should look at [FlowLogic]. */ /** This is an internal interface that is implemented by code in the node module. You should look at [FlowLogic]. */
interface FlowStateMachine<R> { interface FlowStateMachine<R> {
@ -35,6 +36,9 @@ interface FlowStateMachine<R> {
@Suspendable @Suspendable
fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>): SignedTransaction fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>): SignedTransaction
@Suspendable
fun sleepUntil(until: Instant)
fun checkFlowPermission(permissionName: String, extraAuditData: Map<String, String>) fun checkFlowPermission(permissionName: String, extraAuditData: Map<String, String>)
fun recordAuditEvent(eventType: String, comment: String, extraAuditData: Map<String, String>) fun recordAuditEvent(eventType: String, comment: String, extraAuditData: Map<String, String>)
@ -45,6 +49,7 @@ interface FlowStateMachine<R> {
@Suspendable @Suspendable
fun persistFlowStackSnapshot(flowClass: Class<out FlowLogic<*>>) fun persistFlowStackSnapshot(flowClass: Class<out FlowLogic<*>>)
val logic: FlowLogic<R>
val serviceHub: ServiceHub val serviceHub: ServiceHub
val logger: Logger val logger: Logger
val id: StateMachineRunId val id: StateMachineRunId

View File

@ -300,3 +300,6 @@ fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serial
* @suppress * @suppress
*/ */
fun TransactionBuilder.toLedgerTransaction(services: ServiceHub, serializationContext: SerializationContext) = toLedgerTransactionWithContext(services, serializationContext) fun TransactionBuilder.toLedgerTransaction(services: ServiceHub, serializationContext: SerializationContext) = toLedgerTransactionWithContext(services, serializationContext)
/** Convenience method to get the package name of a class literal. */
val KClass<*>.packageName get() = java.`package`.name

View File

@ -1,5 +1,6 @@
package net.corda.core.node package net.corda.core.node
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
@ -42,4 +43,9 @@ data class NodeInfo(val addresses: List<NetworkHostAndPort>,
/** Returns true if [party] is one of the identities of this node, else false. */ /** Returns true if [party] is one of the identities of this node, else false. */
fun isLegalIdentity(party: Party): Boolean = party in legalIdentities fun isLegalIdentity(party: Party): Boolean = party in legalIdentities
fun identityFromX500Name(name: CordaX500Name): Party {
val identity = legalIdentitiesAndCerts.singleOrNull { it.name == name } ?: throw IllegalArgumentException("Node does not have an identity \"$name\"")
return identity.party
}
} }

View File

@ -18,8 +18,7 @@ import java.security.PublicKey
* from an authoritative service, and adds easy lookup of the data stored within it. Generally it would be initialised * from an authoritative service, and adds easy lookup of the data stored within it. Generally it would be initialised
* with a specified network map service, which it fetches data from and then subscribes to updates of. * with a specified network map service, which it fetches data from and then subscribes to updates of.
*/ */
interface NetworkMapCache { interface NetworkMapCache : NetworkMapCacheBase {
@CordaSerializable @CordaSerializable
sealed class MapChange { sealed class MapChange {
abstract val node: NodeInfo abstract val node: NodeInfo
@ -29,6 +28,22 @@ interface NetworkMapCache {
data class Modified(override val node: NodeInfo, val previousNode: NodeInfo) : MapChange() data class Modified(override val node: NodeInfo, val previousNode: NodeInfo) : MapChange()
} }
/**
* Look up the node info for a specific party. Will attempt to de-anonymise the party if applicable; if the party
* is anonymised and the well known party cannot be resolved, it is impossible ot identify the node and therefore this
* returns null.
* Notice that when there are more than one node for a given party (in case of distributed services) first service node
* found will be returned. See also: [NetworkMapCache.getNodesByLegalIdentityKey].
*
* @param party party to retrieve node information for.
* @return the node for the identity, or null if the node could not be found. This does not necessarily mean there is
* no node for the party, only that this cache is unaware of it.
*/
fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo?
}
/** Subset of [NetworkMapCache] that doesn't depend on an [IdentityService]. */
interface NetworkMapCacheBase {
// DOCSTART 1 // DOCSTART 1
/** /**
* A list of notary services available on the network. * A list of notary services available on the network.
@ -40,7 +55,7 @@ interface NetworkMapCache {
// DOCEND 1 // DOCEND 1
/** Tracks changes to the network map cache. */ /** Tracks changes to the network map cache. */
val changed: Observable<MapChange> val changed: Observable<NetworkMapCache.MapChange>
/** Future to track completion of the NetworkMapService registration. */ /** Future to track completion of the NetworkMapService registration. */
val nodeReady: CordaFuture<Void?> val nodeReady: CordaFuture<Void?>
@ -48,20 +63,7 @@ interface NetworkMapCache {
* Atomically get the current party nodes and a stream of updates. Note that the Observable buffers updates until the * Atomically get the current party nodes and a stream of updates. Note that the Observable buffers updates until the
* first subscriber is registered so as to avoid racing with early updates. * first subscriber is registered so as to avoid racing with early updates.
*/ */
fun track(): DataFeed<List<NodeInfo>, MapChange> fun track(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange>
/**
* Look up the node info for a specific party. Will attempt to de-anonymise the party if applicable; if the party
* is anonymised and the well known party cannot be resolved, it is impossible ot identify the node and therefore this
* returns null.
* Notice that when there are more than one node for a given party (in case of distributed services) first service node
* found will be returned. See also: [getNodesByLegalIdentityKey].
*
* @param party party to retrieve node information for.
* @return the node for the identity, or null if the node could not be found. This does not necessarily mean there is
* no node for the party, only that this cache is unaware of it.
*/
fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo?
/** /**
* Look up the node info for a legal name. * Look up the node info for a legal name.

View File

@ -1,5 +1,6 @@
package net.corda.core.node.services package net.corda.core.node.services
import com.google.common.primitives.Booleans
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.* import net.corda.core.crypto.*
@ -15,12 +16,13 @@ import java.security.PublicKey
abstract class NotaryService : SingletonSerializeAsToken() { abstract class NotaryService : SingletonSerializeAsToken() {
companion object { companion object {
const val ID_PREFIX = "corda.notary." const val ID_PREFIX = "corda.notary."
fun constructId(validating: Boolean, raft: Boolean = false, bft: Boolean = false): String { fun constructId(validating: Boolean, raft: Boolean = false, bft: Boolean = false, custom: Boolean = false): String {
require(!raft || !bft) require(Booleans.countTrue(raft, bft, custom) <= 1) { "At most one of raft, bft or custom may be true" }
return StringBuffer(ID_PREFIX).apply { return StringBuffer(ID_PREFIX).apply {
append(if (validating) "validating" else "simple") append(if (validating) "validating" else "simple")
if (raft) append(".raft") if (raft) append(".raft")
if (bft) append(".bft") if (bft) append(".bft")
if (custom) append(".custom")
}.toString() }.toString()
} }
} }

View File

@ -1,5 +1,3 @@
@file:JvmName("SerializationAPI")
package net.corda.core.serialization package net.corda.core.serialization
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
@ -8,6 +6,9 @@ import net.corda.core.internal.WriteOnceProperty
import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.sequence import net.corda.core.utilities.sequence
import java.sql.Blob
data class ObjectWithCompatibleContext<out T : Any>(val obj: T, val context: SerializationContext)
/** /**
* An abstraction for serializing and deserializing objects, with support for versioning of the wire format via * An abstraction for serializing and deserializing objects, with support for versioning of the wire format via
@ -23,6 +24,16 @@ abstract class SerializationFactory {
*/ */
abstract fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T abstract fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T
/**
* Deserialize the bytes in to an object, using the prefixed bytes to determine the format.
*
* @param byteSequence The bytes to deserialize, including a format header prefix.
* @param clazz The class or superclass or the object to be deserialized, or [Any] or [Object] if unknown.
* @param context A context that configures various parameters to deserialization.
* @return deserialized object along with [SerializationContext] to identify encoding used.
*/
abstract fun <T : Any> deserializeWithCompatibleContext(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): ObjectWithCompatibleContext<T>
/** /**
* Serialize an object to bytes using the preferred serialization format version from the context. * Serialize an object to bytes using the preferred serialization format version from the context.
* *
@ -88,6 +99,8 @@ abstract class SerializationFactory {
} }
} }
typealias VersionHeader = ByteSequence
/** /**
* Parameters to serialization and deserialization. * Parameters to serialization and deserialization.
*/ */
@ -95,7 +108,7 @@ interface SerializationContext {
/** /**
* When serializing, use the format this header sequence represents. * When serializing, use the format this header sequence represents.
*/ */
val preferredSerializationVersion: ByteSequence val preferredSerializationVersion: VersionHeader
/** /**
* The class loader to use for deserialization. * The class loader to use for deserialization.
*/ */
@ -148,7 +161,7 @@ interface SerializationContext {
/** /**
* Helper method to return a new context based on this context but with serialization using the format this header sequence represents. * Helper method to return a new context based on this context but with serialization using the format this header sequence represents.
*/ */
fun withPreferredSerializationVersion(versionHeader: ByteSequence): SerializationContext fun withPreferredSerializationVersion(versionHeader: VersionHeader): SerializationContext
/** /**
* The use case that we are serializing for, since it influences the implementations chosen. * The use case that we are serializing for, since it influences the implementations chosen.
@ -175,6 +188,15 @@ inline fun <reified T : Any> ByteSequence.deserialize(serializationFactory: Seri
return serializationFactory.deserialize(this, T::class.java, context) return serializationFactory.deserialize(this, T::class.java, context)
} }
/**
* Additionally returns [SerializationContext] which was used for encoding.
* It might be helpful to know [SerializationContext] to use the same encoding in the reply.
*/
inline fun <reified T : Any> ByteSequence.deserializeWithCompatibleContext(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory,
context: SerializationContext = serializationFactory.defaultContext): ObjectWithCompatibleContext<T> {
return serializationFactory.deserializeWithCompatibleContext(this, T::class.java, context)
}
/** /**
* Convenience extension method for deserializing SerializedBytes with type matching, utilising the defaults. * Convenience extension method for deserializing SerializedBytes with type matching, utilising the defaults.
*/ */
@ -187,6 +209,11 @@ inline fun <reified T : Any> SerializedBytes<T>.deserialize(serializationFactory
*/ */
inline fun <reified T : Any> ByteArray.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): T = this.sequence().deserialize(serializationFactory, context) inline fun <reified T : Any> ByteArray.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): T = this.sequence().deserialize(serializationFactory, context)
/**
* Convenience extension method for deserializing a JDBC Blob, utilising the defaults.
*/
inline fun <reified T : Any> Blob.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): T = this.getBytes(1, this.length().toInt()).deserialize(serializationFactory, context)
/** /**
* Convenience extension method for serializing an object of type T, utilising the defaults. * Convenience extension method for serializing an object of type T, utilising the defaults.
*/ */

View File

@ -25,7 +25,7 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
override val outputs: List<TransactionState<ContractState>> = deserialiseComponentGroup(ComponentGroupEnum.OUTPUTS_GROUP, { SerializedBytes<TransactionState<ContractState>>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) }) override val outputs: List<TransactionState<ContractState>> = deserialiseComponentGroup(ComponentGroupEnum.OUTPUTS_GROUP, { SerializedBytes<TransactionState<ContractState>>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) })
/** Ordered list of ([CommandData], [PublicKey]) pairs that instruct the contracts what to do. */ /** Ordered list of ([CommandData], [PublicKey]) pairs that instruct the contracts what to do. */
val commands: List<Command<*>> = deserialiseComponentGroup(ComponentGroupEnum.COMMANDS_GROUP, { SerializedBytes<Command<*>>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) }) val commands: List<Command<*>> = deserialiseCommands()
override val notary: Party? = let { override val notary: Party? = let {
val notaries: List<Party> = deserialiseComponentGroup(ComponentGroupEnum.NOTARY_GROUP, { SerializedBytes<Party>(it).deserialize() }) val notaries: List<Party> = deserialiseComponentGroup(ComponentGroupEnum.NOTARY_GROUP, { SerializedBytes<Party>(it).deserialize() })
@ -74,6 +74,31 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
emptyList() emptyList()
} }
} }
// Method to deserialise Commands from its two groups:
// COMMANDS_GROUP which contains the CommandData part
// and SIGNERS_GROUP which contains the Signers part.
private fun deserialiseCommands(): List<Command<*>> {
// TODO: we could avoid deserialising unrelated signers.
// However, current approach ensures the transaction is not malformed
// and it will throw if any of the signers objects is not List of public keys).
val signersList = deserialiseComponentGroup(ComponentGroupEnum.SIGNERS_GROUP, { SerializedBytes<List<PublicKey>>(it).deserialize() })
val commandDataList = deserialiseComponentGroup(ComponentGroupEnum.COMMANDS_GROUP, { SerializedBytes<CommandData>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) })
val group = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal }
if (group is FilteredComponentGroup) {
check(commandDataList.size <= signersList.size) { "Invalid Transaction. Less Signers (${signersList.size}) than CommandData (${commandDataList.size}) objects" }
val componentHashes = group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) }
val leafIndices = componentHashes.map { group.partialMerkleTree.leafIndex(it) }
if (leafIndices.isNotEmpty())
check(leafIndices.max()!! < signersList.size) { "Invalid Transaction. A command with no corresponding signer detected" }
return commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[leafIndices[index]]) }
} else {
// It is a WireTransaction
// or a FilteredTransaction with no Commands (in which case group is null).
check(commandDataList.size == signersList.size) { "Invalid Transaction. Sizes of CommandData (${commandDataList.size}) and Signers (${signersList.size}) do not match" }
return commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[index]) }
}
}
} }
/** /**
@ -111,11 +136,12 @@ class FilteredTransaction private constructor(
val filteredSerialisedComponents: MutableMap<Int, MutableList<OpaqueBytes>> = hashMapOf() val filteredSerialisedComponents: MutableMap<Int, MutableList<OpaqueBytes>> = hashMapOf()
val filteredComponentNonces: MutableMap<Int, MutableList<SecureHash>> = hashMapOf() val filteredComponentNonces: MutableMap<Int, MutableList<SecureHash>> = hashMapOf()
val filteredComponentHashes: MutableMap<Int, MutableList<SecureHash>> = hashMapOf() // Required for partial Merkle tree generation. val filteredComponentHashes: MutableMap<Int, MutableList<SecureHash>> = hashMapOf() // Required for partial Merkle tree generation.
var signersIncluded = false
fun <T : Any> filter(t: T, componentGroupIndex: Int, internalIndex: Int) { fun <T : Any> filter(t: T, componentGroupIndex: Int, internalIndex: Int) {
if (filtering.test(t)) { if (filtering.test(t)) {
val group = filteredSerialisedComponents[componentGroupIndex] val group = filteredSerialisedComponents[componentGroupIndex]
// Because the filter passed, we know there is a match. We also use first vs single as the init function // Because the filter passed, we know there is a match. We also use first Vs single as the init function
// of WireTransaction ensures there are no duplicated groups. // of WireTransaction ensures there are no duplicated groups.
val serialisedComponent = wtx.componentGroups.first { it.groupIndex == componentGroupIndex }.components[internalIndex] val serialisedComponent = wtx.componentGroups.first { it.groupIndex == componentGroupIndex }.components[internalIndex]
if (group == null) { if (group == null) {
@ -132,6 +158,17 @@ class FilteredTransaction private constructor(
filteredComponentNonces[componentGroupIndex]!!.add(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex]) filteredComponentNonces[componentGroupIndex]!!.add(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex])
filteredComponentHashes[componentGroupIndex]!!.add(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex]) filteredComponentHashes[componentGroupIndex]!!.add(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex])
} }
// If at least one command is visible, then all command-signers should be visible as well.
// This is required for visibility purposes, see FilteredTransaction.checkAllCommandsVisible() for more details.
if (componentGroupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal && !signersIncluded) {
signersIncluded = true
val signersGroupIndex = ComponentGroupEnum.SIGNERS_GROUP.ordinal
// There exist commands, thus the signers group is not empty.
val signersGroupComponents = wtx.componentGroups.first { it.groupIndex == signersGroupIndex }
filteredSerialisedComponents.put(signersGroupIndex, signersGroupComponents.components.toMutableList())
filteredComponentNonces.put(signersGroupIndex, wtx.availableComponentNonces[signersGroupIndex]!!.toMutableList())
filteredComponentHashes.put(signersGroupIndex, wtx.availableComponentHashes[signersGroupIndex]!!.toMutableList())
}
} }
} }
@ -142,6 +179,10 @@ class FilteredTransaction private constructor(
wtx.attachments.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.ATTACHMENTS_GROUP.ordinal, internalIndex) } wtx.attachments.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.ATTACHMENTS_GROUP.ordinal, internalIndex) }
if (wtx.notary != null) filter(wtx.notary, ComponentGroupEnum.NOTARY_GROUP.ordinal, 0) if (wtx.notary != null) filter(wtx.notary, ComponentGroupEnum.NOTARY_GROUP.ordinal, 0)
if (wtx.timeWindow != null) filter(wtx.timeWindow, ComponentGroupEnum.TIMEWINDOW_GROUP.ordinal, 0) if (wtx.timeWindow != null) filter(wtx.timeWindow, ComponentGroupEnum.TIMEWINDOW_GROUP.ordinal, 0)
// It is highlighted that because there is no a signers property in TraversableTransaction,
// one cannot specifically filter them in or out.
// The above is very important to ensure someone won't filter out the signers component group if at least one
// command is included in a FilteredTransaction.
// It's sometimes possible that when we receive a WireTransaction for which there is a new or more unknown component groups, // It's sometimes possible that when we receive a WireTransaction for which there is a new or more unknown component groups,
// we decide to filter and attach this field to a FilteredTransaction. // we decide to filter and attach this field to a FilteredTransaction.
@ -207,7 +248,9 @@ class FilteredTransaction private constructor(
/** /**
* Function that checks if all of the components in a particular group are visible. * Function that checks if all of the components in a particular group are visible.
* This functionality is required on non-Validating Notaries to check that all inputs are visible. * This functionality is required on non-Validating Notaries to check that all inputs are visible.
* It might also be applied in Oracles, where an Oracle should know it can see all commands. * It might also be applied in Oracles or any other entity requiring [Command] visibility, but because this method
* cannot distinguish between related and unrelated to the signer [Command]s, one should use the
* [checkCommandVisibility] method, which is specifically designed for [Command] visibility purposes.
* The logic behind this algorithm is that we check that the root of the provided group partialMerkleTree matches with the * The logic behind this algorithm is that we check that the root of the provided group partialMerkleTree matches with the
* root of a fullMerkleTree if computed using all visible components. * root of a fullMerkleTree if computed using all visible components.
* Note that this method is usually called after or before [verify], to also ensure that the provided partial Merkle * Note that this method is usually called after or before [verify], to also ensure that the provided partial Merkle
@ -229,18 +272,54 @@ class FilteredTransaction private constructor(
visibilityCheck(group.groupIndex < groupHashes.size) { "There is no matching component group hash for group ${group.groupIndex}" } visibilityCheck(group.groupIndex < groupHashes.size) { "There is no matching component group hash for group ${group.groupIndex}" }
val groupPartialRoot = groupHashes[group.groupIndex] val groupPartialRoot = groupHashes[group.groupIndex]
val groupFullRoot = MerkleTree.getMerkleTree(group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) }).hash val groupFullRoot = MerkleTree.getMerkleTree(group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) }).hash
visibilityCheck(groupPartialRoot == groupFullRoot) { "The partial Merkle tree root does not match with the received root for group ${group.groupIndex}" } visibilityCheck(groupPartialRoot == groupFullRoot) { "Some components for group ${group.groupIndex} are not visible" }
// Verify the top level Merkle tree from groupHashes.
visibilityCheck(MerkleTree.getMerkleTree(groupHashes).hash == id) { "Transaction is malformed. Top level Merkle tree cannot be verified against transaction's id" }
} }
} }
inline private fun verificationCheck(value: Boolean, lazyMessage: () -> Any): Unit { /**
* Function that checks if all of the commands that should be signed by the input public key are visible.
* This functionality is required from Oracles to check that all of the commands they should sign are visible.
* This algorithm uses the [ComponentGroupEnum.SIGNERS_GROUP] to count how many commands should be signed by the
* input [PublicKey] and it then matches it with the size of received [commands].
* Note that this method does not throw if there are no commands for this key to sign in the original [WireTransaction].
* @param publicKey signer's [PublicKey]
* @throws ComponentVisibilityException if not all of the related commands are visible.
*/
@Throws(ComponentVisibilityException::class)
fun checkCommandVisibility(publicKey: PublicKey) {
val commandSigners = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.SIGNERS_GROUP.ordinal }
val expectedNumOfCommands = expectedNumOfCommands(publicKey, commandSigners)
val receivedForThisKeyNumOfCommands = commands.filter { publicKey in it.signers }.size
visibilityCheck(expectedNumOfCommands == receivedForThisKeyNumOfCommands) { "$expectedNumOfCommands commands were expected, but received $receivedForThisKeyNumOfCommands" }
}
// Function to return number of expected commands to sign.
private fun expectedNumOfCommands(publicKey: PublicKey, commandSigners: ComponentGroup?): Int {
checkAllComponentsVisible(ComponentGroupEnum.SIGNERS_GROUP)
if (commandSigners == null) return 0
fun signersKeys (internalIndex: Int, opaqueBytes: OpaqueBytes): List<PublicKey> {
try {
return SerializedBytes<List<PublicKey>>(opaqueBytes.bytes).deserialize()
} catch (e: Exception) {
throw Exception("Malformed transaction, signers at index $internalIndex cannot be deserialised", e)
}
}
return commandSigners.components
.mapIndexed { internalIndex, opaqueBytes -> signersKeys(internalIndex, opaqueBytes) }
.filter { signers -> publicKey in signers }.size
}
inline private fun verificationCheck(value: Boolean, lazyMessage: () -> Any) {
if (!value) { if (!value) {
val message = lazyMessage() val message = lazyMessage()
throw FilteredTransactionVerificationException(id, message.toString()) throw FilteredTransactionVerificationException(id, message.toString())
} }
} }
inline private fun visibilityCheck(value: Boolean, lazyMessage: () -> Any): Unit { inline private fun visibilityCheck(value: Boolean, lazyMessage: () -> Any) {
if (!value) { if (!value) {
val message = lazyMessage() val message = lazyMessage()
throw ComponentVisibilityException(id, message.toString()) throw ComponentVisibilityException(id, message.toString())

View File

@ -1,8 +1,6 @@
package net.corda.core.transactions package net.corda.core.transactions
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.NamedByHash import net.corda.core.contracts.NamedByHash
import net.corda.core.contracts.TransactionState
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.isFulfilledBy import net.corda.core.crypto.isFulfilledBy
import net.corda.core.transactions.SignedTransaction.SignaturesMissingException import net.corda.core.transactions.SignedTransaction.SignaturesMissingException

View File

@ -6,9 +6,10 @@ import net.corda.core.crypto.*
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.Emoji import net.corda.core.internal.Emoji
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.*
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.serialize
import net.corda.core.utilities.OpaqueBytes
import java.security.PublicKey import java.security.PublicKey
import java.security.SignatureException import java.security.SignatureException
import java.util.function.Predicate import java.util.function.Predicate
@ -213,10 +214,14 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
val componentGroupMap: MutableList<ComponentGroup> = mutableListOf() val componentGroupMap: MutableList<ComponentGroup> = mutableListOf()
if (inputs.isNotEmpty()) componentGroupMap.add(ComponentGroup(INPUTS_GROUP.ordinal, inputs.map { it.serialize() })) if (inputs.isNotEmpty()) componentGroupMap.add(ComponentGroup(INPUTS_GROUP.ordinal, inputs.map { it.serialize() }))
if (outputs.isNotEmpty()) componentGroupMap.add(ComponentGroup(OUTPUTS_GROUP.ordinal, outputs.map { it.serialize() })) if (outputs.isNotEmpty()) componentGroupMap.add(ComponentGroup(OUTPUTS_GROUP.ordinal, outputs.map { it.serialize() }))
if (commands.isNotEmpty()) componentGroupMap.add(ComponentGroup(COMMANDS_GROUP.ordinal, commands.map { it.serialize() })) // Adding commandData only to the commands group. Signers are added in their own group.
if (commands.isNotEmpty()) componentGroupMap.add(ComponentGroup(COMMANDS_GROUP.ordinal, commands.map { it.value.serialize() }))
if (attachments.isNotEmpty()) componentGroupMap.add(ComponentGroup(ATTACHMENTS_GROUP.ordinal, attachments.map { it.serialize() })) if (attachments.isNotEmpty()) componentGroupMap.add(ComponentGroup(ATTACHMENTS_GROUP.ordinal, attachments.map { it.serialize() }))
if (notary != null) componentGroupMap.add(ComponentGroup(NOTARY_GROUP.ordinal, listOf(notary.serialize()))) if (notary != null) componentGroupMap.add(ComponentGroup(NOTARY_GROUP.ordinal, listOf(notary.serialize())))
if (timeWindow != null) componentGroupMap.add(ComponentGroup(TIMEWINDOW_GROUP.ordinal, listOf(timeWindow.serialize()))) if (timeWindow != null) componentGroupMap.add(ComponentGroup(TIMEWINDOW_GROUP.ordinal, listOf(timeWindow.serialize())))
// Adding signers to their own group. This is required for command visibility purposes: a party receiving
// a FilteredTransaction can now verify it sees all the commands it should sign.
if (commands.isNotEmpty()) componentGroupMap.add(ComponentGroup(SIGNERS_GROUP.ordinal, commands.map { it.signers.serialize() }))
return componentGroupMap return componentGroupMap
} }
} }

View File

@ -118,8 +118,11 @@ sealed class ByteSequence : Comparable<ByteSequence> {
* In an ideal JVM this would be a value type and be completely overhead free. Project Valhalla is adding such * In an ideal JVM this would be a value type and be completely overhead free. Project Valhalla is adding such
* functionality to Java, but it won't arrive for a few years yet! * functionality to Java, but it won't arrive for a few years yet!
*/ */
open class OpaqueBytes(override val bytes: ByteArray) : ByteSequence() { open class OpaqueBytes(bytes: ByteArray) : ByteSequence() {
companion object { companion object {
/**
* Create [OpaqueBytes] from a sequence of [Byte] values.
*/
@JvmStatic @JvmStatic
fun of(vararg b: Byte) = OpaqueBytes(byteArrayOf(*b)) fun of(vararg b: Byte) = OpaqueBytes(byteArrayOf(*b))
} }
@ -128,13 +131,35 @@ open class OpaqueBytes(override val bytes: ByteArray) : ByteSequence() {
require(bytes.isNotEmpty()) require(bytes.isNotEmpty())
} }
override val size: Int get() = bytes.size /**
override val offset: Int get() = 0 * The bytes are always cloned so that this object becomes immutable. This has been done
* to prevent tampering with entities such as [SecureHash] and [PrivacySalt], as well as
* preserve the integrity of our hash constants [zeroHash] and [allOnesHash].
*
* Cloning like this may become a performance issue, depending on whether or not the JIT
* compiler is ever able to optimise away the clone. In which case we may need to revisit
* this later.
*/
override final val bytes: ByteArray = bytes
get() = field.clone()
override val size: Int = bytes.size
override val offset: Int = 0
} }
/**
* Copy [size] bytes from this [ByteArray] starting from [offset] into a new [ByteArray].
*/
fun ByteArray.sequence(offset: Int = 0, size: Int = this.size) = ByteSequence.of(this, offset, size) fun ByteArray.sequence(offset: Int = 0, size: Int = this.size) = ByteSequence.of(this, offset, size)
/**
* Converts this [ByteArray] into a [String] of hexadecimal digits.
*/
fun ByteArray.toHexString(): String = DatatypeConverter.printHexBinary(this) fun ByteArray.toHexString(): String = DatatypeConverter.printHexBinary(this)
/**
* Converts this [String] of hexadecimal digits into a [ByteArray].
* @throws IllegalArgumentException if the [String] contains incorrectly-encoded characters.
*/
fun String.parseAsHex(): ByteArray = DatatypeConverter.parseHexBinary(this) fun String.parseAsHex(): ByteArray = DatatypeConverter.parseHexBinary(this)
/** /**

View File

@ -3,9 +3,8 @@
package net.corda.core.utilities package net.corda.core.utilities
import net.corda.core.crypto.Base58 import net.corda.core.crypto.Base58
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import java.nio.charset.Charset import java.nio.charset.Charset
import java.security.PublicKey import java.security.PublicKey
import java.util.* import java.util.*
@ -15,11 +14,13 @@ import javax.xml.bind.DatatypeConverter
// [ByteArray] encoders // [ByteArray] encoders
/** Convert a byte array to a Base58 encoded [String]. */
fun ByteArray.toBase58(): String = Base58.encode(this) fun ByteArray.toBase58(): String = Base58.encode(this)
/** Convert a byte array to a Base64 encoded [String]. */
fun ByteArray.toBase64(): String = Base64.getEncoder().encodeToString(this) fun ByteArray.toBase64(): String = Base64.getEncoder().encodeToString(this)
/** Convert a byte array to a hex (base 16) capitalized encoded string.*/ /** Convert a byte array to a hex (Base16) capitalized encoded [String]. */
fun ByteArray.toHex(): String = DatatypeConverter.printHexBinary(this) fun ByteArray.toHex(): String = DatatypeConverter.printHexBinary(this)
@ -65,7 +66,15 @@ fun String.hexToBase64(): String = hexToByteArray().toBase64()
// TODO We use for both CompositeKeys and EdDSAPublicKey custom serializers and deserializers. We need to specify encoding. // TODO We use for both CompositeKeys and EdDSAPublicKey custom serializers and deserializers. We need to specify encoding.
// TODO: follow the crypto-conditions ASN.1 spec, some changes are needed to be compatible with the condition // TODO: follow the crypto-conditions ASN.1 spec, some changes are needed to be compatible with the condition
// structure, e.g. mapping a PublicKey to a condition with the specific feature (ED25519). // structure, e.g. mapping a PublicKey to a condition with the specific feature (ED25519).
fun parsePublicKeyBase58(base58String: String): PublicKey = base58String.base58ToByteArray().deserialize<PublicKey>() /**
* Method to return the [PublicKey] object given its Base58-[String] representation.
* @param base58String the Base58 encoded format of the serialised [PublicKey].
* @return the resulted [PublicKey] after decoding the [base58String] input and then deserialising to a [PublicKey] object.
*/
fun parsePublicKeyBase58(base58String: String): PublicKey = Crypto.decodePublicKey(base58String.base58ToByteArray())
fun PublicKey.toBase58String(): String = this.serialize().bytes.toBase58() /** Return the Base58 representation of the serialised public key. */
fun PublicKey.toSHA256Bytes(): ByteArray = this.serialize().bytes.sha256().bytes // TODO: decide on the format of hashed key (encoded Vs serialised). fun PublicKey.toBase58String(): String = this.encoded.toBase58()
/** Return the bytes of the SHA-256 output for this public key. */
fun PublicKey.toSHA256Bytes(): ByteArray = this.encoded.sha256().bytes

View File

@ -1,5 +1,3 @@
@file:JvmName("KotlinUtils")
package net.corda.core.utilities package net.corda.core.utilities
import net.corda.core.internal.concurrent.get import net.corda.core.internal.concurrent.get

View File

@ -1,13 +1,9 @@
package net.corda.core.contracts package net.corda.core.contracts
import net.corda.core.contracts.ComponentGroupEnum.* import net.corda.core.contracts.ComponentGroupEnum.*
import net.corda.core.crypto.MerkleTree import net.corda.core.crypto.*
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.secureRandomBytes
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.transactions.ComponentGroup import net.corda.core.transactions.*
import net.corda.core.transactions.ComponentVisibilityException
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
@ -34,22 +30,24 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() {
private val inputGroup by lazy { ComponentGroup(INPUTS_GROUP.ordinal, inputs.map { it.serialize() }) } private val inputGroup by lazy { ComponentGroup(INPUTS_GROUP.ordinal, inputs.map { it.serialize() }) }
private val outputGroup by lazy { ComponentGroup(OUTPUTS_GROUP.ordinal, outputs.map { it.serialize() }) } private val outputGroup by lazy { ComponentGroup(OUTPUTS_GROUP.ordinal, outputs.map { it.serialize() }) }
private val commandGroup by lazy { ComponentGroup(COMMANDS_GROUP.ordinal, commands.map { it.serialize() }) } private val commandGroup by lazy { ComponentGroup(COMMANDS_GROUP.ordinal, commands.map { it.value.serialize() }) }
private val attachmentGroup by lazy { ComponentGroup(ATTACHMENTS_GROUP.ordinal, attachments.map { it.serialize() }) } // The list is empty. private val attachmentGroup by lazy { ComponentGroup(ATTACHMENTS_GROUP.ordinal, attachments.map { it.serialize() }) } // The list is empty.
private val notaryGroup by lazy { ComponentGroup(NOTARY_GROUP.ordinal, listOf(notary.serialize())) } private val notaryGroup by lazy { ComponentGroup(NOTARY_GROUP.ordinal, listOf(notary.serialize())) }
private val timeWindowGroup by lazy { ComponentGroup(TIMEWINDOW_GROUP.ordinal, listOf(timeWindow.serialize())) } private val timeWindowGroup by lazy { ComponentGroup(TIMEWINDOW_GROUP.ordinal, listOf(timeWindow.serialize())) }
private val signersGroup by lazy { ComponentGroup(SIGNERS_GROUP.ordinal, commands.map { it.signers.serialize() }) }
private val newUnknownComponentGroup = ComponentGroup(20, listOf(OpaqueBytes(secureRandomBytes(4)), OpaqueBytes(secureRandomBytes(8)))) private val newUnknownComponentGroup = ComponentGroup(100, listOf(OpaqueBytes(secureRandomBytes(4)), OpaqueBytes(secureRandomBytes(8))))
private val newUnknownComponentEmptyGroup = ComponentGroup(21, emptyList()) private val newUnknownComponentEmptyGroup = ComponentGroup(101, emptyList())
// Do not add attachments (empty list). // Do not add attachments (empty list).
private val componentGroupsA by lazy { private val componentGroupsA by lazy {
listOf( listOf(
inputGroup, inputGroup,
outputGroup, outputGroup,
commandGroup, commandGroup,
notaryGroup, notaryGroup,
timeWindowGroup timeWindowGroup,
signersGroup
) )
} }
private val wireTransactionA by lazy { WireTransaction(componentGroups = componentGroupsA, privacySalt = privacySalt) } private val wireTransactionA by lazy { WireTransaction(componentGroups = componentGroupsA, privacySalt = privacySalt) }
@ -74,7 +72,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() {
commandGroup, commandGroup,
attachmentGroup, attachmentGroup,
notaryGroup, notaryGroup,
timeWindowGroup timeWindowGroup,
signersGroup
) )
assertFails { WireTransaction(componentGroups = componentGroupsEmptyAttachment, privacySalt = privacySalt) } assertFails { WireTransaction(componentGroups = componentGroupsEmptyAttachment, privacySalt = privacySalt) }
@ -86,7 +85,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() {
outputGroup, outputGroup,
commandGroup, commandGroup,
notaryGroup, notaryGroup,
timeWindowGroup timeWindowGroup,
signersGroup
) )
val wireTransaction1ShuffledInputs = WireTransaction(componentGroups = componentGroupsB, privacySalt = privacySalt) val wireTransaction1ShuffledInputs = WireTransaction(componentGroups = componentGroupsB, privacySalt = privacySalt)
// The ID has changed due to change of the internal ordering in inputs. // The ID has changed due to change of the internal ordering in inputs.
@ -106,7 +106,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() {
inputGroup, inputGroup,
commandGroup, commandGroup,
notaryGroup, notaryGroup,
timeWindowGroup timeWindowGroup,
signersGroup
) )
assertEquals(wireTransactionA, WireTransaction(componentGroups = shuffledComponentGroupsA, privacySalt = privacySalt)) assertEquals(wireTransactionA, WireTransaction(componentGroups = shuffledComponentGroupsA, privacySalt = privacySalt))
} }
@ -123,7 +124,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() {
commandGroup, commandGroup,
ComponentGroup(ATTACHMENTS_GROUP.ordinal, inputGroup.components), ComponentGroup(ATTACHMENTS_GROUP.ordinal, inputGroup.components),
notaryGroup, notaryGroup,
timeWindowGroup timeWindowGroup,
signersGroup
) )
assertFails { WireTransaction(componentGroupsB, privacySalt) } assertFails { WireTransaction(componentGroupsB, privacySalt) }
@ -134,7 +136,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() {
commandGroup, // First commandsGroup. commandGroup, // First commandsGroup.
commandGroup, // Second commandsGroup. commandGroup, // Second commandsGroup.
notaryGroup, notaryGroup,
timeWindowGroup timeWindowGroup,
signersGroup
) )
assertFails { WireTransaction(componentGroupsDuplicatedCommands, privacySalt) } assertFails { WireTransaction(componentGroupsDuplicatedCommands, privacySalt) }
@ -144,7 +147,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() {
outputGroup, outputGroup,
commandGroup, commandGroup,
notaryGroup, notaryGroup,
timeWindowGroup timeWindowGroup,
signersGroup
) )
assertFails { WireTransaction(componentGroupsC, privacySalt) } assertFails { WireTransaction(componentGroupsC, privacySalt) }
@ -154,23 +158,24 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() {
commandGroup, commandGroup,
notaryGroup, notaryGroup,
timeWindowGroup, timeWindowGroup,
newUnknownComponentGroup // A new unknown component with ordinal 20 that we cannot process. signersGroup,
newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process.
) )
// The old client (receiving more component types than expected) is still compatible. // The old client (receiving more component types than expected) is still compatible.
val wireTransactionCompatibleA = WireTransaction(componentGroupsCompatibleA, privacySalt) val wireTransactionCompatibleA = WireTransaction(componentGroupsCompatibleA, privacySalt)
assertEquals(wireTransactionCompatibleA.availableComponentGroups, wireTransactionA.availableComponentGroups) // The known components are the same. assertEquals(wireTransactionCompatibleA.availableComponentGroups, wireTransactionA.availableComponentGroups) // The known components are the same.
assertNotEquals(wireTransactionCompatibleA, wireTransactionA) // But obviously, its Merkle root has changed Vs wireTransactionA (which doesn't include this extra component). assertNotEquals(wireTransactionCompatibleA, wireTransactionA) // But obviously, its Merkle root has changed Vs wireTransactionA (which doesn't include this extra component).
assertEquals(6, wireTransactionCompatibleA.componentGroups.size)
// The old client will trhow if receiving an empty component (even if this unknown). // The old client will throw if receiving an empty component (even if this is unknown).
val componentGroupsCompatibleEmptyNew = listOf( val componentGroupsCompatibleEmptyNew = listOf(
inputGroup, inputGroup,
outputGroup, outputGroup,
commandGroup, commandGroup,
notaryGroup, notaryGroup,
timeWindowGroup, timeWindowGroup,
newUnknownComponentEmptyGroup // A new unknown component with ordinal 21 that we cannot process. signersGroup,
newUnknownComponentEmptyGroup // A new unknown component with ordinal 101 that we cannot process.
) )
assertFails { WireTransaction(componentGroupsCompatibleEmptyNew, privacySalt) } assertFails { WireTransaction(componentGroupsCompatibleEmptyNew, privacySalt) }
} }
@ -179,7 +184,9 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() {
fun `FilteredTransaction constructors and compatibility`() { fun `FilteredTransaction constructors and compatibility`() {
// Filter out all of the components. // Filter out all of the components.
val ftxNothing = wireTransactionA.buildFilteredTransaction(Predicate { false }) // Nothing filtered. val ftxNothing = wireTransactionA.buildFilteredTransaction(Predicate { false }) // Nothing filtered.
assertEquals(6, ftxNothing.groupHashes.size) // Although nothing filtered, we still receive the group hashes for the top level Merkle tree. // Although nothing filtered, we still receive the group hashes for the top level Merkle tree.
// Note that attachments are not sent, but group hashes include the allOnesHash flag for the attachment group hash; that's why we expect +1 group hashes.
assertEquals(wireTransactionA.componentGroups.size + 1, ftxNothing.groupHashes.size)
ftxNothing.verify() ftxNothing.verify()
// Include all of the components. // Include all of the components.
@ -191,6 +198,7 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() {
ftxAll.checkAllComponentsVisible(ATTACHMENTS_GROUP) ftxAll.checkAllComponentsVisible(ATTACHMENTS_GROUP)
ftxAll.checkAllComponentsVisible(NOTARY_GROUP) ftxAll.checkAllComponentsVisible(NOTARY_GROUP)
ftxAll.checkAllComponentsVisible(TIMEWINDOW_GROUP) ftxAll.checkAllComponentsVisible(TIMEWINDOW_GROUP)
ftxAll.checkAllComponentsVisible(SIGNERS_GROUP)
// Filter inputs only. // Filter inputs only.
fun filtering(elem: Any): Boolean { fun filtering(elem: Any): Boolean {
@ -222,12 +230,14 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() {
assertNotNull(ftxOneInput.filteredComponentGroups.firstOrNull { it.groupIndex == INPUTS_GROUP.ordinal }!!.partialMerkleTree) // And the Merkle tree. assertNotNull(ftxOneInput.filteredComponentGroups.firstOrNull { it.groupIndex == INPUTS_GROUP.ordinal }!!.partialMerkleTree) // And the Merkle tree.
// The old client (receiving more component types than expected) is still compatible. // The old client (receiving more component types than expected) is still compatible.
val componentGroupsCompatibleA = listOf(inputGroup, val componentGroupsCompatibleA = listOf(
inputGroup,
outputGroup, outputGroup,
commandGroup, commandGroup,
notaryGroup, notaryGroup,
timeWindowGroup, timeWindowGroup,
newUnknownComponentGroup // A new unknown component with ordinal 10,000 that we cannot process. signersGroup,
newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process.
) )
val wireTransactionCompatibleA = WireTransaction(componentGroupsCompatibleA, privacySalt) val wireTransactionCompatibleA = WireTransaction(componentGroupsCompatibleA, privacySalt)
val ftxCompatible = wireTransactionCompatibleA.buildFilteredTransaction(Predicate(::filtering)) val ftxCompatible = wireTransactionCompatibleA.buildFilteredTransaction(Predicate(::filtering))
@ -245,9 +255,288 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() {
ftxCompatibleAll.verify() ftxCompatibleAll.verify()
assertEquals(wireTransactionCompatibleA.id, ftxCompatibleAll.id) assertEquals(wireTransactionCompatibleA.id, ftxCompatibleAll.id)
// Check we received the last (6th) element that we cannot process (backwards compatibility). // Check we received the last element that we cannot process (backwards compatibility).
assertEquals(6, ftxCompatibleAll.filteredComponentGroups.size) assertEquals(wireTransactionCompatibleA.componentGroups.size, ftxCompatibleAll.filteredComponentGroups.size)
// Hide one component group only.
// Filter inputs only.
fun filterOutInputs(elem: Any): Boolean {
return when (elem) {
is StateRef -> false
else -> true
}
}
val ftxCompatibleNoInputs = wireTransactionCompatibleA.buildFilteredTransaction(Predicate(::filterOutInputs))
ftxCompatibleNoInputs.verify()
assertFailsWith<ComponentVisibilityException> { ftxCompatibleNoInputs.checkAllComponentsVisible(INPUTS_GROUP) }
assertEquals(wireTransactionCompatibleA.componentGroups.size - 1, ftxCompatibleNoInputs.filteredComponentGroups.size)
assertEquals(wireTransactionCompatibleA.componentGroups.map { it.groupIndex }.max()!!, ftxCompatibleNoInputs.groupHashes.size - 1)
}
@Test
fun `Command visibility tests`() {
// 1st and 3rd commands require a signature from KEY_1.
val twoCommandsforKey1 = listOf(dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_1.public))
val componentGroups = listOf(
inputGroup,
outputGroup,
ComponentGroup(COMMANDS_GROUP.ordinal, twoCommandsforKey1.map { it.value.serialize() }),
notaryGroup,
timeWindowGroup,
ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() }),
newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process.
)
val wtx = WireTransaction(componentGroups = componentGroups, privacySalt = PrivacySalt())
// Filter all commands.
fun filterCommandsOnly(elem: Any): Boolean {
return when (elem) {
is Command<*> -> true // Even if one Command is filtered, all signers are automatically filtered as well
else -> false
}
}
// Filter out commands only.
fun filterOutCommands(elem: Any): Boolean {
return when (elem) {
is Command<*> -> false
else -> true
}
}
// Filter KEY_1 commands.
fun filterKEY1Commands(elem: Any): Boolean {
return when (elem) {
is Command<*> -> DUMMY_KEY_1.public in elem.signers
else -> false
}
}
// Filter only one KEY_1 command.
fun filterTwoSignersCommands(elem: Any): Boolean {
return when (elem) {
is Command<*> -> elem.signers.size == 2 // dummyCommand(DUMMY_KEY_1.public) is filtered out.
else -> false
}
}
// Again filter only one KEY_1 command.
fun filterSingleSignersCommands(elem: Any): Boolean {
return when (elem) {
is Command<*> -> elem.signers.size == 1 // dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public) is filtered out.
else -> false
}
}
val allCommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterCommandsOnly))
val noCommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterOutCommands))
val key1CommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterKEY1Commands))
val oneKey1CommandFtxA = wtx.buildFilteredTransaction(Predicate(::filterTwoSignersCommands))
val oneKey1CommandFtxB = wtx.buildFilteredTransaction(Predicate(::filterSingleSignersCommands))
allCommandsFtx.checkCommandVisibility(DUMMY_KEY_1.public)
assertFailsWith<ComponentVisibilityException> { noCommandsFtx.checkCommandVisibility(DUMMY_KEY_1.public) }
key1CommandsFtx.checkCommandVisibility(DUMMY_KEY_1.public)
assertFailsWith<ComponentVisibilityException> { oneKey1CommandFtxA.checkCommandVisibility(DUMMY_KEY_1.public) }
assertFailsWith<ComponentVisibilityException> { oneKey1CommandFtxB.checkCommandVisibility(DUMMY_KEY_1.public) }
allCommandsFtx.checkAllComponentsVisible(SIGNERS_GROUP)
assertFailsWith<ComponentVisibilityException> { noCommandsFtx.checkAllComponentsVisible(SIGNERS_GROUP) } // If we filter out all commands, signers are not sent as well.
key1CommandsFtx.checkAllComponentsVisible(SIGNERS_GROUP) // If at least one Command is visible, then all Signers are visible.
oneKey1CommandFtxA.checkAllComponentsVisible(SIGNERS_GROUP) // If at least one Command is visible, then all Signers are visible.
oneKey1CommandFtxB.checkAllComponentsVisible(SIGNERS_GROUP) // If at least one Command is visible, then all Signers are visible.
// We don't send a list of signers.
val componentGroupsCompatible = listOf(
inputGroup,
outputGroup,
ComponentGroup(COMMANDS_GROUP.ordinal, twoCommandsforKey1.map { it.value.serialize() }),
notaryGroup,
timeWindowGroup,
// ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() }),
newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process.
)
// Invalid Transaction. Sizes of CommandData and Signers (empty) do not match.
assertFailsWith<IllegalStateException> { WireTransaction(componentGroups = componentGroupsCompatible, privacySalt = PrivacySalt()) }
// We send smaller list of signers.
val componentGroupsLessSigners = listOf(
inputGroup,
outputGroup,
ComponentGroup(COMMANDS_GROUP.ordinal, twoCommandsforKey1.map { it.value.serialize() }),
notaryGroup,
timeWindowGroup,
ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() }.subList(0, 1)), // Send first signer only.
newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process.
)
// Invalid Transaction. Sizes of CommandData and Signers (empty) do not match.
assertFailsWith<IllegalStateException> { WireTransaction(componentGroups = componentGroupsLessSigners, privacySalt = PrivacySalt()) }
// Test if there is no command to sign.
val commandsNoKey1= listOf(dummyCommand(DUMMY_KEY_2.public))
val componentGroupsNoKey1ToSign = listOf(
inputGroup,
outputGroup,
ComponentGroup(COMMANDS_GROUP.ordinal, commandsNoKey1.map { it.value.serialize() }),
notaryGroup,
timeWindowGroup,
ComponentGroup(SIGNERS_GROUP.ordinal, commandsNoKey1.map { it.signers.serialize() }),
newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process.
)
val wtxNoKey1 = WireTransaction(componentGroups = componentGroupsNoKey1ToSign, privacySalt = PrivacySalt())
val allCommandsNoKey1Ftx= wtxNoKey1.buildFilteredTransaction(Predicate(::filterCommandsOnly))
allCommandsNoKey1Ftx.checkCommandVisibility(DUMMY_KEY_1.public) // This will pass, because there are indeed no commands to sign in the original transaction.
}
@Test
fun `FilteredTransaction signer manipulation tests`() {
// Required to call the private constructor.
val ftxConstructor = FilteredTransaction::class.java.declaredConstructors[1]
ftxConstructor.isAccessible = true
// 1st and 3rd commands require a signature from KEY_1.
val twoCommandsforKey1 = listOf(dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_1.public))
val componentGroups = listOf(
inputGroup,
outputGroup,
ComponentGroup(COMMANDS_GROUP.ordinal, twoCommandsforKey1.map { it.value.serialize() }),
notaryGroup,
timeWindowGroup,
ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() })
)
val wtx = WireTransaction(componentGroups = componentGroups, privacySalt = PrivacySalt())
// Filter KEY_1 commands (commands 1 and 3).
fun filterKEY1Commands(elem: Any): Boolean {
return when (elem) {
is Command<*> -> DUMMY_KEY_1.public in elem.signers
else -> false
}
}
// Filter KEY_2 commands (commands 1 and 2).
fun filterKEY2Commands(elem: Any): Boolean {
return when (elem) {
is Command<*> -> DUMMY_KEY_2.public in elem.signers
else -> false
}
}
val key1CommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterKEY1Commands))
val key2CommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterKEY2Commands))
// val commandDataComponents = key1CommandsFtx.filteredComponentGroups[0].components
val commandDataHashes = wtx.availableComponentHashes[ComponentGroupEnum.COMMANDS_GROUP.ordinal]!!
val noLastCommandDataPMT = PartialMerkleTree.build(
MerkleTree.getMerkleTree(commandDataHashes),
commandDataHashes.subList(0, 1)
)
val noLastCommandDataComponents = key1CommandsFtx.filteredComponentGroups[0].components.subList(0, 1)
val noLastCommandDataNonces = key1CommandsFtx.filteredComponentGroups[0].nonces.subList(0, 1)
val noLastCommandDataGroup = FilteredComponentGroup(
ComponentGroupEnum.COMMANDS_GROUP.ordinal,
noLastCommandDataComponents,
noLastCommandDataNonces,
noLastCommandDataPMT
)
val signerComponents = key1CommandsFtx.filteredComponentGroups[1].components
val signerHashes = wtx.availableComponentHashes[ComponentGroupEnum.SIGNERS_GROUP.ordinal]!!
val noLastSignerPMT = PartialMerkleTree.build(
MerkleTree.getMerkleTree(signerHashes),
signerHashes.subList(0, 2)
)
val noLastSignerComponents = key1CommandsFtx.filteredComponentGroups[1].components.subList(0, 2)
val noLastSignerNonces = key1CommandsFtx.filteredComponentGroups[1].nonces.subList(0, 2)
val noLastSignerGroup = FilteredComponentGroup(
ComponentGroupEnum.SIGNERS_GROUP.ordinal,
noLastSignerComponents,
noLastSignerNonces,
noLastSignerPMT
)
val noLastSignerGroupSamePartialTree = FilteredComponentGroup(
ComponentGroupEnum.SIGNERS_GROUP.ordinal,
noLastSignerComponents,
noLastSignerNonces,
key1CommandsFtx.filteredComponentGroups[1].partialMerkleTree) // We don't update that, so we can catch the index mismatch.
val updatedFilteredComponentsNoSignersKey2 = listOf(key2CommandsFtx.filteredComponentGroups[0], noLastSignerGroup)
val updatedFilteredComponentsNoSignersKey2SamePMT = listOf(key2CommandsFtx.filteredComponentGroups[0], noLastSignerGroupSamePartialTree)
// There are only two components in key1CommandsFtx (commandData and signers).
assertEquals(2, key1CommandsFtx.componentGroups.size)
// Remove last signer for which there is a pointer from a visible commandData. This is the case of Key1.
// This will result to an invalid transaction.
// A command with no corresponding signer detected
// because the pointer of CommandData (3rd leaf) cannot find a corresponding (3rd) signer.
val updatedFilteredComponentsNoSignersKey1SamePMT = listOf(key1CommandsFtx.filteredComponentGroups[0], noLastSignerGroupSamePartialTree)
assertFails { ftxConstructor.newInstance(key1CommandsFtx.id, updatedFilteredComponentsNoSignersKey1SamePMT, key1CommandsFtx.groupHashes) }
// Remove both last signer (KEY1) and related command.
// Update partial Merkle tree for signers.
val updatedFilteredComponentsNoLastCommandAndSigners = listOf(noLastCommandDataGroup, noLastSignerGroup)
val ftxNoLastCommandAndSigners = ftxConstructor.newInstance(key1CommandsFtx.id, updatedFilteredComponentsNoLastCommandAndSigners, key1CommandsFtx.groupHashes) as FilteredTransaction
// verify() will pass as the transaction is well-formed.
ftxNoLastCommandAndSigners.verify()
// checkCommandVisibility() will not pass, because checkAllComponentsVisible(ComponentGroupEnum.SIGNERS_GROUP) will fail.
assertFailsWith<ComponentVisibilityException> { ftxNoLastCommandAndSigners.checkCommandVisibility(DUMMY_KEY_1.public) }
// Remove last signer for which there is no pointer from a visible commandData. This is the case of Key2.
// Do not change partial Merkle tree for signers.
// This time the object can be constructed as there is no pointer mismatch.
val ftxNoLastSigner = ftxConstructor.newInstance(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2SamePMT, key2CommandsFtx.groupHashes) as FilteredTransaction
// verify() will fail as we didn't change the partial Merkle tree.
assertFailsWith<FilteredTransactionVerificationException> { ftxNoLastSigner.verify() }
// checkCommandVisibility() will not pass.
assertFailsWith<ComponentVisibilityException> { ftxNoLastSigner.checkCommandVisibility(DUMMY_KEY_2.public) }
// Remove last signer for which there is no pointer from a visible commandData. This is the case of Key2.
// Update partial Merkle tree for signers.
val ftxNoLastSignerB = ftxConstructor.newInstance(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2, key2CommandsFtx.groupHashes) as FilteredTransaction
// verify() will pass, the transaction is well-formed.
ftxNoLastSignerB.verify()
// But, checkAllComponentsVisible() will not pass.
assertFailsWith<ComponentVisibilityException> { ftxNoLastSignerB.checkCommandVisibility(DUMMY_KEY_2.public) }
// Modify last signer (we have a pointer from commandData).
// Update partial Merkle tree for signers.
val alterSignerComponents = signerComponents.subList(0, 2) + signerComponents[1] // Third one is removed and the 2nd command is added twice.
val alterSignersHashes = wtx.availableComponentHashes[ComponentGroupEnum.SIGNERS_GROUP.ordinal]!!.subList(0, 2) + componentHash(key1CommandsFtx.filteredComponentGroups[1].nonces[2], alterSignerComponents[2])
val alterMTree = MerkleTree.getMerkleTree(alterSignersHashes)
val alterSignerPMTK = PartialMerkleTree.build(
alterMTree,
alterSignersHashes
)
val alterSignerGroup = FilteredComponentGroup(
ComponentGroupEnum.SIGNERS_GROUP.ordinal,
alterSignerComponents,
key1CommandsFtx.filteredComponentGroups[1].nonces,
alterSignerPMTK
)
val alterFilteredComponents = listOf(key1CommandsFtx.filteredComponentGroups[0], alterSignerGroup)
// Do not update groupHashes.
val ftxAlterSigner = ftxConstructor.newInstance(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes) as FilteredTransaction
// Visible components in signers group cannot be verified against their partial Merkle tree.
assertFailsWith<FilteredTransactionVerificationException> { ftxAlterSigner.verify() }
// Also, checkAllComponentsVisible() will not pass (groupHash matching will fail).
assertFailsWith<ComponentVisibilityException> { ftxAlterSigner.checkCommandVisibility(DUMMY_KEY_1.public) }
// Update groupHashes.
val ftxAlterSignerB = ftxConstructor.newInstance(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes.subList(0, 6) + alterMTree.hash) as FilteredTransaction
// Visible components in signers group cannot be verified against their partial Merkle tree.
assertFailsWith<FilteredTransactionVerificationException> { ftxAlterSignerB.verify() }
// Also, checkAllComponentsVisible() will not pass (top level Merkle tree cannot be verified against transaction's id).
assertFailsWith<ComponentVisibilityException> { ftxAlterSignerB.checkCommandVisibility(DUMMY_KEY_1.public) }
ftxConstructor.isAccessible = false
} }
} }

View File

@ -1,9 +1,12 @@
package net.corda.core.contracts package net.corda.core.contracts
import net.corda.core.internal.div
import net.corda.core.internal.times
import net.corda.core.utilities.millis import net.corda.core.utilities.millis
import net.corda.core.utilities.minutes import net.corda.core.utilities.minutes
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
import java.time.Duration
import java.time.Instant import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.time.ZoneOffset.UTC import java.time.ZoneOffset.UTC
@ -17,6 +20,7 @@ class TimeWindowTest {
assertThat(timeWindow.fromTime).isEqualTo(now) assertThat(timeWindow.fromTime).isEqualTo(now)
assertThat(timeWindow.untilTime).isNull() assertThat(timeWindow.untilTime).isNull()
assertThat(timeWindow.midpoint).isNull() assertThat(timeWindow.midpoint).isNull()
assertThat(timeWindow.length).isNull()
assertThat(timeWindow.contains(now - 1.millis)).isFalse() assertThat(timeWindow.contains(now - 1.millis)).isFalse()
assertThat(timeWindow.contains(now)).isTrue() assertThat(timeWindow.contains(now)).isTrue()
assertThat(timeWindow.contains(now + 1.millis)).isTrue() assertThat(timeWindow.contains(now + 1.millis)).isTrue()
@ -28,6 +32,7 @@ class TimeWindowTest {
assertThat(timeWindow.fromTime).isNull() assertThat(timeWindow.fromTime).isNull()
assertThat(timeWindow.untilTime).isEqualTo(now) assertThat(timeWindow.untilTime).isEqualTo(now)
assertThat(timeWindow.midpoint).isNull() assertThat(timeWindow.midpoint).isNull()
assertThat(timeWindow.length).isNull()
assertThat(timeWindow.contains(now - 1.millis)).isTrue() assertThat(timeWindow.contains(now - 1.millis)).isTrue()
assertThat(timeWindow.contains(now)).isFalse() assertThat(timeWindow.contains(now)).isFalse()
assertThat(timeWindow.contains(now + 1.millis)).isFalse() assertThat(timeWindow.contains(now + 1.millis)).isFalse()
@ -42,6 +47,7 @@ class TimeWindowTest {
assertThat(timeWindow.fromTime).isEqualTo(fromTime) assertThat(timeWindow.fromTime).isEqualTo(fromTime)
assertThat(timeWindow.untilTime).isEqualTo(untilTime) assertThat(timeWindow.untilTime).isEqualTo(untilTime)
assertThat(timeWindow.midpoint).isEqualTo(today.atTime(12, 15).toInstant(UTC)) assertThat(timeWindow.midpoint).isEqualTo(today.atTime(12, 15).toInstant(UTC))
assertThat(timeWindow.length).isEqualTo(Duration.between(fromTime, untilTime))
assertThat(timeWindow.contains(fromTime - 1.millis)).isFalse() assertThat(timeWindow.contains(fromTime - 1.millis)).isFalse()
assertThat(timeWindow.contains(fromTime)).isTrue() assertThat(timeWindow.contains(fromTime)).isTrue()
assertThat(timeWindow.contains(fromTime + 1.millis)).isTrue() assertThat(timeWindow.contains(fromTime + 1.millis)).isTrue()
@ -51,17 +57,21 @@ class TimeWindowTest {
@Test @Test
fun fromStartAndDuration() { fun fromStartAndDuration() {
val timeWindow = TimeWindow.fromStartAndDuration(now, 10.minutes) val duration = 10.minutes
val timeWindow = TimeWindow.fromStartAndDuration(now, duration)
assertThat(timeWindow.fromTime).isEqualTo(now) assertThat(timeWindow.fromTime).isEqualTo(now)
assertThat(timeWindow.untilTime).isEqualTo(now + 10.minutes) assertThat(timeWindow.untilTime).isEqualTo(now + duration)
assertThat(timeWindow.midpoint).isEqualTo(now + 5.minutes) assertThat(timeWindow.midpoint).isEqualTo(now + duration / 2)
assertThat(timeWindow.length).isEqualTo(duration)
} }
@Test @Test
fun withTolerance() { fun withTolerance() {
val timeWindow = TimeWindow.withTolerance(now, 10.minutes) val tolerance = 10.minutes
assertThat(timeWindow.fromTime).isEqualTo(now - 10.minutes) val timeWindow = TimeWindow.withTolerance(now, tolerance)
assertThat(timeWindow.untilTime).isEqualTo(now + 10.minutes) assertThat(timeWindow.fromTime).isEqualTo(now - tolerance)
assertThat(timeWindow.untilTime).isEqualTo(now + tolerance)
assertThat(timeWindow.midpoint).isEqualTo(now) assertThat(timeWindow.midpoint).isEqualTo(now)
assertThat(timeWindow.length).isEqualTo(tolerance * 2)
} }
} }

View File

@ -1,6 +1,5 @@
package net.corda.core.crypto package net.corda.core.crypto
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash.Companion.zeroHash import net.corda.core.crypto.SecureHash.Companion.zeroHash
import net.corda.core.identity.Party import net.corda.core.identity.Party
@ -14,10 +13,12 @@ import net.corda.testing.*
import org.junit.Test import org.junit.Test
import java.security.PublicKey import java.security.PublicKey
import java.util.function.Predicate import java.util.function.Predicate
import java.util.stream.IntStream
import kotlin.streams.toList
import kotlin.test.* import kotlin.test.*
class PartialMerkleTreeTest : TestDependencyInjectionBase() { class PartialMerkleTreeTest : TestDependencyInjectionBase() {
val nodes = "abcdef" private val nodes = "abcdef"
private val hashed = nodes.map { private val hashed = nodes.map {
initialiseTestSerialization() initialiseTestSerialization()
try { try {
@ -115,16 +116,18 @@ class PartialMerkleTreeTest : TestDependencyInjectionBase() {
val d = testTx.serialize().deserialize() val d = testTx.serialize().deserialize()
assertEquals(testTx.id, d.id) assertEquals(testTx.id, d.id)
val mt = testTx.buildFilteredTransaction(Predicate(::filtering)) val ftx = testTx.buildFilteredTransaction(Predicate(::filtering))
assertEquals(4, mt.filteredComponentGroups.size) // We expect 5 and not 4 component groups, because there is at least one command in the ftx and thus,
assertEquals(1, mt.inputs.size) // the signers component is also sent (required for visibility purposes).
assertEquals(0, mt.attachments.size) assertEquals(5, ftx.filteredComponentGroups.size)
assertEquals(1, mt.outputs.size) assertEquals(1, ftx.inputs.size)
assertEquals(1, mt.commands.size) assertEquals(0, ftx.attachments.size)
assertNull(mt.notary) assertEquals(1, ftx.outputs.size)
assertNotNull(mt.timeWindow) assertEquals(1, ftx.commands.size)
mt.verify() assertNull(ftx.notary)
assertNotNull(ftx.timeWindow)
ftx.verify()
} }
@Test @Test
@ -246,4 +249,50 @@ class PartialMerkleTreeTest : TestDependencyInjectionBase() {
privacySalt = privacySalt privacySalt = privacySalt
) )
} }
@Test
fun `Find leaf index`() {
// A Merkle tree with 20 leaves.
val sampleLeaves = IntStream.rangeClosed(0, 19).toList().map { SecureHash.sha256(it.toString()) }
val merkleTree = MerkleTree.getMerkleTree(sampleLeaves)
// Provided hashes are not in the tree.
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("20"))) }
// One of the provided hashes is not in the tree.
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("20"), SecureHash.sha256("1"), SecureHash.sha256("5"))) }
val pmt = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("1"), SecureHash.sha256("5"), SecureHash.sha256("0"), SecureHash.sha256("19")))
// First leaf.
assertEquals(0, pmt.leafIndex(SecureHash.sha256("0")))
// Second leaf.
assertEquals(1, pmt.leafIndex(SecureHash.sha256("1")))
// A random leaf.
assertEquals(5, pmt.leafIndex(SecureHash.sha256("5")))
// The last leaf.
assertEquals(19, pmt.leafIndex(SecureHash.sha256("19")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmt.leafIndex(SecureHash.sha256("10")) }
// The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree).
assertFailsWith<MerkleTreeException> { pmt.leafIndex(SecureHash.sha256("30")) }
val pmtFirstElementOnly = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("0")))
assertEquals(0, pmtFirstElementOnly.leafIndex(SecureHash.sha256("0")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmtFirstElementOnly.leafIndex(SecureHash.sha256("10")) }
val pmtLastElementOnly = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("19")))
assertEquals(19, pmtLastElementOnly.leafIndex(SecureHash.sha256("19")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmtLastElementOnly.leafIndex(SecureHash.sha256("10")) }
val pmtOneElement = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("5")))
assertEquals(5, pmtOneElement.leafIndex(SecureHash.sha256("5")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmtOneElement.leafIndex(SecureHash.sha256("10")) }
val pmtAllIncluded = PartialMerkleTree.build(merkleTree, sampleLeaves)
for (i in 0..19) assertEquals(i, pmtAllIncluded.leafIndex(SecureHash.sha256(i.toString())))
// The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree).
assertFailsWith<MerkleTreeException> { pmtAllIncluded.leafIndex(SecureHash.sha256("30")) }
}
} }

View File

@ -12,12 +12,12 @@ import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.StartedNode import net.corda.node.internal.StartedNode
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.persistence.NodeAttachmentService import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.node.utilities.DatabaseTransactionManager
import net.corda.nodeapi.internal.ServiceInfo import net.corda.nodeapi.internal.ServiceInfo
import net.corda.testing.ALICE import net.corda.testing.ALICE
import net.corda.testing.ALICE_NAME
import net.corda.testing.BOB import net.corda.testing.BOB
import net.corda.testing.chooseIdentity
import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork
import net.corda.testing.singleIdentity
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -55,13 +55,13 @@ class AttachmentTests {
@Test @Test
fun `download and store`() { fun `download and store`() {
mockNet.createNotaryNode()
val aliceNode = mockNet.createPartyNode(ALICE.name) val aliceNode = mockNet.createPartyNode(ALICE.name)
val bobNode = mockNet.createPartyNode(BOB.name) val bobNode = mockNet.createPartyNode(BOB.name)
// Ensure that registration was successful before progressing any further // Ensure that registration was successful before progressing any further
mockNet.runNetwork() mockNet.runNetwork()
aliceNode.internals.ensureRegistered() aliceNode.internals.ensureRegistered()
val alice = aliceNode.info.singleIdentity()
aliceNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java) aliceNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
bobNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java) bobNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
@ -73,7 +73,7 @@ class AttachmentTests {
// Get node one to run a flow to fetch it and insert it. // Get node one to run a flow to fetch it and insert it.
mockNet.runNetwork() mockNet.runNetwork()
val bobFlow = bobNode.startAttachmentFlow(setOf(id), aliceNode.info.chooseIdentity()) val bobFlow = bobNode.startAttachmentFlow(setOf(id), alice)
mockNet.runNetwork() mockNet.runNetwork()
assertEquals(0, bobFlow.resultFuture.getOrThrow().fromDisk.size) assertEquals(0, bobFlow.resultFuture.getOrThrow().fromDisk.size)
@ -87,13 +87,12 @@ class AttachmentTests {
// Shut down node zero and ensure node one can still resolve the attachment. // Shut down node zero and ensure node one can still resolve the attachment.
aliceNode.dispose() aliceNode.dispose()
val response: FetchDataFlow.Result<Attachment> = bobNode.startAttachmentFlow(setOf(id), aliceNode.info.chooseIdentity()).resultFuture.getOrThrow() val response: FetchDataFlow.Result<Attachment> = bobNode.startAttachmentFlow(setOf(id), alice).resultFuture.getOrThrow()
assertEquals(attachment, response.fromDisk[0]) assertEquals(attachment, response.fromDisk[0])
} }
@Test @Test
fun `missing`() { fun `missing`() {
mockNet.createNotaryNode()
val aliceNode = mockNet.createPartyNode(ALICE.name) val aliceNode = mockNet.createPartyNode(ALICE.name)
val bobNode = mockNet.createPartyNode(BOB.name) val bobNode = mockNet.createPartyNode(BOB.name)
@ -107,7 +106,8 @@ class AttachmentTests {
// Get node one to fetch a non-existent attachment. // Get node one to fetch a non-existent attachment.
val hash = SecureHash.randomSHA256() val hash = SecureHash.randomSHA256()
mockNet.runNetwork() mockNet.runNetwork()
val bobFlow = bobNode.startAttachmentFlow(setOf(hash), aliceNode.info.chooseIdentity()) val alice = aliceNode.info.singleIdentity()
val bobFlow = bobNode.startAttachmentFlow(setOf(hash), alice)
mockNet.runNetwork() mockNet.runNetwork()
val e = assertFailsWith<FetchDataFlow.HashNotFound> { bobFlow.resultFuture.getOrThrow() } val e = assertFailsWith<FetchDataFlow.HashNotFound> { bobFlow.resultFuture.getOrThrow() }
assertEquals(hash, e.requested) assertEquals(hash, e.requested)
@ -130,6 +130,7 @@ class AttachmentTests {
// Ensure that registration was successful before progressing any further // Ensure that registration was successful before progressing any further
mockNet.runNetwork() mockNet.runNetwork()
aliceNode.internals.ensureRegistered() aliceNode.internals.ensureRegistered()
val alice = aliceNode.services.myInfo.identityFromX500Name(ALICE_NAME)
aliceNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java) aliceNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
bobNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java) bobNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
@ -146,12 +147,12 @@ class AttachmentTests {
val corruptAttachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = attachment) val corruptAttachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = attachment)
aliceNode.database.transaction { aliceNode.database.transaction {
DatabaseTransactionManager.current().session.update(corruptAttachment) session.update(corruptAttachment)
} }
// Get n1 to fetch the attachment. Should receive corrupted bytes. // Get n1 to fetch the attachment. Should receive corrupted bytes.
mockNet.runNetwork() mockNet.runNetwork()
val bobFlow = bobNode.startAttachmentFlow(setOf(id), aliceNode.info.chooseIdentity()) val bobFlow = bobNode.startAttachmentFlow(setOf(id), alice)
mockNet.runNetwork() mockNet.runNetwork()
assertFailsWith<FetchDataFlow.DownloadedVsRequestedDataMismatch> { bobFlow.resultFuture.getOrThrow() } assertFailsWith<FetchDataFlow.DownloadedVsRequestedDataMismatch> { bobFlow.resultFuture.getOrThrow() }
} }

View File

@ -23,29 +23,37 @@ import kotlin.reflect.KClass
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
class CollectSignaturesFlowTests { class CollectSignaturesFlowTests {
companion object {
private val cordappPackages = listOf("net.corda.testing.contracts")
}
lateinit var mockNet: MockNetwork lateinit var mockNet: MockNetwork
lateinit var aliceNode: StartedNode<MockNetwork.MockNode> lateinit var aliceNode: StartedNode<MockNetwork.MockNode>
lateinit var bobNode: StartedNode<MockNetwork.MockNode> lateinit var bobNode: StartedNode<MockNetwork.MockNode>
lateinit var charlieNode: StartedNode<MockNetwork.MockNode> lateinit var charlieNode: StartedNode<MockNetwork.MockNode>
lateinit var alice: Party
lateinit var bob: Party
lateinit var charlie: Party
lateinit var notary: Party lateinit var notary: Party
@Before @Before
fun setup() { fun setup() {
setCordappPackages("net.corda.testing.contracts") mockNet = MockNetwork(cordappPackages = cordappPackages)
mockNet = MockNetwork()
val notaryNode = mockNet.createNotaryNode() val notaryNode = mockNet.createNotaryNode()
aliceNode = mockNet.createPartyNode(ALICE.name) aliceNode = mockNet.createPartyNode(ALICE.name)
bobNode = mockNet.createPartyNode(BOB.name) bobNode = mockNet.createPartyNode(BOB.name)
charlieNode = mockNet.createPartyNode(CHARLIE.name) charlieNode = mockNet.createPartyNode(CHARLIE.name)
mockNet.runNetwork() mockNet.runNetwork()
notary = notaryNode.services.getDefaultNotary()
aliceNode.internals.ensureRegistered() aliceNode.internals.ensureRegistered()
alice = aliceNode.info.singleIdentity()
bob = bobNode.info.singleIdentity()
charlie = charlieNode.info.singleIdentity()
notary = notaryNode.services.getDefaultNotary()
} }
@After @After
fun tearDown() { fun tearDown() {
mockNet.stopNodes() mockNet.stopNodes()
unsetCordappPackages()
} }
private fun registerFlowOnAllNodes(flowClass: KClass<out FlowLogic<*>>) { private fun registerFlowOnAllNodes(flowClass: KClass<out FlowLogic<*>>) {
@ -141,7 +149,8 @@ class CollectSignaturesFlowTests {
@Test @Test
fun `successfully collects two signatures`() { fun `successfully collects two signatures`() {
val bConfidentialIdentity = bobNode.database.transaction { val bConfidentialIdentity = bobNode.database.transaction {
bobNode.services.keyManagementService.freshKeyAndCert(bobNode.info.chooseIdentityAndCert(), false) val bobCert = bobNode.services.myInfo.legalIdentitiesAndCerts.single { it.name == bob.name }
bobNode.services.keyManagementService.freshKeyAndCert(bobCert, false)
} }
aliceNode.database.transaction { aliceNode.database.transaction {
// Normally this is handled by TransactionKeyFlow, but here we have to manually let A know about the identity // Normally this is handled by TransactionKeyFlow, but here we have to manually let A know about the identity
@ -149,7 +158,7 @@ class CollectSignaturesFlowTests {
} }
registerFlowOnAllNodes(TestFlowTwo.Responder::class) registerFlowOnAllNodes(TestFlowTwo.Responder::class)
val magicNumber = 1337 val magicNumber = 1337
val parties = listOf(aliceNode.info.chooseIdentity(), bConfidentialIdentity.party, charlieNode.info.chooseIdentity()) val parties = listOf(alice, bConfidentialIdentity.party, charlie)
val state = DummyContract.MultiOwnerState(magicNumber, parties) val state = DummyContract.MultiOwnerState(magicNumber, parties)
val flow = aliceNode.services.startFlow(TestFlowTwo.Initiator(state)) val flow = aliceNode.services.startFlow(TestFlowTwo.Initiator(state))
mockNet.runNetwork() mockNet.runNetwork()
@ -161,7 +170,7 @@ class CollectSignaturesFlowTests {
@Test @Test
fun `no need to collect any signatures`() { fun `no need to collect any signatures`() {
val onePartyDummyContract = DummyContract.generateInitial(1337, notary, aliceNode.info.chooseIdentity().ref(1)) val onePartyDummyContract = DummyContract.generateInitial(1337, notary, alice.ref(1))
val ptx = aliceNode.services.signInitialTransaction(onePartyDummyContract) val ptx = aliceNode.services.signInitialTransaction(onePartyDummyContract)
val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet())) val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet()))
mockNet.runNetwork() mockNet.runNetwork()
@ -173,8 +182,8 @@ class CollectSignaturesFlowTests {
@Test @Test
fun `fails when not signed by initiator`() { fun `fails when not signed by initiator`() {
val onePartyDummyContract = DummyContract.generateInitial(1337, notary, aliceNode.info.chooseIdentity().ref(1)) val onePartyDummyContract = DummyContract.generateInitial(1337, notary, alice.ref(1))
val miniCorpServices = MockServices(MINI_CORP_KEY) val miniCorpServices = MockServices(cordappPackages, MINI_CORP_KEY)
val ptx = miniCorpServices.signInitialTransaction(onePartyDummyContract) val ptx = miniCorpServices.signInitialTransaction(onePartyDummyContract)
val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet())) val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet()))
mockNet.runNetwork() mockNet.runNetwork()
@ -186,9 +195,9 @@ class CollectSignaturesFlowTests {
@Test @Test
fun `passes with multiple initial signatures`() { fun `passes with multiple initial signatures`() {
val twoPartyDummyContract = DummyContract.generateInitial(1337, notary, val twoPartyDummyContract = DummyContract.generateInitial(1337, notary,
aliceNode.info.chooseIdentity().ref(1), alice.ref(1),
bobNode.info.chooseIdentity().ref(2), bob.ref(2),
bobNode.info.chooseIdentity().ref(3)) bob.ref(3))
val signedByA = aliceNode.services.signInitialTransaction(twoPartyDummyContract) val signedByA = aliceNode.services.signInitialTransaction(twoPartyDummyContract)
val signedByBoth = bobNode.services.addSignature(signedByA) val signedByBoth = bobNode.services.addSignature(signedByA)
val flow = aliceNode.services.startFlow(CollectSignaturesFlow(signedByBoth, emptySet())) val flow = aliceNode.services.startFlow(CollectSignaturesFlow(signedByBoth, emptySet()))

View File

@ -40,8 +40,7 @@ class ContractUpgradeFlowTest {
@Before @Before
fun setup() { fun setup() {
setCordappPackages("net.corda.testing.contracts", "net.corda.finance.contracts.asset", "net.corda.core.flows") mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset", "net.corda.core.flows"))
mockNet = MockNetwork()
val notaryNode = mockNet.createNotaryNode() val notaryNode = mockNet.createNotaryNode()
aliceNode = mockNet.createPartyNode(ALICE.name) aliceNode = mockNet.createPartyNode(ALICE.name)
bobNode = mockNet.createPartyNode(BOB.name) bobNode = mockNet.createPartyNode(BOB.name)
@ -56,7 +55,6 @@ class ContractUpgradeFlowTest {
@After @After
fun tearDown() { fun tearDown() {
mockNet.stopNodes() mockNet.stopNodes()
unsetCordappPackages()
} }
@Test @Test
@ -121,7 +119,7 @@ class ContractUpgradeFlowTest {
return startRpcClient<CordaRPCOps>( return startRpcClient<CordaRPCOps>(
rpcAddress = startRpcServer( rpcAddress = startRpcServer(
rpcUser = user, rpcUser = user,
ops = CordaRPCOpsImpl(node.services, node.smm, node.database) ops = CordaRPCOpsImpl(node.services, node.smm, node.database, node.services)
).get().broker.hostAndPort!!, ).get().broker.hostAndPort!!,
username = user.username, username = user.username,
password = user.password password = user.password

View File

@ -6,7 +6,7 @@ import net.corda.core.utilities.getOrThrow
import net.corda.finance.POUNDS import net.corda.finance.POUNDS
import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.asset.Cash
import net.corda.finance.issuedBy import net.corda.finance.issuedBy
import net.corda.node.internal.StartedNode import net.corda.node.services.api.StartedNodeServices
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork
import org.junit.After import org.junit.After
@ -16,53 +16,57 @@ import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
class FinalityFlowTests { class FinalityFlowTests {
lateinit var mockNet: MockNetwork private lateinit var mockNet: MockNetwork
lateinit var aliceNode: StartedNode<MockNetwork.MockNode> private lateinit var aliceServices: StartedNodeServices
lateinit var bobNode: StartedNode<MockNetwork.MockNode> private lateinit var bobServices: StartedNodeServices
lateinit var notary: Party private lateinit var alice: Party
private lateinit var bob: Party
private lateinit var notary: Party
@Before @Before
fun setup() { fun setup() {
setCordappPackages("net.corda.finance.contracts.asset") mockNet = MockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset"))
mockNet = MockNetwork() val notaryNode = mockNet.createNotaryNode()
mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME)
aliceNode = mockNet.createPartyNode(ALICE.name) val bobNode = mockNet.createPartyNode(BOB_NAME)
bobNode = mockNet.createPartyNode(BOB.name)
mockNet.runNetwork() mockNet.runNetwork()
aliceNode.internals.ensureRegistered() aliceNode.internals.ensureRegistered()
notary = aliceNode.services.getDefaultNotary() aliceServices = aliceNode.services
bobServices = bobNode.services
alice = aliceNode.info.singleIdentity()
bob = bobNode.info.singleIdentity()
notary = notaryNode.services.getDefaultNotary()
} }
@After @After
fun tearDown() { fun tearDown() {
mockNet.stopNodes() mockNet.stopNodes()
unsetCordappPackages()
} }
@Test @Test
fun `finalise a simple transaction`() { fun `finalise a simple transaction`() {
val amount = 1000.POUNDS.issuedBy(aliceNode.info.chooseIdentity().ref(0)) val amount = 1000.POUNDS.issuedBy(alice.ref(0))
val builder = TransactionBuilder(notary) val builder = TransactionBuilder(notary)
Cash().generateIssue(builder, amount, bobNode.info.chooseIdentity(), notary) Cash().generateIssue(builder, amount, bob, notary)
val stx = aliceNode.services.signInitialTransaction(builder) val stx = aliceServices.signInitialTransaction(builder)
val flow = aliceNode.services.startFlow(FinalityFlow(stx)) val flow = aliceServices.startFlow(FinalityFlow(stx))
mockNet.runNetwork() mockNet.runNetwork()
val notarisedTx = flow.resultFuture.getOrThrow() val notarisedTx = flow.resultFuture.getOrThrow()
notarisedTx.verifyRequiredSignatures() notarisedTx.verifyRequiredSignatures()
val transactionSeenByB = bobNode.services.database.transaction { val transactionSeenByB = bobServices.database.transaction {
bobNode.services.validatedTransactions.getTransaction(notarisedTx.id) bobServices.validatedTransactions.getTransaction(notarisedTx.id)
} }
assertEquals(notarisedTx, transactionSeenByB) assertEquals(notarisedTx, transactionSeenByB)
} }
@Test @Test
fun `reject a transaction with unknown parties`() { fun `reject a transaction with unknown parties`() {
val amount = 1000.POUNDS.issuedBy(aliceNode.info.chooseIdentity().ref(0)) val amount = 1000.POUNDS.issuedBy(alice.ref(0))
val fakeIdentity = CHARLIE // Charlie isn't part of this network, so node A won't recognise them val fakeIdentity = CHARLIE // Charlie isn't part of this network, so node A won't recognise them
val builder = TransactionBuilder(notary) val builder = TransactionBuilder(notary)
Cash().generateIssue(builder, amount, fakeIdentity, notary) Cash().generateIssue(builder, amount, fakeIdentity, notary)
val stx = aliceNode.services.signInitialTransaction(builder) val stx = aliceServices.signInitialTransaction(builder)
val flow = aliceNode.services.startFlow(FinalityFlow(stx)) val flow = aliceServices.startFlow(FinalityFlow(stx))
mockNet.runNetwork() mockNet.runNetwork()
assertFailsWith<IllegalArgumentException> { assertFailsWith<IllegalArgumentException> {
flow.resultFuture.getOrThrow() flow.resultFuture.getOrThrow()

View File

@ -61,6 +61,7 @@ class InternalUtilsTest {
assertArrayEquals(intArrayOf(1, 2, 3, 4), (1 until 5).stream().toArray()) assertArrayEquals(intArrayOf(1, 2, 3, 4), (1 until 5).stream().toArray())
assertArrayEquals(intArrayOf(1, 3), (1..4 step 2).stream().toArray()) assertArrayEquals(intArrayOf(1, 3), (1..4 step 2).stream().toArray())
assertArrayEquals(intArrayOf(1, 3), (1..3 step 2).stream().toArray()) assertArrayEquals(intArrayOf(1, 3), (1..3 step 2).stream().toArray())
@Suppress("EmptyRange") // It's supposed to be empty.
assertArrayEquals(intArrayOf(), (1..0).stream().toArray()) assertArrayEquals(intArrayOf(), (1..0).stream().toArray())
assertArrayEquals(intArrayOf(1, 0), (1 downTo 0).stream().toArray()) assertArrayEquals(intArrayOf(1, 0), (1 downTo 0).stream().toArray())
assertArrayEquals(intArrayOf(3, 1), (3 downTo 0 step 2).stream().toArray()) assertArrayEquals(intArrayOf(3, 1), (3 downTo 0 step 2).stream().toArray())

View File

@ -36,8 +36,7 @@ class ResolveTransactionsFlowTest {
@Before @Before
fun setup() { fun setup() {
setCordappPackages("net.corda.testing.contracts") mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
mockNet = MockNetwork()
notaryNode = mockNet.createNotaryNode() notaryNode = mockNet.createNotaryNode()
megaCorpNode = mockNet.createPartyNode(MEGA_CORP.name) megaCorpNode = mockNet.createPartyNode(MEGA_CORP.name)
miniCorpNode = mockNet.createPartyNode(MINI_CORP.name) miniCorpNode = mockNet.createPartyNode(MINI_CORP.name)
@ -52,7 +51,6 @@ class ResolveTransactionsFlowTest {
@After @After
fun tearDown() { fun tearDown() {
mockNet.stopNodes() mockNet.stopNodes()
unsetCordappPackages()
} }
// DOCEND 3 // DOCEND 3

View File

@ -16,7 +16,7 @@ import net.corda.node.internal.InitiatedFlowFactory
import net.corda.node.internal.StartedNode import net.corda.node.internal.StartedNode
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.persistence.NodeAttachmentService import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.node.utilities.DatabaseTransactionManager import net.corda.node.utilities.currentDBSession
import net.corda.nodeapi.internal.ServiceInfo import net.corda.nodeapi.internal.ServiceInfo
import net.corda.testing.chooseIdentity import net.corda.testing.chooseIdentity
import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork
@ -54,7 +54,7 @@ private fun StartedNode<*>.hackAttachment(attachmentId: SecureHash, content: Str
* @see NodeAttachmentService.importAttachment * @see NodeAttachmentService.importAttachment
*/ */
private fun updateAttachment(attachmentId: SecureHash, data: ByteArray) { private fun updateAttachment(attachmentId: SecureHash, data: ByteArray) {
val session = DatabaseTransactionManager.current().session val session = currentDBSession()
val attachment = session.get<NodeAttachmentService.DBAttachment>(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString()) val attachment = session.get<NodeAttachmentService.DBAttachment>(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString())
attachment?.let { attachment?.let {
attachment.content = data attachment.content = data

View File

@ -12,11 +12,12 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import java.security.SignatureException import java.security.SignatureException
import java.util.* import java.util.*
import kotlin.reflect.jvm.javaField
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
class TransactionSerializationTests : TestDependencyInjectionBase() { class TransactionSerializationTests : TestDependencyInjectionBase() {
val TEST_CASH_PROGRAM_ID = "net.corda.core.serialization.TransactionSerializationTests\$TestCash" private val TEST_CASH_PROGRAM_ID = "net.corda.core.serialization.TransactionSerializationTests\$TestCash"
class TestCash : Contract { class TestCash : Contract {
override fun verify(tx: LedgerTransaction) { override fun verify(tx: LedgerTransaction) {
@ -65,7 +66,10 @@ class TransactionSerializationTests : TestDependencyInjectionBase() {
stx.verifyRequiredSignatures() stx.verifyRequiredSignatures()
// Corrupt the data and ensure the signature catches the problem. // Corrupt the data and ensure the signature catches the problem.
stx.id.bytes[5] = stx.id.bytes[5].inc() val bytesField = stx.id::bytes.javaField?.apply { setAccessible(true) }
val bytes = bytesField?.get(stx.id) as ByteArray
bytes[5] = bytes[5].inc()
assertFailsWith(SignatureException::class) { assertFailsWith(SignatureException::class) {
stx.verifyRequiredSignatures() stx.verifyRequiredSignatures()
} }

View File

@ -7,6 +7,8 @@
API: Persistence API: Persistence
================ ================
.. contents::
Corda offers developers the option to expose all or some part of a contract state to an *Object Relational Mapping* Corda offers developers the option to expose all or some part of a contract state to an *Object Relational Mapping*
(ORM) tool to be persisted in a RDBMS. The purpose of this is to assist *vault* development by effectively indexing (ORM) tool to be persisted in a RDBMS. The purpose of this is to assist *vault* development by effectively indexing
persisted contract states held in the vault for the purpose of running queries over them and to allow relational joins persisted contract states held in the vault for the purpose of running queries over them and to allow relational joins
@ -79,10 +81,10 @@ Custom schema registration
Custom contract schemas are automatically registered at startup time for CorDapps. The node bootstrap process will scan Custom contract schemas are automatically registered at startup time for CorDapps. The node bootstrap process will scan
for schemas (any class that extends the ``MappedSchema`` interface) in the `plugins` configuration directory in your CorDapp jar. for schemas (any class that extends the ``MappedSchema`` interface) in the `plugins` configuration directory in your CorDapp jar.
For testing purposes it is necessary to manually register custom schemas as follows: For testing purposes it is necessary to manually register the packages containing custom schemas as follows:
- Tests using ``MockNetwork`` and ``MockNode`` must explicitly register custom schemas using the `registerCustomSchemas()` method of ``MockNode`` - Tests using ``MockNetwork`` and ``MockNode`` must explicitly register packages using the `cordappPackages` parameter of ``MockNetwork``
- Tests using ``MockServices`` must explicitly register schemas using `customSchemas` attribute of the ``MockServices`` `makeTestDatabaseAndMockServices()` helper method. - Tests using ``MockServices`` must explicitly register packages using the `cordappPackages` parameter of the ``MockServices`` `makeTestDatabaseAndMockServices()` helper method.
.. note:: Tests using the `DriverDSL` will automatically register your custom schemas if they are in the same project structure as the driver call. .. note:: Tests using the `DriverDSL` will automatically register your custom schemas if they are in the same project structure as the driver call.

View File

@ -1,33 +1,42 @@
API: Vault Query API: Vault Query
================ ================
Corda has been architected from the ground up to encourage usage of industry standard, proven query frameworks and libraries for accessing RDBMS backed transactional stores (including the Vault). .. contents::
Overview
--------
Corda has been architected from the ground up to encourage usage of industry standard, proven query frameworks and
libraries for accessing RDBMS backed transactional stores (including the Vault).
Corda provides a number of flexible query mechanisms for accessing the Vault: Corda provides a number of flexible query mechanisms for accessing the Vault:
- Vault Query API - Vault Query API
- using a JDBC session (as described in :ref:`Persistence <jdbc_session_ref>`) - Using a JDBC session (as described in :ref:`Persistence <jdbc_session_ref>`)
- custom JPA_/JPQL_ queries - Custom JPA_/JPQL_ queries
- custom 3rd party Data Access frameworks such as `Spring Data <http://projects.spring.io/spring-data>`_ - Custom 3rd party Data Access frameworks such as `Spring Data <http://projects.spring.io/spring-data>`_
The majority of query requirements can be satisfied by using the Vault Query API, which is exposed via the ``VaultService`` for use directly by flows: The majority of query requirements can be satisfied by using the Vault Query API, which is exposed via the
``VaultService`` for use directly by flows:
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/node/services/VaultService.kt .. literalinclude:: ../../core/src/main/kotlin/net/corda/core/node/services/VaultService.kt
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryAPI :start-after: DOCSTART VaultQueryAPI
:end-before: DOCEND VaultQueryAPI :end-before: DOCEND VaultQueryAPI
:dedent: 4
and via ``CordaRPCOps`` for use by RPC client applications: And via ``CordaRPCOps`` for use by RPC client applications:
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt .. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryByAPI :start-after: DOCSTART VaultQueryByAPI
:end-before: DOCEND VaultQueryByAPI :end-before: DOCEND VaultQueryByAPI
:dedent: 4
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt .. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultTrackByAPI :start-after: DOCSTART VaultTrackByAPI
:end-before: DOCEND VaultTrackByAPI :end-before: DOCEND VaultTrackByAPI
:dedent: 4
Helper methods are also provided with default values for arguments: Helper methods are also provided with default values for arguments:
@ -35,51 +44,84 @@ Helper methods are also provided with default values for arguments:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryAPIHelpers :start-after: DOCSTART VaultQueryAPIHelpers
:end-before: DOCEND VaultQueryAPIHelpers :end-before: DOCEND VaultQueryAPIHelpers
:dedent: 4
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt .. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultTrackAPIHelpers :start-after: DOCSTART VaultTrackAPIHelpers
:end-before: DOCEND VaultTrackAPIHelpers :end-before: DOCEND VaultTrackAPIHelpers
:dedent: 4
The API provides both static (snapshot) and dynamic (snapshot with streaming updates) methods for a defined set of filter criteria. The API provides both static (snapshot) and dynamic (snapshot with streaming updates) methods for a defined set of
filter criteria:
- Use ``queryBy`` to obtain a only current snapshot of data (for a given ``QueryCriteria``) - Use ``queryBy`` to obtain a only current snapshot of data (for a given ``QueryCriteria``)
- Use ``trackBy`` to obtain a both a current snapshot and a future stream of updates (for a given ``QueryCriteria``) - Use ``trackBy`` to obtain a both a current snapshot and a future stream of updates (for a given ``QueryCriteria``)
.. note:: Streaming updates are only filtered based on contract type and state status (UNCONSUMED, CONSUMED, ALL) .. note:: Streaming updates are only filtered based on contract type and state status (UNCONSUMED, CONSUMED, ALL)
Simple pagination (page number and size) and sorting (directional ordering using standard or custom property attributes) is also specifiable. Simple pagination (page number and size) and sorting (directional ordering using standard or custom property
Defaults are defined for Paging (pageNumber = 1, pageSize = 200) and Sorting (direction = ASC). attributes) is also specifiable. Defaults are defined for paging (pageNumber = 1, pageSize = 200) and sorting
(direction = ASC).
The ``QueryCriteria`` interface provides a flexible mechanism for specifying different filtering criteria, including and/or composition and a rich set of operators to include: binary logical (AND, OR), comparison (LESS_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL), equality (EQUAL, NOT_EQUAL), likeness (LIKE, NOT_LIKE), nullability (IS_NULL, NOT_NULL), and collection based (IN, NOT_IN). Standard SQL-92 aggregate functions (SUM, AVG, MIN, MAX, COUNT) are also supported. The ``QueryCriteria`` interface provides a flexible mechanism for specifying different filtering criteria, including
and/or composition and a rich set of operators to include:
* Binary logical (AND, OR)
* Comparison (LESS_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL)
* Equality (EQUAL, NOT_EQUAL)
* Likeness (LIKE, NOT_LIKE)
* Nullability (IS_NULL, NOT_NULL)
* Collection based (IN, NOT_IN)
* Standard SQL-92 aggregate functions (SUM, AVG, MIN, MAX, COUNT)
There are four implementations of this interface which can be chained together to define advanced filters. There are four implementations of this interface which can be chained together to define advanced filters.
1. ``VaultQueryCriteria`` provides filterable criteria on attributes within the Vault states table: status (UNCONSUMED, CONSUMED), state reference(s), contract state type(s), notaries, soft locked states, timestamps (RECORDED, CONSUMED). 1. ``VaultQueryCriteria`` provides filterable criteria on attributes within the Vault states table: status (UNCONSUMED,
CONSUMED), state reference(s), contract state type(s), notaries, soft locked states, timestamps (RECORDED, CONSUMED).
.. note:: Sensible defaults are defined for frequently used attributes (status = UNCONSUMED, always include soft locked states). .. note:: Sensible defaults are defined for frequently used attributes (status = UNCONSUMED, always include soft
locked states).
2. ``FungibleAssetQueryCriteria`` provides filterable criteria on attributes defined in the Corda Core ``FungibleAsset`` contract state interface, used to represent assets that are fungible, countable and issued by a specific party (eg. ``Cash.State`` and ``CommodityContract.State`` in the Corda finance module). Filterable attributes include: participants(s), owner(s), quantity, issuer party(s) and issuer reference(s). 2. ``FungibleAssetQueryCriteria`` provides filterable criteria on attributes defined in the Corda Core
``FungibleAsset`` contract state interface, used to represent assets that are fungible, countable and issued by a
specific party (eg. ``Cash.State`` and ``CommodityContract.State`` in the Corda finance module). Filterable
attributes include: participants(s), owner(s), quantity, issuer party(s) and issuer reference(s).
.. note:: All contract states that extend the ``FungibleAsset`` now automatically persist that interfaces common state attributes to the **vault_fungible_states** table. .. note:: All contract states that extend the ``FungibleAsset`` now automatically persist that interfaces common
state attributes to the **vault_fungible_states** table.
3. ``LinearStateQueryCriteria`` provides filterable criteria on attributes defined in the Corda Core ``LinearState`` and ``DealState`` contract state interfaces, used to represent entities that continuously supercede themselves, all of which share the same *linearId* (eg. trade entity states such as the ``IRSState`` defined in the SIMM valuation demo). Filterable attributes include: participant(s), linearId(s), uuid(s), and externalId(s). 3. ``LinearStateQueryCriteria`` provides filterable criteria on attributes defined in the Corda Core ``LinearState``
and ``DealState`` contract state interfaces, used to represent entities that continuously supersede themselves, all
of which share the same ``linearId`` (e.g. trade entity states such as the ``IRSState`` defined in the SIMM
valuation demo). Filterable attributes include: participant(s), linearId(s), uuid(s), and externalId(s).
.. note:: All contract states that extend ``LinearState`` or ``DealState`` now automatically persist those interfaces common state attributes to the **vault_linear_states** table. .. note:: All contract states that extend ``LinearState`` or ``DealState`` now automatically persist those
interfaces common state attributes to the **vault_linear_states** table.
4. ``VaultCustomQueryCriteria`` provides the means to specify one or many arbitrary expressions on attributes defined by a custom contract state that implements its own schema as described in the :doc:`Persistence </api-persistence>` documentation and associated examples. Custom criteria expressions are expressed using one of several type-safe ``CriteriaExpression``: BinaryLogical, Not, ColumnPredicateExpression, AggregateFunctionExpression. The ``ColumnPredicateExpression`` allows for specification arbitrary criteria using the previously enumerated operator types. The ``AggregateFunctionExpression`` allows for the specification of an aggregate function type (sum, avg, max, min, count) with optional grouping and sorting. Furthermore, a rich DSL is provided to enable simple construction of custom criteria using any combination of ``ColumnPredicate``. See the ``Builder`` object in ``QueryCriteriaUtils`` for a complete specification of the DSL. 4. ``VaultCustomQueryCriteria`` provides the means to specify one or many arbitrary expressions on attributes defined
by a custom contract state that implements its own schema as described in the :doc:`Persistence </api-persistence>`
documentation and associated examples. Custom criteria expressions are expressed using one of several type-safe
``CriteriaExpression``: BinaryLogical, Not, ColumnPredicateExpression, AggregateFunctionExpression. The
``ColumnPredicateExpression`` allows for specification arbitrary criteria using the previously enumerated operator
types. The ``AggregateFunctionExpression`` allows for the specification of an aggregate function type (sum, avg,
max, min, count) with optional grouping and sorting. Furthermore, a rich DSL is provided to enable simple
construction of custom criteria using any combination of ``ColumnPredicate``. See the ``Builder`` object in
``QueryCriteriaUtils`` for a complete specification of the DSL.
.. note:: custom contract schemas are automatically registered upon node startup for CorDapps. Please refer to .. note:: Custom contract schemas are automatically registered upon node startup for CorDapps. Please refer to
:doc:`Persistence </api-persistence>` for mechanisms of registering custom schemas for different testing purposes. :doc:`Persistence </api-persistence>` for mechanisms of registering custom schemas for different testing
purposes.
All ``QueryCriteria`` implementations are composable using ``and`` and ``or`` operators. All ``QueryCriteria`` implementations are composable using ``and`` and ``or`` operators.
All ``QueryCriteria`` implementations provide an explicitly specifiable set of common attributes: All ``QueryCriteria`` implementations provide an explicitly specifiable set of common attributes:
1. State status attribute (``Vault.StateStatus``), which defaults to filtering on UNCONSUMED states. 1. State status attribute (``Vault.StateStatus``), which defaults to filtering on UNCONSUMED states.
When chaining several criterias using AND / OR, the last value of this attribute will override any previous. When chaining several criterias using AND / OR, the last value of this attribute will override any previous
2. Contract state types (``<Set<Class<out ContractState>>``), which will contain at minimum one type (by default this will be ``ContractState`` which resolves to all state types). 2. Contract state types (``<Set<Class<out ContractState>>``), which will contain at minimum one type (by default this
When chaining several criteria using ``and`` and ``or`` operators, all specified contract state types are combined into a single set. will be ``ContractState`` which resolves to all state types). When chaining several criteria using ``and`` and
``or`` operators, all specified contract state types are combined into a single set
An example of a custom query is illustrated here: An example of a custom query is illustrated here:
@ -87,20 +129,30 @@ An example of a custom query is illustrated here:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample20 :start-after: DOCSTART VaultQueryExample20
:end-before: DOCEND VaultQueryExample20 :end-before: DOCEND VaultQueryExample20
:dedent: 12
.. note:: Custom contract states that implement the ``Queryable`` interface may now extend common schemas types ``FungiblePersistentState`` or, ``LinearPersistentState``. Previously, all custom contracts extended the root ``PersistentState`` class and defined repeated mappings of ``FungibleAsset`` and ``LinearState`` attributes. See ``SampleCashSchemaV2`` and ``DummyLinearStateSchemaV2`` as examples. .. note:: Custom contract states that implement the ``Queryable`` interface may now extend common schemas types
``FungiblePersistentState`` or, ``LinearPersistentState``. Previously, all custom contracts extended the root
``PersistentState`` class and defined repeated mappings of ``FungibleAsset`` and ``LinearState`` attributes. See
``SampleCashSchemaV2`` and ``DummyLinearStateSchemaV2`` as examples.
Examples of these ``QueryCriteria`` objects are presented below for Kotlin and Java. Examples of these ``QueryCriteria`` objects are presented below for Kotlin and Java.
.. note:: When specifying the Contract Type as a parameterised type to the QueryCriteria in Kotlin, queries now include all concrete implementations of that type if this is an interface. Previously, it was only possible to query on Concrete types (or the universe of all Contract States). .. note:: When specifying the ``ContractType`` as a parameterised type to the ``QueryCriteria`` in Kotlin, queries now
include all concrete implementations of that type if this is an interface. Previously, it was only possible to query
on concrete types (or the universe of all ``ContractState``).
The Vault Query API leverages the rich semantics of the underlying JPA Hibernate_ based :doc:`Persistence </api-persistence>` framework adopted by Corda. The Vault Query API leverages the rich semantics of the underlying JPA Hibernate_ based
:doc:`Persistence </api-persistence>` framework adopted by Corda.
.. _Hibernate: https://docs.jboss.org/hibernate/jpa/2.1/api/ .. _Hibernate: https://docs.jboss.org/hibernate/jpa/2.1/api/
.. note:: Permissioning at the database level will be enforced at a later date to ensure authenticated, role-based, read-only access to underlying Corda tables. .. note:: Permissioning at the database level will be enforced at a later date to ensure authenticated, role-based,
read-only access to underlying Corda tables.
.. note:: API's now provide ease of use calling semantics from both Java and Kotlin. However, it should be noted that Java custom queries are significantly more verbose due to the use of reflection fields to reference schema attribute types. .. note:: API's now provide ease of use calling semantics from both Java and Kotlin. However, it should be noted that
Java custom queries are significantly more verbose due to the use of reflection fields to reference schema attribute
types.
An example of a custom query in Java is illustrated here: An example of a custom query in Java is illustrated here:
@ -108,17 +160,24 @@ An example of a custom query in Java is illustrated here:
:language: java :language: java
:start-after: DOCSTART VaultJavaQueryExample3 :start-after: DOCSTART VaultJavaQueryExample3
:end-before: DOCEND VaultJavaQueryExample3 :end-before: DOCEND VaultJavaQueryExample3
:dedent: 16
.. note:: Queries by ``Party`` specify the ``AbstractParty`` which may be concrete or anonymous. In the later case, where an anonymous party does not resolve to an X500Name via the IdentityService, no query results will ever be produced. For performance reasons, queries do not use PublicKey as search criteria. .. note:: Queries by ``Party`` specify the ``AbstractParty`` which may be concrete or anonymous. In the later case,
where an anonymous party does not resolve to an X500 name via the ``IdentityService``, no query results will ever be
produced. For performance reasons, queries do not use ``PublicKey`` as search criteria.
Pagination Pagination
---------- ----------
The API provides support for paging where large numbers of results are expected (by default, a page size is set to 200 results). The API provides support for paging where large numbers of results are expected (by default, a page size is set to 200
Defining a sensible default page size enables efficient checkpointing within flows, and frees the developer from worrying about pagination where results). Defining a sensible default page size enables efficient checkpointing within flows, and frees the developer
result sets are expected to be constrained to 200 or fewer entries. Where large result sets are expected (such as using the RPC API for reporting and/or UI display), it is strongly recommended to define a ``PageSpecification`` to correctly process results with efficient memory utilistion. A fail-fast mode is in place to alert API users to the need for pagination where a single query returns more than 200 results and no ``PageSpecification`` from worrying about pagination where result sets are expected to be constrained to 200 or fewer entries. Where large
has been supplied. result sets are expected (such as using the RPC API for reporting and/or UI display), it is strongly recommended to
define a ``PageSpecification`` to correctly process results with efficient memory utilisation. A fail-fast mode is in
place to alert API users to the need for pagination where a single query returns more than 200 results and no
``PageSpecification`` has been supplied.
.. note:: A pages maximum size ``MAX_PAGE_SIZE`` is defined as ``Int.MAX_VALUE`` and should be used with extreme caution as results returned may exceed your JVM's memory footprint. .. note:: A pages maximum size ``MAX_PAGE_SIZE`` is defined as ``Int.MAX_VALUE`` and should be used with extreme
caution as results returned may exceed your JVM's memory footprint.
Example usage Example usage
------------- -------------
@ -126,7 +185,7 @@ Example usage
Kotlin Kotlin
^^^^^^ ^^^^^^
**General snapshot queries using** ``VaultQueryCriteria`` **General snapshot queries using** ``VaultQueryCriteria``:
Query for all unconsumed states (simplest query possible): Query for all unconsumed states (simplest query possible):
@ -134,6 +193,7 @@ Query for all unconsumed states (simplest query possible):
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample1 :start-after: DOCSTART VaultQueryExample1
:end-before: DOCEND VaultQueryExample1 :end-before: DOCEND VaultQueryExample1
:dedent: 12
Query for unconsumed states for some state references: Query for unconsumed states for some state references:
@ -141,6 +201,7 @@ Query for unconsumed states for some state references:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample2 :start-after: DOCSTART VaultQueryExample2
:end-before: DOCEND VaultQueryExample2 :end-before: DOCEND VaultQueryExample2
:dedent: 12
Query for unconsumed states for several contract state types: Query for unconsumed states for several contract state types:
@ -148,6 +209,7 @@ Query for unconsumed states for several contract state types:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample3 :start-after: DOCSTART VaultQueryExample3
:end-before: DOCEND VaultQueryExample3 :end-before: DOCEND VaultQueryExample3
:dedent: 12
Query for unconsumed states for a given notary: Query for unconsumed states for a given notary:
@ -155,6 +217,7 @@ Query for unconsumed states for a given notary:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample4 :start-after: DOCSTART VaultQueryExample4
:end-before: DOCEND VaultQueryExample4 :end-before: DOCEND VaultQueryExample4
:dedent: 12
Query for unconsumed states for a given set of participants: Query for unconsumed states for a given set of participants:
@ -162,6 +225,7 @@ Query for unconsumed states for a given set of participants:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample5 :start-after: DOCSTART VaultQueryExample5
:end-before: DOCEND VaultQueryExample5 :end-before: DOCEND VaultQueryExample5
:dedent: 12
Query for unconsumed states recorded between two time intervals: Query for unconsumed states recorded between two time intervals:
@ -169,6 +233,7 @@ Query for unconsumed states recorded between two time intervals:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample6 :start-after: DOCSTART VaultQueryExample6
:end-before: DOCEND VaultQueryExample6 :end-before: DOCEND VaultQueryExample6
:dedent: 12
.. note:: This example illustrates usage of a ``Between`` ``ColumnPredicate``. .. note:: This example illustrates usage of a ``Between`` ``ColumnPredicate``.
@ -178,17 +243,21 @@ Query for all states with pagination specification (10 results per page):
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample7 :start-after: DOCSTART VaultQueryExample7
:end-before: DOCEND VaultQueryExample7 :end-before: DOCEND VaultQueryExample7
:dedent: 12
.. note:: The result set metadata field `totalStatesAvailable` allows you to further paginate accordingly as demonstrated in the following example. .. note:: The result set metadata field `totalStatesAvailable` allows you to further paginate accordingly as
demonstrated in the following example.
Query for all states using pagination specification and iterate using `totalStatesAvailable` field until no further pages available: Query for all states using pagination specification and iterate using `totalStatesAvailable` field until no further
pages available:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt .. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExamplePaging :start-after: DOCSTART VaultQueryExamplePaging
:end-before: DOCEND VaultQueryExamplePaging :end-before: DOCEND VaultQueryExamplePaging
:dedent: 8
**LinearState and DealState queries using** ``LinearStateQueryCriteria`` **LinearState and DealState queries using** ``LinearStateQueryCriteria``:
Query for unconsumed linear states for given linear ids: Query for unconsumed linear states for given linear ids:
@ -196,6 +265,7 @@ Query for unconsumed linear states for given linear ids:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample8 :start-after: DOCSTART VaultQueryExample8
:end-before: DOCEND VaultQueryExample8 :end-before: DOCEND VaultQueryExample8
:dedent: 12
Query for all linear states associated with a linear id: Query for all linear states associated with a linear id:
@ -203,6 +273,7 @@ Query for all linear states associated with a linear id:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample9 :start-after: DOCSTART VaultQueryExample9
:end-before: DOCEND VaultQueryExample9 :end-before: DOCEND VaultQueryExample9
:dedent: 12
Query for unconsumed deal states with deals references: Query for unconsumed deal states with deals references:
@ -210,6 +281,7 @@ Query for unconsumed deal states with deals references:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample10 :start-after: DOCSTART VaultQueryExample10
:end-before: DOCEND VaultQueryExample10 :end-before: DOCEND VaultQueryExample10
:dedent: 12
Query for unconsumed deal states with deals parties: Query for unconsumed deal states with deals parties:
@ -217,8 +289,9 @@ Query for unconsumed deal states with deals parties:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample11 :start-after: DOCSTART VaultQueryExample11
:end-before: DOCEND VaultQueryExample11 :end-before: DOCEND VaultQueryExample11
:dedent: 12
**FungibleAsset and DealState queries using** ``FungibleAssetQueryCriteria`` **FungibleAsset and DealState queries using** ``FungibleAssetQueryCriteria``:
Query for fungible assets for a given currency: Query for fungible assets for a given currency:
@ -226,6 +299,7 @@ Query for fungible assets for a given currency:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample12 :start-after: DOCSTART VaultQueryExample12
:end-before: DOCEND VaultQueryExample12 :end-before: DOCEND VaultQueryExample12
:dedent: 12
Query for fungible assets for a minimum quantity: Query for fungible assets for a minimum quantity:
@ -233,19 +307,21 @@ Query for fungible assets for a minimum quantity:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample13 :start-after: DOCSTART VaultQueryExample13
:end-before: DOCEND VaultQueryExample13 :end-before: DOCEND VaultQueryExample13
:dedent: 12
.. note:: This example uses the builder DSL. .. note:: This example uses the builder DSL.
Query for fungible assets for a specifc issuer party: Query for fungible assets for a specific issuer party:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt .. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample14 :start-after: DOCSTART VaultQueryExample14
:end-before: DOCEND VaultQueryExample14 :end-before: DOCEND VaultQueryExample14
:dedent: 12
**Aggregate Function queries using** ``VaultCustomQueryCriteria`` **Aggregate Function queries using** ``VaultCustomQueryCriteria``:
.. note:: Query results for aggregate functions are contained in the `otherResults` attribute of a results Page. .. note:: Query results for aggregate functions are contained in the ``otherResults`` attribute of a results Page.
Aggregations on cash using various functions: Aggregations on cash using various functions:
@ -253,8 +329,9 @@ Aggregations on cash using various functions:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample21 :start-after: DOCSTART VaultQueryExample21
:end-before: DOCEND VaultQueryExample21 :end-before: DOCEND VaultQueryExample21
:dedent: 12
.. note:: `otherResults` will contain 5 items, one per calculated aggregate function. .. note:: ``otherResults`` will contain 5 items, one per calculated aggregate function.
Aggregations on cash grouped by currency for various functions: Aggregations on cash grouped by currency for various functions:
@ -262,8 +339,10 @@ Aggregations on cash grouped by currency for various functions:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample22 :start-after: DOCSTART VaultQueryExample22
:end-before: DOCEND VaultQueryExample22 :end-before: DOCEND VaultQueryExample22
:dedent: 12
.. note:: `otherResults` will contain 24 items, one result per calculated aggregate function per currency (the grouping attribute - currency in this case - is returned per aggregate result). .. note:: ``otherResults`` will contain 24 items, one result per calculated aggregate function per currency (the
grouping attribute - currency in this case - is returned per aggregate result).
Sum aggregation on cash grouped by issuer party and currency and sorted by sum: Sum aggregation on cash grouped by issuer party and currency and sorted by sum:
@ -271,10 +350,15 @@ Sum aggregation on cash grouped by issuer party and currency and sorted by sum:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample23 :start-after: DOCSTART VaultQueryExample23
:end-before: DOCEND VaultQueryExample23 :end-before: DOCEND VaultQueryExample23
:dedent: 12
.. note:: `otherResults` will contain 12 items sorted from largest summed cash amount to smallest, one result per calculated aggregate function per issuer party and currency (grouping attributes are returned per aggregate result). .. note:: ``otherResults`` will contain 12 items sorted from largest summed cash amount to smallest, one result per
calculated aggregate function per issuer party and currency (grouping attributes are returned per aggregate result).
**Dynamic queries** (also using ``VaultQueryCriteria``) are an extension to the snapshot queries by returning an additional ``QueryResults`` return type in the form of an ``Observable<Vault.Update>``. Refer to `ReactiveX Observable <http://reactivex.io/documentation/observable.html>`_ for a detailed understanding and usage of this type. Dynamic queries (also using ``VaultQueryCriteria``) are an extension to the snapshot queries by returning an
additional ``QueryResults`` return type in the form of an ``Observable<Vault.Update>``. Refer to
`ReactiveX Observable <http://reactivex.io/documentation/observable.html>`_ for a detailed understanding and usage of
this type.
Track unconsumed cash states: Track unconsumed cash states:
@ -282,6 +366,7 @@ Track unconsumed cash states:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample15 :start-after: DOCSTART VaultQueryExample15
:end-before: DOCEND VaultQueryExample15 :end-before: DOCEND VaultQueryExample15
:dedent: 20
Track unconsumed linear states: Track unconsumed linear states:
@ -289,8 +374,9 @@ Track unconsumed linear states:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample16 :start-after: DOCSTART VaultQueryExample16
:end-before: DOCEND VaultQueryExample16 :end-before: DOCEND VaultQueryExample16
:dedent: 20
.. note:: This will return both Deal and Linear states. .. note:: This will return both ``DealState`` and ``LinearState`` states.
Track unconsumed deal states: Track unconsumed deal states:
@ -298,8 +384,9 @@ Track unconsumed deal states:
:language: kotlin :language: kotlin
:start-after: DOCSTART VaultQueryExample17 :start-after: DOCSTART VaultQueryExample17
:end-before: DOCEND VaultQueryExample17 :end-before: DOCEND VaultQueryExample17
:dedent: 20
.. note:: This will return only Deal states. .. note:: This will return only ``DealState`` states.
Java examples Java examples
^^^^^^^^^^^^^ ^^^^^^^^^^^^^
@ -310,6 +397,7 @@ Query for all unconsumed linear states:
:language: java :language: java
:start-after: DOCSTART VaultJavaQueryExample0 :start-after: DOCSTART VaultJavaQueryExample0
:end-before: DOCEND VaultJavaQueryExample0 :end-before: DOCEND VaultJavaQueryExample0
:dedent: 12
Query for all consumed cash states: Query for all consumed cash states:
@ -317,6 +405,7 @@ Query for all consumed cash states:
:language: java :language: java
:start-after: DOCSTART VaultJavaQueryExample1 :start-after: DOCSTART VaultJavaQueryExample1
:end-before: DOCEND VaultJavaQueryExample1 :end-before: DOCEND VaultJavaQueryExample1
:dedent: 12
Query for consumed deal states or linear ids, specify a paging specification and sort by unique identifier: Query for consumed deal states or linear ids, specify a paging specification and sort by unique identifier:
@ -324,8 +413,9 @@ Query for consumed deal states or linear ids, specify a paging specification and
:language: java :language: java
:start-after: DOCSTART VaultJavaQueryExample2 :start-after: DOCSTART VaultJavaQueryExample2
:end-before: DOCEND VaultJavaQueryExample2 :end-before: DOCEND VaultJavaQueryExample2
:dedent: 12
**Aggregate Function queries using** ``VaultCustomQueryCriteria`` **Aggregate Function queries using** ``VaultCustomQueryCriteria``:
Aggregations on cash using various functions: Aggregations on cash using various functions:
@ -333,6 +423,7 @@ Aggregations on cash using various functions:
:language: java :language: java
:start-after: DOCSTART VaultJavaQueryExample21 :start-after: DOCSTART VaultJavaQueryExample21
:end-before: DOCEND VaultJavaQueryExample21 :end-before: DOCEND VaultJavaQueryExample21
:dedent: 16
Aggregations on cash grouped by currency for various functions: Aggregations on cash grouped by currency for various functions:
@ -340,6 +431,7 @@ Aggregations on cash grouped by currency for various functions:
:language: java :language: java
:start-after: DOCSTART VaultJavaQueryExample22 :start-after: DOCSTART VaultJavaQueryExample22
:end-before: DOCEND VaultJavaQueryExample22 :end-before: DOCEND VaultJavaQueryExample22
:dedent: 16
Sum aggregation on cash grouped by issuer party and currency and sorted by sum: Sum aggregation on cash grouped by issuer party and currency and sorted by sum:
@ -347,6 +439,7 @@ Sum aggregation on cash grouped by issuer party and currency and sorted by sum:
:language: java :language: java
:start-after: DOCSTART VaultJavaQueryExample23 :start-after: DOCSTART VaultJavaQueryExample23
:end-before: DOCEND VaultJavaQueryExample23 :end-before: DOCEND VaultJavaQueryExample23
:dedent: 16
Track unconsumed cash states: Track unconsumed cash states:
@ -354,13 +447,16 @@ Track unconsumed cash states:
:language: java :language: java
:start-after: DOCSTART VaultJavaQueryExample4 :start-after: DOCSTART VaultJavaQueryExample4
:end-before: DOCEND VaultJavaQueryExample4 :end-before: DOCEND VaultJavaQueryExample4
:dedent: 12
Track unconsumed deal states or linear states (with snapshot including specification of paging and sorting by unique identifier): Track unconsumed deal states or linear states (with snapshot including specification of paging and sorting by unique
identifier):
.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java .. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
:language: java :language: java
:start-after: DOCSTART VaultJavaQueryExample4 :start-after: DOCSTART VaultJavaQueryExample4
:end-before: DOCEND VaultJavaQueryExample4 :end-before: DOCEND VaultJavaQueryExample4
:dedent: 12
Troubleshooting Troubleshooting
--------------- ---------------
@ -373,24 +469,27 @@ If the results your were expecting do not match actual returned query results we
Behavioural notes Behavioural notes
----------------- -----------------
1. **TrackBy** updates do not take into account the full criteria specification due to different and more restrictive syntax 1. ``TrackBy`` updates do not take into account the full criteria specification due to different and more restrictive
in `observables <https://github.com/ReactiveX/RxJava/wiki>`_ filtering (vs full SQL-92 JDBC filtering as used in snapshot views). syntax in `observables <https://github.com/ReactiveX/RxJava/wiki>`_ filtering (vs full SQL-92 JDBC filtering as used
Specifically, dynamic updates are filtered by ``contractStateType`` and ``stateType`` (UNCONSUMED, CONSUMED, ALL) only. in snapshot views). Specifically, dynamic updates are filtered by ``contractStateType`` and ``stateType``
2. **QueryBy** and **TrackBy snapshot views** using pagination may return different result sets as each paging request is a (UNCONSUMED, CONSUMED, ALL) only
separate SQL query on the underlying database, and it is entirely conceivable that state modifications are taking 2. ``QueryBy`` and ``TrackBy`` snapshot views using pagination may return different result sets as each paging request
place in between and/or in parallel to paging requests. is a separate SQL query on the underlying database, and it is entirely conceivable that state modifications are
When using pagination, always check the value of the ``totalStatesAvailable`` (from the ``Vault.Page`` result) and taking place in between and/or in parallel to paging requests. When using pagination, always check the value of the
adjust further paging requests appropriately. ``totalStatesAvailable`` (from the ``Vault.Page`` result) and adjust further paging requests appropriately.
Other use case scenarios Other use case scenarios
------------------------ ------------------------
For advanced use cases that require sophisticated pagination, sorting, grouping, and aggregation functions, it is recommended that the CorDapp developer utilise one of the many proven frameworks that ship with this capability out of the box. Namely, implementations of JPQL (JPA Query Language) such as **Hibernate** for advanced SQL access, and **Spring Data** for advanced pagination and ordering constructs. For advanced use cases that require sophisticated pagination, sorting, grouping, and aggregation functions, it is
recommended that the CorDapp developer utilise one of the many proven frameworks that ship with this capability out of
the box. Namely, implementations of JPQL (JPA Query Language) such as Hibernate for advanced SQL access, and
Spring Data for advanced pagination and ordering constructs.
The Corda Tutorials provide examples satisfying these additional Use Cases: The Corda Tutorials provide examples satisfying these additional Use Cases:
1. Template / Tutorial CorDapp service using Vault API Custom Query to access attributes of IOU State 1. Example CorDapp service using Vault API Custom Query to access attributes of IOU State
2. Template / Tutorial CorDapp service query extension executing Named Queries via JPQL_ 2. Example CorDapp service query extension executing Named Queries via JPQL_
3. `Advanced pagination <https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/PagingAndSortingRepository.html>`_ queries using Spring Data JPA_ 3. `Advanced pagination <https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/PagingAndSortingRepository.html>`_ queries using Spring Data JPA_
.. _JPQL: http://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#hql .. _JPQL: http://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#hql

View File

@ -97,7 +97,7 @@ Loading the Yo! CordDapp on your Corda nodes lets you send simple Yo! messages t
* **Loading the Yo! CorDapp onto your nodes** * **Loading the Yo! CorDapp onto your nodes**
The nodes you will use to send and receive Yo messages require the Yo! CorDapp jar file to be saved to their plugins directory. The nodes you will use to send and receive Yo messages require the Yo! CorDapp jar file to be saved to their cordapps directory.
Connect to one of your Corda nodes (make sure this is not the Notary node) using an SSH client of your choice (e.g. Putty) and log into the virtual machine using the public IP address and your SSH key or username / password combination you defined in Step 1 of the Azure build process. Type the following command: Connect to one of your Corda nodes (make sure this is not the Notary node) using an SSH client of your choice (e.g. Putty) and log into the virtual machine using the public IP address and your SSH key or username / password combination you defined in Step 1 of the Azure build process. Type the following command:
@ -105,14 +105,14 @@ For Corda nodes running release M10
.. sourcecode:: shell .. sourcecode:: shell
cd /opt/corda/plugins cd /opt/corda/cordapps
wget http://downloads.corda.net/cordapps/net/corda/yo/0.10.1/yo-0.10.1.jar wget http://downloads.corda.net/cordapps/net/corda/yo/0.10.1/yo-0.10.1.jar
For Corda nodes running release M11 For Corda nodes running release M11
.. sourcecode:: shell .. sourcecode:: shell
cd /opt/corda/plugins cd /opt/corda/cordapps
wget http://downloads.corda.net/cordapps/net/corda/yo/0.11.0/yo-0.11.0.jar wget http://downloads.corda.net/cordapps/net/corda/yo/0.11.0/yo-0.11.0.jar
Now restart Corda and the Corda webserver using the following commands or restart your Corda VM from the Azure portal: Now restart Corda and the Corda webserver using the following commands or restart your Corda VM from the Azure portal:

View File

@ -6,14 +6,19 @@ from the previous milestone release.
UNRELEASED UNRELEASED
---------- ----------
* ``OpaqueBytes.bytes`` now returns a clone of its underlying ``ByteArray``, and has been redeclared as ``final``.
This is a minor change to the public API, but is required to ensure that classes like ``SecureHash`` are immutable.
* ``FlowLogic`` now exposes a series of function called ``receiveAll(...)`` allowing to join ``receive(...)`` instructions. * ``FlowLogic`` now exposes a series of function called ``receiveAll(...)`` allowing to join ``receive(...)`` instructions.
* Renamed "plugins" directory on nodes to "cordapps"
* The ``Cordformation`` gradle plugin has been split into ``cordformation`` and ``cordapp``. The former builds and * The ``Cordformation`` gradle plugin has been split into ``cordformation`` and ``cordapp``. The former builds and
deploys nodes for development and testing, the latter turns a project into a cordapp project that generates JARs in deploys nodes for development and testing, the latter turns a project into a cordapp project that generates JARs in
the standard CorDapp format. the standard CorDapp format.
* ``Cordform`` and node identity generation * ``Cordform`` and node identity generation:
* Cordform may not specify a value for ``NetworkMap``, when that happens, during the task execution the following happens: * Removed the parameter ``NetworkMap`` from Cordform. Now at the end of the deployment the following happens:
1. Each node is started and its signed serialized NodeInfo is written to disk in the node base directory. 1. Each node is started and its signed serialized NodeInfo is written to disk in the node base directory.
2. Every serialized ``NodeInfo`` above is copied in every other node "additional-node-info" folder under the NodeInfo folder. 2. Every serialized ``NodeInfo`` above is copied in every other node "additional-node-info" folder under the NodeInfo folder.
@ -21,7 +26,7 @@ UNRELEASED
* ``Cordapp`` now has a name field for identifying CorDapps and all CorDapp names are printed to console at startup. * ``Cordapp`` now has a name field for identifying CorDapps and all CorDapp names are printed to console at startup.
* Enums now respsect the whitelist applied to the Serializer factory serializing / deserializing them. If the enum isn't * Enums now respect the whitelist applied to the Serializer factory serializing / deserializing them. If the enum isn't
either annotated with the @CordaSerializable annotation or explicitly whitelisted then a NotSerializableException is either annotated with the @CordaSerializable annotation or explicitly whitelisted then a NotSerializableException is
thrown. thrown.
@ -36,6 +41,28 @@ UNRELEASED
* Cordformation node building DSL can have an additional parameter `configFile` with the path to a properties file * Cordformation node building DSL can have an additional parameter `configFile` with the path to a properties file
to be appended to node.conf. to be appended to node.conf.
* ``FlowLogic`` now has a static method called ``sleep`` which can be used in certain circumstances to help with resolving
contention over states in flows. This should be used in place of any other sleep primitive since these are not compatible
with flows and their use will be prevented at some point in the future. Pay attention to the warnings and limitations
described in the documentation for this method. This helps resolve a bug in ``Cash`` coin selection.
A new static property `currentTopLevel` returns the top most `FlowLogic` instance, or null if not in a flow.
* ``CordaService`` annotated classes should be upgraded to take a constructor parameter of type ``AppServiceHub`` which
allows services to start flows marked with the ``StartableByService`` annotation. For backwards compatability
service classes with only ``ServiceHub`` constructors will still work.
* ``TimeWindow`` now has a ``length`` property that returns the length of the time-window, or ``null`` if the
time-window is open-ended.
* A new ``SIGNERS_GROUP`` with ordinal 6 has been added to ``ComponentGroupEnum`` that corresponds to the ``Command``
signers.
* ``PartialMerkleTree`` is equipped with a ``leafIndex`` function that returns the index of a hash (leaf) in the
partial Merkle tree structure.
* A new function ``checkCommandVisibility(publicKey: PublicKey)`` has been added to ``FilteredTransaction`` to check
if every command that a signer should receive (e.g. an Oracle) is indeed visible.
.. _changelog_v1: .. _changelog_v1:
Release 1.0 Release 1.0

View File

@ -74,7 +74,7 @@ The following modules are available but we do not commit to their stability or c
* **net.corda.samples.demos.bankofcorda**: simulates the role of an asset issuing authority (eg. central bank for cash) * **net.corda.samples.demos.bankofcorda**: simulates the role of an asset issuing authority (eg. central bank for cash)
* **net.corda.samples.demos.irs**: demonstrates an Interest Rate Swap agreement between two banks * **net.corda.samples.demos.irs**: demonstrates an Interest Rate Swap agreement between two banks
* **net.corda.samples.demos.notary**: a simple demonstration of a node getting multiple transactions notarised by a distributed (Raft or BFT SMaRt) notary * **net.corda.samples.demos.notary**: a simple demonstration of a node getting multiple transactions notarised by a distributed (Raft or BFT SMaRt) notary
* **net.corda.samples.demos.simmvaluation**: See our [main documentation site](https://docs.corda.net/initial-margin-agreement.html) regarding the SIMM valuation and agreement on a distributed ledger * **net.corda.samples.demos.simmvaluation**: A demo of SIMM valuation and agreement on a distributed ledger
* **net.corda.samples.demos.trader**: demonstrates four nodes, a notary, an issuer of cash (Bank of Corda), and two parties trading with each other, exchanging cash for a commercial paper * **net.corda.samples.demos.trader**: demonstrates four nodes, a notary, an issuer of cash (Bank of Corda), and two parties trading with each other, exchanging cash for a commercial paper
* **net.corda.node.smoke.test.utils**: test utilities for smoke testing * **net.corda.node.smoke.test.utils**: test utilities for smoke testing
* **net.corda.node.test.common**: common test functionality * **net.corda.node.test.common**: common test functionality
@ -90,4 +90,4 @@ The following modules are available but we do not commit to their stability or c
.. warning:: Code inside any package in the ``net.corda`` namespace which contains ``.internal`` or in ``net.corda.node`` for internal use only. .. warning:: Code inside any package in the ``net.corda`` namespace which contains ``.internal`` or in ``net.corda.node`` for internal use only.
Future releases will reject any CorDapps that use types from these packages. Future releases will reject any CorDapps that use types from these packages.
.. warning:: The web server module will be removed in future. You should call Corda nodes through RPC from your web server of choice e.g., Spring Boot, Vertx, Undertow. .. warning:: The web server module will be removed in future. You should call Corda nodes through RPC from your web server of choice e.g., Spring Boot, Vertx, Undertow.

View File

@ -94,7 +94,7 @@ path to the node's base directory.
.. note:: The driver will not automatically create a webserver instance, but the Cordformation will. If this field .. note:: The driver will not automatically create a webserver instance, but the Cordformation will. If this field
is present the web server will start. is present the web server will start.
:notary: Optional config object which if present configures the node to run as a notary. If part of a Raft or BFT SMaRt :notary: Optional configuration object which if present configures the node to run as a notary. If part of a Raft or BFT SMaRt
cluster then specify ``raft`` or ``bftSMaRt`` respectively as described below. If a single node notary then omit both. cluster then specify ``raft`` or ``bftSMaRt`` respectively as described below. If a single node notary then omit both.
:validating: Boolean to determine whether the notary is a validating or non-validating one. :validating: Boolean to determine whether the notary is a validating or non-validating one.
@ -108,11 +108,15 @@ path to the node's base directory.
members must be active and be able to communicate with the cluster leader for joining. If empty, a new members must be active and be able to communicate with the cluster leader for joining. If empty, a new
cluster will be bootstrapped. cluster will be bootstrapped.
:bftSMaRt: If part of a distributed BFT SMaRt cluster specify this config object, with the following settings: :bftSMaRt: If part of a distributed BFT-SMaRt cluster specify this config object, with the following settings:
:replicaId: :replicaId: The zero-based index of the current replica. All replicas must specify a unique replica id.
:clusterAddresses: :clusterAddresses: List of all BFT-SMaRt cluster member addresses.
:custom: If `true`, will load and install a notary service from a CorDapp. See :doc:`tutorial-custom-notary`.
Only one of ``raft``, ``bftSMaRt`` or ``custom`` configuration values may be specified.
:networkMapService: If `null`, or missing the node is declaring itself as the NetworkMapService host. Otherwise this is :networkMapService: If `null`, or missing the node is declaring itself as the NetworkMapService host. Otherwise this is
a config object with the details of the network map service: a config object with the details of the network map service:

View File

@ -78,11 +78,11 @@ For further information about managing dependencies, see
Installing CorDapps Installing CorDapps
------------------- -------------------
At runtime, nodes will load any plugins present in their ``plugins`` folder. Therefore in order to install a cordapp to At runtime, nodes will load any CorDapp JARs present in their ``cordapps`` folder. Therefore in order to install a CorDapp to
a node the cordapp JAR must be added to the ``<node_dir>/plugins/`` folder, where ``node_dir`` is the folder in which the a node the CorDapp JAR must be added to the ``<node_dir>/cordapps/`` folder, where ``node_dir`` is the folder in which the
node's JAR and configuration files are stored). node's JAR and configuration files are stored).
The ``deployNodes`` gradle task, if correctly configured, will automatically place your cordapp JAR as well as any The ``deployNodes`` gradle task, if correctly configured, will automatically place your CorDapp JAR as well as any
dependent cordapp JARs specified into the directory automatically. dependent cordapp JARs specified into the directory automatically.
Example Example

View File

@ -37,13 +37,13 @@ Profiles
notary/ notary/
node.conf node.conf
plugins/ cordapps/
banka/ banka/
node.conf node.conf
plugins/ cordapps/
bankb/ bankb/
node.conf node.conf
plugins/ cordapps/
example-cordapp.jar example-cordapp.jar
... ...
@ -133,7 +133,7 @@ current working directory of the JVM):
corda-webserver.jar corda-webserver.jar
explorer/ explorer/
node-explorer.jar node-explorer.jar
plugins/ cordapps/
bank-of-corda.jar bank-of-corda.jar
.. ..

View File

@ -11,13 +11,12 @@ Cordform is the local node deployment system for CorDapps. The nodes generated a
debugging, and testing node configurations, but not for production or testnet deployment. debugging, and testing node configurations, but not for production or testnet deployment.
Here is an example Gradle task called ``deployNodes`` that uses the Cordform plugin to deploy three nodes, plus a Here is an example Gradle task called ``deployNodes`` that uses the Cordform plugin to deploy three nodes, plus a
notary/network map node: notary node:
.. sourcecode:: groovy .. sourcecode:: groovy
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
directory "./build/nodes" directory "./build/nodes"
networkMap "O=Controller,OU=corda,L=London,C=UK"
node { node {
name "O=Controller,OU=corda,L=London,C=UK" name "O=Controller,OU=corda,L=London,C=UK"
notary = [validating : true] notary = [validating : true]
@ -52,9 +51,7 @@ notary/network map node:
} }
} }
You can extend ``deployNodes`` to generate any number of nodes you like. The only requirement is that you must specify You can extend ``deployNodes`` to generate any number of nodes you like.
one node as running the network map service, by putting their name in the ``networkMap`` field. In our example, the
``Controller`` is set as the network map service.
.. warning:: When adding nodes, make sure that there are no port clashes! .. warning:: When adding nodes, make sure that there are no port clashes!
@ -85,9 +82,14 @@ run all the nodes at once. Each node in the ``nodes`` folder has the following s
.. sourcecode:: none .. sourcecode:: none
. nodeName . nodeName
├── corda.jar // The Corda runtime ├── corda.jar // The Corda runtime
├── node.conf // The node's configuration ├── node.conf // The node's configuration
└── plugins // Any installed CorDapps ├── cordapps // Any installed CorDapps
└── additional-node-infos // Directory containing all the addresses and certificates of the other nodes.
.. note:: During the build process each node generates a NodeInfo file which is written in its own root directory,
the plug-in proceeds and copies each node NodeInfo to every other node ``additional-node-infos`` directory.
The NodeInfo file contains a node hostname and port, legal name and security certificate.
.. note:: Outside of development environments, do not store your node directories in the build folder. .. note:: Outside of development environments, do not store your node directories in the build folder.

View File

@ -1,7 +1,6 @@
apply plugin: 'kotlin' apply plugin: 'kotlin'
apply plugin: 'application' apply plugin: 'application'
apply plugin: 'net.corda.plugins.cordformation' apply plugin: 'net.corda.plugins.cordformation'
apply plugin: 'net.corda.plugins.cordapp'
apply plugin: 'net.corda.plugins.quasar-utils' apply plugin: 'net.corda.plugins.quasar-utils'
repositories { repositories {
@ -73,7 +72,6 @@ task integrationTest(type: Test) {
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
directory "./build/nodes" directory "./build/nodes"
networkMap "O=Notary Service,OU=corda,L=London,C=GB"
node { node {
name "O=Notary Service,OU=corda,L=London,C=GB" name "O=Notary Service,OU=corda,L=London,C=GB"
notary = [validating : true] notary = [validating : true]

View File

@ -0,0 +1,46 @@
package net.corda.docs.java.tutorial.helloworld;
// DOCSTART 01
import net.corda.core.contracts.CommandData;
import net.corda.core.contracts.CommandWithParties;
import net.corda.core.contracts.Contract;
import net.corda.core.identity.Party;
import net.corda.core.transactions.LedgerTransaction;
import java.security.PublicKey;
import java.util.List;
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
import static net.corda.core.contracts.ContractsDSL.requireThat;
public class IOUContract implements Contract {
// Our Create command.
public static class Create implements CommandData {
}
@Override
public void verify(LedgerTransaction tx) {
final CommandWithParties<Create> command = requireSingleCommand(tx.getCommands(), Create.class);
requireThat(check -> {
// Constraints on the shape of the transaction.
check.using("No inputs should be consumed when issuing an IOU.", tx.getInputs().isEmpty());
check.using("There should be one output state of type IOUState.", tx.getOutputs().size() == 1);
// IOU-specific constraints.
final IOUState out = tx.outputsOfType(IOUState.class).get(0);
final Party lender = out.getLender();
final Party borrower = out.getBorrower();
check.using("The IOU's value must be non-negative.", out.getValue() > 0);
check.using("The lender and the borrower cannot be the same entity.", lender != borrower);
// Constraints on the signers.
final List<PublicKey> signers = command.getSigners();
check.using("There must only be one signer.", signers.size() == 1);
check.using("The signer must be the lender.", signers.contains(lender.getOwningKey()));
return null;
});
}
}
// DOCEND 01

View File

@ -0,0 +1,68 @@
package net.corda.docs.java.tutorial.helloworld;
// DOCSTART 01
import co.paralleluniverse.fibers.Suspendable;
import net.corda.core.contracts.Command;
import net.corda.core.contracts.StateAndContract;
import net.corda.core.flows.*;
import net.corda.core.identity.Party;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder;
import net.corda.core.utilities.ProgressTracker;
@InitiatingFlow
@StartableByRPC
public class IOUFlow extends FlowLogic<Void> {
private final Integer iouValue;
private final Party otherParty;
/**
* The progress tracker provides checkpoints indicating the progress of the flow to observers.
*/
private final ProgressTracker progressTracker = new ProgressTracker();
public IOUFlow(Integer iouValue, Party otherParty) {
this.iouValue = iouValue;
this.otherParty = otherParty;
}
@Override
public ProgressTracker getProgressTracker() {
return progressTracker;
}
/**
* The flow logic is encapsulated within the call() method.
*/
@Suspendable
@Override
public Void call() throws FlowException {
// We retrieve the notary identity from the network map.
final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
// We create a transaction builder.
final TransactionBuilder txBuilder = new TransactionBuilder();
txBuilder.setNotary(notary);
// We create the transaction components.
IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty);
String outputContract = IOUContract.class.getName();
StateAndContract outputContractAndState = new StateAndContract(outputState, outputContract);
Command cmd = new Command<>(new IOUContract.Create(), getOurIdentity().getOwningKey());
// We add the items to the builder.
txBuilder.withItems(outputContractAndState, cmd);
// Verifying the transaction.
txBuilder.verify(getServiceHub());
// Signing the transaction.
final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);
// Finalising the transaction.
subFlow(new FinalityFlow(signedTx));
return null;
}
}
// DOCEND 01

View File

@ -0,0 +1,39 @@
package net.corda.docs.java.tutorial.helloworld;
// DOCSTART 01
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.ContractState;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.Party;
import java.util.List;
public class IOUState implements ContractState {
private final int value;
private final Party lender;
private final Party borrower;
public IOUState(int value, Party lender, Party borrower) {
this.value = value;
this.lender = lender;
this.borrower = borrower;
}
public int getValue() {
return value;
}
public Party getLender() {
return lender;
}
public Party getBorrower() {
return borrower;
}
@Override
public List<AbstractParty> getParticipants() {
return ImmutableList.of(lender, borrower);
}
}
// DOCEND 01

View File

@ -0,0 +1,50 @@
package net.corda.docs.java.tutorial.twoparty;
// DOCSTART 01
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.CommandData;
import net.corda.core.contracts.CommandWithParties;
import net.corda.core.contracts.Contract;
import net.corda.core.identity.Party;
import net.corda.core.transactions.LedgerTransaction;
import java.security.PublicKey;
import java.util.List;
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
import static net.corda.core.contracts.ContractsDSL.requireThat;
// DOCEND 01
public class IOUContract implements Contract {
// Our Create command.
public static class Create implements CommandData {
}
@Override
public void verify(LedgerTransaction tx) {
final CommandWithParties<net.corda.docs.java.tutorial.helloworld.IOUContract.Create> command = requireSingleCommand(tx.getCommands(), net.corda.docs.java.tutorial.helloworld.IOUContract.Create.class);
requireThat(check -> {
// Constraints on the shape of the transaction.
check.using("No inputs should be consumed when issuing an IOU.", tx.getInputs().isEmpty());
check.using("There should be one output state of type IOUState.", tx.getOutputs().size() == 1);
// IOU-specific constraints.
final IOUState out = tx.outputsOfType(IOUState.class).get(0);
final Party lender = out.getLender();
final Party borrower = out.getBorrower();
check.using("The IOU's value must be non-negative.", out.getValue() > 0);
check.using("The lender and the borrower cannot be the same entity.", lender != borrower);
// DOCSTART 02
// Constraints on the signers.
final List<PublicKey> signers = command.getSigners();
check.using("There must be two signers.", signers.size() == 2);
check.using("The borrower and lender must be signers.", signers.containsAll(
ImmutableList.of(borrower.getOwningKey(), lender.getOwningKey())));
// DOCEND 02
return null;
});
}
}

View File

@ -0,0 +1,82 @@
package net.corda.docs.java.tutorial.twoparty;
// DOCSTART 01
import co.paralleluniverse.fibers.Suspendable;
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.Command;
import net.corda.core.contracts.StateAndContract;
import net.corda.core.flows.*;
import net.corda.core.identity.Party;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder;
import net.corda.core.utilities.ProgressTracker;
import java.security.PublicKey;
import java.util.List;
// DOCEND 01
@InitiatingFlow
@StartableByRPC
public class IOUFlow extends FlowLogic<Void> {
private final Integer iouValue;
private final Party otherParty;
/**
* The progress tracker provides checkpoints indicating the progress of the flow to observers.
*/
private final ProgressTracker progressTracker = new ProgressTracker();
public IOUFlow(Integer iouValue, Party otherParty) {
this.iouValue = iouValue;
this.otherParty = otherParty;
}
@Override
public ProgressTracker getProgressTracker() {
return progressTracker;
}
/**
* The flow logic is encapsulated within the call() method.
*/
@Suspendable
@Override
public Void call() throws FlowException {
// We retrieve the notary identity from the network map.
final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
// We create a transaction builder.
final TransactionBuilder txBuilder = new TransactionBuilder();
txBuilder.setNotary(notary);
// DOCSTART 02
// We create the transaction components.
IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty);
String outputContract = IOUContract.class.getName();
StateAndContract outputContractAndState = new StateAndContract(outputState, outputContract);
List<PublicKey> requiredSigners = ImmutableList.of(getOurIdentity().getOwningKey(), otherParty.getOwningKey());
Command cmd = new Command<>(new IOUContract.Create(), requiredSigners);
// We add the items to the builder.
txBuilder.withItems(outputContractAndState, cmd);
// Verifying the transaction.
txBuilder.verify(getServiceHub());
// Signing the transaction.
final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);
// Creating a session with the other party.
FlowSession otherpartySession = initiateFlow(otherParty);
// Obtaining the counterparty's signature.
SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(
signedTx, ImmutableList.of(otherpartySession), CollectSignaturesFlow.tracker()));
// Finalising the transaction.
subFlow(new FinalityFlow(fullySignedTx));
return null;
// DOCEND 02
}
}

View File

@ -0,0 +1,47 @@
package net.corda.docs.java.tutorial.twoparty;
// DOCSTART 01
import co.paralleluniverse.fibers.Suspendable;
import net.corda.core.contracts.ContractState;
import net.corda.core.flows.*;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.utilities.ProgressTracker;
import net.corda.docs.java.tutorial.helloworld.IOUFlow;
import net.corda.docs.java.tutorial.helloworld.IOUState;
import static net.corda.core.contracts.ContractsDSL.requireThat;
@InitiatedBy(IOUFlow.class)
public class IOUFlowResponder extends FlowLogic<Void> {
private final FlowSession otherPartySession;
public IOUFlowResponder(FlowSession otherPartySession) {
this.otherPartySession = otherPartySession;
}
@Suspendable
@Override
public Void call() throws FlowException {
class SignTxFlow extends SignTransactionFlow {
private SignTxFlow(FlowSession otherPartySession, ProgressTracker progressTracker) {
super(otherPartySession, progressTracker);
}
@Override
protected void checkTransaction(SignedTransaction stx) {
requireThat(require -> {
ContractState output = stx.getTx().getOutputs().get(0).getData();
require.using("This must be an IOU transaction.", output instanceof IOUState);
IOUState iou = (IOUState) output;
require.using("The IOU's value can't be too high.", iou.getValue() < 100);
return null;
});
}
}
subFlow(new SignTxFlow(otherPartySession, SignTransactionFlow.Companion.tracker()));
return null;
}
}
// DOCEND 01

View File

@ -0,0 +1,37 @@
package net.corda.docs.java.tutorial.twoparty;
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.ContractState;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.Party;
import java.util.List;
public class IOUState implements ContractState {
private final int value;
private final Party lender;
private final Party borrower;
public IOUState(int value, Party lender, Party borrower) {
this.value = value;
this.lender = lender;
this.borrower = borrower;
}
public int getValue() {
return value;
}
public Party getLender() {
return lender;
}
public Party getBorrower() {
return borrower;
}
@Override
public List<AbstractParty> getParticipants() {
return ImmutableList.of(lender, borrower);
}
}

View File

@ -4,7 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount
import net.corda.core.flows.* import net.corda.core.flows.*
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub import net.corda.core.node.AppServiceHub
import net.corda.core.node.services.CordaService import net.corda.core.node.services.CordaService
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
@ -23,7 +23,7 @@ import java.util.*
object CustomVaultQuery { object CustomVaultQuery {
@CordaService @CordaService
class Service(val services: ServiceHub) : SingletonSerializeAsToken() { class Service(val services: AppServiceHub) : SingletonSerializeAsToken() {
private companion object { private companion object {
val log = loggerFor<Service>() val log = loggerFor<Service>()
} }
@ -49,7 +49,7 @@ object CustomVaultQuery {
val session = services.jdbcSession() val session = services.jdbcSession()
val prepStatement = session.prepareStatement(nativeQuery) val prepStatement = session.prepareStatement(nativeQuery)
val rs = prepStatement.executeQuery() val rs = prepStatement.executeQuery()
var topUpLimits: MutableList<Amount<Currency>> = mutableListOf() val topUpLimits: MutableList<Amount<Currency>> = mutableListOf()
while (rs.next()) { while (rs.next()) {
val currencyStr = rs.getString(1) val currencyStr = rs.getString(1)
val amount = rs.getLong(2) val amount = rs.getLong(2)

View File

@ -0,0 +1,33 @@
package net.corda.docs.tutorial.helloworld
// DOCSTART 01
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.Contract
import net.corda.core.contracts.requireSingleCommand
import net.corda.core.contracts.requireThat
import net.corda.core.transactions.LedgerTransaction
class IOUContract : Contract {
// Our Create command.
class Create : CommandData
override fun verify(tx: LedgerTransaction) {
val command = tx.commands.requireSingleCommand<Create>()
requireThat {
// Constraints on the shape of the transaction.
"No inputs should be consumed when issuing an IOU." using (tx.inputs.isEmpty())
"There should be one output state of type IOUState." using (tx.outputs.size == 1)
// IOU-specific constraints.
val out = tx.outputsOfType<IOUState>().single()
"The IOU's value must be non-negative." using (out.value > 0)
"The lender and the borrower cannot be the same entity." using (out.lender != out.borrower)
// Constraints on the signers.
"There must only be one signer." using (command.signers.toSet().size == 1)
"The signer must be the lender." using (command.signers.contains(out.lender.owningKey))
}
}
}
// DOCEND 01

View File

@ -0,0 +1,52 @@
package net.corda.docs.tutorial.helloworld
// DOCSTART 01
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Command
import net.corda.core.contracts.StateAndContract
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
import kotlin.reflect.jvm.jvmName
@InitiatingFlow
@StartableByRPC
class IOUFlow(val iouValue: Int,
val otherParty: Party) : FlowLogic<Unit>() {
/** The progress tracker provides checkpoints indicating the progress of the flow to observers. */
override val progressTracker = ProgressTracker()
/** The flow logic is encapsulated within the call() method. */
@Suspendable
override fun call() {
// We retrieve the notary identity from the network map.
val notary = serviceHub.networkMapCache.notaryIdentities[0]
// We create a transaction builder
val txBuilder = TransactionBuilder(notary = notary)
// We create the transaction components.
val outputState = IOUState(iouValue, ourIdentity, otherParty)
val outputContract = IOUContract::class.jvmName
val outputContractAndState = StateAndContract(outputState, outputContract)
val cmd = Command(IOUContract.Create(), ourIdentity.owningKey)
// We add the items to the builder.
txBuilder.withItems(outputContractAndState, cmd)
// Verifying the transaction.
txBuilder.verify(serviceHub)
// Signing the transaction.
val signedTx = serviceHub.signInitialTransaction(txBuilder)
// Finalising the transaction.
subFlow(FinalityFlow(signedTx))
}
}
// DOCEND 01

View File

@ -0,0 +1,12 @@
package net.corda.docs.tutorial.helloworld
// DOCSTART 01
import net.corda.core.contracts.ContractState
import net.corda.core.identity.Party
class IOUState(val value: Int,
val lender: Party,
val borrower: Party) : ContractState {
override val participants get() = listOf(lender, borrower)
}
// DOCEND 01

View File

@ -0,0 +1,36 @@
package net.corda.docs.tutorial.twoparty
// DOCSTART 01
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.Contract
import net.corda.core.contracts.requireSingleCommand
import net.corda.core.contracts.requireThat
import net.corda.core.transactions.LedgerTransaction
// DOCEND 01
class IOUContract : Contract {
// Our Create command.
class Create : CommandData
override fun verify(tx: LedgerTransaction) {
val command = tx.commands.requireSingleCommand<Create>()
requireThat {
// Constraints on the shape of the transaction.
"No inputs should be consumed when issuing an IOU." using (tx.inputs.isEmpty())
"There should be one output state of type IOUState." using (tx.outputs.size == 1)
// IOU-specific constraints.
val out = tx.outputsOfType<net.corda.docs.tutorial.helloworld.IOUState>().single()
"The IOU's value must be non-negative." using (out.value > 0)
"The lender and the borrower cannot be the same entity." using (out.lender != out.borrower)
// DOCSTART 02
// Constraints on the signers.
"There must be two signers." using (command.signers.toSet().size == 2)
"The borrower and lender must be signers." using (command.signers.containsAll(listOf(
out.borrower.owningKey, out.lender.owningKey)))
// DOCEND 02
}
}
}

View File

@ -0,0 +1,57 @@
package net.corda.docs.tutorial.twoparty
// DOCSTART 01
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Command
import net.corda.core.contracts.StateAndContract
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
import kotlin.reflect.jvm.jvmName
// DOCEND 01
@InitiatingFlow
@StartableByRPC
class IOUFlow(val iouValue: Int,
val otherParty: Party) : FlowLogic<Unit>() {
/** The progress tracker provides checkpoints indicating the progress of the flow to observers. */
override val progressTracker = ProgressTracker()
/** The flow logic is encapsulated within the call() method. */
@Suspendable
override fun call() {
// We retrieve the notary identity from the network map.
val notary = serviceHub.networkMapCache.notaryIdentities[0]
// We create a transaction builder
val txBuilder = TransactionBuilder(notary = notary)
// DOCSTART 02
// We create the transaction components.
val outputState = IOUState(iouValue, ourIdentity, otherParty)
val outputContract = IOUContract::class.jvmName
val outputContractAndState = StateAndContract(outputState, outputContract)
val cmd = Command(IOUContract.Create(), listOf(ourIdentity.owningKey, otherParty.owningKey))
// We add the items to the builder.
txBuilder.withItems(outputContractAndState, cmd)
// Verifying the transaction.
txBuilder.verify(serviceHub)
// Signing the transaction.
val signedTx = serviceHub.signInitialTransaction(txBuilder)
// Creating a session with the other party.
val otherpartySession = initiateFlow(otherParty)
// Obtaining the counterparty's signature.
val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, listOf(otherpartySession), CollectSignaturesFlow.tracker()))
// Finalising the transaction.
subFlow(FinalityFlow(fullySignedTx))
// DOCEND 02
}
}

View File

@ -0,0 +1,30 @@
package net.corda.docs.tutorial.twoparty
// DOCSTART 01
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.requireThat
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.SignTransactionFlow
import net.corda.core.transactions.SignedTransaction
import net.corda.docs.tutorial.helloworld.IOUFlow
import net.corda.docs.tutorial.helloworld.IOUState
@InitiatedBy(IOUFlow::class)
class IOUFlowResponder(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val signTransactionFlow = object : SignTransactionFlow(otherPartySession, SignTransactionFlow.tracker()) {
override fun checkTransaction(stx: SignedTransaction) = requireThat {
val output = stx.tx.outputs.single().data
"This must be an IOU transaction." using (output is IOUState)
val iou = output as IOUState
"The IOU's value can't be too high." using (iou.value < 100)
}
}
subFlow(signTransactionFlow)
}
}
// DOCEND 01

View File

@ -0,0 +1,10 @@
package net.corda.docs.tutorial.twoparty
import net.corda.core.contracts.ContractState
import net.corda.core.identity.Party
class IOUState(val value: Int,
val lender: Party,
val borrower: Party) : ContractState {
override val participants get() = listOf(lender, borrower)
}

View File

@ -2,6 +2,7 @@ package net.corda.docs
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.packageName
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.finance.* import net.corda.finance.*
@ -26,23 +27,18 @@ class CustomVaultQueryTest {
@Before @Before
fun setup() { fun setup() {
setCordappPackages("net.corda.finance.contracts.asset") mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName))
mockNet = MockNetwork(threadPerNode = true)
mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name) mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name)
nodeA = mockNet.createPartyNode() nodeA = mockNet.createPartyNode()
nodeB = mockNet.createPartyNode() nodeB = mockNet.createPartyNode()
nodeA.internals.registerInitiatedFlow(TopupIssuerFlow.TopupIssuer::class.java) nodeA.internals.registerInitiatedFlow(TopupIssuerFlow.TopupIssuer::class.java)
nodeA.internals.installCordaService(CustomVaultQuery.Service::class.java) nodeA.installCordaService(CustomVaultQuery.Service::class.java)
nodeA.internals.registerCustomSchemas(setOf(CashSchemaV1))
nodeB.internals.registerCustomSchemas(setOf(CashSchemaV1))
notary = nodeA.services.getDefaultNotary() notary = nodeA.services.getDefaultNotary()
} }
@After @After
fun cleanUp() { fun cleanUp() {
mockNet.stopNodes() mockNet.stopNodes()
unsetCordappPackages()
} }
@Test @Test

View File

@ -1,6 +1,7 @@
package net.corda.docs package net.corda.docs
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.packageName
import net.corda.core.toFuture import net.corda.core.toFuture
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
@ -24,13 +25,10 @@ class FxTransactionBuildTutorialTest {
@Before @Before
fun setup() { fun setup() {
setCordappPackages("net.corda.finance.contracts.asset") mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName))
mockNet = MockNetwork(threadPerNode = true)
mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name) mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name)
nodeA = mockNet.createPartyNode() nodeA = mockNet.createPartyNode()
nodeB = mockNet.createPartyNode() nodeB = mockNet.createPartyNode()
nodeA.internals.registerCustomSchemas(setOf(CashSchemaV1))
nodeB.internals.registerCustomSchemas(setOf(CashSchemaV1))
nodeB.internals.registerInitiatedFlow(ForeignExchangeRemoteFlow::class.java) nodeB.internals.registerInitiatedFlow(ForeignExchangeRemoteFlow::class.java)
notary = nodeA.services.getDefaultNotary() notary = nodeA.services.getDefaultNotary()
} }
@ -38,7 +36,6 @@ class FxTransactionBuildTutorialTest {
@After @After
fun cleanUp() { fun cleanUp() {
mockNet.stopNodes() mockNet.stopNodes()
unsetCordappPackages()
} }
@Test @Test

View File

@ -3,17 +3,15 @@ package net.corda.docs
import net.corda.core.contracts.LinearState import net.corda.core.contracts.LinearState
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.UniqueIdentifier import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.services.queryBy import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.toFuture import net.corda.core.toFuture
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.StartedNode import net.corda.node.services.api.StartedNodeServices
import net.corda.testing.DUMMY_NOTARY import net.corda.testing.*
import net.corda.testing.chooseIdentity
import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork
import net.corda.testing.setCordappPackages
import net.corda.testing.unsetCordappPackages
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -21,8 +19,10 @@ import kotlin.test.assertEquals
class WorkflowTransactionBuildTutorialTest { class WorkflowTransactionBuildTutorialTest {
lateinit var mockNet: MockNetwork lateinit var mockNet: MockNetwork
lateinit var nodeA: StartedNode<MockNetwork.MockNode> lateinit var aliceServices: StartedNodeServices
lateinit var nodeB: StartedNode<MockNetwork.MockNode> lateinit var bobServices: StartedNodeServices
lateinit var alice: Party
lateinit var bob: Party
// Helper method to locate the latest Vault version of a LinearState // Helper method to locate the latest Vault version of a LinearState
private inline fun <reified T : LinearState> ServiceHub.latest(ref: UniqueIdentifier): StateAndRef<T> { private inline fun <reified T : LinearState> ServiceHub.latest(ref: UniqueIdentifier): StateAndRef<T> {
@ -32,67 +32,70 @@ class WorkflowTransactionBuildTutorialTest {
@Before @Before
fun setup() { fun setup() {
setCordappPackages("net.corda.docs") mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.docs"))
mockNet = MockNetwork(threadPerNode = true) // While we don't use the notary, we need there to be one on the network
mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name) mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name)
nodeA = mockNet.createPartyNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME)
nodeB = mockNet.createPartyNode() val bobNode = mockNet.createPartyNode(BOB_NAME)
nodeA.internals.registerInitiatedFlow(RecordCompletionFlow::class.java) aliceNode.internals.registerInitiatedFlow(RecordCompletionFlow::class.java)
aliceServices = aliceNode.services
bobServices = bobNode.services
alice = aliceNode.services.myInfo.identityFromX500Name(ALICE_NAME)
bob = bobNode.services.myInfo.identityFromX500Name(BOB_NAME)
} }
@After @After
fun cleanUp() { fun cleanUp() {
mockNet.stopNodes() mockNet.stopNodes()
unsetCordappPackages()
} }
@Test @Test
fun `Run workflow to completion`() { fun `Run workflow to completion`() {
// Setup a vault subscriber to wait for successful upload of the proposal to NodeB // Setup a vault subscriber to wait for successful upload of the proposal to NodeB
val nodeBVaultUpdate = nodeB.services.vaultService.updates.toFuture() val nodeBVaultUpdate = bobServices.vaultService.updates.toFuture()
// Kick of the proposal flow // Kick of the proposal flow
val flow1 = nodeA.services.startFlow(SubmitTradeApprovalFlow("1234", nodeB.info.chooseIdentity())) val flow1 = aliceServices.startFlow(SubmitTradeApprovalFlow("1234", bob))
// Wait for the flow to finish // Wait for the flow to finish
val proposalRef = flow1.resultFuture.getOrThrow() val proposalRef = flow1.resultFuture.getOrThrow()
val proposalLinearId = proposalRef.state.data.linearId val proposalLinearId = proposalRef.state.data.linearId
// Wait for NodeB to include it's copy in the vault // Wait for NodeB to include it's copy in the vault
nodeBVaultUpdate.get() nodeBVaultUpdate.get()
// Fetch the latest copy of the state from both nodes // Fetch the latest copy of the state from both nodes
val latestFromA = nodeA.database.transaction { val latestFromA = aliceServices.database.transaction {
nodeA.services.latest<TradeApprovalContract.State>(proposalLinearId) aliceServices.latest<TradeApprovalContract.State>(proposalLinearId)
} }
val latestFromB = nodeB.database.transaction { val latestFromB = bobServices.database.transaction {
nodeB.services.latest<TradeApprovalContract.State>(proposalLinearId) bobServices.latest<TradeApprovalContract.State>(proposalLinearId)
} }
// Confirm the state as as expected // Confirm the state as as expected
assertEquals(WorkflowState.NEW, proposalRef.state.data.state) assertEquals(WorkflowState.NEW, proposalRef.state.data.state)
assertEquals("1234", proposalRef.state.data.tradeId) assertEquals("1234", proposalRef.state.data.tradeId)
assertEquals(nodeA.info.chooseIdentity(), proposalRef.state.data.source) assertEquals(alice, proposalRef.state.data.source)
assertEquals(nodeB.info.chooseIdentity(), proposalRef.state.data.counterparty) assertEquals(bob, proposalRef.state.data.counterparty)
assertEquals(proposalRef, latestFromA) assertEquals(proposalRef, latestFromA)
assertEquals(proposalRef, latestFromB) assertEquals(proposalRef, latestFromB)
// Setup a vault subscriber to pause until the final update is in NodeA and NodeB // Setup a vault subscriber to pause until the final update is in NodeA and NodeB
val nodeAVaultUpdate = nodeA.services.vaultService.updates.toFuture() val nodeAVaultUpdate = aliceServices.vaultService.updates.toFuture()
val secondNodeBVaultUpdate = nodeB.services.vaultService.updates.toFuture() val secondNodeBVaultUpdate = bobServices.vaultService.updates.toFuture()
// Run the manual completion flow from NodeB // Run the manual completion flow from NodeB
val flow2 = nodeB.services.startFlow(SubmitCompletionFlow(latestFromB.ref, WorkflowState.APPROVED)) val flow2 = bobServices.startFlow(SubmitCompletionFlow(latestFromB.ref, WorkflowState.APPROVED))
// wait for the flow to end // wait for the flow to end
val completedRef = flow2.resultFuture.getOrThrow() val completedRef = flow2.resultFuture.getOrThrow()
// wait for the vault updates to stabilise // wait for the vault updates to stabilise
nodeAVaultUpdate.get() nodeAVaultUpdate.get()
secondNodeBVaultUpdate.get() secondNodeBVaultUpdate.get()
// Fetch the latest copies from the vault // Fetch the latest copies from the vault
val finalFromA = nodeA.database.transaction { val finalFromA = aliceServices.database.transaction {
nodeA.services.latest<TradeApprovalContract.State>(proposalLinearId) aliceServices.latest<TradeApprovalContract.State>(proposalLinearId)
} }
val finalFromB = nodeB.database.transaction { val finalFromB = bobServices.database.transaction {
nodeB.services.latest<TradeApprovalContract.State>(proposalLinearId) bobServices.latest<TradeApprovalContract.State>(proposalLinearId)
} }
// Confirm the state is as expected // Confirm the state is as expected
assertEquals(WorkflowState.APPROVED, completedRef.state.data.state) assertEquals(WorkflowState.APPROVED, completedRef.state.data.state)
assertEquals("1234", completedRef.state.data.tradeId) assertEquals("1234", completedRef.state.data.tradeId)
assertEquals(nodeA.info.chooseIdentity(), completedRef.state.data.source) assertEquals(alice, completedRef.state.data.source)
assertEquals(nodeB.info.chooseIdentity(), completedRef.state.data.counterparty) assertEquals(bob, completedRef.state.data.counterparty)
assertEquals(completedRef, finalFromA) assertEquals(completedRef, finalFromA)
assertEquals(completedRef, finalFromB) assertEquals(completedRef, finalFromB)
} }

View File

@ -78,80 +78,15 @@ Let's write a contract that enforces these constraints. We'll do this by modifyi
.. container:: codeset .. container:: codeset
.. code-block:: kotlin .. literalinclude:: example-code/src/main/kotlin/net/corda/docs/tutorial/helloworld/contract.kt
:language: kotlin
:start-after: DOCSTART 01
:end-before: DOCEND 01
... .. literalinclude:: example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUContract.java
:language: java
import net.corda.core.contracts.* :start-after: DOCSTART 01
:end-before: DOCEND 01
...
class IOUContract : Contract {
// Our Create command.
class Create : CommandData
override fun verify(tx: LedgerTransaction) {
val command = tx.commands.requireSingleCommand<Create>()
requireThat {
// Constraints on the shape of the transaction.
"No inputs should be consumed when issuing an IOU." using (tx.inputs.isEmpty())
"There should be one output state of type IOUState." using (tx.outputs.size == 1)
// IOU-specific constraints.
val out = tx.outputs.single().data as IOUState
"The IOU's value must be non-negative." using (out.value > 0)
"The lender and the borrower cannot be the same entity." using (out.lender != out.borrower)
// Constraints on the signers.
"There must only be one signer." using (command.signers.toSet().size == 1)
"The signer must be the lender." using (command.signers.contains(out.lender.owningKey))
}
}
}
.. code-block:: java
package com.template.contract;
import com.template.state.IOUState;
import net.corda.core.contracts.CommandWithParties;
import net.corda.core.contracts.CommandData;
import net.corda.core.contracts.Contract;
import net.corda.core.transactions.LedgerTransaction;
import net.corda.core.identity.Party;
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
import static net.corda.core.contracts.ContractsDSL.requireThat;
public class IOUContract implements Contract {
// Our Create command.
public static class Create implements CommandData {}
@Override
public void verify(LedgerTransaction tx) {
final CommandWithParties<Create> command = requireSingleCommand(tx.getCommands(), Create.class);
requireThat(check -> {
// Constraints on the shape of the transaction.
check.using("No inputs should be consumed when issuing an IOU.", tx.getInputs().isEmpty());
check.using("There should be one output state of type IOUState.", tx.getOutputs().size() == 1);
// IOU-specific constraints.
final IOUState out = (IOUState) tx.getOutputs().get(0).getData();
final Party lender = out.getLender();
final Party borrower = out.getBorrower();
check.using("The IOU's value must be non-negative.",out.getValue() > 0);
check.using("The lender and the borrower cannot be the same entity.", lender != borrower);
// Constraints on the signers.
check.using("There must only be one signer.", command.getSigners().size() == 1);
check.using("The signer must be the lender.", command.getSigners().contains(lender.getOwningKey()));
return null;
});
}
}
If you're following along in Java, you'll also need to rename ``TemplateContract.java`` to ``IOUContract.java``. If you're following along in Java, you'll also need to rename ``TemplateContract.java`` to ``IOUContract.java``.

View File

@ -33,128 +33,20 @@ FlowLogic
Flows are implemented as ``FlowLogic`` subclasses. You define the steps taken by the flow by overriding Flows are implemented as ``FlowLogic`` subclasses. You define the steps taken by the flow by overriding
``FlowLogic.call``. ``FlowLogic.call``.
We'll write our flow in either ``TemplateFlow.java`` or ``App.kt``. Overwrite both the existing flows in the template We'll write our flow in either ``TemplateFlow.java`` or ``App.kt``. Delete both the existing flows in the template, and
with the following: replace them with the following:
.. container:: codeset .. container:: codeset
.. code-block:: kotlin .. literalinclude:: example-code/src/main/kotlin/net/corda/docs/tutorial/helloworld/flow.kt
:language: kotlin
:start-after: DOCSTART 01
:end-before: DOCEND 01
... .. literalinclude:: example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUFlow.java
:language: java
import net.corda.core.utilities.ProgressTracker :start-after: DOCSTART 01
import net.corda.core.transactions.TransactionBuilder :end-before: DOCEND 01
import net.corda.core.flows.*
...
@InitiatingFlow
@StartableByRPC
class IOUFlow(val iouValue: Int,
val otherParty: Party) : FlowLogic<Unit>() {
/** The progress tracker provides checkpoints indicating the progress of the flow to observers. */
override val progressTracker = ProgressTracker()
/** The flow logic is encapsulated within the call() method. */
@Suspendable
override fun call() {
// We retrieve the notary identity from the network map.
val notary = serviceHub.networkMapCache.notaryIdentities[0]
// We create a transaction builder
val txBuilder = TransactionBuilder(notary = notary)
// We create the transaction components.
val outputState = IOUState(iouValue, ourIdentity, otherParty)
val outputContract = IOUContract::class.jvmName
val outputContractAndState = StateAndContract(outputState, outputContract)
val cmd = Command(IOUContract.Create(), ourIdentity.owningKey)
// We add the items to the builder.
txBuilder.withItems(outputContractAndState, cmd)
// Verifying the transaction.
txBuilder.verify(serviceHub)
// Signing the transaction.
val signedTx = serviceHub.signInitialTransaction(txBuilder)
// Finalising the transaction.
subFlow(FinalityFlow(signedTx))
}
}
.. code-block:: java
package com.template.flow;
import co.paralleluniverse.fibers.Suspendable;
import com.template.contract.IOUContract;
import com.template.state.IOUState;
import net.corda.core.contracts.Command;
import net.corda.core.contracts.StateAndContract;
import net.corda.core.flows.*;
import net.corda.core.identity.Party;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder;
import net.corda.core.utilities.ProgressTracker;
@InitiatingFlow
@StartableByRPC
public class IOUFlow extends FlowLogic<Void> {
private final Integer iouValue;
private final Party otherParty;
/**
* The progress tracker provides checkpoints indicating the progress of the flow to observers.
*/
private final ProgressTracker progressTracker = new ProgressTracker();
public IOUFlow(Integer iouValue, Party otherParty) {
this.iouValue = iouValue;
this.otherParty = otherParty;
}
@Override
public ProgressTracker getProgressTracker() {
return progressTracker;
}
/**
* The flow logic is encapsulated within the call() method.
*/
@Suspendable
@Override
public Void call() throws FlowException {
// We retrieve the notary identity from the network map.
final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
// We create a transaction builder.
final TransactionBuilder txBuilder = new TransactionBuilder();
txBuilder.setNotary(notary);
// We create the transaction components.
IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty);
String outputContract = IOUContract.class.getName();
StateAndContract outputContractAndState = new StateAndContract(outputState, outputContract);
Command cmd = new Command<>(new IOUContract.Create(), getOurIdentity().getOwningKey());
// We add the items to the builder.
txBuilder.withItems(outputContractAndState, cmd);
// Verifying the transaction.
txBuilder.verify(getServiceHub());
// Signing the transaction.
final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);
// Finalising the transaction.
subFlow(new FinalityFlow(signedTx));
return null;
}
}
If you're following along in Java, you'll also need to rename ``TemplateFlow.java`` to ``IOUFlow.java``. If you're following along in Java, you'll also need to rename ``TemplateFlow.java`` to ``IOUFlow.java``.

View File

@ -5,7 +5,7 @@ By this point, :doc:`your dev environment should be set up <getting-set-up>`, yo
:doc:`your first CorDapp <tutorial-cordapp>`, and you're familiar with Corda's :doc:`key concepts <key-concepts>`. What :doc:`your first CorDapp <tutorial-cordapp>`, and you're familiar with Corda's :doc:`key concepts <key-concepts>`. What
comes next? comes next?
If you're a developer, the next step is to write your own CorDapp. Each CorDapp takes the form of a plugin that is If you're a developer, the next step is to write your own CorDapp. Each CorDapp takes the form of a JAR that is
installed on one or more Corda nodes, and gives them the ability to conduct some new process - anything from installed on one or more Corda nodes, and gives them the ability to conduct some new process - anything from
issuing a debt instrument to making a restaurant booking. issuing a debt instrument to making a restaurant booking.

View File

@ -17,39 +17,36 @@ Kotlin) file. We won't be using it, and it will cause build errors unless we rem
Deploying our CorDapp Deploying our CorDapp
--------------------- ---------------------
Let's take a look at the nodes we're going to deploy. Open the project's ``build.gradle`` file and scroll down to the Let's take a look at the nodes we're going to deploy. Open the project's ``build.gradle`` file and scroll down to the
``task deployNodes`` section. This section defines three nodes - the Controller, NodeA, and NodeB: ``task deployNodes`` section. This section defines three nodes - the Controller, PartyA, and PartyB:
.. container:: codeset .. code:: bash
.. code-block:: kotlin task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
directory "./build/nodes"
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { node {
directory "./build/nodes" name "O=Controller,L=London,C=GB"
networkMap "O=Controller,L=London,C=GB" advertisedServices = ["corda.notary.validating"]
node { p2pPort 10002
name "O=Controller,L=London,C=GB" rpcPort 10003
notary = [validating : true] cordapps = ["net.corda:corda-finance:$corda_release_version"]
p2pPort 10002
rpcPort 10003
cordapps = ["net.corda:corda-finance:$corda_release_version"]
}
node {
name "O=PartyA,L=London,C=GB"
p2pPort 10005
rpcPort 10006
webPort 10007
cordapps = ["net.corda:corda-finance:$corda_release_version"]
rpcUsers = [[ user: "user1", "password": "test", "permissions": []]]
}
node {
name "O=PartyB,L=New York,C=US"
p2pPort 10008
rpcPort 10009
webPort 10010
cordapps = ["net.corda:corda-finance:$corda_release_version"]
rpcUsers = [[ user: "user1", "password": "test", "permissions": []]]
}
} }
node {
name "O=PartyA,L=London,C=GB"
p2pPort 10005
rpcPort 10006
webPort 10007
cordapps = ["net.corda:corda-finance:$corda_release_version"]
rpcUsers = [[ user: "user1", "password": "test", "permissions": []]]
}
node {
name "O=PartyB,L=New York,C=US"
p2pPort 10008
rpcPort 10009
webPort 10010
cordapps = ["net.corda:corda-finance:$corda_release_version"]
rpcUsers = [[ user: "user1", "password": "test", "permissions": []]]
}
}
We have three standard nodes, plus a special Controller node that is running the network map service, and is also We have three standard nodes, plus a special Controller node that is running the network map service, and is also
advertising a validating notary service. Feel free to add additional node definitions here to expand the size of the advertising a validating notary service. Feel free to add additional node definitions here to expand the size of the
@ -62,7 +59,7 @@ We can run this ``deployNodes`` task using Gradle. For each node definition, Gra
We can do that now by running the following commands from the root of the project: We can do that now by running the following commands from the root of the project:
.. code:: python .. code:: bash
// On Windows // On Windows
gradlew clean deployNodes gradlew clean deployNodes
@ -75,19 +72,18 @@ Running the nodes
Running ``deployNodes`` will build the nodes under ``build/nodes``. If we navigate to one of these folders, we'll see Running ``deployNodes`` will build the nodes under ``build/nodes``. If we navigate to one of these folders, we'll see
the three node folders. Each node folder has the following structure: the three node folders. Each node folder has the following structure:
.. code:: python .. code:: bash
. .
|____corda.jar // The runnable node |____corda.jar // The runnable node
|____corda-webserver.jar // The node's webserver |____corda-webserver.jar // The node's webserver
|____dependencies
|____node.conf // The node's configuration file |____node.conf // The node's configuration file
|____plugins |____cordapps
|____java/kotlin-source-0.1.jar // Our IOU CorDapp |____java/kotlin-source-0.1.jar // Our IOU CorDapp
Let's start the nodes by running the following commands from the root of the project: Let's start the nodes by running the following commands from the root of the project:
.. code:: python .. code:: bash
// On Windows // On Windows
build/nodes/runnodes.bat build/nodes/runnodes.bat
@ -136,7 +132,7 @@ will display a list of the available commands. We can examine the contents of a
The vaults of PartyA and PartyB should both display the following output: The vaults of PartyA and PartyB should both display the following output:
.. code:: python .. code:: bash
states: states:
- state: - state:
@ -192,11 +188,9 @@ There are a number of improvements we could make to this CorDapp:
* We could add an API, to make it easier to interact with the CorDapp * We could add an API, to make it easier to interact with the CorDapp
We will explore some of these improvements in future tutorials. But you should now be ready to develop your own We will explore some of these improvements in future tutorials. But you should now be ready to develop your own
CorDapps. There's `a more fleshed-out version of the IOU CorDapp <https://github.com/corda/cordapp-example>`_ with an CorDapps. You can find a list of sample CorDapps `here <https://www.corda.net/samples/>`_.
API and web front-end, and a set of example CorDapps in `the main Corda repo <https://github.com/corda/corda>`_, under
``samples``. An explanation of how to run these samples :doc:`here <running-the-demos>`.
As you write CorDapps, you can learn more about the API available :doc:`here <api>`. As you write CorDapps, you can learn more about the Corda API :doc:`here <corda-api>`.
If you get stuck at any point, please reach out on `Slack <https://slack.corda.net/>`_, If you get stuck at any point, please reach out on `Slack <https://slack.corda.net/>`_,
`Discourse <https://discourse.corda.net/>`_, or `Stack Overflow <https://stackoverflow.com/questions/tagged/corda>`_. `Discourse <https://discourse.corda.net/>`_, or `Stack Overflow <https://stackoverflow.com/questions/tagged/corda>`_.

View File

@ -63,53 +63,15 @@ define an ``IOUState``:
.. container:: codeset .. container:: codeset
.. code-block:: kotlin .. literalinclude:: example-code/src/main/kotlin/net/corda/docs/tutorial/helloworld/state.kt
:language: kotlin
:start-after: DOCSTART 01
:end-before: DOCEND 01
class IOUState(val value: Int, .. literalinclude:: example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUState.java
val lender: Party, :language: java
val borrower: Party) : ContractState { :start-after: DOCSTART 01
override val participants get() = listOf(lender, borrower) :end-before: DOCEND 01
}
.. code-block:: java
package com.template.state;
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.ContractState;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.Party;
import java.util.List;
public class IOUState implements ContractState {
private final int value;
private final Party lender;
private final Party borrower;
public IOUState(int value, Party lender, Party borrower) {
this.value = value;
this.lender = lender;
this.borrower = borrower;
}
public int getValue() {
return value;
}
public Party getLender() {
return lender;
}
public Party getBorrower() {
return borrower;
}
@Override
public List<AbstractParty> getParticipants() {
return ImmutableList.of(lender, borrower);
}
}
If you're following along in Java, you'll also need to rename ``TemplateState.java`` to ``IOUState.java``. If you're following along in Java, you'll also need to rename ``TemplateState.java`` to ``IOUState.java``.

View File

@ -42,18 +42,23 @@ implement our IOU CorDapp in Java, we'll need to modify three files. For Kotlin,
.. code-block:: java .. code-block:: java
// 1. The state // 1. The state
src/main/java/com/template/state/TemplateState.java src/main/java/com/template/TemplateState.java
// 2. The contract // 2. The contract
src/main/java/com/template/contract/TemplateContract.java src/main/java/com/template/TemplateContract.java
// 3. The flow // 3. The flow
src/main/java/com/template/flow/TemplateFlow.java src/main/java/com/template/TemplateFlow.java
.. code-block:: kotlin .. code-block:: kotlin
src/main/kotlin/com/template/App.kt src/main/kotlin/com/template/App.kt
To prevent build errors later on, you should delete the following file:
* Java: ``src/test/java/com/template/FlowTests.java``
* Kotlin: ``src/test/kotlin/com/template/FlowTests.kt``
Progress so far Progress so far
--------------- ---------------
We now have a template that we can build upon to define our IOU CorDapp. We now have a template that we can build upon to define our IOU CorDapp.

View File

@ -95,9 +95,9 @@ to specify JAR URLs in the case that the CorDapp(s) involved in testing already
MockNetwork/MockNode MockNetwork/MockNode
******************** ********************
The most simple way to ensure that a vanilla instance of a MockNode generates the correct CorDapps is to make a call The most simple way to ensure that a vanilla instance of a MockNode generates the correct CorDapps is to use the
to ``setCordappPackages`` before the MockNetwork/Node are created and then ``unsetCordappPackages`` after the test ``cordappPackages`` constructor parameter (Kotlin) or the ``setCordappPackages`` method on ``MockNetworkParameters`` (Java)
has finished. These calls will cause the ``AbstractNode`` to use the named packages as sources for CorDapps. All files when creating the MockNetwork. This will cause the ``AbstractNode`` to use the named packages as sources for CorDapps. All files
within those packages will be zipped into a JAR and added to the attachment store and loaded as CorDapps by the within those packages will be zipped into a JAR and added to the attachment store and loaded as CorDapps by the
``CordappLoader``. An example of this usage would be: ``CordappLoader``. An example of this usage would be:
@ -108,17 +108,7 @@ within those packages will be zipped into a JAR and added to the attachment stor
@Before @Before
void setup() { void setup() {
// The ordering of the two below lines is important - if the MockNetwork is created before the nodes and network network = new MockNetwork(new MockNetworkParameters().setCordappPackages(Arrays.asList("com.domain.cordapp")))
// are created the CorDapps will not be loaded into the MockNodes correctly.
setCordappPackages(Arrays.asList("com.domain.cordapp"))
network = new MockNetwork()
}
@After
void teardown() {
// This must be called at the end otherwise the global state set by setCordappPackages may leak into future
// tests in the same test runner environment.
unsetCordappPackages()
} }
... // Your tests go here ... // Your tests go here
@ -146,4 +136,4 @@ The driver takes a parameter called ``extraCordappPackagesToScan`` which is a li
Full Nodes Full Nodes
********** **********
When testing against full nodes simply place your CorDapp into the plugins directory of the node. When testing against full nodes simply place your CorDapp into the cordapps directory of the node.

View File

@ -36,7 +36,7 @@ The core elements of the architecture are:
* A network interface for interacting with other nodes * A network interface for interacting with other nodes
* An RPC interface for interacting with the node's owner * An RPC interface for interacting with the node's owner
* A service hub for allowing the node's flows to call upon the node's other services * A service hub for allowing the node's flows to call upon the node's other services
* A plugin registry for extending the node by installing CorDapps * A cordapp interface and provider for extending the node by installing CorDapps
Persistence layer Persistence layer
----------------- -----------------
@ -68,11 +68,11 @@ updates. The key services provided are:
* Information about the node itself * Information about the node itself
* The current time, as tracked by the node * The current time, as tracked by the node
The plugin registry The CorDapp provider
------------------- --------------------
The plugin registry is where new CorDapps are installed to extend the behavior of the node. The CorDapp provider is where new CorDapps are installed to extend the behavior of the node.
The node also has several plugins installed by default to handle common tasks such as: The node also has several CorDapps installed by default to handle common tasks such as:
* Retrieving transactions and attachments from counterparties * Retrieving transactions and attachments from counterparties
* Upgrading contracts * Upgrading contracts

View File

@ -209,10 +209,8 @@ NodeAttachmentService
The ``NodeAttachmentService`` provides an implementation of the The ``NodeAttachmentService`` provides an implementation of the
``AttachmentStorage`` interface exposed on the ``ServiceHub`` allowing ``AttachmentStorage`` interface exposed on the ``ServiceHub`` allowing
transactions to add documents, copies of the contract code and binary transactions to add documents, copies of the contract code and binary
data to transactions. The data is persisted to the local file system data to transactions. The service is also interfaced to by the web server,
inside the attachments subfolder of the node workspace. The service is which allows files to be uploaded via an HTTP post request.
also interfaced to by the web server, which allows files to be uploaded
via an HTTP post request.
Flow framework and event scheduling services Flow framework and event scheduling services
-------------------------------------------- --------------------------------------------
@ -320,7 +318,7 @@ does this by tracking update notifications from the
``TransactionStorage`` service and processing relevant updates to delete ``TransactionStorage`` service and processing relevant updates to delete
consumed states and insert new states. The resulting update is then consumed states and insert new states. The resulting update is then
persisted to the database. The ``VaultService`` then exposes query and persisted to the database. The ``VaultService`` then exposes query and
event notification APIs to flows and CorDapp plugins to allow them event notification APIs to flows and CorDapp services to allow them
to respond to updates, or query for states meeting various conditions to to respond to updates, or query for states meeting various conditions to
begin the formation of new transactions consuming them. The equivalent begin the formation of new transactions consuming them. The equivalent
services are also forwarded to RPC clients, so that they may show services are also forwarded to RPC clients, so that they may show

Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 KiB

After

Width:  |  Height:  |  Size: 282 KiB

View File

@ -10,7 +10,7 @@ already installed. You run each node by navigating to ``<node_dir>`` in a termin
java -jar corda.jar java -jar corda.jar
.. warning:: If your working directory is not ``<node_dir>`` your plugins and configuration will not be used. .. warning:: If your working directory is not ``<node_dir>`` your cordapps and configuration will not be used.
The configuration file and workspace paths can be overridden on the command line. For example: The configuration file and workspace paths can be overridden on the command line. For example:

View File

@ -35,7 +35,7 @@ To run from the command line in Unix:
2. Run ``./samples/trader-demo/build/nodes/runnodes`` to open up four new terminals with the four nodes 2. Run ``./samples/trader-demo/build/nodes/runnodes`` to open up four new terminals with the four nodes
3. Run ``./gradlew samples:trader-demo:runBank`` to instruct the bank node to issue cash and commercial paper to the buyer and seller nodes respectively. 3. Run ``./gradlew samples:trader-demo:runBank`` to instruct the bank node to issue cash and commercial paper to the buyer and seller nodes respectively.
4. Run ``./gradlew samples:trader-demo:runSeller`` to trigger the transaction. If you entered ``flow watch`` 4. Run ``./gradlew samples:trader-demo:runSeller`` to trigger the transaction. If you entered ``flow watch``
you can see flows running on both sides of transaction. Additionally you should see final trade information displayed you can see flows running on both sides of transaction. Additionally you should see final trade information displayed
to your terminal. to your terminal.
@ -45,7 +45,7 @@ To run from the command line in Windows:
2. Run ``samples\trader-demo\build\nodes\runnodes`` to open up four new terminals with the four nodes 2. Run ``samples\trader-demo\build\nodes\runnodes`` to open up four new terminals with the four nodes
3. Run ``gradlew samples:trader-demo:runBank`` to instruct the buyer node to request issuance of some cash from the Bank of Corda node 3. Run ``gradlew samples:trader-demo:runBank`` to instruct the buyer node to request issuance of some cash from the Bank of Corda node
4. Run ``gradlew samples:trader-demo:runSeller`` to trigger the transaction. If you entered ``flow watch`` 4. Run ``gradlew samples:trader-demo:runSeller`` to trigger the transaction. If you entered ``flow watch``
you can see flows running on both sides of transaction. Additionally you should see final trade information displayed you can see flows running on both sides of transaction. Additionally you should see final trade information displayed
to your terminal. to your terminal.
@ -112,8 +112,11 @@ Notary demo
This demo shows a party getting transactions notarised by either a single-node or a distributed notary service. This demo shows a party getting transactions notarised by either a single-node or a distributed notary service.
All versions of the demo start two counterparty nodes. All versions of the demo start two counterparty nodes.
One of the counterparties will generate transactions that transfer a self-issued asset to the other party and submit them for notarisation. One of the counterparties will generate transactions that transfer a self-issued asset to the other party and submit them for notarisation.
The `Raft <https://raft.github.io/>`_ version of the demo will start three distributed notary nodes.
The `BFT SMaRt <https://bft-smart.github.io/library/>`_ version of the demo will start four distributed notary nodes. * The `Raft <https://raft.github.io/>`_ version of the demo will start three distributed notary nodes.
* The `BFT SMaRt <https://bft-smart.github.io/library/>`_ version of the demo will start four distributed notary nodes.
* The Single version of the demo will start a single-node validating notary service.
* The Custom version of the demo will load and start a custom single-node notary service that is defined the demo CorDapp.
The output will display a list of notarised transaction IDs and corresponding signer public keys. In the Raft distributed notary, The output will display a list of notarised transaction IDs and corresponding signer public keys. In the Raft distributed notary,
every node in the cluster can service client requests, and one signature is sufficient to satisfy the notary composite key requirement. every node in the cluster can service client requests, and one signature is sufficient to satisfy the notary composite key requirement.
@ -122,9 +125,9 @@ You will notice that successive transactions get signed by different members of
To run the Raft version of the demo from the command line in Unix: To run the Raft version of the demo from the command line in Unix:
1. Run ``./gradlew samples:notary-demo:deployNodes``, which will create all three types of notaries' node directories 1. Run ``./gradlew samples:notary-demo:deployNodes``, which will create node directories for all versions of the demo,
with configs under ``samples/notary-demo/build/nodes/nodesRaft`` (``nodesBFT`` and ``nodesSingle`` for BFT and with configs under ``samples/notary-demo/build/nodes/nodesRaft`` (``nodesBFT``, ``nodesSingle``, and ``nodesCustom`` for
Single notaries). BFT, Single and Custom notaries respectively).
2. Run ``./samples/notary-demo/build/nodes/nodesRaft/runnodes``, which will start the nodes in separate terminal windows/tabs. 2. Run ``./samples/notary-demo/build/nodes/nodesRaft/runnodes``, which will start the nodes in separate terminal windows/tabs.
Wait until a "Node started up and registered in ..." message appears on each of the terminals Wait until a "Node started up and registered in ..." message appears on each of the terminals
3. Run ``./gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests 3. Run ``./gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests
@ -133,8 +136,8 @@ To run the Raft version of the demo from the command line in Unix:
To run from the command line in Windows: To run from the command line in Windows:
1. Run ``gradlew samples:notary-demo:deployNodes``, which will create all three types of notaries' node directories 1. Run ``gradlew samples:notary-demo:deployNodes``, which will create all three types of notaries' node directories
with configs under ``samples/notary-demo/build/nodes/nodesRaft`` (``nodesBFT`` and ``nodesSingle`` for BFT and with configs under ``samples/notary-demo/build/nodes/nodesRaft`` (``nodesBFT``, ``nodesSingle``, and ``nodesCustom`` for
Single notaries). BFT, Single and Custom notaries respectively).
2. Run ``samples\notary-demo\build\nodes\nodesRaft\runnodes``, which will start the nodes in separate terminal windows/tabs. 2. Run ``samples\notary-demo\build\nodes\nodesRaft\runnodes``, which will start the nodes in separate terminal windows/tabs.
Wait until a "Node started up and registered in ..." message appears on each of the terminals Wait until a "Node started up and registered in ..." message appears on each of the terminals
3. Run ``gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests 3. Run ``gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests
@ -142,6 +145,7 @@ To run from the command line in Windows:
To run the BFT SMaRt notary demo, use ``nodesBFT`` instead of ``nodesRaft`` in the path (you will see messages from notary nodes To run the BFT SMaRt notary demo, use ``nodesBFT`` instead of ``nodesRaft`` in the path (you will see messages from notary nodes
trying to communicate each other sometime with connection errors, that's normal). For a single notary node, use ``nodesSingle``. trying to communicate each other sometime with connection errors, that's normal). For a single notary node, use ``nodesSingle``.
For the custom notary service use ``nodesCustom`.
Distributed notary nodes store consumed states in a replicated commit log, which is backed by a H2 database on each node. Distributed notary nodes store consumed states in a replicated commit log, which is backed by a H2 database on each node.
You can ascertain that the commit log is synchronised across the cluster by accessing and comparing each of the nodes' backing stores You can ascertain that the commit log is synchronised across the cluster by accessing and comparing each of the nodes' backing stores

View File

@ -63,7 +63,7 @@ The long term goal is to migrate the current serialization format for everything
#. A desire to support open-ended polymorphism, where the number of subclasses of a superclass can expand over time #. A desire to support open-ended polymorphism, where the number of subclasses of a superclass can expand over time
and do not need to be defined in the schema *upfront*, which is key to many Corda concepts, such as contract states. and do not need to be defined in the schema *upfront*, which is key to many Corda concepts, such as contract states.
#. Increased security from deserialized objects being constructed through supported constructors rather than having #. Increased security from deserialized objects being constructed through supported constructors rather than having
data poked directy into their fields without an opportunity to validate consistency or intercept attempts to manipulate data poked directly into their fields without an opportunity to validate consistency or intercept attempts to manipulate
supposed invariants. supposed invariants.
Documentation on that format, and how JVM classes are translated to AMQP, will be linked here when it is available. Documentation on that format, and how JVM classes are translated to AMQP, will be linked here when it is available.
@ -259,9 +259,10 @@ Kotlin Objects
`````````````` ``````````````
#. Kotlin ``object`` s are singletons and treated differently. They are recorded into the stream with no properties #. Kotlin ``object`` s are singletons and treated differently. They are recorded into the stream with no properties
and deserialize back to the singleton instance. and deserialize back to the singleton instance. Currently, the same is not true of Java singletons,
and they will deserialize to new instances of the class.
Currently, the same is not true of Java singletons, and they will deserialize to new instances of the class. #. Kotlin's anonymous ``object`` s are not currently supported. I.e. constructs like:
``object : Contract {...}`` will not serialize correctly and need to be re-written as an explicit class declaration.
The Carpenter The Carpenter
````````````` `````````````

View File

@ -11,31 +11,37 @@ Remember that each state references a contract. The contract imposes constraints
If the transaction does not obey the constraints of all the contracts of all its states, it cannot become a valid If the transaction does not obey the constraints of all the contracts of all its states, it cannot become a valid
ledger update. ledger update.
We need to modify our contract so that the borrower's signature is required in any IOU creation transaction. This will We need to modify our contract so that the borrower's signature is required in any IOU creation transaction.
only require changing a single line of code. In ``IOUContract.java``/``IOUContract.kt``, update the final two lines of
the ``requireThat`` block as follows: In ``IOUContract.java``/``IOUContract.kt``, change the imports block to the following:
.. container:: codeset .. container:: codeset
.. code-block:: kotlin .. literalinclude:: example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/contract.kt
:language: kotlin
:start-after: DOCSTART 01
:end-before: DOCEND 01
// Constraints on the signers. .. literalinclude:: example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUContract.java
"There must be two signers." using (command.signers.toSet().size == 2) :language: java
"The borrower and lender must be signers." using (command.signers.containsAll(listOf( :start-after: DOCSTART 01
out.borrower.owningKey, out.lender.owningKey))) :end-before: DOCEND 01
.. code-block:: java And update the final block of constraints in the ``requireThat`` block as follows:
... .. container:: codeset
import com.google.common.collect.ImmutableList; .. literalinclude:: example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/contract.kt
:language: kotlin
:start-after: DOCSTART 02
:end-before: DOCEND 02
:dedent: 12
... .. literalinclude:: example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUContract.java
:language: java
// Constraints on the signers. :start-after: DOCSTART 02
check.using("There must be two signers.", command.getSigners().size() == 2); :end-before: DOCEND 02
check.using("The borrower and lender must be signers.", command.getSigners().containsAll( :dedent: 12
ImmutableList.of(borrower.getOwningKey(), lender.getOwningKey())));
Progress so far Progress so far
--------------- ---------------

View File

@ -21,76 +21,35 @@ by invoking a built-in flow called ``FinalityFlow`` as a subflow. We're going to
We also need to add the borrower's public key to the transaction's command, making the borrower one of the required We also need to add the borrower's public key to the transaction's command, making the borrower one of the required
signers on the transaction. signers on the transaction.
In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows: In ``IOUFlow.java``/``IOUFlow.kt``, change the imports block to the following:
.. container:: codeset .. container:: codeset
.. code-block:: kotlin .. literalinclude:: example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/flow.kt
:language: kotlin
:start-after: DOCSTART 01
:end-before: DOCEND 01
... .. literalinclude:: example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlow.java
:language: java
:start-after: DOCSTART 01
:end-before: DOCEND 01
// We create the transaction components. And update ``IOUFlow.call`` by changing the code following the creation of the ``TransactionBuilder`` as follows:
val outputState = IOUState(iouValue, ourIdentity, otherParty)
val outputContract = IOUContract::class.jvmName
val outputContractAndState = StateAndContract(outputState, outputContract)
val cmd = Command(IOUContract.Create(), listOf(ourIdentity.owningKey, otherParty.owningKey))
// We add the items to the builder. .. container:: codeset
txBuilder.withItems(outputContractAndState, cmd)
// Verifying the transaction. .. literalinclude:: example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/flow.kt
txBuilder.verify(serviceHub) :language: kotlin
:start-after: DOCSTART 02
:end-before: DOCEND 02
:dedent: 8
// Signing the transaction. .. literalinclude:: example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlow.java
val signedTx = serviceHub.signInitialTransaction(txBuilder) :language: java
:start-after: DOCSTART 02
// Creating a session with the other party. :end-before: DOCEND 02
val otherpartySession = initiateFlow(otherParty) :dedent: 8
// Obtaining the counterparty's signature.
val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, listOf(otherpartySession), CollectSignaturesFlow.tracker()))
// Finalising the transaction.
subFlow(FinalityFlow(fullySignedTx))
.. code-block:: java
...
import com.google.common.collect.ImmutableList;
import java.security.PublicKey;
import java.util.Collections;
import java.util.List;
...
// We create the transaction components.
IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty);
String outputContract = IOUContract.class.getName();
StateAndContract outputContractAndState = new StateAndContract(outputState, outputContract);
List<PublicKey> requiredSigners = ImmutableList.of(getOurIdentity().getOwningKey(), otherParty.getOwningKey());
Command cmd = new Command<>(new IOUContract.Create(), requiredSigners);
// We add the items to the builder.
txBuilder.withItems(outputContractAndState, cmd);
// Verifying the transaction.
txBuilder.verify(getServiceHub());
// Signing the transaction.
final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);
// Creating a session with the other party.
FlowSession otherpartySession = initiateFlow(otherParty);
// Obtaining the counterparty's signature.
SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(
signedTx, ImmutableList.of(otherpartySession), CollectSignaturesFlow.tracker()));
// Finalising the transaction.
subFlow(new FinalityFlow(signedTx));
return null;
To make the borrower a required signer, we simply add the borrower's public key to the list of signers on the command. To make the borrower a required signer, we simply add the borrower's public key to the list of signers on the command.
@ -116,81 +75,15 @@ In a new ``IOUFlowResponder.java`` file in Java, or within the ``App.kt`` file i
.. container:: codeset .. container:: codeset
.. code-block:: kotlin .. literalinclude:: example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/flowResponder.kt
:language: kotlin
:start-after: DOCSTART 01
:end-before: DOCEND 01
... .. literalinclude:: example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlowResponder.java
:language: java
import net.corda.core.transactions.SignedTransaction :start-after: DOCSTART 01
:end-before: DOCEND 01
...
@InitiatedBy(IOUFlow::class)
class IOUFlowResponder(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val signTransactionFlow = object : SignTransactionFlow(otherPartySession, SignTransactionFlow.tracker()) {
override fun checkTransaction(stx: SignedTransaction) = requireThat {
val output = stx.tx.outputs.single().data
"This must be an IOU transaction." using (output is IOUState)
val iou = output as IOUState
"The IOU's value can't be too high." using (iou.value < 100)
}
}
subFlow(signTransactionFlow)
}
}
.. code-block:: java
package com.template.flow;
import co.paralleluniverse.fibers.Suspendable;
import com.template.state.IOUState;
import net.corda.core.contracts.ContractState;
import net.corda.core.flows.FlowException;
import net.corda.core.flows.FlowLogic;
import net.corda.core.flows.FlowSession;
import net.corda.core.flows.InitiatedBy;
import net.corda.core.flows.SignTransactionFlow;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.utilities.ProgressTracker;
import static net.corda.core.contracts.ContractsDSL.requireThat;
@InitiatedBy(IOUFlow.class)
public class IOUFlowResponder extends FlowLogic<Void> {
private final FlowSession otherPartySession;
public IOUFlowResponder(FlowSession otherPartySession) {
this.otherPartySession = otherPartySession;
}
@Suspendable
@Override
public Void call() throws FlowException {
class SignTxFlow extends SignTransactionFlow {
private signTxFlow(FlowSession otherPartySession, ProgressTracker progressTracker) {
super(otherPartySession, progressTracker);
}
@Override
protected void checkTransaction(SignedTransaction stx) {
requireThat(require -> {
ContractState output = stx.getTx().getOutputs().get(0).getData();
require.using("This must be an IOU transaction.", output instanceof IOUState);
IOUState iou = (IOUState) output;
require.using("The IOU's value can't be too high.", iou.getValue() < 100);
return null;
});
}
}
subFlow(new SignTxFlow(otherPartySession, SignTransactionFlow.Companion.tracker()));
return null;
}
}
As with the ``IOUFlow``, our ``IOUFlowResponder`` flow is a ``FlowLogic`` subclass where we've overridden As with the ``IOUFlow``, our ``IOUFlowResponder`` flow is a ``FlowLogic`` subclass where we've overridden
``FlowLogic.call``. ``FlowLogic.call``.

View File

@ -20,4 +20,4 @@ IOU onto the ledger. We'll need to make two changes:
signature (as well as the lender's) to become valid ledger updates signature (as well as the lender's) to become valid ledger updates
* The ``IOUFlow`` will need to be updated to allow for the gathering of the borrower's signature * The ``IOUFlow`` will need to be updated to allow for the gathering of the borrower's signature
We'll start by updating the contract. We'll start by updating the contract.

View File

@ -17,7 +17,7 @@ if:
We will deploy the CorDapp on 4 test nodes: We will deploy the CorDapp on 4 test nodes:
* **Controller**, which hosts the network map service and a validating notary service * **Controller**, which hosts a validating notary service
* **PartyA** * **PartyA**
* **PartyB** * **PartyB**
* **PartyC** * **PartyC**
@ -210,9 +210,9 @@ Building the example CorDapp
. nodeName . nodeName
├── corda.jar ├── corda.jar
├── node.conf ├── node.conf
└── plugins └── cordapps
``corda.jar`` is the Corda runtime, ``plugins`` contains our node's CorDapps, and the node's configuration is ``corda.jar`` is the Corda runtime, ``cordapps`` contains our node's CorDapps, and the node's configuration is
given by ``node.conf`` given by ``node.conf``
Running the example CorDapp Running the example CorDapp
@ -276,7 +276,7 @@ IntelliJ
The node driver defined in ``/src/test/kotlin/com/example/Main.kt`` allows you to specify how many nodes you would like The node driver defined in ``/src/test/kotlin/com/example/Main.kt`` allows you to specify how many nodes you would like
to run and the configuration settings for each node. For the example CorDapp, the driver starts up four nodes to run and the configuration settings for each node. For the example CorDapp, the driver starts up four nodes
and adds an RPC user for all but the "Controller" node (which serves as the notary and network map service): and adds an RPC user for all but the "Controller" node (which serves as the notary):
.. sourcecode:: kotlin .. sourcecode:: kotlin
@ -489,9 +489,6 @@ You must now edit the configuration file for each node, including the controller
and make the following changes: and make the following changes:
* Change the Artemis messaging address to the machine's IP address (e.g. ``p2pAddress="10.18.0.166:10006"``) * Change the Artemis messaging address to the machine's IP address (e.g. ``p2pAddress="10.18.0.166:10006"``)
* Change the network map service's address to the IP address of the machine where the controller node is running
(e.g. ``networkMapService { address="10.18.0.166:10002" legalName="O=Controller,L=London,C=GB" ``). The controller
will not have the ``networkMapService`` configuration entry
After starting each node, the nodes will be able to see one another and agree IOUs among themselves. After starting each node, the nodes will be able to see one another and agree IOUs among themselves.

View File

@ -1,17 +1,17 @@
.. highlight:: kotlin .. highlight:: kotlin
Writing a custom notary service Writing a custom notary service (experimental)
=============================== ==============================================
.. warning:: Customising a notary service is an advanced feature and not recommended for most use-cases. Currently, .. warning:: Customising a notary service is still an experimental feature and not recommended for most use-cases. Currently,
customising Raft or BFT notaries is not yet fully supported. If you want to write your own Raft notary you will have to customising Raft or BFT notaries is not yet fully supported. If you want to write your own Raft notary you will have to
implement a custom database connector (or use a separate database for the notary), and use a custom configuration file. implement a custom database connector (or use a separate database for the notary), and use a custom configuration file.
Similarly to writing an oracle service, the first step is to create a service class in your CorDapp and annotate it Similarly to writing an oracle service, the first step is to create a service class in your CorDapp and annotate it
with ``@CordaService``. The Corda node scans for any class with this annotation and initialises them. The only requirement with ``@CordaService``. The Corda node scans for any class with this annotation and initialises them. The custom notary
is that the class provide a constructor with a single parameter of type ``ServiceHub``. service class should provide a constructor with two parameters of types ``AppServiceHub`` and ``PublicKey``.
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt .. literalinclude:: ../../samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt
:language: kotlin :language: kotlin
:start-after: START 1 :start-after: START 1
:end-before: END 1 :end-before: END 1
@ -20,7 +20,16 @@ The next step is to write a notary service flow. You are free to copy and modify
as ``ValidatingNotaryFlow``, ``NonValidatingNotaryFlow``, or implement your own from scratch (following the as ``ValidatingNotaryFlow``, ``NonValidatingNotaryFlow``, or implement your own from scratch (following the
``NotaryFlow.Service`` template). Below is an example of a custom flow for a *validating* notary service: ``NotaryFlow.Service`` template). Below is an example of a custom flow for a *validating* notary service:
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt .. literalinclude:: ../../samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt
:language: kotlin :language: kotlin
:start-after: START 2 :start-after: START 2
:end-before: END 2 :end-before: END 2
To enable the service, add the following to the node configuration:
.. parsed-literal::
notary : {
validating : true # Set to false if your service is non-validating
custom : true
}

View File

@ -16,6 +16,7 @@ Tutorials
flow-testing flow-testing
running-a-notary running-a-notary
oracles oracles
tutorial-custom-notary
tutorial-tear-offs tutorial-tear-offs
tutorial-attachments tutorial-attachments
event-scheduling event-scheduling

View File

@ -25,6 +25,19 @@ versions you are currently using are still in force.
We also strongly recommend cross referencing with the :doc:`changelog` to confirm changes. We also strongly recommend cross referencing with the :doc:`changelog` to confirm changes.
UNRELEASED
----------
Testing
^^^^^^^
* The registration mechanism for CorDapps in ``MockNetwork`` unit tests has changed.
It is now done via the ``cordappPackages`` constructor parameter of MockNetwork.
This takes a list of `String` values which should be the
package names of the CorDapps containing the contract verification code you wish to load.
The ``unsetCordappPackages`` method is now redundant and has been removed.
:ref:`Milestone 14 <changelog_m14>` :ref:`Milestone 14 <changelog_m14>`
------------ ------------
@ -220,6 +233,8 @@ Miscellaneous
apps would not typically select random, unknown counterparties from the network map based on self-declared capabilities. apps would not typically select random, unknown counterparties from the network map based on self-declared capabilities.
We will introduce a replacement for this functionality, business networks, in a future release. We will introduce a replacement for this functionality, business networks, in a future release.
For now, your should retrieve the service by legal name using ``NetworkMapCache.getNodeByLegalName``.
Gotchas Gotchas
^^^^^^^ ^^^^^^^

View File

@ -0,0 +1,53 @@
buildscript {
// For sharing constants between builds
Properties constants = new Properties()
file("$projectDir/../../constants.properties").withInputStream { constants.load(it) }
ext.kotlin_version = constants.getProperty("kotlinVersion")
ext.javaassist_version = "3.12.1.GA"
repositories {
mavenLocal()
mavenCentral()
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
repositories {
mavenLocal()
mavenCentral()
jcenter()
}
apply plugin: 'kotlin'
apply plugin: 'kotlin-kapt'
apply plugin: 'idea'
description 'A javaagent to allow hooking into Kryo'
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "javassist:javassist:$javaassist_version"
compile "com.esotericsoftware:kryo:4.0.0"
compile "co.paralleluniverse:quasar-core:$quasar_version:jdk8"
}
jar {
archiveName = "${project.name}.jar"
manifest {
attributes(
'Premain-Class': 'net.corda.kryohook.KryoHookAgent',
'Can-Redefine-Classes': 'true',
'Can-Retransform-Classes': 'true',
'Can-Set-Native-Method-Prefix': 'true',
'Implementation-Title': "KryoHook",
'Implementation-Version': rootProject.version
)
}
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}

View File

@ -0,0 +1,167 @@
package net.corda.kryohook
import co.paralleluniverse.strands.Strand
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.io.Output
import javassist.ClassPool
import javassist.CtClass
import java.io.ByteArrayInputStream
import java.lang.StringBuilder
import java.lang.instrument.ClassFileTransformer
import java.lang.instrument.Instrumentation
import java.security.ProtectionDomain
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
class KryoHookAgent {
companion object {
@JvmStatic
fun premain(argumentsString: String?, instrumentation: Instrumentation) {
Runtime.getRuntime().addShutdownHook(Thread {
val statsTrees = KryoHook.events.values.flatMap {
readTrees(it, 0).second
}
val builder = StringBuilder()
statsTrees.forEach {
prettyStatsTree(0, it, builder)
}
print(builder.toString())
})
instrumentation.addTransformer(KryoHook)
}
}
}
fun prettyStatsTree(indent: Int, statsTree: StatsTree, builder: StringBuilder) {
when (statsTree) {
is StatsTree.Object -> {
builder.append(kotlin.CharArray(indent) { ' ' })
builder.append(statsTree.className)
builder.append(" ")
builder.append(statsTree.size)
builder.append("\n")
for (child in statsTree.children) {
prettyStatsTree(indent + 2, child, builder)
}
}
}
}
/**
* The hook simply records the write() entries and exits together with the output offset at the time of the call.
* This is recorded in a StrandID -> List<StatsEvent> map.
*
* Later we "parse" these lists into a tree.
*/
object KryoHook : ClassFileTransformer {
val classPool = ClassPool.getDefault()
val hookClassName = javaClass.name
override fun transform(
loader: ClassLoader?,
className: String,
classBeingRedefined: Class<*>?,
protectionDomain: ProtectionDomain?,
classfileBuffer: ByteArray
): ByteArray? {
if (className.startsWith("java") || className.startsWith("javassist") || className.startsWith("kotlin")) {
return null
}
return try {
val clazz = classPool.makeClass(ByteArrayInputStream(classfileBuffer))
instrumentClass(clazz)?.toBytecode()
} catch (throwable: Throwable) {
println("SOMETHING WENT WRONG")
throwable.printStackTrace(System.out)
null
}
}
private fun instrumentClass(clazz: CtClass): CtClass? {
for (method in clazz.declaredBehaviors) {
if (method.name == "write") {
val parameterTypeNames = method.parameterTypes.map { it.name }
if (parameterTypeNames == listOf("com.esotericsoftware.kryo.Kryo", "com.esotericsoftware.kryo.io.Output", "java.lang.Object")) {
if (method.isEmpty) continue
println("Instrumenting ${clazz.name}")
method.insertBefore("$hookClassName.${this::writeEnter.name}($1, $2, $3);")
method.insertAfter("$hookClassName.${this::writeExit.name}($1, $2, $3);")
return clazz
}
}
}
return null
}
// StrandID -> StatsEvent map
val events = ConcurrentHashMap<Long, ArrayList<StatsEvent>>()
@JvmStatic
fun writeEnter(kryo: Kryo, output: Output, obj: Any) {
events.getOrPut(Strand.currentStrand().id) { ArrayList() }.add(
StatsEvent.Enter(obj.javaClass.name, output.total())
)
}
@JvmStatic
fun writeExit(kryo: Kryo, output: Output, obj: Any) {
events.get(Strand.currentStrand().id)!!.add(
StatsEvent.Exit(obj.javaClass.name, output.total())
)
}
}
/**
* TODO we could add events on entries/exits to field serializers to get more info on what's being serialised.
*/
sealed class StatsEvent {
data class Enter(val className: String, val offset: Long) : StatsEvent()
data class Exit(val className: String, val offset: Long) : StatsEvent()
}
/**
* TODO add Field constructor.
*/
sealed class StatsTree {
data class Object(
val className: String,
val size: Long,
val children: List<StatsTree>
) : StatsTree()
}
fun readTree(events: List<StatsEvent>, index: Int): Pair<Int, StatsTree> {
val event = events[index]
when (event) {
is StatsEvent.Enter -> {
val (nextIndex, children) = readTrees(events, index + 1)
val exit = events[nextIndex] as StatsEvent.Exit
require(event.className == exit.className)
return Pair(nextIndex + 1, StatsTree.Object(event.className, exit.offset - event.offset, children))
}
is StatsEvent.Exit -> {
throw IllegalStateException("Wasn't expecting Exit")
}
}
}
fun readTrees(events: List<StatsEvent>, index: Int): Pair<Int, List<StatsTree>> {
val trees = ArrayList<StatsTree>()
var i = index
while (true) {
val event = events.getOrNull(i)
when (event) {
is StatsEvent.Enter -> {
val (nextIndex, tree) = readTree(events, i)
trees.add(tree)
i = nextIndex
}
is StatsEvent.Exit -> {
return Pair(i, trees)
}
null -> {
return Pair(i, trees)
}
}
}
}

View File

@ -0,0 +1,23 @@
What is this
------------
This is a javaagent that hooks into Kryo serializers to record a breakdown of how many bytes objects take in the output.
The dump is quite ugly now, but the in-memory representation is a simple tree so we could put some nice visualisation on
top if we want.
How do I run it
---------------
Build the agent:
```
./gradlew experimental:kryo-hook:jar
```
Add this JVM flag to what you're running:
```
-javaagent:<PROJECT>/experimental/kryo-hook/build/libs/kryo-hook.jar
```
The agent will dump the output when the JVM shuts down.

View File

@ -5,16 +5,18 @@ import net.corda.finance.contracts.FixOf
import net.corda.finance.contracts.Frequency import net.corda.finance.contracts.Frequency
import net.corda.finance.contracts.Tenor import net.corda.finance.contracts.Tenor
import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.setCordappPackages import net.corda.testing.EnforceVerifyOrFail
import net.corda.testing.transaction import net.corda.testing.TransactionDSL
import net.corda.testing.unsetCordappPackages import net.corda.testing.TransactionDSLInterpreter
import org.junit.After
import org.junit.Before
import org.junit.Ignore import org.junit.Ignore
import org.junit.Test import org.junit.Test
import java.time.Instant import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
fun transaction(script: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) = run {
net.corda.testing.transaction(cordappPackages = listOf("net.corda.finance.contracts.universal"), dsl = script)
}
class Cap { class Cap {
val TEST_TX_TIME_1: Instant get() = Instant.parse("2017-09-02T12:00:00.00Z") val TEST_TX_TIME_1: Instant get() = Instant.parse("2017-09-02T12:00:00.00Z")
@ -167,16 +169,6 @@ class Cap {
} }
} }
@Before
fun setup() {
setCordappPackages("net.corda.finance.contracts.universal")
}
@After
fun tearDown() {
unsetCordappPackages()
}
@Test @Test
fun issue() { fun issue() {
transaction { transaction {

View File

@ -3,11 +3,6 @@ package net.corda.finance.contracts.universal
import net.corda.finance.contracts.FixOf import net.corda.finance.contracts.FixOf
import net.corda.finance.contracts.Tenor import net.corda.finance.contracts.Tenor
import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.setCordappPackages
import net.corda.testing.transaction
import net.corda.testing.unsetCordappPackages
import org.junit.After
import org.junit.Before
import org.junit.Ignore import org.junit.Ignore
import org.junit.Test import org.junit.Test
import java.time.Instant import java.time.Instant
@ -53,17 +48,6 @@ class Caplet {
val stateFixed = UniversalContract.State(listOf(DUMMY_NOTARY), contractFixed) val stateFixed = UniversalContract.State(listOf(DUMMY_NOTARY), contractFixed)
val stateFinal = UniversalContract.State(listOf(DUMMY_NOTARY), contractFinal) val stateFinal = UniversalContract.State(listOf(DUMMY_NOTARY), contractFinal)
@Before
fun setup() {
setCordappPackages("net.corda.finance.contracts.universal")
}
@After
fun tearDown() {
unsetCordappPackages()
}
@Test @Test
fun issue() { fun issue() {
transaction { transaction {

View File

@ -1,11 +1,6 @@
package net.corda.finance.contracts.universal package net.corda.finance.contracts.universal
import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.setCordappPackages
import net.corda.testing.transaction
import net.corda.testing.unsetCordappPackages
import org.junit.After
import org.junit.Before
import org.junit.Ignore import org.junit.Ignore
import org.junit.Test import org.junit.Test
import java.time.Instant import java.time.Instant
@ -50,17 +45,6 @@ class FXFwdTimeOption
val inState = UniversalContract.State(listOf(DUMMY_NOTARY), initialContract) val inState = UniversalContract.State(listOf(DUMMY_NOTARY), initialContract)
val outState1 = UniversalContract.State(listOf(DUMMY_NOTARY), outContract1) val outState1 = UniversalContract.State(listOf(DUMMY_NOTARY), outContract1)
val outState2 = UniversalContract.State(listOf(DUMMY_NOTARY), outContract2) val outState2 = UniversalContract.State(listOf(DUMMY_NOTARY), outContract2)
@Before
fun setup() {
setCordappPackages("net.corda.finance.contracts.universal")
}
@After
fun tearDown() {
unsetCordappPackages()
}
@Test @Test
fun `issue - signature`() { fun `issue - signature`() {
transaction { transaction {

View File

@ -1,11 +1,6 @@
package net.corda.finance.contracts.universal package net.corda.finance.contracts.universal
import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.setCordappPackages
import net.corda.testing.transaction
import net.corda.testing.unsetCordappPackages
import org.junit.After
import org.junit.Before
import org.junit.Ignore import org.junit.Ignore
import org.junit.Test import org.junit.Test
import java.time.Instant import java.time.Instant
@ -41,17 +36,6 @@ class FXSwap {
val outStateBad3 = UniversalContract.State(listOf(DUMMY_NOTARY), transferBad3) val outStateBad3 = UniversalContract.State(listOf(DUMMY_NOTARY), transferBad3)
val inState = UniversalContract.State(listOf(DUMMY_NOTARY), contract) val inState = UniversalContract.State(listOf(DUMMY_NOTARY), contract)
@Before
fun setup() {
setCordappPackages("net.corda.finance.contracts.universal")
}
@After
fun tearDown() {
unsetCordappPackages()
}
@Test @Test
fun `issue - signature`() { fun `issue - signature`() {

Some files were not shown because too many files have changed in this diff Show More