I am trying to query a vault of other node (PartyB) from PartyA, where the flow should be initiated by RPC of PartyA. If PartyB agrees to share the result PartyA should be able to see the result. Is it possible?
No. The node operator can only query their own node's vault.
However, if PartyB is willing to share their vault's contents with PartyA, you can write a flow pair that will allow PartyA to request states from PartyB's vault.
For example, if you want to request a LinearState with a specific ID, you could write:
#InitiatingFlow
#StartableByRPC
public static class RequestStateFlow extends FlowLogic<StateAndRef> {
private final UniqueIdentifier stateLinearId;
private final Party otherParty;
public RequestStateFlow(UniqueIdentifier stateLinearId, Party otherParty) {
this.stateLinearId = stateLinearId;
this.otherParty = otherParty;
}
#Suspendable
#Override
public StateAndRef call() throws FlowException {
FlowSession session = initiateFlow(otherParty);
return session.sendAndReceive(StateAndRef.class, stateLinearId).unwrap(it -> it);
}
}
#InitiatedBy(RequestStateFlow.class)
public static class SendStateFlow extends FlowLogic<Void> {
private final FlowSession otherPartySession;
public SendStateFlow(FlowSession otherPartySession) {
this.otherPartySession = otherPartySession;
}
#Suspendable
#Override
public Void call() throws FlowException {
UniqueIdentifier stateLinearId = otherPartySession.receive(UniqueIdentifier.class).unwrap(it -> it);
QueryCriteria.LinearStateQueryCriteria criteria = new QueryCriteria.LinearStateQueryCriteria(
null,
ImmutableList.of(stateLinearId),
Vault.StateStatus.UNCONSUMED,
null);
Vault.Page<LinearState> results = getServiceHub().getVaultService().queryBy(LinearState.class, criteria);
StateAndRef state = results.getStates().get(0);
otherPartySession.send(state);
return null;
}
}
Related
I am exploring this cordapp example https://github.com/corda/corda-training-template.git
I have modified the code as per the solution template available in Github. Whenever I am starting the flow, I am getting error like (no constructor found, missing parameter state), please refer to the below screenshot.
I have initiated this flow from node A so I am confused about what will be the constructor parameter. I have pasted the state code below.
Flow issue screenshot
IOUState Constructor code
#BelongsToContract(IOUContract.class)
public class IOUState implements ContractState, LinearState {
public final Amount<Currency> amount;
public final Party lender;
public final Party borrower;
public final Amount<Currency> paid;
private final UniqueIdentifier linearId;
// Private constructor used only for copying a State object
#ConstructorForDeserialization
private IOUState(Amount<Currency> amount, Party lender, Party borrower, Amount<Currency> paid, UniqueIdentifier linearId){
this.amount = amount;
this.lender = lender;
this.borrower = borrower;
this.paid = paid;
this.linearId = linearId;
}
public IOUState(Amount<Currency> amount, Party lender, Party borrower) {
this(amount, lender, borrower, new Amount<>(0, amount.getToken()), new UniqueIdentifier());
}
public Amount<Currency> getAmount() {
return amount;
}
public Party getLender() {
return lender;
}
public Party getBorrower() {
return borrower;
}
public Amount<Currency> getPaid() {
return paid;
}
#Override
public UniqueIdentifier getLinearId() {
return linearId;
}
/**
* This method will return a list of the nodes which can "use" this state in a valid transaction. In this case, the
* lender or the borrower.
*/
#Override
public List<AbstractParty> getParticipants() {
return ImmutableList.of(lender, borrower);
}
/**
* Helper methods for when building transactions for settling and transferring IOUs.
* - [pay] adds an amount to the paid property. It does no validation.
* - [withNewLender] creates a copy of the current state with a newly specified lender. For use when transferring.
* - [copy] creates a copy of the state using the internal copy constructor ensuring the LinearId is preserved.
*/
public IOUState pay(Amount<Currency> amountToPay) {
Amount<Currency> newAmountPaid = this.paid.plus(amountToPay);
return new IOUState(amount, lender, borrower, newAmountPaid, linearId);
}
public IOUState withNewLender(Party newLender) {
return new IOUState(amount, newLender, borrower, paid, linearId);
}
public IOUState copy(Amount<Currency> amount, Party lender, Party borrower, Amount<Currency> paid) {
return new IOUState(amount, lender, borrower, paid, this.getLinearId());
}
}
I assume that one of the input parameters in your flow's constructor is a class (Amount<Currency>), so you have to pass an instance of that class when you start the flow in the node's shell.
See here how to create an instance of a class in the node's shell.
The problem in your code is that you're using Amount<Currency>; meaning to construct an instance, you'd have to do something like this:
Amount<Currency> usdAmount = new Amount(100, Currency.getInstance("USD"));
We have to use Currency.getInstance(), because Currency doesn't have a public constructor. From Currency code:
/**
* Constructs a <code>Currency</code> instance. The constructor is private
* so that we can insure that there's never more than one instance for a
* given currency.
*/
private Currency(String currencyCode, int defaultFractionDigits, int numericCode) {
this.currencyCode = currencyCode;
this.defaultFractionDigits = defaultFractionDigits;
this.numericCode = numericCode;
}
The node shell doesn't allow calling functions (i.e. Currency.getInstance()).
So in order for your flow to work, you have to modify its constructor and make it accept only primitive parameters as opposed to taking classes as well; then inside the constructor you can create an instance of the Amount class. It would look like this:
// Class attributes.
private Amount<Currency> iouAmount;
private Party otherParty;
// Constructor.
public Initiator(String currencyCode, long quantity, Party otherParty) {
this.iouAmount = new Amount(quantity, Currency.getInstance(currencyCode));
this.otherParty = otherParty;
}
Now inside the node's shell:
flow start IOUIssueFlow$Initiator currencyCode: USD, quantity: 100, otherParty: PartyB
Please note that you're using Amount, so quantity must be of type long. See here.
When i run the flow i do get a done message but when i query the vault I dont see any transactions there. I cannot figure out what I am doing wrong. The same code was working last week properly.
I am trying to send " Hello" to another node. similar to YO cordaAPP
Here is my flow class
package java_bootcamp;
import co.paralleluniverse.fibers.Suspendable;
import net.corda.core.flows.*;
import net.corda.core.identity.Party;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder;
import net.corda.core.utilities.ProgressTracker;
/* Our flow, automating the process of updating the ledger.
* See src/main/java/examples/IAmAFlowPair.java for an example. */
#InitiatingFlow
#StartableByRPC
public class TokenIssueFlow extends FlowLogic<SignedTransaction> {
private final ProgressTracker progressTracker = new ProgressTracker();
private final Party receiver;
private final String text;
public TokenIssueFlow(Party receiver, String text) {
this.receiver = receiver;
this.text = text;
}
#Override
public ProgressTracker getProgressTracker() {
return progressTracker;
}
#Suspendable
#Override
public SignedTransaction call() throws FlowException {
// We choose our transaction's notary (the notary prevents double-spends).
Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
// We get a reference to our own identity.
Party issuer = getOurIdentity();
/* ============================================================================
* TODO 1 - Create our TokenState to represent on-ledger tokens!
* ===========================================================================*/
// We create our new TokenState.
TokenState tokenState = new TokenState(issuer,receiver,text);
/* ============================================================================
* TODO 3 - Build our token issuance transaction to update the ledger!
* ===========================================================================*/
// We build our transaction.
TransactionBuilder transactionBuilder = new TransactionBuilder(notary);
transactionBuilder.addCommand(new TokenContract.Issue(),issuer.getOwningKey());
transactionBuilder.addOutputState(tokenState,TokenContract.ID);
/* ============================================================================
* TODO 2 - Write our TokenContract to control token issuance!
* ===========================================================================*/
// We check our transaction is valid based on its contracts.
transactionBuilder.verify(getServiceHub());
// We sign the transaction with our private key, making it immutable.
SignedTransaction signedTransaction = getServiceHub().signInitialTransaction(transactionBuilder);
// We get the transaction notarised and recorded automatically by the platform.
return subFlow(new FinalityFlow(signedTransaction));
}
}
Apologies to everyone. I was not paying attention.
While running the vaultQuery I was specifying the FLOW class instead to STATE class.
Previously
run vaultQuery contractStateType: com.template.HelloFlow:
Correct Way:
run vaultQuery contractStateType: java_bootcamp.TokenState
when I create a flow, they do not appear in the flow list. Do I need to create them in the cordapp module? Project builds normally.
Project schema1
So without the source code, there's no way for us to really know why it doesn't appear in the list.
It's worth mentioning there are some obscure bugs as well, I suspect that what you're running into here is that your code isn't overriding the progressTracker.
Note the docs here:
Flows cannot be run unless they override the progress tracker
So just try to keep that in mind that it could be causing your issue.
Here's a progressTracker example from the yo cordapp on the corda github.
public class YoFlow extends FlowLogic<SignedTransaction> {
private static final ProgressTracker.Step CREATING = new ProgressTracker.Step("Creating a new Yo!");
private static final ProgressTracker.Step SIGNING = new ProgressTracker.Step("Signing the Yo!");
private static final ProgressTracker.Step VERIFYING = new ProgressTracker.Step("Verfiying the Yo!");
private static final ProgressTracker.Step FINALISING = new ProgressTracker.Step("Sending the Yo!") {
#Nullable
#Override
public ProgressTracker childProgressTracker() {
return FinalityFlow.tracker();
}
};
ProgressTracker progressTracker = new ProgressTracker(
CREATING,
SIGNING,
VERIFYING,
FINALISING
);
#Nullable
#Override
public ProgressTracker getProgressTracker() {
return progressTracker;
}
private final Party target;
public YoFlow(Party target) {
this.target = target;
}
#Suspendable
#Override
public SignedTransaction call() throws FlowException {
progressTracker.setCurrentStep(CREATING);
Source: Docs limitations page: https://docs.corda.net/docs/corda-os/4.4/shell.html#limitations
I am trying to mock a responder flow inside my unit tests, my responder flow does several validations that deals with configurations and off ledger services. I would like to mock the value to always return a true so that the unit test does not have any dependencies on the other components in the network.
The purpose is only for unit tests, is there any way I could mock the response using API as I am aware that we have to register the responder classes during the mock network setup?
Simply define a dummy responder flow, and register that instead of the real responder flow when setting up the mock network:
public class FlowTests {
private MockNetwork network;
private StartedMockNode a;
private StartedMockNode b;
#InitiatedBy(ExampleFlow.Initiator.class)
public static class DummyResponder extends FlowLogic<Void> {
private final FlowSession otherPartySession;
public DummyResponder(FlowSession otherPartySession) {
this.otherPartySession = otherPartySession;
}
#Suspendable
#Override
public Void call() throws FlowException {
otherPartySession.send(true);
return null;
}
}
#Before
public void setup() {
network = new MockNetwork(ImmutableList.of("com.example.contract"));
a = network.createPartyNode(null);
b = network.createPartyNode(null);
// For real nodes this happens automatically, but we have to manually register the flow for tests.
for (StartedMockNode node : ImmutableList.of(a, b)) {
node.registerInitiatedFlow(DummyResponder.class);
}
network.runNetwork();
}
#After
public void tearDown() {
network.stopNodes();
}
#Rule
public final ExpectedException exception = ExpectedException.none();
#Test
public void flowUsesDummyResponder() throws ExecutionException, InterruptedException {
ExampleFlow.Initiator flow = new ExampleFlow.Initiator(-1, b.getInfo().getLegalIdentities().get(0));
CordaFuture<Boolean> future = a.startFlow(flow);
network.runNetwork();
Boolean bool = future.get();
assertEquals(true, bool);
}
}
I have replicated the Obligation Cordapp Transfer functionality and i am struck with the linearId of Unique Identifier. I have successfully exercised the Issue Cordapp and for transfer of Obligation, i have provided the flow command with linearId of generated Obligation. The parameter which i am passing through linearId is interpreted as the External id [argument in UniqueIdentifier] instead of id and so it is unable to find the Obligation to transfer.
Here are reference for issue.
The generated id for the Obligation created is
**linearId : externalId: null
id: "4799c549-8c2b-401f-90dd-1dc115fbcfba"
Thu Mar 29 18:41:02 IST 2018>>> flow start TransferObligation$Initiator newLender: "O=PartyC,L=Paris,C=FR",anonymous: false,linearId: "4799c549-8c2b-401f-90dd-1dc115fbcfba
The passed argument, linearId has to take as id [argument in UniqueIdentifier and is randomly generated if external id is not equal to null] as per Obligation Cordapp and has to do required transfer functionality. Instead it is taking linearId passed as externalId [argument in UniqueIdentifier] and so total linearId becoming externalId_id.
>Linear Id parameter is:4799c549-8c2b-401f-90dd-1dc115fbcfba_ace60f85-b360-4cf7b198-4d3d471f9d63
>Obtaining IOU from vault.
>States Size:0
Observing that ace60f85-b360-4cf7b198-4d3d471f9d63 is appended as id which is randomly generated and unable to find the Obligation generated.
How corda will interpret linearId passed? as externalId/Id as default?
In Obligation Cordapp it demonstrated in samples took as Id and required job is done.But the cordapp which i am exercising takes the passed parameter as externalId.
Or any changes has to be done in code level to take the linearId parameter as id?
Edit 1:
We are developing the code in java The transfer obligation for reference.Although it is just a replicate.Please have a look at it
package com.example.flow;
import co.paralleluniverse.fibers.Suspendable;
import com.example.contract.ActivityContract;
import com.example.state.Activity;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import net.corda.confidential.IdentitySyncFlow;
import net.corda.confidential.SwapIdentitiesFlow;
import net.corda.core.contracts.Command;
import net.corda.core.contracts.StateAndRef;
import net.corda.core.contracts.UniqueIdentifier;
import net.corda.core.flows.*;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.AnonymousParty;
import net.corda.core.identity.Party;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder;
import net.corda.core.utilities.ProgressTracker;
import net.corda.core.utilities.ProgressTracker.Step;
import java.security.PublicKey;
import java.util.*;
import static com.example.contract.ActivityContract.Activity_Contract_ID;
public class ActivityTransferFlow {
#StartableByRPC
#InitiatingFlow
public static class Initiator extends ActivityBaseFlow{
public final UniqueIdentifier linearId;
public final Party newLender;
public final Boolean anonymous;
private final Step INITIATION = new Step("Obtaining IOU from vault.");
private final Step BUILDING = new Step("Building and Verifying Transaction");
private final Step SIGNING = new Step("Signing gathered transaction");
private final Step SYNCING = new Step("Syncing gathered identities "){
#Override
public ProgressTracker childProgressTracker() {
return IdentitySyncFlow.Send.Companion.tracker();
}
};
private final Step GATHERING = new Step("Gathering counterparty signatures"){
#Override
public ProgressTracker childProgressTracker() {
return CollectSignaturesFlow.Companion.tracker();
}
};
private final Step FINALISING = new Step("Finalising transaction"){
#Override
public ProgressTracker childProgressTracker() {
return FinalityFlow.Companion.tracker();
}
};
private final ProgressTracker progressTracker = new ProgressTracker(
INITIATION,
BUILDING,
SIGNING,
SYNCING,
GATHERING,
FINALISING
);
public Initiator(UniqueIdentifier linearId, Party newLender, Boolean anonymous) {
this.linearId = linearId;
this.newLender = newLender;
this.anonymous = anonymous;
}
#Override
public ProgressTracker getProgressTracker() {
return progressTracker;
}
#Suspendable
#Override
public SignedTransaction call() throws FlowException {
// step1:Retrieve Activity specified by linear id from the vault
progressTracker.setCurrentStep(INITIATION);
final StateAndRef<Activity> activityToTransfer= getObligationByLinearId(linearId);
}
final Activity inputActivity=activityToTransfer.getState().getData();
//step2:This flow can only be initiated by current recipient
final AbstractParty lenderIdentity =getLenderIdentity(inputActivity);
//step3:Abort if the borrower started this flow
if(!getOurIdentity().equals(lenderIdentity))
{
throw new IllegalStateException("Activity transfer can only be initiated by the lender.");
}
//step4:Creating the new obligation state reflecting a new lender
progressTracker.setCurrentStep(BUILDING);
final Activity transferredActivity =createOutputActivity(inputActivity);
//step4:Create transfer command
final List<PublicKey> signerKeys = new ImmutableList.Builder<PublicKey>()
.addAll(inputActivity.getParticipantKeys())
.add(transferredActivity.getLender().getOwningKey()).build();
final Command transferCommand = new Command<>(new ActivityContract.Commands.ActivityTransfer(), signerKeys);
//step5:Create a transaction builder and then add states and commands.
final TransactionBuilder builder = new TransactionBuilder(getNotary())
.addInputState(activityToTransfer)
.addOutputState(transferredActivity, Activity_Contract_ID)
.addCommand(transferCommand);
//step6:Verify and sign the transaction
progressTracker.setCurrentStep(SIGNING);
builder.verify(getServiceHub());
final SignedTransaction ptx=getServiceHub().signInitialTransaction(builder, inputActivity.getLender().getOwningKey());
//step7:Getting party object from the borrower
progressTracker.setCurrentStep(SYNCING);
final Party borrower=getBorrowerIdentity(inputActivity);
//step8:Send any keys or certificates so the signers can verify each other identity
Set<FlowSession> sessions=new HashSet<>();
Set<Party> parties= ImmutableSet.of(borrower,newLender);
for (Party party:parties) {
sessions.add(initiateFlow(party));
}
subFlow(new IdentitySyncFlow.Send(sessions,ptx.getTx(),SYNCING.childProgressTracker()));
//step9:Gathering signatures from the borrower and the new lender
progressTracker.setCurrentStep(GATHERING);
final SignedTransaction stx=subFlow(new CollectSignaturesFlow(
ptx,
sessions,
ImmutableList.of(inputActivity.getLender().getOwningKey()),
GATHERING.childProgressTracker()
));
//Step10:Notarise and record the transaction into vault and broadcast the transaction
progressTracker.setCurrentStep(FINALISING);
return subFlow(new FinalityFlow(stx,ImmutableSet.of(getOurIdentity())));
}
#Suspendable
private AbstractParty getLenderIdentity(Activity inputObligation) {
if (inputObligation.getLender() instanceof AnonymousParty) {
return resolveIdentity(inputObligation.getLender());
} else {
return inputObligation.getLender();
}
}
#Suspendable
private Activity createOutputActivity(Activity inputActivity) throws FlowException {
if (anonymous) {
final HashMap<Party, AnonymousParty> txKeys = subFlow(new SwapIdentitiesFlow(newLender));
if (!txKeys.containsKey(newLender)) {
throw new FlowException("Couldn't get lender's conf. identity.");
}
final AnonymousParty anonymousLender = txKeys.get(newLender);
return inputActivity.withNewLender(anonymousLender);
} else {
return inputActivity.withNewLender(newLender);
}
}
#Suspendable
private Party getBorrowerIdentity(Activity inputActivity) {
if (inputActivity.getBorrower() instanceof AnonymousParty) {
return resolveIdentity(inputActivity.getBorrower());
} else {
return (Party) inputActivity.getBorrower();
}
}
}
#InitiatedBy(Initiator.class)
public static class Responder extends FlowLogic<SignedTransaction> {
private final FlowSession otherFlow;
public Responder(FlowSession otherFlow) {
this.otherFlow = otherFlow;
}
#Suspendable
#Override
public SignedTransaction call() throws FlowException {
subFlow(new IdentitySyncFlow.Receive(otherFlow));
SignedTransaction stx = subFlow(new ActivityBaseFlow.SignTxFlowNoChecking(otherFlow, SignTransactionFlow.Companion.tracker()));
return waitForLedgerCommit(stx.getId());
}
}
}
Edit 2:
The getObligationByLinearId method in ActivityBaseFlow
and the command we use is
flow start ActivityTransferFlow$Initiator linearId: d21827b7-e4be-4874-9383-e9f339d7c9ea,newLender: "O=PartyC,L=Paris,C=FR",anonymous: false
StateAndRef<Activity> getObligationByLinearId(UniqueIdentifier linearId) throws FlowException {
System.out.println("Linear Id parameter is:"+linearId);
QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria(
null,
ImmutableList.of(linearId),
Vault.StateStatus.UNCONSUMED,
null);
List<StateAndRef<Activity>> obligations = getServiceHub().getVaultService().queryBy(Activity.class, queryCriteria).getStates();
if (obligations.size() != 1) {
System.out.println("Linear Id 1:"+linearId);
throw new FlowException(String.format("Obligation with id %s not found.", linearId));
}
//System.out.println("Linear Id 2:"+linearId);
return obligations.get(0);
}
The UniqueIdentifier constructor is taking the string input from flow start as the externalId.
Change your flow to accept a string instead and manually parse it using UniqueIdentifier.fromString(inputString). i.e
public Initiator(String inputString, Party newLender, Boolean anonymous) {
this.linearId = UniqueIdentifier.fromString(inputString);
this.newLender = newLender;
this.anonymous = anonymous;
}