mirror of
https://github.com/corda/corda.git
synced 2024-12-24 07:06:44 +00:00
CORDA-2375 Ensure node has unique attachment contract classname/version from signed JARs (#4535)
Corda Node ensures a given contract class and version can be sourced from only one signed and trusted Attachment (JAR). An attempt to import a signed JAR as a trusted uploader (or promote to be trusted) with a class and version already present in the other trusted Attachment will raise DuplicateContractClassException. Minor fixes to Hibernate Attachment Query parser (original query to select attachment without signers would always return no attachments)
This commit is contained in:
parent
e87a8ed496
commit
9b8fda0d6d
@ -212,7 +212,7 @@ data class Sort(val columns: Collection<SortColumn>) : BaseSort() {
|
||||
data class AttachmentSort(val columns: Collection<AttachmentSortColumn>) : BaseSort() {
|
||||
|
||||
enum class AttachmentSortAttribute(val columnName: String) {
|
||||
INSERTION_DATE("insertion_date"),
|
||||
INSERTION_DATE("insertionDate"),
|
||||
UPLOADER("uploader"),
|
||||
FILENAME("filename"),
|
||||
VERSION ("version")
|
||||
|
@ -230,6 +230,20 @@ The contract attachment non-downgrade rule is enforced in two locations:
|
||||
A version number is stored in the manifest information of the enclosing JAR file. This version identifier should be a whole number starting
|
||||
from 1. This information should be set using the Gradle cordapp plugin, or manually, as described in :doc:`versioning`.
|
||||
|
||||
|
||||
Uniqueness requirement Contract and Version for Signature Constraint
|
||||
--------------------------------------------------------------------
|
||||
|
||||
CorDapps in Corda 4 may be signed (to use new signature constraints functionality) or unsigned, and versioned.
|
||||
The following controls are enforced for these different types of jars within the attachment store of a node:
|
||||
|
||||
- Signed contract JARs must be uniquely versioned per contract class (or group of).
|
||||
At runtime the node will throw a `DuplicateContractClassException`` exception if this condition is violated.
|
||||
|
||||
- Unsigned contract JARs: there may exist multiple instances of the same contract jar.
|
||||
At run-time the node will warn of duplicates encountered.
|
||||
The most recent version given by insertionDate into the attachment storage will be used upon transaction building/resolution.
|
||||
|
||||
Issues when using the HashAttachmentConstraint
|
||||
----------------------------------------------
|
||||
|
||||
|
@ -11,6 +11,12 @@ import net.corda.core.serialization.CordaSerializable
|
||||
*/
|
||||
class DuplicateAttachmentException(attachmentHash: String) : java.nio.file.FileAlreadyExistsException(attachmentHash), ClientRelevantError
|
||||
|
||||
/**
|
||||
* Thrown to indicate that a contract class name of the same version was already uploaded to a Corda node.
|
||||
*/
|
||||
class DuplicateContractClassException(contractClassName: String, version: Int, attachmentHashes: List<String>) :
|
||||
Exception("Contract $contractClassName version '$version' already present in the attachments $attachmentHashes"), ClientRelevantError
|
||||
|
||||
/**
|
||||
* Thrown to indicate that a flow was not designed for RPC and should be started from an RPC client.
|
||||
*/
|
||||
|
@ -80,18 +80,24 @@ class FlowCheckpointVersionNodeStartupCheckTest {
|
||||
@Test
|
||||
fun `restart node with incompatible version of suspended flow due to different jar hash`() {
|
||||
driver(parametersForRestartingNodes()) {
|
||||
val uniqueName = "different-jar-hash-test-${UUID.randomUUID()}"
|
||||
val cordapp = defaultCordapp.copy(name = uniqueName)
|
||||
val uniqueWorkflowJarName = "different-jar-hash-test-${UUID.randomUUID()}"
|
||||
val uniqueContractJarName = "contract-$uniqueWorkflowJarName"
|
||||
val defaultWorkflowJar = cordappWithPackages(SendMessageFlow::class.packageName)
|
||||
val defaultContractJar = cordappWithPackages(MessageState::class.packageName)
|
||||
val contractJar = defaultContractJar.copy(name = uniqueContractJarName)
|
||||
val workflowJar = defaultWorkflowJar.copy(name = uniqueWorkflowJarName)
|
||||
|
||||
val bobBaseDir = createSuspendedFlowInBob(setOf(cordapp))
|
||||
val bobBaseDir = createSuspendedFlowInBob(setOf(workflowJar, contractJar))
|
||||
|
||||
val cordappsDir = bobBaseDir / "cordapps"
|
||||
val cordappJar = cordappsDir.list().single { it.toString().endsWith(".jar") }
|
||||
val cordappJar = cordappsDir.list().single {
|
||||
! it.toString().contains(uniqueContractJarName) && it.toString().endsWith(".jar")
|
||||
}
|
||||
// Make sure we're dealing with right jar
|
||||
assertThat(cordappJar.fileName.toString()).contains(uniqueName)
|
||||
assertThat(cordappJar.fileName.toString()).contains(uniqueWorkflowJarName)
|
||||
|
||||
// The name is part of the MANIFEST so changing it is sufficient to change the jar hash
|
||||
val modifiedCordapp = cordapp.copy(name = "${cordapp.name}-modified")
|
||||
val modifiedCordapp = workflowJar.copy(name = "${workflowJar.name}-modified")
|
||||
val modifiedCordappJar = CustomCordapp.getJarFile(modifiedCordapp)
|
||||
modifiedCordappJar.moveTo(cordappJar, REPLACE_EXISTING)
|
||||
|
||||
@ -125,8 +131,8 @@ class FlowCheckpointVersionNodeStartupCheckTest {
|
||||
)).getOrThrow()
|
||||
}
|
||||
|
||||
val logDir = baseDirectory(BOB_NAME) / NodeStartup.LOGS_DIRECTORY_NAME
|
||||
val logFile = logDir.list { it.filter { it.fileName.toString().endsWith(".log") }.findAny().get() }
|
||||
val logDir = baseDirectory(BOB_NAME)
|
||||
val logFile = logDir.list { it.filter { it.fileName.toString().endsWith("out.log") }.findAny().get() }
|
||||
val matchingLineCount = logFile.readLines { it.filter { line -> logMessage in line }.count() }
|
||||
assertEquals(1, matchingLineCount)
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import net.corda.node.utilities.InfrequentlyMutatedCache
|
||||
import net.corda.node.utilities.NonInvalidatingCache
|
||||
import net.corda.node.utilities.NonInvalidatingWeightBasedCache
|
||||
import net.corda.nodeapi.exceptions.DuplicateAttachmentException
|
||||
import net.corda.nodeapi.exceptions.DuplicateContractClassException
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||
import net.corda.nodeapi.internal.persistence.currentDBSession
|
||||
@ -305,6 +306,22 @@ class NodeAttachmentService(
|
||||
currentDBSession().find(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString()) != null
|
||||
}
|
||||
|
||||
private fun verifyVersionUniquenessForSignedAttachments(contractClassNames: List<ContractClassName>, contractVersion: Int, signers: List<PublicKey>?){
|
||||
if (signers != null && signers.isNotEmpty()) {
|
||||
contractClassNames.forEach {
|
||||
val existingContractsImplementations = queryAttachments(AttachmentQueryCriteria.AttachmentsQueryCriteria(
|
||||
contractClassNamesCondition = Builder.equal(listOf(it)),
|
||||
versionCondition = Builder.equal(contractVersion),
|
||||
uploaderCondition = Builder.`in`(TRUSTED_UPLOADERS),
|
||||
isSignedCondition = Builder.equal(true))
|
||||
)
|
||||
if (existingContractsImplementations.isNotEmpty()) {
|
||||
throw DuplicateContractClassException(it, contractVersion, existingContractsImplementations.map { it.toString() })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: PLT-147: The attachment should be randomised to prevent brute force guessing and thus privacy leaks.
|
||||
private fun import(jar: InputStream, uploader: String?, filename: String?): AttachmentId {
|
||||
return database.transaction {
|
||||
@ -324,6 +341,9 @@ class NodeAttachmentService(
|
||||
val jarSigners = getSigners(bytes)
|
||||
val contractVersion = getVersion(bytes)
|
||||
val session = currentDBSession()
|
||||
|
||||
verifyVersionUniquenessForSignedAttachments(contractClassNames, contractVersion, jarSigners)
|
||||
|
||||
val attachment = NodeAttachmentService.DBAttachment(
|
||||
attId = id.toString(),
|
||||
content = bytes,
|
||||
@ -344,6 +364,7 @@ class NodeAttachmentService(
|
||||
val attachment = session.get(NodeAttachmentService.DBAttachment::class.java, id.toString())
|
||||
// update the `uploader` field (as the existing attachment may have been resolved from a peer)
|
||||
if (attachment.uploader != uploader) {
|
||||
verifyVersionUniquenessForSignedAttachments(contractClassNames, attachment.version, attachment.signers)
|
||||
attachment.uploader = uploader
|
||||
session.saveOrUpdate(attachment)
|
||||
log.info("Updated attachment $id with uploader $uploader")
|
||||
@ -430,7 +451,8 @@ class NodeAttachmentService(
|
||||
private fun getContractAttachmentVersions(contractClassName: String): NavigableMap<Version, AttachmentIds> = contractsCache.get(contractClassName) { name ->
|
||||
val attachmentQueryCriteria = AttachmentQueryCriteria.AttachmentsQueryCriteria(contractClassNamesCondition = Builder.equal(listOf(name)),
|
||||
versionCondition = Builder.greaterThanOrEqual(0), uploaderCondition = Builder.`in`(TRUSTED_UPLOADERS))
|
||||
val attachmentSort = AttachmentSort(listOf(AttachmentSort.AttachmentSortColumn(AttachmentSort.AttachmentSortAttribute.VERSION, Sort.Direction.DESC)))
|
||||
val attachmentSort = AttachmentSort(listOf(AttachmentSort.AttachmentSortColumn(AttachmentSort.AttachmentSortAttribute.VERSION, Sort.Direction.DESC),
|
||||
AttachmentSort.AttachmentSortColumn(AttachmentSort.AttachmentSortAttribute.INSERTION_DATE, Sort.Direction.DESC)))
|
||||
database.transaction {
|
||||
val session = currentDBSession()
|
||||
val criteriaBuilder = session.criteriaBuilder
|
||||
@ -447,15 +469,17 @@ class NodeAttachmentService(
|
||||
val query = session.createQuery(criteriaQuery)
|
||||
|
||||
// execution
|
||||
TreeMap(query.resultList.groupBy { it.version }.map { makeAttachmentIds(it) }.toMap())
|
||||
TreeMap(query.resultList.groupBy { it.version }.map { makeAttachmentIds(it, name) }.toMap())
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeAttachmentIds(it: Map.Entry<Int, List<DBAttachment>>): Pair<Version, AttachmentIds> {
|
||||
check(it.value.size <= 2)
|
||||
val signed = it.value.filter { it.signers?.isNotEmpty() ?: false }.map { AttachmentId.parse(it.attId) }.singleOrNull()
|
||||
val unsigned = it.value.filter { it.signers?.isEmpty() ?: true }.map { AttachmentId.parse(it.attId) }.singleOrNull()
|
||||
return it.key to AttachmentIds(signed, unsigned)
|
||||
private fun makeAttachmentIds(it: Map.Entry<Int, List<DBAttachment>>, contractClassName: String): Pair<Version, AttachmentIds> {
|
||||
val signed = it.value.filter { it.signers?.isNotEmpty() ?: false }.map { AttachmentId.parse(it.attId) }
|
||||
check (signed.size <= 1) //sanity check
|
||||
val unsigned = it.value.filter { it.signers?.isEmpty() ?: true }.map { AttachmentId.parse(it.attId) }
|
||||
if (unsigned.size > 1) //TODO cater better for whiltelisted JARs - CORDA-2405
|
||||
log.warn("Selecting attachment ${unsigned.first()} from duplicated, unsigned attachments ${unsigned.map { it.toString() }} for contract $contractClassName version '${it.key}'.")
|
||||
return it.key to AttachmentIds(signed.singleOrNull(), unsigned.firstOrNull())
|
||||
}
|
||||
|
||||
override fun getContractAttachmentWithHighestContractVersion(contractClassName: String, minContractVersion: Int): AttachmentId? {
|
||||
|
@ -231,11 +231,12 @@ class HibernateAttachmentQueryCriteriaParser(override val criteriaBuilder: Crite
|
||||
}
|
||||
|
||||
criteria.isSignedCondition?.let { isSigned ->
|
||||
val joinDBAttachmentToSigners = root.joinList<NodeAttachmentService.DBAttachment, PublicKey>("signers")
|
||||
if (isSigned == Builder.equal(true))
|
||||
if (isSigned == Builder.equal(true)) {
|
||||
val joinDBAttachmentToSigners = root.joinList<NodeAttachmentService.DBAttachment, PublicKey>("signers")
|
||||
predicateSet.add(criteriaBuilder.and(joinDBAttachmentToSigners.isNotNull))
|
||||
else
|
||||
predicateSet.add(criteriaBuilder.and(joinDBAttachmentToSigners.isNull))
|
||||
} else {
|
||||
predicateSet.add(criteriaBuilder.equal(criteriaBuilder.size(root.get<List<PublicKey>?>("signers")),0))
|
||||
}
|
||||
}
|
||||
|
||||
criteria.versionCondition?.let {
|
||||
|
@ -18,6 +18,7 @@ import net.corda.core.node.services.vault.Sort
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
||||
import net.corda.nodeapi.exceptions.DuplicateAttachmentException
|
||||
import net.corda.nodeapi.exceptions.DuplicateContractClassException
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.testing.core.internal.ContractJarTestUtils.makeTestContractJar
|
||||
@ -33,6 +34,7 @@ import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.startFlow
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
@ -268,10 +270,18 @@ class NodeAttachmentServiceTest {
|
||||
storage.queryAttachments(AttachmentsQueryCriteria(signersCondition = Builder.equal(listOf(publicKey)))).size
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
3,
|
||||
storage.queryAttachments(AttachmentsQueryCriteria(isSignedCondition = Builder.equal(true))).size
|
||||
)
|
||||
val allAttachments = storage.queryAttachments(AttachmentsQueryCriteria())
|
||||
assertEquals(6, allAttachments.size)
|
||||
|
||||
val signedAttachments = storage.queryAttachments(AttachmentsQueryCriteria(isSignedCondition = Builder.equal(true)))
|
||||
assertEquals(3, signedAttachments.size)
|
||||
|
||||
val unsignedAttachments = storage.queryAttachments(AttachmentsQueryCriteria(isSignedCondition = Builder.equal(false)))
|
||||
assertEquals(3, unsignedAttachments.size)
|
||||
|
||||
assertNotEquals(signedAttachments.toSet(), unsignedAttachments.toSet())
|
||||
|
||||
assertEquals(signedAttachments.toSet() + unsignedAttachments.toSet(), allAttachments.toSet())
|
||||
|
||||
assertEquals(
|
||||
1,
|
||||
@ -315,6 +325,138 @@ class NodeAttachmentServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cannot import jar with duplicated contract class, version and signers for trusted uploader`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val (contractJar, _) = makeTestSignedContractJar(file.path, "com.example.MyContract")
|
||||
val anotherContractJar = makeTestContractJar(file.path, listOf( "com.example.MyContract", "com.example.AnotherContract"), true, generateManifest = false, jarFileName = "another-sample.jar")
|
||||
contractJar.read { storage.privilegedImportAttachment(it, "app", "sample.jar") }
|
||||
|
||||
assertThatExceptionOfType(DuplicateContractClassException::class.java).isThrownBy {
|
||||
anotherContractJar.read { storage.privilegedImportAttachment(it, "app", "another-sample.jar") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can import jar with duplicated contract class, version and signers - when one uploader is trusted and other isnt`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val (contractJar, _) = makeTestSignedContractJar(file.path, "com.example.MyContract")
|
||||
val anotherContractJar = makeTestContractJar(file.path, listOf( "com.example.MyContract", "com.example.AnotherContract"), true, generateManifest = false, jarFileName = "another-sample.jar")
|
||||
val attachmentId = contractJar.read { storage.importAttachment(it, "uploaderA", "sample.jar") }
|
||||
val anotherAttachmentId = anotherContractJar.read { storage.privilegedImportAttachment(it, "app", "another-sample.jar") }
|
||||
assertNotEquals(attachmentId, anotherAttachmentId)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can promote to trusted uploader for the same attachment`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val (contractJar, _) = makeTestSignedContractJar(file.path, "com.example.MyContract")
|
||||
val attachmentId = contractJar.read { storage.importAttachment(it, "uploaderA", "sample.jar") }
|
||||
val reimportedAttachmentId = contractJar.read { storage.privilegedImportAttachment(it, "app", "sample.jar") }
|
||||
assertEquals(attachmentId, reimportedAttachmentId)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cannot promote to trusted uploader if other trusted attachment already has duplicated contract class, version and signers`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val (contractJar, _) = makeTestSignedContractJar(file.path, "com.example.MyContract")
|
||||
contractJar.read { storage.importAttachment(it, "uploaderA", "sample.jar") }
|
||||
val anotherContractJar = makeTestContractJar(file.path, listOf( "com.example.MyContract", "com.example.AnotherContract"), true, generateManifest = false, jarFileName = "another-sample.jar")
|
||||
anotherContractJar.read { storage.privilegedImportAttachment(it, "app", "another-sample.jar") }
|
||||
|
||||
assertThatExceptionOfType(DuplicateContractClassException::class.java).isThrownBy {
|
||||
contractJar.read { storage.privilegedImportAttachment(it, "app", "sample.jar") }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cannot promote to trusted uploder the same jar if other trusted uplodaer `() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val (contractJar, _) = makeTestSignedContractJar(file.path, "com.example.MyContract")
|
||||
val anotherContractJar = makeTestContractJar(file.path, listOf( "com.example.MyContract", "com.example.AnotherContract"), true, generateManifest = false, jarFileName = "another-sample.jar")
|
||||
contractJar.read { storage.privilegedImportAttachment(it, "app", "sample.jar") }
|
||||
|
||||
assertThatExceptionOfType(DuplicateContractClassException::class.java).isThrownBy {
|
||||
anotherContractJar.read { storage.privilegedImportAttachment(it, "app", "another-sample.jar") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can import duplicated contract class and signers if versions differ`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val (contractJar, _) = makeTestSignedContractJar(file.path, "com.example.MyContract", 2)
|
||||
val anotherContractJar = makeTestContractJar(file.path, listOf( "com.example.MyContract", "com.example.AnotherContract"), true, generateManifest = false, jarFileName = "another-sample.jar")
|
||||
contractJar.read { storage.importAttachment(it, "uploaderA", "sample.jar") }
|
||||
anotherContractJar.read { storage.importAttachment(it, "uploaderA", "another-sample.jar") }
|
||||
|
||||
val attachments = storage.queryAttachments(AttachmentsQueryCriteria(contractClassNamesCondition = Builder.equal(listOf("com.example.MyContract"))))
|
||||
assertEquals(2, attachments.size)
|
||||
attachments.forEach {
|
||||
assertTrue("com.example.MyContract" in (storage.openAttachment(it) as ContractAttachment).allContracts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can import duplicated contract class and version from unsiged attachment if a signed attachment already exists`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val (contractJar, _) = makeTestSignedContractJar(file.path, "com.example.MyContract")
|
||||
val anotherContractJar = makeTestContractJar(file.path, listOf( "com.example.MyContract", "com.example.AnotherContract"), generateManifest = false, jarFileName = "another-sample.jar")
|
||||
contractJar.read { storage.importAttachment(it, "uploaderA", "sample.jar") }
|
||||
anotherContractJar.read { storage.importAttachment(it, "uploaderB", "another-sample.jar") }
|
||||
|
||||
val attachments = storage.queryAttachments(AttachmentsQueryCriteria(contractClassNamesCondition = Builder.equal(listOf("com.example.MyContract"))))
|
||||
assertEquals(2, attachments.size)
|
||||
attachments.forEach {
|
||||
val att = storage.openAttachment(it)
|
||||
assertTrue(att is ContractAttachment)
|
||||
assertTrue("com.example.MyContract" in (att as ContractAttachment).allContracts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can import duplicated contract class and version from siged attachment if an unsigned attachment already exists`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val contractJar = makeTestContractJar(file.path, "com.example.MyContract")
|
||||
val anotherContractJar = makeTestContractJar(file.path, listOf( "com.example.MyContract", "com.example.AnotherContract"), true, generateManifest = false, jarFileName = "another-sample.jar")
|
||||
contractJar.read { storage.importAttachment(it, "uploaderA", "sample.jar") }
|
||||
anotherContractJar.read { storage.importAttachment(it, "uploaderB", "another-sample.jar") }
|
||||
|
||||
val attachments = storage.queryAttachments(AttachmentsQueryCriteria(contractClassNamesCondition = Builder.equal(listOf("com.example.MyContract"))))
|
||||
assertEquals(2, attachments.size)
|
||||
attachments.forEach {
|
||||
val att = storage.openAttachment(it)
|
||||
assertTrue(att is ContractAttachment)
|
||||
assertTrue("com.example.MyContract" in (att as ContractAttachment).allContracts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can import duplicated contract class and version for unsigned attachments`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val contractJar = makeTestContractJar(file.path, "com.example.MyContract")
|
||||
val anotherContractJar = makeTestContractJar(file.path, listOf( "com.example.MyContract", "com.example.AnotherContract"), generateManifest = false, jarFileName = "another-sample.jar")
|
||||
contractJar.read { storage.importAttachment(it, "uploaderA", "sample.jar") }
|
||||
anotherContractJar.read { storage.importAttachment(it, "uploaderB", "another-sample.jar") }
|
||||
|
||||
val attachments = storage.queryAttachments(AttachmentsQueryCriteria(contractClassNamesCondition = Builder.equal(listOf("com.example.MyContract"))))
|
||||
assertEquals(2, attachments.size)
|
||||
attachments.forEach {
|
||||
val att = storage.openAttachment(it)
|
||||
assertTrue(att is ContractAttachment)
|
||||
assertTrue("com.example.MyContract" in (att as ContractAttachment).allContracts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sorting and compound conditions work`() {
|
||||
val (jarA, hashA) = makeTestJar(listOf(Pair("a", "a")))
|
||||
|
@ -44,15 +44,20 @@ object ContractJarTestUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun makeTestSignedContractJar(workingDir: Path, contractName: String, version: Int = 1): Pair<Path, PublicKey> {
|
||||
private fun Path.signWithDummyKey(jarName: Path): PublicKey{
|
||||
val alias = "testAlias"
|
||||
val pwd = "testPassword"
|
||||
workingDir.generateKey(alias, pwd, ALICE_NAME.toString())
|
||||
this.generateKey(alias, pwd, ALICE_NAME.toString())
|
||||
val signer = this.signJar(jarName.toAbsolutePath().toString(), alias, pwd)
|
||||
(this / "_shredder").delete()
|
||||
(this / "_teststore").delete()
|
||||
return signer
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun makeTestSignedContractJar(workingDir: Path, contractName: String, version: Int = 1): Pair<Path, PublicKey> {
|
||||
val jarName = makeTestContractJar(workingDir, contractName, true, version)
|
||||
val signer = workingDir.signJar(jarName.toAbsolutePath().toString(), alias, pwd)
|
||||
(workingDir / "_shredder").delete()
|
||||
(workingDir / "_teststore").delete()
|
||||
val signer = workingDir.signWithDummyKey(jarName)
|
||||
return workingDir.resolve(jarName) to signer
|
||||
}
|
||||
|
||||
@ -67,6 +72,23 @@ object ContractJarTestUtils {
|
||||
return workingDir.resolve(jarName)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun makeTestContractJar(workingDir: Path, contractNames: List<String>, signed: Boolean = false, version: Int = 1, generateManifest: Boolean = true, jarFileName : String? = null): Path {
|
||||
contractNames.forEach {
|
||||
val packages = it.split(".")
|
||||
val className = packages.last()
|
||||
createTestClass(workingDir, className, packages.subList(0, packages.size - 1))
|
||||
}
|
||||
val packages = contractNames.first().split(".")
|
||||
val jarName = jarFileName ?: "attachment-${packages.last()}-$version-${(if (signed) "signed" else "")}.jar"
|
||||
workingDir.createJar(jarName, *contractNames.map{ "${it.replace(".", "/")}.class" }.toTypedArray() )
|
||||
if (generateManifest)
|
||||
workingDir.addManifest(jarName, Pair(Attributes.Name(CORDAPP_CONTRACT_VERSION), version.toString()))
|
||||
if (signed)
|
||||
workingDir.signWithDummyKey(workingDir.resolve(jarName))
|
||||
return workingDir.resolve(jarName)
|
||||
}
|
||||
|
||||
private fun createTestClass(workingDir: Path, className: String, packages: List<String>): Path {
|
||||
val newClass = """package ${packages.joinToString(".")};
|
||||
import net.corda.core.contracts.*;
|
||||
|
@ -84,7 +84,7 @@ object JarSignatureTestUtils {
|
||||
manifest.mainAttributes[attributeName] = value
|
||||
}
|
||||
val output = JarOutputStream(FileOutputStream((this / fileName).toFile()), manifest)
|
||||
var entry= input.nextEntry
|
||||
var entry = input.nextEntry
|
||||
val buffer = ByteArray(1 shl 14)
|
||||
while (true) {
|
||||
output.putNextEntry(entry)
|
||||
|
Loading…
Reference in New Issue
Block a user