Skip to content

Commit

Permalink
feat(weaver-corda): support array of remote views, consequent user fl…
Browse files Browse the repository at this point in the history
…ow call

Signed-off-by: Sandeep Nishad <sannish@sannish1.sl.cloud9.ibm.com>
  • Loading branch information
sandeepnRES authored and Sandeep Nishad committed Aug 24, 2023
1 parent 2813b75 commit cbc0f1c
Show file tree
Hide file tree
Showing 11 changed files with 487 additions and 193 deletions.
12 changes: 8 additions & 4 deletions weaver/core/drivers/corda-driver/src/main/kotlin/CordaDriver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import java.util.*

import org.hyperledger.cacti.weaver.imodule.corda.flows.HandleExternalRequest
import org.hyperledger.cacti.weaver.sdk.corda.InteroperableHelper
import org.hyperledger.cacti.weaver.sdk.corda.RelayOptions
import org.hyperledger.cacti.weaver.protos.common.query.QueryOuterClass
import org.hyperledger.cacti.weaver.protos.common.state.State
import org.hyperledger.cacti.weaver.protos.corda.ViewDataOuterClass
Expand Down Expand Up @@ -143,13 +144,16 @@ fun createAggregatedCordaView(views: List<State.View>) : Either<Error, State.Vie
fun createGrpcConnection(address: String) = try {
parseRelayAddress(address).map { relayAddresses ->
// TODO: if the first relay address fails, retry with other relay addresses in the list.
val relayOptions = RelayOptions(
useTlsForRelay = System.getenv("RELAY_TLS")?.toBoolean() ?: false,
relayTlsTrustStorePath = System.getenv("RELAY_TLSCA_TRUST_STORE")?.toString() ?: "",
relayTlsTrustStorePassword = System.getenv("RELAY_TLSCA_TRUST_STORE_PASSWORD")?.toString() ?: "",
tlsCACertPathsForRelay = System.getenv("RELAY_TLSCA_CERT_PATHS")?.toString() ?: ""
)
val channel = InteroperableHelper.getChannelToRelay(
relayAddresses[0].host,
relayAddresses[0].port,
System.getenv("RELAY_TLS")?.toBoolean() ?: false,
System.getenv("RELAY_TLSCA_TRUST_STORE")?.toString() ?: "",
System.getenv("RELAY_TLSCA_TRUST_STORE_PASSWORD")?.toString() ?: "",
System.getenv("RELAY_TLSCA_CERT_PATHS")?.toString() ?: "")
relayOptions)
GrpcClient(channel)
}
} catch (e: Exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import net.corda.core.contracts.BelongsToContract
import net.corda.core.contracts.LinearState
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable

/**
* A representation of state and proof retrieved from an external network.
Expand All @@ -27,3 +28,11 @@ data class ExternalState(
override val linearId: UniqueIdentifier = UniqueIdentifier(),
override val participants: List<Party> = listOf()
) : LinearState

@CordaSerializable
data class InvocationSpec(
val disableInvocation: Boolean = true,
val invokeFlowName: String = "",
val invokeFlowArgs: List<Any> = listOf(),
val interopArgsIndex: Int = -1
)

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import java.util.Base64
import net.corda.core.identity.CordaX500Name
import org.hyperledger.cacti.weaver.sdk.corda.AssetTransferSDK
import org.hyperledger.cacti.weaver.sdk.corda.InteroperableHelper
import org.hyperledger.cacti.weaver.sdk.corda.RelayOptions
import com.cordaSimpleApplication.contract.AssetContract
import com.cordaSimpleApplication.contract.BondAssetContract
import java.util.Calendar
Expand Down Expand Up @@ -613,16 +614,19 @@ fun requestStateFromRemoteNetwork(
val networkName = System.getenv("NETWORK_NAME") ?: "Corda_Network"

try {
val relayOptions = RelayOptions(
useTlsForRelay = config["RELAY_TLS"]!!.toBoolean(),
relayTlsTrustStorePath = config["RELAY_TLSCA_TRUST_STORE"]!!,
relayTlsTrustStorePassword = config["RELAY_TLSCA_TRUST_STORE_PASSWORD"]!!,
tlsCACertPathsForRelay = config["RELAY_TLSCA_CERT_PATHS"]!!
)
InteroperableHelper.interopFlow(
proxy,
arrayOf(externalStateAddress),
localRelayAddress,
externalStateAddress,
networkName,
externalStateParticipants,
config["RELAY_TLS"]!!.toBoolean(),
config["RELAY_TLSCA_TRUST_STORE"]!!,
config["RELAY_TLSCA_TRUST_STORE_PASSWORD"]!!,
config["RELAY_TLSCA_CERT_PATHS"]!!
externalStateParticipants = externalStateParticipants,
relayOptions = relayOptions
).fold({
println("Error in Interop Flow: ${it.message}")
exitProcess(1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,20 @@ import arrow.core.Right
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.requireObject
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.option
import io.grpc.ManagedChannelBuilder
import java.lang.Exception
import kotlinx.coroutines.*
import net.corda.core.messaging.startFlow
import java.util.*
import net.corda.core.identity.Party
import net.corda.core.transactions.SignedTransaction

import com.cordaSimpleApplication.flow.CreateState
import com.cordaSimpleApplication.state.SimpleState

import org.hyperledger.cacti.weaver.sdk.corda.InteroperableHelper
import org.hyperledger.cacti.weaver.sdk.corda.RelayOptions

/**
* The CLI command used to trigger a request for state from an external network.
Expand All @@ -39,6 +42,7 @@ import org.hyperledger.cacti.weaver.sdk.corda.InteroperableHelper
*/
class RequestStateCommand : CliktCommand(help = "Requests state from a foreign network. " +
"Requires the port number for the local relay and remote relay name") {
val key: String? by option("-k", "--key", help="key to store the external state")
val localRelayAddress: String by argument()
val externalStateAddress: String by argument()
val config by requireObject<Map<String, String>>()
Expand All @@ -50,32 +54,39 @@ class RequestStateCommand : CliktCommand(help = "Requests state from a foreign n
password = "test",
rpcPort = config["CORDA_PORT"]!!.toInt())
try {
val relayOptions = RelayOptions(
useTlsForRelay = config["RELAY_TLS"]!!.toBoolean(),
relayTlsTrustStorePath = config["RELAY_TLSCA_TRUST_STORE"]!!,
relayTlsTrustStorePassword = config["RELAY_TLSCA_TRUST_STORE_PASSWORD"]!!,
tlsCACertPathsForRelay = config["RELAY_TLSCA_CERT_PATHS"]!!
)
val k: String = if (key != null) {
key!!
} else {
"external_state"
}
val args: List<Any> = listOf<Any>(k, arrayOf<String>())
InteroperableHelper.interopFlow(
rpc.proxy,
localRelayAddress,
externalStateAddress,
rpc.proxy,
arrayOf(externalStateAddress),
localRelayAddress,
networkName,
listOf<Party>(),
config["RELAY_TLS"]!!.toBoolean(),
config["RELAY_TLSCA_TRUST_STORE"]!!,
config["RELAY_TLSCA_TRUST_STORE_PASSWORD"]!!,
config["RELAY_TLSCA_CERT_PATHS"]!!
false,
"com.cordaSimpleApplication.flow.CreateFromExternalState",
args,
1,
externalStateParticipants = listOf<Party>(),
relayOptions = relayOptions
).fold({
println("Error in Interop Flow: ${it.message}")
}, {
val linearId = it.toString()
println("Interop flow successful and external-state was stored with linearId $linearId.\n")
val value = InteroperableHelper.getExternalStatePayloadString(
rpc.proxy,
linearId
)
val key = externalStateAddress.split(":").last()
val createdState = rpc.proxy.startFlow(::CreateState, key, value)
.returnValue.get().tx.outputStates.first() as SimpleState
println("Created simplestate: ${createdState}")
println("Successful response: ${it}")
val stx = it as SignedTransaction
val createdState = stx.tx.outputStates.first() as SimpleState
println("Created simplestate: ${createdState}")
})
} catch (e: Exception) {
println("Error: ${e.toString()}")
println("Error in request state: ${e.toString()}")
} finally {
rpc.close()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ dependencies {
cordaCompile "$corda_core_release_group:corda-core:$corda_core_release_version"

testCompile "$corda_release_group:corda-node-driver:$corda_release_version"

implementation(group: 'org.hyperledger.cacti.weaver.imodule.corda', name: 'interop-contracts', version: "$cacti_version")
}

publishing {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import net.corda.core.contracts.requireSingleCommand
import net.corda.core.contracts.requireThat
import net.corda.core.transactions.LedgerTransaction

import org.hyperledger.cacti.weaver.imodule.corda.states.ExternalState

/**
* SimpleContract defines the rules for managing [SimpleState].
*
Expand Down Expand Up @@ -52,6 +54,12 @@ class SimpleContract : Contract {
val out = tx.outputsOfType<SimpleState>().single()
"The participant must be the signer." using (command.signers.containsAll(out.participants.map { it.owningKey }))
}
is Commands.CreateFromExternal -> requireThat {
"One external state as input should be consumed when issuing a SimpleState from ExternalState." using (tx.inputsOfType<ExternalState>().size == 1)
"Only one output state should be created." using (tx.outputs.size == 1)
val out = tx.outputsOfType<SimpleState>().single()
"The participant must be the signer." using (command.signers.containsAll(out.participants.map { it.owningKey }))
}
is Commands.Update -> requireThat {
"There should be one input state" using (tx.inputs.size == 1)
"The input state should be of type SimpleState" using (tx.inputs[0].state.data is SimpleState)
Expand Down Expand Up @@ -79,6 +87,7 @@ class SimpleContract : Contract {
*/
interface Commands : CommandData {
class Create : Commands
class CreateFromExternal: Commands
class Update : Commands
class Delete : Commands
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.cordaSimpleApplication.state.SimpleState
import javassist.NotFoundException
import net.corda.core.contracts.Command
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.contracts.requireThat
import net.corda.core.flows.*
import net.corda.core.node.services.Vault
import net.corda.core.node.services.queryBy
Expand All @@ -20,8 +21,15 @@ 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 net.corda.core.serialization.CordaSerializable
import java.util.Arrays.asList

import org.hyperledger.cacti.weaver.imodule.corda.states.ExternalState
import org.hyperledger.cacti.weaver.imodule.corda.contracts.ExternalStateContract
import org.hyperledger.cacti.weaver.imodule.corda.flows.GetExternalStateAndRefByLinearId
import org.hyperledger.cacti.weaver.imodule.corda.flows.getViewFromExternalState
import org.hyperledger.cacti.weaver.imodule.corda.flows.getPaylaodFromView

/**
* The CreateState flow is used to create a new [SimpleState].
*
Expand Down Expand Up @@ -89,6 +97,120 @@ class CreateState(val key: String, val value: String) : FlowLogic<SignedTransact
}
}

/**
* The CreateState flow is used to create a new [SimpleState].
*
* @property key the key for the [SimpleState].
* @property value the value for the [SimpleState].
*/
@InitiatingFlow
@StartableByRPC
class CreateFromExternalState(
val key: String,
val externalStateLinearIds: Array<UniqueIdentifier>
) : FlowLogic<SignedTransaction>() {
/**
* The progress tracker checkpoints each stage of the flow and outputs the specified messages when each
* checkpoint is reached in the code. See the 'progressTracker.currentStep' expressions within the call() function.
*/
companion object {
object GENERATING_TRANSACTION : Step("Generating transaction based on new simple state.")
object VERIFYING_TRANSACTION : Step("Verifying contract constraints.")
object SIGNING_TRANSACTION : Step("Signing transaction with our private key.")
object FINALISING_TRANSACTION : Step("Obtaining notary signature and recording transaction.") {
override fun childProgressTracker() = FinalityFlow.tracker()
}
fun tracker() = ProgressTracker(
GENERATING_TRANSACTION,
VERIFYING_TRANSACTION,
SIGNING_TRANSACTION,
FINALISING_TRANSACTION
)
}

override val progressTracker = tracker()

/**
* The call() method captures the logic to build and sign a transaction that creates a [SimpleState].
*
* @return returns the signed transaction.
*/
@Suspendable
override fun call(): SignedTransaction {
println("Creating state from External State...")
// Obtain a reference to the notary we want to use.
val notary = serviceHub.networkMapCache.notaryIdentities[0]
val externalStateAndRef = subFlow(GetExternalStateAndRefByLinearId(externalStateLinearIds[0].toString()))
val value = getPaylaodFromView(getViewFromExternalState(externalStateAndRef.state.data)).toString(Charsets.UTF_8)

progressTracker.currentStep = GENERATING_TRANSACTION
// Generate an unsigned transaction.
val simpleState = SimpleState(key, value, serviceHub.myInfo.legalIdentities.first())
println("Storing simple state in the ledger: $simpleState\n")
val txCommand = Command(SimpleContract.Commands.CreateFromExternal(), simpleState.participants.map { it.owningKey })
val externalStateConsumeCommand = Command(ExternalStateContract.Commands.Consume(),
externalStateAndRef.state.data.participants.map { it.owningKey }
)
val txBuilder = TransactionBuilder(notary)
.addInputState(externalStateAndRef)
.addOutputState(simpleState, SimpleContract.ID)
.addCommand(txCommand)
.addCommand(externalStateConsumeCommand)

// Stage 2.
progressTracker.currentStep = VERIFYING_TRANSACTION
// Verify that the transaction is valid.
txBuilder.verify(serviceHub)

// Stage 3.
progressTracker.currentStep = SIGNING_TRANSACTION
// Sign the transaction.
val signedTx = serviceHub.signInitialTransaction(txBuilder)

// Stage 5.
progressTracker.currentStep = FINALISING_TRANSACTION
var sessions = listOf<FlowSession>()
for (party in externalStateAndRef.state.data.participants) {
if (!ourIdentity.equals(party)) {
val session = initiateFlow(party)
sessions += session
}
}

val sTx = subFlow(CollectSignaturesFlow(signedTx, sessions))

// Notarise and record the transaction in the party's vault.
return subFlow(FinalityFlow(sTx, sessions, FINALISING_TRANSACTION.childProgressTracker()))
}
}
@InitiatedBy(CreateFromExternalState::class)
class CreateFromExternalStateAcceptor(val session: FlowSession) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
val signTransactionFlow = object : SignTransactionFlow(session) {
override fun checkTransaction(stx: SignedTransaction) = requireThat {
val tx = stx.tx.toLedgerTransaction(serviceHub)

val externalState = tx.inputsOfType<ExternalState>().single()
val out = tx.outputsOfType<SimpleState>().single()

val externalValue = getPaylaodFromView(getViewFromExternalState(externalState)).toString(Charsets.UTF_8)

"Value in SimpleState should match with external state" using (out.value == externalValue)
}
}
try {
val txId = subFlow(signTransactionFlow).id
println("${ourIdentity} signed transaction.")
return subFlow(ReceiveFinalityFlow(session, expectedTxId = txId))
} catch (e: Exception) {
val errorMsg = "Error signing create from external state transaction: ${e.message}\n"
println(errorMsg)
throw Error(errorMsg)
}
}
}

/**
* The UpdateState flow is used to update an existing [SimpleState].
*
Expand Down
3 changes: 3 additions & 0 deletions weaver/sdks/corda/github.properties.rm
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
username=sandeep.nishad1@ibm.com
password=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
url=https://maven.pkg.github.com/hyperledger/cacti
Loading

0 comments on commit cbc0f1c

Please sign in to comment.