mirror of
https://github.com/corda/corda.git
synced 2025-06-13 04:38:19 +00:00
Expose a JDBC connection (session) via the ServiceHub for generic JDB… (#1000)
* Expose a JDBC connection (session) via the ServiceHub for generic JDBC usage. * Updated documentation. * Fix failing JUnit following rebase from master. * JDBC session Tutorial WIP * Fix broken JUnits following update to consumeCash() using observable event update as return. * Fixed broken JUnits. * Refactoring createSession() into new CordaPersistence class following rebase from master. * Updated fully working example. * Minor updates following feedback from MH. * Fixed compiler error following rebase. * Fixes following rebase from master. * Further updates and clarifications to documentation.
This commit is contained in:
@ -0,0 +1,150 @@
|
||||
package net.corda.docs
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatedBy
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.node.services.CordaService
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.unwrap
|
||||
import net.corda.flows.*
|
||||
import java.util.*
|
||||
|
||||
// DOCSTART CustomVaultQuery
|
||||
object CustomVaultQuery {
|
||||
|
||||
@CordaService
|
||||
class Service(val services: PluginServiceHub) : SingletonSerializeAsToken() {
|
||||
private companion object {
|
||||
val log = loggerFor<Service>()
|
||||
}
|
||||
fun rebalanceCurrencyReserves(): List<Amount<Currency>> {
|
||||
val nativeQuery = """
|
||||
select
|
||||
cashschema.ccy_code,
|
||||
sum(cashschema.pennies)
|
||||
from
|
||||
vault_states vaultschema
|
||||
join
|
||||
contract_cash_states cashschema
|
||||
where
|
||||
vaultschema.output_index=cashschema.output_index
|
||||
and vaultschema.transaction_id=cashschema.transaction_id
|
||||
and vaultschema.state_status=0
|
||||
group by
|
||||
cashschema.ccy_code
|
||||
order by
|
||||
sum(cashschema.pennies) desc
|
||||
"""
|
||||
log.info("SQL to execute: $nativeQuery")
|
||||
val session = services.jdbcSession()
|
||||
val prepStatement = session.prepareStatement(nativeQuery)
|
||||
val rs = prepStatement.executeQuery()
|
||||
var topUpLimits: MutableList<Amount<Currency>> = mutableListOf()
|
||||
while (rs.next()) {
|
||||
val currencyStr = rs.getString(1)
|
||||
val amount = rs.getLong(2)
|
||||
log.info("$currencyStr : $amount")
|
||||
topUpLimits.add(Amount(amount, Currency.getInstance(currencyStr)))
|
||||
}
|
||||
return topUpLimits
|
||||
}
|
||||
}
|
||||
}
|
||||
// DOCEND CustomVaultQuery
|
||||
|
||||
/**
|
||||
* This is a slightly modified version of the IssuerFlow, which uses a 3rd party custom query to
|
||||
* retrieve a list of currencies and top up amounts to be used in the issuance.
|
||||
*/
|
||||
|
||||
object TopupIssuerFlow {
|
||||
@CordaSerializable
|
||||
data class TopupRequest(val issueToParty: Party,
|
||||
val issuerPartyRef: OpaqueBytes,
|
||||
val notaryParty: Party)
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class TopupIssuanceRequester(val issueToParty: Party,
|
||||
val issueToPartyRef: OpaqueBytes,
|
||||
val issuerBankParty: Party,
|
||||
val notaryParty: Party) : FlowLogic<List<AbstractCashFlow.Result>>() {
|
||||
@Suspendable
|
||||
@Throws(CashException::class)
|
||||
override fun call(): List<AbstractCashFlow.Result> {
|
||||
val topupRequest = TopupRequest(issueToParty, issueToPartyRef, notaryParty)
|
||||
return sendAndReceive<List<AbstractCashFlow.Result>>(issuerBankParty, topupRequest).unwrap { it }
|
||||
}
|
||||
}
|
||||
|
||||
@InitiatedBy(TopupIssuanceRequester::class)
|
||||
class TopupIssuer(val otherParty: Party) : FlowLogic<List<SignedTransaction>>() {
|
||||
companion object {
|
||||
object AWAITING_REQUEST : ProgressTracker.Step("Awaiting issuance request")
|
||||
object ISSUING : ProgressTracker.Step("Issuing asset")
|
||||
object TRANSFERRING : ProgressTracker.Step("Transferring asset to issuance requester")
|
||||
object SENDING_TOP_UP_ISSUE_REQUEST : ProgressTracker.Step("Requesting asset issue top up")
|
||||
|
||||
fun tracker() = ProgressTracker(AWAITING_REQUEST, ISSUING, TRANSFERRING, SENDING_TOP_UP_ISSUE_REQUEST)
|
||||
}
|
||||
|
||||
override val progressTracker: ProgressTracker = tracker()
|
||||
|
||||
// DOCSTART TopupIssuer
|
||||
@Suspendable
|
||||
@Throws(CashException::class)
|
||||
override fun call(): List<SignedTransaction> {
|
||||
progressTracker.currentStep = AWAITING_REQUEST
|
||||
val topupRequest = receive<TopupRequest>(otherParty).unwrap {
|
||||
it
|
||||
}
|
||||
|
||||
val customVaultQueryService = serviceHub.cordaService(CustomVaultQuery.Service::class.java)
|
||||
val reserveLimits = customVaultQueryService.rebalanceCurrencyReserves()
|
||||
|
||||
val txns: List<SignedTransaction> = reserveLimits.map { amount ->
|
||||
// request asset issue
|
||||
logger.info("Requesting currency issue $amount")
|
||||
val txn = issueCashTo(amount, topupRequest.issueToParty, topupRequest.issuerPartyRef)
|
||||
progressTracker.currentStep = SENDING_TOP_UP_ISSUE_REQUEST
|
||||
return@map txn.stx
|
||||
}
|
||||
|
||||
send(otherParty, txns)
|
||||
return txns
|
||||
}
|
||||
// DOCEND TopupIssuer
|
||||
|
||||
@Suspendable
|
||||
private fun issueCashTo(amount: Amount<Currency>,
|
||||
issueTo: Party,
|
||||
issuerPartyRef: OpaqueBytes): AbstractCashFlow.Result {
|
||||
// TODO: pass notary in as request parameter
|
||||
val notaryParty = serviceHub.networkMapCache.notaryNodes[0].notaryIdentity
|
||||
// invoke Cash subflow to issue Asset
|
||||
progressTracker.currentStep = ISSUING
|
||||
val issueRecipient = serviceHub.myInfo.legalIdentity
|
||||
val issueCashFlow = CashIssueFlow(amount, issuerPartyRef, issueRecipient, notaryParty, anonymous = false)
|
||||
val issueTx = subFlow(issueCashFlow)
|
||||
// NOTE: issueCashFlow performs a Broadcast (which stores a local copy of the txn to the ledger)
|
||||
// short-circuit when issuing to self
|
||||
if (issueTo == serviceHub.myInfo.legalIdentity)
|
||||
return issueTx
|
||||
// now invoke Cash subflow to Move issued assetType to issue requester
|
||||
progressTracker.currentStep = TRANSFERRING
|
||||
val moveCashFlow = CashPaymentFlow(amount, issueTo, anonymous = false)
|
||||
val moveTx = subFlow(moveCashFlow)
|
||||
// NOTE: CashFlow PayCash calls FinalityFlow which performs a Broadcast (which stores a local copy of the txn to the ledger)
|
||||
return moveTx
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package net.corda.docs
|
||||
|
||||
import net.corda.contracts.getCashBalances
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.getOrThrow
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.flows.CashIssueFlow
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
import net.corda.testing.DUMMY_NOTARY_KEY
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import org.junit.After
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.util.*
|
||||
|
||||
class CustomVaultQueryTest {
|
||||
|
||||
lateinit var mockNet: MockNetwork
|
||||
lateinit var notaryNode: MockNetwork.MockNode
|
||||
lateinit var nodeA: MockNetwork.MockNode
|
||||
lateinit var nodeB: MockNetwork.MockNode
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockNet = MockNetwork(threadPerNode = true)
|
||||
val notaryService = ServiceInfo(ValidatingNotaryService.type)
|
||||
notaryNode = mockNet.createNode(
|
||||
legalName = DUMMY_NOTARY.name,
|
||||
overrideServices = mapOf(notaryService to DUMMY_NOTARY_KEY),
|
||||
advertisedServices = *arrayOf(ServiceInfo(NetworkMapService.type), notaryService))
|
||||
nodeA = mockNet.createPartyNode(notaryNode.network.myAddress)
|
||||
nodeB = mockNet.createPartyNode(notaryNode.network.myAddress)
|
||||
|
||||
nodeA.registerInitiatedFlow(TopupIssuerFlow.TopupIssuer::class.java)
|
||||
nodeA.installCordaService(CustomVaultQuery.Service::class.java)
|
||||
}
|
||||
|
||||
@After
|
||||
fun cleanUp() {
|
||||
mockNet.stopNodes()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test custom vault query`() {
|
||||
// issue some cash in several currencies
|
||||
issueCashForCurrency(POUNDS(1000))
|
||||
issueCashForCurrency(DOLLARS(900))
|
||||
issueCashForCurrency(SWISS_FRANCS(800))
|
||||
val (cashBalancesOriginal, _) = getBalances()
|
||||
|
||||
// top up all currencies (by double original)
|
||||
topUpCurrencies()
|
||||
val (cashBalancesAfterTopup, _) = getBalances()
|
||||
|
||||
Assert.assertEquals(cashBalancesOriginal[GBP]?.times(2), cashBalancesAfterTopup[GBP])
|
||||
Assert.assertEquals(cashBalancesOriginal[USD]?.times(2) , cashBalancesAfterTopup[USD])
|
||||
Assert.assertEquals(cashBalancesOriginal[CHF]?.times( 2), cashBalancesAfterTopup[CHF])
|
||||
}
|
||||
|
||||
private fun issueCashForCurrency(amountToIssue: Amount<Currency>) {
|
||||
// Use NodeA as issuer and create some dollars
|
||||
val flowHandle1 = nodeA.services.startFlow(CashIssueFlow(amountToIssue,
|
||||
OpaqueBytes.of(0x01),
|
||||
nodeA.info.legalIdentity,
|
||||
notaryNode.info.notaryIdentity,
|
||||
false))
|
||||
// Wait for the flow to stop and print
|
||||
flowHandle1.resultFuture.getOrThrow()
|
||||
}
|
||||
|
||||
private fun topUpCurrencies() {
|
||||
val flowHandle1 = nodeA.services.startFlow(TopupIssuerFlow.TopupIssuanceRequester(
|
||||
nodeA.info.legalIdentity,
|
||||
OpaqueBytes.of(0x01),
|
||||
nodeA.info.legalIdentity,
|
||||
notaryNode.info.notaryIdentity))
|
||||
flowHandle1.resultFuture.getOrThrow()
|
||||
}
|
||||
|
||||
private fun getBalances(): Pair<Map<Currency, Amount<Currency>>, Map<Currency, Amount<Currency>>> {
|
||||
// Print out the balances
|
||||
val balancesNodesA =
|
||||
nodeA.database.transaction {
|
||||
nodeA.services.getCashBalances()
|
||||
}
|
||||
println("BalanceA\n" + balancesNodesA)
|
||||
|
||||
val balancesNodesB =
|
||||
nodeB.database.transaction {
|
||||
nodeB.services.getCashBalances()
|
||||
}
|
||||
println("BalanceB\n" + balancesNodesB)
|
||||
|
||||
return Pair(balancesNodesA, balancesNodesB)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user