Cash: add a notion of state construction based on Richard's suggestion of a nonce+simply checking the output states are for deposits owned by the signer. No genesis tx is used.

This commit is contained in:
Mike Hearn 2015-11-16 19:29:11 +01:00
parent 5f30684805
commit 853b37a6e1
2 changed files with 55 additions and 10 deletions

View File

@ -2,6 +2,7 @@ package contracts
import core.*
import java.security.PublicKey
import java.security.SecureRandom
import java.util.*
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -48,11 +49,16 @@ object Cash : Contract {
override fun toString() = "Cash($amount at $deposit owned by $owner)"
}
sealed class Commands {
/** A command proving ownership of some input states, the signature covers the output states. */
// Just for grouping
class Commands {
object Move : Command
/**
* Allows new cash states to be issued into existence: the nonce ("number used onces") ensures the transaction
* has a unique ID even when there are no inputs.
*/
data class Issue(val nonce: Long = SecureRandom.getInstanceStrong().nextLong()) : Command
/**
* A command stating that money has been withdrawn from the shared ledger and is now accounted for
* in some other way.
@ -63,6 +69,26 @@ object Cash : Contract {
/** This is the function EVERYONE runs */
override fun verify(tx: TransactionForVerification) {
with(tx) {
val cashOutputs = outStates.filterIsInstance<Cash.State>()
requireThat {
"all outputs represent at least one penny" by cashOutputs.none { it.amount.pennies == 0 }
}
// If we have an issue command, perform special processing: the transaction is allowed to have no inputs,
// and the output states must have a deposit reference owned by the signer. Note that this means literally
// anyone with access to the network can issue cash claims of arbitrary amounts! It is up to the recipient
// to decide if the backing institution is trustworthy or not, via some as-yet-unwritten identity service.
// See ADP-22 for discussion.
val issueCommand = args.select<Commands.Issue>().singleOrNull()
if (issueCommand != null) {
requireThat {
"the issue command has a nonce" by (issueCommand.value.nonce != 0L)
"output deposits are owned by a command signer" by
cashOutputs.all { issueCommand.signingInstitutions.contains(it.deposit.institution) }
}
return
}
val cashInputs = inStates.filterIsInstance<Cash.State>()
requireThat {
@ -74,7 +100,6 @@ object Cash : Contract {
val currency = cashInputs.first().amount.currency
// Select all the output states that are cash states. There may be zero if all money is being withdrawn.
val cashOutputs = outStates.filterIsInstance<Cash.State>()
requireThat {
"all outputs use the currency of the inputs" by cashOutputs.all { it.amount.currency == currency }
}

View File

@ -6,10 +6,6 @@ import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
// TODO: Some basic invariants should be enforced by the platform before contract execution:
// 1. No duplicate input states
// 2. There must be at least one input state (note: not "one of the type the contract wants")
class CashTests {
val inState = Cash.State(
deposit = InstitutionReference(MEGA_CORP, OpaqueBytes.of(1)),
@ -56,14 +52,38 @@ class CashTests {
}
@Test
fun testCannotSummonMoney() {
// Make sure that the contract runs even if there are no cash input states.
fun issueMoney() {
// Check we can't "move" money into existence.
transaction {
input { DummyContract.State() }
output { outState }
arg { Cash.Commands.Move }
this `fails requirement` "there is at least one cash input"
}
// Check we can issue money only as long as the issuer institution is a command signer, i.e. any recognised
// institution is allowed to issue as much cash as they want.
transaction {
output { outState }
arg { Cash.Commands.Issue() }
this `fails requirement` "output deposits are owned by a command signer"
}
transaction {
output {
Cash.State(
amount = 1000.DOLLARS,
owner = DUMMY_PUBKEY_1,
deposit = InstitutionReference(MINI_CORP, OpaqueBytes.of(12, 34))
)
}
transaction {
arg(MINI_CORP_KEY) { Cash.Commands.Issue(0) }
this `fails requirement` "has a nonce"
}
arg(MINI_CORP_KEY) { Cash.Commands.Issue() }
this.accepts()
}
}
@Test