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
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.
I have difficulty understanding some Kafka concepts in Java Spring Boot. I’d like to test a consumer against a real Kafka broker running on a server, which has some producers that write / have already written data to various topics. I would like to establish a connection with the server, consume the data, and verify or process its content in a test.
An enormous majority of examples (actually all I have seen so far) in the internet refer to embedded kafka, EmbeddedKafkaBroker, and show both a producer and a consumer implemented on one machine, locally. I haven’t found any example that would explain how to make a connection with a remote kafka server and read data from a particular topic.
I've written some code and I've printed the broker address with:
System.out.println(embeddedKafkaBroker.getBrokerAddress(0));
What I got is 127.0.0.1:9092, which means that it is local, so the connection with the remote server has not been established.
On the other hand, when I run the SpringBootApplication I get the payload from the remote broker.
Receiver:
#Component
public class Receiver {
private static final String TOPIC_NAME = "X";
private static final Logger LOGGER = LoggerFactory.getLogger(Receiver.class);
private CountDownLatch latch = new CountDownLatch(1);
public CountDownLatch getLatch() {
return latch;
}
#KafkaListener(topics = TOPIC_NAME)
public void receive(final byte[] payload) {
LOGGER.info("received the following payload: '{}'", payload);
latch.countDown();
}
}
Config:
#EnableKafka
#Configuration
public class ByteReceiverConfig {
#Autowired
EmbeddedKafkaBroker kafkaEmbeded;
#Value("${spring.kafka.bootstrap-servers}")
private String bootstrapServers;
#Value("${spring.kafka.consumer.group-id}")
private String groupIdConfig;
#Bean
public KafkaListenerContainerFactory<?> kafkaListenerContainerFactory() {
final ConcurrentKafkaListenerContainerFactory<Object, Object> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
return factory;
}
#Bean
ConsumerFactory<Object, Object> consumerFactory() {
return new DefaultKafkaConsumerFactory<>(consumerProperties());
}
#Bean
Map<String, Object> consumerProperties() {
final Map<String, Object> properties =
KafkaTestUtils.consumerProps("junit-test", "true", this.kafkaEmbeded);
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, ByteArrayDeserializer.class);
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ByteArrayDeserializer.class);
properties.put(ConsumerConfig.GROUP_ID_CONFIG, groupIdConfig);
return properties;
}
Test:
#EnableAutoConfiguration
#EnableKafka
#SpringBootTest(classes = {ByteReceiverConfig.class, Receiver.class})
#EmbeddedKafka
#ContextConfiguration(classes = ByteReceiverConfig.class)
#TestPropertySource(properties = { "spring.kafka.bootstrap-servers=${spring.embedded.kafka.brokers}",
"spring.kafka.consumer.group-id=EmbeddedKafkaTest"})
public class KafkaTest {
#Autowired
private KafkaListenerEndpointRegistry kafkaListenerEndpointRegistry;
#Autowired
EmbeddedKafkaBroker embeddedKafkaBroker;
#Autowired
Receiver receiver;
#BeforeEach
void waitForAssignment() {
for (MessageListenerContainer messageListenerContainer : kafkaListenerEndpointRegistry.getListenerContainers()) {
System.out.println(messageListenerContainer.getAssignedPartitions().isEmpty());
System.out.println(messageListenerContainer.toString());
System.out.println(embeddedKafkaBroker.getTopics().size());
System.out.println(embeddedKafkaBroker.getPartitionsPerTopic());
System.out.println(embeddedKafkaBroker.getBrokerAddress(0));
System.out.println(embeddedKafkaBroker.getBrokersAsString());
ContainerTestUtils.waitForAssignment(messageListenerContainer,
embeddedKafkaBroker.getPartitionsPerTopic());
}
#Test
public void testReceive() {
}
}
I would like somebody to shed some light on the following issues:
1.Can an instance of the class EmbeddedKafkaBroker be used to test data that comes from a remote broker, or is it only used for local tests, in which I would procude i.e send data to a topic that I created and consume data myself?
2.Is it possible to write a test class for a real kafka server? For instance to verify if a connection has been establish, or if a data has been read from a specific topic. What annotations, configurations, and classes would be needed in such case?
3.If I only want to consume data, do I have to provide the producer configuration in a config file (it would be strange, but all examples I have encountered so far did it)?
4.Do you know any resources (books, websites etc.) that show real examples of using kafka i.e. with a remote kafka server, with a procuder or a consumer only?
You don't need an embedded broker at all if you want to talk to an external broker only.
Yes, just set the bootstrap servers property appropriately.
No, you don't need producer configuration.
EDIT
#SpringBootApplication
public class So56044105Application {
public static void main(String[] args) {
SpringApplication.run(So56044105Application.class, args);
}
#Bean
public NewTopic topic() {
return new NewTopic("so56044105", 1, (short) 1);
}
}
spring.kafka.bootstrap-servers=10.0.0.8:9092
spring.kafka.consumer.enable-auto-commit=false
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { So56044105Application.class, So56044105ApplicationTests.Config.class })
public class So56044105ApplicationTests {
#Autowired
public Config config;
#Test
public void test() throws InterruptedException {
assertThat(config.latch.await(10, TimeUnit.SECONDS)).isTrue();
assertThat(config.received.get(0)).isEqualTo("foo");
}
#Configuration
public static class Config implements ConsumerSeekAware {
List<String> received = new ArrayList<>();
CountDownLatch latch = new CountDownLatch(3);
#KafkaListener(id = "so56044105", topics = "so56044105")
public void listen(String in) {
System.out.println(in);
this.received.add(in);
this.latch.countDown();
}
#Override
public void registerSeekCallback(ConsumerSeekCallback callback) {
}
#Override
public void onPartitionsAssigned(Map<TopicPartition, Long> assignments, ConsumerSeekCallback callback) {
System.out.println("Seeking to beginning");
assignments.keySet().forEach(tp -> callback.seekToBeginning(tp.topic(), tp.partition()));
}
#Override
public void onIdleContainer(Map<TopicPartition, Long> assignments, ConsumerSeekCallback callback) {
}
}
}
There are some examples in this repository for bootstrapping real Kafka producers and consumers across a variety of configurations — plaintext, SSL, with and without authentication, etc.
Note: the repo above contains examples for the Effective Kafka book, which I am the author of. However, they can be used freely without the book and hopefully they make just as much sense on their own.
More to the point, here are a pair of examples for a basic producer and a consumer.
/** A sample Kafka producer. */
import static java.lang.System.*;
import java.util.*;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.*;
public final class BasicProducerSample {
public static void main(String[] args) throws InterruptedException {
final var topic = "getting-started";
final Map<String, Object> config =
Map.of(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092",
ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName(),
ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName(),
ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
try (var producer = new KafkaProducer<String, String>(config)) {
while (true) {
final var key = "myKey";
final var value = new Date().toString();
out.format("Publishing record with value %s%n",
value);
final Callback callback = (metadata, exception) -> {
out.format("Published with metadata: %s, error: %s%n",
metadata, exception);
};
// publish the record, handling the metadata in the callback
producer.send(new ProducerRecord<>(topic, key, value), callback);
// wait a second before publishing another
Thread.sleep(1000);
}
}
}
}
/** A sample Kafka consumer. */
import static java.lang.System.*;
import java.time.*;
import java.util.*;
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.serialization.*;
public final class BasicConsumerSample {
public static void main(String[] args) {
final var topic = "getting-started";
final Map<String, Object> config =
Map.of(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092",
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName(),
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName(),
ConsumerConfig.GROUP_ID_CONFIG, "basic-consumer-sample",
ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest",
ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
try (var consumer = new KafkaConsumer<String, String>(config)) {
consumer.subscribe(Set.of(topic));
while (true) {
final var records = consumer.poll(Duration.ofMillis(100));
for (var record : records) {
out.format("Got record with value %s%n", record.value());
}
consumer.commitAsync();
}
}
}
}
Now, these are obviously not unit tests. But with very little rework they could be turned into one. The next step would be to remove Thread.sleep() and add assertions. Note, since Kafka is inherently asynchronous, naively asserting a published message in a consumer immediately after publishing will fail. For a robust, repeatable test, you may want to use something like Timesert.
I have created a CordaService running on my node. I want this service to start flows based on various conditions. However, the ServiceHub provided to the service does not provide the ability to start flows.
Is there any flow for a service to start a flow? How would I do thi?
Yes. Simply pass your CordaService an AppServiceHub instead of a ServiceHub in its constructor.
The AppServiceHub interface extends the ServiceHub interface to give a node the ability to start flows:
interface AppServiceHub : ServiceHub {
fun <T> startFlow(flow: FlowLogic<T>): FlowHandle<T>
fun <T> startTrackedFlow(flow: FlowLogic<T>): FlowProgressHandle<T>
}
Yes, pass AppServiceHub to the constructor
In Kotlin:
class MyCordaService(private val serviceHub: AppServiceHub) : SingletonSerializeAsToken() {
init {
// code ran at service creation / node startup
}
// public api of service
}
or Java:
public class MyCordaService extends SingletonSerializeAsToken {
private AppServiceHub serviceHub;
public MyCordaService(AppServiceHub serviceHub) {
this.serviceHub = serviceHub;
// code ran at service creation / node startup
}
// public api of service
}
Important: to avoid possible any potential deadlocks between running nodes, start inner flows from their own Thread
For example:
public class MyCordaService extends SingletonSerializeAsToken {
private AppServiceHub serviceHub;
public MyCordaService(AppServiceHub serviceHub) {
this.serviceHub = serviceHub;
// code ran at service creation / node startup
}
// public api of service
public void doSomething(){
// do something and start a new flow
Thread flowThread = new Thread(new StartFlow());
flowThread.start();
}
private class StartFlow implements Runnable {
#Override
public void run() {
// start new flow
CordaFuture<SignedTransaction> cordaFuture= appServiceHub.startFlow(new
Flow(params).getReturnValue();
SignedTransaction signedTransaction = cordaFuture.get();
}
}
}
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;
}
}
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;
}