From 99f8477bf109b7ba5f9477c5adcca5cb86d88f56 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Tue, 6 Sep 2016 12:31:23 +0200 Subject: [PATCH] Minor: move code from TransactionTools.kt into the respective classes, and add some @Throws annotations that were missing (matters for java users) --- .../core/contracts/TransactionTools.kt | 42 ------------------ .../core/transactions/SignedTransaction.kt | 18 ++++++++ .../core/transactions/WireTransaction.kt | 24 ++++++++++ .../protocols/ResolveTransactionsProtocol.kt | 1 - .../protocols/ValidatingNotaryProtocol.kt | 1 - .../com/r3corda/core/node/isolated.jar | Bin 8270 -> 8278 bytes .../contracts/AccountReceivableTests.kt | 1 - 7 files changed, 42 insertions(+), 45 deletions(-) delete mode 100644 core/src/main/kotlin/com/r3corda/core/contracts/TransactionTools.kt diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/TransactionTools.kt b/core/src/main/kotlin/com/r3corda/core/contracts/TransactionTools.kt deleted file mode 100644 index ad4ff4f6cd..0000000000 --- a/core/src/main/kotlin/com/r3corda/core/contracts/TransactionTools.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.r3corda.core.contracts - -import com.r3corda.core.node.ServiceHub -import com.r3corda.core.transactions.LedgerTransaction -import com.r3corda.core.transactions.SignedTransaction -import com.r3corda.core.transactions.WireTransaction -import java.io.FileNotFoundException - -// TODO: Move these into the actual classes (i.e. where people would expect to find them) and split Transactions.kt into multiple files - -/** - * Looks up identities and attachments from storage to generate a [LedgerTransaction]. A transaction is expected to - * have been fully resolved using the resolution protocol by this point. - * - * @throws FileNotFoundException if a required attachment was not found in storage. - * @throws TransactionResolutionException if an input points to a transaction not found in storage. - */ -fun WireTransaction.toLedgerTransaction(services: ServiceHub): LedgerTransaction { - // Look up random keys to authenticated identities. This is just a stub placeholder and will all change in future. - val authenticatedArgs = commands.map { - val parties = it.signers.mapNotNull { pk -> services.identityService.partyFromKey(pk) } - AuthenticatedObject(it.signers, parties, it.value) - } - // Open attachments specified in this transaction. If we haven't downloaded them, we fail. - val attachments = attachments.map { - services.storageService.attachments.openAttachment(it) ?: throw FileNotFoundException(it.toString()) - } - val resolvedInputs = inputs.map { StateAndRef(services.loadState(it), it) } - return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, signers, timestamp, type) -} - -/** - * Calls [verify] to check all required signatures are present, and then calls [WireTransaction.toLedgerTransaction] - * with the passed in [ServiceHub] to resolve the dependencies, returning an unverified LedgerTransaction. - * - * @throws FileNotFoundException if a required attachment was not found in storage. - * @throws TransactionResolutionException if an input points to a transaction not found in storage. - */ -fun SignedTransaction.toLedgerTransaction(services: ServiceHub): LedgerTransaction { - verifySignatures() - return tx.toLedgerTransaction(services) -} diff --git a/core/src/main/kotlin/com/r3corda/core/transactions/SignedTransaction.kt b/core/src/main/kotlin/com/r3corda/core/transactions/SignedTransaction.kt index f740afa0d8..982fa87a9d 100644 --- a/core/src/main/kotlin/com/r3corda/core/transactions/SignedTransaction.kt +++ b/core/src/main/kotlin/com/r3corda/core/transactions/SignedTransaction.kt @@ -1,10 +1,13 @@ package com.r3corda.core.transactions import com.r3corda.core.contracts.NamedByHash +import com.r3corda.core.contracts.TransactionResolutionException import com.r3corda.core.crypto.DigitalSignature import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.toStringsShort +import com.r3corda.core.node.ServiceHub import com.r3corda.core.serialization.SerializedBytes +import java.io.FileNotFoundException import java.security.PublicKey import java.security.SignatureException import java.util.* @@ -38,6 +41,7 @@ data class SignedTransaction(val txBits: SerializedBytes, * * @throws SignatureException if a signature is invalid, does not match or if any signature is missing. */ + @Throws(SignatureException::class) fun verifySignatures(throwIfSignaturesAreMissing: Boolean = true): Set { // Embedded WireTransaction is not deserialised until after we check the signatures. for (sig in sigs) @@ -97,4 +101,18 @@ data class SignedTransaction(val txBits: SerializedBytes, if (sigKeys.containsAll(requiredKeys)) return emptySet() return requiredKeys - sigKeys } + + /** + * Calls [verifySignatures] to check all required signatures are present, and then calls + * [WireTransaction.toLedgerTransaction] with the passed in [ServiceHub] to resolve the dependencies, + * returning an unverified LedgerTransaction. + * + * @throws FileNotFoundException if a required attachment was not found in storage. + * @throws TransactionResolutionException if an input points to a transaction not found in storage. + */ + @Throws(FileNotFoundException::class, TransactionResolutionException::class) + fun toLedgerTransaction(services: ServiceHub): LedgerTransaction { + verifySignatures() + return tx.toLedgerTransaction(services) + } } diff --git a/core/src/main/kotlin/com/r3corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/com/r3corda/core/transactions/WireTransaction.kt index 672a97db9e..04d818a021 100644 --- a/core/src/main/kotlin/com/r3corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/com/r3corda/core/transactions/WireTransaction.kt @@ -5,11 +5,13 @@ import com.r3corda.core.contracts.* import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.SecureHash import com.r3corda.core.indexOfOrThrow +import com.r3corda.core.node.ServiceHub import com.r3corda.core.serialization.SerializedBytes import com.r3corda.core.serialization.THREAD_LOCAL_KRYO import com.r3corda.core.serialization.deserialize import com.r3corda.core.serialization.serialize import com.r3corda.core.utilities.Emoji +import java.io.FileNotFoundException import java.security.PublicKey /** @@ -59,4 +61,26 @@ data class WireTransaction(val inputs: List, for (attachment in attachments) buf.appendln("${Emoji.paperclip}ATTACHMENT: $attachment") return buf.toString() } + + /** + * Looks up identities and attachments from storage to generate a [LedgerTransaction]. A transaction is expected to + * have been fully resolved using the resolution protocol by this point. + * + * @throws FileNotFoundException if a required attachment was not found in storage. + * @throws TransactionResolutionException if an input points to a transaction not found in storage. + */ + @Throws(FileNotFoundException::class, TransactionResolutionException::class) + fun toLedgerTransaction(services: ServiceHub): LedgerTransaction { + // Look up public keys to authenticated identities. This is just a stub placeholder and will all change in future. + val authenticatedArgs = commands.map { + val parties = it.signers.mapNotNull { pk -> services.identityService.partyFromKey(pk) } + AuthenticatedObject(it.signers, parties, it.value) + } + // Open attachments specified in this transaction. If we haven't downloaded them, we fail. + val attachments = attachments.map { + services.storageService.attachments.openAttachment(it) ?: throw FileNotFoundException(it.toString()) + } + val resolvedInputs = inputs.map { StateAndRef(services.loadState(it), it) } + return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, signers, timestamp, type) + } } diff --git a/core/src/main/kotlin/com/r3corda/protocols/ResolveTransactionsProtocol.kt b/core/src/main/kotlin/com/r3corda/protocols/ResolveTransactionsProtocol.kt index 3ef021bbbe..47d8306355 100644 --- a/core/src/main/kotlin/com/r3corda/protocols/ResolveTransactionsProtocol.kt +++ b/core/src/main/kotlin/com/r3corda/protocols/ResolveTransactionsProtocol.kt @@ -5,7 +5,6 @@ import com.r3corda.core.checkedAdd import com.r3corda.core.transactions.LedgerTransaction import com.r3corda.core.transactions.SignedTransaction import com.r3corda.core.transactions.WireTransaction -import com.r3corda.core.contracts.toLedgerTransaction import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.SecureHash import com.r3corda.core.protocols.ProtocolLogic diff --git a/core/src/main/kotlin/com/r3corda/protocols/ValidatingNotaryProtocol.kt b/core/src/main/kotlin/com/r3corda/protocols/ValidatingNotaryProtocol.kt index ac913a5fb5..a66811a402 100644 --- a/core/src/main/kotlin/com/r3corda/protocols/ValidatingNotaryProtocol.kt +++ b/core/src/main/kotlin/com/r3corda/protocols/ValidatingNotaryProtocol.kt @@ -4,7 +4,6 @@ import co.paralleluniverse.fibers.Suspendable import com.r3corda.core.transactions.SignedTransaction import com.r3corda.core.contracts.TransactionVerificationException import com.r3corda.core.transactions.WireTransaction -import com.r3corda.core.contracts.toLedgerTransaction import com.r3corda.core.crypto.Party import com.r3corda.core.node.services.TimestampChecker import com.r3corda.core.node.services.UniquenessProvider diff --git a/core/src/main/resources/com/r3corda/core/node/isolated.jar b/core/src/main/resources/com/r3corda/core/node/isolated.jar index 93f4c94cbd151e4e8b8c28cabdc56b5f4957420a..271f8fa554beba52a6f09004de932bde75f7e264 100644 GIT binary patch delta 5025 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 Cr-)Gi delta 5017 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