Merge branch 'master' into release/4

This commit is contained in:
Katelyn Baker 2019-04-03 16:01:54 +01:00
commit 71114cd412
539 changed files with 4954 additions and 2258 deletions

View File

@ -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<? super net.corda.core.concurrent.CordaFuture<V>, ? 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 <init>(net.corda.core.contracts.Attachment, String)
public <init>(net.corda.core.contracts.Attachment, String, java.util.Set<String>)
@ -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 <init>(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 <init>(net.corda.core.crypto.SecureHash, String, Throwable)
public <init>(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 <init>(net.corda.core.crypto.SecureHash, String, Throwable)
public <init>(net.corda.core.crypto.SecureHash, String, Throwable, String)
public <init>(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 <init>(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<net.corda.core.crypto.TransactionSignature>)
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 <init>(java.util.Set<? extends java.security.PublicKey>, java.util.List<String>, 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 <init>(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 <init>(byte[], int, int)
@NotNull

View File

@ -1,5 +1,6 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

View File

@ -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',

View File

@ -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>(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>(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(

View File

@ -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)
}
}

View File

@ -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, {})
}
}

View File

@ -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<AbstractParty> = 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<StateRef, SignedTransaction?>
): 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 ->

View File

@ -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<ReconnectOps>(ops = ops, customPort = serverPort).getOrThrow()
val response = eventually<RPCException, String>(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.
}
}

View File

@ -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<String> perms = Collections.singletonList("ALL");
private List<String> perms = singletonList("ALL");
private Set<String> 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<Currency> dollars123 = new Amount<>(123, Currency.getInstance("USD"));
FlowHandle<AbstractCashFlow.Result> flowHandle = rpcProxy.startFlowDynamic(CashIssueFlow.class,
@ -105,7 +105,7 @@ public class StandaloneCordaRPCJavaClientTest {
flowHandle.getReturnValue().get();
Amount<Currency> balance = getCashBalance(rpcProxy, Currency.getInstance("USD"));
System.out.print("Balance: " + balance + "\n");
System.out.println("Balance: " + balance);
assertEquals(dollars123, balance, "matching");
}

View File

@ -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)

View File

@ -4,6 +4,7 @@
<Properties>
<Property name="log-path">${sys:log-path:-logs}</Property>
<Property name="log-name">node-${hostName}</Property>
<Property name="diagnostic-log-name">diagnostic-${hostName}</Property>
<Property name="archive">${log-path}/archive</Property>
<Property name="defaultLogLevel">${sys:defaultLogLevel:-info}</Property>
<Property name="consoleLogLevel">${sys:consoleLogLevel:-error}</Property>
@ -105,6 +106,46 @@
</RollingRandomAccessFile>
<!-- Will generate up to 100 log files for a given day. During every rollover it will delete
those that are older than 60 days, but keep the most recent 10 GB -->
<RollingRandomAccessFile name="Diagnostic-RollingFile-Appender"
fileName="${log-path}/${diagnostic-log-name}.log"
filePattern="${archive}/${diagnostic-log-name}.%date{yyyy-MM-dd}-%i.log.gz">
<PatternLayout>
<ScriptPatternSelector defaultPattern="[%-5level] %date{ISO8601}{UTC}Z [%t] %c{2}.%method - %msg%n">
<Script name="MDCSelector" language="javascript"><![CDATA[
result = null;
if (!logEvent.getContextData().size() == 0) {
result = "WithMDC";
} else {
result = null;
}
result;
]]>
</Script>
<PatternMatch key="WithMDC" pattern="[%-5level] %date{ISO8601}{UTC}Z [%t] %c{2}.%method - %msg %X%n"/>
</ScriptPatternSelector>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
<DefaultRolloverStrategy min="1" max="100">
<Delete basePath="${archive}" maxDepth="1">
<IfFileName glob="${log-name}*.log.gz"/>
<IfLastModified age="60d">
<IfAny>
<IfAccumulatedFileSize exceeds="10 GB"/>
</IfAny>
</IfLastModified>
</Delete>
</DefaultRolloverStrategy>
</RollingRandomAccessFile>
<Rewrite name="Console-ErrorCode-Selector">
<AppenderRef ref="Console-Selector"/>
<ErrorCodeRewritePolicy/>
@ -119,6 +160,10 @@
<AppenderRef ref="RollingFile-Appender"/>
<ErrorCodeRewritePolicy/>
</Rewrite>
<Rewrite name="Diagnostic-RollingFile-ErrorCode-Appender">
<AppenderRef ref="Diagnostic-RollingFile-Appender"/>
<ErrorCodeRewritePolicy/>
</Rewrite>
</Appenders>
<Loggers>
@ -130,6 +175,9 @@
<AppenderRef ref="Console-ErrorCode-Appender-Println"/>
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>
</Logger>
<Logger name="org.hibernate" level="warn" additivity="false">
<AppenderRef ref="Diagnostic-RollingFile-ErrorCode-Appender"/>
</Logger>
<Logger name="org.hibernate.SQL" level="info" additivity="false">
<AppenderRef ref="Console-ErrorCode-Selector"/>
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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<V> : Future<V> {
/**
* Run the given callback when this future is done, on the completion thread.

View File

@ -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,

View File

@ -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<SecureHash>) :
class UntrustedAttachmentsException(val txId: SecureHash, val ids: List<SecureHash>) :
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.
*/
}

View File

@ -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<NodeAndWeight>) : PublicKey {
companion object {
const val KEY_ALGORITHM = "COMPOSITE"

View File

@ -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)

View File

@ -7,7 +7,6 @@ import java.security.PublicKey
@KeepForDJVM
object NullKeys {
@CordaSerializable
object NullPublicKey : PublicKey, Comparable<PublicKey> {
override fun getAlgorithm() = "NULL"
override fun getEncoded() = byteArrayOf(0)

View File

@ -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)
}

View File

@ -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 {

View File

@ -468,7 +468,7 @@ abstract class FlowLogic<out T> {
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)

View File

@ -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].

View File

@ -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

View File

@ -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<String>): 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
}

View File

@ -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<String, String>): ByteArray {
@ -524,6 +542,7 @@ fun <E> MutableSet<E>.toSynchronised(): MutableSet<E> = 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<T, U>(val originalList: List<T>, val transform: (T, Int) -> U) : AbstractList<U>() {
private val partialResolvedList = MutableList<U?>(originalList.size) { null }
@ -532,6 +551,15 @@ class LazyMappedList<T, U>(val originalList: List<T>, 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<T, U>(val originalList: List<T>, val transform: (T, Int) ->
*/
fun <T, U> List<T>.lazyMapped(transform: (T, Int) -> U): List<U> = 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 <T> List<T>.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<String, Boolean>(MAX_SIZE)).toSynchronised()

View File

@ -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<Pair<String, Set<CodeSigner>>> get() =
entries.thatAreSignable.shreddedFrom(this).toFileSignerSet().toList()
private val Sequence<JarEntry>.thatAreSignable: Sequence<JarEntry> get() =
filterNot { entry -> entry.isDirectory || unsignableEntryName.matches(entry.name) }
private val Sequence<JarEntry>.thatAreSignable: Sequence<JarEntry> get() = filterNot { isNotSignable(it) }
private fun Sequence<JarEntry>.shreddedFrom(jar: JarInputStream): Sequence<JarEntry> = map { entry ->
val shredder = ByteArray(1024) // can't share or re-use this, as it's used to compute CRCs during shredding

View File

@ -62,7 +62,8 @@ class LazyPool<A>(
*/
fun close(): Iterable<A> {
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
}

View File

@ -25,7 +25,12 @@ class LazyStickyPool<A : Any>(
private val boxes = Array(size) { InstanceBox<A>() }
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 {

View File

@ -65,7 +65,6 @@ class ResolveTransactionsFlow(txHashesArg: Set<SecureHash>,
}
}
@CordaSerializable
class ExcessivelyLargeTransactionGraph : FlowException()
// TODO: Figure out a more appropriate DOS limit here, 5000 is simply a very bad guess.

View File

@ -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)
}

View File

@ -98,15 +98,20 @@ data class NetworkParameters(
require(noPackageOverlap(packageOwnership.keys)) { "Multiple packages added to the packageOwnership overlap." }
}
fun copy(minimumPlatformVersion: Int,
notaries: List<NotaryInfo>,
maxMessageSize: Int,
maxTransactionSize: Int,
modifiedTime: Instant,
epoch: Int,
whitelistedContractImplementations: Map<String, List<AttachmentId>>
/**
* 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<NotaryInfo> = this.notaries,
maxMessageSize: Int = this.maxMessageSize,
maxTransactionSize: Int = this.maxTransactionSize,
modifiedTime: Instant = this.modifiedTime,
epoch: Int = this.epoch,
whitelistedContractImplementations: Map<String, List<AttachmentId>> = 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<NotaryInfo>,
maxMessageSize: Int,
maxTransactionSize: Int,
modifiedTime: Instant,
epoch: Int,
whitelistedContractImplementations: Map<String, List<AttachmentId>>,
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<NotaryInfo> = this.notaries,
maxMessageSize: Int = this.maxMessageSize,
maxTransactionSize: Int = this.maxTransactionSize,
modifiedTime: Instant = this.modifiedTime,
epoch: Int = this.epoch,
whitelistedContractImplementations: Map<String, List<AttachmentId>> = 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
)
}

View File

@ -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<QueryCriteria, IQueryCriteriaP
) : CommonQueryCriteria() {
// V3 c'tors
// These have to be manually specified as @JvmOverloads for some reason causes declaration clashes
@DeprecatedConstructorForDeserialization(version = 6)
constructor(
status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
contractStateTypes: Set<Class<out ContractState>>? = null,
@ -110,14 +112,19 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
softLockingCondition: SoftLockingCondition? = null,
timeCondition: TimeCondition? = null
) : this(status, contractStateTypes, stateRefs, notary, softLockingCondition, timeCondition, participants = null)
@DeprecatedConstructorForDeserialization(version = 1)
constructor(status: Vault.StateStatus) : this(status, participants = null)
@DeprecatedConstructorForDeserialization(version = 2)
constructor(status: Vault.StateStatus, contractStateTypes: Set<Class<out ContractState>>?) : this(status, contractStateTypes, participants = null)
@DeprecatedConstructorForDeserialization(version = 3)
constructor(status: Vault.StateStatus, contractStateTypes: Set<Class<out ContractState>>?, stateRefs: List<StateRef>?) : this(
status, contractStateTypes, stateRefs, participants = null
)
@DeprecatedConstructorForDeserialization(version = 4)
constructor(status: Vault.StateStatus, contractStateTypes: Set<Class<out ContractState>>?, stateRefs: List<StateRef>?, notary: List<AbstractParty>?) : this(
status, contractStateTypes, stateRefs, notary, participants = null
)
@DeprecatedConstructorForDeserialization(version = 5)
constructor(status: Vault.StateStatus, contractStateTypes: Set<Class<out ContractState>>?, stateRefs: List<StateRef>?, notary: List<AbstractParty>?, softLockingCondition: SoftLockingCondition?) : this(
status, contractStateTypes, stateRefs, notary, softLockingCondition, participants = null
)
@ -174,6 +181,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
) : CommonQueryCriteria() {
// V3 c'tor
@JvmOverloads
@DeprecatedConstructorForDeserialization(version = 2)
constructor(
participants: List<AbstractParty>? = null,
uuid: List<UUID>? = null,
@ -182,6 +190,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
contractStateTypes: Set<Class<out ContractState>>? = null
) : this(participants, uuid, externalId, status, contractStateTypes, Vault.RelevancyStatus.ALL)
@DeprecatedConstructorForDeserialization(version = 3)
constructor(
participants: List<AbstractParty>? = null,
linearId: List<UniqueIdentifier>? = null,
@ -191,6 +200,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
) : this(participants, linearId?.map { it.id }, linearId?.mapNotNull { it.externalId }, status, contractStateTypes, relevancyStatus)
// V3 c'tor
@DeprecatedConstructorForDeserialization(version = 1)
constructor(
participants: List<AbstractParty>? = null,
linearId: List<UniqueIdentifier>? = null,
@ -264,6 +274,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
override val relevancyStatus: Vault.RelevancyStatus
) : CommonQueryCriteria() {
@JvmOverloads
@DeprecatedConstructorForDeserialization(version = 1)
constructor(
participants: List<AbstractParty>? = null,
owner: List<AbstractParty>? = null,
@ -325,6 +336,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
override val relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
) : CommonQueryCriteria() {
@JvmOverloads
@DeprecatedConstructorForDeserialization(version = 1)
constructor(
expression: CriteriaExpression<L, Boolean>,
status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
@ -381,10 +393,13 @@ sealed class AttachmentQueryCriteria : GenericQueryCriteria<AttachmentQueryCrite
val isSignedCondition: ColumnPredicate<Boolean>? = null,
val versionCondition: ColumnPredicate<Int>? = null) : AttachmentQueryCriteria() {
// V3 c'tors
@DeprecatedConstructorForDeserialization(version = 3)
constructor(uploaderCondition: ColumnPredicate<String>? = null,
filenameCondition: ColumnPredicate<String>? = null,
uploadDateCondition: ColumnPredicate<Instant>? = null) : this(uploaderCondition, filenameCondition, uploadDateCondition, null)
@DeprecatedConstructorForDeserialization(version = 1)
constructor(uploaderCondition: ColumnPredicate<String>?) : this(uploaderCondition, null)
@DeprecatedConstructorForDeserialization(version = 2)
constructor(uploaderCondition: ColumnPredicate<String>?, filenameCondition: ColumnPredicate<String>?) : this(uploaderCondition, filenameCondition, null)
override fun visit(parser: AttachmentsQueryCriteriaParser): Collection<Predicate> {

View File

@ -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<out T : Any>(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 : Any> T.serialize(serializationFactory: SerializationFactory = Serializa
*/
@Suppress("unused")
@KeepForDJVM
@CordaSerializable
class SerializedBytes<T : Any>(bytes: ByteArray) : OpaqueBytes(bytes) {
companion object {
/**

View File

@ -327,6 +327,7 @@ object AttachmentsClassLoaderBuilder {
.withClassLoader(transactionClassLoader)
.withWhitelist(whitelistedClasses)
.withCustomSerializers(serializers)
.withoutCarpenter()
}
// Deserialize all relevant classes in the transaction classloader.

View File

@ -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<SerializedStateAndRef>? = 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
}
/**

View File

@ -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<CoreTransaction>,
// 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<CoreTransaction>,
}
@KeepForDJVM
@CordaSerializable
class SignaturesMissingException(val missing: Set<PublicKey>, val descriptions: List<String>, override val id: SecureHash)
: NamedByHash, SignatureException(missingSignatureMsg(missing, descriptions, id)), CordaThrowable by CordaException(missingSignatureMsg(missing, descriptions, id))

View File

@ -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<TransactionState<*>> = 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<Command<*>> {
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<Command<*>> = ArrayList(commands)
/**
* Sign the built transaction and return it. This is an internal function for use by the service hub, please use

View File

@ -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

View File

@ -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<ByteSequence> {
/**
@ -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 {
/**

View File

@ -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

View File

@ -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<Change> get() = Observable.empty()
@ -85,16 +86,22 @@ class ProgressTracker(vararg inputSteps: Step) {
private val childProgressTrackers = mutableMapOf<Step, Child>()
/** 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<Pair<Int, Step>> = _allSteps()
// This field won't be serialized.
private val _changes by transient { PublishSubject.create<Change>() }
private val _stepsTreeChanges by transient { PublishSubject.create<List<Pair<Int, String>>>() }
private val _changes by transient { ReplaySubject.create<Change>() }
private val _stepsTreeChanges by transient { ReplaySubject.create<List<Pair<Int, String>>>() }
private val _stepsTreeIndexChanges by transient { ReplaySubject.create<Int>() }
/**
* 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<Pair<Int, Step>> {
@ -291,7 +308,9 @@ class ProgressTracker(vararg inputSteps: Step) {
*/
val stepsTreeIndexChanges: Observable<Int> 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.

View File

@ -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)

View File

@ -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()

View File

@ -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)
}
}

View File

@ -57,19 +57,6 @@ class FinalityFlowTests : WithFinality {
willThrow<IllegalArgumentException>())
}
@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()))

View File

@ -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<RefState.State>().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<LinearState>().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<RefState.State>(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<RefState.State>(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<LinearState>(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<RefState.State>(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<RefState.State>(updatedQuery)
assertEquals(newRefState, theOriginalReferencedStateOnNodeZero.states.single())
assertEquals(Vault.StateStatus.CONSUMED, theOriginalReferencedStateOnNodeZero.statesMetadata.single().status)
}
// A dummy reference state contract.
class RefState : Contract {
companion object {

View File

@ -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<StateRef> = emptyList()
) : CoreTransaction() {
override val outputs: List<TransactionState<ContractState>> = (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<AbstractParty> = emptyList()
}

View File

@ -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`() {

View File

@ -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<AbstractParty> = emptyList()
}

View File

@ -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

View File

@ -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<Int, Int> { _, _ ->
throw MissingAttachmentsException(emptyList(), "Uncatchable!")
}
lazyList.eagerDeserialise { _, _ -> -999 }
}
@Test
fun testDeserialisationExceptions() {
val lazyList = (0 until 5).toList().lazyMapped<Int, Int> { _, 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")
}
}
}

View File

@ -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<List<Pair<Int, String>>>()
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<List<Pair<Int, String>>>()
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<Int>()
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<String>()
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<List<Pair<Int, String>>>()
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<List<Pair<Int, String>>>()
val stepIndexNotifications = mutableListOf<Int>()
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 }
}
}

12
djvm/.gitignore vendored
View File

@ -1,3 +1,13 @@
tmp/
# DJVM-specific files
**/tmp/
*.log
*.log.gz
# IntelliJ
*.iml
*.ipr
*.iws
.idea/
**/out/

View File

@ -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'
}

View File

@ -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] ]
}

87
djvm/djvm/build.gradle Normal file
View File

@ -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
}
}

View File

@ -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
}

View File

@ -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<Boolean> {
@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<Boolean> {
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<Boolean> {
}
}
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)

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info">
<ThresholdFilter level="info"/>
<ThresholdFilter level="trace"/>
<Appenders>
<!-- Will generate up to 10 log files for a given day. During every rollover it will delete
those that are older than 60 days, but keep the most recent 10 GB -->

16
djvm/djvm/cli/src/shell/djvm Executable file
View File

@ -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 "$@"

View File

@ -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 %*

View File

@ -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

View File

@ -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 "$@"

View File

@ -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

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