Skip to content

Commit

Permalink
Added Java test contract flow
Browse files Browse the repository at this point in the history
  • Loading branch information
parisyup committed May 17, 2024
1 parent 1793ce2 commit 69f4ac8
Show file tree
Hide file tree
Showing 4 changed files with 318 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public void verify(UtxoLedgerTransaction transaction) {
IOUState output = transaction.getOutputStates(IOUState.class).get(0);
requireThat(output.getParticipants().size() == 2, "The output state should have two and only two participants.");

requireThat(transaction.getOutputStates(IOUState.class).size() == 1, "Must have one output state.");
// Switches case based on the command
if(command.getClass() == IOUContract.Issue.class) {// Rules applied only to transactions with the Issue Command.
requireThat(transaction.getOutputContractStates().size() == 1, "Only one output states should be created when issuing an IOU.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.base.exceptions.CordaRuntimeException;
import net.corda.v5.base.types.MemberX500Name;
import net.corda.v5.crypto.SecureHash;
import net.corda.v5.ledger.utxo.UtxoLedgerService;
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
package com.r3.developers.samples.obligation.workflows;

import com.r3.developers.samples.obligation.workflows.FinalizeIOUFlow;
import com.r3.developers.samples.obligation.contracts.IOUContract;
import com.r3.developers.samples.obligation.states.IOUState;
import net.corda.v5.application.flows.ClientRequestBody;
import net.corda.v5.application.flows.ClientStartableFlow;
import net.corda.v5.application.flows.CordaInject;
import net.corda.v5.application.flows.FlowEngine;
import net.corda.v5.application.marshalling.JsonMarshallingService;
import net.corda.v5.application.membership.MemberLookup;
import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.base.exceptions.CordaRuntimeException;
import net.corda.v5.base.types.MemberX500Name;
import net.corda.v5.ledger.common.NotaryLookup;
import net.corda.v5.ledger.utxo.Command;
import net.corda.v5.ledger.utxo.StateAndRef;
import net.corda.v5.ledger.utxo.StateRef;
import net.corda.v5.ledger.utxo.UtxoLedgerService;
import net.corda.v5.membership.MemberInfo;
import net.corda.v5.membership.NotaryInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.PublicKey;
import java.time.Duration;
import java.time.Instant;
import java.util.*;

import static java.util.stream.Collectors.toList;

public class TestContractFlow implements ClientStartableFlow {

private static final Logger log = LoggerFactory.getLogger(TestContractFlow.class);

@CordaInject
private JsonMarshallingService jsonMarshallingService;

@CordaInject
private MemberLookup memberLookup;

@CordaInject
private UtxoLedgerService ledgerService;

@CordaInject
private NotaryLookup notaryLookup;

@CordaInject
private FlowEngine flowEngine;


public StateRef inputStateRef;

@Suspendable
@Override
public String call(ClientRequestBody requestBody) {
Map<String, String> results = new HashMap<>();

log.info("TestContractFlow.call() called");

try {
TestContractFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, TestContractFlowArgs.class);

MemberInfo myInfo = memberLookup.myInfo();
MemberInfo otherMember = memberLookup.lookup(MemberX500Name.parse(flowArgs.getOtherMember()));
if (otherMember == null) {
throw new CordaRuntimeException("MemberLookup can't find otherMember specified in flow arguments.");
}

NotaryInfo notary = notaryLookup.getNotaryServices().stream().findFirst().get();

UUID iouStateId = null;

try {
IOUState iouState = new IOUState(
10,
myInfo.getName(),
otherMember.getName(),
3,
UUID.randomUUID(),
Arrays.asList(myInfo.getLedgerKeys().get(0), otherMember.getLedgerKeys().get(0))
);

iouStateId = iouState.getLinearId();

var txBuilder = ledgerService.createTransactionBuilder()
.setNotary(notary.getName())
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addOutputState(iouState)
.addCommand(new IOUContract.Issue())
.addSignatories(iouState.getParticipants());

var signedTransaction = txBuilder.toSignedTransaction();

inputStateRef = new StateRef(signedTransaction.getId(), 0);
flowEngine.subFlow(new FinalizeIOUFlow.FinalizeIOU(signedTransaction, Arrays.asList(otherMember.getName())));

} catch (Exception e) {
throw new CordaRuntimeException("Set up transaction could not be created because of exception: " + e.getMessage());
}

// ************* START TESTS ****************

// Multiple Commands not permitted
results.put("Multiple Commands not permitted", testMultipleCommandsNotPermitted(myInfo, otherMember, notary));

// Only Two Participants
results.put("Only Two Participants", testOnlyTwoParticipants(myInfo, otherMember, notary));

// Settle needs only one output
results.put("Settle needs only one output", testSettleNeedsOnlyOneOutput(myInfo, otherMember, notary));

// Transfer needs only one output
results.put("Transfer needs only one output", testTransferNeedsOnlyOneOutput(myInfo, otherMember, notary));

// Issue needs only one output
results.put("Issue needs only one output", testIssueNeedsOnlyOneOutput(myInfo, otherMember, notary));

return results.toString();

} catch (Exception e) {
log.warn("Failed to process utxo flow for request body '" + requestBody + "' because: '" + e.getMessage() + "'");
throw e;
}
}

@Suspendable
private String testMultipleCommandsNotPermitted(MemberInfo myInfo, MemberInfo otherMember, NotaryInfo notary) {
try {
IOUState iouState = new IOUState(
10,
myInfo.getName(),
otherMember.getName(),
3,
UUID.randomUUID(),
Arrays.asList(myInfo.getLedgerKeys().get(0), otherMember.getLedgerKeys().get(0))
);

var txBuilder = ledgerService.createTransactionBuilder()
.setNotary(notary.getName())
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addOutputState(iouState)
.addInputState(inputStateRef)
.addCommand(new IOUContract.Issue())
.addCommand(new IOUContract.Transfer())
.addSignatories(iouState.getParticipants());

var signedTransaction = txBuilder.toSignedTransaction();

return "Fail";

} catch (Exception e) {
String exceptionMessage = e.getMessage() != null ? e.getMessage() : "No exception message";
if (exceptionMessage.contains("Require a single command")) {
return "Pass";
} else {
return "Contract failed but with a different Exception: " + e.getMessage();
}
}
}

@Suspendable
private String testOnlyTwoParticipants(MemberInfo myInfo, MemberInfo otherMember, NotaryInfo notary) {
try {
IOUState iouState = new IOUState(
10,
myInfo.getName(),
otherMember.getName(),
3,
UUID.randomUUID(),
Collections.singletonList(myInfo.getLedgerKeys().get(0))
);

var txBuilder = ledgerService.createTransactionBuilder()
.setNotary(notary.getName())
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addOutputState(iouState)
.addCommand(new IOUContract.Issue())
.addSignatories(iouState.getParticipants());

var signedTransaction = txBuilder.toSignedTransaction();

return "Fail";

} catch (Exception e) {
String exceptionMessage = e.getMessage() != null ? e.getMessage() : "No exception message";
if (exceptionMessage.contains("only two participants")) {
return "Pass";
} else {
return "Contract failed but with a different Exception: " + e.getMessage();
}
}
}

@Suspendable
private String testSettleNeedsOnlyOneOutput(MemberInfo myInfo, MemberInfo otherMember, NotaryInfo notary) {
try {
IOUState iouState = new IOUState(
10,
myInfo.getName(),
otherMember.getName(),
3,
UUID.randomUUID(),
Arrays.asList(myInfo.getLedgerKeys().get(0), otherMember.getLedgerKeys().get(0))
);

var txBuilder = ledgerService.createTransactionBuilder()
.setNotary(notary.getName())
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addOutputState(iouState)
.addOutputState(iouState)
.addInputState(inputStateRef)
.addCommand(new IOUContract.Settle())
.addSignatories(iouState.getParticipants());

var signedTransaction = txBuilder.toSignedTransaction();

return "Fail";

} catch (Exception e) {
String exceptionMessage = e.getMessage() != null ? e.getMessage() : "No exception message";
if (exceptionMessage.contains("one output state")) {
return "Pass";
} else {
return "Contract failed but with a different Exception: " + e.getMessage();
}
}
}

@Suspendable
private String testTransferNeedsOnlyOneOutput(MemberInfo myInfo, MemberInfo otherMember, NotaryInfo notary) {
try {
IOUState iouOutputState = new IOUState(
10,
myInfo.getName(),
otherMember.getName(),
3,
UUID.randomUUID(),
Arrays.asList(myInfo.getLedgerKeys().get(0), otherMember.getLedgerKeys().get(0))
);

var txBuilder = ledgerService.createTransactionBuilder()
.setNotary(notary.getName())
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addOutputState(iouOutputState)
.addOutputState(iouOutputState)
.addInputState(inputStateRef)
.addCommand(new IOUContract.Transfer())
.addSignatories(iouOutputState.getParticipants());

var signedTransaction = txBuilder.toSignedTransaction();

return "Fail";

} catch (Exception e) {
String exceptionMessage = e.getMessage() != null ? e.getMessage() : "No exception message";
if (exceptionMessage.contains("one output state")) {
return "Pass";
} else {
return "Contract failed but with a different Exception: " + e.getMessage();
}
}
}

@Suspendable
private String testIssueNeedsOnlyOneOutput(MemberInfo myInfo, MemberInfo otherMember, NotaryInfo notary) {
try {
IOUState iouState = new IOUState(
10,
myInfo.getName(),
otherMember.getName(),
3,
UUID.randomUUID(),
Arrays.asList(myInfo.getLedgerKeys().get(0), otherMember.getLedgerKeys().get(0))
);

var txBuilder = ledgerService.createTransactionBuilder()
.setNotary(notary.getName())
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addOutputState(iouState)
.addOutputState(iouState)
.addCommand(new IOUContract.Issue())
.addSignatories(iouState.getParticipants());

var signedTransaction = txBuilder.toSignedTransaction();

return "Fail";

} catch (Exception e) {
String exceptionMessage = e.getMessage() != null ? e.getMessage() : "No exception message";
if (exceptionMessage.contains("one output state")) {
return "Pass";
} else {
return "Contract failed but with a different Exception: " + e.getMessage();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.r3.developers.samples.obligation.workflows;


public class TestContractFlowArgs {
private String otherMember;

public TestContractFlowArgs() {
}

public TestContractFlowArgs(String otherMember) {
this.otherMember = otherMember;
}

public String getOtherMember() {
return otherMember;
}

}

0 comments on commit 69f4ac8

Please sign in to comment.