Is there a way in a Corda Contract where I can check if the issuer is a particular party only. Say I have a network of 3 nodes-NodeA,NodeB,Notary. Can I check in a contract whether the issue is done by PartyA only
i.e the issuing key of the issuer is always equal to PartyA's issuing key?
Yes, you can perform this check. For example:
class IssuedContract : Contract {
companion object {
// Used to identify our contract when building a transaction.
const val ID = "com.issuing.IssuedContract"
}
private val expectedIssuer = CordaX500Name("Expected Issuer", "London", "GB")
override fun verify(tx: LedgerTransaction) {
val issuedStates = tx.outputsOfType<IssuedState>()
issuedStates.forEach { issuedState ->
if (issuedState.issuer.name != expectedIssuer)
throw IllegalArgumentException("The state is not issued by the correct party.")
}
}
}
However, this approach would be brittle. The contract would only work specifically for the named issuer.
In general, it would be better for each node to check whether they are happy with the chosen issuer before signing transactions in flows.
Related
Our Corda network has 3 nodes in addition to the notary.
The image shows what each node should do.
Only in this scenario we are in trouble "Need to move tokens from account holder to Party B"
Flow code:
class TransferETokenDiffNodeFlow(val actualHolder: AbstractParty,
val newHolder: AbstractParty,
val numEtokens: Double,
val observables: MutableList = mutableListOf()) :
FlowLogic() {
private fun notary() = serviceHub.networkMapCache.notaryIdentities.first()
#Suspendable
override fun call(): SignedTransaction {
progressTracker.currentStep = INITIALIZING
val txBuilder = TransactionBuilder(notary())
val actualHolderStateRef = accountService.accountInfo(actualHolder.owningKey)
?: throw AccountNotFoundException("Account not found exception.")
val actualHolderInfo = actualHolderStateRef.state.data
val actualHolderSession = initiateFlow(actualHolderInfo.host)
actualHolderSession.send(numEtokens)
actualHolderSession.send(actualHolder)
actualHolderSession.send(newHolder)
val inputs = subFlow(ReceiveStateAndRefFlow(actualHolderSession))
val tokens: List = actualHolderSession.receive>().unwrap { it -> it}
progressTracker.currentStep = BUILDING
addMoveTokens(txBuilder, inputs, tokens)
progressTracker.currentStep = SIGNING
val initialSignedTrnx = serviceHub.signInitialTransaction(txBuilder)
progressTracker.currentStep = GATHERING_SIGS
val fulySignedTx= subFlow(CollectSignaturesFlow(initialSignedTrnx, listOf(actualHolderSession)))
progressTracker.currentStep = FINALISING_CREATE
val stx = subFlow(FinalityFlow(fulySignedTx, listOf(actualHolderSession)))
progressTracker.currentStep = FINALISING
val statesTx = stx.tx.outRefsOfType()
statesTx.forEach { state ->
observables.forEach { observable ->
subFlow(ShareStateAndSyncAccounts(state, observable))
}
}
return stx
}
}
//ResponderFlow code:
class TransferETokenDiffNodeFlowResponder(val counterpartySession: FlowSession) : FlowLogic() {
#Suspendable
override fun call(): SignedTransaction {
val numEtokens = counterpartySession.receive().unwrap { it }
val actualHolder = counterpartySession.receive().unwrap { it }
val newHolder = counterpartySession.receive().unwrap { it }
val partyAndAmount = PartyAndAmount(newHolder, numEtokens of EnergyTokenType.getInstance("ENERGY"))
val actualHolderStateRef = accountService.accountInfo(actualHolder.owningKey)
?: throw AccountNotFoundException("Account not found exception.")
val actualHolderInfo = actualHolderStateRef.state.data
val criteria = QueryCriteria.VaultQueryCriteria(externalIds = listOf(actualHolderInfo.identifier.id),
status = Vault.StateStatus.UNCONSUMED)
val selector = DatabaseTokenSelection(serviceHub)
val (inputs, outputs) = selector.generateMove(listOf(partyAndAmount).toPairs(),
actualHolder, TokenQueryBy(queryCriteria = criteria), runId.uuid)
subFlow(SendStateAndRefFlow(counterpartySession, inputs))
counterpartySession.send(outputs)
subFlow(object : SignTransactionFlow(counterpartySession) {
#Throws(FlowException::class)
override fun checkTransaction(stx: SignedTransaction) {
}
})
return subFlow(ReceiveFinalityFlow(counterpartySession))
}
}
We need to execute the flow at Party C and the actualHolder is the account holder and the newHolder is Party B.
With this code the returns an error:
net.corda.core.CordaRuntimeException: java.lang.IllegalArgumentException: Flow sessions were not provided for the following transaction participants: [O = Party B, L = Curitiba, C = BR]
But if I change the code and add the Party B session, it returns the error:
java.lang.IllegalArgumentException: The Initiator of CollectSignaturesFlow must pass in exactly the sessions required to sign the transaction.
The question is, why doesn't addMoveTokens put newHolder as a required signer?
And how can I solve this problem?
There are many points to discuss in your code; let's start with the error:
When you move tokens, the only required signer is the current holder; so in your case you should pass only one FlowSession which is the session with PartyA (since it's the holder of the tokens); so you should only have:
CollectSignaturesFlow(initialSignedTrnx, listOf(actualHolderSession))
As for finalizing the transaction, sessions for all participants must be passed. The union of all participants in your case is the current holders of the tokens (i.e. PartyA) and the new holders (i.e. PartyB); so you should have:
FinalityFlow(fulySignedTx, listOf(actualHolderSession, partyB))
That's why when you passed only actualHolderSession for both sub-flows; you received that a session is missing (because finality flow wants a session for PartyB as well); and when you added PartyB session for both, the collect signatures flow complained that you're passing an extra session (PartyB isn't required to sign the move command, only the current holder is).
Since your responder flow is handling both tasks (signing and finalizing); you must send from the initiator to the responder some data (String or whatever you choose) to signify the role of the responder, so for PartyA you would send SignerAndFinalizer; while for PartyB you send Finalizer; and in the beginning of your responder, you receive the "role" and act accordingly (i.e. if it's Finalizer you don't call SignTransactionFlow, only ReceiveFinalityFlow).
Now to other topics:
I don't recommend that a party moves tokens that are held by another party. Imagine you own money in your account, and I move that money from your account. It should be the node that holds the tokens that initiates the move. Actually that's how the ready flows of the SDK operate.
If you look at the AbstractMoveTokensFlow you will see that they only sign the transaction locally by relying on ObserverAwareFinalityFlow (see here); to confirm that, you can see that inside ObserverAwareFinalityFlow there is no CollectSignaturesFlow (i.e. it doesn't request signatures from other nodes), only signs locally (see here).
What this all means is that if you used the ready flow of the SDK to move tokens that are held by a node different than the node that's calling the move flow; you'll get an error, because the signature of the holder is required, but the ready flow doesn't collect signatures from other nodes, only from the node that called the move flow, which isn't the holder of the tokens.
I recommend following the approach of the SDK, in that only the holder of the tokens can move their tokens.
Another things mentioned by the Accounts library documentation is not to mix using accounts and non-accounts (i.e. only move from account to account or from party to party); it is advisable instead to create a "default" account for your node (e.g. create an account with a name that matches the X500 name of your node), read here.
I am trying to do the following scenario ,
There is a basic IOU transaction which is done from Party A to Party B in which Party A who is the initiator will only sign the transaction , Party B just accepts it(without signing) . To achieve the following I did the following things which worked fine in Corda 3 using the IOU flow of the corda samples project.
1.In the transaction command , I just passed only the Initiator owning key instead of sending both the participants owning keys
I removed the "All Signers" check from the contract
I removed the "Gather counter party signature" Step.
When I moved the same to corda 4 , I noticed the following
Since I am not setting the acceptors owning key as part of the
txcommand , The transaction is saved in the initiator but not part
of the acceptor because of session issue which I understood from the
following.reference
Looking at the reference I rectified it accordingly, now Corda is expecting the signing of the acceptor also which is against my use case, if I do not add , it is throwing the following error
'net.corda.core.transactions.SignedTransaction$SignaturesMissingException:Missing
signatures on transaction '
Please let me know if any workaround is present on the same.
P.S : I am using the cordapp-example Java code
Here is the sample flow which achieves the same functionality
#InitiatingFlow
#StartableByRPC
class IssuerRegistration(
val issuerData: IssuerData
) : BaseFlow<String>() {
override val progressTracker = ProgressTracker()
#Suspendable
override fun call(): String {
// We retrieve the notary identity from the network map.
val notary = firstNotary
// We create the transaction components.
val meta_Info = Meta_Info("pdf", Utils.getCurrentDateTime(), Utils.getCurrentDateTime())
val ids = Ids(issuerData.ids.dunsId)
//create list of all parties involved in this flow
val partList = ArrayList<Party>()
partList.add(ourIdentity)
partList.add(createParty("partyb", serviceHub))
//random unique id of the state
val userUniqueId = UUID.randomUUID()
//create output state actually this will
//save to related parties vault
val outputState = IssuerState(userUniqueId.toString(),
issuerData.company_id,
issuerData.company_name,
issuerData.company_symbol,
ids,
meta_Info,
partList)
//registration command will be verified by issuercontract
val command = Command(RegistrationCommand.IssuerRegistration(),
ourIdentity.owningKey)
// We create a transaction builder and add the components.
val txBuilder = TransactionBuilder(notary = notary)
.addOutputState(outputState, RegistrationContract.ID)
.addCommand(command)
// Verifying the transaction.
txBuilder.verify(serviceHub)
// Signing the transaction. from sender side
val signedTx = serviceHub.signInitialTransaction(txBuilder)
val listOfSession = ArrayList<FlowSession>()
//creating all other party session to get
//signature from to validate the transaction
val otherPartySession = initiateFlow(otherPartySession)
listOfSession.add(otherPartySession)
//end the flow and write the transation data to related parties vault
subFlow(FinalityFlow(signedTx, listOfSession))
return userUniqueId.toString()
}
}
// Replace Responder's definition with:
#InitiatedBy(Flows.IssuerRegistration::class)
class IssueResponder(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
#Suspendable
override fun call() {
subFlow(ReceiveFinalityFlow(otherPartySession))
}
}
Issue was on the acceptor side , since I was using the SignTransactionFlow which expects the participants to all be signers. Once I commented the code and just ran the ReceiveFinalityFlow it worked fine. Reference : SignTransactionFlow
I am making a CorDapp that involves two parties - a Client and an Underwriter. I have two main flows, one being IssuePolicy and another being PayoutPolicy. When I run each flow once, there are no issues. When I run IssuePolicy again, I receive the error: The Initiator of CollectSignaturesFlow must pass in exactly the sessions required to sign the transaction. I've already searched Stack Overflow and found this post: Flow Exception in CollectSignaturesFlow but I believe I have signed with one party and initiated flow with the other, so I am not sure whether this solution applies to me. Any help would be appreciated!
IssuePolicy Flow:
// Step 3. Building.
progressTracker.currentStep = BUILDING
val notary = serviceHub.networkMapCache.notaryIdentities[0]
val utx = TransactionBuilder(notary = notary)
.addOutputState(policy, INSUREFLIGHT_CONTRACT_ID)
.addCommand(InsureFlightContract.Commands.Issue(), policy.participants.map { it.owningKey })
.setTimeWindow(serviceHub.clock.instant(), 30.seconds)
// Stage 4. Get some cash from the vault and add a spend to our transaction builder.
// We pay cash to the underwriter's policy key.
val (_, cashSigningKeys) = Cash.generateSpend(serviceHub, utx, premium, underwriter)
check(cashSigningKeys == cashSigningKeys){
throw FlowException("")
}
// Step 5. Sign the transaction.
progressTracker.currentStep = SIGNING
val ptx = serviceHub.signInitialTransaction(utx, policy.client.owningKey)
// Step 6. Get the counter-party signature.
progressTracker.currentStep = COLLECTING
val otherpartySession = initiateFlow(underwriter)
val stx = subFlow(CollectSignaturesFlow(
ptx,
listOf(otherpartySession),
COLLECTING.childProgressTracker())
)
// Step 7. Finalize the transaction.
progressTracker.currentStep = FINALIZING
return subFlow(FinalityFlow(stx, FINALIZING.childProgressTracker()))
}
}
// Allows counterparty to respond.
#InitiatedBy(Initiator::class)
class IssuePolicyResponder(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
#Suspendable
override fun call() {
val signTransactionFlow = object : SignTransactionFlow(otherPartySession, SignTransactionFlow.tracker()) {
override fun checkTransaction(stx: SignedTransaction) = requireThat {
}
}
subFlow(signTransactionFlow)
}
}
}
Policy State Definition:
//Policy Class, includes premium, claim, client, underwriter, flight, and policyID
data class Policy(val premium: Amount<Currency>,
val claim: Amount<Currency>,
val client: Party,
val underwriter: Party,
val flight: String,
override val linearId: UniqueIdentifier = UniqueIdentifier()) : LinearState {
//Get clients and underwriters
override val participants: List<Party> get() = listOf(client, underwriter)
//Functions to update policy parameters
fun payPremium(amountToPay: Amount<Currency>) = copy(premium = premium + amountToPay)
fun payClaim(amountToPay: Amount<Currency>) = copy(claim = claim + amountToPay)
fun withNewClient(newClient: Party) = copy(client = newClient)
fun withNewUnderwriter(newUnderwriter: Party) = copy(underwriter = newUnderwriter)
fun withNewFlight(newFlight: String) = copy(flight = newFlight)
//Provides response
override fun toString(): String {
val clientString = (client as? Party)?.name?.organisation ?: client.owningKey.toBase58String()
val underwriterString = (underwriter as? Party)?.name?.organisation ?: underwriter.owningKey.toBase58String()
return "Policy($linearId): $clientString has paid a premium of $$premium for flight $flight, underwritten by $underwriterString with" +
"a claim amount of $$claim."
}
}
Commands in the Contract:
interface Commands : CommandData {
class Issue : TypeOnlyCommandData(), Commands
class Settle : TypeOnlyCommandData(), Commands
}
override fun verify(tx: LedgerTransaction): Unit {
val command = tx.commands.requireSingleCommand<Commands>()
val setOfSigners = command.signers.toSet()
when (command.value) {
is Commands.Issue -> verifyIssue(tx, setOfSigners)
is Commands.Settle -> verifySettle(tx, setOfSigners)
else -> throw IllegalArgumentException("Unrecognized command. You can only issue or settle.")
}
}
Not all the cash in your node's vault will be owned by your node's main public key. This is because when Cash.generateSpend generates change outputs, this change is assigned to a new confidential identity instead of your node's main identity for privacy reasons.
This is the purpose of the cashSigningKeys returned by Cash.generateSpend. It's a list of the public keys that own all the cash added to the transaction builder by Cash.generateSpend.
I'm guessing that the first time you run IssuePolicy/PayoutPolicy, it generates some cash owned by a new confidential identity. You never sign the transaction with the key of the new confidential identity. Effectively, it is this confidential identity's session that is missing when you call CollectSignaturesFlow.
Of course, it doesn't make sense to create a session with this confidential identity, since it actually corresponds to the node running the flow. Instead, you need to take the cashSigningKeys returned by Cash.generateSpend, and sign the transaction with these as well before calling CollectSignaturesFlow, by calling:
val ptx = serviceHub.signInitialTransaction(
utx,
cashSigningKeys + policy.client.owningKey)
I have implemented the user interaction flow described here: Corda: User interaction for verifying the transaction request received from the initiator node.
As the proposer, how can I check whether the transaction is accepted or rejected?
You can use an RPC client such as the following:
fun main(args: Array<String>) {
val nodeAddress = parse(args[0])
val client = CordaRPCClient(nodeAddress)
val proxy = client.start("user1", "test").proxy
// Observe FooState updates from the vault.
val (_, updates) = proxy.vaultTrack(FooState::class.java)
// Check each update.
updates.toBlocking().subscribe { update ->
// If the update produced a state with the `accepted` flag, the proposal was accepted.
if (update.produced.size == 1 && update.produced.single().state.data.accepted) {
logger.info("Proposal was accepted.")
// If the update didn't produce a state, the proposal was rejected.
} else if (update.produced.isEmpty()) {
logger.info("Proposal was rejected.")
}
}
}
I want to be able to have individual users send messages to each other using SignalR, therefore I need to send to a Specific Client ID. How can I define the client ID for a specific user at the start of the session - say a GUID Primary Key for the user?
Replace the IConnectionIdFactory with your own https://github.com/SignalR/SignalR/wiki/Extensibility.
Sample usage:
http://www.kevgriffin.com/maintaining-signalr-connectionids-across-page-instances/
EDIT: This is no longer supported in the latest versions of SignalR. But you can define a user id for a specific connection using the new IUserIdProvider
In SignalR version 1, using the Hubs approach, I override the Hub OnConnected() method and save an association of a .NET membership userId with the current connection id (Context.ConnectionId) in a SQL database.
Then I override the Hub OnDisconnected() method and delete the association between the .NET membership userId and the current connection id. This means, on a page reload, the userId/connectionId association will be up-to-date.
Something along the lines of:
public class MyHub : Hub
{
private MembershipUser _user
{
get { return Membership.GetUser(); }
}
private Guid _userId
{
get { return (Guid) _user.ProviderUserKey; }
}
private Guid _connectionId
{
get { return Guid.Parse(Context.ConnectionId); }
}
public override Task OnConnected()
{
var userConnectionRepository = new UserConnectionRepository();
userConnectionRepository.Create(_userId, _connectionId);
userConnectionRepository.Submit();
return base.OnConnected();
}
public override Task OnDisconnected()
{
var userConnectionRepository = new UserConnectionRepository();
userConnectionRepository.Delete(_userId, _connectionId);
userConnectionRepository.Submit();
return base.OnDisconnected();
}
}
Then when I need to trigger a SignalR event for a specific user, I can work out the connectionId from the database association(s) with the current userId - there may be more than one association if multiple browser instances are involved.
The SignalR Client Side documentation outlines the following:
connection.id
- Gets or sets the client id for the current connection
This certainly indicates that one should be able to set the clientID client side, without all the above plumbing. Is this not working? If working, how would this line of code look like?