diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 87186bf097..1f04313ce4 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -88,7 +88,6 @@ public final class net.corda.core.concurrent.ConcurrencyUtils extends java.lang. @NotNull public static final String shortCircuitedTaskFailedMessage = "Short-circuited task failed:" ## -@CordaSerializable public interface net.corda.core.concurrent.CordaFuture extends java.util.concurrent.Future public abstract void then(kotlin.jvm.functions.Function1, ? extends W>) @NotNull @@ -571,7 +570,6 @@ public interface net.corda.core.contracts.Contract public abstract void verify(net.corda.core.transactions.LedgerTransaction) ## @DoNotImplement -@CordaSerializable public final class net.corda.core.contracts.ContractAttachment extends java.lang.Object implements net.corda.core.contracts.Attachment public (net.corda.core.contracts.Attachment, String) public (net.corda.core.contracts.Attachment, String, java.util.Set) @@ -1013,7 +1011,6 @@ public final class net.corda.core.contracts.TransactionState extends java.lang.O ## public final class net.corda.core.contracts.TransactionStateKt extends java.lang.Object ## -@CordaSerializable public abstract class net.corda.core.contracts.TransactionVerificationException extends net.corda.core.flows.FlowException public (net.corda.core.crypto.SecureHash, String, Throwable) @NotNull @@ -1039,13 +1036,13 @@ public static final class net.corda.core.contracts.TransactionVerificationExcept ## @CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$ContractCreationError extends net.corda.core.contracts.TransactionVerificationException - public (net.corda.core.crypto.SecureHash, String, Throwable) + public (net.corda.core.crypto.SecureHash, String, Throwable, String) @NotNull public final String getContractClass() ## @CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$ContractRejection extends net.corda.core.contracts.TransactionVerificationException - public (net.corda.core.crypto.SecureHash, String, Throwable) + public (net.corda.core.crypto.SecureHash, String, Throwable, String) public (net.corda.core.crypto.SecureHash, net.corda.core.contracts.Contract, Throwable) @NotNull public final String getContractClass() @@ -1390,7 +1387,6 @@ public class net.corda.core.crypto.Base58 extends java.lang.Object public static java.math.BigInteger decodeToBigInteger(String) public static String encode(byte[]) ## -@CordaSerializable public final class net.corda.core.crypto.CompositeKey extends java.lang.Object implements java.security.PublicKey public (int, java.util.List, kotlin.jvm.internal.DefaultConstructorMarker) public final void checkValidity() @@ -1746,7 +1742,6 @@ public final class net.corda.core.crypto.NullKeys extends java.lang.Object public final net.corda.core.crypto.TransactionSignature getNULL_SIGNATURE() public static final net.corda.core.crypto.NullKeys INSTANCE ## -@CordaSerializable public static final class net.corda.core.crypto.NullKeys$NullPublicKey extends java.lang.Object implements java.security.PublicKey, java.lang.Comparable public int compareTo(java.security.PublicKey) @NotNull @@ -6225,7 +6220,6 @@ public final class net.corda.core.transactions.SignedTransaction extends java.la public final net.corda.core.transactions.SignedTransaction withAdditionalSignatures(Iterable) public static final net.corda.core.transactions.SignedTransaction$Companion Companion ## -@CordaSerializable public static final class net.corda.core.transactions.SignedTransaction$SignaturesMissingException extends java.security.SignatureException implements net.corda.core.CordaThrowable, net.corda.core.contracts.NamedByHash public (java.util.Set, java.util.List, net.corda.core.crypto.SecureHash) public void addSuppressed(Throwable[]) @@ -6412,7 +6406,6 @@ public final class net.corda.core.utilities.ByteArrays extends java.lang.Object @NotNull public static final String toHexString(byte[]) ## -@CordaSerializable public abstract class net.corda.core.utilities.ByteSequence extends java.lang.Object implements java.lang.Comparable public (byte[], int, int, kotlin.jvm.internal.DefaultConstructorMarker) public int compareTo(net.corda.core.utilities.ByteSequence) @@ -6628,7 +6621,6 @@ public static final class net.corda.core.utilities.OpaqueBytes$Companion extends @NotNull public final net.corda.core.utilities.OpaqueBytes of(byte...) ## -@CordaSerializable public final class net.corda.core.utilities.OpaqueBytesSubSequence extends net.corda.core.utilities.ByteSequence public (byte[], int, int) @NotNull diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml index 0f7bc519db..6e6eec1148 100644 --- a/.idea/codeStyles/codeStyleConfig.xml +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -1,5 +1,6 @@ - + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 43b71d0801..187eb72efb 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { file("$projectDir/constants.properties").withInputStream { constants.load(it) } // Our version: bump this on release. - ext.corda_release_version = "4.0" + ext.corda_release_version = constants.getProperty("cordaVersion") ext.corda_platform_version = constants.getProperty("platformVersion") ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion") @@ -14,7 +14,7 @@ buildscript { ext.kotlin_version = constants.getProperty("kotlinVersion") ext.quasar_group = 'co.paralleluniverse' - ext.quasar_version = '0.7.10' + ext.quasar_version = constants.getProperty("quasarVersion") // gradle-capsule-plugin:1.0.2 contains capsule:1.0.1 by default. // We must configure it manually to use the latest capsule version. @@ -57,6 +57,7 @@ buildscript { ext.jsr305_version = constants.getProperty("jsr305Version") ext.shiro_version = '1.4.0' ext.artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion') + ext.hikari_version = '2.5.1' ext.liquibase_version = '3.5.5' ext.artifactory_contextUrl = 'https://ci-artifactory.corda.r3cev.com/artifactory' ext.snake_yaml_version = constants.getProperty('snakeYamlVersion') @@ -80,7 +81,7 @@ buildscript { // Update 121 is required for ObjectInputFilter. // Updates [131, 161] also have zip compression bugs on MacOS (High Sierra). // when the java version in NodeStartup.hasMinimumJavaVersion() changes, so must this check - ext.java8_minUpdateVersion = '171' + ext.java8_minUpdateVersion = constants.getProperty('java8MinUpdateVersion') repositories { mavenLocal() @@ -161,9 +162,9 @@ allprojects { suppressionFile = '.ci/dependency-checker/suppressedLibraries.xml' cveValidForHours = 1 format = 'ALL' - failOnError = project.getProperty('owasp.failOnError') + failOnError = project.property('owasp.failOnError') // by default CVSS is '11' which passes everything. Set between 0-10 to catch vulnerable deps - failBuildOnCVSS = project.getProperty('owasp.failBuildOnCVSS').toFloat() + failBuildOnCVSS = project.property('owasp.failBuildOnCVSS').toFloat() analyzers { assemblyEnabled = false @@ -179,7 +180,7 @@ allprojects { options.encoding = 'UTF-8' } - tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { kotlinOptions { languageVersion = "1.2" apiVersion = "1.2" @@ -207,8 +208,12 @@ allprojects { // Prevent the project from creating temporary files outside of the build directory. systemProperty 'java.io.tmpdir', buildDir.absolutePath + if (project.hasProperty('test.parallel') && project.property('test.parallel').toBoolean()) { + maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) as int ?: 1 + } + if (System.getProperty("test.maxParallelForks") != null) { - maxParallelForks = Integer.valueOf(System.getProperty("test.maxParallelForks")) + maxParallelForks = Integer.getInteger('test.maxParallelForks') logger.debug("System property test.maxParallelForks found - setting max parallel forks to $maxParallelForks for $project") } @@ -232,6 +237,7 @@ allprojects { jcenter() maven { url "$artifactory_contextUrl/corda-dependencies" } maven { url 'https://jitpack.io' } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } } configurations { @@ -271,14 +277,6 @@ allprojects { } } -subprojects { - tasks.withType(Test) { - if (project.getProperty('test.parallel').toBoolean()) { - maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 - } - } -} - // Check that we are running on a Java 8 JDK. The source/targetCompatibility values above aren't sufficient to // guarantee this because those are properties checked by the Java plugin, but we're using Kotlin. // @@ -361,7 +359,6 @@ bintrayConfig { 'corda-core', 'corda-core-deterministic', 'corda-deterministic-verifier', - 'corda-djvm', 'corda', 'corda-finance-workflows', 'corda-finance-contracts', diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt index 094294fd60..14d82e2ab4 100644 --- a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt +++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt @@ -15,16 +15,20 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier import com.fasterxml.jackson.databind.deser.ContextualDeserializer import com.fasterxml.jackson.databind.deser.std.DelegatingDeserializer +import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.databind.node.IntNode import com.fasterxml.jackson.databind.node.ObjectNode import com.fasterxml.jackson.databind.ser.BeanPropertyWriter import com.fasterxml.jackson.databind.ser.BeanSerializerModifier +import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer +import com.fasterxml.jackson.databind.ser.std.UUIDSerializer import com.google.common.primitives.Booleans import net.corda.client.jackson.JacksonSupport import net.corda.core.contracts.* import net.corda.core.crypto.* import net.corda.core.crypto.PartialMerkleTree.PartialTree +import net.corda.core.flows.StateMachineRunId import net.corda.core.identity.* import net.corda.core.internal.DigitalSignatureWithCert import net.corda.core.internal.createComponentGroups @@ -40,7 +44,6 @@ import net.corda.core.utilities.parseAsHex import net.corda.core.utilities.toHexString import net.corda.serialization.internal.AllWhitelist import net.corda.serialization.internal.amqp.* -import net.corda.serialization.internal.model.LocalTypeInformation import java.math.BigDecimal import java.security.PublicKey import java.security.cert.CertPath @@ -79,6 +82,7 @@ class CordaModule : SimpleModule("corda-core") { context.setMixInAnnotations(SignatureMetadata::class.java, SignatureMetadataMixin::class.java) context.setMixInAnnotations(PartialTree::class.java, PartialTreeMixin::class.java) context.setMixInAnnotations(NodeInfo::class.java, NodeInfoMixin::class.java) + context.setMixInAnnotations(StateMachineRunId::class.java, StateMachineRunIdMixin::class.java) } } @@ -418,6 +422,28 @@ private interface SecureHashSHA256Mixin @JsonDeserialize(using = JacksonSupport.PublicKeyDeserializer::class) private interface PublicKeyMixin +@JsonSerialize(using = StateMachineRunIdSerializer::class) +@JsonDeserialize(using = StateMachineRunIdDeserializer::class) +private interface StateMachineRunIdMixin + +private class StateMachineRunIdSerializer : StdScalarSerializer(StateMachineRunId::class.java) { + private val uuidSerializer = UUIDSerializer() + + override fun isEmpty(provider: SerializerProvider?, value: StateMachineRunId): Boolean { + return uuidSerializer.isEmpty(provider, value.uuid) + } + + override fun serialize(value: StateMachineRunId, gen: JsonGenerator?, provider: SerializerProvider?) { + uuidSerializer.serialize(value.uuid, gen, provider) + } +} + +private class StateMachineRunIdDeserializer : FromStringDeserializer(StateMachineRunId::class.java) { + override fun _deserialize(value: String, ctxt: DeserializationContext?): StateMachineRunId { + return StateMachineRunId(UUID.fromString(value)) + } +} + @Suppress("unused_parameter") @ToStringSerialize private abstract class AmountMixin @JsonCreator(mode = DISABLED) constructor( diff --git a/client/jackson/src/test/kotlin/net/corda/client/jackson/StateMachineRunIdTest.kt b/client/jackson/src/test/kotlin/net/corda/client/jackson/StateMachineRunIdTest.kt new file mode 100644 index 0000000000..e91f8fd870 --- /dev/null +++ b/client/jackson/src/test/kotlin/net/corda/client/jackson/StateMachineRunIdTest.kt @@ -0,0 +1,29 @@ +package net.corda.client.jackson + +import com.fasterxml.jackson.databind.ObjectMapper +import net.corda.client.jackson.internal.CordaModule +import net.corda.core.flows.StateMachineRunId +import org.junit.Assert.assertEquals +import org.junit.Test +import java.util.* + +class StateMachineRunIdTest { + private companion object { + private const val ID = "a9da3d32-a08d-4add-a633-66bc6bf6183d" + private val jsonMapper: ObjectMapper = ObjectMapper().registerModule(CordaModule()) + } + + @Test + fun `state machine run ID deserialise`() { + val str = """"$ID"""" + val runID = jsonMapper.readValue(str, StateMachineRunId::class.java) + assertEquals(StateMachineRunId(UUID.fromString(ID)), runID) + } + + @Test + fun `state machine run ID serialise`() { + val runId = StateMachineRunId(UUID.fromString(ID)) + val str = jsonMapper.writeValueAsString(runId) + assertEquals(""""$ID"""", str) + } +} \ No newline at end of file diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt index 95caf1bb17..389ebc6194 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt @@ -119,19 +119,19 @@ class NodeMonitorModel : AutoCloseable { }.toSet() val consumedStates = statesSnapshot.states.toSet() - unconsumedStates val initialVaultUpdate = Vault.Update(consumedStates, unconsumedStates, references = emptySet()) - vaultUpdates.startWith(initialVaultUpdate).subscribe({ vaultUpdatesSubject.onNext(it) }, {}) + vaultUpdates.startWith(initialVaultUpdate).subscribe(vaultUpdatesSubject::onNext, {}) // Transactions val (transactions, newTransactions) = proxy.internalVerifiedTransactionsFeed() - newTransactions.startWith(transactions).subscribe({ transactionsSubject.onNext(it) }, {}) + newTransactions.startWith(transactions).subscribe(transactionsSubject::onNext, {}) // SM -> TX mapping val (smTxMappings, futureSmTxMappings) = proxy.stateMachineRecordedTransactionMappingFeed() - futureSmTxMappings.startWith(smTxMappings).subscribe({ stateMachineTransactionMappingSubject.onNext(it) }, {}) + futureSmTxMappings.startWith(smTxMappings).subscribe(stateMachineTransactionMappingSubject::onNext, {}) // Parties on network val (parties, futurePartyUpdate) = proxy.networkMapFeed() - futurePartyUpdate.startWith(parties.map { MapChange.Added(it) }).subscribe({ networkMapSubject.onNext(it) }, {}) + futurePartyUpdate.startWith(parties.map(MapChange::Added)).subscribe(networkMapSubject::onNext, {}) } } diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt index 7d208748c8..7c16ab4425 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt @@ -5,11 +5,24 @@ import net.corda.client.jfx.utils.distinctBy import net.corda.client.jfx.utils.lift import net.corda.client.jfx.utils.map import net.corda.client.jfx.utils.recordInSequence -import net.corda.core.contracts.ContractState -import net.corda.core.contracts.StateAndRef -import net.corda.core.contracts.StateRef +import net.corda.core.contracts.* +import net.corda.core.crypto.entropyToKeyPair +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import net.corda.core.internal.eagerDeserialise +import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction +import java.math.BigInteger.ZERO + +private class Unknown : Contract { + override fun verify(tx: LedgerTransaction) = throw UnsupportedOperationException() + + object State : ContractState { + override val participants: List = emptyList() + } +} /** * [PartiallyResolvedTransaction] holds a [SignedTransaction] that has zero or more inputs resolved. The intent is @@ -41,10 +54,24 @@ data class PartiallyResolvedTransaction( } companion object { + private val DUMMY_NOTARY = Party(CordaX500Name("Dummy Notary", "Nowhere", "ZZ"), entropyToKeyPair(ZERO).public) + fun fromSignedTransaction( transaction: SignedTransaction, inputTransactions: Map ): PartiallyResolvedTransaction { + /** + * Forcibly deserialize our transaction outputs up-front. + * Replace any [TransactionState] objects that fail to + * deserialize with a dummy transaction state that uses + * the transaction's notary. + */ + val unknownTransactionState = TransactionState( + data = Unknown.State, + contract = Unknown::class.java.name, + notary = transaction.notary ?: DUMMY_NOTARY + ) + transaction.coreTransaction.outputs.eagerDeserialise { _, _ -> unknownTransactionState } return PartiallyResolvedTransaction( transaction = transaction, inputs = transaction.inputs.map { stateRef -> diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt index 67ecfbb310..6a6cb8ad43 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt @@ -11,7 +11,8 @@ import net.corda.core.serialization.serialize import net.corda.core.utilities.* import net.corda.node.services.rpc.RPCServerConfiguration import net.corda.nodeapi.RPCApi -import net.corda.nodeapi.eventually +import net.corda.testing.common.internal.eventually +import net.corda.testing.common.internal.succeeds import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.driver.internal.incrementalPortAllocation import net.corda.testing.internal.testThreadFactory @@ -249,7 +250,9 @@ class RPCStabilityTests { assertEquals("pong", client.ping()) serverFollower.shutdown() startRpcServer(ops = ops, customPort = serverPort).getOrThrow() - val response = eventually(10.seconds) { client.ping() } + val response = eventually { + succeeds { client.ping() } + } assertEquals("pong", response) clientFollower.shutdown() // Driver would do this after the new server, causing hang. } @@ -316,13 +319,13 @@ class RPCStabilityTests { }) serverFollower.shutdown() - Thread.sleep(100) - - assertTrue(terminateHandlerCalled) - assertTrue(errorHandlerCalled) - assertEquals("Connection failure detected.", exceptionMessage) - assertTrue(subscription.isUnsubscribed) + eventually { + assertTrue(terminateHandlerCalled) + assertTrue(errorHandlerCalled) + assertEquals("Connection failure detected.", exceptionMessage) + assertTrue(subscription.isUnsubscribed) + } clientFollower.shutdown() // Driver would do this after the new server, causing hang. } } diff --git a/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java b/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java index 77fee26039..2c17ac72e3 100644 --- a/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java +++ b/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java @@ -25,6 +25,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; +import static java.util.Collections.singletonList; import static kotlin.test.AssertionsKt.assertEquals; import static kotlin.test.AssertionsKt.fail; import static net.corda.finance.workflows.GetBalances.getCashBalance; @@ -51,13 +52,12 @@ public class StandaloneCordaRPCJavaClientTest { } } - private List perms = Collections.singletonList("ALL"); + private List perms = singletonList("ALL"); private Set permSet = new HashSet<>(perms); - private User rpcUser = new User("user1", "test", permSet); + private User superUser = new User("superUser", "test", permSet); private AtomicInteger port = new AtomicInteger(15000); - private NodeProcess.Factory factory; private NodeProcess notary; private CordaRPCOps rpcProxy; private CordaRPCConnection connection; @@ -69,16 +69,16 @@ public class StandaloneCordaRPCJavaClientTest { port.getAndIncrement(), port.getAndIncrement(), true, - Collections.singletonList(rpcUser), + singletonList(superUser), true ); @Before public void setUp() { - factory = new NodeProcess.Factory(); + NodeProcess.Factory factory = new NodeProcess.Factory(); copyCordapps(factory, notaryConfig); notary = factory.create(notaryConfig); - connection = notary.connect(); + connection = notary.connect(superUser); rpcProxy = connection.getProxy(); notaryNodeIdentity = rpcProxy.nodeInfo().getLegalIdentities().get(0); } @@ -95,7 +95,7 @@ public class StandaloneCordaRPCJavaClientTest { } @Test - public void testCashBalances() throws NoSuchFieldException, ExecutionException, InterruptedException { + public void testCashBalances() throws ExecutionException, InterruptedException { Amount dollars123 = new Amount<>(123, Currency.getInstance("USD")); FlowHandle flowHandle = rpcProxy.startFlowDynamic(CashIssueFlow.class, @@ -105,7 +105,7 @@ public class StandaloneCordaRPCJavaClientTest { flowHandle.getReturnValue().get(); Amount balance = getCashBalance(rpcProxy, Currency.getInstance("USD")); - System.out.print("Balance: " + balance + "\n"); + System.out.println("Balance: " + balance); assertEquals(dollars123, balance, "matching"); } diff --git a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt index 5731368884..7cd3d6ac46 100644 --- a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt +++ b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt @@ -3,6 +3,7 @@ package net.corda.kotlin.rpc import com.google.common.hash.Hashing import com.google.common.hash.HashingInputStream import net.corda.client.rpc.CordaRPCConnection +import net.corda.client.rpc.PermissionException import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party @@ -29,10 +30,8 @@ import net.corda.nodeapi.internal.config.User import net.corda.smoketesting.NodeConfig import net.corda.smoketesting.NodeProcess import org.apache.commons.io.output.NullOutputStream -import org.junit.After -import org.junit.Before -import org.junit.Ignore -import org.junit.Test +import org.junit.* +import org.junit.rules.ExpectedException import java.io.FilterInputStream import java.io.InputStream import java.util.* @@ -46,7 +45,10 @@ import kotlin.test.assertTrue class StandaloneCordaRPClientTest { private companion object { private val log = contextLogger() - val user = User("user1", "test", permissions = setOf("ALL")) + val superUser = User("superUser", "test", permissions = setOf("ALL")) + val nonUser = User("nonUser", "test", permissions = emptySet()) + val rpcUser = User("rpcUser", "test", permissions = setOf("InvokeRpc.startFlow", "InvokeRpc.killFlow")) + val flowUser = User("flowUser", "test", permissions = setOf("StartFlow.net.corda.finance.flows.CashIssueFlow")) val port = AtomicInteger(15200) const val attachmentSize = 2116 val timeout = 60.seconds @@ -65,15 +67,18 @@ class StandaloneCordaRPClientTest { rpcPort = port.andIncrement, rpcAdminPort = port.andIncrement, isNotary = true, - users = listOf(user) + users = listOf(superUser, nonUser, rpcUser, flowUser) ) + @get:Rule + val exception: ExpectedException = ExpectedException.none() + @Before fun setUp() { factory = NodeProcess.Factory() StandaloneCordaRPCJavaClientTest.copyCordapps(factory, notaryConfig) notary = factory.create(notaryConfig) - connection = notary.connect() + connection = notary.connect(superUser) rpcProxy = connection.proxy notaryNode = fetchNotaryIdentity() notaryNodeIdentity = rpcProxy.nodeInfo().legalIdentitiesAndCerts.first().party @@ -81,9 +86,7 @@ class StandaloneCordaRPClientTest { @After fun done() { - try { - connection.close() - } finally { + connection.use { notary.close() } } @@ -232,6 +235,27 @@ class StandaloneCordaRPClientTest { assertEquals(629.DOLLARS, balance) } + @Test + fun `test kill flow without killFlow permission`() { + exception.expect(PermissionException::class.java) + exception.expectMessage("User not authorized to perform RPC call killFlow") + + val flowHandle = rpcProxy.startFlow(::CashIssueFlow, 10.DOLLARS, OpaqueBytes.of(0), notaryNodeIdentity) + notary.connect(nonUser).use { connection -> + val rpcProxy = connection.proxy + rpcProxy.killFlow(flowHandle.id) + } + } + + @Test + fun `test kill flow with killFlow permission`() { + val flowHandle = rpcProxy.startFlow(::CashIssueFlow, 83.DOLLARS, OpaqueBytes.of(0), notaryNodeIdentity) + notary.connect(rpcUser).use { connection -> + val rpcProxy = connection.proxy + assertTrue(rpcProxy.killFlow(flowHandle.id)) + } + } + private fun fetchNotaryIdentity(): NodeInfo { val nodeInfo = rpcProxy.networkMapSnapshot() assertEquals(1, nodeInfo.size) diff --git a/config/dev/log4j2.xml b/config/dev/log4j2.xml index 6e27ecccb2..c19afbb322 100644 --- a/config/dev/log4j2.xml +++ b/config/dev/log4j2.xml @@ -4,6 +4,7 @@ ${sys:log-path:-logs} node-${hostName} + diagnostic-${hostName} ${log-path}/archive ${sys:defaultLogLevel:-info} ${sys:consoleLogLevel:-error} @@ -105,6 +106,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -119,6 +160,10 @@ + + + + @@ -130,6 +175,9 @@ + + + diff --git a/constants.properties b/constants.properties index 97045c2585..dbb49a6433 100644 --- a/constants.properties +++ b/constants.properties @@ -1,11 +1,18 @@ -gradlePluginsVersion=4.0.39 +# This file is parsed from Python in the docs/source/conf.py file +# because some versions here need to be matched by app authors in +# their own projects. So don't get fancy with syntax! + +cordaVersion=5.0-SNAPSHOT +gradlePluginsVersion=4.0.42 kotlinVersion=1.2.71 +java8MinUpdateVersion=171 # ***************************************************************# # When incrementing platformVersion make sure to update # # net.corda.core.internal.CordaUtilsKt.PLATFORM_VERSION as well. # # ***************************************************************# platformVersion=4 guavaVersion=25.1-jre +quasarVersion=0.7.10 proguardVersion=6.0.3 bouncycastleVersion=1.60 disruptorVersion=3.4.2 diff --git a/core-deterministic/build.gradle b/core-deterministic/build.gradle index a8162e2c18..9cc6c0e2bf 100644 --- a/core-deterministic/build.gradle +++ b/core-deterministic/build.gradle @@ -22,7 +22,7 @@ dependencies { // and without any obviously non-deterministic ones such as Hibernate. deterministicLibraries "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" deterministicLibraries "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - deterministicLibraries "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final" + deterministicLibraries "javax.persistence:javax.persistence-api:2.2" deterministicLibraries "org.bouncycastle:bcprov-jdk15on:$bouncycastle_version" deterministicLibraries "org.bouncycastle:bcpkix-jdk15on:$bouncycastle_version" deterministicLibraries "com.google.code.findbugs:jsr305:$jsr305_version" diff --git a/core/build.gradle b/core/build.gradle index 5a8edc4d97..7ab4a50ff8 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -106,8 +106,8 @@ dependencies { compile "org.bouncycastle:bcprov-jdk15on:${bouncycastle_version}" compile "org.bouncycastle:bcpkix-jdk15on:${bouncycastle_version}" - // JPA 2.1 annotations. - compile "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final" + // JPA 2.2 annotations. + compile "javax.persistence:javax.persistence-api:2.2" // required to use @Type annotation compile "org.hibernate:hibernate-core:$hibernate_version" diff --git a/core/src/main/kotlin/net/corda/core/concurrent/CordaFuture.kt b/core/src/main/kotlin/net/corda/core/concurrent/CordaFuture.kt index 2f6d95795a..4977f3a34b 100644 --- a/core/src/main/kotlin/net/corda/core/concurrent/CordaFuture.kt +++ b/core/src/main/kotlin/net/corda/core/concurrent/CordaFuture.kt @@ -1,6 +1,5 @@ package net.corda.core.concurrent -import net.corda.core.serialization.CordaSerializable import java.util.concurrent.CompletableFuture import java.util.concurrent.Future @@ -8,7 +7,6 @@ import java.util.concurrent.Future * Same as [Future] with additional methods to provide some of the features of [java.util.concurrent.CompletableFuture] while minimising the API surface area. * In Kotlin, to avoid compile errors, whenever CordaFuture is used in a parameter or extension method receiver type, its type parameter should be specified with out variance. */ -@CordaSerializable interface CordaFuture : Future { /** * Run the given callback when this future is done, on the completion thread. diff --git a/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt b/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt index 5fc1ad04de..6ba6b07d1b 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt @@ -14,7 +14,6 @@ import java.security.PublicKey * @property additionalContracts Additional contract names contained within the JAR. */ @KeepForDJVM -@CordaSerializable class ContractAttachment private constructor( val attachment: Attachment, val contract: ContractClassName, diff --git a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt index 3c80993d8d..ef56349e7b 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt @@ -46,7 +46,6 @@ class AttachmentResolutionException(val hash: SecureHash) : FlowException("Attac * @property txId the Merkle root hash (identifier) of the transaction that failed verification. */ @Suppress("MemberVisibilityCanBePrivate") -@CordaSerializable abstract class TransactionVerificationException(val txId: SecureHash, message: String, cause: Throwable?) : FlowException("$message, transaction: $txId", cause) { @@ -57,8 +56,8 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S * @property contractClass The fully qualified class name of the failing contract. */ @KeepForDJVM - class ContractRejection(txId: SecureHash, val contractClass: String, cause: Throwable) : TransactionVerificationException(txId, "Contract verification failed: ${cause.message}, contract: $contractClass", cause) { - constructor(txId: SecureHash, contract: Contract, cause: Throwable) : this(txId, contract.javaClass.name, cause) + class ContractRejection internal constructor(txId: SecureHash, val contractClass: String, cause: Throwable?, message: String) : TransactionVerificationException(txId, "Contract verification failed: $message, contract: $contractClass", cause) { + internal constructor(txId: SecureHash, contract: Contract, cause: Throwable) : this(txId, contract.javaClass.name, cause, cause.message ?: "") } /** @@ -121,8 +120,10 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S * @property contractClass The fully qualified class name of the failing contract. */ @KeepForDJVM - class ContractCreationError(txId: SecureHash, val contractClass: String, cause: Throwable) - : TransactionVerificationException(txId, "Contract verification failed: ${cause.message}, could not create contract class: $contractClass", cause) + class ContractCreationError internal constructor(txId: SecureHash, val contractClass: String, cause: Throwable?, message: String) + : TransactionVerificationException(txId, "Contract verification failed: $message, could not create contract class: $contractClass", cause) { + internal constructor(txId: SecureHash, contractClass: String, cause: Throwable) : this(txId, contractClass, cause, cause.message ?: "") + } /** * An output state has a notary that doesn't match the transaction's notary field. It must! @@ -267,7 +268,7 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S */ @CordaSerializable @KeepForDJVM - class OverlappingAttachmentsException(txId: SecureHash, path: String) : TransactionVerificationException(txId, "Multiple attachments define a file at $path.", null) + class OverlappingAttachmentsException(txId: SecureHash, val path: String) : TransactionVerificationException(txId, "Multiple attachments define a file at $path.", null) /** * Thrown to indicate that a contract attachment is not signed by the network-wide package owner. Please note that @@ -275,22 +276,29 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S * and because attachment classloaders are reused this is independent of any particular transaction. */ @CordaSerializable - class PackageOwnershipException(txId: SecureHash, val attachmentHash: AttachmentId, val invalidClassName: String, val packageName: String) : TransactionVerificationException(txId, + class PackageOwnershipException(txId: SecureHash, @Suppress("unused") val attachmentHash: AttachmentId, @Suppress("unused") val invalidClassName: String, val packageName: String) : TransactionVerificationException(txId, """The attachment JAR: $attachmentHash containing the class: $invalidClassName is not signed by the owner of package $packageName specified in the network parameters. Please check the source of this attachment and if it is malicious contact your zone operator to report this incident. For details see: https://docs.corda.net/network-map.html#network-parameters""".trimIndent(), null) @CordaSerializable - class InvalidAttachmentException(txId: SecureHash, attachmentHash: AttachmentId) : TransactionVerificationException(txId, + class InvalidAttachmentException(txId: SecureHash, @Suppress("unused") val attachmentHash: AttachmentId) : TransactionVerificationException(txId, "The attachment $attachmentHash is not a valid ZIP or JAR file.".trimIndent(), null) // TODO: Make this descend from TransactionVerificationException so that untrusted attachments cause flows to be hospitalized. /** Thrown during classloading upon encountering an untrusted attachment (eg. not in the [TRUSTED_UPLOADERS] list) */ @KeepForDJVM @CordaSerializable - class UntrustedAttachmentsException(txId: SecureHash, val ids: List) : + class UntrustedAttachmentsException(val txId: SecureHash, val ids: List) : CordaException("Attempting to load untrusted transaction attachments: $ids. " + "At this time these are not loadable because the DJVM sandbox has not yet been integrated. " + "You will need to install that app version yourself, to whitelist it for use. " + "Please follow the operational steps outlined in https://docs.corda.net/cordapp-build-systems.html#cordapp-contract-attachments to learn more and continue.") + + /* + If you add a new class extending [TransactionVerificationException], please add a test in `TransactionVerificationExceptionSerializationTests` + proving that it can actually be serialised. As a rule, exceptions intended to be serialised _must_ have a corresponding readable property + for every named constructor parameter - so make your constructor parameters `val`s even if nothing other than the serializer is ever + going to read them. + */ } diff --git a/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt b/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt index 64f64c3f75..9dbd400660 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt @@ -29,7 +29,6 @@ import java.util.* * signatures required) to satisfy the sub-tree rooted at this node. */ @KeepForDJVM -@CordaSerializable class CompositeKey private constructor(val threshold: Int, children: List) : PublicKey { companion object { const val KEY_ALGORITHM = "COMPOSITE" diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt index 227f5c069b..f4d4a9c2b8 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt @@ -436,7 +436,7 @@ object Crypto { "Unsupported key/algorithm for schemeCodeName: ${signatureScheme.schemeCodeName}" } require(clearData.isNotEmpty()) { "Signing of an empty array is not permitted!" } - val signature = Signature.getInstance(signatureScheme.signatureName, providerMap[signatureScheme.providerName]) + val signature = Instances.getSignatureInstance(signatureScheme.signatureName, providerMap[signatureScheme.providerName]) // Note that deterministic signature schemes, such as EdDSA, original SPHINCS-256 and RSA PKCS#1, do not require // extra randomness, but we have to ensure that non-deterministic algorithms (i.e., ECDSA) use non-blocking // SecureRandom implementation. Also, SPHINCS-256 implementation in BouncyCastle 1.60 fails with @@ -640,7 +640,7 @@ object Crypto { require(isSupportedSignatureScheme(signatureScheme)) { "Unsupported key/algorithm for schemeCodeName: ${signatureScheme.schemeCodeName}" } - val signature = Signature.getInstance(signatureScheme.signatureName, providerMap[signatureScheme.providerName]) + val signature = Instances.getSignatureInstance(signatureScheme.signatureName, providerMap[signatureScheme.providerName]) signature.initVerify(publicKey) signature.update(clearData) return signature.verify(signatureData) diff --git a/core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt b/core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt index 151f00b950..47b7c823eb 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt @@ -7,7 +7,6 @@ import java.security.PublicKey @KeepForDJVM object NullKeys { - @CordaSerializable object NullPublicKey : PublicKey, Comparable { override fun getAlgorithm() = "NULL" override fun getEncoded() = byteArrayOf(0) diff --git a/core/src/main/kotlin/net/corda/core/crypto/internal/Instances.kt b/core/src/main/kotlin/net/corda/core/crypto/internal/Instances.kt new file mode 100644 index 0000000000..f1dee21331 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/crypto/internal/Instances.kt @@ -0,0 +1,12 @@ +package net.corda.core.crypto.internal + +import java.security.Provider +import java.security.Signature + +/** + * This is a collection of crypto related getInstance methods that tend to be quite inefficient and we want to be able to + * optimise them en masse. + */ +object Instances { + fun getSignatureInstance(algorithm: String, provider: Provider?) = Signature.getInstance(algorithm, provider) +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt b/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt index 968e5a7624..0e68ea2d74 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt @@ -114,10 +114,6 @@ class FinalityFlow private constructor(val transaction: SignedTransaction, @Throws(NotaryException::class) override fun call(): SignedTransaction { if (!newApi) { - require(CordappResolver.currentTargetVersion < 4) { - "A flow session for each external participant to the transaction must be provided. If you wish to continue " + - "using this insecure API then specify a target platform version of less than 4 for your CorDapp." - } logger.warnOnce("The current usage of FinalityFlow is unsafe. Please consider upgrading your CorDapp to use " + "FinalityFlow with FlowSessions. (${CordappResolver.currentCordapp?.info})") } else { diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt index 13a18e482d..bca4a37b5c 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt @@ -468,7 +468,7 @@ abstract class FlowLogic { val theirs = subLogic.progressTracker if (ours != null && theirs != null && ours != theirs) { if (ours.currentStep == ProgressTracker.UNSTARTED) { - logger.warn("ProgressTracker has not been started") + logger.debug { "Initializing the progress tracker for flow: ${this::class.java.name}." } ours.nextStep() } ours.setChildProgressTracker(ours.currentStep, theirs) diff --git a/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt index 41b61ff15e..b45f70d767 100644 --- a/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt @@ -18,7 +18,8 @@ import java.security.SignatureException * [SignedTransaction] and perform the resolution back-and-forth required to check the dependencies and download any missing * attachments. The flow will return the [SignedTransaction] after it is resolved and then verified using [SignedTransaction.verify]. * - * Please note that it will *not* store the transaction to the vault unless that is explicitly requested. + * Please note that it will *not* store the transaction to the vault unless that is explicitly requested and checkSufficientSignatures is true. + * Setting statesToRecord to anything else when checkSufficientSignatures is false will *not* update the vault. * * @property otherSideSession session to the other side which is calling [SendTransactionFlow]. * @property checkSufficientSignatures if true checks all required signatures are present. See [SignedTransaction.verify]. diff --git a/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt b/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt index 319771a7cb..0553184a27 100644 --- a/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt @@ -1,7 +1,6 @@ package net.corda.core.internal import net.corda.core.contracts.* -import net.corda.core.crypto.isFulfilledBy import net.corda.core.crypto.keys import net.corda.core.internal.cordapp.CordappImpl import net.corda.core.utilities.loggerFor diff --git a/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt b/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt index 0d6d6dfd6e..f4672df406 100644 --- a/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt @@ -1,6 +1,7 @@ package net.corda.core.internal import net.corda.core.DeleteForDJVM +import net.corda.core.contracts.Attachment import net.corda.core.contracts.ContractAttachment import net.corda.core.contracts.ContractClassName import net.corda.core.flows.DataVendingFlow @@ -41,6 +42,9 @@ fun checkMinimumPlatformVersion(minimumPlatformVersion: Int, requiredMinPlatform } } +@Throws(NumberFormatException::class) +fun getJavaUpdateVersion(javaVersion: String): Long = javaVersion.substringAfter("_").substringBefore("-").toLong() + /** Provide access to internal method for AttachmentClassLoaderTests. */ @DeleteForDJVM fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext): WireTransaction { @@ -106,15 +110,15 @@ fun noPackageOverlap(packages: Collection): Boolean { } /** - * Scans trusted (installed locally) contract attachments to find all that contain the [className]. + * Scans trusted (installed locally) attachments to find all that contain the [className]. * This is required as a workaround until explicit cordapp dependencies are implemented. * DO NOT USE IN CLIENT code. * - * @return the contract attachments with the highest version. + * @return the attachments with the highest version. * * TODO: Should throw when the class is found in multiple contract attachments (not different versions). */ -fun AttachmentStorage.internalFindTrustedAttachmentForClass(className: String): ContractAttachment?{ +fun AttachmentStorage.internalFindTrustedAttachmentForClass(className: String): Attachment? { val allTrusted = queryAttachments( AttachmentQueryCriteria.AttachmentsQueryCriteria().withUploader(Builder.`in`(TRUSTED_UPLOADERS)), AttachmentSort(listOf(AttachmentSort.AttachmentSortColumn(AttachmentSort.AttachmentSortAttribute.VERSION, Sort.Direction.DESC)))) @@ -122,7 +126,7 @@ fun AttachmentStorage.internalFindTrustedAttachmentForClass(className: String): // TODO - add caching if performance is affected. for (attId in allTrusted) { val attch = openAttachment(attId)!! - if (attch is ContractAttachment && attch.openAsJAR().use { hasFile(it, "$className.class") }) return attch + if (attch.openAsJAR().use { hasFile(it, "$className.class") }) return attch } return null } diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index a2ceee9210..8eaabf96d1 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -11,6 +11,7 @@ import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.UntrustworthyData +import net.corda.core.utilities.seconds import org.slf4j.Logger import rx.Observable import rx.Observer @@ -387,7 +388,17 @@ val Class<*>.location: URL get() = protectionDomain.codeSource.location /** Convenience method to get the package name of a class literal. */ val KClass<*>.packageName: String get() = java.packageName -val Class<*>.packageName: String get() = requireNotNull(`package`?.name) { "$this not defined inside a package" } +val Class<*>.packageName: String get() = requireNotNull(this.packageNameOrNull) { "$this not defined inside a package" } +val Class<*>.packageNameOrNull: String? // This intentionally does not go via `package` as that code path is slow and contended and just ends up doing this. + get() { + val name = this.getName() + val i = name.lastIndexOf('.') + if (i != -1) { + return name.substring(0, i) + } else { + return null + } + } inline val Class<*>.isAbstractClass: Boolean get() = Modifier.isAbstract(modifiers) @@ -403,8 +414,15 @@ inline val Member.isFinal: Boolean get() = Modifier.isFinal(modifiers) @DeleteForDJVM fun URL.toPath(): Path = toURI().toPath() +val DEFAULT_HTTP_CONNECT_TIMEOUT = 30.seconds.toMillis() +val DEFAULT_HTTP_READ_TIMEOUT = 30.seconds.toMillis() + @DeleteForDJVM -fun URL.openHttpConnection(): HttpURLConnection = openConnection() as HttpURLConnection +fun URL.openHttpConnection(): HttpURLConnection = openConnection().also { + // The default values are 0 which means infinite timeout. + it.connectTimeout = DEFAULT_HTTP_CONNECT_TIMEOUT.toInt() + it.readTimeout = DEFAULT_HTTP_READ_TIMEOUT.toInt() +} as HttpURLConnection @DeleteForDJVM fun URL.post(serializedData: OpaqueBytes, vararg properties: Pair): ByteArray { @@ -524,6 +542,7 @@ fun MutableSet.toSynchronised(): MutableSet = Collections.synchronized /** * List implementation that applies the expensive [transform] function only when the element is accessed and caches calculated values. * Size is very cheap as it doesn't call [transform]. + * Used internally by [net.corda.core.transactions.TraversableTransaction]. */ class LazyMappedList(val originalList: List, val transform: (T, Int) -> U) : AbstractList() { private val partialResolvedList = MutableList(originalList.size) { null } @@ -532,6 +551,15 @@ class LazyMappedList(val originalList: List, val transform: (T, Int) -> return partialResolvedList[index] ?: transform(originalList[index], index).also { computed -> partialResolvedList[index] = computed } } + internal fun eager(onError: (TransactionDeserialisationException, Int) -> U?) { + for (i in 0 until size) { + try { + get(i) + } catch (ex: TransactionDeserialisationException) { + partialResolvedList[i] = onError(ex, i) + } + } + } } /** @@ -540,6 +568,17 @@ class LazyMappedList(val originalList: List, val transform: (T, Int) -> */ fun List.lazyMapped(transform: (T, Int) -> U): List = LazyMappedList(this, transform) +/** + * Iterate over a [LazyMappedList], forcing it to transform all of its elements immediately. + * This transformation is assumed to be "deserialisation". Does nothing for any other kind of [List]. + * WARNING: Any changes made to the [LazyMappedList] contents are PERMANENT! + */ +fun List.eagerDeserialise(onError: (TransactionDeserialisationException, Int) -> T? = { ex, _ -> throw ex }) { + if (this is LazyMappedList<*, T>) { + eager(onError) + } +} + private const val MAX_SIZE = 100 private val warnings = Collections.newSetFromMap(createSimpleCache(MAX_SIZE)).toSynchronised() diff --git a/core/src/main/kotlin/net/corda/core/internal/JarSignatureCollector.kt b/core/src/main/kotlin/net/corda/core/internal/JarSignatureCollector.kt index 202980b907..38435916d5 100644 --- a/core/src/main/kotlin/net/corda/core/internal/JarSignatureCollector.kt +++ b/core/src/main/kotlin/net/corda/core/internal/JarSignatureCollector.kt @@ -19,6 +19,11 @@ object JarSignatureCollector { */ private val unsignableEntryName = "META-INF/(?:(?:.*[.](?:SF|DSA|RSA|EC)|SIG-.*)|INDEX\\.LIST)".toRegex() + /** + * @return if the [entry] [JarEntry] can be signed. + */ + fun isNotSignable(entry: JarEntry): Boolean = entry.isDirectory || unsignableEntryName.matches(entry.name) + /** * Returns an ordered list of every [PublicKey] which has signed every signable item in the given [JarInputStream]. * @@ -57,8 +62,7 @@ object JarSignatureCollector { private val JarInputStream.fileSignerSets: List>> get() = entries.thatAreSignable.shreddedFrom(this).toFileSignerSet().toList() - private val Sequence.thatAreSignable: Sequence get() = - filterNot { entry -> entry.isDirectory || unsignableEntryName.matches(entry.name) } + private val Sequence.thatAreSignable: Sequence get() = filterNot { isNotSignable(it) } private fun Sequence.shreddedFrom(jar: JarInputStream): Sequence = map { entry -> val shredder = ByteArray(1024) // can't share or re-use this, as it's used to compute CRCs during shredding diff --git a/core/src/main/kotlin/net/corda/core/internal/LazyPool.kt b/core/src/main/kotlin/net/corda/core/internal/LazyPool.kt index bc2d0132a6..2c94dea8df 100644 --- a/core/src/main/kotlin/net/corda/core/internal/LazyPool.kt +++ b/core/src/main/kotlin/net/corda/core/internal/LazyPool.kt @@ -62,7 +62,8 @@ class LazyPool( */ fun close(): Iterable { lifeCycle.justTransition(State.FINISHED) - val elements = poolQueue.toList() + // Does not use kotlin toList() as it currently is not safe to use on concurrent data structures. + val elements = ArrayList(poolQueue) poolQueue.clear() return elements } diff --git a/core/src/main/kotlin/net/corda/core/internal/LazyStickyPool.kt b/core/src/main/kotlin/net/corda/core/internal/LazyStickyPool.kt index 52ec2226ae..868997bc56 100644 --- a/core/src/main/kotlin/net/corda/core/internal/LazyStickyPool.kt +++ b/core/src/main/kotlin/net/corda/core/internal/LazyStickyPool.kt @@ -25,7 +25,12 @@ class LazyStickyPool( private val boxes = Array(size) { InstanceBox() } private fun toIndex(stickTo: Any): Int { - return Math.abs(stickTo.hashCode()) % boxes.size + return stickTo.hashCode().let { hashCode -> + when (hashCode) { + Int.MIN_VALUE -> 0 + else -> Math.abs(hashCode) % boxes.size + } + } } fun borrow(stickTo: Any): A { diff --git a/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt b/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt index a0cec55f95..54fdce23eb 100644 --- a/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt +++ b/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt @@ -64,8 +64,7 @@ class ResolveTransactionsFlow(txHashesArg: Set, return sort.complete() } } - - @CordaSerializable + class ExcessivelyLargeTransactionGraph : FlowException() // TODO: Figure out a more appropriate DOS limit here, 5000 is simply a very bad guess. diff --git a/core/src/main/kotlin/net/corda/core/internal/StatePointerSearch.kt b/core/src/main/kotlin/net/corda/core/internal/StatePointerSearch.kt index 0ae69ff7ae..5e6c3f9baf 100644 --- a/core/src/main/kotlin/net/corda/core/internal/StatePointerSearch.kt +++ b/core/src/main/kotlin/net/corda/core/internal/StatePointerSearch.kt @@ -43,7 +43,7 @@ class StatePointerSearch(val state: ContractState) { val fieldsWithObjects = fields.mapNotNull { field -> // Ignore classes which have not been loaded. // Assumption: all required state classes are already loaded. - val packageName = field.type.`package`?.name + val packageName = field.type.packageNameOrNull if (packageName == null) { null } else { @@ -72,7 +72,7 @@ class StatePointerSearch(val state: ContractState) { is StatePointer<*> -> statePointers.add(obj) is Iterable<*> -> handleIterable(obj) else -> { - val packageName = obj.javaClass.`package`.name + val packageName = obj.javaClass.packageNameOrNull ?: "" val isBlackListed = blackListedPackages.any { packageName.startsWith(it) } if (isBlackListed.not()) fieldQueue.addAllFields(obj) } diff --git a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt index b462e60ce5..cad6fde32a 100644 --- a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt +++ b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt @@ -98,15 +98,20 @@ data class NetworkParameters( require(noPackageOverlap(packageOwnership.keys)) { "Multiple packages added to the packageOwnership overlap." } } - fun copy(minimumPlatformVersion: Int, - notaries: List, - maxMessageSize: Int, - maxTransactionSize: Int, - modifiedTime: Instant, - epoch: Int, - whitelistedContractImplementations: Map> + /** + * This is to address backwards compatibility of the API, invariant to package ownership + * addresses bug CORDA-2769 + */ + fun copy(minimumPlatformVersion: Int = this.minimumPlatformVersion, + notaries: List = this.notaries, + maxMessageSize: Int = this.maxMessageSize, + maxTransactionSize: Int = this.maxTransactionSize, + modifiedTime: Instant = this.modifiedTime, + epoch: Int = this.epoch, + whitelistedContractImplementations: Map> = this.whitelistedContractImplementations, + eventHorizon: Duration = this.eventHorizon ): NetworkParameters { - return copy( + return NetworkParameters( minimumPlatformVersion = minimumPlatformVersion, notaries = notaries, maxMessageSize = maxMessageSize, @@ -114,20 +119,24 @@ data class NetworkParameters( modifiedTime = modifiedTime, epoch = epoch, whitelistedContractImplementations = whitelistedContractImplementations, - eventHorizon = eventHorizon + eventHorizon = eventHorizon, + packageOwnership = packageOwnership ) } - fun copy(minimumPlatformVersion: Int, - notaries: List, - maxMessageSize: Int, - maxTransactionSize: Int, - modifiedTime: Instant, - epoch: Int, - whitelistedContractImplementations: Map>, - eventHorizon: Duration + /** + * This is to address backwards compatibility of the API, invariant to package ownership + * addresses bug CORDA-2769 + */ + fun copy(minimumPlatformVersion: Int = this.minimumPlatformVersion, + notaries: List = this.notaries, + maxMessageSize: Int = this.maxMessageSize, + maxTransactionSize: Int = this.maxTransactionSize, + modifiedTime: Instant = this.modifiedTime, + epoch: Int = this.epoch, + whitelistedContractImplementations: Map> = this.whitelistedContractImplementations ): NetworkParameters { - return copy( + return NetworkParameters( minimumPlatformVersion = minimumPlatformVersion, notaries = notaries, maxMessageSize = maxMessageSize, @@ -135,7 +144,8 @@ data class NetworkParameters( modifiedTime = modifiedTime, epoch = epoch, whitelistedContractImplementations = whitelistedContractImplementations, - eventHorizon = eventHorizon + eventHorizon = eventHorizon, + packageOwnership = packageOwnership ) } diff --git a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt index 83490d874f..fe1a809062 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt @@ -11,6 +11,7 @@ import net.corda.core.identity.AbstractParty import net.corda.core.node.services.Vault import net.corda.core.schemas.StatePersistable import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.utilities.OpaqueBytes import java.security.PublicKey import java.time.Instant @@ -102,6 +103,7 @@ sealed class QueryCriteria : GenericQueryCriteria>? = null, @@ -110,14 +112,19 @@ sealed class QueryCriteria : GenericQueryCriteria>?) : this(status, contractStateTypes, participants = null) + @DeprecatedConstructorForDeserialization(version = 3) constructor(status: Vault.StateStatus, contractStateTypes: Set>?, stateRefs: List?) : this( status, contractStateTypes, stateRefs, participants = null ) + @DeprecatedConstructorForDeserialization(version = 4) constructor(status: Vault.StateStatus, contractStateTypes: Set>?, stateRefs: List?, notary: List?) : this( status, contractStateTypes, stateRefs, notary, participants = null ) + @DeprecatedConstructorForDeserialization(version = 5) constructor(status: Vault.StateStatus, contractStateTypes: Set>?, stateRefs: List?, notary: List?, softLockingCondition: SoftLockingCondition?) : this( status, contractStateTypes, stateRefs, notary, softLockingCondition, participants = null ) @@ -174,6 +181,7 @@ sealed class QueryCriteria : GenericQueryCriteria? = null, uuid: List? = null, @@ -182,6 +190,7 @@ sealed class QueryCriteria : GenericQueryCriteria>? = null ) : this(participants, uuid, externalId, status, contractStateTypes, Vault.RelevancyStatus.ALL) + @DeprecatedConstructorForDeserialization(version = 3) constructor( participants: List? = null, linearId: List? = null, @@ -191,6 +200,7 @@ sealed class QueryCriteria : GenericQueryCriteria? = null, linearId: List? = null, @@ -264,6 +274,7 @@ sealed class QueryCriteria : GenericQueryCriteria? = null, owner: List? = null, @@ -325,6 +336,7 @@ sealed class QueryCriteria : GenericQueryCriteria, status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED, @@ -381,10 +393,13 @@ sealed class AttachmentQueryCriteria : GenericQueryCriteria? = null, val versionCondition: ColumnPredicate? = null) : AttachmentQueryCriteria() { // V3 c'tors + @DeprecatedConstructorForDeserialization(version = 3) constructor(uploaderCondition: ColumnPredicate? = null, filenameCondition: ColumnPredicate? = null, uploadDateCondition: ColumnPredicate? = null) : this(uploaderCondition, filenameCondition, uploadDateCondition, null) + @DeprecatedConstructorForDeserialization(version = 1) constructor(uploaderCondition: ColumnPredicate?) : this(uploaderCondition, null) + @DeprecatedConstructorForDeserialization(version = 2) constructor(uploaderCondition: ColumnPredicate?, filenameCondition: ColumnPredicate?) : this(uploaderCondition, filenameCondition, null) override fun visit(parser: AttachmentsQueryCriteriaParser): Collection { diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt index 101329e05a..c97a511db2 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt @@ -1,8 +1,6 @@ @file:KeepForDJVM package net.corda.core.serialization -import co.paralleluniverse.io.serialization.Serialization -import net.corda.core.CordaInternal import net.corda.core.DeleteForDJVM import net.corda.core.DoNotImplement import net.corda.core.KeepForDJVM @@ -12,6 +10,7 @@ import net.corda.core.serialization.internal.effectiveSerializationEnv import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.sequence +import java.io.NotSerializableException import java.sql.Blob data class ObjectWithCompatibleContext(val obj: T, val context: SerializationContext) @@ -152,7 +151,15 @@ interface SerializationContext { */ val lenientCarpenterEnabled: Boolean /** - * If true the serialization evolver will fail if the binary to be deserialized contains more fields then the current object from the classpath. + * If true, deserialization calls using this context will not fallback to using the Class Carpenter to attempt + * to construct classes present in the schema but not on the current classpath. + * + * The default is false. + */ + val carpenterDisabled: Boolean + /** + * If true the serialization evolver will fail if the binary to be deserialized contains more fields then the current object from + * the classpath. * * The default is false. */ @@ -182,6 +189,12 @@ interface SerializationContext { */ fun withLenientCarpenter(): SerializationContext + /** + * Returns a copy of the current context with carpentry of unknown classes disabled. On encountering + * such a class during deserialization the Serialization framework will throw a [NotSerializableException]. + */ + fun withoutCarpenter() : SerializationContext + /** * Return a new context based on this one but with a strict evolution. * @see preventDataLoss @@ -317,6 +330,7 @@ fun T.serialize(serializationFactory: SerializationFactory = Serializa */ @Suppress("unused") @KeepForDJVM +@CordaSerializable class SerializedBytes(bytes: ByteArray) : OpaqueBytes(bytes) { companion object { /** diff --git a/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt b/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt index ea5f0d5f08..93a61c0f2f 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt @@ -327,6 +327,7 @@ object AttachmentsClassLoaderBuilder { .withClassLoader(transactionClassLoader) .withWhitelist(whitelistedClasses) .withCustomSerializers(serializers) + .withoutCarpenter() } // Deserialize all relevant classes in the transaction classloader. diff --git a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt index 01d1058eb8..b15abebf90 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt @@ -28,12 +28,22 @@ import java.util.function.Predicate * - Deserialising the output states. * * All the above refer to inputs using a (txhash, output index) pair. + * + * Usage notes: + * + * [LedgerTransaction] is an abstraction that is meant to be used during the transaction verification stage. + * It needs full access to input states that might be in transactions that are encrypted and unavailable for code running outside the secure enclave. + * Also, it might need to deserialize states with code that might not be available on the classpath. + * + * Because of this, trying to create or use a [LedgerTransaction] for any other purpose then transaction verification can result in unexpected exceptions, + * which need de be handled. + * + * [LedgerTransaction]s should never be instantiated directly from client code, but rather via WireTransaction.toLedgerTransaction */ @KeepForDJVM @CordaSerializable class LedgerTransaction @ConstructorForDeserialization -// LedgerTransaction is not meant to be created directly from client code, but rather via WireTransaction.toLedgerTransaction private constructor( // DOCSTART 1 /** The resolved input states which will be consumed/invalidated by the execution of this transaction. */ @@ -67,7 +77,6 @@ private constructor( private var serializedReferences: List? = null init { - checkBaseInvariants() if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" } checkNotaryWhitelisted() } @@ -133,6 +142,9 @@ private constructor( // Switch thread local deserialization context to using a cached attachments classloader. This classloader enforces various rules // like no-overlap, package namespace ownership and (in future) deterministic Java. return AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(this.attachments + extraAttachments, getParamsWithGoo(), id) { transactionClassLoader -> + // Create a copy of the outer LedgerTransaction which deserializes all fields inside the [transactionClassLoader]. + // Only the copy will be used for verification, and the outer shell will be discarded. + // This artifice is required to preserve backwards compatibility. Verifier(createLtxForVerification(), transactionClassLoader) } } @@ -163,12 +175,17 @@ private constructor( return FlowLogic.currentTopLevel?.serviceHub?.networkParameters } + /** + * Create the [LedgerTransaction] instance that will be used by contract verification. + * + * This method needs to run in the special transaction attachments classloader context. + */ private fun createLtxForVerification(): LedgerTransaction { val serializedInputs = this.serializedInputs val serializedReferences = this.serializedReferences val componentGroups = this.componentGroups - return if (serializedInputs != null && serializedReferences != null && componentGroups != null) { + val transaction= if (serializedInputs != null && serializedReferences != null && componentGroups != null) { // Deserialize all relevant classes in the transaction classloader. val deserializedInputs = serializedInputs.map { it.toStateAndRef() } val deserializedReferences = serializedReferences.map { it.toStateAndRef() } @@ -198,6 +215,12 @@ private constructor( "The result of the verify method might not be accurate.") this } + + // This check accesses input states and must be run in this context. + // It must run on the instance that is verified, not on the outer LedgerTransaction shell. + transaction.checkBaseInvariants() + + return transaction } /** diff --git a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt index cf92239e2d..a7e343161d 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt @@ -7,6 +7,7 @@ import net.corda.core.KeepForDJVM import net.corda.core.contracts.* import net.corda.core.crypto.* import net.corda.core.identity.Party +import net.corda.core.internal.TransactionDeserialisationException import net.corda.core.internal.TransactionVerifierServiceInternal import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.internalFindTrustedAttachmentForClass @@ -18,6 +19,7 @@ import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow +import java.io.NotSerializableException import java.security.KeyPair import java.security.PublicKey import java.security.SignatureException @@ -226,27 +228,53 @@ data class SignedTransaction(val txBits: SerializedBytes, // TODO: allow non-blocking verification. services.transactionVerifierService.verify(ltx).getOrThrow() } catch (e: NoClassDefFoundError) { - // Transactions created before Corda 4 can be missing dependencies on other cordapps. - // This code attempts to find the missing dependency in the attachment storage among the trusted contract attachments. - // When it finds one, it instructs the verifier to use it to create the transaction classloader. - // TODO - add check that transaction was created before Corda 4. - - // TODO - should this be a [TransactionVerificationException]? - val missingClass = requireNotNull(e.message) { "Transaction $ltx is incorrectly formed." } - - val attachment = requireNotNull(services.attachments.internalFindTrustedAttachmentForClass(missingClass)) { - "Transaction $ltx is incorrectly formed. Could not find local dependency for class: $missingClass." + if (e.message != null) { + verifyWithExtraDependency(e.message!!, ltx, services, e) + } else { + throw e + } + } catch (e: NotSerializableException) { + if (e.cause is ClassNotFoundException && e.cause!!.message != null) { + verifyWithExtraDependency(e.cause!!.message!!.replace(".", "/"), ltx, services, e) + } else { + throw e + } + } catch (e: TransactionDeserialisationException) { + if (e.cause is NotSerializableException && e.cause.cause is ClassNotFoundException && e.cause.cause!!.message != null) { + verifyWithExtraDependency(e.cause.cause!!.message!!.replace(".", "/"), ltx, services, e) + } else { + throw e } - - log.warn("""Detected that transaction ${this.id} does not contain all cordapp dependencies. - |This may be the result of a bug in a previous version of Corda. - |Attempting to verify using the additional dependency: $attachment. - |Please check with the originator that this is a valid transaction.""".trimMargin()) - - (services.transactionVerifierService as TransactionVerifierServiceInternal).verify(ltx, listOf(attachment)).getOrThrow() } } + // Transactions created before Corda 4 can be missing dependencies on other CorDapps. + // This code attempts to find the missing dependency in the attachment storage among the trusted attachments. + // When it finds one, it instructs the verifier to use it to create the transaction classloader. + private fun verifyWithExtraDependency(missingClass: String, ltx: LedgerTransaction, services: ServiceHub, exception: Throwable) { + // If that transaction was created with and after Corda 4 then just fail. + // The lenient dependency verification is only supported for Corda 3 transactions. + // To detect if the transaction was created before Corda 4 we check if the transaction has the NetworkParameters component group. + if (this.networkParametersHash != null) { + throw exception + } + + val attachment = requireNotNull(services.attachments.internalFindTrustedAttachmentForClass(missingClass)) { + """Transaction $ltx is incorrectly formed. Most likely it was created during version 3 of Corda when the verification logic was more lenient. + |Attempted to find local dependency for class: $missingClass, but could not find one. + |If you wish to verify this transaction, please contact the originator of the transaction and install the provided missing JAR. + |You can install it using the RPC command: `uploadAttachment` without restarting the node. + |""".trimMargin() + } + + log.warn("""Detected that transaction ${this.id} does not contain all cordapp dependencies. + |This may be the result of a bug in a previous version of Corda. + |Attempting to verify using the additional trusted dependency: $attachment for class $missingClass. + |Please check with the originator that this is a valid transaction.""".trimMargin()) + + (services.transactionVerifierService as TransactionVerifierServiceInternal).verify(ltx, listOf(attachment)).getOrThrow() + } + /** * Resolves the underlying base transaction and then returns it, handling any special case transactions such as * [NotaryChangeWireTransaction]. @@ -319,7 +347,6 @@ data class SignedTransaction(val txBits: SerializedBytes, } @KeepForDJVM - @CordaSerializable class SignaturesMissingException(val missing: Set, val descriptions: List, override val id: SecureHash) : NamedByHash, SignatureException(missingSignatureMsg(missing, descriptions, id)), CordaThrowable by CordaException(missingSignatureMsg(missing, descriptions, id)) diff --git a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt index 2eb82bac9d..69f9013678 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt @@ -7,8 +7,6 @@ import net.corda.core.contracts.* import net.corda.core.crypto.* import net.corda.core.identity.Party import net.corda.core.internal.* -import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION -import net.corda.core.internal.cordapp.CordappResolver import net.corda.core.node.NetworkParameters import net.corda.core.node.ServiceHub import net.corda.core.node.ServicesForResolution @@ -18,6 +16,7 @@ import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationFactory import net.corda.core.utilities.contextLogger +import java.io.NotSerializableException import java.security.PublicKey import java.time.Duration import java.time.Instant @@ -172,21 +171,25 @@ open class TransactionBuilder( try { wireTx.toLedgerTransaction(services).verify() } catch (e: NoClassDefFoundError) { - val missingClass = e.message - requireNotNull(missingClass) { "Transaction is incorrectly formed." } - - val attachment = services.attachments.internalFindTrustedAttachmentForClass(missingClass!!) - ?: throw IllegalArgumentException("Attempted to find dependent attachment for class $missingClass, but could not find a suitable candidate.") - - log.warnOnce("""The transaction currently built is missing an attachment for class: $missingClass. - Automatically attaching contract dependency $attachment. - It is strongly recommended to check that this is the desired attachment, and to manually add it to the transaction builder. - """.trimIndent()) - - addAttachment(attachment.id) + val missingClass = e.message ?: throw e + addMissingAttachment(missingClass, services) return true - // Ignore these exceptions as they will break unit tests. - // The point here is only to detect missing dependencies. The other exceptions are irrelevant. + } catch (e: TransactionDeserialisationException) { + if (e.cause is NotSerializableException && e.cause.cause is ClassNotFoundException) { + val missingClass = e.cause.cause!!.message ?: throw e + addMissingAttachment(missingClass.replace(".", "/"), services) + return true + } + return false + } catch (e: NotSerializableException) { + if (e.cause is ClassNotFoundException) { + val missingClass = e.cause!!.message ?: throw e + addMissingAttachment(missingClass.replace(".", "/"), services) + return true + } + return false + // Ignore these exceptions as they will break unit tests. + // The point here is only to detect missing dependencies. The other exceptions are irrelevant. } catch (tve: TransactionVerificationException) { } catch (tre: TransactionResolutionException) { } catch (ise: IllegalStateException) { @@ -195,6 +198,21 @@ open class TransactionBuilder( return false } + private fun addMissingAttachment(missingClass: String, services: ServicesForResolution) { + val attachment = services.attachments.internalFindTrustedAttachmentForClass(missingClass) + ?: throw IllegalArgumentException("""The transaction currently built is missing an attachment for class: $missingClass. + Attempted to find a suitable attachment but could not find any in the storage. + Please contact the developer of the CorDapp for further instructions. + """.trimIndent()) + + log.warnOnce("""The transaction currently built is missing an attachment for class: $missingClass. + Automatically attaching contract dependency $attachment. + Please contact the developer of the CorDapp and install the latest version, as this approach might be insecure. + """.trimIndent()) + + addAttachment(attachment.id) + } + /** * This method is responsible for selecting the contract versions to be used for the current transaction and resolve the output state [AutomaticPlaceholderConstraint]s. * The contract attachments are used to create a deterministic Classloader to deserialise the transaction and to run the contract verification. @@ -653,15 +671,8 @@ with @BelongsToContract, or supply an explicit contract parameter to addOutputSt /** Returns an immutable list of output [TransactionState]s. */ fun outputStates(): List> = ArrayList(outputs) - /** Returns an immutable list of [Command]s, grouping by [CommandData] and joining signers (from v4, v3 and below return all commands with duplicates for different signers). */ - fun commands(): List> { - return if (CordappResolver.currentTargetVersion >= CORDA_VERSION_THAT_INTRODUCED_FLATTENED_COMMANDS) { - commands.groupBy { cmd -> cmd.value } - .entries.map { (data, cmds) -> Command(data, cmds.flatMap(Command<*>::signers).toSet().toList()) } - } else { - ArrayList(commands) - } - } + /** Returns an immutable list of [Command]s. */ + fun commands(): List> = ArrayList(commands) /** * Sign the built transaction and return it. This is an internal function for use by the service hub, please use diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt index b13f8ec6b1..f27444c498 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -9,7 +9,6 @@ import net.corda.core.contracts.ComponentGroupEnum.OUTPUTS_GROUP import net.corda.core.crypto.* import net.corda.core.identity.Party import net.corda.core.internal.* -import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION import net.corda.core.node.NetworkParameters import net.corda.core.node.ServiceHub import net.corda.core.node.ServicesForResolution diff --git a/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt b/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt index 296929d29b..a09f7e78b8 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt @@ -20,7 +20,6 @@ import javax.xml.bind.DatatypeConverter * @property offset The start position of the sequence within the byte array. * @property size The number of bytes this sequence represents. */ -@CordaSerializable @KeepForDJVM sealed class ByteSequence(private val _bytes: ByteArray, val offset: Int, val size: Int) : Comparable { /** @@ -145,6 +144,7 @@ sealed class ByteSequence(private val _bytes: ByteArray, val offset: Int, val si * functionality to Java, but it won't arrive for a few years yet! */ @KeepForDJVM +@CordaSerializable open class OpaqueBytes(bytes: ByteArray) : ByteSequence(bytes, 0, bytes.size) { companion object { /** diff --git a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt index c21bd2d271..60e55b0745 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt @@ -4,15 +4,12 @@ package net.corda.core.utilities import net.corda.core.DeleteForDJVM import net.corda.core.KeepForDJVM -import net.corda.core.internal.LazyMappedList import net.corda.core.internal.concurrent.get -import net.corda.core.internal.createSimpleCache import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.CordaSerializable import org.slf4j.Logger import org.slf4j.LoggerFactory import java.time.Duration -import java.util.* import java.util.concurrent.ExecutionException import java.util.concurrent.Future import kotlin.reflect.KProperty diff --git a/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt b/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt index 554526d71c..ffe765d274 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt @@ -5,7 +5,6 @@ import net.corda.core.internal.STRUCTURAL_STEP_PREFIX import net.corda.core.serialization.CordaSerializable import rx.Observable import rx.Subscription -import rx.subjects.PublishSubject import rx.subjects.ReplaySubject import java.util.* @@ -51,7 +50,9 @@ class ProgressTracker(vararg inputSteps: Step) { } } - /** The superclass of all step objects. */ + /** + * The superclass of all step objects. + */ @CordaSerializable open class Step(open val label: String) { open val changes: Observable get() = Observable.empty() @@ -85,16 +86,22 @@ class ProgressTracker(vararg inputSteps: Step) { private val childProgressTrackers = mutableMapOf() - /** The steps in this tracker, same as the steps passed to the constructor but with UNSTARTED and DONE inserted. */ + /** + * The steps in this tracker, same as the steps passed to the constructor but with UNSTARTED and DONE inserted. + */ val steps = arrayOf(UNSTARTED, STARTING, *inputSteps, DONE) private var _allStepsCache: List> = _allSteps() // This field won't be serialized. - private val _changes by transient { PublishSubject.create() } - private val _stepsTreeChanges by transient { PublishSubject.create>>() } + private val _changes by transient { ReplaySubject.create() } + private val _stepsTreeChanges by transient { ReplaySubject.create>>() } private val _stepsTreeIndexChanges by transient { ReplaySubject.create() } + /** + * Reading returns the value of steps[stepIndex], writing moves the position of the current tracker. Once moved to + * the [DONE] state, this tracker is finished and the current step cannot be moved again. + */ var currentStep: Step get() = steps[stepIndex] set(value) { @@ -135,6 +142,9 @@ class ProgressTracker(vararg inputSteps: Step) { steps.forEach { configureChildTrackerForStep(it) } + // Immediately update the step tree observable to ensure the first update the client receives is the initial state of the progress + // tracker. + _stepsTreeChanges.onNext(allStepsLabels) this.currentStep = UNSTARTED } @@ -145,13 +155,17 @@ class ProgressTracker(vararg inputSteps: Step) { } } - /** The zero-based index of the current step in the [steps] array (i.e. with UNSTARTED and DONE) */ + /** + * The zero-based index of the current step in the [steps] array (i.e. with UNSTARTED and DONE) + */ var stepIndex: Int = 0 private set(value) { field = value } - /** The zero-bases index of the current step in a [allStepsLabels] list */ + /** + * The zero-bases index of the current step in a [allStepsLabels] list + */ var stepsTreeIndex: Int = -1 private set(value) { if (value != field) { @@ -161,26 +175,12 @@ class ProgressTracker(vararg inputSteps: Step) { } /** - * Reading returns the value of steps[stepIndex], writing moves the position of the current tracker. Once moved to - * the [DONE] state, this tracker is finished and the current step cannot be moved again. + * Returns the current step, descending into children to find the deepest step we are up to. */ - - /** Returns the current step, descending into children to find the deepest step we are up to. */ + @Suppress("unused") val currentStepRecursive: Step get() = getChildProgressTracker(currentStep)?.currentStepRecursive ?: currentStep - /** Returns the current step, descending into children to find the deepest started step we are up to. */ - private val currentStartedStepRecursive: Step - get() { - val step = getChildProgressTracker(currentStep)?.currentStartedStepRecursive ?: currentStep - return if (step == UNSTARTED) currentStep else step - } - - private fun currentStepRecursiveWithoutUnstarted(): Step { - val stepRecursive = getChildProgressTracker(currentStep)?.currentStartedStepRecursive - return if (stepRecursive == null || stepRecursive == UNSTARTED) currentStep else stepRecursive - } - fun getChildProgressTracker(step: Step): ProgressTracker? = childProgressTrackers[step]?.tracker fun setChildProgressTracker(step: ProgressTracker.Step, childProgressTracker: ProgressTracker) { @@ -214,12 +214,17 @@ class ProgressTracker(vararg inputSteps: Step) { _stepsTreeChanges.onError(error) } - /** The parent of this tracker: set automatically by the parent when a tracker is added as a child */ + /** + * The parent of this tracker: set automatically by the parent when a tracker is added as a child + */ var parent: ProgressTracker? = null private set - /** Walks up the tree to find the top level tracker. If this is the top level tracker, returns 'this' */ - @Suppress("unused") // TODO: Review by EOY2016 if this property is useful anywhere. + /** + * Walks up the tree to find the top level tracker. If this is the top level tracker, returns 'this'. + * Required for API compatibility. + */ + @Suppress("unused") val topLevelTracker: ProgressTracker get() { var cursor: ProgressTracker = this @@ -234,9 +239,21 @@ class ProgressTracker(vararg inputSteps: Step) { recalculateStepsTreeIndex() } + private fun getStepIndexAtLevel(): Int { + // This gets the index of the current step in the context of this progress tracker, so it will always be at the top level in + // the allStepsCache. + val index = _allStepsCache.indexOf(Pair(0, currentStep)) + return if (index >= 0) index else 0 + } + + private fun getCurrentStepTreeIndex(): Int { + val indexAtLevel = getStepIndexAtLevel() + val additionalIndex = getChildProgressTracker(currentStep)?.getCurrentStepTreeIndex() ?: 0 + return indexAtLevel + additionalIndex + } + private fun recalculateStepsTreeIndex() { - val step = currentStepRecursiveWithoutUnstarted() - stepsTreeIndex = _allStepsCache.indexOfFirst { it.second == step } + stepsTreeIndex = getCurrentStepTreeIndex() } private fun _allSteps(level: Int = 0): List> { @@ -291,7 +308,9 @@ class ProgressTracker(vararg inputSteps: Step) { */ val stepsTreeIndexChanges: Observable get() = _stepsTreeIndexChanges - /** Returns true if the progress tracker has ended, either by reaching the [DONE] step or prematurely with an error */ + /** + * Returns true if the progress tracker has ended, either by reaching the [DONE] step or prematurely with an error + */ val hasEnded: Boolean get() = _changes.hasCompleted() || _changes.hasThrowable() } // TODO: Expose the concept of errors. diff --git a/core/src/smoke-test/kotlin/net/corda/core/NodeVersioningTest.kt b/core/src/smoke-test/kotlin/net/corda/core/NodeVersioningTest.kt index 33216034da..9cca6d959a 100644 --- a/core/src/smoke-test/kotlin/net/corda/core/NodeVersioningTest.kt +++ b/core/src/smoke-test/kotlin/net/corda/core/NodeVersioningTest.kt @@ -21,7 +21,7 @@ import kotlin.streams.toList class NodeVersioningTest { private companion object { - val user = User("user1", "test", permissions = setOf("ALL")) + val superUser = User("superUser", "test", permissions = setOf("ALL")) val port = AtomicInteger(15100) } @@ -33,7 +33,7 @@ class NodeVersioningTest { rpcPort = port.andIncrement, rpcAdminPort = port.andIncrement, isNotary = true, - users = listOf(user) + users = listOf(superUser) ) private val aliceConfig = NodeConfig( @@ -42,7 +42,7 @@ class NodeVersioningTest { rpcPort = port.andIncrement, rpcAdminPort = port.andIncrement, isNotary = false, - users = listOf(user) + users = listOf(superUser) ) private lateinit var notary: NodeProcess @@ -73,7 +73,7 @@ class NodeVersioningTest { selfCordapp.copyToDirectory(cordappsDir) factory.create(aliceConfig).use { alice -> - alice.connect().use { + alice.connect(superUser).use { val rpc = it.proxy assertThat(rpc.protocolVersion).isEqualTo(PLATFORM_VERSION) assertThat(rpc.nodeInfo().platformVersion).isEqualTo(PLATFORM_VERSION) diff --git a/core/src/smoke-test/kotlin/net/corda/core/cordapp/CordappSmokeTest.kt b/core/src/smoke-test/kotlin/net/corda/core/cordapp/CordappSmokeTest.kt index 1d51f74194..6e9cf72bff 100644 --- a/core/src/smoke-test/kotlin/net/corda/core/cordapp/CordappSmokeTest.kt +++ b/core/src/smoke-test/kotlin/net/corda/core/cordapp/CordappSmokeTest.kt @@ -38,7 +38,7 @@ import kotlin.streams.toList class CordappSmokeTest { private companion object { - val user = User("user1", "test", permissions = setOf("ALL")) + val superUser = User("superUser", "test", permissions = setOf("ALL")) val port = AtomicInteger(15100) } @@ -50,7 +50,7 @@ class CordappSmokeTest { rpcPort = port.andIncrement, rpcAdminPort = port.andIncrement, isNotary = true, - users = listOf(user) + users = listOf(superUser) ) private val aliceConfig = NodeConfig( @@ -59,7 +59,7 @@ class CordappSmokeTest { rpcPort = port.andIncrement, rpcAdminPort = port.andIncrement, isNotary = false, - users = listOf(user) + users = listOf(superUser) ) private lateinit var notary: NodeProcess @@ -92,7 +92,7 @@ class CordappSmokeTest { createDummyNodeInfo(additionalNodeInfoDir) factory.create(aliceConfig).use { alice -> - alice.connect().use { connectionToAlice -> + alice.connect(superUser).use { connectionToAlice -> val aliceIdentity = connectionToAlice.proxy.nodeInfo().legalIdentitiesAndCerts.first().party val future = connectionToAlice.proxy.startFlow(::GatherContextsFlow, aliceIdentity).returnValue val (sessionInitContext, sessionConfirmContext) = future.getOrThrow() diff --git a/core/src/test/kotlin/net/corda/core/contracts/TransactionVerificationExceptionSerialisationTests.kt b/core/src/test/kotlin/net/corda/core/contracts/TransactionVerificationExceptionSerialisationTests.kt index 6ee97c6031..4ece674122 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/TransactionVerificationExceptionSerialisationTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/TransactionVerificationExceptionSerialisationTests.kt @@ -8,6 +8,7 @@ import net.corda.serialization.internal.amqp.DeserializationInput import net.corda.serialization.internal.amqp.SerializationOutput import net.corda.serialization.internal.amqp.SerializerFactoryBuilder import net.corda.serialization.internal.amqp.custom.PublicKeySerializer +import net.corda.serialization.internal.amqp.custom.ThrowableSerializer import net.corda.testing.core.DUMMY_BANK_A_NAME import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.TestIdentity @@ -18,11 +19,12 @@ class TransactionVerificationExceptionSerialisationTests { private fun defaultFactory() = SerializerFactoryBuilder.build( AllWhitelist, ClassLoader.getSystemClassLoader() - ) + ).apply { register(ThrowableSerializer(this)) } private val context get() = AMQP_RPC_CLIENT_CONTEXT private val txid = SecureHash.allOnesHash + private val attachmentHash = SecureHash.allOnesHash private val factory = defaultFactory() @Test @@ -52,7 +54,7 @@ class TransactionVerificationExceptionSerialisationTests { context) assertEquals(exception.message, exception2.message) - assertEquals(exception.cause?.message, exception2.cause?.message) + assertEquals("java.lang.Throwable: ${exception.cause?.message}", exception2.cause?.message) assertEquals(exception.txId, exception2.txId) } @@ -89,7 +91,7 @@ class TransactionVerificationExceptionSerialisationTests { context) assertEquals(exception.message, exception2.message) - assertEquals(exception.cause?.message, exception2.cause?.message) + assertEquals("java.lang.Throwable: ${exception.cause?.message}", exception2.cause?.message) assertEquals(exception.txId, exception2.txId) } @@ -122,4 +124,55 @@ class TransactionVerificationExceptionSerialisationTests { assertEquals(exception.cause?.message, exception2.cause?.message) assertEquals(exception.txId, exception2.txId) } + + @Test + fun overlappingAttachmentsExceptionTest() { + val exc = TransactionVerificationException.OverlappingAttachmentsException(txid, "foo/bar/baz") + val exc2 = DeserializationInput(factory).deserialize( + SerializationOutput(factory).serialize(exc, context), + context) + + assertEquals(exc.message, exc2.message) + } + + @Test + fun packageOwnershipExceptionTest() { + val exc = TransactionVerificationException.PackageOwnershipException( + txid, + attachmentHash, + "InvalidClass", + "com.invalid") + + val exc2 = DeserializationInput(factory).deserialize( + SerializationOutput(factory).serialize(exc, context), + context) + + assertEquals(exc.message, exc2.message) + } + + @Test + fun invalidAttachmentExceptionTest() { + val exc = TransactionVerificationException.InvalidAttachmentException( + txid, + attachmentHash) + + val exc2 = DeserializationInput(factory).deserialize( + SerializationOutput(factory).serialize(exc, context), + context) + + assertEquals(exc.message, exc2.message) + } + + @Test + fun untrustedAttachmentsExceptionTest() { + val exc = TransactionVerificationException.UntrustedAttachmentsException( + txid, + listOf(attachmentHash)) + + val exc2 = DeserializationInput(factory).deserialize( + SerializationOutput(factory).serialize(exc, context), + context) + + assertEquals(exc.message, exc2.message) + } } \ No newline at end of file diff --git a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt index 0452b9cddd..a637cb8f1a 100644 --- a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt @@ -57,19 +57,6 @@ class FinalityFlowTests : WithFinality { willThrow()) } - @Test - fun `prevent use of the old API if the CorDapp target version is 4`() { - val bob = createBob() - val stx = aliceNode.issuesCashTo(bob) - val resultFuture = CordappResolver.withCordapp(targetPlatformVersion = 4) { - @Suppress("DEPRECATION") - aliceNode.startFlowAndRunNetwork(FinalityFlow(stx)).resultFuture - } - assertThatIllegalArgumentException().isThrownBy { - resultFuture.getOrThrow() - }.withMessageContaining("A flow session for each external participant to the transaction must be provided.") - } - @Test fun `allow use of the old API if the CorDapp target version is 3`() { val oldBob = createBob(cordapps = listOf(tokenOldCordapp())) diff --git a/core/src/test/kotlin/net/corda/core/flows/ReferencedStatesFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/ReferencedStatesFlowTests.kt index a4f84d8fd5..b64b60be49 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ReferencedStatesFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ReferencedStatesFlowTests.kt @@ -15,15 +15,9 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.VersionInfo import net.corda.testing.common.internal.testNetworkParameters -import net.corda.testing.core.SerializationEnvironmentRule -import net.corda.testing.internal.vault.DUMMY_LINEAR_CONTRACT_PROGRAM_ID -import net.corda.testing.internal.vault.DummyLinearContract -import net.corda.testing.node.StartedMockNode import net.corda.testing.node.internal.* -import net.corda.testing.node.transaction import org.junit.After import org.junit.Before -import org.junit.Rule import org.junit.Test import kotlin.test.assertEquals @@ -121,6 +115,58 @@ class ReferencedStatesFlowTests { assertEquals(2, allRefStates.states.size) } + @Test + fun `check old ref state is consumed when update used in tx with relevant states`() { + // 1. Create a state to be used as a reference state. Don't share it. + val newRefTx = nodes[0].services.startFlow(CreateRefState()).resultFuture.getOrThrow() + val newRefState = newRefTx.tx.outRefsOfType().single() + + // 2. Use the "newRefState" in a transaction involving another party (nodes[1]) which creates a new state. They should store the new state and the reference state. + val newTx = nodes[0].services.startFlow(UseRefState(nodes[1].info.legalIdentities.first(), newRefState.state.data.linearId)) + .resultFuture.getOrThrow() + // Wait until node 1 stores the new tx. + nodes[1].services.validatedTransactions.updates.filter { it.id == newTx.id }.toFuture().getOrThrow() + // Check that nodes[1] has finished recording the transaction (and updating the vault.. hopefully!). + // nodes[1] should have two states. The newly created output of type "Regular.State" and the reference state created by nodes[0]. + assertEquals(2, nodes[1].services.vaultService.queryBy().states.size) + // Now let's find the specific reference state on nodes[1]. + val refStateLinearId = newRefState.state.data.linearId + val query = QueryCriteria.LinearStateQueryCriteria(linearId = listOf(refStateLinearId)) + val theReferencedState = nodes[1].services.vaultService.queryBy(query) + // There should be one result - the reference state. + assertEquals(newRefState, theReferencedState.states.single()) + // The reference state should not be consumed. + assertEquals(Vault.StateStatus.UNCONSUMED, theReferencedState.statesMetadata.single().status) + // nodes[0] should also have the same state. + val nodeZeroQuery = QueryCriteria.LinearStateQueryCriteria(linearId = listOf(refStateLinearId)) + val theReferencedStateOnNodeZero = nodes[0].services.vaultService.queryBy(nodeZeroQuery) + assertEquals(newRefState, theReferencedStateOnNodeZero.states.single()) + assertEquals(Vault.StateStatus.UNCONSUMED, theReferencedStateOnNodeZero.statesMetadata.single().status) + + // 3. Update the reference state but don't share the update. + val updatedRefTx = nodes[0].services.startFlow(UpdateRefState(newRefState)).resultFuture.getOrThrow() + + // 4. Use the evolved state as a reference state. + val updatedTx = nodes[0].services.startFlow(UseRefState(nodes[1].info.legalIdentities.first(), newRefState.state.data.linearId)) + .resultFuture.getOrThrow() + // Wait until node 1 stores the new tx. + nodes[1].services.validatedTransactions.updates.filter { it.id == updatedTx.id }.toFuture().getOrThrow() + // Check that nodes[1] has finished recording the transaction (and updating the vault.. hopefully!). + // nodes[1] should have four states. The originals, plus the newly created output of type "Regular.State" and the reference state created by nodes[0]. + assertEquals(4, nodes[1].services.vaultService.queryBy(QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL)).states.size) + // Now let's find the original reference state on nodes[1]. + val updatedQuery = QueryCriteria.VaultQueryCriteria(stateRefs = listOf(newRefState.ref), status = Vault.StateStatus.ALL) + val theOriginalReferencedState = nodes[1].services.vaultService.queryBy(updatedQuery) + // There should be one result - the original reference state. + assertEquals(newRefState, theOriginalReferencedState.states.single()) + // The reference state should be consumed. + assertEquals(Vault.StateStatus.CONSUMED, theOriginalReferencedState.statesMetadata.single().status) + // nodes[0] should also have the same state. + val theOriginalReferencedStateOnNodeZero = nodes[0].services.vaultService.queryBy(updatedQuery) + assertEquals(newRefState, theOriginalReferencedStateOnNodeZero.states.single()) + assertEquals(Vault.StateStatus.CONSUMED, theOriginalReferencedStateOnNodeZero.statesMetadata.single().status) + } + // A dummy reference state contract. class RefState : Contract { companion object { diff --git a/core/src/test/kotlin/net/corda/core/internal/TopologicalSortTest.kt b/core/src/test/kotlin/net/corda/core/internal/TopologicalSortTest.kt index 8425bca130..556070f2c6 100644 --- a/core/src/test/kotlin/net/corda/core/internal/TopologicalSortTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/TopologicalSortTest.kt @@ -1,9 +1,7 @@ package net.corda.core.internal import net.corda.client.mock.Generator -import net.corda.core.contracts.ContractState -import net.corda.core.contracts.StateRef -import net.corda.core.contracts.TransactionState +import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignatureMetadata import net.corda.core.crypto.TransactionSignature @@ -30,11 +28,12 @@ class TopologicalSortTest { override val references: List = emptyList() ) : CoreTransaction() { override val outputs: List> = (1..numberOfOutputs).map { - TransactionState(DummyState(), "", notary) + TransactionState(DummyState(), Contract::class.java.name, notary) } override val networkParametersHash: SecureHash? = testNetworkParameters().serialize().hash } + @BelongsToContract(Contract::class) class DummyState : ContractState { override val participants: List = emptyList() } diff --git a/core/src/test/kotlin/net/corda/core/node/NetworkParametersTest.kt b/core/src/test/kotlin/net/corda/core/node/NetworkParametersTest.kt index 9f2b549ddc..939763db56 100644 --- a/core/src/test/kotlin/net/corda/core/node/NetworkParametersTest.kt +++ b/core/src/test/kotlin/net/corda/core/node/NetworkParametersTest.kt @@ -4,6 +4,7 @@ import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.crypto.generateKeyPair import net.corda.core.internal.getPackageOwnerOf +import net.corda.core.node.services.AttachmentId import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.days import net.corda.core.utilities.getOrThrow @@ -12,10 +13,7 @@ import net.corda.finance.flows.CashIssueFlow import net.corda.node.services.config.NotaryConfig import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.testing.common.internal.testNetworkParameters -import net.corda.testing.core.ALICE_NAME -import net.corda.testing.core.BOB_NAME -import net.corda.testing.core.DUMMY_NOTARY_NAME -import net.corda.testing.core.singleIdentity +import net.corda.testing.core.* import net.corda.testing.node.MockNetworkNotarySpec import net.corda.testing.node.MockNetworkParameters import net.corda.testing.node.internal.InternalMockNetwork @@ -26,6 +24,7 @@ import org.assertj.core.api.Assertions.* import org.junit.After import org.junit.Test import java.nio.file.Path +import java.time.Duration import java.time.Instant import kotlin.test.assertEquals import kotlin.test.assertFails @@ -64,6 +63,34 @@ class NetworkParametersTest { alice.start() } + @Test + fun `that we can copy while preserving the event horizon`() { + // this is defensive tests in response to CORDA-2769 + val aliceNotaryParty = TestIdentity(ALICE_NAME).party + val aliceNotaryInfo = NotaryInfo(aliceNotaryParty, false) + val nm1 = NetworkParameters( + minimumPlatformVersion = 1, + notaries = listOf(aliceNotaryInfo), + maxMessageSize = Int.MAX_VALUE, + maxTransactionSize = Int.MAX_VALUE, + modifiedTime = Instant.now(), + epoch = 1, + whitelistedContractImplementations = mapOf("MyClass" to listOf(AttachmentId.allOnesHash)), + eventHorizon = Duration.ofDays(1) + ) + val twoDays = Duration.ofDays(2) + val nm2 = nm1.copy(minimumPlatformVersion = 2, eventHorizon = twoDays) + + assertEquals(2, nm2.minimumPlatformVersion) + assertEquals(nm1.notaries, nm2.notaries) + assertEquals(nm1.maxMessageSize, nm2.maxMessageSize) + assertEquals(nm1.maxTransactionSize, nm2.maxTransactionSize) + assertEquals(nm1.modifiedTime, nm2.modifiedTime) + assertEquals(nm1.epoch, nm2.epoch) + assertEquals(nm1.whitelistedContractImplementations, nm2.whitelistedContractImplementations) + assertEquals(twoDays, nm2.eventHorizon) + } + // Notaries tests @Test fun `choosing notary not specified in network parameters will fail`() { diff --git a/core/src/test/kotlin/net/corda/core/node/VaultUpdateTests.kt b/core/src/test/kotlin/net/corda/core/node/VaultUpdateTests.kt index 53c9e1ae6a..d56bdfef57 100644 --- a/core/src/test/kotlin/net/corda/core/node/VaultUpdateTests.kt +++ b/core/src/test/kotlin/net/corda/core/node/VaultUpdateTests.kt @@ -14,7 +14,7 @@ import kotlin.test.assertFailsWith class VaultUpdateTests { private companion object { - const val DUMMY_PROGRAM_ID = "net.corda.core.node.VaultUpdateTests.DummyContract" + const val DUMMY_PROGRAM_ID = "net.corda.core.node.VaultUpdateTests\$DummyContract" val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party val emptyUpdate = Vault.Update(emptySet(), emptySet(), type = Vault.UpdateType.GENERAL, references = emptySet()) } @@ -25,6 +25,7 @@ class VaultUpdateTests { } } + @BelongsToContract(DummyContract::class) private class DummyState : ContractState { override val participants: List = emptyList() } diff --git a/core/src/test/kotlin/net/corda/core/transactions/TransactionBuilderTest.kt b/core/src/test/kotlin/net/corda/core/transactions/TransactionBuilderTest.kt index 4881ebf5e7..abadaedeb7 100644 --- a/core/src/test/kotlin/net/corda/core/transactions/TransactionBuilderTest.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionBuilderTest.kt @@ -10,8 +10,6 @@ import net.corda.core.crypto.SecureHash import net.corda.core.identity.Party import net.corda.core.internal.AbstractAttachment import net.corda.core.internal.PLATFORM_VERSION -import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION -import net.corda.core.internal.cordapp.CordappResolver import net.corda.core.node.ServicesForResolution import net.corda.core.node.ZoneVersionTooLowException import net.corda.core.node.services.AttachmentStorage @@ -115,27 +113,6 @@ class TransactionBuilderTest { assertThat(wtx.references).containsOnly(referenceStateRef) } - @Test - fun `multiple commands with same data are joined without duplicates in terms of signers`() { - // This behaviour is only activated for platform version 4 onwards. - CordappResolver.withCordapp(targetPlatformVersion = 4) { - val aliceParty = TestIdentity(ALICE_NAME).party - val bobParty = TestIdentity(BOB_NAME).party - val tx = TransactionBuilder(notary) - tx.addCommand(DummyCommandData, notary.owningKey, aliceParty.owningKey) - tx.addCommand(DummyCommandData, aliceParty.owningKey, bobParty.owningKey) - - val commands = tx.commands() - - assertThat(commands).hasSize(1) - assertThat(commands.single()).satisfies { cmd -> - assertThat(cmd.value).isEqualTo(DummyCommandData) - assertThat(cmd.signers).hasSize(3) - assertThat(cmd.signers).contains(notary.owningKey, bobParty.owningKey, aliceParty.owningKey) - } - } - } - @Test fun `automatic signature constraint`() { val aliceParty = TestIdentity(ALICE_NAME).party diff --git a/core/src/test/kotlin/net/corda/core/utilities/LazyMappedListTest.kt b/core/src/test/kotlin/net/corda/core/utilities/LazyMappedListTest.kt index 1cd47f542d..c1dc7cf1cb 100644 --- a/core/src/test/kotlin/net/corda/core/utilities/LazyMappedListTest.kt +++ b/core/src/test/kotlin/net/corda/core/utilities/LazyMappedListTest.kt @@ -1,11 +1,20 @@ package net.corda.core.utilities +import net.corda.core.contracts.ComponentGroupEnum.* import net.corda.core.internal.lazyMapped +import net.corda.core.internal.TransactionDeserialisationException +import net.corda.core.internal.eagerDeserialise +import net.corda.core.serialization.MissingAttachmentsException +import org.junit.Rule import org.junit.Test +import org.junit.rules.ExpectedException import kotlin.test.assertEquals class LazyMappedListTest { + @get:Rule + val exception: ExpectedException = ExpectedException.none() + @Test fun `LazyMappedList works`() { val originalList = (1 until 10).toList() @@ -33,4 +42,29 @@ class LazyMappedListTest { assertEquals(1, callCounter) } + @Test + fun testMissingAttachments() { + exception.expect(MissingAttachmentsException::class.java) + exception.expectMessage("Uncatchable!") + + val lazyList = (0 until 5).toList().lazyMapped { _, _ -> + throw MissingAttachmentsException(emptyList(), "Uncatchable!") + } + + lazyList.eagerDeserialise { _, _ -> -999 } + } + + @Test + fun testDeserialisationExceptions() { + val lazyList = (0 until 5).toList().lazyMapped { _, index -> + throw TransactionDeserialisationException( + OUTPUTS_GROUP, index, IllegalStateException("Catch this!")) + } + + lazyList.eagerDeserialise { _, _ -> -999 } + assertEquals(5, lazyList.size) + lazyList.forEachIndexed { idx, item -> + assertEquals(-999, item, "Item[$idx] mismatch") + } + } } diff --git a/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt b/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt index 0014cabbcd..13769fcb0f 100644 --- a/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt +++ b/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt @@ -36,12 +36,14 @@ class ProgressTrackerTest { lateinit var pt: ProgressTracker lateinit var pt2: ProgressTracker lateinit var pt3: ProgressTracker + lateinit var pt4: ProgressTracker @Before fun before() { pt = SimpleSteps.tracker() pt2 = ChildSteps.tracker() pt3 = BabySteps.tracker() + pt4 = ChildSteps.tracker() } @Test @@ -129,8 +131,8 @@ class ProgressTrackerTest { assertCurrentStepsTree(6, SimpleSteps.THREE) // Assert no structure changes and proper steps propagation. - assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 2, 4, 6)) - assertThat(stepsTreeNotification).isEmpty() + assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(0, 1, 2, 4, 6)) + assertThat(stepsTreeNotification).hasSize(2) // The initial tree state, plus one per tree update } @Test @@ -164,8 +166,8 @@ class ProgressTrackerTest { assertCurrentStepsTree(7, ChildSteps.SEA) // Assert no structure changes and proper steps propagation. - assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 4, 7)) - assertThat(stepsTreeNotification).isEmpty() + assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(0, 1, 4, 7)) + assertThat(stepsTreeNotification).hasSize(3) // The initial tree state, plus one per update } @Test @@ -179,7 +181,7 @@ class ProgressTrackerTest { } // Put current state as a first change for simplicity when asserting. - val stepsTreeNotification = mutableListOf(pt.allStepsLabels) + val stepsTreeNotification = mutableListOf>>() pt.stepsTreeChanges.subscribe { stepsTreeNotification += it } @@ -201,8 +203,8 @@ class ProgressTrackerTest { assertCurrentStepsTree(10, SimpleSteps.FOUR) // Assert no structure changes and proper steps propagation. - assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(2, 7, 10)) - assertThat(stepsTreeNotification).hasSize(2) // 1 change + 1 our initial state + assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(0, 2, 7, 10)) + assertThat(stepsTreeNotification).hasSize(3) // The initial tree state, plus one per update. } @Test @@ -216,7 +218,7 @@ class ProgressTrackerTest { } // Put current state as a first change for simplicity when asserting. - val stepsTreeNotification = mutableListOf(pt.allStepsLabels) + val stepsTreeNotification = mutableListOf>>() pt.stepsTreeChanges.subscribe { stepsTreeNotification += it } @@ -236,8 +238,8 @@ class ProgressTrackerTest { assertCurrentStepsTree(3, BabySteps.UNOS) // Assert no structure changes and proper steps propagation. - assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(2, 5, 3)) - assertThat(stepsTreeNotification).hasSize(2) // 1 change + 1 our initial state. + assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(0, 2, 5, 3)) + assertThat(stepsTreeNotification).hasSize(3) // The initial tree state, plus one per update } @Test @@ -256,12 +258,66 @@ class ProgressTrackerTest { pt.currentStep = SimpleSteps.TWO val stepsIndexNotifications = LinkedList() - pt.stepsTreeIndexChanges.subscribe() { + pt.stepsTreeIndexChanges.subscribe { stepsIndexNotifications += it } pt2.currentStep = ChildSteps.AYY - assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 2, 3)) + assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(0, 1, 2, 3)) + } + + @Test + fun `all step changes seen if subscribed mid flow`() { + val steps = mutableListOf() + pt.nextStep() + pt.nextStep() + pt.nextStep() + pt.changes.subscribe { steps.add(it.toString())} + pt.nextStep() + pt.nextStep() + pt.nextStep() + assertEquals(listOf("Starting", "one", "two", "three", "four", "Done"), steps) + } + + @Test + fun `all tree changes seen if subscribed mid flow`() { + val stepTreeNotifications = mutableListOf>>() + val firstStepLabels = pt.allStepsLabels + + pt.setChildProgressTracker(SimpleSteps.TWO, pt2) + val secondStepLabels = pt.allStepsLabels + + pt.setChildProgressTracker(SimpleSteps.TWO, pt3) + val thirdStepLabels = pt.allStepsLabels + pt.stepsTreeChanges.subscribe { stepTreeNotifications.add(it)} + + // Should have one notification for original tree, then one for each time it changed. + assertEquals(3, stepTreeNotifications.size) + assertEquals(listOf(firstStepLabels, secondStepLabels, thirdStepLabels), stepTreeNotifications) + } + + @Test + fun `trees with child trackers with duplicate steps reported correctly`() { + val stepTreeNotifications = mutableListOf>>() + val stepIndexNotifications = mutableListOf() + pt.stepsTreeChanges.subscribe { stepTreeNotifications += it } + pt.stepsTreeIndexChanges.subscribe { stepIndexNotifications += it } + pt.setChildProgressTracker(SimpleSteps.ONE, pt2) + pt.setChildProgressTracker(SimpleSteps.TWO, pt4) + + pt.currentStep = SimpleSteps.ONE + pt2.currentStep = ChildSteps.AYY + pt2.nextStep() + pt2.nextStep() + pt.nextStep() + pt4.currentStep = ChildSteps.AYY + + assertEquals(listOf(0, 1, 2, 3, 4, 5, 6), stepIndexNotifications) + } + + @Test + fun `cannot assign step not belonging to this progress tracker`() { + assertFails { pt.currentStep = BabySteps.UNOS } } } diff --git a/djvm/.gitignore b/djvm/.gitignore index 9aaddce91f..9c2a46a770 100644 --- a/djvm/.gitignore +++ b/djvm/.gitignore @@ -1,3 +1,13 @@ -tmp/ +# DJVM-specific files +**/tmp/ *.log *.log.gz + +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ + +**/out/ + diff --git a/djvm/build.gradle b/djvm/build.gradle index fd207b04a5..d4e33ab268 100644 --- a/djvm/build.gradle +++ b/djvm/build.gradle @@ -1,75 +1,120 @@ -plugins { - id 'com.github.johnrengelman.shadow' +buildscript { + ext { + corda_djvm_version = '5.0-SNAPSHOT' + artifactory_contextUrl = 'https://ci-artifactory.corda.r3cev.com/artifactory' + } + + repositories { + mavenCentral() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } } + +plugins { + id 'net.corda.plugins.publish-utils' version '4.0.42' apply false + id 'com.github.johnrengelman.shadow' version '5.0.0' apply false + id 'com.jfrog.artifactory' version '4.7.3' apply false + id 'com.jfrog.bintray' version '1.4' apply false + id 'com.gradle.build-scan' version '2.2.1' +} + +import static org.gradle.api.JavaVersion.* +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +subprojects { + group 'net.corda' + version corda_djvm_version + + repositories { + mavenCentral() + jcenter() + } + + tasks.withType(JavaCompile) { + sourceCompatibility = VERSION_1_8 + targetCompatibility = VERSION_1_8 + options.encoding = 'UTF-8' + } + + tasks.withType(KotlinCompile) { + kotlinOptions { + languageVersion = '1.2' + apiVersion = '1.2' + jvmTarget = VERSION_1_8 + javaParameters = true // Useful for reflection. + freeCompilerArgs = ['-Xjvm-default=enable'] + } + } + + tasks.withType(Jar) { task -> + manifest { + attributes('Corda-Vendor': 'Corda Open Source') + attributes('Automatic-Module-Name': "net.corda.${task.project.name.replaceAll('-', '.')}") + } + } + + tasks.withType(Test) { + // Prevent the project from creating temporary files outside of the build directory. + systemProperty 'java.io.tmpdir', buildDir.absolutePath + } +} + apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'com.jfrog.artifactory' -apply plugin: 'idea' -description 'Corda deterministic JVM sandbox' - -ext { - // Shaded version of ASM to avoid conflict with root project. - asm_version = '6.2.1' -} - -repositories { - maven { - url "$artifactory_contextUrl/corda-dev" +bintrayConfig { + user = System.getenv('CORDA_BINTRAY_USER') + key = System.getenv('CORDA_BINTRAY_KEY') + repo = 'corda' + org = 'r3' + licenses = ['Apache-2.0'] + vcsUrl = 'https://github.com/corda/corda' + projectUrl = 'https://github.com/corda/corda' + gpgSign = true + gpgPassphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE') + publications = [ + 'corda-djvm', + 'corda-djvm-cli' + ] + license { + name = 'Apache-2.0' + url = 'https://www.apache.org/licenses/LICENSE-2.0' + distribution = 'repo' + } + developer { + id = 'R3' + name = 'R3' + email = 'dev@corda.net' } } -configurations { - testCompile.extendsFrom shadow - jdkRt.resolutionStrategy { - // Always check the repository for a newer SNAPSHOT. - cacheChangingModulesFor 0, 'seconds' +artifactory { + publish { + contextUrl = artifactory_contextUrl + repository { + repoKey = 'corda-dev' + username = System.getenv('CORDA_ARTIFACTORY_USERNAME') + password = System.getenv('CORDA_ARTIFACTORY_PASSWORD') + } + + defaults { + // The root project has applied 'publish-utils' but has nothing to publish. + if (project != rootProject) { + publications(project.extensions.publish.name()) + } + } } } -dependencies { - shadow "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - shadow "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - shadow "org.slf4j:slf4j-api:$slf4j_version" - - // ASM: byte code manipulation library - compile "org.ow2.asm:asm:$asm_version" - compile "org.ow2.asm:asm-commons:$asm_version" - - // ClassGraph: classpath scanning - shadow "io.github.classgraph:classgraph:$class_graph_version" - - // Test utilities - testCompile "junit:junit:$junit_version" - testCompile "org.assertj:assertj-core:$assertj_version" - testCompile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" - jdkRt "net.corda:deterministic-rt:latest.integration" +wrapper { + gradleVersion = "5.2.1" + distributionType = Wrapper.DistributionType.ALL } -jar.enabled = false - -shadowJar { - baseName 'corda-djvm' - classifier '' - relocate 'org.objectweb.asm', 'djvm.org.objectweb.asm' -} -assemble.dependsOn shadowJar - -tasks.withType(Test) { - systemProperty 'deterministic-rt.path', configurations.jdkRt.asPath -} - -artifacts { - publish shadowJar -} - -publish { - dependenciesFrom configurations.shadow - name shadowJar.baseName -} - -idea { - module { - downloadJavadoc = true - downloadSources = true - } +buildScan { + termsOfServiceUrl = 'https://gradle.com/terms-of-service' + termsOfServiceAgree = 'yes' } diff --git a/djvm/cli/build.gradle b/djvm/cli/build.gradle deleted file mode 100644 index d72a4a74c0..0000000000 --- a/djvm/cli/build.gradle +++ /dev/null @@ -1,48 +0,0 @@ -plugins { - id 'com.github.johnrengelman.shadow' -} - -repositories { - maven { - url "$artifactory_contextUrl/corda-dev" - } -} - -configurations { - deterministicRt -} - -dependencies { - compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" - compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version" - - compile "info.picocli:picocli:$picocli_version" - compile project(path: ":djvm", configuration: "shadow") - - // Deterministic runtime - used in whitelist generation - deterministicRt project(path: ':jdk8u-deterministic', configuration: 'jdk') -} - -jar.enabled = false - -shadowJar { - baseName = "corda-djvm" - classifier = 'cli' - manifest { - attributes( - 'Automatic-Module-Name': 'net.corda.djvm', - 'Main-Class': 'net.corda.djvm.tools.cli.Program', - 'Build-Date': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") - ) - } -} -assemble.dependsOn shadowJar - -task generateWhitelist(type: JavaExec, dependsOn: shadowJar) { - // This is an example of how a whitelist can be generated from a JAR. In most applications though, it is recommended - // that the minimal set whitelist is used. - main = '-jar' - args = [shadowJar.outputs.files.singleFile, 'whitelist', 'generate', '-o', "$buildDir/jdk8-deterministic.dat.gz", configurations.deterministicRt.files[0] ] -} diff --git a/djvm/djvm/build.gradle b/djvm/djvm/build.gradle new file mode 100644 index 0000000000..1c329cf704 --- /dev/null +++ b/djvm/djvm/build.gradle @@ -0,0 +1,87 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' + id 'com.github.johnrengelman.shadow' + id 'net.corda.plugins.publish-utils' + id 'com.jfrog.artifactory' + id 'idea' +} + +description 'Corda deterministic JVM sandbox' + +repositories { + maven { + url "$artifactory_contextUrl/corda-dev" + } +} + +configurations { + testImplementation.extendsFrom shadow + jdkRt.resolutionStrategy { + // Always check the repository for a newer SNAPSHOT. + cacheChangingModulesFor 0, 'seconds' + } +} + +dependencies { + shadow "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + shadow "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + shadow "org.slf4j:slf4j-api:$slf4j_version" + + // ASM: byte code manipulation library + implementation "org.ow2.asm:asm:$asm_version" + implementation "org.ow2.asm:asm-commons:$asm_version" + + // ClassGraph: classpath scanning + shadow "io.github.classgraph:classgraph:$class_graph_version" + + // Test utilities + testImplementation "junit:junit:$junit_version" + testImplementation "org.assertj:assertj-core:$assertj_version" + testImplementation "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" + jdkRt "net.corda:deterministic-rt:latest.integration" +} + +jar.enabled = false + +shadowJar { + baseName 'corda-djvm' + classifier '' + relocate 'org.objectweb.asm', 'djvm.org.objectweb.asm' + + // These particular classes are only needed to "bootstrap" + // the compilation of the other sandbox classes. At runtime, + // we will generate better versions from deterministic-rt.jar. + exclude 'sandbox/java/lang/Appendable.class' + exclude 'sandbox/java/lang/CharSequence.class' + exclude 'sandbox/java/lang/Character\$Subset.class' + exclude 'sandbox/java/lang/Character\$Unicode*.class' + exclude 'sandbox/java/lang/Comparable.class' + exclude 'sandbox/java/lang/Enum.class' + exclude 'sandbox/java/lang/Iterable.class' + exclude 'sandbox/java/lang/StackTraceElement.class' + exclude 'sandbox/java/lang/StringBuffer.class' + exclude 'sandbox/java/lang/StringBuilder.class' + exclude 'sandbox/java/nio/**' + exclude 'sandbox/java/util/**' +} +assemble.dependsOn shadowJar + +tasks.withType(Test) { + systemProperty 'deterministic-rt.path', configurations.jdkRt.asPath +} + +artifacts { + publish shadowJar +} + +publish { + dependenciesFrom configurations.shadow + name shadowJar.baseName +} + +idea { + module { + downloadJavadoc = true + downloadSources = true + } +} diff --git a/djvm/djvm/cli/build.gradle b/djvm/djvm/cli/build.gradle new file mode 100644 index 0000000000..3c3562bbd0 --- /dev/null +++ b/djvm/djvm/cli/build.gradle @@ -0,0 +1,64 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' + id 'com.github.johnrengelman.shadow' + id 'net.corda.plugins.publish-utils' + id 'com.jfrog.artifactory' +} + +description 'Corda deterministic JVM sandbox command-line tool' + +ext { + djvmName = 'corda-djvm-cli' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + implementation "org.jetbrains.kotlin:kotlin-reflect" + implementation "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" + implementation "org.apache.logging.log4j:log4j-core:$log4j_version" + implementation "com.jcabi:jcabi-manifests:$jcabi_manifests_version" + + implementation "info.picocli:picocli:$picocli_version" + implementation project(path: ':djvm', configuration: 'shadow') +} + +jar.enabled = false + +shadowJar { + baseName djvmName + classifier '' + manifest { + attributes( + 'Automatic-Module-Name': 'net.corda.djvm.cli', + 'Main-Class': 'net.corda.djvm.tools.cli.Program', + 'Build-Date': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + 'Class-Path': 'tmp/' + ) + } +} + +task shadowZip(type: Zip) { + archiveBaseName = djvmName + archiveClassifier = '' + + from(shadowJar) { + rename "$djvmName-(.*).jar", "${djvmName}.jar" + } + from('src/shell/') { + fileMode = 0755 + } + zip64 true +} + +assemble.dependsOn shadowZip + +artifacts { + publish shadowZip +} + +publish { + dependenciesFrom configurations.shadow + publishSources = false + publishJavadoc = false + name shadowZip.baseName +} diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/BuildCommand.kt b/djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/BuildCommand.kt similarity index 100% rename from djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/BuildCommand.kt rename to djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/BuildCommand.kt diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/CheckCommand.kt b/djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/CheckCommand.kt similarity index 100% rename from djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/CheckCommand.kt rename to djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/CheckCommand.kt diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/ClassCommand.kt b/djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/ClassCommand.kt similarity index 100% rename from djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/ClassCommand.kt rename to djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/ClassCommand.kt diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/CommandBase.kt b/djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/CommandBase.kt similarity index 94% rename from djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/CommandBase.kt rename to djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/CommandBase.kt index d4a3c4afb8..603c480af4 100644 --- a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/CommandBase.kt +++ b/djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/CommandBase.kt @@ -8,6 +8,8 @@ import net.corda.djvm.references.ClassReference import net.corda.djvm.references.EntityReference import net.corda.djvm.references.MemberReference import net.corda.djvm.rewiring.SandboxClassLoadingException +import org.apache.logging.log4j.Level +import org.apache.logging.log4j.core.config.Configurator import picocli.CommandLine import picocli.CommandLine.Help.Ansi import picocli.CommandLine.Option @@ -19,7 +21,7 @@ abstract class CommandBase : Callable { @Option( names = ["-l", "--level"], - description = ["The minimum severity level to log (TRACE, INFO, WARNING or ERROR."], + description = ["The minimum severity level to log (TRACE, DEBUG, INFO, WARNING or ERROR."], converter = [SeverityConverter::class] ) protected var level: Severity = Severity.WARNING @@ -102,6 +104,7 @@ abstract class CommandBase : Callable { printError("Error: Cannot set verbose and quiet modes at the same time") return false } + configureLogging() return try { handleCommand() } catch (exception: Throwable) { @@ -110,6 +113,17 @@ abstract class CommandBase : Callable { } } + private fun configureLogging() { + val logLevel = when(level) { + Severity.ERROR -> Level.ERROR + Severity.WARNING -> Level.WARN + Severity.INFORMATIONAL -> Level.INFO + Severity.DEBUG -> Level.DEBUG + Severity.TRACE -> Level.TRACE + } + Configurator.setRootLevel(logLevel) + } + protected fun printException(exception: Throwable) = when (exception) { is SandboxClassLoadingException -> { printMessages(exception.messages, exception.classOrigins) @@ -262,4 +276,4 @@ abstract class CommandBase : Callable { } } -} \ No newline at end of file +} diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Commands.kt b/djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Commands.kt similarity index 100% rename from djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Commands.kt rename to djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Commands.kt diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/InspectionCommand.kt b/djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/InspectionCommand.kt similarity index 100% rename from djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/InspectionCommand.kt rename to djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/InspectionCommand.kt diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/NewCommand.kt b/djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/NewCommand.kt similarity index 100% rename from djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/NewCommand.kt rename to djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/NewCommand.kt diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Program.kt b/djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Program.kt similarity index 100% rename from djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Program.kt rename to djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Program.kt diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/RunCommand.kt b/djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/RunCommand.kt similarity index 100% rename from djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/RunCommand.kt rename to djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/RunCommand.kt diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/ShowCommand.kt b/djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/ShowCommand.kt similarity index 100% rename from djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/ShowCommand.kt rename to djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/ShowCommand.kt diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/TreeCommand.kt b/djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/TreeCommand.kt similarity index 100% rename from djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/TreeCommand.kt rename to djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/TreeCommand.kt diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Utilities.kt b/djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Utilities.kt similarity index 100% rename from djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Utilities.kt rename to djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Utilities.kt diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/VersionProvider.kt b/djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/VersionProvider.kt similarity index 100% rename from djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/VersionProvider.kt rename to djvm/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/VersionProvider.kt diff --git a/djvm/cli/src/main/resources/log4j2.xml b/djvm/djvm/cli/src/main/resources/log4j2.xml similarity index 96% rename from djvm/cli/src/main/resources/log4j2.xml rename to djvm/djvm/cli/src/main/resources/log4j2.xml index 93e84b6252..648a657dc3 100644 --- a/djvm/cli/src/main/resources/log4j2.xml +++ b/djvm/djvm/cli/src/main/resources/log4j2.xml @@ -1,7 +1,7 @@ - + diff --git a/djvm/djvm/cli/src/shell/djvm b/djvm/djvm/cli/src/shell/djvm new file mode 100755 index 0000000000..368b50beb7 --- /dev/null +++ b/djvm/djvm/cli/src/shell/djvm @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +SCRIPT_DIR=$(dirname $(readlink -f ${BASH_SOURCE[0]})) + +CLASSPATH="${CLASSPATH:-}" + +DEBUG=`echo "${DEBUG:-0}" | sed 's/^[Nn][Oo]*$/0/g'` +DEBUG_PORT=5005 +DEBUG_AGENT="" + +if [ "$DEBUG" != 0 ]; then + echo "Opening remote debugging session on port $DEBUG_PORT" + DEBUG_AGENT="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=$DEBUG_PORT" +fi + +exec java $DEBUG_AGENT -cp "$CLASSPATH:.:tmp:$SCRIPT_DIR/corda-djvm-cli.jar" net.corda.djvm.tools.cli.Program "$@" diff --git a/djvm/djvm/cli/src/shell/djvm.bat b/djvm/djvm/cli/src/shell/djvm.bat new file mode 100644 index 0000000000..74fc1386d0 --- /dev/null +++ b/djvm/djvm/cli/src/shell/djvm.bat @@ -0,0 +1,15 @@ +@ECHO off + +SETLOCAL ENABLEEXTENSIONS + +IF NOT DEFINED CLASSPATH (SET CLASSPATH=) + +IF DEFINED DEBUG ( + SET DEBUG_PORT=5005 + SET DEBUG_AGENT=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=%DEBUG_PORT% + ECHO Opening remote debugging session on port %DEBUG_PORT% +) ELSE ( + SET DEBUG_AGENT= +) + +CALL java %DEBUG_AGENT% -cp "%CLASSPATH%;.;tmp;%~dp0\corda-djvm-cli.jar" net.corda.djvm.tools.cli.Program %* diff --git a/djvm/djvm/cli/src/shell/install b/djvm/djvm/cli/src/shell/install new file mode 100755 index 0000000000..69058ef4f1 --- /dev/null +++ b/djvm/djvm/cli/src/shell/install @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +SCRIPT_DIR=$(dirname $(readlink -f ${BASH_SOURCE[0]})) + +# Generate auto-completion file for Bash and ZSH +java -cp ${SCRIPT_DIR}/corda-djvm-cli.jar \ + picocli.AutoComplete -n djvm net.corda.djvm.tools.cli.Commands -f diff --git a/djvm/shell/.gitignore b/djvm/djvm/shell/.gitignore similarity index 100% rename from djvm/shell/.gitignore rename to djvm/djvm/shell/.gitignore diff --git a/djvm/shell/djvm b/djvm/djvm/shell/djvm similarity index 64% rename from djvm/shell/djvm rename to djvm/djvm/shell/djvm index ec3c19e5a7..2b7ff4fe8a 100755 --- a/djvm/shell/djvm +++ b/djvm/djvm/shell/djvm @@ -3,8 +3,7 @@ file="${BASH_SOURCE[0]}" linked_file="$(test -L "$file" && readlink "$file" || echo "$file")" base_dir="$(cd "$(dirname "$linked_file")/../" && pwd)" -version="$(cat $base_dir/../build.gradle | sed -n 's/^[ ]*ext\.corda_release_version[ =]*"\([^"]*\)".*$/\1/p')" -jar_file="$base_dir/cli/build/libs/corda-djvm-$version-cli.jar" +djvm_cli_jar=$(ls -1 $base_dir/cli/build/libs/corda-djvm-cli-*.jar) CLASSPATH="${CLASSPATH:-}" @@ -17,4 +16,4 @@ if [ "$DEBUG" != 0 ]; then DEBUG_AGENT="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=$DEBUG_PORT" fi -java $DEBUG_AGENT -cp "$CLASSPATH:.:tmp:$jar_file" net.corda.djvm.tools.cli.Program "$@" +java $DEBUG_AGENT -cp "$CLASSPATH:.:tmp:$djvm_cli_jar" net.corda.djvm.tools.cli.Program "$@" diff --git a/djvm/shell/install b/djvm/djvm/shell/install similarity index 58% rename from djvm/shell/install rename to djvm/djvm/shell/install index 7e458e0bb4..1870d34884 100755 --- a/djvm/shell/install +++ b/djvm/djvm/shell/install @@ -2,16 +2,23 @@ file="${BASH_SOURCE[0]}" base_dir="$(cd "$(dirname "$file")/" && pwd)" -version="$(cat $base_dir/../../build.gradle | sed -n 's/^[ ]*ext\.corda_release_version[ =]*"\([^"]*\)".*$/\1/p')" # Build DJVM module and CLI cd "$base_dir/.." -../gradlew shadowJar +if !(../gradlew shadowJar); then + echo "Failed to build DJVM" + exit 1 +fi + +djvm_cli_jar=$(ls -1 $base_dir/../cli/build/libs/corda-djvm-cli-*.jar) # Generate auto-completion file for Bash and ZSH cd "$base_dir" -java -cp "$base_dir/../cli/build/libs/corda-djvm-$version-cli.jar" \ - picocli.AutoComplete -n djvm net.corda.djvm.tools.cli.Commands -f +if !(java -cp $djvm_cli_jar \ + picocli.AutoComplete -n djvm net.corda.djvm.tools.cli.Commands -f); then + echo "Failed to generate auto-completion file" + exit 1 +fi # Create a symbolic link to the `djvm` utility sudo ln -sf "$base_dir/djvm" /usr/local/bin/djvm diff --git a/djvm/src/main/java/sandbox/java/lang/Appendable.java b/djvm/djvm/src/main/java/sandbox/java/lang/Appendable.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/Appendable.java rename to djvm/djvm/src/main/java/sandbox/java/lang/Appendable.java diff --git a/djvm/src/main/java/sandbox/java/lang/Boolean.java b/djvm/djvm/src/main/java/sandbox/java/lang/Boolean.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/Boolean.java rename to djvm/djvm/src/main/java/sandbox/java/lang/Boolean.java diff --git a/djvm/src/main/java/sandbox/java/lang/Byte.java b/djvm/djvm/src/main/java/sandbox/java/lang/Byte.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/Byte.java rename to djvm/djvm/src/main/java/sandbox/java/lang/Byte.java diff --git a/djvm/src/main/java/sandbox/java/lang/CharSequence.java b/djvm/djvm/src/main/java/sandbox/java/lang/CharSequence.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/CharSequence.java rename to djvm/djvm/src/main/java/sandbox/java/lang/CharSequence.java diff --git a/djvm/src/main/java/sandbox/java/lang/Character.java b/djvm/djvm/src/main/java/sandbox/java/lang/Character.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/Character.java rename to djvm/djvm/src/main/java/sandbox/java/lang/Character.java diff --git a/djvm/src/main/java/sandbox/java/lang/Comparable.java b/djvm/djvm/src/main/java/sandbox/java/lang/Comparable.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/Comparable.java rename to djvm/djvm/src/main/java/sandbox/java/lang/Comparable.java diff --git a/djvm/src/main/java/sandbox/java/lang/DJVMThrowableWrapper.java b/djvm/djvm/src/main/java/sandbox/java/lang/DJVMThrowableWrapper.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/DJVMThrowableWrapper.java rename to djvm/djvm/src/main/java/sandbox/java/lang/DJVMThrowableWrapper.java diff --git a/djvm/src/main/java/sandbox/java/lang/Double.java b/djvm/djvm/src/main/java/sandbox/java/lang/Double.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/Double.java rename to djvm/djvm/src/main/java/sandbox/java/lang/Double.java diff --git a/djvm/src/main/java/sandbox/java/lang/Enum.java b/djvm/djvm/src/main/java/sandbox/java/lang/Enum.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/Enum.java rename to djvm/djvm/src/main/java/sandbox/java/lang/Enum.java diff --git a/djvm/src/main/java/sandbox/java/lang/Float.java b/djvm/djvm/src/main/java/sandbox/java/lang/Float.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/Float.java rename to djvm/djvm/src/main/java/sandbox/java/lang/Float.java diff --git a/djvm/src/main/java/sandbox/java/lang/Integer.java b/djvm/djvm/src/main/java/sandbox/java/lang/Integer.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/Integer.java rename to djvm/djvm/src/main/java/sandbox/java/lang/Integer.java diff --git a/djvm/src/main/java/sandbox/java/lang/Iterable.java b/djvm/djvm/src/main/java/sandbox/java/lang/Iterable.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/Iterable.java rename to djvm/djvm/src/main/java/sandbox/java/lang/Iterable.java diff --git a/djvm/src/main/java/sandbox/java/lang/Long.java b/djvm/djvm/src/main/java/sandbox/java/lang/Long.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/Long.java rename to djvm/djvm/src/main/java/sandbox/java/lang/Long.java diff --git a/djvm/src/main/java/sandbox/java/lang/Number.java b/djvm/djvm/src/main/java/sandbox/java/lang/Number.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/Number.java rename to djvm/djvm/src/main/java/sandbox/java/lang/Number.java diff --git a/djvm/src/main/java/sandbox/java/lang/Object.java b/djvm/djvm/src/main/java/sandbox/java/lang/Object.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/Object.java rename to djvm/djvm/src/main/java/sandbox/java/lang/Object.java diff --git a/djvm/src/main/java/sandbox/java/lang/Runtime.java b/djvm/djvm/src/main/java/sandbox/java/lang/Runtime.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/Runtime.java rename to djvm/djvm/src/main/java/sandbox/java/lang/Runtime.java diff --git a/djvm/src/main/java/sandbox/java/lang/Short.java b/djvm/djvm/src/main/java/sandbox/java/lang/Short.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/Short.java rename to djvm/djvm/src/main/java/sandbox/java/lang/Short.java diff --git a/djvm/src/main/java/sandbox/java/lang/StackTraceElement.java b/djvm/djvm/src/main/java/sandbox/java/lang/StackTraceElement.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/StackTraceElement.java rename to djvm/djvm/src/main/java/sandbox/java/lang/StackTraceElement.java diff --git a/djvm/src/main/java/sandbox/java/lang/String.java b/djvm/djvm/src/main/java/sandbox/java/lang/String.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/String.java rename to djvm/djvm/src/main/java/sandbox/java/lang/String.java diff --git a/djvm/src/main/java/sandbox/java/lang/StringBuffer.java b/djvm/djvm/src/main/java/sandbox/java/lang/StringBuffer.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/StringBuffer.java rename to djvm/djvm/src/main/java/sandbox/java/lang/StringBuffer.java diff --git a/djvm/src/main/java/sandbox/java/lang/StringBuilder.java b/djvm/djvm/src/main/java/sandbox/java/lang/StringBuilder.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/StringBuilder.java rename to djvm/djvm/src/main/java/sandbox/java/lang/StringBuilder.java diff --git a/djvm/src/main/java/sandbox/java/lang/System.java b/djvm/djvm/src/main/java/sandbox/java/lang/System.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/System.java rename to djvm/djvm/src/main/java/sandbox/java/lang/System.java diff --git a/djvm/src/main/java/sandbox/java/lang/ThreadLocal.java b/djvm/djvm/src/main/java/sandbox/java/lang/ThreadLocal.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/ThreadLocal.java rename to djvm/djvm/src/main/java/sandbox/java/lang/ThreadLocal.java diff --git a/djvm/src/main/java/sandbox/java/lang/Throwable.java b/djvm/djvm/src/main/java/sandbox/java/lang/Throwable.java similarity index 100% rename from djvm/src/main/java/sandbox/java/lang/Throwable.java rename to djvm/djvm/src/main/java/sandbox/java/lang/Throwable.java diff --git a/djvm/src/main/java/sandbox/java/nio/charset/Charset.java b/djvm/djvm/src/main/java/sandbox/java/nio/charset/Charset.java similarity index 100% rename from djvm/src/main/java/sandbox/java/nio/charset/Charset.java rename to djvm/djvm/src/main/java/sandbox/java/nio/charset/Charset.java diff --git a/djvm/src/main/java/sandbox/java/util/Comparator.java b/djvm/djvm/src/main/java/sandbox/java/util/Comparator.java similarity index 100% rename from djvm/src/main/java/sandbox/java/util/Comparator.java rename to djvm/djvm/src/main/java/sandbox/java/util/Comparator.java diff --git a/djvm/src/main/java/sandbox/java/util/LinkedHashMap.java b/djvm/djvm/src/main/java/sandbox/java/util/LinkedHashMap.java similarity index 100% rename from djvm/src/main/java/sandbox/java/util/LinkedHashMap.java rename to djvm/djvm/src/main/java/sandbox/java/util/LinkedHashMap.java diff --git a/djvm/src/main/java/sandbox/java/util/Locale.java b/djvm/djvm/src/main/java/sandbox/java/util/Locale.java similarity index 100% rename from djvm/src/main/java/sandbox/java/util/Locale.java rename to djvm/djvm/src/main/java/sandbox/java/util/Locale.java diff --git a/djvm/src/main/java/sandbox/java/util/Map.java b/djvm/djvm/src/main/java/sandbox/java/util/Map.java similarity index 100% rename from djvm/src/main/java/sandbox/java/util/Map.java rename to djvm/djvm/src/main/java/sandbox/java/util/Map.java diff --git a/djvm/src/main/java/sandbox/java/util/function/Function.java b/djvm/djvm/src/main/java/sandbox/java/util/function/Function.java similarity index 100% rename from djvm/src/main/java/sandbox/java/util/function/Function.java rename to djvm/djvm/src/main/java/sandbox/java/util/function/Function.java diff --git a/djvm/src/main/java/sandbox/java/util/function/Supplier.java b/djvm/djvm/src/main/java/sandbox/java/util/function/Supplier.java similarity index 100% rename from djvm/src/main/java/sandbox/java/util/function/Supplier.java rename to djvm/djvm/src/main/java/sandbox/java/util/function/Supplier.java diff --git a/djvm/src/main/java/sandbox/sun/misc/JavaLangAccess.java b/djvm/djvm/src/main/java/sandbox/sun/misc/JavaLangAccess.java similarity index 100% rename from djvm/src/main/java/sandbox/sun/misc/JavaLangAccess.java rename to djvm/djvm/src/main/java/sandbox/sun/misc/JavaLangAccess.java diff --git a/djvm/src/main/java/sandbox/sun/misc/SharedSecrets.java b/djvm/djvm/src/main/java/sandbox/sun/misc/SharedSecrets.java similarity index 100% rename from djvm/src/main/java/sandbox/sun/misc/SharedSecrets.java rename to djvm/djvm/src/main/java/sandbox/sun/misc/SharedSecrets.java diff --git a/djvm/src/main/kotlin/net/corda/djvm/SandboxConfiguration.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/SandboxConfiguration.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/SandboxConfiguration.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/SandboxConfiguration.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/SandboxRuntimeContext.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/SandboxRuntimeContext.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/SandboxRuntimeContext.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/SandboxRuntimeContext.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/analysis/AnalysisConfiguration.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/analysis/AnalysisConfiguration.kt similarity index 98% rename from djvm/src/main/kotlin/net/corda/djvm/analysis/AnalysisConfiguration.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/analysis/AnalysisConfiguration.kt index f17059dd18..7b3988d0ad 100644 --- a/djvm/src/main/kotlin/net/corda/djvm/analysis/AnalysisConfiguration.kt +++ b/djvm/djvm/src/main/kotlin/net/corda/djvm/analysis/AnalysisConfiguration.kt @@ -8,7 +8,6 @@ import net.corda.djvm.references.ClassModule import net.corda.djvm.references.Member import net.corda.djvm.references.MemberModule import net.corda.djvm.references.MethodBody -import net.corda.djvm.source.AbstractSourceClassLoader import net.corda.djvm.source.BootstrapClassLoader import net.corda.djvm.source.SourceClassLoader import org.objectweb.asm.Opcodes.* @@ -48,7 +47,7 @@ class AnalysisConfiguration private constructor( val classModule: ClassModule, val memberModule: MemberModule, private val bootstrapClassLoader: BootstrapClassLoader?, - val supportingClassLoader: AbstractSourceClassLoader, + val supportingClassLoader: SourceClassLoader, private val isRootConfiguration: Boolean ) : Closeable { @@ -299,7 +298,7 @@ class AnalysisConfiguration private constructor( classModule: ClassModule = ClassModule(), memberModule: MemberModule = MemberModule(), bootstrapClassLoader: BootstrapClassLoader? = null, - sourceClassLoaderFactory: (ClassResolver, BootstrapClassLoader?) -> AbstractSourceClassLoader = { classResolver, bootstrapCL -> + sourceClassLoaderFactory: (ClassResolver, BootstrapClassLoader?) -> SourceClassLoader = { classResolver, bootstrapCL -> SourceClassLoader(emptyList(), classResolver, bootstrapCL) } ): AnalysisConfiguration { diff --git a/djvm/src/main/kotlin/net/corda/djvm/analysis/AnalysisContext.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/analysis/AnalysisContext.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/analysis/AnalysisContext.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/analysis/AnalysisContext.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/analysis/AnalysisRuntimeContext.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/analysis/AnalysisRuntimeContext.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/analysis/AnalysisRuntimeContext.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/analysis/AnalysisRuntimeContext.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/analysis/ClassAndMemberVisitor.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/analysis/ClassAndMemberVisitor.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/analysis/ClassAndMemberVisitor.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/analysis/ClassAndMemberVisitor.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/analysis/ClassResolver.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/analysis/ClassResolver.kt similarity index 78% rename from djvm/src/main/kotlin/net/corda/djvm/analysis/ClassResolver.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/analysis/ClassResolver.kt index a05b4ec7ec..8c42acb039 100644 --- a/djvm/src/main/kotlin/net/corda/djvm/analysis/ClassResolver.kt +++ b/djvm/djvm/src/main/kotlin/net/corda/djvm/analysis/ClassResolver.kt @@ -84,23 +84,25 @@ class ClassResolver( * Reverse the resolution of a class name. */ fun reverse(resolvedClassName: String): String { - if (resolvedClassName in pinnedClasses || resolvedClassName in templateClasses) { - return resolvedClassName + return if (resolvedClassName in pinnedClasses || resolvedClassName in templateClasses) { + resolvedClassName + } else { + removeSandboxPrefix(resolvedClassName) } - if (resolvedClassName.startsWith(sandboxPrefix)) { - val nameWithoutPrefix = resolvedClassName.drop(sandboxPrefix.length) - if (resolve(nameWithoutPrefix) == resolvedClassName) { - return nameWithoutPrefix - } - } - return resolvedClassName } /** * Reverse the resolution of a class name from a fully qualified normalized name. */ - fun reverseNormalized(name: String): String { - return reverse(name.asResourcePath).asPackagePath + fun reverseNormalized(className: String): String { + return reverse(className.asResourcePath).asPackagePath + } + + /** + * Generates the equivalent class name outside the sandbox from a fully qualified normalized name. + */ + fun toSourceNormalized(className: String): String { + return toSource(className.asResourcePath).asPackagePath } /** @@ -114,6 +116,28 @@ class ClassResolver( } } + /** + * Maps a class name to its equivalent class outside the sandbox. + * Needed by [net.corda.djvm.source.SourceClassLoader]. + */ + private fun toSource(className: String): String { + return if (className in pinnedClasses) { + className + } else { + removeSandboxPrefix(className) + } + } + + private fun removeSandboxPrefix(className: String): String { + if (className.startsWith(sandboxPrefix)) { + val nameWithoutPrefix = className.drop(sandboxPrefix.length) + if (resolve(nameWithoutPrefix) == className) { + return nameWithoutPrefix + } + } + return className + } + /** * Check if class is whitelisted or pinned. */ diff --git a/djvm/src/main/kotlin/net/corda/djvm/analysis/ExceptionResolver.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/analysis/ExceptionResolver.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/analysis/ExceptionResolver.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/analysis/ExceptionResolver.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/analysis/SourceLocation.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/analysis/SourceLocation.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/analysis/SourceLocation.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/analysis/SourceLocation.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/analysis/Whitelist.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/analysis/Whitelist.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/analysis/Whitelist.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/analysis/Whitelist.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/ClassDefinitionProvider.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/ClassDefinitionProvider.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/ClassDefinitionProvider.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/ClassDefinitionProvider.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/ClassMutator.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/ClassMutator.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/ClassMutator.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/ClassMutator.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/DefinitionProvider.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/DefinitionProvider.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/DefinitionProvider.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/DefinitionProvider.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/Emitter.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/Emitter.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/Emitter.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/Emitter.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/EmitterContext.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/EmitterContext.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/EmitterContext.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/EmitterContext.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/EmitterModule.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/EmitterModule.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/EmitterModule.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/EmitterModule.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/Instruction.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/Instruction.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/Instruction.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/Instruction.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/MemberDefinitionProvider.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/MemberDefinitionProvider.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/MemberDefinitionProvider.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/MemberDefinitionProvider.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/Types.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/Types.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/Types.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/Types.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/instructions/BranchInstruction.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/BranchInstruction.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/instructions/BranchInstruction.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/BranchInstruction.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/instructions/CodeLabel.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/CodeLabel.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/instructions/CodeLabel.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/CodeLabel.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/instructions/ConstantInstruction.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/ConstantInstruction.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/instructions/ConstantInstruction.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/ConstantInstruction.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/instructions/DynamicInvocationInstruction.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/DynamicInvocationInstruction.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/instructions/DynamicInvocationInstruction.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/DynamicInvocationInstruction.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/instructions/IntegerInstruction.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/IntegerInstruction.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/instructions/IntegerInstruction.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/IntegerInstruction.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/instructions/MemberAccessInstruction.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/MemberAccessInstruction.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/instructions/MemberAccessInstruction.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/MemberAccessInstruction.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/instructions/MethodEntry.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/MethodEntry.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/instructions/MethodEntry.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/MethodEntry.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/instructions/NoOperationInstruction.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/NoOperationInstruction.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/instructions/NoOperationInstruction.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/NoOperationInstruction.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/instructions/TableSwitchInstruction.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/TableSwitchInstruction.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/instructions/TableSwitchInstruction.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/TableSwitchInstruction.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/instructions/TryBlock.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/TryBlock.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/instructions/TryBlock.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/TryBlock.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/instructions/TryCatchBlock.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/TryCatchBlock.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/instructions/TryCatchBlock.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/TryCatchBlock.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/instructions/TryFinallyBlock.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/TryFinallyBlock.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/instructions/TryFinallyBlock.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/TryFinallyBlock.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/instructions/TypeInstruction.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/TypeInstruction.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/code/instructions/TypeInstruction.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/code/instructions/TypeInstruction.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/costing/RuntimeCost.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/costing/RuntimeCost.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/costing/RuntimeCost.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/costing/RuntimeCost.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/costing/RuntimeCostSummary.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/costing/RuntimeCostSummary.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/costing/RuntimeCostSummary.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/costing/RuntimeCostSummary.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/costing/TypedRuntimeCost.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/costing/TypedRuntimeCost.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/costing/TypedRuntimeCost.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/costing/TypedRuntimeCost.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/execution/CostSummary.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/execution/CostSummary.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/execution/CostSummary.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/execution/CostSummary.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/execution/DeterministicSandboxExecutor.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/execution/DeterministicSandboxExecutor.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/execution/DeterministicSandboxExecutor.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/execution/DeterministicSandboxExecutor.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/execution/ExecutionProfile.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/execution/ExecutionProfile.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/execution/ExecutionProfile.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/execution/ExecutionProfile.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/execution/ExecutionSummary.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/execution/ExecutionSummary.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/execution/ExecutionSummary.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/execution/ExecutionSummary.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/execution/ExecutionSummaryWithResult.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/execution/ExecutionSummaryWithResult.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/execution/ExecutionSummaryWithResult.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/execution/ExecutionSummaryWithResult.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/execution/IsolatedTask.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/execution/IsolatedTask.kt similarity index 92% rename from djvm/src/main/kotlin/net/corda/djvm/execution/IsolatedTask.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/execution/IsolatedTask.kt index 7d2ae05153..00d6656c1f 100644 --- a/djvm/src/main/kotlin/net/corda/djvm/execution/IsolatedTask.kt +++ b/djvm/djvm/src/main/kotlin/net/corda/djvm/execution/IsolatedTask.kt @@ -38,12 +38,7 @@ class IsolatedTask( exception = (ex as? LinkageError)?.cause ?: ex null } - costs = CostSummary( - runtimeCosts.allocationCost.value, - runtimeCosts.invocationCost.value, - runtimeCosts.jumpCost.value, - runtimeCosts.throwCost.value - ) + costs = CostSummary(runtimeCosts) } logger.trace("Exiting isolated runtime environment...") completionLatch.countDown() diff --git a/djvm/src/main/kotlin/net/corda/djvm/execution/QueueProcessor.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/execution/QueueProcessor.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/execution/QueueProcessor.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/execution/QueueProcessor.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/execution/SandboxException.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/execution/SandboxException.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/execution/SandboxException.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/execution/SandboxException.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/execution/SandboxExecutor.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/execution/SandboxExecutor.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/execution/SandboxExecutor.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/execution/SandboxExecutor.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/formatting/MemberFormatter.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/formatting/MemberFormatter.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/formatting/MemberFormatter.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/formatting/MemberFormatter.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/messages/Message.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/messages/Message.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/messages/Message.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/messages/Message.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/messages/MessageCollection.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/messages/MessageCollection.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/messages/MessageCollection.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/messages/MessageCollection.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/messages/Severity.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/messages/Severity.kt similarity index 89% rename from djvm/src/main/kotlin/net/corda/djvm/messages/Severity.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/messages/Severity.kt index 1d067b950b..9b04b920ef 100644 --- a/djvm/src/main/kotlin/net/corda/djvm/messages/Severity.kt +++ b/djvm/djvm/src/main/kotlin/net/corda/djvm/messages/Severity.kt @@ -12,7 +12,12 @@ enum class Severity(val shortName: String, val precedence: Int, val color: Strin /** * Trace message. */ - TRACE("TRACE", 3, null), + TRACE("TRACE", 4, null), + + /** + * Debug message. + */ + DEBUG("DEBUG", 3, null), /** * Informational message. diff --git a/djvm/src/main/kotlin/net/corda/djvm/references/AnnotationModule.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/references/AnnotationModule.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/references/AnnotationModule.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/references/AnnotationModule.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/references/ClassHierarchy.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/references/ClassHierarchy.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/references/ClassHierarchy.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/references/ClassHierarchy.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/references/ClassModule.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/references/ClassModule.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/references/ClassModule.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/references/ClassModule.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/references/ClassReference.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/references/ClassReference.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/references/ClassReference.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/references/ClassReference.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/references/ClassRepresentation.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/references/ClassRepresentation.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/references/ClassRepresentation.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/references/ClassRepresentation.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/references/EntityReference.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/references/EntityReference.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/references/EntityReference.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/references/EntityReference.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/references/EntityWithAccessFlag.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/references/EntityWithAccessFlag.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/references/EntityWithAccessFlag.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/references/EntityWithAccessFlag.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/references/Member.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/references/Member.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/references/Member.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/references/Member.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/references/MemberInformation.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/references/MemberInformation.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/references/MemberInformation.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/references/MemberInformation.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/references/MemberModule.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/references/MemberModule.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/references/MemberModule.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/references/MemberModule.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/references/MemberReference.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/references/MemberReference.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/references/MemberReference.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/references/MemberReference.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/references/ReferenceMap.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/references/ReferenceMap.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/references/ReferenceMap.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/references/ReferenceMap.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/references/ReferenceWithLocation.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/references/ReferenceWithLocation.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/references/ReferenceWithLocation.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/references/ReferenceWithLocation.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rewiring/ByteCode.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/ByteCode.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rewiring/ByteCode.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/ByteCode.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rewiring/ClassRewriter.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/ClassRewriter.kt similarity index 95% rename from djvm/src/main/kotlin/net/corda/djvm/rewiring/ClassRewriter.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/ClassRewriter.kt index 4804074457..1722958156 100644 --- a/djvm/src/main/kotlin/net/corda/djvm/rewiring/ClassRewriter.kt +++ b/djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/ClassRewriter.kt @@ -7,6 +7,7 @@ import net.corda.djvm.code.ClassMutator import net.corda.djvm.code.EmitterModule import net.corda.djvm.code.emptyAsNull import net.corda.djvm.references.Member +import net.corda.djvm.source.SourceClassLoader import net.corda.djvm.utilities.loggerFor import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassVisitor @@ -17,11 +18,11 @@ import org.objectweb.asm.MethodVisitor * Functionality for rewriting parts of a class as it is being loaded. * * @property configuration The configuration of the sandbox. - * @property classLoader The class loader used to load the classes that are to be rewritten. + * @property classLoader The class loader used to load the source classes that are to be rewritten. */ open class ClassRewriter( private val configuration: SandboxConfiguration, - private val classLoader: ClassLoader + private val classLoader: SourceClassLoader ) { private val analysisConfig = configuration.analysisConfiguration diff --git a/djvm/src/main/kotlin/net/corda/djvm/rewiring/LoadedClass.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/LoadedClass.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rewiring/LoadedClass.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/LoadedClass.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassLoader.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassLoader.kt similarity index 97% rename from djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassLoader.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassLoader.kt index 16ed666419..905acfbff9 100644 --- a/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassLoader.kt +++ b/djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassLoader.kt @@ -9,8 +9,8 @@ import net.corda.djvm.analysis.ExceptionResolver.Companion.isDJVMException import net.corda.djvm.code.asPackagePath import net.corda.djvm.code.asResourcePath import net.corda.djvm.references.ClassReference -import net.corda.djvm.source.AbstractSourceClassLoader import net.corda.djvm.source.ClassSource +import net.corda.djvm.source.SourceClassLoader import net.corda.djvm.utilities.loggerFor import net.corda.djvm.validation.RuleValidator import org.objectweb.asm.Type @@ -27,13 +27,13 @@ import org.objectweb.asm.Type * @param parent This classloader's parent classloader. */ class SandboxClassLoader private constructor( - private val analysisConfiguration: AnalysisConfiguration, - private val ruleValidator: RuleValidator, - private val supportingClassLoader: AbstractSourceClassLoader, - private val rewriter: ClassRewriter, - private val context: AnalysisContext, - throwableClass: Class<*>?, - parent: ClassLoader? + private val analysisConfiguration: AnalysisConfiguration, + private val ruleValidator: RuleValidator, + private val supportingClassLoader: SourceClassLoader, + private val rewriter: ClassRewriter, + private val context: AnalysisContext, + throwableClass: Class<*>?, + parent: ClassLoader? ) : ClassLoader(parent ?: getSystemClassLoader()) { /** diff --git a/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassLoadingException.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassLoadingException.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassLoadingException.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassLoadingException.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassRemapper.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassRemapper.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassRemapper.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassRemapper.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassWriter.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassWriter.kt similarity index 88% rename from djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassWriter.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassWriter.kt index fc0ad559f6..489dd719ef 100644 --- a/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassWriter.kt +++ b/djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassWriter.kt @@ -1,6 +1,7 @@ package net.corda.djvm.rewiring import net.corda.djvm.code.asPackagePath +import net.corda.djvm.source.SourceClassLoader import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassWriter import org.objectweb.asm.ClassWriter.COMPUTE_FRAMES @@ -22,28 +23,28 @@ import org.objectweb.asm.Type */ open class SandboxClassWriter( classReader: ClassReader, - private val cloader: ClassLoader, + private val cloader: SourceClassLoader, flags: Int = COMPUTE_FRAMES or COMPUTE_MAXS ) : ClassWriter(classReader, flags) { - override fun getClassLoader(): ClassLoader = cloader + override fun getClassLoader(): SourceClassLoader = cloader /** * Get the common super type of [type1] and [type2]. */ override fun getCommonSuperClass(type1: String, type2: String): String { - // Need to override [getCommonSuperClass] to ensure that we use ClassLoader.loadClass(). + // Need to override [getCommonSuperClass] to ensure that we use SourceClassLoader.loadSourceClass(). when { type1 == OBJECT_NAME -> return type1 type2 == OBJECT_NAME -> return type2 } val class1 = try { - classLoader.loadClass(type1.asPackagePath) + classLoader.loadSourceClass(type1.asPackagePath) } catch (exception: Exception) { throw TypeNotPresentException(type1, exception) } val class2 = try { - classLoader.loadClass(type2.asPackagePath) + classLoader.loadSourceClass(type2.asPackagePath) } catch (exception: Exception) { throw TypeNotPresentException(type2, exception) } diff --git a/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxRemapper.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxRemapper.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxRemapper.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxRemapper.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rewiring/ThrowableWrapperFactory.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/ThrowableWrapperFactory.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rewiring/ThrowableWrapperFactory.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rewiring/ThrowableWrapperFactory.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/ClassRule.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/ClassRule.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/ClassRule.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/ClassRule.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/InstructionRule.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/InstructionRule.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/InstructionRule.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/InstructionRule.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/MemberRule.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/MemberRule.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/MemberRule.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/MemberRule.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/Rule.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/Rule.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/Rule.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/Rule.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysInheritFromSandboxedObject.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysInheritFromSandboxedObject.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysInheritFromSandboxedObject.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysInheritFromSandboxedObject.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysUseExactMath.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysUseExactMath.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysUseExactMath.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysUseExactMath.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysUseNonSynchronizedMethods.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysUseNonSynchronizedMethods.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysUseNonSynchronizedMethods.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysUseNonSynchronizedMethods.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysUseStrictFloatingPointArithmetic.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysUseStrictFloatingPointArithmetic.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysUseStrictFloatingPointArithmetic.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysUseStrictFloatingPointArithmetic.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/ArgumentUnwrapper.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/ArgumentUnwrapper.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/ArgumentUnwrapper.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/ArgumentUnwrapper.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowCatchingBlacklistedExceptions.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowCatchingBlacklistedExceptions.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowCatchingBlacklistedExceptions.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowCatchingBlacklistedExceptions.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowDynamicInvocation.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowDynamicInvocation.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowDynamicInvocation.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowDynamicInvocation.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowNonDeterministicMethods.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowNonDeterministicMethods.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowNonDeterministicMethods.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowNonDeterministicMethods.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowOverriddenSandboxPackage.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowOverriddenSandboxPackage.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowOverriddenSandboxPackage.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowOverriddenSandboxPackage.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowUnsupportedApiVersions.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowUnsupportedApiVersions.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowUnsupportedApiVersions.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/DisallowUnsupportedApiVersions.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/HandleExceptionUnwrapper.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/HandleExceptionUnwrapper.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/HandleExceptionUnwrapper.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/HandleExceptionUnwrapper.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/IgnoreBreakpoints.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/IgnoreBreakpoints.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/IgnoreBreakpoints.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/IgnoreBreakpoints.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/IgnoreSynchronizedBlocks.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/IgnoreSynchronizedBlocks.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/IgnoreSynchronizedBlocks.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/IgnoreSynchronizedBlocks.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/ReturnTypeWrapper.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/ReturnTypeWrapper.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/ReturnTypeWrapper.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/ReturnTypeWrapper.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/RewriteClassMethods.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/RewriteClassMethods.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/RewriteClassMethods.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/RewriteClassMethods.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/RewriteObjectMethods.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/RewriteObjectMethods.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/RewriteObjectMethods.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/RewriteObjectMethods.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StaticConstantRemover.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StaticConstantRemover.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StaticConstantRemover.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StaticConstantRemover.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StringConstantWrapper.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StringConstantWrapper.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StringConstantWrapper.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StringConstantWrapper.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StubOutFinalizerMethods.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StubOutFinalizerMethods.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StubOutFinalizerMethods.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StubOutFinalizerMethods.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StubOutNativeMethods.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StubOutNativeMethods.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StubOutNativeMethods.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StubOutNativeMethods.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StubOutReflectionMethods.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StubOutReflectionMethods.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StubOutReflectionMethods.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/StubOutReflectionMethods.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/ThrowExceptionWrapper.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/ThrowExceptionWrapper.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/ThrowExceptionWrapper.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/ThrowExceptionWrapper.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/instrumentation/TraceAllocations.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/instrumentation/TraceAllocations.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/instrumentation/TraceAllocations.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/instrumentation/TraceAllocations.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/instrumentation/TraceInvocations.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/instrumentation/TraceInvocations.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/instrumentation/TraceInvocations.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/instrumentation/TraceInvocations.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/instrumentation/TraceJumps.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/instrumentation/TraceJumps.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/instrumentation/TraceJumps.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/instrumentation/TraceJumps.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/instrumentation/TraceThrows.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/instrumentation/TraceThrows.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/rules/implementation/instrumentation/TraceThrows.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/instrumentation/TraceThrows.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/source/ClassSource.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/source/ClassSource.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/source/ClassSource.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/source/ClassSource.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/source/JarInputStreamIterator.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/source/JarInputStreamIterator.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/source/JarInputStreamIterator.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/source/JarInputStreamIterator.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/source/PathClassSource.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/source/PathClassSource.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/source/PathClassSource.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/source/PathClassSource.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/source/SourceClassLoader.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/source/SourceClassLoader.kt similarity index 67% rename from djvm/src/main/kotlin/net/corda/djvm/source/SourceClassLoader.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/source/SourceClassLoader.kt index a644d550ac..a18bae71fc 100644 --- a/djvm/src/main/kotlin/net/corda/djvm/source/SourceClassLoader.kt +++ b/djvm/djvm/src/main/kotlin/net/corda/djvm/source/SourceClassLoader.kt @@ -1,7 +1,6 @@ @file:JvmName("SourceClassLoaderTools") package net.corda.djvm.source -import net.corda.djvm.analysis.AnalysisConfiguration.Companion.SANDBOX_PREFIX import net.corda.djvm.analysis.AnalysisContext import net.corda.djvm.analysis.ClassResolver import net.corda.djvm.analysis.ExceptionResolver.Companion.getDJVMExceptionOwner @@ -22,71 +21,6 @@ import java.nio.file.Path import java.nio.file.Paths import kotlin.streams.toList -abstract class AbstractSourceClassLoader( - paths: List, - private val classResolver: ClassResolver, - parent: ClassLoader? -) : URLClassLoader(resolvePaths(paths), parent) { - - /** - * Open a [ClassReader] for the provided class name. - */ - fun classReader( - className: String, context: AnalysisContext, origin: String? = null - ): ClassReader { - val originalName = classResolver.reverse(className.asResourcePath) - - fun throwClassLoadingError(): Nothing { - context.messages.provisionalAdd(Message( - message ="Class file not found; $originalName.class", - severity = Severity.ERROR, - location = SourceLocation(origin ?: "") - )) - throw SandboxClassLoadingException(context) - } - - return try { - logger.trace("Opening ClassReader for class {}...", originalName) - getResourceAsStream("$originalName.class")?.use { - ClassReader(it) - } ?: run(::throwClassLoadingError) - } catch (exception: IOException) { - throwClassLoadingError() - } - } - - /** - * Find and load the class with the specified name from the search path. - */ - override fun findClass(name: String): Class<*> { - logger.trace("Finding class {}...", name) - val originalName = classResolver.reverseNormalized(name) - return super.findClass(originalName) - } - - /** - * Load the class with the specified binary name. - */ - override fun loadClass(name: String, resolve: Boolean): Class<*> { - logger.trace("Loading class {}, resolve={}...", name, resolve) - val originalName = classResolver.reverseNormalized(name).let { n -> - // A synthetic exception should be mapped back to its - // corresponding exception in the original hierarchy. - if (isDJVMException(n)) { - getDJVMExceptionOwner(n) - } else { - n - } - } - return super.loadClass(originalName, resolve) - } - - protected companion object { - @JvmStatic - protected val logger = loggerFor() - } -} - /** * Class loader to manage an optional JAR of replacement Java APIs. * @param bootstrapJar The location of the JAR containing the Java APIs. @@ -102,48 +36,75 @@ class BootstrapClassLoader( } /** - * Class loader that only provides our built-in sandbox classes. - * @param classResolver The resolver to use to derive the original name of a requested class. - */ -class SandboxSourceClassLoader( - classResolver: ClassResolver, - private val bootstrap: BootstrapClassLoader -) : AbstractSourceClassLoader(emptyList(), classResolver, SandboxSourceClassLoader::class.java.classLoader) { - - /** - * Always check the bootstrap classloader first. If we're requesting - * built-in sandbox classes then delegate to our parent classloader, - * otherwise deny the request. - */ - override fun getResource(name: String): URL? { - val resource = bootstrap.findResource(name) - if (resource != null) { - return resource - } else if (isJvmInternal(name)) { - logger.error("Denying request for actual {}", name) - return null - } - - return if (name.startsWith(SANDBOX_PREFIX)) { - parent.getResource(name) - } else { - null - } - } -} - -/** - * Customizable class loader that allows the user to explicitly specify additional JARs and directories to scan. + * Customizable class loader that allows the user to specify explicitly additional JARs and directories to scan. * * @param paths The directories and explicit JAR files to scan. * @property classResolver The resolver to use to derive the original name of a requested class. * @property bootstrap The [BootstrapClassLoader] containing the Java APIs for the sandbox. */ -class SourceClassLoader( +class SourceClassLoader private constructor( paths: List, - classResolver: ClassResolver, - private val bootstrap: BootstrapClassLoader? = null -) : AbstractSourceClassLoader(paths, classResolver, SourceClassLoader::class.java.classLoader) { + private val classResolver: ClassResolver, + private val bootstrap: BootstrapClassLoader?, + parent: ClassLoader? +) : URLClassLoader(resolvePaths(paths), parent) { + private companion object { + private val logger = loggerFor() + } + + constructor(paths: List, classResolver: ClassResolver, bootstrap: BootstrapClassLoader? = null) + :this(paths, classResolver, bootstrap, SourceClassLoader::class.java.classLoader) + + /** + * An empty [SourceClassLoader] that can only delegate to its [BootstrapClassLoader]. + */ + constructor(classResolver: ClassResolver, bootstrap: BootstrapClassLoader) + : this(emptyList(), classResolver, bootstrap, null) + + /** + * Open a [ClassReader] for the provided class name. + */ + fun classReader( + className: String, context: AnalysisContext, origin: String? = null + ): ClassReader { + val originalName = classResolver.reverse(className.asResourcePath) + + fun throwClassLoadingError(): Nothing { + context.messages.provisionalAdd(Message( + message ="Class file not found; $originalName.class", + severity = Severity.ERROR, + location = SourceLocation(origin ?: "") + )) + throw SandboxClassLoadingException(context) + } + + return try { + logger.trace("Opening ClassReader for class {}...", originalName) + getResourceAsStream("$originalName.class")?.use(::ClassReader) ?: run(::throwClassLoadingError) + } catch (exception: IOException) { + throwClassLoadingError() + } + } + + /** + * Load the class with the specified binary name. + */ + @Throws(ClassNotFoundException::class) + fun loadSourceClass(name: String): Class<*> { + logger.trace("Loading source class {}...", name) + // We need the name of the equivalent class outside of the sandbox. + // This class is expected to belong to the application classloader. + val originalName = classResolver.toSourceNormalized(name).let { n -> + // A synthetic exception should be mapped back to its + // corresponding exception in the original hierarchy. + if (isDJVMException(n)) { + getDJVMExceptionOwner(n) + } else { + n + } + } + return loadClass(originalName) + } /** * First check the bootstrap classloader, if we have one. diff --git a/djvm/src/main/kotlin/net/corda/djvm/utilities/Discovery.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/utilities/Discovery.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/utilities/Discovery.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/utilities/Discovery.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/utilities/Logging.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/utilities/Logging.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/utilities/Logging.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/utilities/Logging.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/utilities/Processor.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/utilities/Processor.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/utilities/Processor.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/utilities/Processor.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/validation/ConstraintProvider.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/validation/ConstraintProvider.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/validation/ConstraintProvider.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/validation/ConstraintProvider.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/validation/ReferenceValidationSummary.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/validation/ReferenceValidationSummary.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/validation/ReferenceValidationSummary.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/validation/ReferenceValidationSummary.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/validation/RuleContext.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/validation/RuleContext.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/validation/RuleContext.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/validation/RuleContext.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/validation/RuleValidator.kt b/djvm/djvm/src/main/kotlin/net/corda/djvm/validation/RuleValidator.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/validation/RuleValidator.kt rename to djvm/djvm/src/main/kotlin/net/corda/djvm/validation/RuleValidator.kt diff --git a/djvm/src/main/kotlin/sandbox/Task.kt b/djvm/djvm/src/main/kotlin/sandbox/Task.kt similarity index 100% rename from djvm/src/main/kotlin/sandbox/Task.kt rename to djvm/djvm/src/main/kotlin/sandbox/Task.kt diff --git a/djvm/src/main/kotlin/sandbox/java/lang/DJVM.kt b/djvm/djvm/src/main/kotlin/sandbox/java/lang/DJVM.kt similarity index 100% rename from djvm/src/main/kotlin/sandbox/java/lang/DJVM.kt rename to djvm/djvm/src/main/kotlin/sandbox/java/lang/DJVM.kt diff --git a/djvm/src/main/kotlin/sandbox/java/lang/DJVMException.kt b/djvm/djvm/src/main/kotlin/sandbox/java/lang/DJVMException.kt similarity index 100% rename from djvm/src/main/kotlin/sandbox/java/lang/DJVMException.kt rename to djvm/djvm/src/main/kotlin/sandbox/java/lang/DJVMException.kt diff --git a/djvm/src/main/kotlin/sandbox/net/corda/djvm/costing/RuntimeCostAccounter.kt b/djvm/djvm/src/main/kotlin/sandbox/net/corda/djvm/costing/RuntimeCostAccounter.kt similarity index 100% rename from djvm/src/main/kotlin/sandbox/net/corda/djvm/costing/RuntimeCostAccounter.kt rename to djvm/djvm/src/main/kotlin/sandbox/net/corda/djvm/costing/RuntimeCostAccounter.kt diff --git a/djvm/src/main/kotlin/sandbox/net/corda/djvm/costing/ThresholdViolationError.kt b/djvm/djvm/src/main/kotlin/sandbox/net/corda/djvm/costing/ThresholdViolationError.kt similarity index 100% rename from djvm/src/main/kotlin/sandbox/net/corda/djvm/costing/ThresholdViolationError.kt rename to djvm/djvm/src/main/kotlin/sandbox/net/corda/djvm/costing/ThresholdViolationError.kt diff --git a/djvm/src/main/kotlin/sandbox/net/corda/djvm/rules/RuleViolationError.kt b/djvm/djvm/src/main/kotlin/sandbox/net/corda/djvm/rules/RuleViolationError.kt similarity index 100% rename from djvm/src/main/kotlin/sandbox/net/corda/djvm/rules/RuleViolationError.kt rename to djvm/djvm/src/main/kotlin/sandbox/net/corda/djvm/rules/RuleViolationError.kt diff --git a/djvm/src/test/java/net/corda/djvm/WithJava.java b/djvm/djvm/src/test/java/net/corda/djvm/WithJava.java similarity index 100% rename from djvm/src/test/java/net/corda/djvm/WithJava.java rename to djvm/djvm/src/test/java/net/corda/djvm/WithJava.java diff --git a/djvm/src/test/java/net/corda/djvm/execution/SandboxEnumJavaTest.java b/djvm/djvm/src/test/java/net/corda/djvm/execution/SandboxEnumJavaTest.java similarity index 100% rename from djvm/src/test/java/net/corda/djvm/execution/SandboxEnumJavaTest.java rename to djvm/djvm/src/test/java/net/corda/djvm/execution/SandboxEnumJavaTest.java diff --git a/djvm/djvm/src/test/java/net/corda/djvm/execution/SandboxExecutorJavaTest.java b/djvm/djvm/src/test/java/net/corda/djvm/execution/SandboxExecutorJavaTest.java new file mode 100644 index 0000000000..802288de3a --- /dev/null +++ b/djvm/djvm/src/test/java/net/corda/djvm/execution/SandboxExecutorJavaTest.java @@ -0,0 +1,64 @@ +package net.corda.djvm.execution; + +import net.corda.djvm.TestBase; +import net.corda.djvm.WithJava; +import org.junit.Test; + +import java.util.Set; +import java.util.function.Function; + +import static java.util.Collections.singleton; +import static net.corda.djvm.messages.Severity.WARNING; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +public class SandboxExecutorJavaTest extends TestBase { + private static final int TX_ID = 101; + + @Test + public void testTransaction() { + //TODO: Transaction should not be a pinned class! It needs + // to be marshalled into and out of the sandbox. + Set> pinnedClasses = singleton(Transaction.class); + sandbox(new Object[0], pinnedClasses, WARNING, true, ctx -> { + SandboxExecutor contractExecutor = new DeterministicSandboxExecutor<>(ctx.getConfiguration()); + Transaction tx = new Transaction(TX_ID); + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> WithJava.run(contractExecutor, ContractWrapper.class, tx)) + .withMessageContaining("Contract constraint violated: txId=" + TX_ID); + return null; + }); + } + + public interface Contract { + @SuppressWarnings("unused") + void verify(Transaction tx); + } + + public static class ContractImplementation implements Contract { + @Override + public void verify(Transaction tx) { + throw new IllegalArgumentException("Contract constraint violated: txId=" + tx.getId()); + } + } + + public static class ContractWrapper implements Function { + @Override + public Void apply(Transaction input) { + new ContractImplementation().verify(input); + return null; + } + } + + @SuppressWarnings("WeakerAccess") + public static class Transaction { + private final int id; + + Transaction(int id) { + this.id = id; + } + + public int getId() { + return id; + } + } +} \ No newline at end of file diff --git a/djvm/src/test/java/net/corda/djvm/execution/SandboxObjectHashCodeJavaTest.java b/djvm/djvm/src/test/java/net/corda/djvm/execution/SandboxObjectHashCodeJavaTest.java similarity index 100% rename from djvm/src/test/java/net/corda/djvm/execution/SandboxObjectHashCodeJavaTest.java rename to djvm/djvm/src/test/java/net/corda/djvm/execution/SandboxObjectHashCodeJavaTest.java diff --git a/djvm/src/test/java/net/corda/djvm/execution/SandboxThrowableJavaTest.java b/djvm/djvm/src/test/java/net/corda/djvm/execution/SandboxThrowableJavaTest.java similarity index 100% rename from djvm/src/test/java/net/corda/djvm/execution/SandboxThrowableJavaTest.java rename to djvm/djvm/src/test/java/net/corda/djvm/execution/SandboxThrowableJavaTest.java diff --git a/djvm/src/test/kotlin/foo/bar/sandbox/A.kt b/djvm/djvm/src/test/kotlin/foo/bar/sandbox/A.kt similarity index 100% rename from djvm/src/test/kotlin/foo/bar/sandbox/A.kt rename to djvm/djvm/src/test/kotlin/foo/bar/sandbox/A.kt diff --git a/djvm/src/test/kotlin/foo/bar/sandbox/B.kt b/djvm/djvm/src/test/kotlin/foo/bar/sandbox/B.kt similarity index 100% rename from djvm/src/test/kotlin/foo/bar/sandbox/B.kt rename to djvm/djvm/src/test/kotlin/foo/bar/sandbox/B.kt diff --git a/djvm/src/test/kotlin/foo/bar/sandbox/C.kt b/djvm/djvm/src/test/kotlin/foo/bar/sandbox/C.kt similarity index 100% rename from djvm/src/test/kotlin/foo/bar/sandbox/C.kt rename to djvm/djvm/src/test/kotlin/foo/bar/sandbox/C.kt diff --git a/djvm/src/test/kotlin/foo/bar/sandbox/Callable.kt b/djvm/djvm/src/test/kotlin/foo/bar/sandbox/Callable.kt similarity index 100% rename from djvm/src/test/kotlin/foo/bar/sandbox/Callable.kt rename to djvm/djvm/src/test/kotlin/foo/bar/sandbox/Callable.kt diff --git a/djvm/src/test/kotlin/foo/bar/sandbox/Empty.kt b/djvm/djvm/src/test/kotlin/foo/bar/sandbox/Empty.kt similarity index 100% rename from djvm/src/test/kotlin/foo/bar/sandbox/Empty.kt rename to djvm/djvm/src/test/kotlin/foo/bar/sandbox/Empty.kt diff --git a/djvm/src/test/kotlin/foo/bar/sandbox/KotlinClass.kt b/djvm/djvm/src/test/kotlin/foo/bar/sandbox/KotlinClass.kt similarity index 100% rename from djvm/src/test/kotlin/foo/bar/sandbox/KotlinClass.kt rename to djvm/djvm/src/test/kotlin/foo/bar/sandbox/KotlinClass.kt diff --git a/djvm/src/test/kotlin/foo/bar/sandbox/MyObject.kt b/djvm/djvm/src/test/kotlin/foo/bar/sandbox/MyObject.kt similarity index 100% rename from djvm/src/test/kotlin/foo/bar/sandbox/MyObject.kt rename to djvm/djvm/src/test/kotlin/foo/bar/sandbox/MyObject.kt diff --git a/djvm/src/test/kotlin/foo/bar/sandbox/StrictFloat.kt b/djvm/djvm/src/test/kotlin/foo/bar/sandbox/StrictFloat.kt similarity index 100% rename from djvm/src/test/kotlin/foo/bar/sandbox/StrictFloat.kt rename to djvm/djvm/src/test/kotlin/foo/bar/sandbox/StrictFloat.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/DJVMExceptionTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/DJVMExceptionTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/DJVMExceptionTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/DJVMExceptionTest.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/DJVMTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/DJVMTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/DJVMTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/DJVMTest.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/TestBase.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/TestBase.kt similarity index 98% rename from djvm/src/test/kotlin/net/corda/djvm/TestBase.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/TestBase.kt index 514a493567..62848d10cd 100644 --- a/djvm/src/test/kotlin/net/corda/djvm/TestBase.kt +++ b/djvm/djvm/src/test/kotlin/net/corda/djvm/TestBase.kt @@ -17,7 +17,7 @@ import net.corda.djvm.rules.Rule import net.corda.djvm.rules.implementation.* import net.corda.djvm.source.BootstrapClassLoader import net.corda.djvm.source.ClassSource -import net.corda.djvm.source.SandboxSourceClassLoader +import net.corda.djvm.source.SourceClassLoader import net.corda.djvm.utilities.Discovery import net.corda.djvm.validation.RuleValidator import org.junit.After @@ -88,7 +88,7 @@ abstract class TestBase { Whitelist.MINIMAL, bootstrapClassLoader = BootstrapClassLoader(DETERMINISTIC_RT), sourceClassLoaderFactory = { classResolver, bootstrapClassLoader -> - SandboxSourceClassLoader(classResolver, bootstrapClassLoader!!) + SourceClassLoader(classResolver, bootstrapClassLoader!!) }, additionalPinnedClasses = setOf( Utilities::class.java diff --git a/djvm/src/test/kotlin/net/corda/djvm/Utilities.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/Utilities.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/Utilities.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/Utilities.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/analysis/ClassAndMemberVisitorTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/analysis/ClassAndMemberVisitorTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/analysis/ClassAndMemberVisitorTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/analysis/ClassAndMemberVisitorTest.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/analysis/ClassResolverTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/analysis/ClassResolverTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/analysis/ClassResolverTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/analysis/ClassResolverTest.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/analysis/SourceLocationTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/analysis/SourceLocationTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/analysis/SourceLocationTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/analysis/SourceLocationTest.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/analysis/WhitelistTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/analysis/WhitelistTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/analysis/WhitelistTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/analysis/WhitelistTest.kt diff --git a/djvm/src/main/kotlin/net/corda/djvm/annotations/NonDeterministic.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/annotations/NonDeterministic.kt similarity index 100% rename from djvm/src/main/kotlin/net/corda/djvm/annotations/NonDeterministic.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/annotations/NonDeterministic.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertionExtensions.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertionExtensions.kt similarity index 94% rename from djvm/src/test/kotlin/net/corda/djvm/assertions/AssertionExtensions.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertionExtensions.kt index 09df337ff3..4b81827f01 100644 --- a/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertionExtensions.kt +++ b/djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertionExtensions.kt @@ -10,8 +10,7 @@ import net.corda.djvm.references.ClassHierarchy import net.corda.djvm.references.Member import net.corda.djvm.references.ReferenceMap import net.corda.djvm.rewiring.LoadedClass -import org.assertj.core.api.Assertions -import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.* import org.assertj.core.api.IterableAssert import org.assertj.core.api.ListAssert import org.assertj.core.api.ThrowableAssertAlternative @@ -67,7 +66,7 @@ object AssertionExtensions { fun ListAssert.withMessage(message: String): ListAssert = this .`as`("HasMessage($message)") .anySatisfy { - Assertions.assertThat(it.message).contains(message) + assertThat(it.message).contains(message) } } diff --git a/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassHierarchy.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassHierarchy.kt similarity index 73% rename from djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassHierarchy.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassHierarchy.kt index 310016df5d..ff57dbe0ee 100644 --- a/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassHierarchy.kt +++ b/djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassHierarchy.kt @@ -1,22 +1,22 @@ package net.corda.djvm.assertions import net.corda.djvm.references.ClassHierarchy -import org.assertj.core.api.Assertions +import org.assertj.core.api.Assertions.* open class AssertiveClassHierarchy(protected val hierarchy: ClassHierarchy) { fun hasCount(count: Int): AssertiveClassHierarchy { - Assertions.assertThat(hierarchy.names.size) + assertThat(hierarchy.names.size) .`as`("Number of classes") .isEqualTo(count) return this } fun hasClass(name: String): AssertiveClassHierarchyWithClass { - Assertions.assertThat(hierarchy.names) + assertThat(hierarchy.names) .`as`("Class($name)") .anySatisfy { - Assertions.assertThat(it).isEqualTo(name) + assertThat(it).isEqualTo(name) } return AssertiveClassHierarchyWithClass(hierarchy, name) } diff --git a/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassHierarchyWithClass.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassHierarchyWithClass.kt similarity index 75% rename from djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassHierarchyWithClass.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassHierarchyWithClass.kt index 6450485143..c1c421e0db 100644 --- a/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassHierarchyWithClass.kt +++ b/djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassHierarchyWithClass.kt @@ -2,7 +2,7 @@ package net.corda.djvm.assertions import net.corda.djvm.references.ClassRepresentation import net.corda.djvm.references.ClassHierarchy -import org.assertj.core.api.Assertions +import org.assertj.core.api.Assertions.* open class AssertiveClassHierarchyWithClass( hierarchy: ClassHierarchy, @@ -13,30 +13,30 @@ open class AssertiveClassHierarchyWithClass( get() = hierarchy[className]!! fun withInterfaceCount(count: Int): AssertiveClassHierarchyWithClass { - Assertions.assertThat(clazz.interfaces.size) + assertThat(clazz.interfaces.size) .`as`("$clazz.InterfaceCount($count)") .isEqualTo(count) return this } fun withInterface(name: String): AssertiveClassHierarchyWithClass { - Assertions.assertThat(clazz.interfaces).contains(name) + assertThat(clazz.interfaces).contains(name) return this } fun withMemberCount(count: Int): AssertiveClassHierarchyWithClass { - Assertions.assertThat(clazz.members.size) + assertThat(clazz.members.size) .`as`("MemberCount($className)") .isEqualTo(count) return this } fun withMember(name: String, signature: String): AssertiveClassHierarchyWithClassAndMember { - Assertions.assertThat(clazz.members.values) + assertThat(clazz.members.values) .`as`("Member($className.$name:$signature") .anySatisfy { - Assertions.assertThat(it.memberName).isEqualTo(name) - Assertions.assertThat(it.signature).isEqualTo(signature) + assertThat(it.memberName).isEqualTo(name) + assertThat(it.signature).isEqualTo(signature) } val member = clazz.members.values.first { it.memberName == name && it.signature == signature diff --git a/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassHierarchyWithClassAndMember.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassHierarchyWithClassAndMember.kt similarity index 83% rename from djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassHierarchyWithClassAndMember.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassHierarchyWithClassAndMember.kt index 38d6269559..7e4a53231b 100644 --- a/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassHierarchyWithClassAndMember.kt +++ b/djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassHierarchyWithClassAndMember.kt @@ -2,7 +2,7 @@ package net.corda.djvm.assertions import net.corda.djvm.references.ClassHierarchy import net.corda.djvm.references.Member -import org.assertj.core.api.Assertions +import org.assertj.core.api.Assertions.* @Suppress("unused", "CanBeParameter") class AssertiveClassHierarchyWithClassAndMember( @@ -12,14 +12,14 @@ class AssertiveClassHierarchyWithClassAndMember( ) : AssertiveClassHierarchyWithClass(hierarchy, className) { fun withAccessFlag(flag: Int): AssertiveClassHierarchyWithClassAndMember { - Assertions.assertThat(member.access and flag) + assertThat(member.access and flag) .`as`("$member.AccessFlag($flag)") .isNotEqualTo(0) return this } fun withNoAccessFlag(flag: Int): AssertiveClassHierarchyWithClassAndMember { - Assertions.assertThat(member.access and flag) + assertThat(member.access and flag) .`as`("$member.AccessFlag($flag)") .isEqualTo(0) return this diff --git a/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassWithByteCode.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassWithByteCode.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassWithByteCode.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveClassWithByteCode.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveDJVMObject.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveDJVMObject.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveDJVMObject.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveDJVMObject.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveMessages.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveMessages.kt similarity index 81% rename from djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveMessages.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveMessages.kt index 45ad527225..6ace6e3cab 100644 --- a/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveMessages.kt +++ b/djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveMessages.kt @@ -2,7 +2,7 @@ package net.corda.djvm.assertions import net.corda.djvm.messages.MessageCollection import net.corda.djvm.messages.Severity -import org.assertj.core.api.Assertions +import org.assertj.core.api.Assertions.* @Suppress("unused") class AssertiveMessages(private val messages: MessageCollection) { @@ -14,7 +14,7 @@ class AssertiveMessages(private val messages: MessageCollection) { } fun hasErrorCount(count: Int): AssertiveMessages { - Assertions.assertThat(messages.statistics[Severity.ERROR]) + assertThat(messages.statistics[Severity.ERROR]) .`as`("Number of errors") .withFailMessage(formatMessages(Severity.ERROR, count)) .isEqualTo(count) @@ -22,7 +22,7 @@ class AssertiveMessages(private val messages: MessageCollection) { } fun hasWarningCount(count: Int): AssertiveMessages { - Assertions.assertThat(messages.statistics[Severity.WARNING]) + assertThat(messages.statistics[Severity.WARNING]) .`as`("Number of warnings") .withFailMessage(formatMessages(Severity.WARNING, count)) .isEqualTo(count) @@ -30,7 +30,7 @@ class AssertiveMessages(private val messages: MessageCollection) { } fun hasInfoCount(count: Int): AssertiveMessages { - Assertions.assertThat(messages.statistics[Severity.INFORMATIONAL]) + assertThat(messages.statistics[Severity.INFORMATIONAL]) .`as`("Number of informational messages") .withFailMessage(formatMessages(Severity.INFORMATIONAL, count)) .isEqualTo(count) @@ -38,7 +38,7 @@ class AssertiveMessages(private val messages: MessageCollection) { } fun hasTraceCount(count: Int): AssertiveMessages { - Assertions.assertThat(messages.statistics[Severity.TRACE]) + assertThat(messages.statistics[Severity.TRACE]) .`as`("Number of trace messages") .withFailMessage(formatMessages(Severity.TRACE, count)) .isEqualTo(count) @@ -46,10 +46,10 @@ class AssertiveMessages(private val messages: MessageCollection) { } fun withMessage(message: String): AssertiveMessages { - Assertions.assertThat(messages.sorted()) + assertThat(messages.sorted()) .`as`("Has message: $message") .anySatisfy { - Assertions.assertThat(it.message).contains(message) + assertThat(it.message).contains(message) } return this } diff --git a/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveReferenceMap.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveReferenceMap.kt similarity index 69% rename from djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveReferenceMap.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveReferenceMap.kt index 5b349edbe3..512a1ab155 100644 --- a/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveReferenceMap.kt +++ b/djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveReferenceMap.kt @@ -3,26 +3,26 @@ package net.corda.djvm.assertions import net.corda.djvm.references.ClassReference import net.corda.djvm.references.MemberReference import net.corda.djvm.references.ReferenceMap -import org.assertj.core.api.Assertions +import org.assertj.core.api.Assertions.* @Suppress("unused") open class AssertiveReferenceMap(private val references: ReferenceMap) { fun hasCount(count: Int): AssertiveReferenceMap { val allReferences = references.joinToString("\n") { " - $it" } - Assertions.assertThat(references.numberOfReferences) + assertThat(references.numberOfReferences) .overridingErrorMessage("Expected $count reference(s), found:\n$allReferences") .isEqualTo(count) return this } fun hasClass(clazz: String): AssertiveReferenceMapWithEntity { - Assertions.assertThat(references.iterator()) + assertThat(references) .`as`("Class($clazz)") .anySatisfy { - Assertions.assertThat(it).isInstanceOf(ClassReference::class.java) + assertThat(it).isInstanceOf(ClassReference::class.java) if (it is ClassReference) { - Assertions.assertThat(it.className).isEqualTo(clazz) + assertThat(it.className).isEqualTo(clazz) } } val reference = ClassReference(clazz) @@ -30,13 +30,13 @@ open class AssertiveReferenceMap(private val references: ReferenceMap) { } fun hasMember(owner: String, member: String, signature: String): AssertiveReferenceMapWithEntity { - Assertions.assertThat(references.iterator()) + assertThat(references) .`as`("Member($owner.$member)") .anySatisfy { - Assertions.assertThat(it).isInstanceOf(MemberReference::class.java) + assertThat(it).isInstanceOf(MemberReference::class.java) if (it is MemberReference) { - Assertions.assertThat(it.className).isEqualTo(owner) - Assertions.assertThat(it.memberName).isEqualTo(member) + assertThat(it.className).isEqualTo(owner) + assertThat(it.memberName).isEqualTo(member) } } val reference = MemberReference(owner, member, signature) diff --git a/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveReferenceMapWithEntity.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveReferenceMapWithEntity.kt similarity index 87% rename from djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveReferenceMapWithEntity.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveReferenceMapWithEntity.kt index ec19591eac..80c797fda3 100644 --- a/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveReferenceMapWithEntity.kt +++ b/djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveReferenceMapWithEntity.kt @@ -3,7 +3,7 @@ package net.corda.djvm.assertions import net.corda.djvm.analysis.SourceLocation import net.corda.djvm.references.EntityReference import net.corda.djvm.references.ReferenceMap -import org.assertj.core.api.Assertions +import org.assertj.core.api.Assertions.* class AssertiveReferenceMapWithEntity( references: ReferenceMap, @@ -12,7 +12,7 @@ class AssertiveReferenceMapWithEntity( ) : AssertiveReferenceMap(references) { fun withLocationCount(count: Int): AssertiveReferenceMapWithEntity { - Assertions.assertThat(locations.size) + assertThat(locations.size) .`as`("LocationCount($entity)") .isEqualTo(count) return this diff --git a/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveRuntimeCostSummary.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveRuntimeCostSummary.kt similarity index 78% rename from djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveRuntimeCostSummary.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveRuntimeCostSummary.kt index 5ac1e33538..a5cc2eca73 100644 --- a/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveRuntimeCostSummary.kt +++ b/djvm/djvm/src/test/kotlin/net/corda/djvm/assertions/AssertiveRuntimeCostSummary.kt @@ -1,7 +1,7 @@ package net.corda.djvm.assertions import net.corda.djvm.costing.RuntimeCostSummary -import org.assertj.core.api.Assertions +import org.assertj.core.api.Assertions.* @Suppress("MemberVisibilityCanBePrivate") class AssertiveRuntimeCostSummary(private val costs: RuntimeCostSummary) { @@ -14,42 +14,42 @@ class AssertiveRuntimeCostSummary(private val costs: RuntimeCostSummary) { } fun hasAllocationCost(cost: Long): AssertiveRuntimeCostSummary { - Assertions.assertThat(costs.allocationCost.value) + assertThat(costs.allocationCost.value) .`as`("Allocation cost") .isEqualTo(cost) return this } fun hasInvocationCost(cost: Long): AssertiveRuntimeCostSummary { - Assertions.assertThat(costs.invocationCost.value) + assertThat(costs.invocationCost.value) .`as`("Invocation cost") .isEqualTo(cost) return this } fun hasInvocationCostGreaterThanOrEqualTo(cost: Long): AssertiveRuntimeCostSummary { - Assertions.assertThat(costs.invocationCost.value) + assertThat(costs.invocationCost.value) .`as`("Invocation cost") .isGreaterThanOrEqualTo(cost) return this } fun hasJumpCost(cost: Long): AssertiveRuntimeCostSummary { - Assertions.assertThat(costs.jumpCost.value) + assertThat(costs.jumpCost.value) .`as`("Jump cost") .isEqualTo(cost) return this } fun hasJumpCostGreaterThanOrEqualTo(cost: Long): AssertiveRuntimeCostSummary { - Assertions.assertThat(costs.jumpCost.value) + assertThat(costs.jumpCost.value) .`as`("Jump cost") .isGreaterThanOrEqualTo(cost) return this } fun hasThrowCost(cost: Long): AssertiveRuntimeCostSummary { - Assertions.assertThat(costs.throwCost.value) + assertThat(costs.throwCost.value) .`as`("Throw cost") .isEqualTo(cost) return this diff --git a/djvm/src/test/kotlin/net/corda/djvm/code/ClassMutatorTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/code/ClassMutatorTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/code/ClassMutatorTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/code/ClassMutatorTest.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/code/EmitterModuleTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/code/EmitterModuleTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/code/EmitterModuleTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/code/EmitterModuleTest.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/costing/RuntimeCostTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/costing/RuntimeCostTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/costing/RuntimeCostTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/costing/RuntimeCostTest.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxEnumTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxEnumTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/execution/SandboxEnumTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxEnumTest.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxExecutorTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxExecutorTest.kt similarity index 99% rename from djvm/src/test/kotlin/net/corda/djvm/execution/SandboxExecutorTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxExecutorTest.kt index 0708af248d..91faffcacd 100644 --- a/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxExecutorTest.kt +++ b/djvm/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxExecutorTest.kt @@ -5,7 +5,6 @@ import foo.bar.sandbox.testClock import foo.bar.sandbox.toNumber import net.corda.djvm.TestBase import net.corda.djvm.analysis.Whitelist.Companion.MINIMAL -import net.corda.djvm.Utilities import net.corda.djvm.Utilities.throwRuleViolationError import net.corda.djvm.Utilities.throwThresholdViolationError import net.corda.djvm.assertions.AssertionExtensions.withProblem @@ -36,10 +35,9 @@ class SandboxExecutorTest : TestBase() { } @Test - fun `can load and execute contract`() = sandbox(DEFAULT, - pinnedClasses = setOf(Transaction::class.java, Utilities::class.java) - ) { + fun `can load and execute contract`() = sandbox(DEFAULT, pinnedClasses = setOf(Transaction::class.java)) { val contractExecutor = DeterministicSandboxExecutor(configuration) + //TODO: Transaction should not be a pinned class! It needs to be marshalled into and out of the sandbox. val tx = Transaction(1) assertThatExceptionOfType(SandboxException::class.java) .isThrownBy { contractExecutor.run(tx) } diff --git a/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxThrowableTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxThrowableTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/execution/SandboxThrowableTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxThrowableTest.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/formatter/MemberFormatterTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/formatter/MemberFormatterTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/formatter/MemberFormatterTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/formatter/MemberFormatterTest.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/references/ClassHierarchyTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/references/ClassHierarchyTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/references/ClassHierarchyTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/references/ClassHierarchyTest.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/references/ClassModuleTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/references/ClassModuleTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/references/ClassModuleTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/references/ClassModuleTest.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/references/MemberModuleTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/references/MemberModuleTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/references/MemberModuleTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/references/MemberModuleTest.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/rewiring/ClassRewriterTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/rewiring/ClassRewriterTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/rewiring/ClassRewriterTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/rewiring/ClassRewriterTest.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/rules/ReferenceExtractorTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/rules/ReferenceExtractorTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/rules/ReferenceExtractorTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/rules/ReferenceExtractorTest.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/rules/RuleValidatorTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/rules/RuleValidatorTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/rules/RuleValidatorTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/rules/RuleValidatorTest.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/source/SourceClassLoaderTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/source/SourceClassLoaderTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/source/SourceClassLoaderTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/source/SourceClassLoaderTest.kt diff --git a/djvm/src/test/kotlin/net/corda/djvm/utilities/DiscoveryTest.kt b/djvm/djvm/src/test/kotlin/net/corda/djvm/utilities/DiscoveryTest.kt similarity index 100% rename from djvm/src/test/kotlin/net/corda/djvm/utilities/DiscoveryTest.kt rename to djvm/djvm/src/test/kotlin/net/corda/djvm/utilities/DiscoveryTest.kt diff --git a/djvm/src/test/kotlin/sandbox/greymalkin/StringReturner.kt b/djvm/djvm/src/test/kotlin/sandbox/greymalkin/StringReturner.kt similarity index 100% rename from djvm/src/test/kotlin/sandbox/greymalkin/StringReturner.kt rename to djvm/djvm/src/test/kotlin/sandbox/greymalkin/StringReturner.kt diff --git a/djvm/src/test/resources/jar-with-single-class.jar b/djvm/djvm/src/test/resources/jar-with-single-class.jar similarity index 100% rename from djvm/src/test/resources/jar-with-single-class.jar rename to djvm/djvm/src/test/resources/jar-with-single-class.jar diff --git a/djvm/src/test/resources/jar-with-two-classes.jar b/djvm/djvm/src/test/resources/jar-with-two-classes.jar similarity index 100% rename from djvm/src/test/resources/jar-with-two-classes.jar rename to djvm/djvm/src/test/resources/jar-with-two-classes.jar diff --git a/djvm/src/test/resources/log4j2-test.xml b/djvm/djvm/src/test/resources/log4j2-test.xml similarity index 100% rename from djvm/src/test/resources/log4j2-test.xml rename to djvm/djvm/src/test/resources/log4j2-test.xml diff --git a/djvm/gradle.properties b/djvm/gradle.properties new file mode 100644 index 0000000000..582007711d --- /dev/null +++ b/djvm/gradle.properties @@ -0,0 +1,13 @@ +kotlin.incremental=true +org.gradle.jvmargs=-XX:+UseG1GC -Xmx1g -Dfile.encoding=UTF-8 + +asm_version=6.2.1 +assertj_version=3.12.1 +class_graph_version=4.6.12 +jcabi_manifests_version=1.1 +jopt_simple_version=5.0.2 +junit_version=4.12 +kotlin_version=1.2.71 +log4j_version=2.11.2 +picocli_version=3.8.0 +slf4j_version=1.7.26 diff --git a/djvm/gradle/wrapper/gradle-wrapper.jar b/djvm/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..87b738cbd0 Binary files /dev/null and b/djvm/gradle/wrapper/gradle-wrapper.jar differ diff --git a/djvm/gradle/wrapper/gradle-wrapper.properties b/djvm/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..1b2b07cfb0 --- /dev/null +++ b/djvm/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/djvm/gradlew b/djvm/gradlew new file mode 100755 index 0000000000..af6708ff22 --- /dev/null +++ b/djvm/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/djvm/gradlew.bat b/djvm/gradlew.bat new file mode 100644 index 0000000000..0f8d5937c4 --- /dev/null +++ b/djvm/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/djvm/settings.gradle b/djvm/settings.gradle new file mode 100644 index 0000000000..b6f85fa9a0 --- /dev/null +++ b/djvm/settings.gradle @@ -0,0 +1,4 @@ +rootProject.name = "deterministic-jvm-sandbox" + +include 'djvm' +include 'djvm:cli' diff --git a/djvm/src/main/kotlin/net/corda/djvm/analysis/PrefixTree.kt b/djvm/src/main/kotlin/net/corda/djvm/analysis/PrefixTree.kt deleted file mode 100644 index 26679cc133..0000000000 --- a/djvm/src/main/kotlin/net/corda/djvm/analysis/PrefixTree.kt +++ /dev/null @@ -1,38 +0,0 @@ -package net.corda.djvm.analysis - -/** - * Trie data structure to make prefix matching more efficient. - */ -class PrefixTree { - - private class Node(val children: MutableMap = mutableMapOf()) - - private val root = Node() - - /** - * Add a new prefix to the set. - */ - fun add(prefix: String) { - var node = root - for (char in prefix) { - val nextNode = node.children.computeIfAbsent(char) { Node() } - node = nextNode - } - } - - /** - * Check if any of the registered prefixes matches the provided string. - */ - fun contains(string: String): Boolean { - var node = root - for (char in string) { - val nextNode = node.children[char] ?: return false - if (nextNode.children.isEmpty()) { - return true - } - node = nextNode - } - return false - } - -} diff --git a/djvm/src/main/kotlin/net/corda/djvm/validation/Reason.kt b/djvm/src/main/kotlin/net/corda/djvm/validation/Reason.kt deleted file mode 100644 index 506d333f81..0000000000 --- a/djvm/src/main/kotlin/net/corda/djvm/validation/Reason.kt +++ /dev/null @@ -1,46 +0,0 @@ -package net.corda.djvm.validation - -/** - * Representation of the reason for why a reference has been marked as invalid. - * - * @property code The code used to label the error. - * @property classes A set of invalid class references, where applicable. - */ -data class Reason( - val code: Code, - val classes: List = emptyList() -) { - - /** - * The derived description of the error. - */ - val description = classes.joinToString(", ").let { - when { - classes.size == 1 -> "${code.singularDescription}; $it" - classes.size > 1 -> "${code.pluralDescription}; $it" - else -> code.singularDescription - } - } - - /** - * Error codes used to label invalid references. - * - * @property singularDescription The description to use when [classes] is empty or has one element. - * @property pluralDescription The description to use when [classes] has more than one element. - */ - @Suppress("KDocMissingDocumentation") - enum class Code( - val singularDescription: String, - val pluralDescription: String = singularDescription - ) { - INVALID_CLASS( - singularDescription = "entity signature contains an invalid reference", - pluralDescription = "entity signature contains invalid references" - ), - NOT_WHITELISTED("entity is not whitelisted"), - ANNOTATED("entity is annotated with @NonDeterministic"), - NON_EXISTENT_CLASS("class does not exist"), - NON_EXISTENT_MEMBER("member does not exist") - } - -} \ No newline at end of file diff --git a/docker/build.gradle b/docker/build.gradle index 30fea9f79f..2891b251ab 100644 --- a/docker/build.gradle +++ b/docker/build.gradle @@ -1,17 +1,9 @@ -evaluationDependsOn(":node:capsule") -buildscript { - repositories { - mavenLocal() - mavenCentral() - jcenter() - } - dependencies { - classpath 'com.bmuschko:gradle-docker-plugin:3.4.4' - } +plugins { + id 'com.bmuschko.docker-remote-api' version '3.4.4' } +evaluationDependsOn(":node:capsule") -import com.bmuschko.gradle.docker.DockerRemoteApiPlugin import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage import com.bmuschko.gradle.docker.tasks.image.DockerPushImage @@ -19,7 +11,6 @@ import java.time.LocalDateTime import java.time.format.DateTimeFormatter apply plugin: 'kotlin' -apply plugin: DockerRemoteApiPlugin apply plugin: 'application' // We need to set mainClassName before applying the shadow plugin. mainClassName = 'net.corda.core.ConfigExporterMain' @@ -96,4 +87,4 @@ task pushCorrettoLatestTag('type': DockerPushImage, dependsOn: [buildOfficialCor imageName = correttoBuildTags[1] } -task pushOfficialImages(dependsOn: [pushZuluTimeStampedTag, pushZuluLatestTag, pushCorrettoTimeStampedTag, pushCorrettoLatestTag]) \ No newline at end of file +task pushOfficialImages(dependsOn: [pushZuluTimeStampedTag, pushZuluLatestTag, pushCorrettoTimeStampedTag, pushCorrettoLatestTag]) diff --git a/docs/README.md b/docs/README.md index 648273ec2b..216274c94d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,3 +6,28 @@ Note: In order to run the documentation build you will need Docker installed. Windows users: If this task fails because Docker can't find make-docsite.sh, go to Settings > Shared Drives in the Docker system tray agent, make sure the relevant drive is shared, and click 'Reset credentials'. + +## rst style guide + +It's probably worth reading [this](http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html) +to get your head around the rst syntax we're using. + +## version placeholders + +Currently we support five placeholders that get substituted at build time: + +```groovy + "|corda_version|" + "|java_version|" + "|kotlin_version|" + "|gradle_plugins_version|" + "|quasar_version|" +``` + +If you put one of these in an rst file anywhere (including in a code tag) then it will be substituted with the value in constants.properties +(which is in the root of the project) at build time. + +The code for this can be found near the top of the conf.py file in the `docs/source` directory. + + + diff --git a/docs/make-docsite.sh b/docs/make-docsite.sh index 5075db9db0..2aedeb5ceb 100755 --- a/docs/make-docsite.sh +++ b/docs/make-docsite.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash echo "Generating PDF document ..." -make latexpdf +make latexpdf LATEXMKOPTS="-quiet" echo "Generating HTML pages ..." make html diff --git a/docs/source/api-contract-constraints.rst b/docs/source/api-contract-constraints.rst index 6da31d01a6..7b2b039764 100644 --- a/docs/source/api-contract-constraints.rst +++ b/docs/source/api-contract-constraints.rst @@ -24,7 +24,7 @@ to it. There are several types of constraint: 1. Hash constraint: exactly one version of the app can be used with this state. -2. Zone whitelist constraint: the compatibility zone operator lists the hashes of the versions that can be used with this contract class name. +2. Compatibility zone whitelisted (or CZ whitelisted) constraint: the compatibility zone operator lists the hashes of the versions that can be used with this contract class name. 3. Signature constraint: any version of the app signed by the given ``CompositeKey`` can be used. 4. Always accept constraint: any app can be used at all. This is insecure but convenient for testing. @@ -46,6 +46,8 @@ to issue the states was signed by Alice and Bob, every transaction must use an a the constraint used by equivalent output states (i.e. output states that use the same contract class name) must match the input state, so it can't be changed and you can't combine states with incompatible constraints together in the same transaction. +.. _implicit_vs_explicit_upgrades: + **Implicit vs explicit.** Constraints are not the only way to manage upgrades to transactions. There are two ways of handling upgrades to a smart contract in Corda: @@ -136,15 +138,15 @@ From there it's suspended waiting to be retried on node restart. This gives the node operator the opportunity to recover from those errors, which in the case of constraint violations means adding the right cordapp jar to the ``cordapps`` folder. +.. _relax_hash_constraints_checking_ref: + Hash constrained states in private networks ------------------------------------------- Where private networks started life using CorDapps with hash constrained states, we have introduced a mechanism to relax the checking of these hash constrained states when upgrading to signed CorDapps using signature constraints. -The following java system property may be set to relax the hash constraint checking behaviour: - - -Dnet.corda.node.disableHashConstraints="true" +The Java system property ``-Dnet.corda.node.disableHashConstraints="true"`` may be set to relax the hash constraint checking behaviour. This mode should only be used upon "out of band" agreement by all participants in a network. @@ -191,56 +193,11 @@ During transaction building the ``AutomaticPlaceholderConstraint`` for output st will be selected based on a variety of factors so that the above holds true. If it can't find attachments in storage or there are no possible constraints, the ``TransactionBuilder`` will throw an exception. +Constraints migration to Corda 4 +-------------------------------- -How to use the ``SignatureAttachmentConstraint`` if states were already created on the network with the ``WhitelistedByZoneAttachmentConstraint`` -------------------------------------------------------------------------------------------------------------------------------------------------- - -1. As the original developer of the corDapp, the first step is to sign the latest version of the JAR that was released (see :doc:`cordapp-build-systems`). -The key used for signing will be used to sign all subsequent releases, so it should be stored appropriately. The JAR can be signed by multiple keys owned -by different parties and it will be expressed as a ``CompositeKey`` in the ``SignatureAttachmentConstraint`` (See :doc:`api-core-types`). -Use `JAR signing and verification tool `_ to sign the existing JAR. -The signing capability of :ref:`corda-gradle-plugins ` cannot be used in this context as it signs the JAR while building it from source. - -2. Whitelist this newly signed JAR with the Zone operator. The Zone operator should check that the JAR is signed and not allow any -more versions of it to be whitelisted in the future. From now on the developer(s) who signed the JAR are responsible for new versions. - -3. Any flows that build transactions using this Cordapp will have the responsibility of transitioning states to the ``SignatureAttachmentConstraint``. -This is done explicitly in the code by setting the constraint of the output states to signers of the latest version of the whitelisted jar. -In the near future we will make this transition automatic if we detect that the previous 2 steps were executed. - -4. As a node operator you need to add the new signed version of the contracts cordapp to the "cordapps" folder together with the latest version of the flows jar -that will contain code like: - -.. container:: codeset - - .. sourcecode:: kotlin - - // This will read the signers for the deployed cordapp. - val attachment = this.serviceHub.cordappProvider.getContractAttachmentID(contractClass) - val signers = this.serviceHub.attachments.openAttachment(attachment!!)!!.signerKeys - - // Create the key that will have to pass for all future versions. - val ownersKey = signers.first() - - val txBuilder = TransactionBuilder(notary) - // Set the Signature constraint on the new state to migrate away from the WhitelistConstraint. - .addOutputState(outputState, constraint = SignatureAttachmentConstraint(ownersKey)) - ... - - .. sourcecode:: java - - // This will read the signers for the deployed cordapp. - SecureHash attachment = this.getServiceHub().getCordappProvider().getContractAttachmentID(contractClass); - List signers = this.getServiceHub().getAttachments().openAttachment(attachment).getSignerKeys(); - - // Create the key that will have to pass for all future versions. - PublicKey ownersKey = signers.get(0); - - TransactionBuilder txBuilder = new TransactionBuilder(notary) - // Set the Signature constraint on the new state to migrate away from the WhitelistConstraint. - .addOutputState(outputState, myContract, new SignatureAttachmentConstraint(ownersKey)) - ... - +Please read :doc:`cordapp-constraint-migration` to understand how to consume and evolve pre-Corda 4 issued hash or CZ whitelisted constrained states +using a Corda 4 signed CorDapp (using signature constraints). Debugging --------- @@ -276,8 +233,8 @@ The same example in Java: }); -Staring a node missing CorDapp(s) -********************************* +Starting a node missing CorDapp(s) +********************************** When running the Corda node ensure all CordDapp JARs are placed in ``cordapps`` directory of each node. By default Gradle Cordform task ``deployNodes`` copies all JARs if CorDapps to deploy are specified. diff --git a/docs/source/api-flows.rst b/docs/source/api-flows.rst index da86668d4d..79c406bbf7 100644 --- a/docs/source/api-flows.rst +++ b/docs/source/api-flows.rst @@ -613,6 +613,21 @@ flow to receive the transaction: ``idOfTxWeSigned`` is an optional parameter used to confirm that we got the right transaction. It comes from using ``SignTransactionFlow`` which is described below. +**Error handling behaviour** + +Once a transaction has been notarised and its input states consumed by the flow initiator (eg. sender), should the participant(s) receiving the +transaction fail to verify it, or the receiving flow (the finality handler) fails due to some other error, we then have a scenario where not +all parties have the correct up to date view of the ledger (a condition where eventual consistency between participants takes longer than is +normally the case under Corda's `eventual consistency model `_). To recover from this scenario, +the receiver's finality handler will automatically be sent to the :doc:`node-flow-hospital` where it's suspended and retried from its last checkpoint +upon node restart, or according to other conditional retry rules explained in :ref:`flow hospital runtime behaviour `. +This gives the node operator the opportunity to recover from the error. Until the issue is resolved the node will continue to retry the flow +on each startup. Upon successful completion by the receiver's finality flow, the ledger will become fully consistent once again. + +.. warning:: It's possible to forcibly terminate the erroring finality handler using the ``killFlow`` RPC but at the risk of an inconsistent view of the ledger. + +.. note:: A future release will allow retrying hospitalised flows without restarting the node, i.e. via RPC. + CollectSignaturesFlow/SignTransactionFlow ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The list of parties who need to sign a transaction is dictated by the transaction's commands. Once we've signed a diff --git a/docs/source/api-persistence.rst b/docs/source/api-persistence.rst index c9b9e4db2f..f729ea14d2 100644 --- a/docs/source/api-persistence.rst +++ b/docs/source/api-persistence.rst @@ -12,7 +12,7 @@ API: Persistence Corda offers developers the option to expose all or some parts of a contract state to an *Object Relational Mapping* (ORM) tool to be persisted in a *Relational Database Management System* (RDBMS). -The purpose of this, is to assist `vault `_ +The purpose of this, is to assist :doc:`key-concepts-vault` development and allow for the persistence of state data to a custom database table. Persisted states held in the vault are indexed for the purposes of executing queries. This also allows for relational joins between Corda tables and the organization's existing data. diff --git a/docs/source/api-scanner.rst b/docs/source/api-scanner.rst index c0fb8ce8b3..e9446001cd 100644 --- a/docs/source/api-scanner.rst +++ b/docs/source/api-scanner.rst @@ -36,7 +36,7 @@ broken Corda's API. How it works ------------ -The ``generateApi`` Gradle task writes a summary of Corda's public API into the file ``build/api/api-corda-.txt``. +The ``generateApi`` Gradle task writes a summary of Corda's public API into the file ``build/api/api-corda-|corda_version|.txt``. The ``.ci/check-api-changes.sh`` script then compares this file with the contents of ``.ci/api-current.txt``, which is a managed file within the Corda repository. diff --git a/docs/source/api-testing.rst b/docs/source/api-testing.rst index 9a6004bdb4..9d7aa36bdc 100644 --- a/docs/source/api-testing.rst +++ b/docs/source/api-testing.rst @@ -161,8 +161,8 @@ Further examples * See the flow testing tutorial :doc:`here ` * See the oracle tutorial :doc:`here ` for information on testing ``@CordaService`` classes * Further examples are available in the Example CorDapp in - `Java `_ and - `Kotlin `_ + `Java `_ and + `Kotlin `_ Contract testing ---------------- @@ -380,5 +380,5 @@ Further examples * See the flow testing tutorial :doc:`here ` * Further examples are available in the Example CorDapp in - `Java `_ and - `Kotlin `_ + `Java `_ and + `Kotlin `_ diff --git a/docs/source/app-upgrade-notes.rst b/docs/source/app-upgrade-notes.rst index 15bee7a3c1..e9a104b0a8 100644 --- a/docs/source/app-upgrade-notes.rst +++ b/docs/source/app-upgrade-notes.rst @@ -30,17 +30,19 @@ Although the RPC API is backwards compatible with Corda 3, the RPC wire protocol updated in lockstep with the node to use the new version of the RPC library. Corda 4 delivers RPC wire stability and therefore in future you will be able to update the node and apps without updating RPC clients. +.. _cordapp_upgrade_version_numbers_ref: + Step 2. Adjust the version numbers in your Gradle build files ------------------------------------------------------------- Alter the versions you depend on in your Gradle file like so: -.. sourcecode:: groovy +.. code:: groovy - ext.corda_release_version = '4.0' - ext.corda_gradle_plugins_version = '4.0.38' - ext.kotlin_version = '1.2.71' - ext.quasar_version = '0.7.10' + ext.corda_release_version = '|corda_version|' + ext.corda_gradle_plugins_version = '|gradle_plugins_version|' + ext.kotlin_version = '|kotlin_version|' + ext.quasar_version = '|quasar_version|' .. note:: You may wish to update your kotlinOptions to use language level 1.2, to benefit from the new features. Apps targeting Corda 4 may not at this time use Kotlin 1.3, as it was released too late in the development cycle @@ -154,6 +156,8 @@ Would become: See :ref:`cordapp_configuration_files_ref` for more information. +.. _cordapp_upgrade_finality_flow_ref: + Step 5. Security: Upgrade your use of FinalityFlow -------------------------------------------------- @@ -161,11 +165,25 @@ The previous ``FinalityFlow`` API is insecure. It doesn't have a receive flow, s all signed transactions that are sent to it, without checks. It is **highly** recommended that existing CorDapps migrate away to the new API, as otherwise things like business network membership checks won't be reliably enforced. -This is a three step process: +The flows that make use of ``FinalityFlow`` in a CorDapp can be classified in the following 2 basic categories: + +* **non-initiating flows**: these are flows that finalise a transaction without the involvement of a counterpart flow at all. +* **initiating flows**: these are flows that initiate a counterpart (responder) flow. + +There is a main difference between these 2 different categories, which is relevant to how the CorDapp can be upgraded. +The second category of flows can be upgraded to use the new ``FinalityFlow`` in a backwards compatible way, which means the upgraded CorDapp can be deployed at the various nodes using a *rolling deployment*. +On the other hand, the first category of flows cannot be upgraded to the new ``FinalityFlow`` in a backwards compatible way, so the changes to these flows need to be deployed simultaneously at all the nodes, using a *lockstep deployment*. + +.. note:: A *lockstep deployment* is one, where all the involved nodes are stopped, upgraded to the new version of the CorDapp and then re-started. + As a result, there can't be any nodes running different versions of the CorDapp at any time. + A *rolling deployment* is one, where every node can be stopped, upgraded to the new version of the CorDapp and re-started independently and on its own pace. + As a result, there can be nodes running different versions of the CorDapp and transact with each other successfully. + +The upgrade is a three step process: 1. Change the flow that calls ``FinalityFlow``. 2. Change or create the flow that will receive the finalised transaction. -3. Make sure your application's minimum and target version numbers are both set to 4 (see step 2). +3. Make sure your application's minimum and target version numbers are both set to 4 (see :ref:`cordapp_upgrade_version_numbers_ref`). Upgrading a non-initiating flow ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -220,7 +238,7 @@ to record the finalised transaction: :end-before: DOCEND SimpleNewResponderFlow :dedent: 4 -.. note:: All the nodes in your business network will need the new CorDapp, otherwise they won't know how to receive the transaction. **This +.. note:: As described above, all the nodes in your business network will need the new CorDapp, otherwise they won't know how to receive the transaction. **This includes nodes which previously didn't have the old CorDapp.** If a node is sent a transaction and it doesn't have the new CorDapp loaded then simply restart it with the CorDapp and the transaction will be recorded. @@ -358,6 +376,9 @@ automatically use them if your application JAR is signed. **We recommend all JAR with developer certificates is deployed to a production node, the node will refuse to start. Therefore to deploy apps built for Corda 4 to production you will need to generate signing keys and integrate them with the build process. +.. note:: Please read the :doc:`cordapp-constraint-migration` guide to understand how to upgrade CorDapps to use Corda 4 signature constraints and consume + existing states on ledger issued with older constraint types (e.g. Corda 3.x states issued with **hash** or **CZ whitelisted** constraints). + Step 10. Security: Package namespace handling --------------------------------------------- @@ -411,4 +432,20 @@ Corda 4 adds several new APIs that help you build applications. Why not explore: * The `new withEntityManager API `_ for using JPA inside your flows and services. * :ref:`reference_states`, that let you use an input state without consuming it. -* :ref:`state_pointers`, that make it easier to 'point' to one state from another and follow the latest version of a linear state. \ No newline at end of file +* :ref:`state_pointers`, that make it easier to 'point' to one state from another and follow the latest version of a linear state. + +Please also read the :doc:`CorDapp Upgradeability Guarantees ` associated with CorDapp upgrading. + +Step 14. Possibly update your checked in quasar.jar +--------------------------------------------------- + +If your project is based on one of the official cordapp templates, it is likely you have a ``lib/quasar.jar`` checked in. It is worth noting +that you only use this if you use the JUnit runner in IntelliJ. In the latest release of the cordapp templates, this directory has +been removed. + +You have some choices here: + +* Upgrade your ``quasar.jar`` to ``|quasar_version|`` +* Delete your ``lib`` directory and switch to using the Gradle test runner + +Instructions for both options can be found in :ref:`Running tests in Intellij `. diff --git a/docs/source/azure-vm.rst b/docs/source/azure-vm.rst index d63e6ce79f..a5793d4872 100644 --- a/docs/source/azure-vm.rst +++ b/docs/source/azure-vm.rst @@ -101,28 +101,7 @@ The nodes you will use to send and receive Yo messages require the Yo! CorDapp j 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: -For Corda nodes running release M10 - -.. sourcecode:: shell - - cd /opt/corda/cordapps - wget http://downloads.corda.net/cordapps/net/corda/yo/0.10.1/yo-0.10.1.jar - -For Corda nodes running release M11 - -.. sourcecode:: shell - - cd /opt/corda/cordapps - wget http://downloads.corda.net/cordapps/net/corda/yo/0.11.0/yo-0.11.0.jar - -For Corda nodes running version 2 - -.. sourcecode:: shell - - cd /opt/corda/plugins - wget http://ci-artifactory.corda.r3cev.com/artifactory/cordapp-showcase/yo-4.jar - - +Build the yo cordapp sample which you can find here: https://github.com/corda/samples/tree/release-V|platform_version|/yo-cordapp and install it in the cordapp directory. Now restart Corda and the Corda webserver using the following commands or restart your Corda VM from the Azure portal: diff --git a/docs/source/building-a-cordapp-index.rst b/docs/source/building-a-cordapp-index.rst index 739ebf3caa..1af5764c7c 100644 --- a/docs/source/building-a-cordapp-index.rst +++ b/docs/source/building-a-cordapp-index.rst @@ -14,10 +14,8 @@ CorDapps debugging-a-cordapp versioning upgrading-cordapps + cordapp-constraint-migration + cordapp-upgradeability secure-coding-guidelines flow-overriding - corda-api flow-cookbook - cheat-sheet - vault - soft-locking diff --git a/docs/source/building-the-docs.rst b/docs/source/building-the-docs.rst index 2bf1b5dcdd..85673f6c3a 100644 --- a/docs/source/building-the-docs.rst +++ b/docs/source/building-the-docs.rst @@ -4,6 +4,36 @@ Building the documentation The documentation is under the ``docs`` folder, and is written in reStructuredText format. Documentation in HTML format is pre-generated, as well as code documentation, and this can be done automatically via a provided script. +Building Using the Docker Image +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This is the method used during the build. If you run: + +.. code-block:: shell + + ./gradlew makeDocs + +This will download a docker image from docker hub and run the build locally inside that by mounting quite a bit of the docs directory at +various places inside the image. + +This image is pre-built with the dependencies that were in requirements.txt at the time of the docker build. + +Changing requirements +--------------------- + +If you want to upgrade, say, the version of sphinx that we're using, you must: + +* Upgrade the version number in requirements.txt +* Build a new docker image: ``cd docs && docker build -t corda/docs-builder:latest -f docs_builder/Dockerfile .`` + + * post doing this the build will run using your image locally + * you can also push this to the docker registry if you have the corda keys + * you can run ``docker run -it corda/docs-builder /bin/bash`` to interactively look in the build docker image (e.g. to see what is in the + requirements.txt file) + +Building from the Command Line (non-docker) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Requirements ------------ diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 94636c5b99..34b444a34f 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -20,7 +20,7 @@ Version 4.0 * New configuration property ``database.initialiseAppSchema`` with values ``UPDATE``, ``VALIDATE`` and ``NONE``. The property controls the behavior of the Hibernate DDL generation. ``UPDATE`` performs an update of CorDapp schemas, while ``VALIDATE`` only verifies their integrity. The property does not affect the node-specific DDL handling and - complements ``database.initialiseSchema`` to disable DDL handling altogether. + complements ``database.initialiseSchema`` to disable DDL handling altogether. * ``JacksonSupport.createInMemoryMapper`` was incorrectly marked as deprecated and is no longer so. diff --git a/docs/source/cli-application-shell-extensions.rst b/docs/source/cli-application-shell-extensions.rst index ba765d73ed..ea38827818 100644 --- a/docs/source/cli-application-shell-extensions.rst +++ b/docs/source/cli-application-shell-extensions.rst @@ -34,7 +34,7 @@ For example, for the Corda node, install the shell extensions using .. code-block:: shell - java -jar corda-.jar install-shell-extensions + java -jar corda-|corda_version|.jar install-shell-extensions And then run the node by running: @@ -62,15 +62,11 @@ Once the shell extensions have been installed, you can upgrade them in one of tw List of existing CLI applications ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -+----------------------------------------------------------------+--------------------------------------------------------------+--------------------------------+ -| Description | JAR name | Alias | -+----------------------------------------------------------------+--------------------------------------------------------------+--------------------------------+ -| :ref:`Corda node` | ``corda-.jar`` | ``corda --(), envelope.getMangled()) + assertCanLoadAll(testSerializationContext, + envelope.getMangled().mangle(), envelope.getMangled()) } @Test @@ -56,7 +58,8 @@ class CompositeMembers : AmqpCarpenterBase(AllWhitelist) { // We load an unknown class, B_mangled, which includes a reference to an unknown class, A_mangled. // This will fail, because A_mangled is not included in our set of classes to load. - assertFailsWith { assertCanLoadAll(envelope.getMangled().mangle()) } + assertFailsWith { assertCanLoadAll(testSerializationContext, + envelope.getMangled().mangle()) } } // See https://github.com/corda/corda/issues/4107 @@ -71,7 +74,7 @@ class CompositeMembers : AmqpCarpenterBase(AllWhitelist) { val uuid = UUID.randomUUID() val(_, envelope) = IOUStateData(10, uuid, "new value").roundTrip() - val recarpented = envelope.getMangled().load() + val recarpented = envelope.getMangled().load(testSerializationContext) val instance = recarpented.new(null, uuid, 10) assertEquals(uuid, instance.get("ref")) } @@ -90,7 +93,7 @@ class CompositeMembers : AmqpCarpenterBase(AllWhitelist) { "java.util.Map", mangledMap.prettyPrint(false)) - assertCanLoadAll(infoForD, mangledMap, mangledC) + assertCanLoadAll(testSerializationContext, infoForD, mangledMap, mangledC) } @Test @@ -104,6 +107,6 @@ class CompositeMembers : AmqpCarpenterBase(AllWhitelist) { val mangledNotAMap = envelope.typeInformationFor>().mangle() val mangledC = envelope.getMangled() - assertCanLoadAll(infoForD, mangledNotAMap, mangledC) + assertCanLoadAll(testSerializationContext, infoForD, mangledNotAMap, mangledC) } } diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/InheritanceSchemaToClassCarpenterTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/InheritanceSchemaToClassCarpenterTests.kt index 336471c9ac..d066315340 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/InheritanceSchemaToClassCarpenterTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/InheritanceSchemaToClassCarpenterTests.kt @@ -2,6 +2,7 @@ package net.corda.serialization.internal.carpenter import net.corda.core.serialization.CordaSerializable import net.corda.serialization.internal.AllWhitelist +import net.corda.serialization.internal.amqp.testutils.testSerializationContext import org.junit.Test import kotlin.test.* import java.io.NotSerializableException @@ -41,7 +42,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { val (_, env) = A(20).roundTrip() val mangledA = env.getMangled() - val carpentedA = mangledA.load() + val carpentedA = mangledA.load(testSerializationContext) val carpentedInstance = carpentedA.new(20) assertEquals(20, carpentedInstance.get("j")) @@ -52,10 +53,11 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { @Test fun interfaceParent2() { + @Suppress("UNUSED") class A(override val j: Int, val jj: Int) : J val (_, env) = A(23, 42).roundTrip() - val carpentedA = env.getMangled().load() + val carpentedA = env.getMangled().load(testSerializationContext) val carpetedInstance = carpentedA.constructors[0].newInstance(23, 42) assertEquals(23, carpetedInstance.get("j")) @@ -70,7 +72,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { class A(override val i: Int, override val ii: Int) : I, II val (_, env) = A(23, 42).roundTrip() - val carpentedA = env.getMangled().load() + val carpentedA = env.getMangled().load(testSerializationContext) val carpetedInstance = carpentedA.constructors[0].newInstance(23, 42) assertEquals(23, carpetedInstance.get("i")) @@ -88,7 +90,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { class A(override val i: Int, override val iii: Int) : III val (_, env) = A(23, 42).roundTrip() - val carpentedA = env.getMangled().load() + val carpentedA = env.getMangled().load(testSerializationContext) val carpetedInstance = carpentedA.constructors[0].newInstance(23, 42) assertEquals(23, carpetedInstance.get("i")) @@ -108,8 +110,8 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { class B(override val i: I, override val iiii: Int) : IIII val (_, env) = B(A(23), 42).roundTrip() - val carpentedA = env.getMangled().load() - val carpentedB = env.getMangled().load() + val carpentedA = env.getMangled().load(testSerializationContext) + val carpentedB = env.getMangled().load(testSerializationContext) val carpentedAInstance = carpentedA.new(23) val carpentedBInstance = carpentedB.new(carpentedAInstance, 42) @@ -127,7 +129,9 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { // if we remove the nested interface we should get an error as it's impossible // to have a concrete class loaded without having access to all of it's elements - assertFailsWith { assertCanLoadAll(env.getMangled().mangle()) } + assertFailsWith { assertCanLoadAll( + testSerializationContext, + env.getMangled().mangle()) } } @Test @@ -137,7 +141,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { val (_, env) = A(23).roundTrip() // This time around we will succeed, because the mangled I is included in the type information to be loaded. - assertCanLoadAll(env.getMangled().mangle(), env.getMangled()) + assertCanLoadAll(testSerializationContext, env.getMangled().mangle(), env.getMangled()) } @Test @@ -146,6 +150,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { val (_, env) = A(23, 42).roundTrip() assertCanLoadAll( + testSerializationContext, env.getMangled().mangle().mangle(), env.getMangled(), env.getMangled() @@ -158,6 +163,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { val (_, env) = A(23, 42).roundTrip() assertCanLoadAll( + testSerializationContext, env.getMangled().mangle().mangle(), env.getMangled(), env.getMangled().mangle() diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt index f5848a8f3b..7174c6a04a 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt @@ -3,6 +3,7 @@ package net.corda.serialization.internal.carpenter import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializableCalculatedProperty import net.corda.serialization.internal.AllWhitelist +import net.corda.serialization.internal.amqp.testutils.testSerializationContext import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNotEquals @@ -15,7 +16,7 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhi data class A(val a: Int, val b: Long) val (_, env) = A(23, 42).roundTrip() - val carpentedInstance = env.getMangled().load().new(23, 42) + val carpentedInstance = env.getMangled().load(testSerializationContext).new(23, 42) assertEquals(23, carpentedInstance.get("a")) assertEquals(42L, carpentedInstance.get("b")) @@ -27,7 +28,7 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhi data class A(val a: Int, val b: String) val (_, env) = A(23, "skidoo").roundTrip() - val carpentedInstance = env.getMangled().load().new(23, "skidoo") + val carpentedInstance = env.getMangled().load(testSerializationContext).new(23, "skidoo") assertEquals(23, carpentedInstance.get("a")) assertEquals("skidoo", carpentedInstance.get("b")) @@ -57,7 +58,7 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhi squared: String """.trimIndent(), remoteTypeInformation.prettyPrint()) - val pinochio = remoteTypeInformation.mangle().load() + val pinochio = remoteTypeInformation.mangle().load(testSerializationContext) assertNotEquals(pinochio.name, C::class.java.name) assertNotEquals(pinochio, C::class.java) @@ -78,7 +79,7 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhi val (_, env) = C(5).roundTrip() - val pinochio = env.getMangled().load() + val pinochio = env.getMangled().load(testSerializationContext) val p = pinochio.new(5) assertEquals(5, p.get("doubled")) diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/model/ClassCarpentingTypeLoaderTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/model/ClassCarpentingTypeLoaderTests.kt index f7b942ccd1..683241c117 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/model/ClassCarpentingTypeLoaderTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/model/ClassCarpentingTypeLoaderTests.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.google.common.reflect.TypeToken import net.corda.serialization.internal.AllWhitelist import net.corda.serialization.internal.amqp.asClass +import net.corda.serialization.internal.amqp.testutils.testSerializationContext import net.corda.serialization.internal.carpenter.ClassCarpenterImpl import org.junit.Test import java.lang.reflect.Type @@ -12,8 +13,8 @@ import kotlin.test.assertEquals class ClassCarpentingTypeLoaderTests { val carpenter = ClassCarpenterImpl(AllWhitelist) - val remoteTypeCarpenter = SchemaBuildingRemoteTypeCarpenter(carpenter) - val typeLoader = ClassCarpentingTypeLoader(remoteTypeCarpenter, carpenter.classloader) + private val remoteTypeCarpenter = SchemaBuildingRemoteTypeCarpenter(carpenter) + private val typeLoader = ClassCarpentingTypeLoader(remoteTypeCarpenter, carpenter.classloader) @Test fun `carpent some related classes`() { @@ -44,7 +45,9 @@ class ClassCarpentingTypeLoaderTests { "previousAddresses" to listOfAddresses.mandatory ), emptyList(), emptyList()) - val types = typeLoader.load(listOf(personInformation, addressInformation, listOfAddresses)) + val types = typeLoader.load(listOf(personInformation, addressInformation, listOfAddresses), + testSerializationContext) + val addressType = types[addressInformation.typeIdentifier]!! val personType = types[personInformation.typeIdentifier]!! diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.1.A b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.1.A index c28d480977..1ced4449f2 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.1.A and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.1.A differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.1.B b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.1.B index 4f5e9ab81c..bdd9650ce0 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.1.B and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.1.B differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.1.C b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.1.C index 14b41b5506..dfd95cb128 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.1.C and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.1.C differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.1.D b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.1.D index 2ca115091f..325e26b991 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.1.D and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.1.D differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.A b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.A index 434b43c42a..0e1501dd02 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.A and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.A differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.B b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.B index f216584cdf..f63ca46c9c 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.B and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.B differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.C b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.C index 5f1227524d..8a24963b2d 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.C and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.C differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.D b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.D index 71b890bdef..fbfd4f7ab6 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.D and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.D differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.E b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.E index cf15290a05..c0a06856f8 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.E and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.2.E differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.A b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.A index 26cbcc8f4e..1ae51edf84 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.A and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.A differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.B b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.B index 8478191ec9..6c1d4753c4 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.B and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.B differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.BOB b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.BOB index 87a4636599..2657522877 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.BOB and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.BOB differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.C b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.C index 7762efa867..1a020f50ec 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.C and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.C differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.D b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.D index 0bc8084e62..02f11f1457 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.D and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.3.D differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.A b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.A index 40f7c037d2..3cc888db30 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.A and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.A differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.B b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.B index 5d996bfe14..09bae81955 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.B and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.B differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.BOB b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.BOB index b741fceac9..a402d6f0c9 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.BOB and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.BOB differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.CAT b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.CAT index bb229ddc82..b695051404 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.CAT and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.CAT differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.D b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.D index becaee5fa6..7e9a076299 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.D and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.D differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.F b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.F index b9bedc2502..ef5076d501 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.F and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.F differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.G b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.G index bd93f918dc..29b2ea46dc 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.G and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.4.G differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.APPLE b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.APPLE index 95b55e36de..8254ede337 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.APPLE and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.APPLE differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.B b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.B index f6aaf7d2ff..e54393c2ff 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.B and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.B differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.BBB b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.BBB index cd181d1b7c..dfbee9ef29 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.BBB and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.BBB differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.CAT b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.CAT index 27d9d766f7..16cf27d286 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.CAT and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.CAT differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.D b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.D index 54420c13fb..309cd94229 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.D and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.D differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.FLUMP b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.FLUMP index 86b4ed8e6a..a58ea9edb3 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.FLUMP and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.FLUMP differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.G b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.G index 92ed95c4d8..64ef55eef2 100644 Binary files a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.G and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.multiOperations.5.G differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EvolvabilityTests.evolutionWithPrimitives b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EvolvabilityTests.evolutionWithPrimitives new file mode 100644 index 0000000000..7d43194b6b Binary files /dev/null and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EvolvabilityTests.evolutionWithPrimitives differ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/JavaEvolutionTests.testNullableInteger b/serialization/src/test/resources/net/corda/serialization/internal/amqp/JavaEvolutionTests.testNullableInteger new file mode 100644 index 0000000000..4d60a11a60 Binary files /dev/null and b/serialization/src/test/resources/net/corda/serialization/internal/amqp/JavaEvolutionTests.testNullableInteger differ diff --git a/settings.gradle b/settings.gradle index 120b0d866b..a1392c809f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -14,9 +14,7 @@ include 'client:jackson' include 'client:jfx' include 'client:mock' include 'client:rpc' -include 'djvm' include 'docker' -include 'djvm:cli' include 'webserver' include 'webserver:webcapsule' include 'experimental' diff --git a/testing/node-driver/build.gradle b/testing/node-driver/build.gradle index 01de45b827..83853a85a7 100644 --- a/testing/node-driver/build.gradle +++ b/testing/node-driver/build.gradle @@ -37,6 +37,8 @@ dependencies { compile "org.eclipse.jetty:jetty-webapp:${jetty_version}" compile "javax.servlet:javax.servlet-api:3.1.0" + compile "org.gradle:gradle-tooling-api:${gradle.gradleVersion}" + // Jersey for JAX-RS implementation for use in Jetty compile "org.glassfish.jersey.core:jersey-server:${jersey_version}" compile "org.glassfish.jersey.containers:jersey-container-servlet-core:${jersey_version}" diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt index 889cecf5c3..eab3ba4c16 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt @@ -92,7 +92,7 @@ class DriverTests { systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()) )) { val baseDirectory = startNode(providedName = DUMMY_BANK_A_NAME).getOrThrow().baseDirectory - val logFile = (baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).list { it.sorted().findFirst().get() } + val logFile = (baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).list { it.filter { a -> a.isRegularFile() && a.fileName.toString().startsWith("node") }.findFirst().get() } val debugLinesPresent = logFile.readLines { lines -> lines.anyMatch { line -> line.startsWith("[DEBUG]") } } assertThat(debugLinesPresent).isTrue() } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 1b98472fec..4704416d0c 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -118,11 +118,24 @@ abstract class PortAllocation { /** * An implementation of [PortAllocation] which allocates ports sequentially */ - open class Incremental(startingPort: Int) : PortAllocation() { + open class Incremental(private val startingPort: Int) : PortAllocation() { + private companion object { + private const val FIRST_EPHEMERAL_PORT = 49152 + } + /** The backing [AtomicInteger] used to keep track of the currently allocated port */ val portCounter = AtomicInteger(startingPort) - override fun nextPort() = portCounter.andIncrement + override fun nextPort(): Int { + return portCounter.getAndUpdate { i -> + val next = i + 1 + if (next >= FIRST_EPHEMERAL_PORT) { + startingPort + } else { + next + } + } + } } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/internal/GlobalTestPortAllocation.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/internal/GlobalTestPortAllocation.kt index b6736bd5f0..f868bce7ef 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/internal/GlobalTestPortAllocation.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/internal/GlobalTestPortAllocation.kt @@ -12,8 +12,8 @@ fun incrementalPortAllocation(startingPortIfNoEnv: Int): PortAllocation { private object GlobalTestPortAllocation : PortAllocation.Incremental(startingPort = startingPort()) -private const val enablingEnvVar = "CORDA_TEST_GLOBAL_PORT_ALLOCATION_ENABLED" -private const val startingPortEnvVariable = "CORDA_TEST_GLOBAL_PORT_ALLOCATION_STARTING_PORT" +private const val enablingEnvVar = "TESTING_GLOBAL_PORT_ALLOCATION_ENABLED" +private const val startingPortEnvVariable = "TESTING_GLOBAL_PORT_ALLOCATION_STARTING_PORT" private val enablingSystemProperty = enablingEnvVar.toLowerCase().replace("_", ".") private val startingPortSystemProperty = startingPortEnvVariable.toLowerCase().replace("_", ".") private const val startingPortDefaultValue = 5000 diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index 1a0a0a9dff..bda6cbe7c5 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -314,12 +314,10 @@ open class MockServices private constructor( constructor(initialIdentityName: CordaX500Name, identityService: IdentityService = makeTestIdentityService()) : this(listOf(getCallerPackage(MockServices::class)!!), TestIdentity(initialIdentityName), identityService) - @JvmOverloads constructor(cordappPackages: List, initialIdentityName: CordaX500Name, identityService: IdentityService, networkParameters: NetworkParameters) : this(cordappPackages, TestIdentity(initialIdentityName), identityService, networkParameters) - @JvmOverloads constructor(cordappPackages: List, initialIdentityName: CordaX500Name, identityService: IdentityService, networkParameters: NetworkParameters, key: KeyPair) : this(cordappPackages, TestIdentity(initialIdentityName, key), identityService, networkParameters) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index b080857285..14bbe399f8 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -695,7 +695,8 @@ class DriverDSLImpl( Permissions.invokeRpc(CordaRPCOps::internalFindVerifiedTransaction), Permissions.invokeRpc("vaultQueryBy"), Permissions.invokeRpc("vaultTrackBy"), - Permissions.invokeRpc(CordaRPCOps::registeredFlows) + Permissions.invokeRpc(CordaRPCOps::registeredFlows), + Permissions.invokeRpc(CordaRPCOps::killFlow) ) private fun oneOf(array: Array) = array[Random().nextInt(array.size)] diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt index 0190bb2bf4..8c924cd047 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt @@ -4,7 +4,8 @@ import io.github.classgraph.ClassGraph import net.corda.core.internal.* import net.corda.core.utilities.contextLogger import net.corda.testing.node.TestCordapp -import org.apache.commons.lang.SystemUtils +import org.gradle.tooling.GradleConnector +import org.gradle.tooling.ProgressEvent import java.nio.file.Path import java.util.* import java.util.concurrent.ConcurrentHashMap @@ -77,23 +78,31 @@ data class TestCordappImpl(val scanPackage: String, override val config: Map + log.info(event.description) + } } - current = current.parent + // Blocks until the build is complete + build.run() } } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappInternal.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappInternal.kt index 1a14b28000..3380c37b5d 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappInternal.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappInternal.kt @@ -6,8 +6,8 @@ import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.internal.writeText import net.corda.testing.node.TestCordapp +import java.nio.file.FileAlreadyExistsException import java.nio.file.Path -import java.nio.file.StandardCopyOption.REPLACE_EXISTING /** * Extends the public [TestCordapp] API with internal extensions for use within the testing framework and for internal testing of the platform. @@ -36,7 +36,11 @@ abstract class TestCordappInternal : TestCordapp() { val configDir = (cordappsDir / "config").createDirectories() jarToCordapp.forEach { jar, cordapp -> - jar.copyToDirectory(cordappsDir, REPLACE_EXISTING) + try { + jar.copyToDirectory(cordappsDir) + } catch (e: FileAlreadyExistsException) { + // Ignore if the node already has the same CorDapp jar. This can happen if the node is being restarted. + } val configString = ConfigValueFactory.fromMap(cordapp.config).toConfig().root().render() (configDir / "${jar.fileName.toString().removeSuffix(".jar")}.conf").writeText(configString) } diff --git a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt index 7f4034ce0f..0d4bc8af8f 100644 --- a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt +++ b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt @@ -9,6 +9,7 @@ import net.corda.core.node.NotaryInfo import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.DevIdentityGenerator +import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.testing.common.internal.asContextEnv import net.corda.testing.common.internal.checkNotOnClasspath @@ -33,8 +34,7 @@ class NodeProcess( private val log = contextLogger() } - fun connect(): CordaRPCConnection { - val user = config.users[0] + fun connect(user: User): CordaRPCConnection { return client.start(user.username, user.password) } @@ -101,7 +101,7 @@ class NodeProcess( } (nodeDir / "node.conf").writeText(config.toText()) - createNetworkParameters(NotaryInfo(notaryParty!!, false), nodeDir) + createNetworkParameters(NotaryInfo(notaryParty!!, true), nodeDir) val process = startNode(nodeDir) val client = CordaRPCClient(NetworkHostAndPort("localhost", config.rpcPort)) diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/Eventually.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/Eventually.kt new file mode 100644 index 0000000000..7660637fa0 --- /dev/null +++ b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/Eventually.kt @@ -0,0 +1,50 @@ +package net.corda.testing.common.internal + +import java.time.Duration + +/** + * Ideas borrowed from "io.kotlintest" with some improvements made + * This is meant for use from Kotlin code use only mainly due to it's inline/reified nature + * + * @param duration How long to wait for, before returning the last test failure. The default is 5 seconds. + * @param waitBetween How long to wait before retrying the test condition. The default is 1/10th of a second. + * @param waitBefore How long to wait before trying the test condition for the first time. It's assumed that [eventually] + * is being used because the condition is not _immediately_ fulfilled, so this defaults to the value of [waitBetween]. + * @param test A test which should pass within the given [duration]. + * + * @throws AssertionError, if the test does not pass within the given [duration]. + */ +inline fun eventually( + duration: Duration = Duration.ofSeconds(5), + waitBetween: Duration = Duration.ofMillis(100), + waitBefore: Duration = waitBetween, + test: () -> R): R { + val end = System.nanoTime() + duration.toNanos() + var times = 0 + var lastFailure: AssertionError? = null + + if (!waitBefore.isZero) Thread.sleep(waitBefore.toMillis()) + + while (System.nanoTime() < end) { + try { + return test() + } catch (e: AssertionError) { + if (!waitBetween.isZero) Thread.sleep(waitBetween.toMillis()) + lastFailure = e + } + times++ + } + + throw AssertionError("Test failed with \"${lastFailure?.message}\" after $duration; attempted $times times") +} + + +/** + * Use when the action you want to retry until it succeeds throws an exception, rather than failing a test. + */ +inline fun succeeds(action: () -> R): R = + try { + action() + } catch (e: Exception) { + throw AssertionError(e.message) + } \ No newline at end of file diff --git a/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt b/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt index 7a61cc3a28..0c12a2dcd5 100644 --- a/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt +++ b/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt @@ -1,5 +1,6 @@ package net.corda.cliutils +import net.corda.cliutils.ExitCodes import net.corda.core.internal.rootMessage import net.corda.core.utilities.contextLogger import org.fusesource.jansi.AnsiConsole @@ -70,7 +71,7 @@ fun CordaCliWrapper.start(args: Array) { Help.Ansi.AUTO } val results = cmd.parseWithHandlers(RunLast().useOut(System.out).useAnsi(defaultAnsiMode), - DefaultExceptionHandler>().useErr(System.err).useAnsi(defaultAnsiMode), + DefaultExceptionHandler>().useErr(System.err).useAnsi(defaultAnsiMode).andExit(ExitCodes.FAILURE), *args) // If an error code has been returned, use this and exit results?.firstOrNull()?.let { @@ -123,12 +124,13 @@ abstract class CliWrapperBase(val alias: String, val description: String) : Call // This needs to be called before loggers (See: NodeStartup.kt:51 logger called by lazy, initLogging happens before). // Node's logging is more rich. In corda configurations two properties, defaultLoggingLevel and consoleLogLevel, are usually used. - open fun initLogging() { + open fun initLogging(): Boolean { System.setProperty("defaultLogLevel", specifiedLogLevel) // These properties are referenced from the XML config file. if (verbose) { System.setProperty("consoleLogLevel", specifiedLogLevel) } System.setProperty("log-path", Paths.get(".").toString()) + return true } // Override this function with the actual method to be run once all the arguments have been parsed. The return number @@ -141,7 +143,9 @@ abstract class CliWrapperBase(val alias: String, val description: String) : Call return runProgram() } - val specifiedLogLevel: String by lazy { System.getProperty("log4j2.level")?.toLowerCase(Locale.ENGLISH) ?: loggingLevel.name.toLowerCase(Locale.ENGLISH) } + val specifiedLogLevel: String by lazy { + System.getProperty("log4j2.level")?.toLowerCase(Locale.ENGLISH) ?: loggingLevel.name.toLowerCase(Locale.ENGLISH) + } } /** @@ -178,20 +182,20 @@ abstract class CordaCliWrapper(alias: String, description: String) : CliWrapperB } override fun call(): Int { - initLogging() + if (!initLogging()) { + return ExitCodes.FAILURE + } logger.info("Application Args: ${args.joinToString(" ")}") installShellExtensionsParser.updateShellExtensions() return runProgram() } fun printHelp() = cmd.usage(System.out) - } fun printWarning(message: String) = System.err.println("${ShellConstants.YELLOW}$message${ShellConstants.RESET}") fun printError(message: String) = System.err.println("${ShellConstants.RED}$message${ShellConstants.RESET}") - /** * Useful commonly used constants applicable to many CLI tools */ diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/plugin/CordappController.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/plugin/CordappController.kt index 5748eb9e27..69b4fbdfc0 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/plugin/CordappController.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/plugin/CordappController.kt @@ -8,10 +8,10 @@ import net.corda.demobench.model.NodeConfigWrapper import tornadofx.* import java.io.IOException import java.nio.file.Path -import java.nio.file.StandardCopyOption +import java.nio.file.StandardCopyOption.* import kotlin.streams.toList -// TODO This class needs to be revisted. It seems to operate on outdated concepts. +// TODO This class needs to be revisited. It seems to operate on outdated concepts. class CordappController : Controller() { companion object { const val FINANCE_CONTRACTS_CORDAPP_FILENAME = "corda-finance-contracts" @@ -20,10 +20,8 @@ class CordappController : Controller() { private val jvm by inject() private val cordappDir: Path = jvm.applicationDir / NodeConfig.CORDAPP_DIR_NAME - private val cordappJars = setOf( - cordappDir / "$FINANCE_CONTRACTS_CORDAPP_FILENAME.jar", - cordappDir / "$FINANCE_WORKFLOWS_CORDAPP_FILENAME.jar" - ) + private val cordappJars = setOf(FINANCE_CONTRACTS_CORDAPP_FILENAME, FINANCE_WORKFLOWS_CORDAPP_FILENAME) + .map { cordappDir / "$it.jar" } /** * Install any built-in cordapps that this node requires. @@ -33,10 +31,10 @@ class CordappController : Controller() { if (!config.cordappsDir.exists()) { config.cordappsDir.createDirectories() } - cordappJars.forEach { financeCordappJar -> - if (financeCordappJar.exists()) { - financeCordappJar.copyToDirectory(config.cordappsDir, StandardCopyOption.REPLACE_EXISTING) - log.info("Installed 'Finance' cordapp: $financeCordappJar") + cordappJars.forEach { cordappJar -> + if (cordappJar.exists()) { + cordappJar.copyToDirectory(config.cordappsDir, REPLACE_EXISTING) + log.info("Installed cordapp: $cordappJar") } } } @@ -55,5 +53,5 @@ class CordappController : Controller() { } } -val Path.isCordapp: Boolean get() = this.isReadable && this.fileName.toString().endsWith(".jar") -val Path.inCordappsDir: Boolean get() = (this.parent != null) && this.parent.endsWith("cordapps/") +val Path.isCordapp: Boolean get() = isReadable && fileName.toString().endsWith(".jar") +val Path.inCordappsDir: Boolean get() = (parent != null) && parent.endsWith("cordapps/") diff --git a/tools/explorer/README.md b/tools/explorer/README.md index d6c6cbfcd7..f6f54a2756 100644 --- a/tools/explorer/README.md +++ b/tools/explorer/README.md @@ -13,36 +13,19 @@ The user can execute cash transaction commands to issue and move cash to other p ./gradlew tools:explorer:run - ## Running Demo Nodes -A demonstration Corda network topology is configured with 5 nodes playing the following roles: -1. Notary -2. Issuer nodes (representing two fictional central banks - UK Bank Plc issuer of GBP and USA Bank Corp issuer of USD) -3. Participant nodes (representing two users - Alice and Bob) +Node Explorer is included with the [DemoBench](https://docs.corda.net/demobench.html) application, +which allows you to create local Corda networks on your desktop. For example: -The Issuer nodes have the ability to issue, move and exit cash amounts. -The Participant nodes are only able to spend cash (eg. move cash). + * Notary + * Bank of Breakfast Tea (*Issuer node* for GBP) + * Bank of Big Apples (*Issuer node* for USD) + * Alice (*Participant node* for user Alice) + * Bob (*Participant node* for user Bob) -**Windows:** - - gradlew.bat tools:explorer:runDemoNodes - -**Other:** - - ./gradlew tools:explorer:runDemoNodes - -**These Corda nodes will be created on the following port on localhost.** - - * Notary -> 20005 (Does not accept logins) - * UK Bank Plc -> 20011 (*Issuer node*) - * USA Bank Corp -> 20008 (*Issuer node*) - * Alice -> 20017 - * Bob -> 20014 - -Explorer login credentials to the Issuer nodes are defaulted to ``manager`` and ``test``. -Explorer login credentials to the Participants nodes are defaulted to ``user1`` and ``test``. -Please note you are not allowed to login to the notary. +DemoBench will deploy all nodes with Corda's Finance CorDapp automatically, and allow you to launch an +instance of Node Explorer for each. ## TODOs: - Shows more useful information in the dashboard. diff --git a/tools/explorer/build.gradle b/tools/explorer/build.gradle index 612fea229c..8f6f77bfa4 100644 --- a/tools/explorer/build.gradle +++ b/tools/explorer/build.gradle @@ -5,12 +5,6 @@ apply plugin: 'application' sourceCompatibility = 1.8 mainClassName = 'net.corda.explorer.Main' -// Use manual resource copying of log4j2.xml rather than source sets. -// This prevents problems in IntelliJ with regard to duplicate source roots. -processResources { - from file("$rootDir/config/dev/log4j2.xml") -} - dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" @@ -22,12 +16,13 @@ dependencies { // Corda Core: Data structures and basic types needed to work with Corda. compile project(':core') compile project(':client:jfx') - compile project(':client:mock') - compile project(':node-driver') compile project(':finance:contracts') compile project(':finance:workflows') compile project(':tools:worldmap') + // Log4J: logging framework (with SLF4J bindings) + compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" + // Capsule is a library for building independently executable fat JARs. // We only need this dependency to compile our Caplet against. compileOnly "co.paralleluniverse:capsule:$capsule_version" @@ -60,21 +55,10 @@ tasks.withType(JavaCompile) { options.compilerArgs << '-proc:none' } -task runDemoNodes(dependsOn: 'classes', type: JavaExec) { - main = 'net.corda.explorer.MainKt' - classpath = sourceSets.main.runtimeClasspath -} - -task runSimulationNodes(dependsOn: 'classes', type: JavaExec) { - main = 'net.corda.explorer.MainKt' - classpath = sourceSets.main.runtimeClasspath - args '-S' -} - jar { manifest { attributes( - 'Automatic-Module-Name': 'net.corda.tools.explorer' + 'Automatic-Module-Name': 'net.corda.tools.explorer' ) } } \ No newline at end of file diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt deleted file mode 100644 index 76c4ab0523..0000000000 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt +++ /dev/null @@ -1,212 +0,0 @@ -package net.corda.explorer - -import joptsimple.OptionSet -import net.corda.client.mock.ErrorFlowsEventGenerator -import net.corda.client.mock.EventGenerator -import net.corda.client.mock.Generator -import net.corda.client.rpc.CordaRPCClient -import net.corda.client.rpc.CordaRPCConnection -import net.corda.core.contracts.Amount -import net.corda.core.identity.CordaX500Name -import net.corda.core.identity.Party -import net.corda.core.internal.concurrent.thenMatch -import net.corda.core.messaging.CordaRPCOps -import net.corda.core.messaging.FlowHandle -import net.corda.core.messaging.startFlow -import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.getOrThrow -import net.corda.finance.GBP -import net.corda.finance.USD -import net.corda.finance.contracts.asset.Cash -import net.corda.finance.flows.AbstractCashFlow -import net.corda.finance.flows.CashExitFlow -import net.corda.finance.flows.CashExitFlow.ExitRequest -import net.corda.finance.flows.CashIssueAndPaymentFlow -import net.corda.finance.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest -import net.corda.finance.flows.CashPaymentFlow -import net.corda.finance.internal.CashConfigDataFlow -import net.corda.node.services.Permissions.Companion.startFlow -import net.corda.testing.core.ALICE_NAME -import net.corda.testing.core.BOB_NAME -import net.corda.testing.driver.* -import net.corda.testing.driver.internal.incrementalPortAllocation -import net.corda.testing.node.User -import net.corda.testing.node.internal.FINANCE_CORDAPPS -import net.corda.testing.node.internal.FINANCE_WORKFLOWS_CORDAPP -import java.time.Instant -import java.util.* - -class ExplorerSimulation(private val options: OptionSet) { - private val user = User("user1", "test", permissions = setOf( - startFlow(), - startFlow() - )) - private val manager = User("manager", "test", permissions = setOf( - startFlow(), - startFlow(), - startFlow(), - startFlow()) - ) - - private lateinit var notaryNode: NodeHandle - private lateinit var aliceNode: NodeHandle - private lateinit var bobNode: NodeHandle - private lateinit var issuerNodeGBP: NodeHandle - private lateinit var issuerNodeUSD: NodeHandle - private lateinit var notary: Party - - private val RPCConnections = ArrayList() - private val issuers = HashMap() - private val parties = ArrayList>() - - init { - startDemoNodes() - } - - private fun onEnd() { - println("Closing RPC connections") - RPCConnections.forEach { it.close() } - } - - private fun startDemoNodes() { - val portAllocation = incrementalPortAllocation(20000) - driver(DriverParameters( - portAllocation = portAllocation, - cordappsForAllNodes = FINANCE_CORDAPPS, - waitForAllNodesToFinish = true, - jmxPolicy = JmxPolicy.defaultEnabled() - )) { - // TODO : Supported flow should be exposed somehow from the node instead of set of ServiceInfo. - val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)) - val bob = startNode(providedName = BOB_NAME, rpcUsers = listOf(user)) - val ukBankName = CordaX500Name(organisation = "UK Bank Plc", locality = "London", country = "GB") - val usaBankName = CordaX500Name(organisation = "USA Bank Corp", locality = "New York", country = "US") - val issuerGBP = startNode(NodeParameters( - providedName = ukBankName, - rpcUsers = listOf(manager), - additionalCordapps = listOf(FINANCE_WORKFLOWS_CORDAPP.copy(config = mapOf("issuableCurrencies" to listOf("GBP")))) - )) - val issuerUSD = startNode(NodeParameters( - providedName = usaBankName, - rpcUsers = listOf(manager), - additionalCordapps = listOf(FINANCE_WORKFLOWS_CORDAPP.copy(config = mapOf("issuableCurrencies" to listOf("USD")))) - )) - - notaryNode = defaultNotaryNode.get() - aliceNode = alice.get() - bobNode = bob.get() - issuerNodeGBP = issuerGBP.get() - issuerNodeUSD = issuerUSD.get() - - arrayOf(notaryNode, aliceNode, bobNode, issuerNodeGBP, issuerNodeUSD).forEach { - println("${it.nodeInfo.legalIdentities.first()} started on ${it.rpcAddress}") - } - - when { - options.has("S") -> startNormalSimulation() - options.has("F") -> startErrorFlowsSimulation() - } - } - } - - private fun setUpRPC() { - // Register with alice to use alice's RPC proxy to create random events. - val aliceClient = CordaRPCClient(aliceNode.rpcAddress) - val aliceConnection = aliceClient.start(user.username, user.password) - val aliceRPC = aliceConnection.proxy - - val bobClient = CordaRPCClient(bobNode.rpcAddress) - val bobConnection = bobClient.start(user.username, user.password) - val bobRPC = bobConnection.proxy - - val issuerClientGBP = CordaRPCClient(issuerNodeGBP.rpcAddress) - val issuerGBPConnection = issuerClientGBP.start(manager.username, manager.password) - val issuerRPCGBP = issuerGBPConnection.proxy - - val issuerClientUSD = CordaRPCClient(issuerNodeUSD.rpcAddress) - val issuerUSDConnection = issuerClientUSD.start(manager.username, manager.password) - val issuerRPCUSD = issuerUSDConnection.proxy - - RPCConnections.addAll(listOf(aliceConnection, bobConnection, issuerGBPConnection, issuerUSDConnection)) - issuers.putAll(mapOf(USD to issuerRPCUSD, GBP to issuerRPCGBP)) - - parties.addAll(listOf(aliceNode.nodeInfo.legalIdentities.first() to aliceRPC, - bobNode.nodeInfo.legalIdentities.first() to bobRPC, - issuerNodeGBP.nodeInfo.legalIdentities.first() to issuerRPCGBP, - issuerNodeUSD.nodeInfo.legalIdentities.first() to issuerRPCUSD)) - } - - private fun startSimulation(eventGenerator: EventGenerator, maxIterations: Int) { - // Log to logger when flow finish. - fun FlowHandle.log(seq: Int, name: String) { - val out = "[$seq] $name $id :" - returnValue.thenMatch({ (stx) -> - Main.log.info("$out ${stx.id} ${(stx.tx.outputs.first().data as Cash.State).amount}") // XXX: Why Main's log? - }, { - Main.log.info("$out ${it.message}") - }) - } - - for (i in 0..maxIterations) { - Thread.sleep(1000) - // Issuer requests. - eventGenerator.issuerGenerator.map { request -> - when (request) { - is IssueAndPaymentRequest -> issuers[request.amount.token]?.let { - println("${Instant.now()} [$i] ISSUING ${request.amount} with ref ${request.issueRef} to ${request.recipient}") - it.startFlow(::CashIssueAndPaymentFlow, request).log(i, "${request.amount.token}Issuer") - } - is ExitRequest -> issuers[request.amount.token]?.let { - println("${Instant.now()} [$i] EXITING ${request.amount} with ref ${request.issuerRef}") - it.startFlow(::CashExitFlow, request).log(i, "${request.amount.token}Exit") - } - else -> throw IllegalArgumentException("Unsupported command: $request") - } - }.generate(SplittableRandom()) - // Party pay requests. - eventGenerator.moveCashGenerator.combine(Generator.pickOne(parties)) { request, (party, rpc) -> - println("${Instant.now()} [$i] SENDING ${request.amount} from $party to ${request.recipient}") - rpc.startFlow(::CashPaymentFlow, request).log(i, party.name.toString()) - }.generate(SplittableRandom()) - } - println("Simulation completed") - } - - private fun startNormalSimulation() { - println("Running simulation mode ...") - setUpRPC() - notary = aliceNode.rpc.notaryIdentities().first() - val eventGenerator = EventGenerator( - parties = parties.map { it.first }, - notary = notary, - currencies = listOf(GBP, USD) - ) - val maxIterations = 100_000 - val anonymous = true - // Pre allocate some money to each party. - eventGenerator.parties.forEach { - for (ref in 0..1) { - for ((currency, issuer) in issuers) { - val amount = Amount(1_000_000, currency) - issuer.startFlow(::CashIssueAndPaymentFlow, amount, OpaqueBytes.of( ref.toByte() ), - it, anonymous, notary).returnValue.getOrThrow() - } - } - } - startSimulation(eventGenerator, maxIterations) - onEnd() - } - - private fun startErrorFlowsSimulation() { - println("Running flows with errors simulation mode ...") - setUpRPC() - val eventGenerator = ErrorFlowsEventGenerator( - parties = parties.map { it.first }, - notary = notary, - currencies = listOf(GBP, USD) - ) - val maxIterations = 10_000 - startSimulation(eventGenerator, maxIterations) - onEnd() - } -} diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt index 9fbc3199fc..00494b8827 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt @@ -8,11 +8,9 @@ import javafx.scene.control.ButtonType import javafx.scene.image.Image import javafx.stage.Stage import jfxtras.resources.JFXtrasFontRoboto -import joptsimple.OptionParser import net.corda.client.jfx.model.Models import net.corda.client.jfx.model.NodeMonitorModel import net.corda.client.jfx.model.observableValue -import net.corda.core.utilities.contextLogger import net.corda.explorer.model.CordaViewModel import net.corda.explorer.model.SettingsModel import net.corda.explorer.views.* @@ -31,10 +29,6 @@ class Main : App(MainView::class) { private val loginView by inject() private val fullscreen by observableValue(SettingsModel::fullscreenProperty) - companion object { - internal val log = contextLogger() - } - override fun start(stage: Stage) { var nodeModel: NodeMonitorModel? = null @@ -122,16 +116,3 @@ class Main : App(MainView::class) { FontAwesomeIconFactory.get() // Force initialisation. } } - -/** - * This main method will start 5 nodes (Notary, USA Bank, UK Bank, Bob and Alice) locally for UI testing, - * which will bind to ports 20005, 20008, 20011, 20014 and 20017 locally. - * - * The simulation starts by pre-allocating chunks of cash to each of the parties in 2 currencies (USD, GBP), then it enters a loop which generates random events. - * On each iteration, the issuers will execute a Cash Issue or Cash Exit flow (at a 9:1 ratio) and a random party will execute a move of cash to another random party. - */ -fun main(args: Array) { - val parser = OptionParser("SF") - val options = parser.parse(*args) - ExplorerSimulation(options) -} diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/views/cordapps/cash/NewTransaction.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/views/cordapps/cash/NewTransaction.kt index f980b0fceb..62a261af27 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/views/cordapps/cash/NewTransaction.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/views/cordapps/cash/NewTransaction.kt @@ -39,7 +39,6 @@ import net.corda.finance.flows.CashIssueAndPaymentFlow import net.corda.finance.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest import net.corda.finance.flows.CashPaymentFlow import net.corda.finance.flows.CashPaymentFlow.PaymentRequest -import net.corda.testing.core.singleIdentityAndCert import org.controlsfx.dialog.ExceptionDialog import tornadofx.* import java.math.BigDecimal @@ -183,7 +182,7 @@ class NewTransaction : Fragment() { partyBLabel.textProperty().bind(transactionTypeCB.valueProperty().map { it?.partyNameB?.let { "$it : " } }) partyBChoiceBox.apply { visibleProperty().bind(transactionTypeCB.valueProperty().map { it?.partyNameB }.isNotNull()) - items = FXCollections.observableList(parties.map { it.singleIdentityAndCert() }).sorted() + items = FXCollections.observableList(parties.map { it.legalIdentitiesAndCerts.single() }).sorted() converter = stringConverter { it?.let { PartyNameFormatter.short.format(it.name) } ?: "" } } // Issuer diff --git a/tools/shell-cli/src/main/kotlin/net/corda/tools/shell/StandaloneShell.kt b/tools/shell-cli/src/main/kotlin/net/corda/tools/shell/StandaloneShell.kt index 8d40c858ea..aa2016aef6 100644 --- a/tools/shell-cli/src/main/kotlin/net/corda/tools/shell/StandaloneShell.kt +++ b/tools/shell-cli/src/main/kotlin/net/corda/tools/shell/StandaloneShell.kt @@ -56,10 +56,11 @@ class StandaloneShell : CordaCliWrapper("corda-shell", "The Corda standalone she private fun getManifestEntry(key: String) = if (Manifests.exists(key)) Manifests.read(key) else "Unknown" - override fun initLogging() { + override fun initLogging() : Boolean { super.initLogging() SLF4JBridgeHandler.removeHandlersForRootLogger() // The default j.u.l config adds a ConsoleHandler. SLF4JBridgeHandler.install() + return true } override fun runProgram(): Int { diff --git a/tools/shell/build.gradle b/tools/shell/build.gradle index 36ee942432..8fef472421 100644 --- a/tools/shell/build.gradle +++ b/tools/shell/build.gradle @@ -22,11 +22,6 @@ sourceSets { srcDir file('src/integration-test/resources') } } - test { - resources { - srcDir file('src/test/resources') - } - } } dependencies { diff --git a/tools/shell/src/main/java/net/corda/tools/shell/FlowShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/FlowShellCommand.java index 53bb921d3d..c5bbffe9e4 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/FlowShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/FlowShellCommand.java @@ -15,12 +15,12 @@ import org.slf4j.LoggerFactory; import java.util.*; -import static java.util.stream.Collectors.joining; +import static net.corda.tools.shell.InteractiveShell.killFlowById; import static net.corda.tools.shell.InteractiveShell.runFlowByNameFragment; import static net.corda.tools.shell.InteractiveShell.runStateMachinesView; @Man( - "Allows you to start flows, list the ones available and to watch flows currently running on the node.\n\n" + + "Allows you to start and kill flows, list the ones available and to watch flows currently running on the node.\n\n" + "Starting flow is the primary way in which you command the node to change the ledger.\n\n" + "This command is generic, so the right way to use it depends on the flow you wish to start. You can use the 'flow start'\n" + "command with either a full class name, or a substring of the class name that's unambiguous. The parameters to the \n" + @@ -28,7 +28,7 @@ import static net.corda.tools.shell.InteractiveShell.runStateMachinesView; ) public class FlowShellCommand extends InteractiveShellCommand { - private static Logger logger = LoggerFactory.getLogger(FlowShellCommand.class); + private static final Logger logger = LoggerFactory.getLogger(FlowShellCommand.class); @Command @Usage("Start a (work)flow on the node. This is how you can change the ledger.") @@ -36,13 +36,13 @@ public class FlowShellCommand extends InteractiveShellCommand { @Usage("The class name of the flow to run, or an unambiguous substring") @Argument String name, @Usage("The data to pass as input") @Argument(unquote = false) List input ) { - logger.info("Executing command \"flow start {} {}\",", name, (input != null) ? input.stream().collect(joining(" ")) : ""); + logger.info("Executing command \"flow start {} {}\",", name, (input != null) ? String.join(" ", input) : ""); startFlow(name, input, out, ops(), ansiProgressRenderer(), objectMapper(null)); } // TODO Limit number of flows shown option? @Command - @Usage("watch information about state machines running on the node with result information") + @Usage("Watch information about state machines running on the node with result information.") public void watch(InvocationContext context) throws Exception { logger.info("Executing command \"flow watch\"."); runStateMachinesView(out, ops()); @@ -63,11 +63,20 @@ public class FlowShellCommand extends InteractiveShellCommand { } @Command - @Usage("list flows that user can start") + @Usage("List flows that user can start.") public void list(InvocationContext context) throws Exception { logger.info("Executing command \"flow list\"."); for (String name : ops().registeredFlows()) { context.provide(name + System.lineSeparator()); } } + + @Command + @Usage("Kill a flow that is running on this node.") + public void kill( + @Usage("The UUID for the flow that we wish to kill") @Argument String id + ) { + logger.info("Executing command \"flow kill {}\".", id); + killFlowById(id, out, ops(), objectMapper(null)); + } } \ No newline at end of file diff --git a/tools/shell/src/main/java/net/corda/tools/shell/RunShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/RunShellCommand.java index 87f471092e..c5ac48399b 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/RunShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/RunShellCommand.java @@ -1,6 +1,5 @@ package net.corda.tools.shell; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import net.corda.client.jackson.StringToMethodCallParser; @@ -20,14 +19,13 @@ import java.util.Map; import java.util.Set; import static java.util.Comparator.comparing; -import static java.util.stream.Collectors.joining; // Note that this class cannot be converted to Kotlin because CRaSH does not understand InvocationContext> which // is the closest you can get in Kotlin to raw types. public class RunShellCommand extends InteractiveShellCommand { - private static Logger logger = LoggerFactory.getLogger(RunShellCommand.class); + private static final Logger logger = LoggerFactory.getLogger(RunShellCommand.class); @Command @Man( @@ -39,7 +37,7 @@ public class RunShellCommand extends InteractiveShellCommand { @Usage("runs a method from the CordaRPCOps interface on the node.") public Object main(InvocationContext context, @Usage("The command to run") @Argument(unquote = false) List command) { - logger.info("Executing command \"run {}\",", (command != null) ? command.stream().collect(joining(" ")) : ""); + logger.info("Executing command \"run {}\",", (command != null) ? String.join(" ", command) : ""); StringToMethodCallParser parser = new StringToMethodCallParser<>(CordaRPCOps.class, objectMapper(InteractiveShell.getCordappsClassloader())); if (command == null) { diff --git a/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt b/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt index bff6a27ece..86444f179f 100644 --- a/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt +++ b/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt @@ -1,6 +1,7 @@ package net.corda.tools.shell import com.fasterxml.jackson.core.JsonFactory +import com.fasterxml.jackson.databind.JsonMappingException import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.databind.module.SimpleModule @@ -16,6 +17,7 @@ import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.UniqueIdentifier import net.corda.core.flows.FlowLogic +import net.corda.core.flows.StateMachineRunId import net.corda.core.internal.* import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.openFuture @@ -348,6 +350,30 @@ object InteractiveShell { return innerLoop(type) } + @JvmStatic + fun killFlowById(id: String, + output: RenderPrintWriter, + rpcOps: CordaRPCOps, + inputObjectMapper: ObjectMapper = createYamlInputMapper(rpcOps)) { + try { + val runId = try { + inputObjectMapper.readValue(id, StateMachineRunId::class.java) + } catch (e: JsonMappingException) { + output.println("Cannot parse flow ID of '$id' - expecting a UUID.", Color.red) + log.error("Failed to parse flow ID", e) + return + } + + if (rpcOps.killFlow(runId)) { + output.println("Killed flow $runId", Color.yellow) + } else { + output.println("Failed to kill flow $runId", Color.red) + } + } finally { + output.flush() + } + } + // TODO: This utility is generally useful and might be better moved to the node class, or an RPC, if we can commit to making it stable API. /** * Given a [FlowLogic] class and a string in one-line Yaml form, finds an applicable constructor and starts diff --git a/tools/shell/src/main/kotlin/net/corda/tools/shell/utlities/ANSIProgressRenderer.kt b/tools/shell/src/main/kotlin/net/corda/tools/shell/utlities/ANSIProgressRenderer.kt index 57ccf776da..6332566073 100644 --- a/tools/shell/src/main/kotlin/net/corda/tools/shell/utlities/ANSIProgressRenderer.kt +++ b/tools/shell/src/main/kotlin/net/corda/tools/shell/utlities/ANSIProgressRenderer.kt @@ -16,22 +16,23 @@ import org.fusesource.jansi.Ansi import org.fusesource.jansi.Ansi.Attribute import org.fusesource.jansi.AnsiConsole import org.fusesource.jansi.AnsiOutputStream +import rx.Observable.combineLatest import rx.Subscription +import java.util.* import java.util.stream.IntStream import kotlin.streams.toList abstract class ANSIProgressRenderer { - private var subscriptionIndex: Subscription? = null - private var subscriptionTree: Subscription? = null + private var updatesSubscription: Subscription? = null protected var usingANSI = false protected var checkEmoji = false private val usingUnicode = !SystemUtils.IS_OS_WINDOWS - protected var treeIndex: Int = 0 - protected var treeIndexProcessed: MutableSet = mutableSetOf() - protected var tree: List> = listOf() + private var treeIndex: Int = 0 + private var treeIndexProcessed: MutableSet = mutableSetOf() + protected var tree: List = listOf() private var installedYet = false @@ -42,15 +43,18 @@ abstract class ANSIProgressRenderer { // prevLinesDraw is just for ANSI mode. protected var prevLinesDrawn = 0 + data class ProgressStep(val level: Int, val description: String, val parentIndex: Int?) + data class InputTreeStep(val level: Int, val description: String) + private fun done(error: Throwable?) { - if (error == null) _render(null) + if (error == null) renderInternal(null) draw(true, error) onDone() } fun render(flowProgressHandle: FlowProgressHandle<*>, onDone: () -> Unit = {}) { this.onDone = onDone - _render(flowProgressHandle) + renderInternal(flowProgressHandle) } protected abstract fun printLine(line:String) @@ -59,9 +63,8 @@ abstract class ANSIProgressRenderer { protected abstract fun setup() - private fun _render(flowProgressHandle: FlowProgressHandle<*>?) { - subscriptionIndex?.unsubscribe() - subscriptionTree?.unsubscribe() + private fun renderInternal(flowProgressHandle: FlowProgressHandle<*>?) { + updatesSubscription?.unsubscribe() treeIndex = 0 treeIndexProcessed.clear() tree = listOf() @@ -75,29 +78,64 @@ abstract class ANSIProgressRenderer { prevLinesDrawn = 0 draw(true) + val treeUpdates = flowProgressHandle?.stepsTreeFeed?.updates + val indexUpdates = flowProgressHandle?.stepsTreeIndexFeed?.updates - flowProgressHandle?.apply { - stepsTreeIndexFeed?.apply { - treeIndex = snapshot - treeIndexProcessed.add(snapshot) - subscriptionIndex = updates.subscribe({ - treeIndex = it - treeIndexProcessed.add(it) + if (treeUpdates == null || indexUpdates == null) { + renderInBold("Cannot print progress for this flow as the required data is missing", Ansi()) + } else { + // By combining the two observables, a race condition where both emit items at roughly the same time is avoided. This could + // result in steps being incorrectly marked as skipped. Instead, whenever either observable emits an item, a pair of the + // last index and last tree is returned, which ensures that updates to either are processed in series. + updatesSubscription = combineLatest(treeUpdates, indexUpdates) { tree, index -> Pair(tree, index) }.subscribe( + { + val newTree = transformTree(it.first.map { elem -> InputTreeStep(elem.first, elem.second) }) + // Process indices first, as if the tree has changed the associated index with this update is for the old tree. Note + // that the one case where this isn't true is the very first update, but in this case the index should be 0 (as this + // update is for the initial state). The remapping on a new tree assumes the step at index 0 is always at least current, + // so this case is handled there. + treeIndex = it.second + treeIndexProcessed.add(it.second) + if (newTree != tree) { + remapIndices(newTree) + tree = newTree + } draw(true) - }, { done(it) }, { done(null) }) - } - stepsTreeFeed?.apply { - tree = snapshot - subscriptionTree = updates.subscribe({ - remapIndices(it) - tree = it - draw(true) - }, { done(it) }, { done(null) }) - } + }, + { done(it) }, + { done(null) } + ) } } - private fun remapIndices(newTree: List>) { + // Create a new tree of steps that also holds a reference to the parent of each step. This is required to uniquely identify each step + // (assuming that each step label is unique at a given level). + private fun transformTree(inputTree: List): List { + if (inputTree.isEmpty()) { + return listOf() + } + val stack = Stack>() + stack.push(Pair(0, inputTree[0])) + return inputTree.mapIndexed { index, step -> + val parentIndex = try { + val top = stack.peek() + val levelDifference = top.second.level - step.level + if (levelDifference >= 0) { + // The top of the stack is at the same or lower level than the current step. Remove items from the top until the topmost + // item is at a higher level - this is the parent step. + repeat(levelDifference + 1) { stack.pop() } + } + stack.peek().first + } catch (e: EmptyStackException) { + // If there is nothing on the stack at any point, it implies that this step is at the top level and has no parent. + null + } + stack.push(Pair(index, step)) + ProgressStep(step.level, step.description, parentIndex) + } + } + + private fun remapIndices(newTree: List) { val newIndices = newTree.filter { treeIndexProcessed.contains(tree.indexOf(it)) }.map { @@ -110,7 +148,7 @@ abstract class ANSIProgressRenderer { @Synchronized protected fun draw(moveUp: Boolean, error: Throwable? = null) { if (!usingANSI) { - val currentMessage = tree.getOrNull(treeIndex)?.second + val currentMessage = tree.getOrNull(treeIndex)?.description if (currentMessage != null && currentMessage != prevMessagePrinted) { printLine(currentMessage) prevMessagePrinted = currentMessage @@ -184,13 +222,13 @@ abstract class ANSIProgressRenderer { error -> if (usingUnicode) "${Emoji.noEntry} " else "ERROR: " else -> " " // Not reached yet. } - a(" ".repeat(step.first)) + a(" ".repeat(step.level)) a(marker) when { - activeStep -> renderInBold(step.second, ansi) - skippedStep -> renderInFaint(step.second, ansi) - else -> a(step.second) + activeStep -> renderInBold(step.description, ansi) + skippedStep -> renderInFaint(step.description, ansi) + else -> a(step.description) } eraseLine(Ansi.Erase.FORWARD) diff --git a/tools/shell/src/test/kotlin/net/corda/tools/shell/InteractiveShellTest.kt b/tools/shell/src/test/kotlin/net/corda/tools/shell/InteractiveShellTest.kt index 045e3add54..df52553a74 100644 --- a/tools/shell/src/test/kotlin/net/corda/tools/shell/InteractiveShellTest.kt +++ b/tools/shell/src/test/kotlin/net/corda/tools/shell/InteractiveShellTest.kt @@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.type.TypeFactory import com.fasterxml.jackson.dataformat.yaml.YAMLFactory -import com.nhaarman.mockito_kotlin.eq +import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.verify import com.nhaarman.mockito_kotlin.whenever @@ -30,6 +30,7 @@ import net.corda.testing.core.TestIdentity import net.corda.testing.core.getTestPartyAndCertificate import net.corda.testing.internal.DEV_ROOT_CA import org.crsh.command.InvocationContext +import org.crsh.text.Color import org.crsh.text.RenderPrintWriter import org.junit.Before import org.junit.Test @@ -128,7 +129,7 @@ class InteractiveShellTest { } private fun objectMapperWithClassLoader(classLoader: ClassLoader?): ObjectMapper { - val objectMapper = ObjectMapper() + val objectMapper = JacksonSupport.createNonRpcMapper() val tf = TypeFactory.defaultInstance().withClassLoader(classLoader) objectMapper.typeFactory = tf @@ -231,6 +232,34 @@ class InteractiveShellTest { verify(printWriter).println(NETWORK_MAP_JSON_PAYLOAD.replace("\n", System.lineSeparator())) } + @Test + fun killFlowWithNonsenseID() { + InteractiveShell.killFlowById("nonsense", printWriter, cordaRpcOps, om) + verify(printWriter).println("Cannot parse flow ID of 'nonsense' - expecting a UUID.", Color.red) + verify(printWriter).flush() + } + + @Test + fun killFlowFailure() { + val runId = StateMachineRunId.createRandom() + whenever(cordaRpcOps.killFlow(any())).thenReturn(false) + + InteractiveShell.killFlowById(runId.uuid.toString(), printWriter, cordaRpcOps, om) + verify(cordaRpcOps).killFlow(runId) + verify(printWriter).println("Failed to kill flow $runId", Color.red) + verify(printWriter).flush() + } + + @Test + fun killFlowSuccess() { + val runId = StateMachineRunId.createRandom() + whenever(cordaRpcOps.killFlow(any())).thenReturn(true) + + InteractiveShell.killFlowById(runId.uuid.toString(), printWriter, cordaRpcOps, om) + verify(cordaRpcOps).killFlow(runId) + verify(printWriter).println("Killed flow $runId", Color.yellow) + verify(printWriter).flush() + } } @ToStringSerialize diff --git a/tools/shell/src/test/kotlin/net/corda/tools/shell/utilities/ANSIProgressRendererTest.kt b/tools/shell/src/test/kotlin/net/corda/tools/shell/utilities/ANSIProgressRendererTest.kt index b20c89168d..08dbe492c2 100644 --- a/tools/shell/src/test/kotlin/net/corda/tools/shell/utilities/ANSIProgressRendererTest.kt +++ b/tools/shell/src/test/kotlin/net/corda/tools/shell/utilities/ANSIProgressRendererTest.kt @@ -40,6 +40,10 @@ class ANSIProgressRendererTest { fun stepActive(stepLabel: String): String { return if (SystemUtils.IS_OS_WINDOWS) """CURRENT: $INTENSITY_BOLD_ON_ASCII$stepLabel$INTENSITY_OFF_ASCII""" else """▶︎ $INTENSITY_BOLD_ON_ASCII$stepLabel$INTENSITY_OFF_ASCII""" } + + fun stepNotRun(stepLabel: String): String { + return """ $stepLabel""" + } } lateinit var printWriter: RenderPrintWriter @@ -59,35 +63,57 @@ class ANSIProgressRendererTest { flowProgressHandle = FlowProgressHandleImpl(StateMachineRunId.createRandom(), openFuture(), Observable.empty(), stepsTreeIndexFeed, stepsTreeFeed) } + private fun checkTrackingState(captor: KArgumentCaptor, updates: Int, trackerState: List) { + verify(printWriter, times(updates)).print(captor.capture()) + assertThat(captor.lastValue.toString()).containsSequence(trackerState) + verify(printWriter, times(updates)).flush() + } + @Test fun `test that steps are rendered appropriately depending on their status`() { progressRenderer.render(flowProgressHandle) feedSubject.onNext(listOf(Pair(0, STEP_1_LABEL), Pair(0, STEP_2_LABEL), Pair(0, STEP_3_LABEL))) // The flow is currently at step 3, while step 1 has been completed and step 2 has been skipped. + indexSubject.onNext(0) indexSubject.onNext(2) val captor = argumentCaptor() - verify(printWriter, times(2)).print(captor.capture()) - assertThat(captor.secondValue.toString()).containsSequence(stepSuccess(STEP_1_LABEL), stepSkipped(STEP_2_LABEL), stepActive(STEP_3_LABEL)) - verify(printWriter, times(2)).flush() + checkTrackingState(captor, 2, listOf(stepSuccess(STEP_1_LABEL), stepSkipped(STEP_2_LABEL), stepActive(STEP_3_LABEL))) } @Test fun `changing tree causes correct steps to be marked as done`() { progressRenderer.render(flowProgressHandle) feedSubject.onNext(listOf(Pair(0, STEP_1_LABEL), Pair(1, STEP_2_LABEL), Pair(1, STEP_3_LABEL), Pair(0, STEP_4_LABEL), Pair(0, STEP_5_LABEL))) + indexSubject.onNext(0) indexSubject.onNext(1) indexSubject.onNext(2) val captor = argumentCaptor() - verify(printWriter, times(3)).print(captor.capture()) - assertThat(captor.lastValue.toString()).containsSequence(stepSuccess(STEP_1_LABEL), stepSuccess(STEP_2_LABEL), stepActive(STEP_3_LABEL)) - verify(printWriter, times(3)).flush() + checkTrackingState(captor, 3, listOf(stepSuccess(STEP_1_LABEL), stepSuccess(STEP_2_LABEL), stepActive(STEP_3_LABEL))) feedSubject.onNext(listOf(Pair(0, STEP_1_LABEL), Pair(0, STEP_4_LABEL), Pair(0, STEP_5_LABEL))) - verify(printWriter, times(4)).print(captor.capture()) - assertThat(captor.lastValue.toString()).containsSequence(stepActive(STEP_1_LABEL)) - assertThat(captor.lastValue.toString()).doesNotContain(stepActive(STEP_5_LABEL)) - verify(printWriter, times(4)).flush() + checkTrackingState(captor, 4, listOf(stepActive(STEP_1_LABEL), stepNotRun(STEP_4_LABEL), stepNotRun(STEP_5_LABEL))) + } + + @Test + fun `duplicate steps in different children handled correctly`() { + val captor = argumentCaptor() + progressRenderer.render(flowProgressHandle) + feedSubject.onNext(listOf(Pair(0, STEP_1_LABEL), Pair(0, STEP_2_LABEL))) + indexSubject.onNext(0) + + checkTrackingState(captor, 1, listOf(stepActive(STEP_1_LABEL), stepNotRun(STEP_2_LABEL))) + + feedSubject.onNext(listOf(Pair(0, STEP_1_LABEL), Pair(1, STEP_3_LABEL), Pair(0, STEP_2_LABEL), Pair(1, STEP_3_LABEL))) + indexSubject.onNext(1) + indexSubject.onNext(2) + indexSubject.onNext(3) + + checkTrackingState(captor, 5, listOf(stepSuccess(STEP_1_LABEL), stepSuccess(STEP_3_LABEL), stepSuccess(STEP_2_LABEL), stepActive(STEP_3_LABEL))) + + feedSubject.onNext(listOf(Pair(0, STEP_1_LABEL), Pair(1, STEP_3_LABEL), Pair(0, STEP_2_LABEL), Pair(1, STEP_3_LABEL), Pair(2, STEP_4_LABEL))) + + checkTrackingState(captor, 6, listOf(stepSuccess(STEP_1_LABEL), stepSuccess(STEP_3_LABEL), stepSuccess(STEP_2_LABEL), stepActive(STEP_3_LABEL), stepNotRun(STEP_4_LABEL))) } } \ No newline at end of file