Further tweaks based on feedback - simplified the embedded timelock contract

This commit is contained in:
jamescarlyle
2016-09-08 22:49:24 +01:00
parent cdb2c3efa6
commit e187c4d91d
4 changed files with 35 additions and 22 deletions

View File

@ -80,7 +80,6 @@ class Cash : OnLedgerAsset<Currency, Cash.State>() {
/** There must be a MoveCommand signed by this key to claim the amount. */ /** There must be a MoveCommand signed by this key to claim the amount. */
override val owner: PublicKey, override val owner: PublicKey,
/** Cash may be encumbered by an additional state. */
override val encumbrance: Int? = null override val encumbrance: Int? = null
) : FungibleAsset<Currency> { ) : FungibleAsset<Currency> {
constructor(deposit: PartyAndReference, amount: Amount<Currency>, owner: PublicKey) constructor(deposit: PartyAndReference, amount: Amount<Currency>, owner: PublicKey)

View File

@ -115,10 +115,11 @@ interface ContractState {
/** /**
* All contract states may be _encumbered_ by up to one other state. * All contract states may be _encumbered_ by up to one other state.
* *
* The encumbrance state, if present, forces additional controls over this state, since the encumbrance state contract * The encumbrance state, if present, forces additional controls over the encumbered state, since the platform checks
* will also be verified during the execution of the transaction. For example, a contract state could be encumbered * that the encumbrance state is present as an input in the same transaction that consumes the encumbered state, and
* with a time-lock contract state; the state is then only processable in a transaction that verifies that the time * the contract code and rules of the encumbrance state will also be verified during the execution of the transaction.
* specified in the encumbrance time-lock has passed. * For example, a cash contract state could be encumbered with a time-lock contract state; the cash state is then only
* processable in a transaction that verifies that the time specified in the encumbrance time-lock has passed.
* *
* The encumbered state refers to another by index, and the referred encumbrance state * The encumbered state refers to another by index, and the referred encumbrance state
* is an output state in a particular position on the same transaction that created the encumbered state. An alternative * is an output state in a particular position on the same transaction that created the encumbered state. An alternative

View File

@ -2,13 +2,11 @@ package com.r3corda.core.contracts
import com.r3corda.contracts.asset.Cash import com.r3corda.contracts.asset.Cash
import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.serialization.OpaqueBytes
import com.r3corda.core.testing.* import com.r3corda.core.testing.*
import org.junit.Test import org.junit.Test
import java.security.PublicKey import java.security.PublicKey
import java.time.Instant import java.time.Instant
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import kotlin.test.assertFailsWith
val TEST_TIMELOCK_ID = TransactionEncumbranceTests.TestTimeLock() val TEST_TIMELOCK_ID = TransactionEncumbranceTests.TestTimeLock()
@ -32,32 +30,30 @@ class TransactionEncumbranceTests {
class TestTimeLock : Contract { class TestTimeLock : Contract {
override val legalContractReference = SecureHash.sha256("TestTimeLock") override val legalContractReference = SecureHash.sha256("TestTimeLock")
override fun verify(tx: TransactionForContract) { override fun verify(tx: TransactionForContract) {
val timestamp: Timestamp? = tx.timestamp val time = tx.timestamp?.before ?: throw IllegalArgumentException("Transactions containing time-locks must be timestamped")
val time = timestamp?.before ?: throw IllegalArgumentException("Transactions containing time-locks must be timestamped")
requireThat { requireThat {
"the time specified in the time-lock has passed" by "the time specified in the time-lock has passed" by
(time >= tx.inputs.filterIsInstance<TestTimeLock.State>().single().validFrom) (time >= tx.inputs.filterIsInstance<TestTimeLock.State>().single().validFrom)
} }
} }
data class State( data class State(
val validFrom: Instant val validFrom: Instant
) : ContractState { ) : ContractState {
override val participants: List<PublicKey> override val participants: List<PublicKey> = emptyList()
get() = throw UnsupportedOperationException()
override val contract: Contract = TEST_TIMELOCK_ID override val contract: Contract = TEST_TIMELOCK_ID
} }
} }
@Test @Test
fun trivial() { fun trivial() {
// A transaction containing an input state that is encumbered must fail if the encumbrance is missing on the inputs. // A transaction containing an input state that is encumbered must fail if the encumbrance has not been presented
assertFailsWith(TransactionVerificationException.TransactionMissingEncumbranceException::class) { // on the input states.
transaction { transaction {
input { encumberedState } input { encumberedState }
output { unencumberedState } output { unencumberedState }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() } command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this.verifies() this `fails with` "Missing required encumbrance 1 in INPUT"
}
} }
// An encumbered state must not be encumbered by itself. // An encumbered state must not be encumbered by itself.
transaction { transaction {
@ -70,6 +66,7 @@ class TransactionEncumbranceTests {
this `fails with` "Missing required encumbrance 1 in OUTPUT" this `fails with` "Missing required encumbrance 1 in OUTPUT"
} }
// An encumbered state must not reference an index greater than the size of the output states. // An encumbered state must not reference an index greater than the size of the output states.
// In this test, the output encumbered state refers to an encumbrance in position 1, but there is only one output.
transaction { transaction {
input { unencumberedState } input { unencumberedState }
// The encumbered state refers to an encumbrance in position 1, so there should be at least 2 outputs. // The encumbered state refers to an encumbrance in position 1, so there should be at least 2 outputs.
@ -82,8 +79,10 @@ class TransactionEncumbranceTests {
@Test @Test
fun testEncumbranceEffects() { fun testEncumbranceEffects() {
// A transaction containing an input state that is encumbered must fail if the encumbrance is not in the correct position. // This test fails because the encumbered state is pointing to the ordinary cash state as the encumbrance,
ledger { // instead of the timelock by mistake, so when we try and use it the transaction fails as we didn't include the
// encumbrance cash state.
ledger {
unverifiedTransaction { unverifiedTransaction {
output("state encumbered by 5pm time-lock") { encumberedState } output("state encumbered by 5pm time-lock") { encumberedState }
output { unencumberedState } output { unencumberedState }
@ -98,7 +97,10 @@ class TransactionEncumbranceTests {
this `fails with` "Missing required encumbrance 1 in INPUT" this `fails with` "Missing required encumbrance 1 in INPUT"
} }
} }
// A transaction containing an input state that is encumbered must fail if the encumbrance is not in the correct transaction. // A transaction containing an input state that is encumbered must fail if the encumbrance is not in the correct
// transaction. In this test, the intended encumbrance is presented alongside the encumbered state for consumption,
// although the encumbered state always refers to the encumbrance produced in the same transaction, and the in this case
// the encumbrance was created in a separate transaction.
ledger { ledger {
unverifiedTransaction { unverifiedTransaction {
output("state encumbered by 5pm time-lock") { encumberedState } output("state encumbered by 5pm time-lock") { encumberedState }
@ -133,6 +135,7 @@ class TransactionEncumbranceTests {
} }
} }
// A transaction with an input state that is encumbered must fail if the rules of the encumbrance are not met. // A transaction with an input state that is encumbered must fail if the rules of the encumbrance are not met.
// In this test, the time-lock encumbrance is being processed in a transaction before the time allowed.
ledger { ledger {
unverifiedTransaction { unverifiedTransaction {
output("state encumbered by 5pm time-lock") { encumberedState } output("state encumbered by 5pm time-lock") { encumberedState }

View File

@ -909,6 +909,16 @@ When we construct a transaction that generates the encumbered state, we must pla
position of that transaction. And when we subsequently consume that encumbered state, the same encumbrance state must be position of that transaction. And when we subsequently consume that encumbered state, the same encumbrance state must be
available somewhere within the input set of states. available somewhere within the input set of states.
In future, we will consider the concept of a *covenant*. This is where the encumbrance travels alongside each iteration of
the encumbered state. For example, a cash state may be encumbered with a *domicile* encumbrance, which checks the domicile of
the identity of the owner that the cash state is being moved to, in order to uphold sanction screening regulations, and prevent
cash being paid to parties domiciled in e.g. North Korea. In this case, the encumbrance should be permanently attached to
the all future cash states stemmimg from this one.
We will also consider marking states that are capable of being encumbrances as such. This will prevent states being used
as encumbrances inadvertently. For example, the time-lock above would be usable as an encumbrance, but it makes no sense to
be able to encumber a cash state with another one.
Clauses Clauses
------- -------