mirror of
https://github.com/corda/corda.git
synced 2024-12-20 05:28:21 +00:00
Merged in rnicoll-timestamping-notary-take2 (pull request #206)
Replace named timestamping authority with notary
This commit is contained in:
commit
2cd5014941
@ -131,6 +131,12 @@ public class JavaCommercialPaper implements Contract {
|
||||
}
|
||||
|
||||
public static class Redeem extends Commands {
|
||||
private final Party notary;
|
||||
|
||||
public Redeem(Party setNotary) {
|
||||
this.notary = setNotary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof Redeem;
|
||||
@ -138,6 +144,12 @@ public class JavaCommercialPaper implements Contract {
|
||||
}
|
||||
|
||||
public static class Issue extends Commands {
|
||||
private final Party notary;
|
||||
|
||||
public Issue(Party setNotary) {
|
||||
this.notary = setNotary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof Issue;
|
||||
@ -163,6 +175,7 @@ public class JavaCommercialPaper implements Contract {
|
||||
|
||||
// For now do not allow multiple pieces of CP to trade in a single transaction.
|
||||
if (cmd.getValue() instanceof JavaCommercialPaper.Commands.Issue) {
|
||||
Commands.Issue issueCommand = (Commands.Issue) cmd.getValue();
|
||||
State output = single(outputs);
|
||||
if (!inputs.isEmpty()) {
|
||||
throw new IllegalStateException("Failed Requirement: output values sum to more than the inputs");
|
||||
@ -171,7 +184,7 @@ public class JavaCommercialPaper implements Contract {
|
||||
throw new IllegalStateException("Failed Requirement: output values sum to more than the inputs");
|
||||
}
|
||||
|
||||
TimestampCommand timestampCommand = tx.getTimestampByName("Notary Service");
|
||||
TimestampCommand timestampCommand = tx.getTimestampBy(issueCommand.notary);
|
||||
if (timestampCommand == null)
|
||||
throw new IllegalArgumentException("Failed Requirement: must be timestamped");
|
||||
|
||||
@ -201,7 +214,7 @@ public class JavaCommercialPaper implements Contract {
|
||||
!output.getMaturityDate().equals(input.getMaturityDate()))
|
||||
throw new IllegalStateException("Failed requirement: the output state is the same as the input state except for owner");
|
||||
} else if (cmd.getValue() instanceof JavaCommercialPaper.Commands.Redeem) {
|
||||
TimestampCommand timestampCommand = tx.getTimestampByName("Notary Service");
|
||||
TimestampCommand timestampCommand = tx.getTimestampBy(((Commands.Redeem) cmd.getValue()).notary);
|
||||
if (timestampCommand == null)
|
||||
throw new IllegalArgumentException("Failed Requirement: must be timestamped");
|
||||
Instant time = timestampCommand.getBefore();
|
||||
@ -232,13 +245,13 @@ public class JavaCommercialPaper implements Contract {
|
||||
public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount<Issued<Currency>> faceValue, @Nullable Instant maturityDate, @NotNull Party notary) {
|
||||
State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate);
|
||||
TransactionState output = new TransactionState<>(state, notary);
|
||||
return new TransactionType.General.Builder().withItems(output, new Command(new Commands.Issue(), issuance.getParty().getOwningKey()));
|
||||
return new TransactionType.General.Builder().withItems(output, new Command(new Commands.Issue(notary), issuance.getParty().getOwningKey()));
|
||||
}
|
||||
|
||||
public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, List<StateAndRef<Cash.State>> wallet) throws InsufficientBalanceException {
|
||||
new Cash().generateSpend(tx, paper.getState().getData().getFaceValue(), paper.getState().getData().getOwner(), wallet);
|
||||
tx.addInputState(paper);
|
||||
tx.addCommand(new Command(new Commands.Redeem(), paper.getState().getData().getOwner()));
|
||||
tx.addCommand(new Command(new Commands.Redeem(paper.getState().getNotary()), paper.getState().getData().getOwner()));
|
||||
}
|
||||
|
||||
public void generateMove(TransactionBuilder tx, StateAndRef<State> paper, PublicKey newOwner) {
|
||||
|
@ -65,11 +65,11 @@ class CommercialPaper : Contract {
|
||||
}
|
||||
|
||||
interface Commands : CommandData {
|
||||
class Move : TypeOnlyCommandData(), Commands
|
||||
class Redeem : TypeOnlyCommandData(), Commands
|
||||
class Move: TypeOnlyCommandData(), Commands
|
||||
data class Redeem(val notary: Party) : 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 : TypeOnlyCommandData(), Commands
|
||||
data class Issue(val notary: Party) : Commands
|
||||
}
|
||||
|
||||
override fun verify(tx: TransactionForContract) {
|
||||
@ -79,11 +79,13 @@ class CommercialPaper : Contract {
|
||||
// There are two possible things that can be done with this CP. The first is trading it. The second is redeeming
|
||||
// it for cash on or after the maturity date.
|
||||
val command = tx.commands.requireSingleCommand<CommercialPaper.Commands>()
|
||||
|
||||
// Here, we match acceptable timestamp authorities by name. The list of acceptable TSAs (oracles) must be
|
||||
// hard coded into the contract because otherwise we could fail to gain consensus, if nodes disagree about
|
||||
// who or what is a trusted authority.
|
||||
val timestamp: TimestampCommand? = tx.commands.getTimestampByName("Mock Company 0", "Notary Service", "Bank A")
|
||||
// If it's an issue, we can't take notary from inputs, so it must be specified in the command
|
||||
val timestamp: TimestampCommand? = if (command.value is Commands.Issue)
|
||||
tx.getTimestampBy((command.value as Commands.Issue).notary)
|
||||
else if (command.value is Commands.Redeem)
|
||||
tx.getTimestampBy((command.value as Commands.Redeem).notary)
|
||||
else
|
||||
null
|
||||
|
||||
for ((inputs, outputs, key) in groups) {
|
||||
when (command.value) {
|
||||
@ -139,7 +141,7 @@ class CommercialPaper : Contract {
|
||||
fun generateIssue(faceValue: Amount<Issued<Currency>>, maturityDate: Instant, notary: Party): TransactionBuilder {
|
||||
val issuance = faceValue.token.issuer
|
||||
val state = TransactionState(State(issuance, issuance.party.owningKey, faceValue, maturityDate), notary)
|
||||
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
|
||||
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Issue(notary), issuance.party.owningKey))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -164,7 +166,7 @@ class CommercialPaper : Contract {
|
||||
val amount = paper.state.data.faceValue.let { amount -> Amount<Currency>(amount.quantity, amount.token.product) }
|
||||
Cash().generateSpend(tx, amount, paper.state.data.owner, wallet)
|
||||
tx.addInputState(paper)
|
||||
tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.data.owner)
|
||||
tx.addCommand(CommercialPaper.Commands.Redeem(paper.state.notary), paper.state.data.owner)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -496,6 +496,8 @@ class InterestRateSwap() : Contract {
|
||||
val groups = tx.groupStates() { state: InterestRateSwap.State -> state.common.tradeID }
|
||||
|
||||
val command = tx.commands.requireSingleCommand<InterestRateSwap.Commands>()
|
||||
// TODO: This needs to either be the notary used for the inputs, or otherwise
|
||||
// derived as the correct notary
|
||||
val time = tx.commands.getTimestampByName("Mock Company 0", "Notary Service", "Bank A")?.midpoint
|
||||
if (time == null) throw IllegalArgumentException("must be timestamped")
|
||||
|
||||
|
@ -390,9 +390,7 @@ class Obligation<P> : Contract {
|
||||
for ((stateIdx, input) in inputs.withIndex()) {
|
||||
val actualOutput = outputs[stateIdx]
|
||||
val deadline = input.dueBefore
|
||||
// TODO: Determining correct timestamp authority needs rework now that timestamping service is part of
|
||||
// notary.
|
||||
val timestamp: TimestampCommand? = tx.commands.getTimestampByName("Mock Company 0", "Notary Service", "Bank A")
|
||||
val timestamp: TimestampCommand? = tx.timestamp
|
||||
val expectedOutput: State<P> = input.copy(lifecycle = expectedOutputLifecycle)
|
||||
|
||||
requireThat {
|
||||
|
@ -3,6 +3,7 @@ package com.r3corda.contracts
|
||||
import com.r3corda.contracts.asset.Cash
|
||||
import com.r3corda.contracts.testing.*
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.days
|
||||
import com.r3corda.core.node.services.testing.MockStorageService
|
||||
@ -18,8 +19,8 @@ import kotlin.test.assertTrue
|
||||
|
||||
interface ICommercialPaperTestTemplate {
|
||||
fun getPaper(): ICommercialPaperState
|
||||
fun getIssueCommand(): CommandData
|
||||
fun getRedeemCommand(): CommandData
|
||||
fun getIssueCommand(notary: Party): CommandData
|
||||
fun getRedeemCommand(notary: Party): CommandData
|
||||
fun getMoveCommand(): CommandData
|
||||
}
|
||||
|
||||
@ -31,8 +32,8 @@ class JavaCommercialPaperTest() : ICommercialPaperTestTemplate {
|
||||
TEST_TX_TIME + 7.days
|
||||
)
|
||||
|
||||
override fun getIssueCommand(): CommandData = JavaCommercialPaper.Commands.Issue()
|
||||
override fun getRedeemCommand(): CommandData = JavaCommercialPaper.Commands.Redeem()
|
||||
override fun getIssueCommand(notary: Party): CommandData = JavaCommercialPaper.Commands.Issue(notary)
|
||||
override fun getRedeemCommand(notary: Party): CommandData = JavaCommercialPaper.Commands.Redeem(notary)
|
||||
override fun getMoveCommand(): CommandData = JavaCommercialPaper.Commands.Move()
|
||||
}
|
||||
|
||||
@ -44,8 +45,8 @@ class KotlinCommercialPaperTest() : ICommercialPaperTestTemplate {
|
||||
maturityDate = TEST_TX_TIME + 7.days
|
||||
)
|
||||
|
||||
override fun getIssueCommand(): CommandData = CommercialPaper.Commands.Issue()
|
||||
override fun getRedeemCommand(): CommandData = CommercialPaper.Commands.Redeem()
|
||||
override fun getIssueCommand(notary: Party): CommandData = CommercialPaper.Commands.Issue(notary)
|
||||
override fun getRedeemCommand(notary: Party): CommandData = CommercialPaper.Commands.Redeem(notary)
|
||||
override fun getMoveCommand(): CommandData = CommercialPaper.Commands.Move()
|
||||
}
|
||||
|
||||
@ -74,7 +75,7 @@ class CommercialPaperTestsGeneric {
|
||||
// Some CP is issued onto the ledger by MegaCorp.
|
||||
transaction("Issuance") {
|
||||
output("paper") { thisTest.getPaper() }
|
||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
|
||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
@ -103,7 +104,7 @@ class CommercialPaperTestsGeneric {
|
||||
}
|
||||
|
||||
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
command(ALICE_PUBKEY) { thisTest.getRedeemCommand() }
|
||||
command(ALICE_PUBKEY) { thisTest.getRedeemCommand(DUMMY_NOTARY) }
|
||||
|
||||
tweak {
|
||||
outputs(700.DOLLARS `issued by` issuer)
|
||||
@ -133,7 +134,7 @@ class CommercialPaperTestsGeneric {
|
||||
fun `key mismatch at issue`() {
|
||||
transaction {
|
||||
output { thisTest.getPaper() }
|
||||
command(DUMMY_PUBKEY_1) { thisTest.getIssueCommand() }
|
||||
command(DUMMY_PUBKEY_1) { thisTest.getIssueCommand(DUMMY_NOTARY) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "output states are issued by a command signer"
|
||||
}
|
||||
@ -143,7 +144,7 @@ class CommercialPaperTestsGeneric {
|
||||
fun `face value is not zero`() {
|
||||
transaction {
|
||||
output { thisTest.getPaper().withFaceValue(0.DOLLARS `issued by` issuer) }
|
||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
|
||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "output values sum to more than the inputs"
|
||||
}
|
||||
@ -153,7 +154,7 @@ class CommercialPaperTestsGeneric {
|
||||
fun `maturity date not in the past`() {
|
||||
transaction {
|
||||
output { thisTest.getPaper().withMaturityDate(TEST_TX_TIME - 10.days) }
|
||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
|
||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "maturity date is not in the past"
|
||||
}
|
||||
@ -164,7 +165,7 @@ class CommercialPaperTestsGeneric {
|
||||
transaction {
|
||||
input(thisTest.getPaper())
|
||||
output { thisTest.getPaper() }
|
||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
|
||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "output values sum to more than the inputs"
|
||||
}
|
||||
|
@ -96,6 +96,7 @@ fun List<AuthenticatedObject<CommandData>>.getTimestampBy(timestampingAuthority:
|
||||
* Note that matching here is done by (verified, legal) name, not by public key. Any signature by any
|
||||
* party with a name that matches (case insensitively) any of the given names will yield a match.
|
||||
*/
|
||||
@Deprecated(message = "Timestamping authority should always be notary for the transaction")
|
||||
fun List<AuthenticatedObject<CommandData>>.getTimestampByName(vararg names: String): TimestampCommand? {
|
||||
val timestampCmd = filter { it.value is TimestampCommand }.singleOrNull() ?: return null
|
||||
val tsaNames = timestampCmd.signingParties.map { it.name.toLowerCase() }
|
||||
|
@ -316,6 +316,9 @@ data class AuthenticatedObject<out T : Any>(
|
||||
* If present in a transaction, contains a time that was verified by the timestamping authority/authorities whose
|
||||
* public keys are identified in the containing [Command] object. The true time must be between (after, before)
|
||||
*/
|
||||
// TODO: Timestamps are now always provided by the consensus service for the transaction, rather than potentially
|
||||
// having multiple timestamps on a transaction. As such, it likely makes more sense for time to be a field on the
|
||||
// transaction, rather than a command
|
||||
data class TimestampCommand(val after: Instant?, val before: Instant?) : CommandData {
|
||||
init {
|
||||
if (after == null && before == null)
|
||||
|
@ -73,7 +73,8 @@ data class TransactionForVerification(val inputs: List<TransactionState<Contract
|
||||
@Throws(TransactionVerificationException::class)
|
||||
fun verify() = type.verify(this)
|
||||
|
||||
fun toTransactionForContract() = TransactionForContract(inputs.map { it.data }, outputs.map { it.data }, attachments, commands, origHash)
|
||||
fun toTransactionForContract() = TransactionForContract(inputs.map { it.data }, outputs.map { it.data },
|
||||
attachments, commands, origHash, inputs.map { it.notary }.singleOrNull())
|
||||
}
|
||||
|
||||
/**
|
||||
@ -84,7 +85,8 @@ data class TransactionForContract(val inputs: List<ContractState>,
|
||||
val outputs: List<ContractState>,
|
||||
val attachments: List<Attachment>,
|
||||
val commands: List<AuthenticatedObject<CommandData>>,
|
||||
val origHash: SecureHash) {
|
||||
val origHash: SecureHash,
|
||||
val inputNotary: Party? = null) {
|
||||
override fun hashCode() = origHash.hashCode()
|
||||
override fun equals(other: Any?) = other is TransactionForContract && other.origHash == origHash
|
||||
|
||||
@ -158,10 +160,15 @@ data class TransactionForContract(val inputs: List<ContractState>,
|
||||
*/
|
||||
data class InOutGroup<T : ContractState, K : Any>(val inputs: List<T>, val outputs: List<T>, val groupingKey: K)
|
||||
|
||||
/** Get the timestamp command for this transaction, using the notary from the input states. */
|
||||
val timestamp: TimestampCommand?
|
||||
get() = if (inputNotary == null) null else commands.getTimestampBy(inputNotary)
|
||||
|
||||
/** Simply calls [commands.getTimestampBy] as a shortcut to make code completion more intuitive. */
|
||||
fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority)
|
||||
|
||||
/** Simply calls [commands.getTimestampByName] as a shortcut to make code completion more intuitive. */
|
||||
@Deprecated(message = "Timestamping authority should always be notary for the transaction")
|
||||
fun getTimestampByName(vararg authorityName: String): TimestampCommand? = commands.getTimestampByName(*authorityName)
|
||||
|
||||
}
|
||||
@ -174,4 +181,4 @@ sealed class TransactionVerificationException(val tx: TransactionForVerification
|
||||
class MoreThanOneNotary(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
|
||||
class SignersMissing(tx: TransactionForVerification, missing: List<PublicKey>) : TransactionVerificationException(tx, null)
|
||||
class InvalidNotaryChange(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
|
||||
}
|
||||
}
|
||||
|
@ -481,7 +481,7 @@ class TwoPartyTradeProtocolTests {
|
||||
output("alice's paper") {
|
||||
CommercialPaper.State(MEGA_CORP.ref(1, 2, 3), owner, amount, TEST_TX_TIME + 7.days)
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue(notary) }
|
||||
if (!withError)
|
||||
timestamp(time = TEST_TX_TIME, notary = notary.owningKey)
|
||||
if (attachmentID != null)
|
||||
|
Loading…
Reference in New Issue
Block a user