mirror of
https://github.com/corda/corda.git
synced 2024-12-19 04:57:58 +00:00
Plumb attachments through to the contract verify functions (no contract uses them yet).
The right way to do this is probably to put the contracts onto the classpath before execution of the contract. However, this interacts closely with the sandboxing work, which isn't yet started, so for now this will do.
This commit is contained in:
parent
1123c28f02
commit
6cb86ab840
@ -14,6 +14,8 @@ import java.util.*
|
||||
class TransactionResolutionException(val hash: SecureHash) : Exception()
|
||||
class TransactionConflictException(val conflictRef: StateRef, val tx1: LedgerTransaction, val tx2: LedgerTransaction) : Exception()
|
||||
|
||||
// TODO: Consider moving this out of the core module and providing a different way for unit tests to test contracts.
|
||||
|
||||
/**
|
||||
* A TransactionGroup defines a directed acyclic graph of transactions that can be resolved with each other and then
|
||||
* verified. Successful verification does not imply the non-existence of other conflicting transactions: simply that
|
||||
@ -49,7 +51,7 @@ class TransactionGroup(val transactions: Set<LedgerTransaction>, val nonVerified
|
||||
// Look up the output in that transaction by index.
|
||||
inputs.add(ltx.outputs[ref.index])
|
||||
}
|
||||
resolved.add(TransactionForVerification(inputs, tx.outputs, tx.commands, tx.hash))
|
||||
resolved.add(TransactionForVerification(inputs, tx.outputs, tx.attachments, tx.commands, tx.hash))
|
||||
}
|
||||
|
||||
for (tx in resolved)
|
||||
@ -62,12 +64,17 @@ class TransactionGroup(val transactions: Set<LedgerTransaction>, val nonVerified
|
||||
/** A transaction in fully resolved and sig-checked form, ready for passing as input to a verification function. */
|
||||
data class TransactionForVerification(val inStates: List<ContractState>,
|
||||
val outStates: List<ContractState>,
|
||||
val attachments: List<Attachment>,
|
||||
val commands: List<AuthenticatedObject<CommandData>>,
|
||||
val origHash: SecureHash) {
|
||||
override fun hashCode() = origHash.hashCode()
|
||||
override fun equals(other: Any?) = other is TransactionForVerification && other.origHash == origHash
|
||||
|
||||
/**
|
||||
* Runs the contracts for this transaction.
|
||||
*
|
||||
* TODO: Move this out of the core data structure definitions, once unit tests are more cleanly separated.
|
||||
*
|
||||
* @throws TransactionVerificationException if a contract throws an exception, the original is in the cause field
|
||||
* @throws IllegalStateException if a state refers to an unknown contract.
|
||||
*/
|
||||
@ -77,6 +84,7 @@ data class TransactionForVerification(val inStates: List<ContractState>,
|
||||
// throws an exception, the entire transaction is invalid.
|
||||
val programHashes = (inStates.map { it.programRef } + outStates.map { it.programRef }).toSet()
|
||||
for (hash in programHashes) {
|
||||
// TODO: Change this interface to ensure that attachment JARs are put on the classpath before execution.
|
||||
val program: Contract = programMap[hash]
|
||||
try {
|
||||
program.verify(this)
|
||||
|
@ -74,14 +74,6 @@ data class WireTransaction(val inputs: List<StateRef>,
|
||||
}
|
||||
}
|
||||
|
||||
fun toLedgerTransaction(identityService: IdentityService): LedgerTransaction {
|
||||
val authenticatedArgs = commands.map {
|
||||
val institutions = it.pubkeys.mapNotNull { pk -> identityService.partyFromKey(pk) }
|
||||
AuthenticatedObject(it.pubkeys, institutions, it.data)
|
||||
}
|
||||
return LedgerTransaction(inputs, attachments, outputs, authenticatedArgs, id)
|
||||
}
|
||||
|
||||
/** Serialises and returns this transaction as a [SignedTransaction] with no signatures attached. */
|
||||
fun toSignedTransaction(withSigs: List<DigitalSignature.WithKey>): SignedTransaction {
|
||||
return SignedTransaction(serialized, withSigs)
|
||||
@ -154,15 +146,6 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
|
||||
return missing
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls [verify] to check all required signatures are present, and then uses the passed [IdentityService] to call
|
||||
* [WireTransaction.toLedgerTransaction] to look up well known identities from pubkeys.
|
||||
*/
|
||||
fun verifyToLedgerTransaction(identityService: IdentityService): LedgerTransaction {
|
||||
verify()
|
||||
return tx.toLedgerTransaction(identityService)
|
||||
}
|
||||
|
||||
/** Returns the same transaction but with an additional (unchecked) signature */
|
||||
fun withAdditionalSignature(sig: DigitalSignature.WithKey) = copy(sigs = sigs + sig)
|
||||
|
||||
@ -308,12 +291,14 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
|
||||
* A LedgerTransaction wraps the data needed to calculate one or more successor states from a set of input states.
|
||||
* It is the first step after extraction from a WireTransaction. The signatures at this point have been lined up
|
||||
* with the commands from the wire, and verified/looked up.
|
||||
*
|
||||
* TODO: This class needs a bit more thought. Should inputs be fully resolved by this point too?
|
||||
*/
|
||||
data class LedgerTransaction(
|
||||
/** The input states which will be consumed/invalidated by the execution of this transaction. */
|
||||
val inputs: List<StateRef>,
|
||||
/** A list of [Attachment] ids that need to be available for this transaction to verify. */
|
||||
val attachments: List<SecureHash>,
|
||||
/** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
|
||||
val attachments: List<Attachment>,
|
||||
/** The states that will be generated by the execution of this transaction. */
|
||||
val outputs: List<ContractState>,
|
||||
/** Arbitrary data passed to the program of each input state. */
|
||||
@ -325,7 +310,7 @@ data class LedgerTransaction(
|
||||
fun <T : ContractState> outRef(index: Int) = StateAndRef(outputs[index] as T, StateRef(hash, index))
|
||||
|
||||
fun toWireTransaction(): WireTransaction {
|
||||
val wtx = WireTransaction(inputs, attachments, outputs, commands.map { Command(it.value, it.signers) })
|
||||
val wtx = WireTransaction(inputs, attachments.map { it.id }, outputs, commands.map { Command(it.value, it.signers) })
|
||||
check(wtx.serialize().hash == hash)
|
||||
return wtx
|
||||
}
|
||||
|
@ -9,10 +9,7 @@
|
||||
package contracts.protocols
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import core.LedgerTransaction
|
||||
import core.SignedTransaction
|
||||
import core.TransactionGroup
|
||||
import core.WireTransaction
|
||||
import core.*
|
||||
import core.crypto.SecureHash
|
||||
import core.messaging.SingleMessageRecipient
|
||||
import core.protocols.ProtocolLogic
|
||||
@ -63,9 +60,9 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
|
||||
|
||||
if (stx != null) {
|
||||
// Check the signatures on the stx first.
|
||||
toVerify += stx!!.verifyToLedgerTransaction(serviceHub.identityService)
|
||||
toVerify += stx!!.verifyToLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments)
|
||||
} else if (wtx != null) {
|
||||
wtx!!.toLedgerTransaction(serviceHub.identityService)
|
||||
wtx!!.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments)
|
||||
}
|
||||
|
||||
// Run all the contracts and throw an exception if any of them reject.
|
||||
@ -116,10 +113,14 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
|
||||
resolveMissingAttachments(downloads)
|
||||
|
||||
// Resolve any legal identities from known public keys in the signatures.
|
||||
val downloadedTxns = downloads.map { it.verifyToLedgerTransaction(serviceHub.identityService) }
|
||||
val downloadedTxns = downloads.map {
|
||||
it.verifyToLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments)
|
||||
}
|
||||
|
||||
// Do the same for transactions loaded from disk (i.e. we checked them previously).
|
||||
val loadedTxns = fromDisk.map { it.verifyToLedgerTransaction(serviceHub.identityService) }
|
||||
val loadedTxns = fromDisk.map {
|
||||
it.verifyToLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments)
|
||||
}
|
||||
|
||||
toVerify.addAll(downloadedTxns)
|
||||
alreadyVerified.addAll(loadedTxns)
|
||||
|
@ -144,7 +144,7 @@ object TwoPartyTradeProtocol {
|
||||
checkDependencies(it)
|
||||
|
||||
// This verifies that the transaction is contract-valid, even though it is missing signatures.
|
||||
serviceHub.verifyTransaction(wtx.toLedgerTransaction(serviceHub.identityService))
|
||||
serviceHub.verifyTransaction(wtx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments))
|
||||
|
||||
if (wtx.outputs.sumCashBy(myKeyPair.public) != price)
|
||||
throw IllegalArgumentException("Transaction is not sending us the right amounnt of cash")
|
||||
|
@ -164,7 +164,7 @@ interface ServiceHub {
|
||||
val dependencies = ltx.inputs.map {
|
||||
storageService.validatedTransactions[it.txhash] ?: throw TransactionResolutionException(it.txhash)
|
||||
}
|
||||
val ltxns = dependencies.map { it.verifyToLedgerTransaction(identityService) }
|
||||
val ltxns = dependencies.map { it.verifyToLedgerTransaction(identityService, storageService.attachments) }
|
||||
TransactionGroup(setOf(ltx), ltxns.toSet()).verify(storageService.contractPrograms)
|
||||
}
|
||||
}
|
||||
|
38
src/main/kotlin/core/TransactionTools.kt
Normal file
38
src/main/kotlin/core/TransactionTools.kt
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
|
||||
* pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
|
||||
* set forth therein.
|
||||
*
|
||||
* All other rights reserved.
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
/**
|
||||
* Looks up identities and attachments from storage to generate a [LedgerTransaction].
|
||||
*
|
||||
* @throws FileNotFoundException if a required transaction was not found in storage.
|
||||
*/
|
||||
fun WireTransaction.toLedgerTransaction(identityService: IdentityService,
|
||||
attachmentStorage: AttachmentStorage): LedgerTransaction {
|
||||
val authenticatedArgs = commands.map {
|
||||
val institutions = it.pubkeys.mapNotNull { pk -> identityService.partyFromKey(pk) }
|
||||
AuthenticatedObject(it.pubkeys, institutions, it.data)
|
||||
}
|
||||
val attachments = attachments.map {
|
||||
attachmentStorage.openAttachment(it) ?: throw FileNotFoundException(it.toString())
|
||||
}
|
||||
return LedgerTransaction(inputs, attachments, outputs, authenticatedArgs, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls [verify] to check all required signatures are present, and then uses the passed [IdentityService] to call
|
||||
* [WireTransaction.toLedgerTransaction] to look up well known identities from pubkeys.
|
||||
*/
|
||||
fun SignedTransaction.verifyToLedgerTransaction(identityService: IdentityService,
|
||||
attachmentStorage: AttachmentStorage): LedgerTransaction {
|
||||
verify()
|
||||
return tx.toLedgerTransaction(identityService, attachmentStorage)
|
||||
}
|
@ -63,6 +63,8 @@ class CommercialPaperTestsGeneric {
|
||||
@Parameterized.Parameter
|
||||
lateinit var thisTest: ICommercialPaperTestTemplate
|
||||
|
||||
val attachments = MockStorageService().attachments
|
||||
|
||||
@Test
|
||||
fun ok() {
|
||||
trade().verify()
|
||||
@ -176,7 +178,7 @@ class CommercialPaperTestsGeneric {
|
||||
timestamp(DUMMY_TIMESTAMPER)
|
||||
}
|
||||
val stx = ptx.toSignedTransaction()
|
||||
stx.verifyToLedgerTransaction(MockIdentityService)
|
||||
stx.verifyToLedgerTransaction(MockIdentityService, attachments)
|
||||
}
|
||||
|
||||
val (alicesWalletTX, alicesWallet) = cashOutputsToWallet(
|
||||
@ -193,7 +195,7 @@ class CommercialPaperTestsGeneric {
|
||||
ptx.signWith(MINI_CORP_KEY)
|
||||
ptx.signWith(ALICE_KEY)
|
||||
val stx = ptx.toSignedTransaction()
|
||||
stx.verifyToLedgerTransaction(MockIdentityService)
|
||||
stx.verifyToLedgerTransaction(MockIdentityService, attachments)
|
||||
}
|
||||
|
||||
// Won't be validated.
|
||||
@ -209,7 +211,7 @@ class CommercialPaperTestsGeneric {
|
||||
ptx.signWith(ALICE_KEY)
|
||||
ptx.signWith(MINI_CORP_KEY)
|
||||
ptx.timestamp(DUMMY_TIMESTAMPER)
|
||||
return ptx.toSignedTransaction().verifyToLedgerTransaction(MockIdentityService)
|
||||
return ptx.toSignedTransaction().verifyToLedgerTransaction(MockIdentityService, attachments)
|
||||
}
|
||||
|
||||
val tooEarlyRedemption = makeRedeemTX(TEST_TX_TIME + 10.days)
|
||||
|
@ -29,6 +29,8 @@ class CrowdFundTests {
|
||||
pledges = ArrayList<CrowdFund.Pledge>()
|
||||
)
|
||||
|
||||
val attachments = MockStorageService().attachments
|
||||
|
||||
@Test
|
||||
fun `key mismatch at issue`() {
|
||||
transactionGroup {
|
||||
@ -114,7 +116,7 @@ class CrowdFundTests {
|
||||
timestamp(DUMMY_TIMESTAMPER)
|
||||
}
|
||||
val stx = ptx.toSignedTransaction()
|
||||
stx.verifyToLedgerTransaction(MockIdentityService)
|
||||
stx.verifyToLedgerTransaction(MockIdentityService, attachments)
|
||||
}
|
||||
|
||||
// let's give Alice some funds that she can invest
|
||||
@ -134,7 +136,7 @@ class CrowdFundTests {
|
||||
ptx.timestamp(DUMMY_TIMESTAMPER)
|
||||
val stx = ptx.toSignedTransaction()
|
||||
// this verify passes - the transaction contains an output cash, necessary to verify the fund command
|
||||
stx.verifyToLedgerTransaction(MockIdentityService)
|
||||
stx.verifyToLedgerTransaction(MockIdentityService, attachments)
|
||||
}
|
||||
|
||||
// Won't be validated.
|
||||
@ -150,7 +152,7 @@ class CrowdFundTests {
|
||||
ptx.signWith(MINI_CORP_KEY)
|
||||
ptx.timestamp(DUMMY_TIMESTAMPER)
|
||||
val stx = ptx.toSignedTransaction()
|
||||
return stx.verifyToLedgerTransaction(MockIdentityService)
|
||||
return stx.verifyToLedgerTransaction(MockIdentityService, attachments)
|
||||
}
|
||||
|
||||
val tooEarlyClose = makeFundedTX(TEST_TX_TIME + 6.days)
|
||||
|
@ -152,7 +152,7 @@ class TransactionGroupTests {
|
||||
}.signAll()
|
||||
|
||||
// Now go through the conversion -> verification path with them.
|
||||
val ltxns = signedTxns.map { it.verifyToLedgerTransaction(MockIdentityService) }.toSet()
|
||||
val ltxns = signedTxns.map { it.verifyToLedgerTransaction(MockIdentityService, MockStorageService().attachments) }.toSet()
|
||||
TransactionGroup(ltxns, emptySet()).verify(MockContractFactory)
|
||||
}
|
||||
}
|
@ -63,7 +63,7 @@ class NodeWalletServiceTest {
|
||||
Cash().generateIssue(this, 100.DOLLARS, MEGA_CORP.ref(1), freshKey.public)
|
||||
signWith(MEGA_CORP_KEY)
|
||||
}.toSignedTransaction()
|
||||
val myOutput = usefulTX.verifyToLedgerTransaction(MockIdentityService).outRef<Cash.State>(0)
|
||||
val myOutput = usefulTX.verifyToLedgerTransaction(MockIdentityService, MockStorageService().attachments).outRef<Cash.State>(0)
|
||||
|
||||
// A tx that spends our money.
|
||||
val spendTX = TransactionBuilder().apply {
|
||||
|
@ -93,7 +93,7 @@ class TransactionSerializationTests {
|
||||
tx.timestamp(DUMMY_TIMESTAMPER)
|
||||
tx.signWith(TestUtils.keypair)
|
||||
val stx = tx.toSignedTransaction()
|
||||
val ltx = stx.verifyToLedgerTransaction(MockIdentityService)
|
||||
val ltx = stx.verifyToLedgerTransaction(MockIdentityService, MockStorageService().attachments)
|
||||
assertEquals(tx.commands().map { it.data }, ltx.commands.map { it.value })
|
||||
assertEquals(tx.inputStates(), ltx.inputs)
|
||||
assertEquals(tx.outputStates(), ltx.outputs)
|
||||
|
@ -150,7 +150,7 @@ open class TransactionForTest : AbstractTransactionForTest() {
|
||||
|
||||
protected fun run(time: Instant) {
|
||||
val cmds = commandsToAuthenticatedObjects()
|
||||
val tx = TransactionForVerification(inStates, outStates.map { it.state }, cmds, SecureHash.randomSHA256())
|
||||
val tx = TransactionForVerification(inStates, outStates.map { it.state }, emptyList(), cmds, SecureHash.randomSHA256())
|
||||
tx.verify(MockContractFactory)
|
||||
}
|
||||
|
||||
@ -299,8 +299,8 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
|
||||
fun transactionGroup(body: TransactionGroupDSL<T>.() -> Unit) {}
|
||||
|
||||
fun toTransactionGroup() = TransactionGroup(
|
||||
txns.map { it.toLedgerTransaction(MockIdentityService) }.toSet(),
|
||||
rootTxns.map { it.toLedgerTransaction(MockIdentityService) }.toSet()
|
||||
txns.map { it.toLedgerTransaction(MockIdentityService, MockStorageService().attachments) }.toSet(),
|
||||
rootTxns.map { it.toLedgerTransaction(MockIdentityService, MockStorageService().attachments) }.toSet()
|
||||
)
|
||||
|
||||
class Failed(val index: Int, cause: Throwable) : Exception("Transaction $index didn't verify", cause)
|
||||
|
Loading…
Reference in New Issue
Block a user