Merged in rnicoll-cash-common (pull request #75)

Refactor verification of cash contract
This commit is contained in:
Ross Nicoll 2016-05-05 11:14:46 +01:00
commit a5b5ceeb9b
2 changed files with 46 additions and 30 deletions

View File

@ -93,8 +93,32 @@ class Cash : Contract {
"there are no zero sized outputs" by outputs.none { it.amount.pennies == 0L }
}
if (verifyIssueCommands(inputs, outputs, tx, currency, issuer)) continue
val inputAmount = inputs.sumCashOrNull() ?: throw IllegalArgumentException("there is at least one cash input for this group")
val outputAmount = outputs.sumCashOrZero(currency)
// If we want to remove cash from the ledger, that must be signed for by the issuer.
// A mis-signed or duplicated exit command will just be ignored here and result in the exit amount being zero.
val exitCommand = tx.commands.select<Commands.Exit>(party = issuer).singleOrNull()
val amountExitingLedger = exitCommand?.value?.amount ?: Amount(0, currency)
requireThat {
"there are no zero sized inputs" by inputs.none { it.amount.pennies == 0L }
"for deposit ${deposit.reference} at issuer ${deposit.party.name} the amounts balance" by
(inputAmount == outputAmount + amountExitingLedger)
}
verifyMoveCommands<Commands.Move>(inputs, tx)
}
}
private fun verifyIssueCommands(inputs: List<State>, outputs: List<State>, tx: TransactionForVerification, currency: Currency, issuer: Party): Boolean {
val issueCommand = tx.commands.select<Commands.Issue>().singleOrNull()
if (issueCommand != null && outputs.isNotEmpty()) {
if (issueCommand == null || outputs.isEmpty()) {
return false
}
// If we have an issue command, perform special processing: the group is allowed to have no inputs,
// and the output states must have a deposit reference owned by the signer.
//
@ -113,32 +137,7 @@ class Cash : Contract {
"output deposits are owned by a command signer" by (issuer in issueCommand.signingParties)
"output values sum to more than the inputs" by (outputAmount > inputAmount)
}
continue
}
val inputAmount = inputs.sumCashOrNull() ?: throw IllegalArgumentException("there is at least one cash input for this group")
val outputAmount = outputs.sumCashOrZero(currency)
// If we want to remove cash from the ledger, that must be signed for by the issuer.
// A mis-signed or duplicated exit command will just be ignored here and result in the exit amount being zero.
val exitCommand = tx.commands.select<Commands.Exit>(party = issuer).singleOrNull()
val amountExitingLedger = exitCommand?.value?.amount ?: Amount(0, currency)
requireThat {
"there are no zero sized inputs" by inputs.none { it.amount.pennies == 0L }
"for deposit ${deposit.reference} at issuer ${deposit.party.name} the amounts balance" by
(inputAmount == outputAmount + amountExitingLedger)
}
// Now check the digital signatures on the move command. Every input has an owning public key, and we must
// see a signature from each of those keys. The actual signatures have been verified against the transaction
// data by the platform before execution.
val owningPubKeys = inputs.map { it.owner }.toSet()
val keysThatSigned = tx.commands.requireSingleCommand<Commands.Move>().signers.toSet()
requireThat {
"the owning keys are the same as the signing keys" by keysThatSigned.containsAll(owningPubKeys)
}
}
return true
}
/**

View File

@ -81,3 +81,20 @@ fun List<AuthenticatedObject<CommandData>>.getTimestampByName(vararg names: Stri
return null
}
/**
* Simple functionality for verifying a move command. Verifies that each input has a signature from its owning key.
*
* @param T the type of the move command
*/
@Throws(IllegalArgumentException::class)
// TODO: Can we have a common Move command for all contracts and avoid the reified type parameter here?
inline fun <reified T : CommandData> verifyMoveCommands(inputs: List<OwnableState>, tx: TransactionForVerification) {
// Now check the digital signatures on the move command. Every input has an owning public key, and we must
// see a signature from each of those keys. The actual signatures have been verified against the transaction
// data by the platform before execution.
val owningPubKeys = inputs.map { it.owner }.toSet()
val keysThatSigned = tx.commands.requireSingleCommand<T>().signers.toSet()
requireThat {
"the owning keys are the same as the signing keys" by keysThatSigned.containsAll(owningPubKeys)
}
}