Moves code sections in tutorials to code files.

This commit is contained in:
Joel Dudley
2017-10-01 23:33:15 +01:00
committed by GitHub
parent 4d4027947b
commit 4641d3c4dd
42 changed files with 2264 additions and 2078 deletions

View File

@ -24,7 +24,7 @@ class IntegrationTestingTutorial {
fun `alice bob cash exchange example`() {
// START 1
driver(startNodesInProcess = true,
extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")) {
extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")) {
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(
startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>()
@ -104,7 +104,7 @@ class IntegrationTestingTutorial {
}
)
}
// END 5
}
}
}
// END 5
}

View File

@ -34,8 +34,6 @@ import java.util.Set;
import static net.corda.core.contracts.ContractsDSL.requireThat;
import static net.corda.testing.TestConstants.getALICE_KEY;
// We group our two flows inside a singleton object to indicate that they work
// together.
@SuppressWarnings("unused")
public class FlowCookbookJava {
// ``InitiatorFlow`` is our first flow, and will communicate with
@ -121,20 +119,24 @@ public class FlowCookbookJava {
// A transaction generally needs a notary:
// - To prevent double-spends if the transaction has inputs
// - To serve as a timestamping authority if the transaction has a time-window
// - To serve as a timestamping authority if the transaction has a
// time-window
// We retrieve a notary from the network map.
// DOCSTART 1
Party specificNotary = getServiceHub().getNetworkMapCache().getNotary(new CordaX500Name("Notary Service", "London", "UK"));
// Alternatively, we can pick an arbitrary notary from the notary list. However, it is always preferable to
// specify which notary to use explicitly, as the notary list might change when new notaries are introduced,
// or old ones decommissioned.
CordaX500Name notaryName = new CordaX500Name("Notary Service", "London", "GB");
Party specificNotary = getServiceHub().getNetworkMapCache().getNotary(notaryName);
// Alternatively, we can pick an arbitrary notary from the notary
// list. However, it is always preferable to specify the notary
// explicitly, as the notary list might change when new notaries are
// introduced, or old ones decommissioned.
Party firstNotary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
// DOCEND 1
// We may also need to identify a specific counterparty.
// Again, we do so using the network map.
// We may also need to identify a specific counterparty. We do so
// using the identity service.
// DOCSTART 2
Party namedCounterparty = getServiceHub().getIdentityService().wellKnownPartyFromX500Name(new CordaX500Name("NodeA", "London", "UK"));
CordaX500Name counterPartyName = new CordaX500Name("NodeA", "London", "GB");
Party namedCounterparty = getServiceHub().getIdentityService().wellKnownPartyFromX500Name(counterPartyName);
Party keyedCounterparty = getServiceHub().getIdentityService().partyFromKey(dummyPubKey);
// DOCEND 2
@ -143,6 +145,13 @@ public class FlowCookbookJava {
------------------------------*/
progressTracker.setCurrentStep(SENDING_AND_RECEIVING_DATA);
// We start by initiating a flow session with the counterparty. We
// will use this session to send and receive messages from the
// counterparty.
// DOCSTART initiateFlow
FlowSession counterpartySession = initiateFlow(counterparty);
// DOCEND initiateFlow
// We can send arbitrary data to a counterparty.
// If this is the first ``send``, the counterparty will either:
// 1. Ignore the message if they are not registered to respond
@ -154,7 +163,6 @@ public class FlowCookbookJava {
// registered to respond to this flow, and has a corresponding
// ``receive`` call.
// DOCSTART 4
FlowSession counterpartySession = initiateFlow(counterparty);
counterpartySession.send(new Object());
// DOCEND 4
@ -617,7 +625,7 @@ public class FlowCookbookJava {
progressTracker.setCurrentStep(RECEIVING_AND_SENDING_DATA);
// We need to respond to the messages sent by the initiator:
// 1. They sent us an ``Any`` instance
// 1. They sent us an ``Object`` instance
// 2. They waited to receive an ``Integer`` instance back
// 3. They sent a ``String`` instance and waited to receive a
// ``Boolean`` instance back

View File

@ -0,0 +1,101 @@
package net.corda.docs.java.tutorial.contract;
import net.corda.core.contracts.*;
import net.corda.core.transactions.LedgerTransaction;
import net.corda.core.transactions.LedgerTransaction.InOutGroup;
import java.time.Instant;
import java.util.Currency;
import java.util.List;
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
import static net.corda.core.contracts.ContractsDSL.requireThat;
import static net.corda.finance.utils.StateSumming.sumCashBy;
public class CommercialPaper implements Contract {
// DOCSTART 1
public static final String IOU_CONTRACT_ID = "com.example.contract.IOUContract";
// DOCEND 1
// DOCSTART 3
@Override
public void verify(LedgerTransaction tx) {
List<InOutGroup<State, State>> groups = tx.groupStates(State.class, State::withoutOwner);
CommandWithParties<Commands> cmd = requireSingleCommand(tx.getCommands(), Commands.class);
// DOCEND 3
// DOCSTART 4
TimeWindow timeWindow = tx.getTimeWindow();
for (InOutGroup group : groups) {
List<State> inputs = group.getInputs();
List<State> outputs = group.getOutputs();
if (cmd.getValue() instanceof Commands.Move) {
State input = inputs.get(0);
requireThat(require -> {
require.using("the transaction is signed by the owner of the CP", cmd.getSigners().contains(input.getOwner().getOwningKey()));
require.using("the state is propagated", outputs.size() == 1);
// Don't need to check anything else, as if outputs.size == 1 then the output is equal to
// the input ignoring the owner field due to the grouping.
return null;
});
} else if (cmd.getValue() instanceof Commands.Redeem) {
// Redemption of the paper requires movement of on-ledger cash.
State input = inputs.get(0);
Amount<Issued<Currency>> received = sumCashBy(tx.getOutputStates(), input.getOwner());
if (timeWindow == null) throw new IllegalArgumentException("Redemptions must be timestamped");
Instant time = timeWindow.getFromTime();
requireThat(require -> {
require.using("the paper must have matured", time.isAfter(input.getMaturityDate()));
require.using("the received amount equals the face value", received == input.getFaceValue());
require.using("the paper must be destroyed", outputs.size() == 0);
require.using("the transaction is signed by the owner of the CP", cmd.getSigners().contains(input.getOwner().getOwningKey()));
return null;
});
} else if (cmd.getValue() instanceof Commands.Issue) {
State output = outputs.get(0);
if (timeWindow == null) throw new IllegalArgumentException("Issuances must be timestamped");
Instant time = timeWindow.getUntilTime();
requireThat(require -> {
// Don't allow people to issue commercial paper under other entities identities.
require.using("output states are issued by a command signer", cmd.getSigners().contains(output.getIssuance().getParty().getOwningKey()));
require.using("output values sum to more than the inputs", output.getFaceValue().getQuantity() > 0);
require.using("the maturity date is not in the past", time.isBefore(output.getMaturityDate()));
// Don't allow an existing CP state to be replaced by this issuance.
require.using("can't reissue an existing state", inputs.isEmpty());
return null;
});
} else {
throw new IllegalArgumentException("Unrecognised command");
}
}
// DOCEND 4
}
// DOCSTART 2
public static class Commands implements CommandData {
public static class Move extends Commands {
@Override
public boolean equals(Object obj) {
return obj instanceof Move;
}
}
public static class Redeem extends Commands {
@Override
public boolean equals(Object obj) {
return obj instanceof Redeem;
}
}
public static class Issue extends Commands {
@Override
public boolean equals(Object obj) {
return obj instanceof Issue;
}
}
}
// DOCEND 2
}

View File

@ -0,0 +1,90 @@
package net.corda.docs.java.tutorial.contract;
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.*;
import net.corda.core.crypto.NullKeys;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.AnonymousParty;
import org.jetbrains.annotations.NotNull;
import java.time.Instant;
import java.util.Currency;
import java.util.List;
// DOCSTART 1
public class State implements OwnableState {
private PartyAndReference issuance;
private AbstractParty owner;
private Amount<Issued<Currency>> faceValue;
private Instant maturityDate;
public State() {
} // For serialization
public State(PartyAndReference issuance, AbstractParty owner, Amount<Issued<Currency>> faceValue,
Instant maturityDate) {
this.issuance = issuance;
this.owner = owner;
this.faceValue = faceValue;
this.maturityDate = maturityDate;
}
public State copy() {
return new State(this.issuance, this.owner, this.faceValue, this.maturityDate);
}
public State withoutOwner() {
return new State(this.issuance, new AnonymousParty(NullKeys.NullPublicKey.INSTANCE), this.faceValue, this.maturityDate);
}
@NotNull
@Override
public CommandAndState withNewOwner(@NotNull AbstractParty newOwner) {
return new CommandAndState(new CommercialPaper.Commands.Move(), new State(this.issuance, newOwner, this.faceValue, this.maturityDate));
}
public PartyAndReference getIssuance() {
return issuance;
}
public AbstractParty getOwner() {
return owner;
}
public Amount<Issued<Currency>> getFaceValue() {
return faceValue;
}
public Instant getMaturityDate() {
return maturityDate;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
State state = (State) o;
if (issuance != null ? !issuance.equals(state.issuance) : state.issuance != null) return false;
if (owner != null ? !owner.equals(state.owner) : state.owner != null) return false;
if (faceValue != null ? !faceValue.equals(state.faceValue) : state.faceValue != null) return false;
return !(maturityDate != null ? !maturityDate.equals(state.maturityDate) : state.maturityDate != null);
}
@Override
public int hashCode() {
int result = issuance != null ? issuance.hashCode() : 0;
result = 31 * result + (owner != null ? owner.hashCode() : 0);
result = 31 * result + (faceValue != null ? faceValue.hashCode() : 0);
result = 31 * result + (maturityDate != null ? maturityDate.hashCode() : 0);
return result;
}
@NotNull
@Override
public List<AbstractParty> getParticipants() {
return ImmutableList.of(this.owner);
}
}
// DOCEND 1

View File

@ -0,0 +1,38 @@
package net.corda.docs.java.tutorial.flowstatemachines;
import net.corda.core.flows.SignTransactionFlow;
import net.corda.core.utilities.ProgressTracker;
import org.jetbrains.annotations.Nullable;
public class TutorialFlowStateMachines {
// DOCSTART 1
private final ProgressTracker progressTracker = new ProgressTracker(
RECEIVING,
VERIFYING,
SIGNING,
COLLECTING_SIGNATURES,
RECORDING
);
private static final ProgressTracker.Step RECEIVING = new ProgressTracker.Step(
"Waiting for seller trading info");
private static final ProgressTracker.Step VERIFYING = new ProgressTracker.Step(
"Verifying seller assets");
private static final ProgressTracker.Step SIGNING = new ProgressTracker.Step(
"Generating and signing transaction proposal");
private static final ProgressTracker.Step COLLECTING_SIGNATURES = new ProgressTracker.Step(
"Collecting signatures from other parties");
private static final ProgressTracker.Step RECORDING = new ProgressTracker.Step(
"Recording completed transaction");
// DOCEND 1
// DOCSTART 2
private static final ProgressTracker.Step VERIFYING_AND_SIGNING = new ProgressTracker.Step("Verifying and signing transaction proposal") {
@Nullable
@Override
public ProgressTracker childProgressTracker() {
return SignTransactionFlow.Companion.tracker();
}
};
// DOCEND 2
}

View File

@ -0,0 +1,270 @@
package net.corda.docs.java.tutorial.testdsl;
import kotlin.Unit;
import net.corda.core.contracts.PartyAndReference;
import net.corda.core.utilities.OpaqueBytes;
import net.corda.finance.contracts.ICommercialPaperState;
import net.corda.finance.contracts.JavaCommercialPaper;
import net.corda.finance.contracts.asset.Cash;
import org.junit.Test;
import java.time.temporal.ChronoUnit;
import static net.corda.finance.Currencies.DOLLARS;
import static net.corda.finance.Currencies.issuedBy;
import static net.corda.finance.contracts.JavaCommercialPaper.JCP_PROGRAM_ID;
import static net.corda.testing.CoreTestUtils.*;
import static net.corda.testing.NodeTestUtils.ledger;
import static net.corda.testing.NodeTestUtils.transaction;
import static net.corda.testing.TestConstants.*;
public class CommercialPaperTest {
private final OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{123});
// DOCSTART 1
private ICommercialPaperState getPaper() {
return new JavaCommercialPaper.State(
getMEGA_CORP().ref(defaultRef),
getMEGA_CORP(),
issuedBy(DOLLARS(1000), getMEGA_CORP().ref(defaultRef)),
getTEST_TX_TIME().plus(7, ChronoUnit.DAYS)
);
}
// DOCEND 1
// DOCSTART 2
@Test
public void simpleCP() {
ICommercialPaperState inState = getPaper();
ledger(l -> {
l.transaction(tx -> {
tx.attachments(JCP_PROGRAM_ID);
tx.input(JCP_PROGRAM_ID, inState);
return tx.verifies();
});
return Unit.INSTANCE;
});
}
// DOCEND 2
// DOCSTART 3
@Test
public void simpleCPMove() {
ICommercialPaperState inState = getPaper();
ledger(l -> {
l.transaction(tx -> {
tx.input(JCP_PROGRAM_ID, inState);
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
tx.attachments(JCP_PROGRAM_ID);
return tx.verifies();
});
return Unit.INSTANCE;
});
}
// DOCEND 3
// DOCSTART 4
@Test
public void simpleCPMoveFails() {
ICommercialPaperState inState = getPaper();
ledger(l -> {
l.transaction(tx -> {
tx.input(JCP_PROGRAM_ID, inState);
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
tx.attachments(JCP_PROGRAM_ID);
return tx.failsWith("the state is propagated");
});
return Unit.INSTANCE;
});
}
// DOCEND 4
// DOCSTART 5
@Test
public void simpleCPMoveSuccess() {
ICommercialPaperState inState = getPaper();
ledger(l -> {
l.transaction(tx -> {
tx.input(JCP_PROGRAM_ID, inState);
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
tx.attachments(JCP_PROGRAM_ID);
tx.failsWith("the state is propagated");
tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(getALICE()));
return tx.verifies();
});
return Unit.INSTANCE;
});
}
// DOCEND 5
// DOCSTART 6
@Test
public void simpleIssuanceWithTweak() {
ledger(l -> {
l.transaction(tx -> {
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
tx.attachments(JCP_PROGRAM_ID);
tx.tweak(tw -> {
tw.command(getBIG_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tw.timeWindow(getTEST_TX_TIME());
return tw.failsWith("output states are issued by a command signer");
});
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.timeWindow(getTEST_TX_TIME());
return tx.verifies();
});
return Unit.INSTANCE;
});
}
// DOCEND 6
// DOCSTART 7
@Test
public void simpleIssuanceWithTweakTopLevelTx() {
transaction(tx -> {
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
tx.attachments(JCP_PROGRAM_ID);
tx.tweak(tw -> {
tw.command(getBIG_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tw.timeWindow(getTEST_TX_TIME());
return tw.failsWith("output states are issued by a command signer");
});
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.timeWindow(getTEST_TX_TIME());
return tx.verifies();
});
}
// DOCEND 7
// DOCSTART 8
@Test
public void chainCommercialPaper() {
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
ledger(l -> {
l.unverifiedTransaction(tx -> {
tx.output(Cash.PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
tx.attachments(Cash.PROGRAM_ID);
return Unit.INSTANCE;
});
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
tx.output(JCP_PROGRAM_ID, "paper", getPaper());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.attachments(JCP_PROGRAM_ID);
tx.timeWindow(getTEST_TX_TIME());
return tx.verifies();
});
l.transaction("Trade", tx -> {
tx.input("paper");
tx.input("alice's $900");
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), getMEGA_CORP()));
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(getALICE()));
tx.command(getALICE_PUBKEY(), new Cash.Commands.Move());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
return Unit.INSTANCE;
});
}
// DOCEND 8
// DOCSTART 9
@Test
public void chainCommercialPaperDoubleSpend() {
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
ledger(l -> {
l.unverifiedTransaction(tx -> {
tx.output(Cash.PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
tx.attachments(Cash.PROGRAM_ID);
return Unit.INSTANCE;
});
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
tx.output(Cash.PROGRAM_ID, "paper", getPaper());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.attachments(JCP_PROGRAM_ID);
tx.timeWindow(getTEST_TX_TIME());
return tx.verifies();
});
l.transaction("Trade", tx -> {
tx.input("paper");
tx.input("alice's $900");
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), getMEGA_CORP()));
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(getALICE()));
tx.command(getALICE_PUBKEY(), new Cash.Commands.Move());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
l.transaction(tx -> {
tx.input("paper");
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
// We moved a paper to other pubkey.
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(getBOB()));
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
l.fails();
return Unit.INSTANCE;
});
}
// DOCEND 9
// DOCSTART 10
@Test
public void chainCommercialPaperTweak() {
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
ledger(l -> {
l.unverifiedTransaction(tx -> {
tx.output(Cash.PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
tx.attachments(Cash.PROGRAM_ID);
return Unit.INSTANCE;
});
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
tx.output(Cash.PROGRAM_ID, "paper", getPaper());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.attachments(JCP_PROGRAM_ID);
tx.timeWindow(getTEST_TX_TIME());
return tx.verifies();
});
l.transaction("Trade", tx -> {
tx.input("paper");
tx.input("alice's $900");
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), getMEGA_CORP()));
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(getALICE()));
tx.command(getALICE_PUBKEY(), new Cash.Commands.Move());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
l.tweak(lw -> {
lw.transaction(tx -> {
tx.input("paper");
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
// We moved a paper to another pubkey.
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(getBOB()));
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
lw.fails();
return Unit.INSTANCE;
});
l.verifies();
return Unit.INSTANCE;
});
}
// DOCEND 10
}

View File

@ -49,7 +49,7 @@ fun main(args: Array<String>) {
startFlowPermission<CashPaymentFlow>(),
startFlowPermission<CashExitFlow>()))
driver(driverDirectory = baseDirectory) {
driver(driverDirectory = baseDirectory, extraCordappPackagesToScan = listOf("net.corda.finance")) {
startNode(providedName = DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type)))
val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user)).get()
// END 1
@ -100,10 +100,9 @@ fun main(args: Array<String>) {
}
}
waitForAllNodesToFinish()
// END 5
}
}
// END 5
// START 6
fun generateTransactions(proxy: CordaRPCOps) {

View File

@ -0,0 +1,136 @@
package net.corda.docs.tutorial.contract
import net.corda.core.contracts.*
import net.corda.core.crypto.NullKeys
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.utils.sumCashBy
import net.corda.testing.chooseIdentityAndCert
import java.time.Instant
import java.util.*
class CommercialPaper : Contract {
// DOCSTART 8
companion object {
const val CP_PROGRAM_ID: ContractClassName = "net.corda.finance.contracts.CommercialPaper"
}
// DOCEND 8
// DOCSTART 3
override fun verify(tx: LedgerTransaction) {
// Group by everything except owner: any modification to the CP at all is considered changing it fundamentally.
val groups = tx.groupStates(State::withoutOwner)
// 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>()
// DOCEND 3
// DOCSTART 4
val timeWindow: TimeWindow? = tx.timeWindow
for ((inputs, outputs, _) in groups) {
when (command.value) {
is Commands.Move -> {
val input = inputs.single()
requireThat {
"the transaction is signed by the owner of the CP" using (input.owner.owningKey in command.signers)
"the state is propagated" using (outputs.size == 1)
// Don't need to check anything else, as if outputs.size == 1 then the output is equal to
// the input ignoring the owner field due to the grouping.
}
}
is Commands.Redeem -> {
// Redemption of the paper requires movement of on-ledger cash.
val input = inputs.single()
val received = tx.outputs.map { it.data }.sumCashBy(input.owner)
val time = timeWindow?.fromTime ?: throw IllegalArgumentException("Redemptions must be timestamped")
requireThat {
"the paper must have matured" using (time >= input.maturityDate)
"the received amount equals the face value" using (received == input.faceValue)
"the paper must be destroyed" using outputs.isEmpty()
"the transaction is signed by the owner of the CP" using (input.owner.owningKey in command.signers)
}
}
is Commands.Issue -> {
val output = outputs.single()
val time = timeWindow?.untilTime ?: throw IllegalArgumentException("Issuances must be timestamped")
requireThat {
// Don't allow people to issue commercial paper under other entities identities.
"output states are issued by a command signer" using (output.issuance.party.owningKey in command.signers)
"output values sum to more than the inputs" using (output.faceValue.quantity > 0)
"the maturity date is not in the past" using (time < output.maturityDate)
// Don't allow an existing CP state to be replaced by this issuance.
"can't reissue an existing state" using inputs.isEmpty()
}
}
else -> throw IllegalArgumentException("Unrecognised command")
}
}
// DOCEND 4
}
// DOCSTART 2
interface Commands : CommandData {
class Move : TypeOnlyCommandData(), Commands
class Redeem : TypeOnlyCommandData(), Commands
class Issue : TypeOnlyCommandData(), Commands
}
// DOCEND 2
// DOCSTART 5
fun generateIssue(issuance: PartyAndReference, faceValue: Amount<Issued<Currency>>, maturityDate: Instant,
notary: Party): TransactionBuilder {
val state = State(issuance, issuance.party, faceValue, maturityDate)
val stateAndContract = StateAndContract(state, CP_PROGRAM_ID)
return TransactionBuilder(notary = notary).withItems(stateAndContract, Command(Commands.Issue(), issuance.party.owningKey))
}
// DOCEND 5
// DOCSTART 6
fun generateMove(tx: TransactionBuilder, paper: StateAndRef<State>, newOwner: AbstractParty) {
tx.addInputState(paper)
val outputState = paper.state.data.withNewOwner(newOwner).ownableState
tx.addOutputState(outputState, CP_PROGRAM_ID)
tx.addCommand(Command(Commands.Move(), paper.state.data.owner.owningKey))
}
// DOCEND 6
// DOCSTART 7
@Throws(InsufficientBalanceException::class)
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, services: ServiceHub) {
// Add the cash movement using the states in our vault.
Cash.generateSpend(
services = services,
tx = tx,
amount = paper.state.data.faceValue.withoutIssuer(),
ourIdentity = services.myInfo.chooseIdentityAndCert(),
to = paper.state.data.owner
)
tx.addInputState(paper)
tx.addCommand(Command(Commands.Redeem(), paper.state.data.owner.owningKey))
}
// DOCEND 7
}
// DOCSTART 1
data class State(
val issuance: PartyAndReference,
override val owner: AbstractParty,
val faceValue: Amount<Issued<Currency>>,
val maturityDate: Instant
) : OwnableState {
override val participants = listOf(owner)
fun withoutOwner() = copy(owner = AnonymousParty(NullKeys.NullPublicKey))
override fun withNewOwner(newOwner: AbstractParty) = CommandAndState(CommercialPaper.Commands.Move(), copy(owner = newOwner))
}
// DOCEND 1

View File

@ -0,0 +1,64 @@
package net.corda.docs.tutorial.flowstatemachines
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Amount
import net.corda.core.contracts.OwnableState
import net.corda.core.contracts.StateAndRef
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.finance.flows.TwoPartyTradeFlow
import java.util.*
// DOCSTART 1
object TwoPartyTradeFlow {
class UnacceptablePriceException(givenPrice: Amount<Currency>) : FlowException("Unacceptable price: $givenPrice")
class AssetMismatchException(val expectedTypeName: String, val typeName: String) : FlowException() {
override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName"
}
/**
* This object is serialised to the network and is the first flow message the seller sends to the buyer.
*
* @param payToIdentity anonymous identity of the seller, for payment to be sent to.
*/
@CordaSerializable
data class SellerTradeInfo(
val price: Amount<Currency>,
val payToIdentity: PartyAndCertificate
)
open class Seller(private val otherSideSession: FlowSession,
private val assetToSell: StateAndRef<OwnableState>,
private val price: Amount<Currency>,
private val myParty: PartyAndCertificate,
override val progressTracker: ProgressTracker = TwoPartyTradeFlow.Seller.tracker()) : FlowLogic<SignedTransaction>() {
companion object {
fun tracker() = ProgressTracker()
}
@Suspendable
override fun call(): SignedTransaction {
TODO()
}
}
open class Buyer(private val sellerSession: FlowSession,
private val notary: Party,
private val acceptablePrice: Amount<Currency>,
private val typeToBuy: Class<out OwnableState>,
private val anonymous: Boolean) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
TODO()
}
}
}
// DOCEND 1

View File

@ -0,0 +1,45 @@
package net.corda.docs.tutorial.tearoffs
import net.corda.core.contracts.Command
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.MerkleTreeException
import net.corda.core.transactions.FilteredTransaction
import net.corda.core.transactions.FilteredTransactionVerificationException
import net.corda.core.transactions.SignedTransaction
import net.corda.finance.contracts.Fix
import net.corda.testing.ALICE
import java.util.function.Predicate
fun main(args: Array<String>) {
// Typealias to make the example coherent.
val oracle = ALICE
val stx = Any() as SignedTransaction
// DOCSTART 1
val filtering = Predicate<Any> {
when (it) {
is Command<*> -> oracle.owningKey in it.signers && it.value is Fix
else -> false
}
}
// DOCEND 1
// DOCSTART 2
val ftx: FilteredTransaction = stx.buildFilteredTransaction(filtering)
// DOCEND 2
// DOCSTART 3
// Direct access to included commands, inputs, outputs, attachments etc.
val cmds: List<Command<*>> = ftx.commands
val ins: List<StateRef> = ftx.inputs
val timeWindow: TimeWindow? = ftx.timeWindow
// ...
// DOCEND 3
try {
ftx.verify()
} catch (e: FilteredTransactionVerificationException) {
throw MerkleTreeException("Rate Fix Oracle: Couldn't verify partial Merkle tree.")
}
}

View File

@ -0,0 +1,247 @@
package net.corda.docs.tutorial.testdsl
import net.corda.core.utilities.days
import net.corda.finance.DOLLARS
import net.corda.finance.`issued by`
import net.corda.finance.contracts.CP_PROGRAM_ID
import net.corda.finance.contracts.CommercialPaper
import net.corda.finance.contracts.ICommercialPaperState
import net.corda.finance.contracts.asset.CASH
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.asset.`issued by`
import net.corda.finance.contracts.asset.`owned by`
import net.corda.testing.*
import org.junit.Test
class CommercialPaperTest {
// DOCSTART 1
fun getPaper(): ICommercialPaperState = CommercialPaper.State(
issuance = MEGA_CORP.ref(123),
owner = MEGA_CORP,
faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123),
maturityDate = TEST_TX_TIME + 7.days
)
// DOCEND 1
// DOCSTART 2
@Test
fun simpleCP() {
val inState = getPaper()
ledger {
transaction {
attachments(CP_PROGRAM_ID)
input(CP_PROGRAM_ID) { inState }
this.verifies()
}
}
}
// DOCEND 2
// DOCSTART 3
@Test
fun simpleCPMove() {
val inState = getPaper()
ledger {
transaction {
input(CP_PROGRAM_ID) { inState }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
attachments(CP_PROGRAM_ID)
this.verifies()
}
}
}
// DOCEND 3
// DOCSTART 4
@Test
fun simpleCPMoveFails() {
val inState = getPaper()
ledger {
transaction {
input(CP_PROGRAM_ID) { inState }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
attachments(CP_PROGRAM_ID)
this `fails with` "the state is propagated"
}
}
}
// DOCEND 4
// DOCSTART 5
@Test
fun simpleCPMoveSuccess() {
val inState = getPaper()
ledger {
transaction {
input(CP_PROGRAM_ID) { inState }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
attachments(CP_PROGRAM_ID)
this `fails with` "the state is propagated"
output(CP_PROGRAM_ID, "alice's paper") { inState.withOwner(ALICE) }
this.verifies()
}
}
}
// DOCEND 5
// DOCSTART 6
@Test
fun `simple issuance with tweak`() {
ledger {
transaction {
output(CP_PROGRAM_ID, "paper") { getPaper() } // Some CP is issued onto the ledger by MegaCorp.
attachments(CP_PROGRAM_ID)
tweak {
// The wrong pubkey.
command(BIG_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
timeWindow(TEST_TX_TIME)
this `fails with` "output states are issued by a command signer"
}
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
timeWindow(TEST_TX_TIME)
this.verifies()
}
}
}
// DOCEND 6
// DOCSTART 7
@Test
fun `simple issuance with tweak and top level transaction`() {
transaction {
output(CP_PROGRAM_ID, "paper") { getPaper() } // Some CP is issued onto the ledger by MegaCorp.
attachments(CP_PROGRAM_ID)
tweak {
// The wrong pubkey.
command(BIG_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
timeWindow(TEST_TX_TIME)
this `fails with` "output states are issued by a command signer"
}
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
timeWindow(TEST_TX_TIME)
this.verifies()
}
}
// DOCEND 7
// DOCSTART 8
@Test
fun `chain commercial paper`() {
val issuer = MEGA_CORP.ref(123)
ledger {
unverifiedTransaction {
attachments(Cash.PROGRAM_ID)
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE)
}
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
output(CP_PROGRAM_ID, "paper") { getPaper() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
attachments(CP_PROGRAM_ID)
timeWindow(TEST_TX_TIME)
this.verifies()
}
transaction("Trade") {
input("paper")
input("alice's $900")
output(Cash.PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP }
output(CP_PROGRAM_ID, "alice's paper") { "paper".output<ICommercialPaperState>().withOwner(ALICE) }
command(ALICE_PUBKEY) { Cash.Commands.Move() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this.verifies()
}
}
}
// DOCEND 8
// DOCSTART 9
@Test
fun `chain commercial paper double spend`() {
val issuer = MEGA_CORP.ref(123)
ledger {
unverifiedTransaction {
attachments(Cash.PROGRAM_ID)
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE)
}
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
output(CP_PROGRAM_ID, "paper") { getPaper() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
attachments(CP_PROGRAM_ID)
timeWindow(TEST_TX_TIME)
this.verifies()
}
transaction("Trade") {
input("paper")
input("alice's $900")
output(Cash.PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP }
output(CP_PROGRAM_ID, "alice's paper") { "paper".output<ICommercialPaperState>().withOwner(ALICE) }
command(ALICE_PUBKEY) { Cash.Commands.Move() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this.verifies()
}
transaction {
input("paper")
// We moved a paper to another pubkey.
output(CP_PROGRAM_ID, "bob's paper") { "paper".output<ICommercialPaperState>().withOwner(BOB) }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this.verifies()
}
this.fails()
}
}
// DOCEND 9
// DOCSTART 10
@Test
fun `chain commercial tweak`() {
val issuer = MEGA_CORP.ref(123)
ledger {
unverifiedTransaction {
attachments(Cash.PROGRAM_ID)
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE)
}
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
output(CP_PROGRAM_ID, "paper") { getPaper() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
attachments(CP_PROGRAM_ID)
timeWindow(TEST_TX_TIME)
this.verifies()
}
transaction("Trade") {
input("paper")
input("alice's $900")
output(Cash.PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP }
output(CP_PROGRAM_ID, "alice's paper") { "paper".output<ICommercialPaperState>().withOwner(ALICE) }
command(ALICE_PUBKEY) { Cash.Commands.Move() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this.verifies()
}
tweak {
transaction {
input("paper")
// We moved a paper to another pubkey.
output(CP_PROGRAM_ID, "bob's paper") { "paper".output<ICommercialPaperState>().withOwner(BOB) }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this.verifies()
}
this.fails()
}
this.verifies()
}
}
// DOCEND 10
}