From e06480017320a776dac7306ea27889b272c2cf96 Mon Sep 17 00:00:00 2001 From: josecoll Date: Tue, 30 Oct 2018 15:44:29 +0000 Subject: [PATCH] CORDA-2112 Update the `uploader` field for Attachments loaded upon Node CorDapp startup (#4127) * CORDA-2112 Update the `uploader` field upon Node startup for CorDapps where an Attachment is already in a nodes attachment store (from an untrusted source). * Removed incorrect assertion. * Fix broken unit test. * Reverted back throw FileAlreadyExistsException API semantics on public API. * De-duplicate DuplicateAttachmentException * Revert original Exception handling logic. --- .../persistence/AttachmentStorageInternal.kt | 6 ++++ .../persistence/NodeAttachmentService.kt | 30 ++++++++++++++----- .../persistence/NodeAttachmentServiceTest.kt | 26 ++++++++++++++++ 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/AttachmentStorageInternal.kt b/node/src/main/kotlin/net/corda/node/services/persistence/AttachmentStorageInternal.kt index 0ca5cd2ffb..0d9a24c898 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/AttachmentStorageInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/AttachmentStorageInternal.kt @@ -2,6 +2,7 @@ package net.corda.node.services.persistence import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentStorage +import net.corda.nodeapi.exceptions.DuplicateAttachmentException import java.io.InputStream interface AttachmentStorageInternal : AttachmentStorage { @@ -10,4 +11,9 @@ interface AttachmentStorageInternal : AttachmentStorage { * and is only for the node. */ fun privilegedImportAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId + + /** + * Similar to above but returns existing [AttachmentId] instead of throwing [DuplicateAttachmentException] + */ + fun privilegedImportOrGetAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId } diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt index 1ea12ffb15..37f5a38d6c 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt @@ -6,13 +6,11 @@ import com.google.common.hash.HashCode import com.google.common.hash.Hashing import com.google.common.hash.HashingInputStream import com.google.common.io.CountingInputStream -import net.corda.core.ClientRelevantError import net.corda.core.CordaRuntimeException import net.corda.core.contracts.Attachment import net.corda.core.contracts.ContractAttachment import net.corda.core.contracts.ContractClassName import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.isFulfilledBy import net.corda.core.crypto.sha256 import net.corda.core.internal.* import net.corda.core.node.ServicesForResolution @@ -286,6 +284,14 @@ class NodeAttachmentService( return import(jar, uploader, filename) } + override fun privilegedImportOrGetAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId { + return try { + import(jar, uploader, filename) + } catch (faee: java.nio.file.FileAlreadyExistsException) { + AttachmentId.parse(faee.message!!) + } + } + override fun hasAttachment(attachmentId: AttachmentId): Boolean = database.transaction { currentDBSession().find(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString()) != null } @@ -306,9 +312,7 @@ class NodeAttachmentService( val id = bytes.sha256() if (!hasAttachment(id)) { checkIsAValidJAR(bytes.inputStream()) - val jarSigners = getSigners(bytes) - val session = currentDBSession() val attachment = NodeAttachmentService.DBAttachment( attId = id.toString(), @@ -318,14 +322,24 @@ class NodeAttachmentService( contractClassNames = contractClassNames, signers = jarSigners ) - session.save(attachment) attachmentCount.inc() log.info("Stored new attachment $id") - id - } else { - throw DuplicateAttachmentException(id.toString()) + return@withContractsInJar id } + if (isUploaderTrusted(uploader)) { + val session = currentDBSession() + 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) { + attachment.uploader = uploader + session.saveOrUpdate(attachment) + log.info("Updated attachment $id with uploader $uploader") + attachmentCache.invalidate(id) + attachmentContentCache.invalidate(id) + } + } + throw DuplicateAttachmentException(id.toString()) } } } diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt index e87d60de3c..bd584796ba 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt @@ -9,6 +9,7 @@ import com.nhaarman.mockito_kotlin.whenever import net.corda.core.JarSignatureTestUtils.createJar import net.corda.core.JarSignatureTestUtils.generateKey import net.corda.core.JarSignatureTestUtils.signJar +import net.corda.core.contracts.ContractAttachment import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 import net.corda.core.flows.FlowLogic @@ -47,6 +48,7 @@ import javax.tools.StandardLocation import javax.tools.ToolProvider import kotlin.test.assertEquals import kotlin.test.assertFailsWith +import kotlin.test.assertNotEquals import kotlin.test.assertNull @@ -123,6 +125,30 @@ class NodeAttachmentServiceTest { } } + @Test + fun `insert contract attachment as an untrusted uploader and then as trusted CorDapp uploader`() { + val contractJarName = makeTestContractJar("com.example.MyContract") + val testJar = dir.resolve(contractJarName) + val expectedHash = testJar.readAll().sha256() + + // PRIVILEGED_UPLOADERS = listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER, P2P_UPLOADER, UNKNOWN_UPLOADER) + // TRUSTED_UPLOADERS = listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER) + + database.transaction { + val id = testJar.read { storage.privilegedImportOrGetAttachment(it, P2P_UPLOADER, null) } + assertEquals(expectedHash, id) + val attachment1 = storage.openAttachment(expectedHash) + + val id2 = testJar.read { storage.privilegedImportOrGetAttachment(it, DEPLOYED_CORDAPP_UPLOADER, null) } + assertEquals(expectedHash, id2) + val attachment2 = storage.openAttachment(expectedHash) + + assertNotEquals(attachment1, attachment2) + assertEquals(P2P_UPLOADER, (attachment1 as ContractAttachment).uploader) + assertEquals(DEPLOYED_CORDAPP_UPLOADER, (attachment2 as ContractAttachment).uploader) + } + } + @Test fun `missing is not cached`() { val (testJar, expectedHash) = makeTestJar()