Merged in clint-vegacorechanges (pull request #380)

Corda Changes required for Vega
This commit is contained in:
Clinton Alexander 2016-09-28 16:28:15 +01:00
commit 7afb034b8c
21 changed files with 64 additions and 73 deletions

26
.gitignore vendored
View File

@ -5,20 +5,28 @@
tags tags
.DS_Store .DS_Store
*.log *.log
*.log.gz
*.orig
# Created by .ignore support plugin (hsz.mobi) # Created by .ignore support plugin (hsz.mobi)
.gradle .gradle
/build/
/node/build/
/contracts/build
/contracts/isolated/build
/core/build
/experimental/build
/docs/build/doctrees /docs/build/doctrees
/test-utils/build
/client/build # General build files
/explorer/build **/build/classes/**
**/build/install/**
**/build/kotlin-classes/**
**/build/libs/**
**/build/resources/**
**/build/tmp/**
**/build/reports/**
**/build/jacoco/***
**/build/test-results/**
**/build/[0-9]*/**
**/build/nodes/**
**/build/scripts/**
**/build/publications/**
# gradle's buildSrc build/ # gradle's buildSrc build/
/buildSrc/build/ /buildSrc/build/

View File

@ -201,7 +201,7 @@ class InterestRateSwap() : Contract {
val threshold: Amount<Currency>, val threshold: Amount<Currency>,
val minimumTransferAmount: Amount<Currency>, val minimumTransferAmount: Amount<Currency>,
val rounding: Amount<Currency>, val rounding: Amount<Currency>,
val valuationDate: String, val valuationDateDescription: String, // This describes (in english) how regularly the swap is to be valued, e.g. "every local working day"
val notificationTime: String, val notificationTime: String,
val resolutionTime: String, val resolutionTime: String,
val interestRate: ReferenceRate, val interestRate: ReferenceRate,
@ -679,22 +679,6 @@ class InterestRateSwap() : Contract {
return ScheduledActivity(protocolLogicRefFactory.create(TwoPartyDealProtocol.FixingRoleDecider::class.java, thisStateRef, duration), instant) return ScheduledActivity(protocolLogicRefFactory.create(TwoPartyDealProtocol.FixingRoleDecider::class.java, thisStateRef, duration), instant)
} }
// TODO: This changing of the public key violates the assumption that Party is a fixed identity key.
override fun withPublicKey(before: Party, after: PublicKey): DealState {
val newParty = Party(before.name, after)
if (before == fixedLeg.fixedRatePayer) {
val deal = copy()
deal.fixedLeg.fixedRatePayer = newParty
return deal
} else if (before == floatingLeg.floatingRatePayer) {
val deal = copy()
deal.floatingLeg.floatingRatePayer = newParty
return deal
} else {
throw IllegalArgumentException("No such party: $before")
}
}
override fun generateAgreement(notary: Party): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, notary) override fun generateAgreement(notary: Party): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, notary)
override fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix) { override fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix) {

View File

@ -7,11 +7,8 @@ import com.r3corda.core.transactions.SignedTransaction
import com.r3corda.core.utilities.DUMMY_NOTARY import com.r3corda.core.utilities.DUMMY_NOTARY
import com.r3corda.core.utilities.DUMMY_NOTARY_KEY import com.r3corda.core.utilities.DUMMY_NOTARY_KEY
import com.r3corda.core.utilities.TEST_TX_TIME import com.r3corda.core.utilities.TEST_TX_TIME
import com.r3corda.testing.LedgerDSL
import com.r3corda.testing.TestLedgerDSLInterpreter
import com.r3corda.testing.TestTransactionDSLInterpreter
import com.r3corda.testing.node.MockServices
import com.r3corda.testing.* import com.r3corda.testing.*
import com.r3corda.testing.node.MockServices
import org.junit.Test import org.junit.Test
import java.math.BigDecimal import java.math.BigDecimal
import java.time.LocalDate import java.time.LocalDate
@ -93,7 +90,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
threshold = Amount(0, EUR), threshold = Amount(0, EUR),
minimumTransferAmount = Amount(250000 * 100, EUR), minimumTransferAmount = Amount(250000 * 100, EUR),
rounding = Amount(10000 * 100, EUR), rounding = Amount(10000 * 100, EUR),
valuationDate = "Every Local Business Day", valuationDateDescription = "Every Local Business Day",
notificationTime = "2:00pm London", notificationTime = "2:00pm London",
resolutionTime = "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ", resolutionTime = "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ",
interestRate = ReferenceRate("T3270", Tenor("6M"), "EONIA"), interestRate = ReferenceRate("T3270", Tenor("6M"), "EONIA"),
@ -183,7 +180,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
threshold = Amount(0, EUR), threshold = Amount(0, EUR),
minimumTransferAmount = Amount(250000 * 100, EUR), minimumTransferAmount = Amount(250000 * 100, EUR),
rounding = Amount(10000 * 100, EUR), rounding = Amount(10000 * 100, EUR),
valuationDate = "Every Local Business Day", valuationDateDescription = "Every Local Business Day",
notificationTime = "2:00pm London", notificationTime = "2:00pm London",
resolutionTime = "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ", resolutionTime = "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ",
interestRate = ReferenceRate("T3270", Tenor("6M"), "EONIA"), interestRate = ReferenceRate("T3270", Tenor("6M"), "EONIA"),

View File

@ -23,6 +23,7 @@ fun commodity(code: String) = Commodity.getInstance(code)!!
@JvmField val USD = currency("USD") @JvmField val USD = currency("USD")
@JvmField val GBP = currency("GBP") @JvmField val GBP = currency("GBP")
@JvmField val EUR = currency("EUR")
@JvmField val CHF = currency("CHF") @JvmField val CHF = currency("CHF")
@JvmField val FCOJ = commodity("FCOJ") @JvmField val FCOJ = commodity("FCOJ")

View File

@ -279,9 +279,6 @@ interface DealState : LinearState {
/** Exposes the Parties involved in a generic way */ /** Exposes the Parties involved in a generic way */
val parties: List<Party> val parties: List<Party>
// TODO: This works by editing the keys used by a Party which is invalid.
fun withPublicKey(before: Party, after: PublicKey): DealState
/** /**
* Generate a partial transaction representing an agreement (command) to this deal, allowing a general * Generate a partial transaction representing an agreement (command) to this deal, allowing a general
* deal/agreement protocol to generate the necessary transaction for potential implementations. * deal/agreement protocol to generate the necessary transaction for potential implementations.

View File

@ -231,7 +231,7 @@ object X509Utilities {
fun generateECDSAKeyPairForSSL(): KeyPair { fun generateECDSAKeyPairForSSL(): KeyPair {
val keyGen = KeyPairGenerator.getInstance(KEY_GENERATION_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME) val keyGen = KeyPairGenerator.getInstance(KEY_GENERATION_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME)
val ecSpec = ECGenParameterSpec(ECDSA_CURVE) // Force named curve, because TLS implementations don't support many curves val ecSpec = ECGenParameterSpec(ECDSA_CURVE) // Force named curve, because TLS implementations don't support many curves
keyGen.initialize(ecSpec, SecureRandom()) keyGen.initialize(ecSpec, newSecureRandom())
return keyGen.generateKeyPair() return keyGen.generateKeyPair()
} }

View File

@ -150,6 +150,10 @@ interface VaultService {
} }
inline fun <reified T : LinearState> VaultService.linearHeadsOfType() = linearHeadsOfType_(T::class.java) inline fun <reified T : LinearState> VaultService.linearHeadsOfType() = linearHeadsOfType_(T::class.java)
inline fun <reified T : DealState> VaultService.dealsWith(party: Party) = linearHeadsOfType<T>().values.filter {
// TODO: Replace name comparison with full party comparison (keys are currenty not equal)
it.state.data.parties.any { it.name == party.name }
}
/** /**
* The KMS is responsible for storing and using private keys to sign things. An implementation of this may, for example, * The KMS is responsible for storing and using private keys to sign things. An implementation of this may, for example,

View File

@ -2,8 +2,11 @@ package com.r3corda.core.protocols
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.messaging.Message
import com.r3corda.core.messaging.runOnNextMessage
import com.r3corda.core.node.ServiceHub import com.r3corda.core.node.ServiceHub
import com.r3corda.core.node.services.DEFAULT_SESSION_ID import com.r3corda.core.node.services.DEFAULT_SESSION_ID
import com.r3corda.core.serialization.deserialize
import com.r3corda.core.utilities.ProgressTracker import com.r3corda.core.utilities.ProgressTracker
import com.r3corda.core.utilities.UntrustworthyData import com.r3corda.core.utilities.UntrustworthyData
import com.r3corda.core.utilities.debug import com.r3corda.core.utilities.debug
@ -11,6 +14,7 @@ import com.r3corda.protocols.HandshakeMessage
import org.slf4j.Logger import org.slf4j.Logger
import rx.Observable import rx.Observable
import java.util.* import java.util.*
import java.util.concurrent.CompletableFuture
/** /**
* A sub-class of [ProtocolLogic<T>] implements a protocol flow using direct, straight line blocking code. Thus you * A sub-class of [ProtocolLogic<T>] implements a protocol flow using direct, straight line blocking code. Thus you
@ -108,6 +112,11 @@ abstract class ProtocolLogic<out T> {
throw IllegalStateException("Session with party $otherParty hasn't been established yet") throw IllegalStateException("Session with party $otherParty hasn't been established yet")
} }
/**
* Check if we already have a session with this party
*/
protected fun hasSession(otherParty: Party) = sessions.containsKey(otherParty)
/** /**
* Invokes the given subprotocol by simply passing through this [ProtocolLogic]s reference to the * Invokes the given subprotocol by simply passing through this [ProtocolLogic]s reference to the
* [ProtocolStateMachine] and then calling the [call] method. * [ProtocolStateMachine] and then calling the [call] method.

View File

@ -71,8 +71,8 @@ abstract class AbstractStateReplacementProtocol<T> {
return finalTx.tx.outRef(0) return finalTx.tx.outRef(0)
} }
abstract internal fun assembleProposal(stateRef: StateRef, modification: T, stx: SignedTransaction): Proposal<T> abstract protected fun assembleProposal(stateRef: StateRef, modification: T, stx: SignedTransaction): Proposal<T>
abstract internal fun assembleTx(): Pair<SignedTransaction, List<PublicKey>> abstract protected fun assembleTx(): Pair<SignedTransaction, List<PublicKey>>
@Suspendable @Suspendable
private fun collectSignatures(participants: List<PublicKey>, stx: SignedTransaction): List<DigitalSignature.WithKey> { private fun collectSignatures(participants: List<PublicKey>, stx: SignedTransaction): List<DigitalSignature.WithKey> {
@ -94,7 +94,10 @@ abstract class AbstractStateReplacementProtocol<T> {
private fun getParticipantSignature(party: Party, stx: SignedTransaction): DigitalSignature.WithKey { private fun getParticipantSignature(party: Party, stx: SignedTransaction): DigitalSignature.WithKey {
val proposal = assembleProposal(originalState.ref, modification, stx) val proposal = assembleProposal(originalState.ref, modification, stx)
// TODO: Move this into protocol logic as a func on the lines of handshake(Party, HandshakeMessage)
if (!hasSession(party)) {
send(party, Handshake(serviceHub.storageService.myLegalIdentity)) send(party, Handshake(serviceHub.storageService.myLegalIdentity))
}
val response = sendAndReceive<Result>(party, proposal) val response = sendAndReceive<Result>(party, proposal)
val participantSignature = response.unwrap { val participantSignature = response.unwrap {
@ -180,7 +183,7 @@ abstract class AbstractStateReplacementProtocol<T> {
* on the change proposed, and may further depend on the node itself (for example configuration). The * on the change proposed, and may further depend on the node itself (for example configuration). The
* proposal is returned if acceptable, otherwise an exception is thrown. * proposal is returned if acceptable, otherwise an exception is thrown.
*/ */
abstract fun verifyProposal(maybeProposal: UntrustworthyData<Proposal<T>>): Proposal<T> abstract protected fun verifyProposal(maybeProposal: UntrustworthyData<Proposal<T>>): Proposal<T>
@Suspendable @Suspendable
private fun verifyTx(stx: SignedTransaction) { private fun verifyTx(stx: SignedTransaction) {

View File

@ -6,6 +6,7 @@ import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.DigitalSignature import com.r3corda.core.crypto.DigitalSignature
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.signWithECDSA import com.r3corda.core.crypto.signWithECDSA
import com.r3corda.core.crypto.toBase58String
import com.r3corda.core.node.NodeInfo import com.r3corda.core.node.NodeInfo
import com.r3corda.core.node.recordTransactions import com.r3corda.core.node.recordTransactions
import com.r3corda.core.node.services.ServiceType import com.r3corda.core.node.services.ServiceType
@ -286,22 +287,11 @@ object TwoPartyDealProtocol {
override fun validateHandshake(handshake: Handshake<T>): Handshake<T> { override fun validateHandshake(handshake: Handshake<T>): Handshake<T> {
// What is the seller trying to sell us? // What is the seller trying to sell us?
val deal: T = handshake.payload val deal: T = handshake.payload
val otherKey = handshake.publicKey
logger.trace { "Got deal request for: ${handshake.payload.ref}" } logger.trace { "Got deal request for: ${handshake.payload.ref}" }
check(dealToBuy == deal) check(dealToBuy == deal)
// We need to substitute in the new public keys for the Parties return handshake.copy(payload = deal)
val myName = serviceHub.storageService.myLegalIdentity.name
val myOldParty = deal.parties.single { it.name == myName }
val theirOldParty = deal.parties.single { it.name != myName }
@Suppress("UNCHECKED_CAST")
val newDeal = deal.
withPublicKey(myOldParty, serviceHub.keyManagementService.freshKey().public).
withPublicKey(theirOldParty, otherKey) as T
return handshake.copy(payload = newDeal)
} }

View File

@ -262,7 +262,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
private fun scheduleTX(instant: Instant, increment: Int = 1): ScheduledStateRef? { private fun scheduleTX(instant: Instant, increment: Int = 1): ScheduledStateRef? {
var scheduledRef: ScheduledStateRef? = null var scheduledRef: ScheduledStateRef? = null
apply { apply {
val freshKey = services.keyManagementService.freshKey() val freshKey = services.storageService.myLegalIdentityKey
val state = TestState(factory.create(TestProtocolLogic::class.java, increment), instant) val state = TestState(factory.create(TestProtocolLogic::class.java, increment), instant)
val usefulTX = TransactionType.General.Builder(null).apply { val usefulTX = TransactionType.General.Builder(null).apply {
addOutputState(state, DUMMY_NOTARY) addOutputState(state, DUMMY_NOTARY)

View File

@ -84,7 +84,7 @@ class VaultWithCashTest {
fun `issue and spend total correctly and irrelevant ignored`() { fun `issue and spend total correctly and irrelevant ignored`() {
databaseTransaction(database) { databaseTransaction(database) {
// A tx that sends us money. // A tx that sends us money.
val freshKey = services.keyManagementService.freshKey() val freshKey = services.storageService.myLegalIdentityKey
val usefulTX = TransactionType.General.Builder(null).apply { val usefulTX = TransactionType.General.Builder(null).apply {
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), freshKey.public, DUMMY_NOTARY) Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), freshKey.public, DUMMY_NOTARY)
signWith(MEGA_CORP_KEY) signWith(MEGA_CORP_KEY)
@ -122,7 +122,7 @@ class VaultWithCashTest {
@Test @Test
fun `branching LinearStates fails to verify`() { fun `branching LinearStates fails to verify`() {
databaseTransaction(database) { databaseTransaction(database) {
val freshKey = services.keyManagementService.freshKey() val freshKey = services.storageService.myLegalIdentityKey
val linearId = UniqueIdentifier() val linearId = UniqueIdentifier()
// Issue a linear state // Issue a linear state
@ -142,7 +142,7 @@ class VaultWithCashTest {
@Test @Test
fun `sequencing LinearStates works`() { fun `sequencing LinearStates works`() {
databaseTransaction(database) { databaseTransaction(database) {
val freshKey = services.keyManagementService.freshKey() val freshKey = services.storageService.myLegalIdentityKey
val linearId = UniqueIdentifier() val linearId = UniqueIdentifier()

View File

@ -85,7 +85,7 @@ fun main(args: Array<String>) {
// Make a garbage transaction that includes a rate fix. // Make a garbage transaction that includes a rate fix.
val tx = TransactionType.General.Builder(notaryNode.identity) val tx = TransactionType.General.Builder(notaryNode.identity)
tx.addOutputState(TransactionState(Cash.State(1500.DOLLARS `issued by` node.storage.myLegalIdentity.ref(1), node.keyManagement.freshKey().public), notaryNode.identity)) tx.addOutputState(TransactionState(Cash.State(1500.DOLLARS `issued by` node.storage.myLegalIdentity.ref(1), node.storage.myLegalIdentityKey.public), notaryNode.identity))
val protocol = RatesFixProtocol(tx, rateOracle.identity, fixOf, expectedRate, rateTolerance) val protocol = RatesFixProtocol(tx, rateOracle.identity, fixOf, expectedRate, rateTolerance)
node.services.startProtocol("demo.ratefix", protocol).get() node.services.startProtocol("demo.ratefix", protocol).get()
node.stop() node.stop()

View File

@ -202,7 +202,7 @@ private fun runBuyer(node: Node, amount: Amount<Currency>) {
databaseTransaction(node.database) { databaseTransaction(node.database) {
node.services.fillWithSomeTestCash(300000.DOLLARS, node.services.fillWithSomeTestCash(300000.DOLLARS,
outputNotary = node.info.identity, // In this demo, the buyer and notary are the same. outputNotary = node.info.identity, // In this demo, the buyer and notary are the same.
ownedBy = node.services.keyManagementService.freshKey().public) ownedBy = node.storage.myLegalIdentityKey.public)
} }
// Wait around until a node asks to start a trade with us. In a real system, this part would happen out of band // Wait around until a node asks to start a trade with us. In a real system, this part would happen out of band
@ -314,7 +314,7 @@ private class TraderDemoProtocolSeller(val otherSide: Party,
progressTracker.currentStep = SELF_ISSUING progressTracker.currentStep = SELF_ISSUING
val notary: NodeInfo = serviceHub.networkMapCache.notaryNodes[0] val notary: NodeInfo = serviceHub.networkMapCache.notaryNodes[0]
val cpOwnerKey = serviceHub.keyManagementService.freshKey() val cpOwnerKey = serviceHub.storageService.myLegalIdentityKey
val commercialPaper = selfIssueSomeCommercialPaper(cpOwnerKey.public, notary) val commercialPaper = selfIssueSomeCommercialPaper(cpOwnerKey.public, notary)
progressTracker.currentStep = TRADING progressTracker.currentStep = TRADING

View File

@ -107,7 +107,7 @@ object AutoOfferProtocol {
otherParty, otherParty,
notary, notary,
dealToBeOffered, dealToBeOffered,
serviceHub.keyManagementService.freshKey(), serviceHub.storageService.myLegalIdentityKey,
progressTracker.getChildProgressTracker(DEALING)!! progressTracker.getChildProgressTracker(DEALING)!!
) )
val stx = subProtocol(instigator, inheritParentSessions = true) val stx = subProtocol(instigator, inheritParentSessions = true)

View File

@ -32,17 +32,11 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
currentDateAndTime = LocalDate.of(2016, 3, 8).atStartOfDay() currentDateAndTime = LocalDate.of(2016, 3, 8).atStartOfDay()
} }
private var nodeAKey: KeyPair? = null
private var nodeBKey: KeyPair? = null
private val executeOnNextIteration = Collections.synchronizedList(LinkedList<() -> Unit>()) private val executeOnNextIteration = Collections.synchronizedList(LinkedList<() -> Unit>())
override fun startMainSimulation(): ListenableFuture<Unit> { override fun startMainSimulation(): ListenableFuture<Unit> {
val future = SettableFuture.create<Unit>() val future = SettableFuture.create<Unit>()
nodeAKey = banks[0].keyManagement.freshKey()
nodeBKey = banks[1].keyManagement.freshKey()
startIRSDealBetween(0, 1).success { startIRSDealBetween(0, 1).success {
// Next iteration is a pause. // Next iteration is a pause.
executeOnNextIteration.add {} executeOnNextIteration.add {}
@ -121,7 +115,7 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
irs.fixedLeg.fixedRatePayer = node1.info.identity irs.fixedLeg.fixedRatePayer = node1.info.identity
irs.floatingLeg.floatingRatePayer = node2.info.identity irs.floatingLeg.floatingRatePayer = node2.info.identity
val instigator = TwoPartyDealProtocol.Instigator(node2.info.identity, notary.info.identity, irs, nodeAKey!!) val instigator = TwoPartyDealProtocol.Instigator(node2.info.identity, notary.info.identity, irs, node1.keyPair!!)
val acceptor = TwoPartyDealProtocol.Acceptor(node1.info.identity, notary.info.identity, irs) val acceptor = TwoPartyDealProtocol.Acceptor(node1.info.identity, notary.info.identity, irs)
connectProtocols(instigator, acceptor) connectProtocols(instigator, acceptor)

View File

@ -3,6 +3,7 @@ package com.r3corda.simulation
import com.google.common.net.HostAndPort import com.google.common.net.HostAndPort
import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import com.r3corda.core.crypto.generateKeyPair
import com.r3corda.core.messaging.SingleMessageRecipient import com.r3corda.core.messaging.SingleMessageRecipient
import com.r3corda.core.node.CityDatabase import com.r3corda.core.node.CityDatabase
import com.r3corda.core.node.PhysicalLocation import com.r3corda.core.node.PhysicalLocation
@ -73,8 +74,11 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
return SimulatedNode(cfg, network, networkMapAddr, advertisedServices, id, keyPair) return SimulatedNode(cfg, network, networkMapAddr, advertisedServices, id, keyPair)
} }
fun createAll(): List<SimulatedNode> = bankLocations. fun createAll(): List<SimulatedNode> {
map { network.createNode(networkMap.info.address, start = false, nodeFactory = this) as SimulatedNode } return bankLocations.map {
network.createNode(networkMap.info.address, start = false, nodeFactory = this, keyPair = generateKeyPair()) as SimulatedNode
}
}
} }
val bankFactory = BankFactory() val bankFactory = BankFactory()

View File

@ -82,7 +82,7 @@
"quantity": 1000000, "quantity": 1000000,
"token": "EUR" "token": "EUR"
}, },
"valuationDate": "Every Local Business Day", "valuationDateDescription": "Every Local Business Day",
"notificationTime": "2:00pm London", "notificationTime": "2:00pm London",
"resolutionTime": "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ", "resolutionTime": "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ",
"interestRate": { "interestRate": {

View File

@ -18,7 +18,7 @@ define([], () => {
rounding: { rounding: {
quantity: 1000000 quantity: 1000000
}, },
valuationDate: "Every Local Business Day", valuationDateDescription: "Every Local Business Day",
notificationTime: "2:00pm London", notificationTime: "2:00pm London",
resolutionTime: "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given", resolutionTime: "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given",
interestRate: { interestRate: {

View File

@ -23,7 +23,7 @@
</tr> </tr>
<tr class="center aligned"> <tr class="center aligned">
<td>Valuation Date</td> <td>Valuation Date</td>
<td>{{deal.common.valuationDate}}</td> <td>{{deal.common.valuationDateDescription}}</td>
</tr> </tr>
<tr class="center aligned"> <tr class="center aligned">
<td>Legal Document Hash</td> <td>Legal Document Hash</td>

View File

@ -82,7 +82,7 @@
"quantity": 1000000, "quantity": 1000000,
"token": "EUR" "token": "EUR"
}, },
"valuationDate": "Every Local Business Day", "valuationDateDescription": "Every Local Business Day",
"notificationTime": "2:00pm London", "notificationTime": "2:00pm London",
"resolutionTime": "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ", "resolutionTime": "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ",
"interestRate": { "interestRate": {