mirror of
https://github.com/corda/corda.git
synced 2025-01-18 02:39:51 +00:00
Small cleanup of commands.
1. Rename Command -> CommandData, WireCommand -> Command, PartialTransaction.addArg -> addCommand 2. Add some helper functions to PartialTransaction to make creation of transactions simpler.
This commit is contained in:
parent
4cec5dac02
commit
6144ccc2c7
@ -66,8 +66,8 @@ class Cash : Contract {
|
||||
}
|
||||
|
||||
// Just for grouping
|
||||
interface Commands : Command {
|
||||
class Move() : TypeOnlyCommand(), Commands
|
||||
interface Commands : CommandData {
|
||||
class Move() : TypeOnlyCommandData(), Commands
|
||||
|
||||
/**
|
||||
* Allows new cash states to be issued into existence: the nonce ("number used once") ensures the transaction
|
||||
@ -155,7 +155,7 @@ class Cash : Contract {
|
||||
check(tx.inputStates().isEmpty())
|
||||
check(tx.outputStates().sumCashOrNull() == null)
|
||||
tx.addOutputState(Cash.State(at, amount, owner))
|
||||
tx.addArg(WireCommand(Cash.Commands.Issue(), listOf(at.party.owningKey)))
|
||||
tx.addCommand(Cash.Commands.Issue(), at.party.owningKey)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -233,7 +233,7 @@ class Cash : Contract {
|
||||
for (state in outputs) tx.addOutputState(state)
|
||||
// What if we already have a move command with the right keys? Filter it out here or in platform code?
|
||||
val keysList = keysUsed.toList()
|
||||
tx.addArg(WireCommand(Commands.Move(), keysList))
|
||||
tx.addCommand(Commands.Move(), keysList)
|
||||
return keysList
|
||||
}
|
||||
}
|
||||
|
@ -53,12 +53,12 @@ class CommercialPaper : Contract {
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
}
|
||||
|
||||
interface Commands : Command {
|
||||
class Move : TypeOnlyCommand(), Commands
|
||||
class Redeem : TypeOnlyCommand(), Commands
|
||||
interface Commands : CommandData {
|
||||
class Move : TypeOnlyCommandData(), Commands
|
||||
class Redeem : TypeOnlyCommandData(), Commands
|
||||
// We don't need a nonce in the issue command, because the issuance.reference field should already be unique per CP.
|
||||
// However, nothing in the platform enforces that uniqueness: it's up to the issuer.
|
||||
class Issue : TypeOnlyCommand(), Commands
|
||||
class Issue : TypeOnlyCommandData(), Commands
|
||||
}
|
||||
|
||||
override fun verify(tx: TransactionForVerification) {
|
||||
@ -119,7 +119,7 @@ class CommercialPaper : Contract {
|
||||
*/
|
||||
fun craftIssue(issuance: PartyReference, faceValue: Amount, maturityDate: Instant): PartialTransaction {
|
||||
val state = State(issuance, issuance.party.owningKey, faceValue, maturityDate)
|
||||
return PartialTransaction().withItems(state, WireCommand(Commands.Issue(), issuance.party.owningKey))
|
||||
return PartialTransaction().withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -128,7 +128,7 @@ class CommercialPaper : Contract {
|
||||
fun craftMove(tx: PartialTransaction, paper: StateAndRef<State>, newOwner: PublicKey) {
|
||||
tx.addInputState(paper.ref)
|
||||
tx.addOutputState(paper.state.copy(owner = newOwner))
|
||||
tx.addArg(WireCommand(Commands.Move(), paper.state.owner))
|
||||
tx.addCommand(Commands.Move(), paper.state.owner)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -143,7 +143,7 @@ class CommercialPaper : Contract {
|
||||
// Add the cash movement using the states in our wallet.
|
||||
Cash().craftSpend(tx, paper.state.faceValue, paper.state.owner, wallet)
|
||||
tx.addInputState(paper.ref)
|
||||
tx.addArg(WireCommand(CommercialPaper.Commands.Redeem(), paper.state.owner))
|
||||
tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.owner)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,10 +66,10 @@ class CrowdFund : Contract {
|
||||
)
|
||||
|
||||
|
||||
interface Commands : Command {
|
||||
class Register : TypeOnlyCommand(), Commands
|
||||
class Pledge : TypeOnlyCommand(), Commands
|
||||
class Close : TypeOnlyCommand(), Commands
|
||||
interface Commands : CommandData {
|
||||
class Register : TypeOnlyCommandData(), Commands
|
||||
class Pledge : TypeOnlyCommandData(), Commands
|
||||
class Close : TypeOnlyCommandData(), Commands
|
||||
}
|
||||
|
||||
override fun verify(tx: TransactionForVerification) {
|
||||
@ -146,7 +146,7 @@ class CrowdFund : Contract {
|
||||
fun craftRegister(owner: PartyReference, fundingTarget: Amount, fundingName: String, closingTime: Instant): PartialTransaction {
|
||||
val campaign = Campaign(owner = owner.party.owningKey, name = fundingName, target = fundingTarget, closingTime = closingTime)
|
||||
val state = State(campaign)
|
||||
return PartialTransaction().withItems(state, WireCommand(Commands.Register(), owner.party.owningKey))
|
||||
return PartialTransaction().withItems(state, Command(Commands.Register(), owner.party.owningKey))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -157,13 +157,13 @@ class CrowdFund : Contract {
|
||||
tx.addOutputState(campaign.state.copy(
|
||||
pledges = campaign.state.pledges + CrowdFund.Pledge(subscriber, 1000.DOLLARS)
|
||||
))
|
||||
tx.addArg(WireCommand(Commands.Pledge(), subscriber))
|
||||
tx.addCommand(Commands.Pledge(), subscriber)
|
||||
}
|
||||
|
||||
fun craftClose(tx: PartialTransaction, campaign: StateAndRef<State>, wallet: List<StateAndRef<Cash.State>>) {
|
||||
tx.addInputState(campaign.ref)
|
||||
tx.addOutputState(campaign.state.copy(closed = true))
|
||||
tx.addArg(WireCommand(Commands.Close(), campaign.state.campaign.owner))
|
||||
tx.addCommand(Commands.Close(), campaign.state.campaign.owner)
|
||||
// If campaign target has not been met, compose cash returns
|
||||
if (campaign.state.pledgedAmount < campaign.state.campaign.target) {
|
||||
for (pledge in campaign.state.pledges) {
|
||||
|
@ -91,7 +91,7 @@ public class JavaCommercialPaper implements Contract {
|
||||
}
|
||||
}
|
||||
|
||||
public static class Commands implements core.Command {
|
||||
public static class Commands implements core.CommandData {
|
||||
public static class Move extends Commands {
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
@ -114,7 +114,7 @@ public class JavaCommercialPaper implements Contract {
|
||||
List<InOutGroup<State>> groups = tx.groupStates(State.class, State::withoutOwner);
|
||||
|
||||
// Find the command that instructs us what to do and check there's exactly one.
|
||||
AuthenticatedObject<Command> cmd = requireSingleCommand(tx.getCommands(), Commands.class);
|
||||
AuthenticatedObject<CommandData> cmd = requireSingleCommand(tx.getCommands(), Commands.class);
|
||||
|
||||
Instant time = tx.getTime(); // Can be null/missing.
|
||||
|
||||
|
@ -167,7 +167,7 @@ private class TwoPartyTradeProtocolImpl(private val smm: StateMachineManager) :
|
||||
val freshKey = serviceHub.keyManagementService.freshKey()
|
||||
val (command, state) = tradeRequest.assetForSale.state.withNewOwner(freshKey.public)
|
||||
ptx.addOutputState(state)
|
||||
ptx.addArg(WireCommand(command, tradeRequest.assetForSale.state.owner))
|
||||
ptx.addCommand(command, tradeRequest.assetForSale.state.owner)
|
||||
|
||||
// Now sign the transaction with whatever keys we need to move the cash.
|
||||
for (k in cashSigningPubKeys) {
|
||||
|
@ -106,17 +106,17 @@ fun Iterable<Amount>.sumOrZero(currency: Currency) = if (iterator().hasNext()) s
|
||||
//// Authenticated commands ///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** Filters the command list by type, party and public key all at once. */
|
||||
inline fun <reified T : Command> List<AuthenticatedObject<Command>>.select(signer: PublicKey? = null, party: Party? = null) =
|
||||
inline fun <reified T : CommandData> List<AuthenticatedObject<CommandData>>.select(signer: PublicKey? = null, party: Party? = null) =
|
||||
filter { it.value is T }.
|
||||
filter { if (signer == null) true else it.signers.contains(signer) }.
|
||||
filter { if (party == null) true else it.signingParties.contains(party) }.
|
||||
map { AuthenticatedObject<T>(it.signers, it.signingParties, it.value as T) }
|
||||
|
||||
inline fun <reified T : Command> List<AuthenticatedObject<Command>>.requireSingleCommand() = try {
|
||||
inline fun <reified T : CommandData> List<AuthenticatedObject<CommandData>>.requireSingleCommand() = try {
|
||||
select<T>().single()
|
||||
} catch (e: NoSuchElementException) {
|
||||
throw IllegalStateException("Required ${T::class.qualifiedName} command") // Better error message.
|
||||
}
|
||||
|
||||
// For Java
|
||||
fun List<AuthenticatedObject<Command>>.requireSingleCommand(klass: Class<out Command>) = filter { klass.isInstance(it) }.single()
|
||||
fun List<AuthenticatedObject<CommandData>>.requireSingleCommand(klass: Class<out CommandData>) = filter { klass.isInstance(it) }.single()
|
||||
|
@ -31,7 +31,7 @@ interface OwnableState : ContractState {
|
||||
val owner: PublicKey
|
||||
|
||||
/** Copies the underlying data structure, replacing the owner field with this new value and leaving the rest alone */
|
||||
fun withNewOwner(newOwner: PublicKey): Pair<Command, OwnableState>
|
||||
fun withNewOwner(newOwner: PublicKey): Pair<CommandData, OwnableState>
|
||||
}
|
||||
|
||||
/** Returns the SHA-256 hash of the serialised contents of this state (not cached!) */
|
||||
@ -63,14 +63,19 @@ data class PartyReference(val party: Party, val reference: OpaqueBytes) {
|
||||
}
|
||||
|
||||
/** Marker interface for classes that represent commands */
|
||||
interface Command
|
||||
interface CommandData
|
||||
|
||||
/** Commands that inherit from this are intended to have no data items: it's only their presence that matters. */
|
||||
abstract class TypeOnlyCommand : Command {
|
||||
abstract class TypeOnlyCommandData : CommandData {
|
||||
override fun equals(other: Any?) = other?.javaClass == javaClass
|
||||
override fun hashCode() = javaClass.name.hashCode()
|
||||
}
|
||||
|
||||
/** Command data/content plus pubkey pair: the signature is stored at the end of the serialized bytes */
|
||||
data class Command(val data: CommandData, val pubkeys: List<PublicKey>) {
|
||||
constructor(data: CommandData, key: PublicKey) : this(data, listOf(key))
|
||||
}
|
||||
|
||||
/** Wraps an object that was signed by a public key, which may be a well known/recognised institutional key. */
|
||||
data class AuthenticatedObject<out T : Any>(
|
||||
val signers: List<PublicKey>,
|
||||
|
@ -46,19 +46,14 @@ import java.util.*
|
||||
* database and replaced with the real objects. TFV is the form that is finally fed into the contracts.
|
||||
*/
|
||||
|
||||
/** Serialized command plus pubkey pair: the signature is stored at the end of the serialized bytes */
|
||||
data class WireCommand(val command: Command, val pubkeys: List<PublicKey>) {
|
||||
constructor(command: Command, key: PublicKey) : this(command, listOf(key))
|
||||
}
|
||||
|
||||
/** Transaction ready for serialisation, without any signatures attached. */
|
||||
data class WireTransaction(val inputStates: List<ContractStateRef>,
|
||||
val outputStates: List<ContractState>,
|
||||
val commands: List<WireCommand>) {
|
||||
val commands: List<Command>) {
|
||||
fun toLedgerTransaction(timestamp: Instant?, identityService: IdentityService, originalHash: SecureHash): LedgerTransaction {
|
||||
val authenticatedArgs = commands.map {
|
||||
val institutions = it.pubkeys.mapNotNull { pk -> identityService.partyFromKey(pk) }
|
||||
AuthenticatedObject(it.pubkeys, institutions, it.command)
|
||||
AuthenticatedObject(it.pubkeys, institutions, it.data)
|
||||
}
|
||||
return LedgerTransaction(inputStates, outputStates, authenticatedArgs, timestamp, originalHash)
|
||||
}
|
||||
@ -67,14 +62,14 @@ data class WireTransaction(val inputStates: List<ContractStateRef>,
|
||||
/** A mutable transaction that's in the process of being built, before all signatures are present. */
|
||||
class PartialTransaction(private val inputStates: MutableList<ContractStateRef> = arrayListOf(),
|
||||
private val outputStates: MutableList<ContractState> = arrayListOf(),
|
||||
private val commands: MutableList<WireCommand> = arrayListOf()) {
|
||||
private val commands: MutableList<Command> = arrayListOf()) {
|
||||
/** A more convenient way to add items to this transaction that calls the add* methods for you based on type */
|
||||
public fun withItems(vararg items: Any): PartialTransaction {
|
||||
for (t in items) {
|
||||
when (t) {
|
||||
is ContractStateRef -> inputStates.add(t)
|
||||
is ContractState -> outputStates.add(t)
|
||||
is WireCommand -> commands.add(t)
|
||||
is Command -> commands.add(t)
|
||||
else -> throw IllegalArgumentException("Wrong argument type: ${t.javaClass}")
|
||||
}
|
||||
}
|
||||
@ -112,17 +107,20 @@ class PartialTransaction(private val inputStates: MutableList<ContractStateRef>
|
||||
outputStates.add(state)
|
||||
}
|
||||
|
||||
fun addArg(arg: WireCommand) {
|
||||
fun addCommand(arg: Command) {
|
||||
check(currentSigs.isEmpty())
|
||||
|
||||
// We should probably merge the lists of pubkeys for identical commands here.
|
||||
commands.add(arg)
|
||||
}
|
||||
|
||||
fun addCommand(data: CommandData, vararg keys: PublicKey) = addCommand(Command(data, listOf(*keys)))
|
||||
fun addCommand(data: CommandData, keys: List<PublicKey>) = addCommand(Command(data, keys))
|
||||
|
||||
// Accessors that yield immutable snapshots.
|
||||
fun inputStates(): List<ContractStateRef> = ArrayList(inputStates)
|
||||
fun outputStates(): List<ContractState> = ArrayList(outputStates)
|
||||
fun commands(): List<WireCommand> = ArrayList(commands)
|
||||
fun commands(): List<Command> = ArrayList(commands)
|
||||
}
|
||||
|
||||
data class SignedWireTransaction(val txBits: SerializedBytes<WireTransaction>, val sigs: List<DigitalSignature.WithKey>) {
|
||||
@ -206,7 +204,7 @@ data class LedgerTransaction(
|
||||
/** The states that will be generated by the execution of this transaction. */
|
||||
val outStates: List<ContractState>,
|
||||
/** Arbitrary data passed to the program of each input state. */
|
||||
val commands: List<AuthenticatedObject<Command>>,
|
||||
val commands: List<AuthenticatedObject<CommandData>>,
|
||||
/** The moment the transaction was timestamped for, if a timestamp was present. */
|
||||
val time: Instant?,
|
||||
/** The hash of the original serialised TimestampedWireTransaction or SignedTransaction */
|
||||
@ -227,7 +225,7 @@ data class LedgerTransaction(
|
||||
/** 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 commands: List<AuthenticatedObject<Command>>,
|
||||
val commands: List<AuthenticatedObject<CommandData>>,
|
||||
val time: Instant?,
|
||||
val origHash: SecureHash) {
|
||||
override fun hashCode() = origHash.hashCode()
|
||||
|
@ -110,7 +110,7 @@ class CashTests {
|
||||
assertEquals(100.DOLLARS, s.amount)
|
||||
assertEquals(MINI_CORP, s.deposit.party)
|
||||
assertEquals(DUMMY_PUBKEY_1, s.owner)
|
||||
assertTrue(ptx.commands()[0].command is Cash.Commands.Issue)
|
||||
assertTrue(ptx.commands()[0].data is Cash.Commands.Issue)
|
||||
assertEquals(MINI_CORP_PUBKEY, ptx.commands()[0].pubkeys[0])
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ class TransactionSerializationTests {
|
||||
@Before
|
||||
fun setup() {
|
||||
tx = PartialTransaction().withItems(
|
||||
fakeStateRef, outputState, changeState, WireCommand(Cash.Commands.Move(), arrayListOf(TestUtils.keypair.public))
|
||||
fakeStateRef, outputState, changeState, Command(Cash.Commands.Move(), arrayListOf(TestUtils.keypair.public))
|
||||
)
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ class TransactionSerializationTests {
|
||||
// If the signature was replaced in transit, we don't like it.
|
||||
assertFailsWith(SignatureException::class) {
|
||||
val tx2 = PartialTransaction().withItems(fakeStateRef, outputState, changeState,
|
||||
WireCommand(Cash.Commands.Move(), arrayListOf(TestUtils.keypair2.public)))
|
||||
Command(Cash.Commands.Move(), TestUtils.keypair2.public))
|
||||
tx2.signWith(TestUtils.keypair2)
|
||||
|
||||
signedTX.copy(sigs = tx2.toSignedTransaction().sigs).verify()
|
||||
@ -89,7 +89,7 @@ class TransactionSerializationTests {
|
||||
tx.signWith(TestUtils.keypair)
|
||||
val ttx = tx.toSignedTransaction().toTimestampedTransactionWithoutTime()
|
||||
val ltx = ttx.verifyToLedgerTransaction(DUMMY_TIMESTAMPER, MockIdentityService)
|
||||
assertEquals(tx.commands().map { it.command }, ltx.commands.map { it.value })
|
||||
assertEquals(tx.commands().map { it.data }, ltx.commands.map { it.value })
|
||||
assertEquals(tx.inputStates(), ltx.inStateRefs)
|
||||
assertEquals(tx.outputStates(), ltx.outStates)
|
||||
assertNull(ltx.time)
|
||||
|
@ -176,13 +176,17 @@ infix fun ContractState.label(label: String) = LabeledOutput(label, this)
|
||||
|
||||
abstract class AbstractTransactionForTest {
|
||||
protected val outStates = ArrayList<LabeledOutput>()
|
||||
protected val commands = ArrayList<AuthenticatedObject<Command>>()
|
||||
protected val commands = ArrayList<Command>()
|
||||
|
||||
open fun output(label: String? = null, s: () -> ContractState) = LabeledOutput(label, s()).apply { outStates.add(this) }
|
||||
|
||||
fun arg(vararg key: PublicKey, c: () -> Command) {
|
||||
protected fun commandsToAuthenticatedObjects(): List<AuthenticatedObject<CommandData>> {
|
||||
return commands.map { AuthenticatedObject(it.pubkeys, it.pubkeys.mapNotNull { TEST_KEYS_TO_CORP_MAP[it] }, it.data) }
|
||||
}
|
||||
|
||||
fun arg(vararg key: PublicKey, c: () -> CommandData) {
|
||||
val keys = listOf(*key)
|
||||
commands.add(AuthenticatedObject(keys, keys.mapNotNull { TEST_KEYS_TO_CORP_MAP[it] }, c()))
|
||||
commands.add(Command(c(), keys))
|
||||
}
|
||||
|
||||
// Forbid patterns like: transaction { ... transaction { ... } }
|
||||
@ -196,7 +200,8 @@ open class TransactionForTest : AbstractTransactionForTest() {
|
||||
fun input(s: () -> ContractState) = inStates.add(s())
|
||||
|
||||
protected fun run(time: Instant) {
|
||||
val tx = TransactionForVerification(inStates, outStates.map { it.state }, commands, time, SecureHash.randomSHA256())
|
||||
val tx = TransactionForVerification(inStates, outStates.map { it.state }, commandsToAuthenticatedObjects(),
|
||||
time, SecureHash.randomSHA256())
|
||||
tx.verify(TEST_PROGRAM_MAP)
|
||||
}
|
||||
|
||||
@ -280,8 +285,8 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
|
||||
* hash (i.e. pretend it was signed)
|
||||
*/
|
||||
fun toLedgerTransaction(time: Instant): LedgerTransaction {
|
||||
val wireCmds = commands.map { WireCommand(it.value, it.signers) }
|
||||
return WireTransaction(inStates, outStates.map { it.state }, wireCmds).toLedgerTransaction(time, MockIdentityService, SecureHash.randomSHA256())
|
||||
val wtx = WireTransaction(inStates, outStates.map { it.state }, commands)
|
||||
return wtx.toLedgerTransaction(time, MockIdentityService, SecureHash.randomSHA256())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
package core.visualiser
|
||||
|
||||
import core.Command
|
||||
import core.CommandData
|
||||
import core.ContractState
|
||||
import core.SecureHash
|
||||
import core.testutils.TransactionGroupDSL
|
||||
@ -67,7 +67,7 @@ class GraphVisualiser(val dsl: TransactionGroupDSL<in ContractState>) {
|
||||
return dsl.labelForState(state) ?: stateToTypeName(state)
|
||||
}
|
||||
|
||||
private fun commandToTypeName(state: Command) = state.javaClass.canonicalName.removePrefix("contracts.").replace('$', '.')
|
||||
private fun commandToTypeName(state: CommandData) = state.javaClass.canonicalName.removePrefix("contracts.").replace('$', '.')
|
||||
private fun stateToTypeName(state: ContractState) = state.javaClass.canonicalName.removePrefix("contracts.").removeSuffix(".State")
|
||||
private fun stateToCSSClass(state: ContractState) = stateToTypeName(state).replace('.', '_').toLowerCase()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user