Merge branch 'master' into dynamic-loading

This commit is contained in:
sofusmortensen 2016-04-08 22:40:18 +02:00
commit 6fe8107d27
52 changed files with 2638 additions and 382 deletions

View File

@ -148,7 +148,36 @@ task getRateFixDemo(type: CreateStartScripts) {
classpath = jar.outputs.files + project.configurations.runtime
}
task getIRSDemo(type: CreateStartScripts) {
mainClassName = "demos.IRSDemoKt"
applicationName = "irsdemo"
defaultJvmOpts = ["-javaagent:${configurations.quasar.singleFile}"]
outputDir = new File(project.buildDir, 'scripts')
classpath = jar.outputs.files + project.configurations.runtime
}
// These lines tell gradle to run the Quasar suspendables scanner to look for unannotated super methods
// that have @Suspendable sub implementations. These tend to cause NPEs and are not caught by the verifier
// NOTE: need to make sure the output isn't on the classpath or every other run it generates empty results, so
// we explicitly delete to avoid that happening. We also need to turn off what seems to be a spurious warning in the IDE
//noinspection GroovyAssignabilityCheck
task quasarScan(dependsOn: ['classes', 'core:classes', 'contracts:classes']) << {
ant.taskdef(name:'scanSuspendables', classname:'co.paralleluniverse.fibers.instrument.SuspendablesScanner',
classpath: "${sourceSets.main.output.classesDir}:${sourceSets.main.output.resourcesDir}:${configurations.runtime.asPath}")
delete "$sourceSets.main.output.resourcesDir/META-INF/suspendables", "$sourceSets.main.output.resourcesDir/META-INF/suspendable-supers"
ant.scanSuspendables(
auto:false,
suspendablesFile: "$sourceSets.main.output.resourcesDir/META-INF/suspendables",
supersFile: "$sourceSets.main.output.resourcesDir/META-INF/suspendable-supers") {
fileset(dir: sourceSets.main.output.classesDir)
}
}
jar.dependsOn quasarScan
applicationDistribution.into("bin") {
from(getRateFixDemo)
from(getIRSDemo)
fileMode = 0755
}

View File

@ -83,7 +83,7 @@ class CommercialPaper : Contract {
// Here, we match acceptable timestamp authorities by name. The list of acceptable TSAs (oracles) must be
// hard coded into the contract because otherwise we could fail to gain consensus, if nodes disagree about
// who or what is a trusted authority.
val timestamp: TimestampCommand? = tx.commands.getTimestampByName("Mock Company 0", "Bank of Zurich")
val timestamp: TimestampCommand? = tx.commands.getTimestampByName("Mock Company 0", "Bank A")
for (group in groups) {
when (command.value) {

View File

@ -8,18 +8,8 @@
package contracts
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.type.SimpleType
import core.*
import core.crypto.SecureHash
import core.node.services.DummyTimestampingAuthority
import org.apache.commons.jexl3.JexlBuilder
import org.apache.commons.jexl3.MapContext
import java.math.BigDecimal
@ -31,7 +21,16 @@ import java.util.*
val IRS_PROGRAM_ID = InterestRateSwap()
// This is a placeholder for some types that we haven't identified exactly what they are just yet for things still in discussion
open class UnknownType()
open class UnknownType() {
override fun equals(other: Any?): Boolean {
return (other is UnknownType)
}
override fun hashCode(): Int {
return 1
}
}
/**
* Event superclass - everything happens on a date.
@ -53,12 +52,12 @@ abstract class PaymentEvent(date: LocalDate) : Event(date) {
* For the floating leg, the rate refers to a reference rate which is to be "fixed" at a point in the future.
*/
abstract class RatePaymentEvent(date: LocalDate,
val accrualStartDate: LocalDate,
val accrualEndDate: LocalDate,
val dayCountBasisDay: DayCountBasisDay,
val dayCountBasisYear: DayCountBasisYear,
val notional: Amount,
val rate: Rate) : PaymentEvent(date) {
val accrualStartDate: LocalDate,
val accrualEndDate: LocalDate,
val dayCountBasisDay: DayCountBasisDay,
val dayCountBasisYear: DayCountBasisYear,
val notional: Amount,
val rate: Rate) : PaymentEvent(date) {
companion object {
val CSVHeader = "AccrualStartDate,AccrualEndDate,DayCountFactor,Days,Date,Ccy,Notional,Rate,Flow"
}
@ -71,7 +70,7 @@ abstract class RatePaymentEvent(date: LocalDate,
dayCountCalculator(accrualStartDate, accrualEndDate, dayCountBasisYear, dayCountBasisDay)
val dayCountFactor: BigDecimal get() =
// TODO : Fix below (use daycount convention for division)
// TODO : Fix below (use daycount convention for division)
(BigDecimal(days).divide(BigDecimal(360.0), 8, RoundingMode.HALF_UP)).setScale(4, RoundingMode.HALF_UP)
open fun asCSV(): String = "$accrualStartDate,$accrualEndDate,$dayCountFactor,$days,$date,${notional.currency},${notional},$rate,$flow"
@ -138,12 +137,6 @@ class FloatingRatePaymentEvent(date: LocalDate,
}
/**
* Don't try and use a rate that isn't ready yet.
*/
class DataNotReadyException : Exception()
/**
* The Interest Rate Swap class. For a quick overview of what an IRS is, see here - http://www.pimco.co.uk/EN/Education/Pages/InterestRateSwapsBasics1-08.aspx (no endorsement)
* This contract has 4 significant data classes within it, the "Common", "Calculation", "FixedLeg" and "FloatingLeg"
@ -240,6 +233,48 @@ class InterestRateSwap() : Contract {
"TerminationDateAdjustment=$terminationDateAdjustment,DayCountBasis=$dayCountBasisDay/$dayCountBasisYear,DayInMonth=$dayInMonth," +
"PaymentRule=$paymentRule,PaymentDelay=$paymentDelay,PaymentCalendar=$paymentCalendar,InterestPeriodAdjustment=$interestPeriodAdjustment"
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as CommonLeg
if (notional != other.notional) return false
if (paymentFrequency != other.paymentFrequency) return false
if (effectiveDate != other.effectiveDate) return false
if (effectiveDateAdjustment != other.effectiveDateAdjustment) return false
if (terminationDate != other.terminationDate) return false
if (terminationDateAdjustment != other.terminationDateAdjustment) return false
if (dayCountBasisDay != other.dayCountBasisDay) return false
if (dayCountBasisYear != other.dayCountBasisYear) return false
if (dayInMonth != other.dayInMonth) return false
if (paymentRule != other.paymentRule) return false
if (paymentDelay != other.paymentDelay) return false
if (paymentCalendar != other.paymentCalendar) return false
if (interestPeriodAdjustment != other.interestPeriodAdjustment) return false
return true
}
override fun hashCode(): Int {
var result = notional.hashCode()
result += 31 * result + paymentFrequency.hashCode()
result += 31 * result + effectiveDate.hashCode()
result += 31 * result + (effectiveDateAdjustment?.hashCode() ?: 0)
result += 31 * result + terminationDate.hashCode()
result += 31 * result + (terminationDateAdjustment?.hashCode() ?: 0)
result += 31 * result + dayCountBasisDay.hashCode()
result += 31 * result + dayCountBasisYear.hashCode()
result += 31 * result + dayInMonth
result += 31 * result + paymentRule.hashCode()
result += 31 * result + paymentDelay
result += 31 * result + paymentCalendar.hashCode()
result += 31 * result + interestPeriodAdjustment.hashCode()
return result
}
}
open class FixedLeg(
@ -264,6 +299,29 @@ class InterestRateSwap() : Contract {
dayCountBasisDay, dayCountBasisYear, dayInMonth, paymentRule, paymentDelay, paymentCalendar, interestPeriodAdjustment) {
override fun toString(): String = "FixedLeg(Payer=$fixedRatePayer," + super.toString() + ",fixedRate=$fixedRate," +
"rollConvention=$rollConvention"
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
if (!super.equals(other)) return false
other as FixedLeg
if (fixedRatePayer != other.fixedRatePayer) return false
if (fixedRate != other.fixedRate) return false
if (rollConvention != other.rollConvention) return false
return true
}
override fun hashCode(): Int {
var result = super.hashCode()
result += 31 * result + fixedRatePayer.hashCode()
result += 31 * result + fixedRate.hashCode()
result += 31 * result + rollConvention.hashCode()
return result
}
}
open class FloatingLeg(
@ -298,6 +356,44 @@ class InterestRateSwap() : Contract {
"FixingPeriond=$fixingPeriod,ResetRule=$resetRule,FixingsPerPayment=$fixingsPerPayment,FixingCalendar=$fixingCalendar," +
"Index=$index,IndexSource=$indexSource,IndexTenor=$indexTenor"
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
if (!super.equals(other)) return false
other as FloatingLeg
if (floatingRatePayer != other.floatingRatePayer) return false
if (rollConvention != other.rollConvention) return false
if (fixingRollConvention != other.fixingRollConvention) return false
if (resetDayInMonth != other.resetDayInMonth) return false
if (fixingPeriod != other.fixingPeriod) return false
if (resetRule != other.resetRule) return false
if (fixingsPerPayment != other.fixingsPerPayment) return false
if (fixingCalendar != other.fixingCalendar) return false
if (index != other.index) return false
if (indexSource != other.indexSource) return false
if (indexTenor != other.indexTenor) return false
return true
}
override fun hashCode(): Int {
var result = super.hashCode()
result += 31 * result + floatingRatePayer.hashCode()
result += 31 * result + rollConvention.hashCode()
result += 31 * result + fixingRollConvention.hashCode()
result += 31 * result + resetDayInMonth
result += 31 * result + fixingPeriod.hashCode()
result += 31 * result + resetRule.hashCode()
result += 31 * result + fixingsPerPayment.hashCode()
result += 31 * result + fixingCalendar.hashCode()
result += 31 * result + index.hashCode()
result += 31 * result + indexSource.hashCode()
result += 31 * result + indexTenor.hashCode()
return result
}
}
/**
@ -305,7 +401,7 @@ class InterestRateSwap() : Contract {
*/
override fun verify(tx: TransactionForVerification) {
val command = tx.commands.requireSingleCommand<InterestRateSwap.Commands>()
val time = tx.commands.getTimestampByName("Mock Company 0", "Bank of Zurich")?.midpoint
val time = tx.commands.getTimestampByName("Mock Company 0", "European Timestamping Service", "Bank A")?.midpoint
if (time == null) throw IllegalArgumentException("must be timestamped")
val irs = tx.outStates.filterIsInstance<InterestRateSwap.State>().single()
@ -356,7 +452,8 @@ class InterestRateSwap() : Contract {
val floatingLeg: FloatingLeg,
val calculation: Calculation,
val common: Common
) : LinearState {
) : FixableDealState {
override val contract = IRS_PROGRAM_ID
override val thread = SecureHash.sha256(common.tradeID)
override val ref = common.tradeID
@ -365,6 +462,39 @@ class InterestRateSwap() : Contract {
return (fixedLeg.fixedRatePayer.owningKey in ourKeys) || (floatingLeg.floatingRatePayer.owningKey in ourKeys)
}
override val parties: Array<Party>
get() = arrayOf(fixedLeg.fixedRatePayer, floatingLeg.floatingRatePayer)
override fun withPublicKey(before: Party, after: PublicKey): State {
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(): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common)
override fun generateFix(ptx: TransactionBuilder, oldStateRef: StateRef, fix: Fix) {
InterestRateSwap().generateFix(ptx, StateAndRef(this, oldStateRef), Pair(fix.of.forDay, Rate(RatioUnit(fix.value))))
}
override fun nextFixingOf(): FixOf? {
val date = calculation.nextFixingDate()
return if (date == null) null else {
val fixingEvent = calculation.getFixing(date)
val oracleRate = fixingEvent.rate as ReferenceRate
FixOf(oracleRate.name, date, oracleRate.tenor)
}
}
/**
* For evaluating arbitrary java on the platform
*/

View File

@ -1,17 +1,8 @@
package contracts
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer
import com.fasterxml.jackson.databind.type.SimpleType
import core.Amount
import core.Tenor
import core.*
import java.math.BigDecimal
import java.time.LocalDate
import java.security.PublicKey
// Things in here will move to the general utils class when we've hammered out various discussions regarding amounts, dates, oracle etc.
@ -21,6 +12,22 @@ import java.time.LocalDate
*/
open class RatioUnit(value: BigDecimal) { // TODO: Discuss this type
val value = value
override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as RatioUnit
if (value != other.value) return false
return true
}
override fun hashCode(): Int{
return value.hashCode()
}
}
/**
@ -37,10 +44,78 @@ open class PercentageRatioUnit(percentageAsString: String) : RatioUnit(BigDecima
*/
val String.percent: PercentageRatioUnit get() = PercentageRatioUnit(this)
/**
* Interface representing an agreement that exposes various attributes that are common and allow
* implementation of general protocols that manipulate many agreement types
*/
interface DealState : LinearState {
/** Human readable well known reference (e.g. trade reference) */
val ref: String
/** Exposes the Parties involved in a generic way */
val parties: Array<Party>
/** Allow swapping in of potentially transaction specific public keys prior to signing */
fun withPublicKey(before: Party, after: PublicKey): DealState
/**
* 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
*
* TODO: Currently this is the "inception" transaction but in future an offer of some description might be an input state ref
*
* TODO: This should more likely be a method on the Contract (on a common interface) and the changes to reference a
* Contract instance from a ContractState are imminent, at which point we can move this out of here
*/
fun generateAgreement(): TransactionBuilder
}
/**
* Interface adding fixing specific methods
*/
interface FixableDealState : DealState {
/**
* When is the next fixing and what is the fixing for?
*
* TODO: In future we would use this to register for an event to trigger a/the fixing protocol
*/
fun nextFixingOf(): FixOf?
/**
* Generate a fixing command for this deal and fix
*
* TODO: This would also likely move to methods on the Contract once the changes to reference
* the Contract from the ContractState are in
*/
fun generateFix(ptx: TransactionBuilder, oldStateRef: StateRef, fix: Fix)
}
/**
* Parent of the Rate family. Used to denote fixed rates, floating rates, reference rates etc
*/
open class Rate(val ratioUnit: RatioUnit? = null)
open class Rate(val ratioUnit: RatioUnit? = null) {
override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as Rate
if (ratioUnit != other.ratioUnit) return false
return true
}
/**
* @returns the hash code of the ratioUnit or zero if the ratioUnit is null, as is the case for floating rate fixings
* that have not yet happened. Yet-to-be fixed floating rates need to be equal such that schedules can be tested
* for equality.
*/
override fun hashCode(): Int{
return ratioUnit?.hashCode() ?: 0
}
}
/**
* A very basic subclass to represent a fixed rate.

View File

@ -269,7 +269,7 @@ open class BusinessCalendar private constructor(val calendars: Array<out String>
calname.flatMap { (TEST_CALENDAR_DATA[it] ?: throw UnknownCalendar(it)).split(",") }.
toSet().
map{ parseDateFromString(it) }.
toList()
toList().sorted()
)
/** Calculates an event schedule that moves events around to ensure they fall on working days. */
@ -299,6 +299,17 @@ open class BusinessCalendar private constructor(val calendars: Array<out String>
}
}
override fun equals(other: Any?): Boolean = if (other is BusinessCalendar) {
/** Note this comparison is OK as we ensure they are sorted in getInstance() */
this.holidayDates == other.holidayDates
} else {
false
}
override fun hashCode(): Int {
return this.holidayDates.hashCode()
}
open fun isWorkingDay(date: LocalDate): Boolean =
when {
date.dayOfWeek == DayOfWeek.SATURDAY -> false

View File

@ -18,6 +18,7 @@ import java.io.OutputStream
import java.security.PublicKey
import java.time.Duration
import java.time.Instant
import java.time.LocalDate
import java.util.jar.JarInputStream
/** Implemented by anything that can be named by a secure hash value (e.g. transactions, attachments). */
@ -54,10 +55,6 @@ interface LinearState: ContractState {
/** Unique thread id within the wallets of all parties */
val thread: SecureHash
/** Human readable well known reference (e.g. trade reference) */
// TODO we will push this down out of here once we have something more sophisticated and a more powerful query API
val ref: String
/** true if this should be tracked by our wallet(s) */
fun isRelevant(ourKeys: Set<PublicKey>): Boolean
}

View File

@ -0,0 +1,85 @@
package core.math
import java.util.*
/**
* Interpolates values between the given data points using a [SplineFunction].
*
* Implementation uses the Natural Cubic Spline algorithm as described in
* R. L. Burden and J. D. Faires (2011), *Numerical Analysis*. 9th ed. Boston, MA: Brooks/Cole, Cengage Learning. p149-150.
*/
class CubicSplineInterpolator(private val xs: DoubleArray, private val ys: DoubleArray) {
init {
require(xs.size == ys.size) { "x and y dimensions should match: ${xs.size} != ${ys.size}" }
require(xs.size >= 3) { "At least 3 data points are required for interpolation, received: ${xs.size}" }
}
private val splineFunction by lazy { computeSplineFunction() }
fun interpolate(x: Double): Double {
require(x >= xs.first() && x <= xs.last()) { "Can't interpolate below ${xs.first()} or above ${xs.last()}" }
return splineFunction.getValue(x)
}
private fun computeSplineFunction(): SplineFunction {
val n = xs.size - 1
// Coefficients of polynomial
val b = DoubleArray(n) // linear
val c = DoubleArray(n + 1) // quadratic
val d = DoubleArray(n) // cubic
// Helpers
val h = DoubleArray(n)
val g = DoubleArray(n)
for (i in 0..n - 1)
h[i] = xs[i + 1] - xs[i]
for (i in 1..n - 1)
g[i] = 3 / h[i] * (ys[i + 1] - ys[i]) - 3 / h[i - 1] * (ys[i] - ys[i - 1])
// Solve tridiagonal linear system (using Crout Factorization)
val m = DoubleArray(n)
val z = DoubleArray(n)
for (i in 1..n - 1) {
val l = 2 * (xs[i + 1] - xs[i - 1]) - h[i - 1] * m[i - 1]
m[i] = h[i]/l
z[i] = (g[i] - h[i - 1] * z[i - 1]) / l
}
for (j in n - 1 downTo 0) {
c[j] = z[j] - m[j] * c[j + 1]
b[j] = (ys[j + 1] - ys[j]) / h[j] - h[j] * (c[j + 1] + 2.0 * c[j]) / 3.0
d[j] = (c[j + 1] - c[j]) / (3.0 * h[j])
}
val segmentMap = TreeMap<Double, Polynomial>()
for (i in 0..n - 1) {
val coefficients = doubleArrayOf(ys[i], b[i], c[i], d[i])
segmentMap.put(xs[i], Polynomial(coefficients))
}
return SplineFunction(segmentMap)
}
}
/**
* Represents a polynomial function of arbitrary degree
* @param coefficients polynomial coefficients in the order of degree (constant first, followed by higher degree term coefficients)
*/
class Polynomial(private val coefficients: DoubleArray) {
private val reversedCoefficients = coefficients.reversed()
fun getValue(x: Double) = reversedCoefficients.fold(0.0, { result, c -> result * x + c })
}
/**
* A *spline* is function piecewise-defined by polynomial functions.
* Points at which polynomial pieces connect are known as *knots*.
*
* @param segmentMap a mapping between a knot and the polynomial that covers the subsequent interval
*/
class SplineFunction(private val segmentMap: TreeMap<Double, Polynomial>) {
fun getValue(x: Double): Double {
val (knot, polynomial) = segmentMap.floorEntry(x)
return polynomial.getValue(x - knot)
}
}

View File

@ -18,4 +18,5 @@ import java.security.PublicKey
*/
interface IdentityService {
fun partyFromKey(key: PublicKey): Party?
fun partyFromName(name: String): Party?
}

View File

@ -42,13 +42,13 @@ import java.util.*
*/
class ProgressTracker(vararg steps: Step) {
sealed class Change {
class Position(val newStep: Step) : Change() {
class Position(val tracker: ProgressTracker, val newStep: Step) : Change() {
override fun toString() = newStep.label
}
class Rendering(val ofStep: Step) : Change() {
class Rendering(val tracker: ProgressTracker, val ofStep: Step) : Change() {
override fun toString() = ofStep.label
}
class Structural(val parent: Step) : Change() {
class Structural(val tracker: ProgressTracker, val parent: Step) : Change() {
override fun toString() = "Structural step change in child of ${parent.label}"
}
}
@ -59,13 +59,13 @@ class ProgressTracker(vararg steps: Step) {
}
/** This class makes it easier to relabel a step on the fly, to provide transient information. */
open class RelabelableStep(currentLabel: String) : Step(currentLabel) {
open inner class RelabelableStep(currentLabel: String) : Step(currentLabel) {
override val changes = BehaviorSubject.create<Change>()
var currentLabel: String = currentLabel
set(value) {
field = value
changes.onNext(ProgressTracker.Change.Rendering(this))
changes.onNext(ProgressTracker.Change.Rendering(this@ProgressTracker, this@RelabelableStep))
}
override val label: String get() = currentLabel
@ -109,7 +109,7 @@ class ProgressTracker(vararg steps: Step) {
curChangeSubscription?.unsubscribe()
stepIndex = index
_changes.onNext(Change.Position(steps[index]))
_changes.onNext(Change.Position(this, steps[index]))
curChangeSubscription = currentStep.changes.subscribe { _changes.onNext(it) }
if (currentStep == DONE) _changes.onCompleted()
@ -128,18 +128,33 @@ class ProgressTracker(vararg steps: Step) {
override fun put(key: Step, value: ProgressTracker): ProgressTracker? {
val r = super.put(key, value)
childSubscriptions[value] = value.changes.subscribe({ _changes.onNext(it) }, { _changes.onError(it) })
_changes.onNext(Change.Structural(key))
value.parent = this@ProgressTracker
_changes.onNext(Change.Structural(this@ProgressTracker, key))
return r
}
override fun remove(key: Step): ProgressTracker? {
if (containsKey(key))
childSubscriptions[this[key]]?.let { it.unsubscribe(); childSubscriptions.remove(this[key]) }
_changes.onNext(Change.Structural(key))
val tracker = this[key]
if (tracker != null) {
tracker.parent = null
childSubscriptions[tracker]?.let { it.unsubscribe(); childSubscriptions.remove(tracker) }
}
_changes.onNext(Change.Structural(this@ProgressTracker, key))
return super.remove(key)
}
}
/** The parent of this tracker: set automatically by the parent when a tracker is added as a child */
var parent: ProgressTracker? = null
/** Walks up the tree to find the top level tracker. If this is the top level tracker, returns 'this' */
val topLevelTracker: ProgressTracker
get() {
var cursor: ProgressTracker = this
while (cursor.parent != null) cursor = cursor.parent!!
return cursor
}
private val childSubscriptions = HashMap<ProgressTracker, Subscription>()
private fun _allSteps(level: Int = 0): List<Pair<Int, Step>> {

View File

@ -0,0 +1,43 @@
package core.math
import org.junit.Assert
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class InterpolatorsTest {
@Test
fun `throws when key to interpolate is outside the data set`() {
val xs = doubleArrayOf(1.0, 2.0, 4.0, 5.0)
val interpolator = CubicSplineInterpolator(xs, ys = xs)
assertFailsWith<IllegalArgumentException> { interpolator.interpolate(0.0) }
assertFailsWith<IllegalArgumentException> { interpolator.interpolate(6.0) }
}
@Test
fun `throws when data set is less than 3 points`() {
val xs = doubleArrayOf(1.0, 2.0)
assertFailsWith<IllegalArgumentException> { CubicSplineInterpolator(xs, ys = xs) }
}
@Test
fun `returns existing value when key is in data set`() {
val xs = doubleArrayOf(1.0, 2.0, 4.0, 5.0)
val interpolatedValue = CubicSplineInterpolator(xs, ys = xs).interpolate(2.0)
assertEquals(2.0, interpolatedValue)
}
@Test
fun `interpolates missing values correctly`() {
val xs = doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0)
val ys = doubleArrayOf(2.0, 4.0, 5.0, 11.0, 10.0)
val toInterpolate = doubleArrayOf(1.5, 2.5, 2.8, 3.3, 3.7, 4.3, 4.7)
// Expected values generated using R's splinefun (package stats v3.2.4), "natural" method
val expected = doubleArrayOf(3.28, 4.03, 4.37, 6.7, 9.46, 11.5, 10.91)
val interpolator = CubicSplineInterpolator(xs, ys)
val actual = toInterpolate.map { interpolator.interpolate(it) }.toDoubleArray()
Assert.assertArrayEquals(expected, actual, 0.01)
}
}

View File

@ -56,7 +56,7 @@ factors are:
* Improved contract flexibility vs Bitcoin
* Improved scalability vs Ethereum, as well as ability to keep parts of the transaction graph private (yet still uniquely addressable)
* No reliance on proof of work
* Re-us of existing sandboxing virtual machines
* Re-use of existing sandboxing virtual machines
* Use of type safe GCd implementation languages.
* Simplified auditing

View File

@ -611,7 +611,7 @@ In this example we see some new features of the DSL:
* The ``transaction`` function can also be given a time, to override the default timestamp on a transaction.
The ``trade`` function is not itself a unit test. Instead it builds up a trade/transaction group, with some slight
differences depending on the parameters provided (Kotlin allows parameters to have default valus). Then it returns
differences depending on the parameters provided (Kotlin allows parameters to have default values). Then it returns
it, unexecuted.
We use it like this:

View File

@ -0,0 +1,104 @@
{
"fixedLeg": {
"fixedRatePayer": "Bank A",
"notional": {
"pennies": 2500000000,
"currency": "USD"
},
"paymentFrequency": "SemiAnnual",
"effectiveDate": "2016-03-16",
"effectiveDateAdjustment": null,
"terminationDate": "2026-03-16",
"terminationDateAdjustment": null,
"fixedRate": {
"ratioUnit": {
"value": "0.01676"
}
},
"dayCountBasisDay": "D30",
"dayCountBasisYear": "Y360",
"rollConvention": "ModifiedFollowing",
"dayInMonth": 10,
"paymentRule": "InArrears",
"paymentDelay": 0,
"paymentCalendar": "London",
"interestPeriodAdjustment": "Adjusted"
},
"floatingLeg": {
"floatingRatePayer": "Bank B",
"notional": {
"pennies": 2500000000,
"currency": "USD"
},
"paymentFrequency": "Quarterly",
"effectiveDate": "2016-03-12",
"effectiveDateAdjustment": null,
"terminationDate": "2026-03-12",
"terminationDateAdjustment": null,
"dayCountBasisDay": "D30",
"dayCountBasisYear": "Y360",
"rollConvention": "ModifiedFollowing",
"fixingRollConvention": "ModifiedFollowing",
"dayInMonth": 10,
"resetDayInMonth": 10,
"paymentRule": "InArrears",
"paymentDelay": 0,
"paymentCalendar": [ "London" ],
"interestPeriodAdjustment": "Adjusted",
"fixingPeriod": "TWODAYS",
"resetRule": "InAdvance",
"fixingsPerPayment": "Quarterly",
"fixingCalendar": [ "NewYork" ],
"index": "ICE LIBOR",
"indexSource": "Rates Service Provider",
"indexTenor": {
"name": "3M"
}
},
"calculation": {
"expression": "( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) -(floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))",
"floatingLegPaymentSchedule": {
},
"fixedLegPaymentSchedule": {
}
},
"common": {
"baseCurrency": "EUR",
"eligibleCurrency": "EUR",
"eligibleCreditSupport": "Cash in an Eligible Currency",
"independentAmounts": {
"pennies": 0,
"currency": "EUR"
},
"threshold": {
"pennies": 0,
"currency": "EUR"
},
"minimumTransferAmount": {
"pennies": 25000000,
"currency": "EUR"
},
"rounding": {
"pennies": 1000000,
"currency": "EUR"
},
"valuationDate": "Every Local Business Day",
"notificationTime": "2:00pm London",
"resolutionTime": "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ",
"interestRate": {
"oracle": "Rates Service Provider",
"tenor": {
"name": "6M"
},
"ratioUnit": null,
"name": "EONIA"
},
"addressForTransfers": "",
"exposure": {},
"localBusinessDay": [ "London" , "NewYork" ],
"dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360",
"tradeID": "tradeXXX",
"hashLegalDocs": "put hash here"
},
"programRef": "1E6BBA305D445341F0026E51B6C7F3ACB834AFC6C2510C0EF7BC0477235EFECF"
}

View File

@ -1,51 +1,51 @@
# Some pretend noddy rate fixes, for the interest rate oracles.
LIBOR 2016-03-16 1M = 0.678
LIBOR 2016-03-16 2M = 0.655
ICE LIBOR 2016-03-16 1M = 0.678
ICE LIBOR 2016-03-16 2M = 0.655
EURIBOR 2016-03-15 1M = 0.123
EURIBOR 2016-03-15 2M = 0.111
LIBOR 2016-03-08 3M = 0.01
LIBOR 2016-06-08 3M = 0.01
LIBOR 2016-09-08 3M = 0.01
LIBOR 2016-12-08 3M = 0.01
LIBOR 2017-03-08 3M = 0.01
LIBOR 2017-06-08 3M = 0.01
LIBOR 2017-09-07 3M = 0.01
LIBOR 2017-12-07 3M = 0.01
LIBOR 2018-03-08 3M = 0.01
LIBOR 2018-06-07 3M = 0.01
LIBOR 2018-09-06 3M = 0.01
LIBOR 2018-12-06 3M = 0.01
LIBOR 2019-03-07 3M = 0.01
LIBOR 2019-06-06 3M = 0.01
LIBOR 2019-09-06 3M = 0.01
LIBOR 2019-12-06 3M = 0.01
LIBOR 2020-03-06 3M = 0.01
LIBOR 2020-06-08 3M = 0.01
LIBOR 2020-09-08 3M = 0.01
LIBOR 2020-12-08 3M = 0.01
LIBOR 2021-03-08 3M = 0.01
LIBOR 2021-06-08 3M = 0.01
LIBOR 2021-09-08 3M = 0.01
LIBOR 2021-12-08 3M = 0.01
LIBOR 2022-03-08 3M = 0.01
LIBOR 2022-06-08 3M = 0.01
LIBOR 2022-09-08 3M = 0.01
LIBOR 2022-12-08 3M = 0.01
LIBOR 2023-03-08 3M = 0.01
LIBOR 2023-06-08 3M = 0.01
LIBOR 2023-09-07 3M = 0.01
LIBOR 2023-12-07 3M = 0.01
LIBOR 2024-03-07 3M = 0.01
LIBOR 2024-06-06 3M = 0.01
LIBOR 2024-09-06 3M = 0.01
LIBOR 2024-12-06 3M = 0.01
LIBOR 2025-03-06 3M = 0.01
LIBOR 2025-06-06 3M = 0.01
LIBOR 2025-09-08 3M = 0.01
LIBOR 2025-12-08 3M = 0.01
LIBOR 2026-03-06 3M = 0.01
LIBOR 2026-06-08 3M = 0.01
LIBOR 2026-09-08 3M = 0.01
LIBOR 2026-12-08 3M = 0.01
ICE LIBOR 2016-03-08 3M = 0.0063515
ICE LIBOR 2016-06-08 3M = 0.0063520
ICE LIBOR 2016-09-08 3M = 0.0063521
ICE LIBOR 2016-12-08 3M = 0.0063515
ICE LIBOR 2017-03-08 3M = 0.0063525
ICE LIBOR 2017-06-08 3M = 0.0063530
ICE LIBOR 2017-09-07 3M = 0.0063531
ICE LIBOR 2017-12-07 3M = 0.0063532
ICE LIBOR 2018-03-08 3M = 0.0063533
ICE LIBOR 2018-06-07 3M = 0.0063534
ICE LIBOR 2018-09-06 3M = 0.0063535
ICE LIBOR 2018-12-06 3M = 0.0063536
ICE LIBOR 2019-03-07 3M = 0.0063537
ICE LIBOR 2019-06-06 3M = 0.0063538
ICE LIBOR 2019-09-06 3M = 0.0063539
ICE LIBOR 2019-12-06 3M = 0.0063540
ICE LIBOR 2020-03-06 3M = 0.0063541
ICE LIBOR 2020-06-08 3M = 0.0063542
ICE LIBOR 2020-09-08 3M = 0.0063543
ICE LIBOR 2020-12-08 3M = 0.0063544
ICE LIBOR 2021-03-08 3M = 0.0063545
ICE LIBOR 2021-06-08 3M = 0.0063546
ICE LIBOR 2021-09-08 3M = 0.0063547
ICE LIBOR 2021-12-08 3M = 0.0063548
ICE LIBOR 2022-03-08 3M = 0.0063549
ICE LIBOR 2022-06-08 3M = 0.0063550
ICE LIBOR 2022-09-08 3M = 0.0063551
ICE LIBOR 2022-12-08 3M = 0.0063553
ICE LIBOR 2023-03-08 3M = 0.0063554
ICE LIBOR 2023-06-08 3M = 0.0063555
ICE LIBOR 2023-09-07 3M = 0.0063556
ICE LIBOR 2023-12-07 3M = 0.0063557
ICE LIBOR 2024-03-07 3M = 0.0063558
ICE LIBOR 2024-06-06 3M = 0.0063559
ICE LIBOR 2024-09-06 3M = 0.0063560
ICE LIBOR 2024-12-06 3M = 0.0063561
ICE LIBOR 2025-03-06 3M = 0.0063562
ICE LIBOR 2025-06-06 3M = 0.0063563
ICE LIBOR 2025-09-08 3M = 0.0063564
ICE LIBOR 2025-12-08 3M = 0.0063565
ICE LIBOR 2026-03-06 3M = 0.0063566
ICE LIBOR 2026-06-08 3M = 0.0063567
ICE LIBOR 2026-09-08 3M = 0.0063568
ICE LIBOR 2026-12-08 3M = 0.0063569

65
scripts/irs-demo.sh Executable file
View File

@ -0,0 +1,65 @@
#!/bin/bash
mode=$1
if [ ! -e ./gradlew ]; then
echo "Run from the root directory please"
exit 1
fi
if [ ! -d build/install/r3prototyping ]; then
./gradlew installDist
fi
if [[ "$mode" == "nodeA" ]]; then
if [ ! -d nodeA ]; then
mkdir nodeA
echo "myLegalName = Bank A" >nodeA/config
fi
RC=83
while [ $RC -eq 83 ]
do
build/install/r3prototyping/bin/irsdemo --dir=nodeA --network-address=localhost --fake-trade-with-address=localhost:31340 --fake-trade-with-identity=nodeB/identity-public --timestamper-identity-file=nodeA/identity-public --timestamper-address=localhost --rates-oracle-address=localhost:31340 --rates-oracle-identity-file=nodeB/identity-public
RC=$?
done
elif [[ "$mode" == "nodeB" ]]; then
if [ ! -d nodeB ]; then
mkdir nodeB
echo "myLegalName = Bank B" >nodeB/config
fi
# enable job control
set -o monitor
RC=83
while [ $RC -eq 83 ]
do
build/install/r3prototyping/bin/irsdemo --dir=nodeB --network-address=localhost:31340 --fake-trade-with-address=localhost --fake-trade-with-identity=nodeA/identity-public --timestamper-identity-file=nodeA/identity-public --timestamper-address=localhost --rates-oracle-address=localhost:31340 --rates-oracle-identity-file=nodeB/identity-public &
while ! curl -F rates=@scripts/example.rates.txt http://localhost:31341/upload/interest-rates; do
echo "Retry to upload interest rates to oracle after 5 seconds"
sleep 5
done
fg %1
RC=$?
done
elif [[ "$mode" == "trade" && "$2" != "" ]]; then
tradeID=$2
echo "Uploading tradeID ${tradeID}"
sed "s/tradeXXX/${tradeID}/g" scripts/example-irs-trade.json | curl -H "Content-Type: application/json" -d @- http://localhost:31338/api/irs/deals
elif [[ "$mode" == "date" && "$2" != "" ]]; then
demodate=$2
echo "Setting demo date to ${demodate}"
echo "\"$demodate\"" | curl -H "Content-Type: application/json" -X PUT -d @- http://localhost:31338/api/irs/demodate
else
echo "Run like this, one in each tab:"
echo
echo " scripts/irs-demo.sh nodeA"
echo " scripts/irs-demo.sh nodeB"
echo
echo "To upload a trade as e.g. trade10"
echo " scripts/irs-demo.sh trade trade10"
echo
echo "To set the demo date, and post fixings in the interval, to e.g. 2017-01-30"
echo " scripts/irs-demo.sh date 2017-01-30"
fi

View File

@ -12,14 +12,14 @@ fi
if [[ "$mode" == "buyer" ]]; then
if [ ! -d buyer ]; then
mkdir buyer
echo "myLegalName = Bank of Zurich" >buyer/config
echo "myLegalName = Bank A" >buyer/config
fi
build/install/r3prototyping/bin/r3prototyping --dir=buyer --service-fake-trades --network-address=localhost
elif [[ "$mode" == "seller" ]]; then
if [ ! -d seller ]; then
mkdir seller
echo "myLegalName = Bank of London" >seller/config
echo "myLegalName = Bank B" >seller/config
fi
build/install/r3prototyping/bin/r3prototyping --dir=seller --fake-trade-with=localhost --network-address=localhost:31340 --timestamper-identity-file=buyer/identity-public --timestamper-address=localhost

View File

@ -1,6 +1,7 @@
package api
import com.google.common.util.concurrent.ListenableFuture
import contracts.DealState
import core.*
import core.crypto.DigitalSignature
import core.crypto.SecureHash
@ -27,7 +28,7 @@ class APIServerImpl(val node: AbstractNode): APIServer {
return states.values.map { it.ref }
}
else if (query.criteria is StatesQuery.Criteria.Deal) {
val states = node.services.walletService.linearHeadsInstanceOf(LinearState::class.java) {
val states = node.services.walletService.linearHeadsInstanceOf(DealState::class.java) {
it.ref == query.criteria.ref
}
return states.values.map { it.ref }
@ -73,6 +74,7 @@ class APIServerImpl(val node: AbstractNode): APIServer {
} else if (args.containsKey(parameter.name)) {
val value = args[parameter.name]
if (value is Any) {
// TODO consider supporting more complex test here to support coercing numeric/Kotlin types
if (!(parameter.type.javaType as Class<*>).isAssignableFrom(value.javaClass)) {
// Not null and not assignable
break@nextConstructor

View File

@ -1,21 +1,8 @@
package api
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers
import com.fasterxml.jackson.databind.deser.std.StringArrayDeserializer
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.module.kotlin.KotlinModule
import core.BusinessCalendar
import core.Party
import core.crypto.SecureHash
import com.fasterxml.jackson.databind.ObjectMapper
import core.node.services.ServiceHub
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalDateTime
import core.utilities.JsonSupport
import javax.ws.rs.ext.ContextResolver
import javax.ws.rs.ext.Provider
@ -25,119 +12,6 @@ import javax.ws.rs.ext.Provider
*/
@Provider
class Config(val services: ServiceHub): ContextResolver<ObjectMapper> {
val defaultObjectMapper = createDefaultMapper(services)
override fun getContext(type: java.lang.Class<*>): ObjectMapper {
return defaultObjectMapper
}
class ServiceHubObjectMapper(var serviceHub: ServiceHub): ObjectMapper() {
}
companion object {
private fun createDefaultMapper(services: ServiceHub): ObjectMapper {
val mapper = ServiceHubObjectMapper(services)
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
val timeModule = SimpleModule("java.time")
timeModule.addSerializer(LocalDate::class.java, ToStringSerializer)
timeModule.addDeserializer(LocalDate::class.java, LocalDateDeserializer)
timeModule.addKeyDeserializer(LocalDate::class.java, LocalDateKeyDeserializer)
timeModule.addSerializer(LocalDateTime::class.java, ToStringSerializer)
val cordaModule = SimpleModule("core")
cordaModule.addSerializer(Party::class.java, PartySerializer)
cordaModule.addDeserializer(Party::class.java, PartyDeserializer)
cordaModule.addSerializer(BigDecimal::class.java, ToStringSerializer)
cordaModule.addDeserializer(BigDecimal::class.java, NumberDeserializers.BigDecimalDeserializer())
cordaModule.addSerializer(SecureHash::class.java, SecureHashSerializer)
// It's slightly remarkable, but apparently Jackson works out that this is the only possibility
// for a SecureHash at the moment and tries to use SHA256 directly even though we only give it SecureHash
cordaModule.addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
cordaModule.addDeserializer(BusinessCalendar::class.java, CalendarDeserializer)
mapper.registerModule(timeModule)
mapper.registerModule(cordaModule)
mapper.registerModule(KotlinModule())
return mapper
}
}
object ToStringSerializer: JsonSerializer<Any>() {
override fun serialize(obj: Any, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.toString())
}
}
object LocalDateDeserializer: JsonDeserializer<LocalDate>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): LocalDate {
return try {
LocalDate.parse(parser.text)
} catch (e: Exception) {
throw JsonParseException("Invalid LocalDate ${parser.text}: ${e.message}", parser.currentLocation)
}
}
}
object LocalDateKeyDeserializer: KeyDeserializer() {
override fun deserializeKey(text: String, p1: DeserializationContext): Any? {
return LocalDate.parse(text)
}
}
object PartySerializer: JsonSerializer<Party>() {
override fun serialize(obj: Party, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.name)
}
}
object PartyDeserializer: JsonDeserializer<Party>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): Party {
if(parser.currentToken == JsonToken.FIELD_NAME) {
parser.nextToken()
}
val mapper = parser.codec as ServiceHubObjectMapper
// TODO this needs to use some industry identifier(s) not just these human readable names
val nodeForPartyName = mapper.serviceHub.networkMapService.nodeForPartyName(parser.text) ?: throw JsonParseException("Could not find a Party with name: ${parser.text}", parser.currentLocation)
return nodeForPartyName.identity
}
}
object SecureHashSerializer: JsonSerializer<SecureHash>() {
override fun serialize(obj: SecureHash, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.toString())
}
}
/**
* Implemented as a class so that we can instantiate for T
*/
class SecureHashDeserializer<T : SecureHash>: JsonDeserializer<T>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): T {
if(parser.currentToken == JsonToken.FIELD_NAME) {
parser.nextToken()
}
return try {
return SecureHash.parse(parser.text) as T
} catch (e: Exception) {
throw JsonParseException("Invalid hash ${parser.text}: ${e.message}", parser.currentLocation)
}
}
}
object CalendarDeserializer: JsonDeserializer<BusinessCalendar>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): BusinessCalendar {
return try {
val array = StringArrayDeserializer.instance.deserialize(parser, context)
BusinessCalendar.getInstance(*array)
} catch (e: Exception) {
throw JsonParseException("Invalid calendar(s) ${parser.text}: ${e.message}", parser.currentLocation)
}
}
}
val defaultObjectMapper = JsonSupport.createDefaultMapper(services.identityService)
override fun getContext(type: java.lang.Class<*>) = defaultObjectMapper
}

View File

@ -0,0 +1,109 @@
package api
import contracts.InterestRateSwap
import core.utilities.loggerFor
import demos.protocols.AutoOfferProtocol
import demos.protocols.ExitServerProtocol
import demos.protocols.UpdateBusinessDayProtocol
import java.net.URI
import java.time.LocalDate
import javax.ws.rs.*
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
/**
* This provides a simplified API, currently for demonstration use only.
*
* It provides several JSON REST calls as follows:
*
* GET /api/irs/deals - returns an array of all deals tracked by the wallet of this node.
* GET /api/irs/deals/{ref} - return the deal referenced by the externally provided refence that was previously uploaded.
* POST /api/irs/deals - Payload is a JSON formatted [InterestRateSwap.State] create a new deal (includes an externally provided reference for use above).
*
* TODO: where we currently refer to singular external deal reference, of course this could easily be multiple identifiers e.g. CUSIP, ISIN.
*
* GET /api/irs/demodate - return the current date as viewed by the system in YYYY-MM-DD format.
* PUT /api/irs/demodate - put date in format YYYY-MM-DD to advance the current date as viewed by the system and
* simulate any associated business processing (currently fixing).
*
* TODO: replace simulated date advancement with business event based implementation
*
* PUT /api/irs/restart - (empty payload) cause the node to restart for API user emergency use in case any servers become unresponsive,
* or if the demodate or population of deals should be reset (will only work while persistence is disabled).
*/
@Path("irs")
class InterestRateSwapAPI(val api: APIServer) {
private val logger = loggerFor<InterestRateSwapAPI>()
private fun generateDealLink(deal: InterestRateSwap.State) = "/api/irs/deals/"+deal.common.tradeID
private fun getDealByRef(ref: String): InterestRateSwap.State? {
val states = api.queryStates(StatesQuery.selectDeal(ref))
return if (states.isEmpty()) null else {
val deals = api.fetchStates(states).values.map { it as InterestRateSwap.State}.filterNotNull()
return if(deals.isEmpty()) null else deals[0]
}
}
private fun getAllDeals(): Array<InterestRateSwap.State> {
val states = api.queryStates(StatesQuery.selectAllDeals())
val swaps = api.fetchStates(states).values.map { it as InterestRateSwap.State }.filterNotNull().toTypedArray()
return swaps
}
@GET
@Path("deals")
@Produces(MediaType.APPLICATION_JSON)
fun fetchDeals(): Array<InterestRateSwap.State> = getAllDeals()
@POST
@Path("deals")
@Consumes(MediaType.APPLICATION_JSON)
fun storeDeal(newDeal: InterestRateSwap.State): Response {
api.invokeProtocolSync(ProtocolClassRef(AutoOfferProtocol.Requester::class.java.name!!), mapOf("dealToBeOffered" to newDeal))
return Response.created(URI.create(generateDealLink(newDeal))).build()
}
@GET
@Path("deals/{ref}")
@Produces(MediaType.APPLICATION_JSON)
fun fetchDeal(@PathParam("ref") ref: String): Response {
val deal = getDealByRef(ref)
if (deal == null) {
return Response.status(Response.Status.NOT_FOUND).build()
} else {
return Response.ok().entity(deal).build()
}
}
@PUT
@Path("demodate")
@Consumes(MediaType.APPLICATION_JSON)
fun storeDemoDate(newDemoDate: LocalDate): Response {
val priorDemoDate = api.serverTime().toLocalDate()
// Can only move date forwards
if(newDemoDate.isAfter(priorDemoDate)) {
api.invokeProtocolSync(ProtocolClassRef(UpdateBusinessDayProtocol.Broadcast::class.java.name!!), mapOf("date" to newDemoDate))
return Response.ok().build()
}
val msg = "demodate is already $priorDemoDate and can only be updated with a later date"
logger.info("Attempt to set demodate to $newDemoDate but $msg")
return Response.status(Response.Status.CONFLICT).entity(msg).build()
}
@GET
@Path("demodate")
@Produces(MediaType.APPLICATION_JSON)
fun fetchDemoDate(): LocalDate {
return api.serverTime().toLocalDate()
}
@PUT
@Path("restart")
@Consumes(MediaType.APPLICATION_JSON)
fun exitServer(): Response {
api.invokeProtocolSync(ProtocolClassRef(ExitServerProtocol.Broadcast::class.java.name!!), mapOf("exitCode" to 83))
return Response.ok().build()
}
}

View File

@ -80,6 +80,8 @@ class StateMachineManager(val serviceHub: ServiceHub, val runInThread: Executor)
// checkpointing when unit tests are run inside Gradle. The right fix is probably to stop Quasar's
// bit-too-clever-for-its-own-good ThreadLocal serialisation trick. It already wasted far more time than it can
// ever recover.
//
// TODO: Remove this now that TLS serialisation is fixed.
val checkpointing: Boolean get() = !System.err.javaClass.name.contains("LinePerThreadBufferingOutputStream")
/** Returns a list of all state machines executing the given protocol logic at the top level (subprotocols do not count) */

View File

@ -34,6 +34,7 @@ import java.nio.file.FileAlreadyExistsException
import java.nio.file.Files
import java.nio.file.Path
import java.security.KeyPair
import java.security.PublicKey
import java.time.Clock
import java.util.*
import java.util.concurrent.Executors
@ -42,7 +43,7 @@ import java.util.concurrent.Executors
* A base node implementation that can be customised either for production (with real implementations that do real
* I/O), or a mock implementation suitable for unit test environments.
*/
abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration, val timestamperAddress: LegallyIdentifiableNode?, val platformClock: Clock) {
abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration, val timestamperAddress: NodeInfo?, val platformClock: Clock) {
companion object {
val PRIVATE_KEY_FILE_NAME = "identity-private-key"
val PUBLIC_IDENTITY_FILE_NAME = "identity-public"
@ -61,7 +62,7 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
val services = object : ServiceHub {
override val networkService: MessagingService get() = net
override val networkMapService: NetworkMapService = MockNetworkMapService()
override val networkMapCache: NetworkMapCache = MockNetworkMapCache()
override val storageService: StorageService get() = storage
override val walletService: WalletService get() = wallet
override val keyManagementService: KeyManagementService get() = keyManagement
@ -70,8 +71,8 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
override val clock: Clock get() = platformClock
}
val legallyIdentifiableAddress: LegallyIdentifiableNode by lazy {
LegallyIdentifiableNode(net.myAddress, storage.myLegalIdentity, findMyLocation())
val info: NodeInfo by lazy {
NodeInfo(net.myAddress, storage.myLegalIdentity, findMyLocation())
}
protected open fun findMyLocation(): PhysicalLocation? = CityDatabase[configuration.nearestCity]
@ -93,20 +94,9 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
smm = StateMachineManager(services, serverThread)
wallet = NodeWalletService(services)
keyManagement = E2ETestKeyManagementService()
makeInterestRateOracleService()
makeInterestRatesOracleService()
api = APIServerImpl(this)
// Insert a network map entry for the timestamper: this is all temp scaffolding and will go away. If we are
// given the details, the timestamping node is somewhere else. Otherwise, we do our own timestamping.
val tsid = if (timestamperAddress != null) {
inNodeTimestampingService = null
timestamperAddress
} else {
inNodeTimestampingService = NodeTimestamperService(net, storage.myLegalIdentity, storage.myLegalIdentityKey, platformClock)
LegallyIdentifiableNode(net.myAddress, storage.myLegalIdentity)
}
(services.networkMapService as MockNetworkMapService).timestampingNodes.add(tsid)
makeTimestampingService(timestamperAddress)
identity = makeIdentityService()
// This object doesn't need to be referenced from this class because it registers handlers on the network
@ -116,20 +106,50 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
return this
}
protected fun makeInterestRateOracleService() {
// Constructing the service registers message handlers that ensure the service won't be garbage collected.
private fun makeTimestampingService(timestamperAddress: NodeInfo?) {
// Insert a network map entry for the timestamper: this is all temp scaffolding and will go away. If we are
// given the details, the timestamping node is somewhere else. Otherwise, we do our own timestamping.
val tsid = if (timestamperAddress != null) {
inNodeTimestampingService = null
timestamperAddress
} else {
inNodeTimestampingService = NodeTimestamperService(net, storage.myLegalIdentity, storage.myLegalIdentityKey, platformClock)
NodeInfo(net.myAddress, storage.myLegalIdentity)
}
(services.networkMapCache as MockNetworkMapCache).timestampingNodes.add(tsid)
}
lateinit var interestRatesService: NodeInterestRates.Service
open protected fun makeInterestRatesOracleService() {
// TODO: Once the service has data, automatically register with the network map service (once built).
_servicesThatAcceptUploads += NodeInterestRates.Service(this)
interestRatesService = NodeInterestRates.Service(this)
_servicesThatAcceptUploads += interestRatesService
}
protected open fun makeIdentityService(): IdentityService {
// We don't have any identity infrastructure right now, so we just throw together the only two identities we
// know about: our own, and the identity of the remote timestamper node (if any).
val knownIdentities = if (timestamperAddress != null)
// We don't have any identity infrastructure right now, so we just throw together the only identities we
// know about: our own, the identity of the remote timestamper node (if any), plus whatever is in the
// network map.
//
// TODO: All this will be replaced soon enough.
val fixedIdentities = if (timestamperAddress != null)
listOf(storage.myLegalIdentity, timestamperAddress.identity)
else
listOf(storage.myLegalIdentity)
return FixedIdentityService(knownIdentities)
return object : IdentityService {
private val identities: List<Party> get() = fixedIdentities + services.networkMapCache.partyNodes.map { it.identity }
private val keyToParties: Map<PublicKey, Party> get() = identities.associateBy { it.owningKey }
private val nameToParties: Map<String, Party> get() = identities.associateBy { it.name }
override fun partyFromKey(key: PublicKey): Party? = keyToParties[key]
override fun partyFromName(name: String): Party? = nameToParties[name]
override fun toString(): String {
return identities.joinToString { it.name }
}
}
}
open fun stop() {

View File

@ -9,12 +9,13 @@
package core.node
import api.Config
import api.InterestRateSwapAPI
import api.ResponseFilter
import com.codahale.metrics.JmxReporter
import com.google.common.net.HostAndPort
import core.node.services.LegallyIdentifiableNode
import core.messaging.MessagingService
import core.node.services.ArtemisMessagingService
import core.node.services.NodeInfo
import core.node.servlets.AttachmentDownloadServlet
import core.node.servlets.DataUploadServlet
import core.utilities.loggerFor
@ -51,7 +52,7 @@ class ConfigurationException(message: String) : Exception(message)
* @param clock The clock used within the node and by all protocols etc
*/
class Node(dir: Path, val p2pAddr: HostAndPort, configuration: NodeConfiguration,
timestamperAddress: LegallyIdentifiableNode?,
timestamperAddress: NodeInfo?,
clock: Clock = Clock.systemUTC()) : AbstractNode(dir, configuration, timestamperAddress, clock) {
companion object {
/** The port that is used by default if none is specified. As you know, 31337 is the most elite number. */
@ -97,6 +98,7 @@ class Node(dir: Path, val p2pAddr: HostAndPort, configuration: NodeConfiguration
resourceConfig.register(Config(services))
resourceConfig.register(ResponseFilter())
resourceConfig.register(api)
resourceConfig.register(InterestRateSwapAPI(api))
// Give the app a slightly better name in JMX rather than a randomly generated one and enable JMX
resourceConfig.addProperties(mapOf(ServerProperties.APPLICATION_NAME to "node.api",
ServerProperties.MONITORING_STATISTICS_MBEANS_ENABLED to "true"))

View File

@ -8,14 +8,23 @@
package core.node.services
import core.node.services.IdentityService
import core.Party
import java.security.PublicKey
import javax.annotation.concurrent.ThreadSafe
/**
* Scaffolding: a dummy identity service that just expects to have identities loaded off disk or found elsewhere.
* This class allows the provided list of identities to be mutated after construction, so it takes the list lock
* when doing lookups and recalculates the mapping each time. The ability to change the list is used by the
* MockNetwork code.
*/
class FixedIdentityService(private val identities: List<Party>) : IdentityService {
private val keyToParties: Map<PublicKey, Party> get() = identities.associateBy { it.owningKey }
@ThreadSafe
class FixedIdentityService(val identities: List<Party>) : IdentityService {
private val keyToParties: Map<PublicKey, Party>
get() = synchronized(identities) { identities.associateBy { it.owningKey } }
private val nameToParties: Map<String, Party>
get() = synchronized(identities) { identities.associateBy { it.name } }
override fun partyFromKey(key: PublicKey): Party? = keyToParties[key]
override fun partyFromName(name: String): Party? = nameToParties[name]
}

View File

@ -13,9 +13,11 @@ import core.crypto.DummyPublicKey
import core.messaging.SingleMessageRecipient
import java.util.*
/** Info about a network node that has operated by some sort of verified identity. */
data class LegallyIdentifiableNode(val address: SingleMessageRecipient, val identity: Party,
val physicalLocation: PhysicalLocation? = null)
/**
* Info about a network node that acts on behalf of some sort of verified identity.
*/
data class NodeInfo(val address: SingleMessageRecipient, val identity: Party,
val physicalLocation: PhysicalLocation? = null)
/**
* A network map contains lists of nodes on the network along with information about their identity keys, services
@ -26,25 +28,27 @@ data class LegallyIdentifiableNode(val address: SingleMessageRecipient, val iden
*
* This interface assumes fast, synchronous access to an in-memory map.
*/
interface NetworkMapService {
val timestampingNodes: List<LegallyIdentifiableNode>
val ratesOracleNodes: List<LegallyIdentifiableNode>
val partyNodes: List<LegallyIdentifiableNode>
interface NetworkMapCache {
val timestampingNodes: List<NodeInfo>
val ratesOracleNodes: List<NodeInfo>
val partyNodes: List<NodeInfo>
val regulators: List<NodeInfo>
fun nodeForPartyName(name: String): LegallyIdentifiableNode? = partyNodes.singleOrNull { it.identity.name == name }
fun nodeForPartyName(name: String): NodeInfo? = partyNodes.singleOrNull { it.identity.name == name }
}
// TODO: Move this to the test tree once a real network map is implemented and this scaffolding is no longer needed.
class MockNetworkMapService : NetworkMapService {
class MockNetworkMapCache : NetworkMapCache {
data class MockAddress(val id: String): SingleMessageRecipient
override val timestampingNodes = Collections.synchronizedList(ArrayList<LegallyIdentifiableNode>())
override val ratesOracleNodes = Collections.synchronizedList(ArrayList<LegallyIdentifiableNode>())
override val partyNodes = Collections.synchronizedList(ArrayList<LegallyIdentifiableNode>())
override val timestampingNodes = Collections.synchronizedList(ArrayList<NodeInfo>())
override val ratesOracleNodes = Collections.synchronizedList(ArrayList<NodeInfo>())
override val partyNodes = Collections.synchronizedList(ArrayList<NodeInfo>())
override val regulators = Collections.synchronizedList(ArrayList<NodeInfo>())
init {
partyNodes.add(LegallyIdentifiableNode(MockAddress("excalibur:8080"), Party("Excalibur", DummyPublicKey("Excalibur"))))
partyNodes.add(LegallyIdentifiableNode(MockAddress("another:8080"), Party("ANOther", DummyPublicKey("ANOther"))))
partyNodes.add(NodeInfo(MockAddress("bankC:8080"), Party("Bank C", DummyPublicKey("Bank C"))))
partyNodes.add(NodeInfo(MockAddress("bankD:8080"), Party("Bank D", DummyPublicKey("Bank D"))))
}
}

View File

@ -43,16 +43,21 @@ object NodeInterestRates {
/** Parses a string of the form "LIBOR 16-March-2016 1M" into a [FixOf] */
fun parseFixOf(key: String): FixOf {
val (name, date, tenorString) = key.split(' ')
val words = key.split(' ')
val tenorString = words.last()
val date = words.dropLast(1).last()
val name = words.dropLast(2).joinToString(" ")
return FixOf(name, LocalDate.parse(date), Tenor(tenorString))
}
/** Parses lines containing fixes */
fun parseFile(s: String): Map<FixOf, Fix> {
val results = HashMap<FixOf, Fix>()
fun parseFile(s: String): Map<FixOf, TreeMap<LocalDate,Fix>> {
val results = HashMap<FixOf, TreeMap<LocalDate,Fix>>()
for (line in s.lines()) {
val (fixOf, fix) = parseOneRate(line.trim())
results[fixOf] = fix
val (fixOf, fix) = parseOneRate(line)
val genericKey = FixOf(fixOf.name, LocalDate.MIN, fixOf.ofTenor)
val existingMap = results.computeIfAbsent(genericKey, { TreeMap() })
existingMap[fixOf.forDay] = fix
}
return results
}
@ -91,14 +96,13 @@ object NodeInterestRates {
override val acceptableFileExtensions = listOf(".rates", ".txt")
override fun upload(data: InputStream): String {
val fixes: Map<FixOf, Fix> = data.
val fixes: Map<FixOf, TreeMap<LocalDate,Fix>> = parseFile(data.
bufferedReader().
readLines().
map { it.trim() }.
// Filter out comment and empty lines.
filterNot { it.startsWith("#") || it.isBlank() }.
map { parseOneRate(it) }.
associate { it.first to it.second }
joinToString("\n"))
// TODO: Save the uploaded fixes to the storage service and reload on construction.
@ -106,12 +110,16 @@ object NodeInterestRates {
// the pointer to the stack before working with the map.
oracle.knownFixes = fixes
return "Accepted ${fixes.size} new interest rate fixes"
val sumOfFixes = fixes.map { it.value.size }.sum()
return "Accepted $sumOfFixes new interest rate fixes"
}
}
/**
* An implementation of an interest rate fix oracle which is given data in a simple string format.
*
* NOTE the implementation has changed such that it will find the nearest dated entry on OR BEFORE the date
* requested so as not to need to populate every possible date in the flat file that (typically) feeds this service
*/
@ThreadSafe
class Oracle(val identity: Party, private val signingKey: KeyPair) {
@ -119,8 +127,13 @@ object NodeInterestRates {
require(signingKey.public == identity.owningKey)
}
/** The fix data being served by this oracle. */
@Transient var knownFixes = emptyMap<FixOf, Fix>()
/** The fix data being served by this oracle.
*
* This is now a map of FixOf (but with date set to LocalDate.MIN, so just index name and tenor)
* to a sorted map of LocalDate to Fix, allowing for approximate date finding so that we do not need
* to populate the file with a rate for every day.
*/
@Volatile var knownFixes = emptyMap<FixOf, TreeMap<LocalDate,Fix>>()
set(value) {
require(value.isNotEmpty())
field = value
@ -130,13 +143,24 @@ object NodeInterestRates {
require(queries.isNotEmpty())
val knownFixes = knownFixes // Snapshot
val answers: List<Fix?> = queries.map { knownFixes[it] }
val answers: List<Fix?> = queries.map { getKnownFix(knownFixes, it) }
val firstNull = answers.indexOf(null)
if (firstNull != -1)
throw UnknownFix(queries[firstNull])
return answers.filterNotNull()
}
private fun getKnownFix(knownFixes: Map<FixOf, TreeMap<LocalDate, Fix>>, fixOf: FixOf): Fix? {
val rates = knownFixes[FixOf(fixOf.name, LocalDate.MIN, fixOf.ofTenor)]
// Greatest key less than or equal to the date we're looking for
val floor = rates?.floorEntry(fixOf.forDay)?.value
return if (floor!=null) {
Fix(fixOf, floor.value)
} else {
null
}
}
fun sign(wtx: WireTransaction): DigitalSignature.LegallyIdentifiable {
// Extract the fix commands marked as being signable by us.
val fixes: List<Fix> = wtx.commands.
@ -150,7 +174,7 @@ object NodeInterestRates {
// For each fix, verify that the data is correct.
val knownFixes = knownFixes // Snapshot
for (fix in fixes) {
val known = knownFixes[fix.of]
val known = getKnownFix(knownFixes, fix.of)
if (known == null || known != fix)
throw UnknownFix(fix.of)
}

View File

@ -13,7 +13,6 @@ import contracts.Cash
import core.*
import core.crypto.SecureHash
import core.messaging.MessagingService
import core.node.services.NetworkMapService
import java.io.InputStream
import java.security.KeyPair
import java.security.PrivateKey
@ -73,8 +72,8 @@ interface WalletService {
*/
val linearHeads: Map<SecureHash, StateAndRef<LinearState>>
fun <T : LinearState> linearHeadsInstanceOf(clazz: Class<T>, predicate: (T) -> Boolean = { true } ): Map<SecureHash, StateAndRef<LinearState>> {
return linearHeads.filterValues { clazz.isInstance(it.state) }.filterValues { predicate(it.state as T) }
fun <T : LinearState> linearHeadsInstanceOf(clazz: Class<T>, predicate: (T) -> Boolean = { true } ): Map<SecureHash, StateAndRef<T>> {
return linearHeads.filterValues { clazz.isInstance(it.state) }.filterValues { predicate(it.state as T) }.mapValues { StateAndRef(it.value.state as T, it.value.ref) }
}
fun statesForRefs(refs: List<StateRef>): Map<StateRef, ContractState?> {
@ -96,6 +95,18 @@ interface WalletService {
fun notify(tx: WireTransaction): Wallet = notifyAll(listOf(tx))
}
// TODO: Document this
@Suppress("UNCHECKED_CAST")
inline fun <reified T : LinearState> WalletService.linearHeadsOfType(): Map<SecureHash, StateAndRef<T>> {
return linearHeads.mapNotNull {
val s = it.value.state
if (s is T)
Pair(it.key, it.value as StateAndRef<T>)
else
null
}.toMap()
}
/**
* The KMS is responsible for storing and using private keys to sign things. An implementation of this may, for example,
* call out to a hardware security module that enforces various auditing and frequency-of-use requirements.
@ -156,7 +167,7 @@ interface ServiceHub {
val identityService: IdentityService
val storageService: StorageService
val networkService: MessagingService
val networkMapService: NetworkMapService
val networkMapCache: NetworkMapCache
val monitoringService: MonitoringService
val clock: Clock

View File

@ -48,7 +48,10 @@ abstract class ProtocolLogic<T> {
return psm.sendAndReceive(topic, destination, sessionIDForSend, sessionIDForReceive, obj, T::class.java)
}
inline fun <reified T : Any> receive(topic: String, sessionIDForReceive: Long): UntrustworthyData<T> {
return psm.receive(topic, sessionIDForReceive, T::class.java)
return receive(topic, sessionIDForReceive, T::class.java)
}
@Suspendable fun <T : Any> receive(topic: String, sessionIDForReceive: Long, clazz: Class<T>): UntrustworthyData<T> {
return psm.receive(topic, sessionIDForReceive, clazz)
}
@Suspendable fun send(topic: String, destination: MessageRecipients, sessionID: Long, obj: Any) {
psm.send(topic, destination, sessionID, obj)

View File

@ -0,0 +1,141 @@
/*
* Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
* pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
* set forth therein.
*
* All other rights reserved.
*/
package core.testing
import com.fasterxml.jackson.module.kotlin.readValue
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import contracts.InterestRateSwap
import core.*
import core.crypto.SecureHash
import core.node.services.FixedIdentityService
import core.node.services.linearHeadsOfType
import core.utilities.JsonSupport
import protocols.TwoPartyDealProtocol
import java.time.LocalDate
import java.util.*
/**
* A simulation in which banks execute interest rate swaps with each other, including the fixing events.
*/
class IRSSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork.LatencyCalculator?) : Simulation(runAsync, latencyInjector) {
val om = JsonSupport.createDefaultMapper(FixedIdentityService(network.identities))
init {
currentDay = LocalDate.of(2016, 3, 10) // Should be 12th but the actual first fixing date gets rolled backwards.
}
private val executeOnNextIteration = Collections.synchronizedList(LinkedList<() -> Unit>())
override fun start() {
startIRSDealBetween(0, 1).success {
// Next iteration is a pause.
executeOnNextIteration.add {}
executeOnNextIteration.add {
// Keep fixing until there's no more left to do.
doNextFixing(0, 1)?.addListener(object : Runnable {
override fun run() {
// Pause for an iteration.
executeOnNextIteration.add {}
executeOnNextIteration.add {
doNextFixing(0, 1)?.addListener(this, RunOnCallerThread)
}
}
}, RunOnCallerThread)
}
}
}
private fun doNextFixing(i: Int, j: Int): ListenableFuture<*>? {
println("Doing a fixing between $i and $j")
val node1: SimulatedNode = banks[i]
val node2: SimulatedNode = banks[j]
val sessionID = random63BitValue()
val swaps: Map<SecureHash, StateAndRef<InterestRateSwap.State>> = node1.services.walletService.linearHeadsOfType<InterestRateSwap.State>()
val theDealRef: StateAndRef<InterestRateSwap.State> = swaps.values.single()
// Do we have any more days left in this deal's lifetime? If not, return.
val nextFixingDate = theDealRef.state.calculation.nextFixingDate() ?: return null
extraNodeLabels[node1] = "Fixing event on $nextFixingDate"
extraNodeLabels[node2] = "Fixing event on $nextFixingDate"
// For some reason the first fix is always before the effective date.
if (nextFixingDate > currentDay)
currentDay = nextFixingDate
val sideA = TwoPartyDealProtocol.Floater(node2.net.myAddress, sessionID, timestamper.info,
theDealRef, node1.services.keyManagementService.freshKey(), sessionID)
val sideB = TwoPartyDealProtocol.Fixer(node1.net.myAddress, timestamper.info.identity,
theDealRef, sessionID)
linkConsensus(listOf(node1, node2, regulators[0]), sideB)
linkProtocolProgress(node1, sideA)
linkProtocolProgress(node2, sideB)
// We have to start the protocols in separate iterations, as adding to the SMM effectively 'iterates' that node
// in the simulation, so if we don't do this then the two sides seem to act simultaneously.
val retFuture = SettableFuture.create<Any>()
val futA = node1.smm.add("floater", sideA)
executeOnNextIteration += {
val futB = node2.smm.add("fixer", sideB)
Futures.allAsList(futA, futB).then {
retFuture.set(null)
}
}
return retFuture
}
private fun startIRSDealBetween(i: Int, j: Int): ListenableFuture<SignedTransaction> {
val node1: SimulatedNode = banks[i]
val node2: SimulatedNode = banks[j]
extraNodeLabels[node1] = "Setting up deal"
extraNodeLabels[node2] = "Setting up deal"
// We load the IRS afresh each time because the leg parts of the structure aren't data classes so they don't
// have the convenient copy() method that'd let us make small adjustments. Instead they're partly mutable.
// TODO: We should revisit this in post-Excalibur cleanup and fix, e.g. by introducing an interface.
val irs = om.readValue<InterestRateSwap.State>(javaClass.getResource("trade.json"))
irs.fixedLeg.fixedRatePayer = node1.info.identity
irs.floatingLeg.floatingRatePayer = node2.info.identity
if (irs.fixedLeg.effectiveDate < irs.floatingLeg.effectiveDate)
currentDay = irs.fixedLeg.effectiveDate
else
currentDay = irs.floatingLeg.effectiveDate
val sessionID = random63BitValue()
val instigator = TwoPartyDealProtocol.Instigator(node2.net.myAddress, timestamper.info,
irs, node1.services.keyManagementService.freshKey(), sessionID)
val acceptor = TwoPartyDealProtocol.Acceptor(node1.net.myAddress, timestamper.info.identity,
irs, sessionID)
// TODO: Eliminate the need for linkProtocolProgress
linkConsensus(listOf(node1, node2, regulators[0]), acceptor)
linkProtocolProgress(node1, instigator)
linkProtocolProgress(node2, acceptor)
val instigatorFuture: ListenableFuture<SignedTransaction> = node1.smm.add("instigator", instigator)
return Futures.transformAsync(Futures.allAsList(instigatorFuture, node2.smm.add("acceptor", acceptor))) {
instigatorFuture
}
}
override fun iterate() {
if (executeOnNextIteration.isNotEmpty())
executeOnNextIteration.removeAt(0)()
super.iterate()
}
}

View File

@ -15,7 +15,7 @@ import core.ThreadBox
import core.crypto.sha256
import core.messaging.*
import core.node.services.DummyTimestampingAuthority
import core.node.services.LegallyIdentifiableNode
import core.node.services.NodeInfo
import core.node.services.NodeTimestamperService
import core.utilities.loggerFor
import rx.Observable
@ -150,15 +150,15 @@ class InMemoryMessagingNetwork {
override fun hashCode() = id.hashCode()
}
private var timestampingAdvert: LegallyIdentifiableNode? = null
private var timestampingAdvert: NodeInfo? = null
@Synchronized
fun setupTimestampingNode(manuallyPumped: Boolean): Pair<LegallyIdentifiableNode, InMemoryMessaging> {
fun setupTimestampingNode(manuallyPumped: Boolean): Pair<NodeInfo, InMemoryMessaging> {
check(timestampingAdvert == null)
val (handle, builder) = createNode(manuallyPumped)
val node = builder.start().get()
NodeTimestamperService(node, DummyTimestampingAuthority.identity, DummyTimestampingAuthority.key)
timestampingAdvert = LegallyIdentifiableNode(handle, DummyTimestampingAuthority.identity)
timestampingAdvert = NodeInfo(handle, DummyTimestampingAuthority.identity)
return Pair(timestampingAdvert!!, node)
}

View File

@ -16,7 +16,7 @@ import core.messaging.SingleMessageRecipient
import core.node.AbstractNode
import core.node.NodeConfiguration
import core.node.services.FixedIdentityService
import core.node.services.LegallyIdentifiableNode
import core.node.services.NodeInfo
import core.node.services.PhysicalLocation
import core.utilities.loggerFor
import org.slf4j.Logger
@ -54,18 +54,18 @@ class MockNetwork(private val threadPerNode: Boolean = false,
/** Allows customisation of how nodes are created. */
interface Factory {
fun create(dir: Path, config: NodeConfiguration, network: MockNetwork,
timestamperAddr: LegallyIdentifiableNode?): MockNode
timestamperAddr: NodeInfo?): MockNode
}
object DefaultFactory : Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork,
timestamperAddr: LegallyIdentifiableNode?): MockNode {
timestamperAddr: NodeInfo?): MockNode {
return MockNode(dir, config, network, timestamperAddr)
}
}
open class MockNode(dir: Path, config: NodeConfiguration, val mockNet: MockNetwork,
withTimestamper: LegallyIdentifiableNode?, val forcedID: Int = -1) : AbstractNode(dir, config, withTimestamper, Clock.systemUTC()) {
withTimestamper: NodeInfo?, val forcedID: Int = -1) : AbstractNode(dir, config, withTimestamper, Clock.systemUTC()) {
override val log: Logger = loggerFor<MockNode>()
override val serverThread: ExecutorService =
if (mockNet.threadPerNode)
@ -93,10 +93,12 @@ class MockNetwork(private val threadPerNode: Boolean = false,
mockNet.identities.add(storage.myLegalIdentity)
return this
}
val place: PhysicalLocation get() = info.physicalLocation!!
}
/** Returns a started node, optionally created by the passed factory method */
fun createNode(withTimestamper: LegallyIdentifiableNode?, forcedID: Int = -1, nodeFactory: Factory = defaultFactory): MockNode {
fun createNode(withTimestamper: NodeInfo?, forcedID: Int = -1, nodeFactory: Factory = defaultFactory): MockNode {
val newNode = forcedID == -1
val id = if (newNode) counter++ else forcedID
@ -132,7 +134,7 @@ class MockNetwork(private val threadPerNode: Boolean = false,
*/
fun createTwoNodes(nodeFactory: Factory = defaultFactory): Pair<MockNode, MockNode> {
require(nodes.isEmpty())
return Pair(createNode(null, -1, nodeFactory), createNode(nodes[0].legallyIdentifiableAddress, -1, nodeFactory))
return Pair(createNode(null, -1, nodeFactory), createNode(nodes[0].info, -1, nodeFactory))
}
fun addressToNode(address: SingleMessageRecipient): MockNode = nodes.single { it.net.myAddress == address }

View File

@ -0,0 +1,213 @@
/*
* Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
* pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
* set forth therein.
*
* All other rights reserved.
*/
package core.testing
import com.google.common.util.concurrent.ListenableFuture
import core.node.NodeConfiguration
import core.node.services.CityDatabase
import core.node.services.MockNetworkMapCache
import core.node.services.NodeInfo
import core.node.services.PhysicalLocation
import core.protocols.ProtocolLogic
import core.then
import core.utilities.ProgressTracker
import rx.Observable
import rx.subjects.PublishSubject
import java.nio.file.Path
import java.time.LocalDate
import java.util.*
/**
* Base class for network simulations that are based on the unit test / mock environment.
*
* Sets up some nodes that can run protocols between each other, and exposes their progress trackers. Provides banks
* in a few cities around the world.
*/
abstract class Simulation(val runAsync: Boolean,
val latencyInjector: InMemoryMessagingNetwork.LatencyCalculator?) {
init {
if (!runAsync && latencyInjector != null)
throw IllegalArgumentException("The latency injector is only useful when using manual pumping.")
}
val bankLocations = listOf("London", "Frankfurt", "Rome")
// This puts together a mock network of SimulatedNodes.
open class SimulatedNode(dir: Path, config: NodeConfiguration, mockNet: MockNetwork,
withTimestamper: NodeInfo?) : MockNetwork.MockNode(dir, config, mockNet, withTimestamper) {
override fun findMyLocation(): PhysicalLocation? = CityDatabase[configuration.nearestCity]
}
inner class BankFactory : MockNetwork.Factory {
var counter = 0
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode {
val letter = 'A' + counter
val city = bankLocations[counter++ % bankLocations.size]
val cfg = object : NodeConfiguration {
// TODO: Set this back to "Bank of $city" after video day.
override val myLegalName: String = "Bank $letter"
override val exportJMXto: String = ""
override val nearestCity: String = city
}
val node = SimulatedNode(dir, cfg, network, timestamperAddr)
// TODO: This is obviously bogus: there should be a single network map for the whole simulated network.
(node.services.networkMapCache as MockNetworkMapCache).ratesOracleNodes += ratesOracle.info
(node.services.networkMapCache as MockNetworkMapCache).regulators += regulators.map { it.info }
return node
}
fun createAll(): List<SimulatedNode> = bankLocations.map { network.createNode(timestamper.info, nodeFactory = this) as SimulatedNode }
}
val bankFactory = BankFactory()
object TimestampingNodeFactory : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode {
val cfg = object : NodeConfiguration {
override val myLegalName: String = "Timestamping Service" // A magic string recognised by the CP contract
override val exportJMXto: String = ""
override val nearestCity: String = "Zurich"
}
return SimulatedNode(dir, cfg, network, timestamperAddr)
}
}
object RatesOracleFactory : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode {
val cfg = object : NodeConfiguration {
override val myLegalName: String = "Rates Service Provider"
override val exportJMXto: String = ""
override val nearestCity: String = "Madrid"
}
val n = object : SimulatedNode(dir, cfg, network, timestamperAddr) {
override fun makeInterestRatesOracleService() {
super.makeInterestRatesOracleService()
interestRatesService.upload(javaClass.getResourceAsStream("example.rates.txt"))
(services.networkMapCache as MockNetworkMapCache).ratesOracleNodes += info
}
}
return n
}
}
object RegulatorFactory : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode {
val cfg = object : NodeConfiguration {
override val myLegalName: String = "Regulator A"
override val exportJMXto: String = ""
override val nearestCity: String = "Paris"
}
val n = object : SimulatedNode(dir, cfg, network, timestamperAddr) {
// TODO: Regulatory nodes don't actually exist properly, this is a last minute demo request.
// So we just fire a message at a node that doesn't know how to handle it, and it'll ignore it.
// But that's fine for visualisation purposes.
}
return n
}
}
val network = MockNetwork(false)
val timestamper: SimulatedNode = network.createNode(null, nodeFactory = TimestampingNodeFactory) as SimulatedNode
val ratesOracle: SimulatedNode = network.createNode(null, nodeFactory = RatesOracleFactory) as SimulatedNode
val serviceProviders: List<SimulatedNode> = listOf(timestamper, ratesOracle)
val banks: List<SimulatedNode> = bankFactory.createAll()
val regulators: List<SimulatedNode> = listOf(network.createNode(null, nodeFactory = RegulatorFactory) as SimulatedNode)
private val _allProtocolSteps = PublishSubject.create<Pair<SimulatedNode, ProgressTracker.Change>>()
private val _doneSteps = PublishSubject.create<Collection<SimulatedNode>>()
val allProtocolSteps: Observable<Pair<SimulatedNode, ProgressTracker.Change>> = _allProtocolSteps
val doneSteps: Observable<Collection<SimulatedNode>> = _doneSteps
private var pumpCursor = 0
/**
* The current simulated date. By default this never changes. If you want it to change, you should do so from
* within your overridden [iterate] call. Changes in the current day surface in the [dateChanges] observable.
*/
var currentDay: LocalDate = LocalDate.now()
protected set(value) {
field = value
_dateChanges.onNext(value)
}
private val _dateChanges = PublishSubject.create<LocalDate>()
val dateChanges: Observable<LocalDate> = _dateChanges
/**
* A place for simulations to stash human meaningful text about what the node is "thinking", which might appear
* in the UI somewhere.
*/
val extraNodeLabels = Collections.synchronizedMap(HashMap<SimulatedNode, String>())
/**
* Iterates the simulation by one step.
*
* The default implementation circles around the nodes, pumping until one of them handles a message. The next call
* will carry on from where this one stopped. In an environment where you want to take actions between anything
* interesting happening, or control the precise speed at which things operate (beyond the latency injector), this
* is a useful way to do things.
*/
open fun iterate() {
// Keep going until one of the nodes has something to do, or we have checked every node.
val endpoints = network.messagingNetwork.endpoints
var countDown = endpoints.size
while (countDown > 0) {
val handledMessage = endpoints[pumpCursor].pump(false)
if (handledMessage) break
// If this node had nothing to do, advance the cursor with wraparound and try again.
pumpCursor = (pumpCursor + 1) % endpoints.size
countDown--
}
}
protected fun linkProtocolProgress(node: SimulatedNode, protocol: ProtocolLogic<*>) {
val pt = protocol.progressTracker ?: return
pt.changes.subscribe { change: ProgressTracker.Change ->
// Runs on node thread.
_allProtocolSteps.onNext(Pair(node, change))
}
// This isn't technically a "change" but it helps with UIs to send this notification of the first step.
_allProtocolSteps.onNext(Pair(node, ProgressTracker.Change.Position(pt, pt.steps[1])))
}
protected fun linkConsensus(nodes: Collection<SimulatedNode>, protocol: ProtocolLogic<*>) {
protocol.progressTracker?.changes?.subscribe { change: ProgressTracker.Change ->
// Runs on node thread.
if (protocol.progressTracker!!.currentStep == ProgressTracker.DONE) {
_doneSteps.onNext(nodes)
}
}
}
open fun start() {}
fun stop() {
network.nodes.forEach { it.stop() }
}
/**
* Given a function that returns a future, iterates that function with arguments like (0, 1), (1, 2), (2, 3) etc
* each time the returned future completes.
*/
fun startTradingCircle(tradeBetween: (indexA: Int, indexB: Int) -> ListenableFuture<*>) {
fun next(i: Int, j: Int) {
tradeBetween(i, j).then {
val ni = (i + 1) % banks.size
val nj = (j + 1) % banks.size
next(ni, nj)
}
}
next(0, 1)
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
* pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
* set forth therein.
*
* All other rights reserved.
*/
package core.testing
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import contracts.CommercialPaper
import core.*
import core.node.services.NodeWalletService
import core.utilities.BriefLogFormatter
import protocols.TwoPartyTradeProtocol
import java.time.Instant
/**
* Simulates a never ending series of trades that go pair-wise through the banks (e.g. A and B trade with each other,
* then B and C trade with each other, then C and A etc).
*/
class TradeSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork.LatencyCalculator?) : Simulation(runAsync, latencyInjector) {
override fun start() {
BriefLogFormatter.loggingOn("bank", "core.TransactionGroup", "recordingmap")
startTradingCircle { i, j -> tradeBetween(i, j) }
}
private fun tradeBetween(buyerBankIndex: Int, sellerBankIndex: Int): ListenableFuture<MutableList<SignedTransaction>> {
val buyer = banks[buyerBankIndex]
val seller = banks[sellerBankIndex]
(buyer.services.walletService as NodeWalletService).fillWithSomeTestCash(1500.DOLLARS)
val issuance = run {
val tx = CommercialPaper().generateIssue(seller.info.identity.ref(1, 2, 3), 1100.DOLLARS, Instant.now() + 10.days)
tx.setTime(Instant.now(), timestamper.info.identity, 30.seconds)
tx.signWith(timestamper.storage.myLegalIdentityKey)
tx.signWith(seller.storage.myLegalIdentityKey)
tx.toSignedTransaction(true)
}
seller.services.storageService.validatedTransactions[issuance.id] = issuance
val sessionID = random63BitValue()
val buyerProtocol = TwoPartyTradeProtocol.Buyer(seller.net.myAddress, timestamper.info.identity,
1000.DOLLARS, CommercialPaper.State::class.java, sessionID)
val sellerProtocol = TwoPartyTradeProtocol.Seller(buyer.net.myAddress, timestamper.info,
issuance.tx.outRef(0), 1000.DOLLARS, seller.storage.myLegalIdentityKey, sessionID)
linkConsensus(listOf(buyer, seller, timestamper), sellerProtocol)
linkProtocolProgress(buyer, buyerProtocol)
linkProtocolProgress(seller, sellerProtocol)
val buyerFuture = buyer.smm.add("bank.$buyerBankIndex.${TwoPartyTradeProtocol.TRADE_TOPIC}.buyer", buyerProtocol)
val sellerFuture = seller.smm.add("bank.$sellerBankIndex.${TwoPartyTradeProtocol.TRADE_TOPIC}.seller", sellerProtocol)
return Futures.successfulAsList(buyerFuture, sellerFuture)
}
}

View File

@ -0,0 +1,137 @@
/*
* Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
* pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
* set forth therein.
*
* All other rights reserved.
*/
package core.utilities
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers
import com.fasterxml.jackson.databind.deser.std.StringArrayDeserializer
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.module.kotlin.KotlinModule
import core.BusinessCalendar
import core.Party
import core.crypto.SecureHash
import core.node.services.IdentityService
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalDateTime
/**
* Utilities and serialisers for working with JSON representations of basic types. This adds Jackson support for
* the java.time API, some core types, and Kotlin data classes.
*/
object JsonSupport {
fun createDefaultMapper(identities: IdentityService): ObjectMapper {
val mapper = ServiceHubObjectMapper(identities)
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
val timeModule = SimpleModule("java.time")
timeModule.addSerializer(LocalDate::class.java, ToStringSerializer)
timeModule.addDeserializer(LocalDate::class.java, LocalDateDeserializer)
timeModule.addKeyDeserializer(LocalDate::class.java, LocalDateKeyDeserializer)
timeModule.addSerializer(LocalDateTime::class.java, ToStringSerializer)
val cordaModule = SimpleModule("core")
cordaModule.addSerializer(Party::class.java, PartySerializer)
cordaModule.addDeserializer(Party::class.java, PartyDeserializer)
cordaModule.addSerializer(BigDecimal::class.java, ToStringSerializer)
cordaModule.addDeserializer(BigDecimal::class.java, NumberDeserializers.BigDecimalDeserializer())
cordaModule.addSerializer(SecureHash::class.java, SecureHashSerializer)
// It's slightly remarkable, but apparently Jackson works out that this is the only possibility
// for a SecureHash at the moment and tries to use SHA256 directly even though we only give it SecureHash
cordaModule.addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
cordaModule.addDeserializer(BusinessCalendar::class.java, CalendarDeserializer)
mapper.registerModule(timeModule)
mapper.registerModule(cordaModule)
mapper.registerModule(KotlinModule())
return mapper
}
class ServiceHubObjectMapper(val identities: IdentityService): ObjectMapper()
object ToStringSerializer: JsonSerializer<Any>() {
override fun serialize(obj: Any, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.toString())
}
}
object LocalDateDeserializer: JsonDeserializer<LocalDate>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): LocalDate {
return try {
LocalDate.parse(parser.text)
} catch (e: Exception) {
throw JsonParseException("Invalid LocalDate ${parser.text}: ${e.message}", parser.currentLocation)
}
}
}
object LocalDateKeyDeserializer: KeyDeserializer() {
override fun deserializeKey(text: String, p1: DeserializationContext): Any? {
return LocalDate.parse(text)
}
}
object PartySerializer: JsonSerializer<Party>() {
override fun serialize(obj: Party, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.name)
}
}
object PartyDeserializer: JsonDeserializer<Party>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): Party {
if(parser.currentToken == JsonToken.FIELD_NAME) {
parser.nextToken()
}
val mapper = parser.codec as ServiceHubObjectMapper
// TODO this needs to use some industry identifier(s) not just these human readable names
return mapper.identities.partyFromName(parser.text) ?: throw JsonParseException("Could not find a Party with name: ${parser.text}", parser.currentLocation)
}
}
object SecureHashSerializer: JsonSerializer<SecureHash>() {
override fun serialize(obj: SecureHash, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.toString())
}
}
/**
* Implemented as a class so that we can instantiate for T
*/
class SecureHashDeserializer<T : SecureHash>: JsonDeserializer<T>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): T {
if(parser.currentToken == JsonToken.FIELD_NAME) {
parser.nextToken()
}
try {
@Suppress("UNCHECKED_CAST")
return SecureHash.parse(parser.text) as T
} catch (e: Exception) {
throw JsonParseException("Invalid hash ${parser.text}: ${e.message}", parser.currentLocation)
}
}
}
object CalendarDeserializer: JsonDeserializer<BusinessCalendar>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): BusinessCalendar {
return try {
val array = StringArrayDeserializer.instance.deserialize(parser, context)
BusinessCalendar.getInstance(*array)
} catch (e: Exception) {
throw JsonParseException("Invalid calendar(s) ${parser.text}: ${e.message}", parser.currentLocation)
}
}
}
}

View File

@ -0,0 +1,182 @@
/*
* Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
* pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
* set forth therein.
*
* All other rights reserved.
*/
package demos
import com.google.common.net.HostAndPort
import com.typesafe.config.ConfigFactory
import core.Party
import core.logElapsedTime
import core.node.Node
import core.node.NodeConfiguration
import core.node.NodeConfigurationFromConfig
import core.node.services.ArtemisMessagingService
import core.node.services.NodeInfo
import core.node.services.MockNetworkMapCache
import core.serialization.deserialize
import core.utilities.BriefLogFormatter
import joptsimple.OptionParser
import demos.protocols.AutoOfferProtocol
import demos.protocols.ExitServerProtocol
import demos.protocols.UpdateBusinessDayProtocol
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.system.exitProcess
// IRS DEMO
//
// TODO: Please see TBD
fun main(args: Array<String>) {
val parser = OptionParser()
val networkAddressArg = parser.accepts("network-address").withRequiredArg().required()
val dirArg = parser.accepts("directory").withRequiredArg().defaultsTo("nodedata")
// Temporary flags until network map and service discovery is fleshed out. The identity file does NOT contain the
// network address because all this stuff is meant to come from a dynamic discovery service anyway, and the identity
// is meant to be long-term stable. It could contain a domain name, but we may end up not routing messages directly
// to DNS-identified endpoints anyway (e.g. consider onion routing as a possibility).
val timestamperIdentityFile = parser.accepts("timestamper-identity-file").withRequiredArg().required()
val timestamperNetAddr = parser.accepts("timestamper-address").requiredIf(timestamperIdentityFile).withRequiredArg()
val rateOracleIdentityFile = parser.accepts("rates-oracle-identity-file").withRequiredArg().required()
val rateOracleNetAddr = parser.accepts("rates-oracle-address").requiredIf(rateOracleIdentityFile).withRequiredArg()
// Use these to list one or more peers (again, will be superseded by discovery implementation)
val fakeTradeWithAddr = parser.accepts("fake-trade-with-address").withRequiredArg().required()
val fakeTradeWithIdentityFile = parser.accepts("fake-trade-with-identity-file").withRequiredArg().required()
val options = try {
parser.parse(*args)
} catch (e: Exception) {
println(e.message)
printHelp()
exitProcess(1)
}
// Suppress the Artemis MQ noise, and activate the demo logging.
BriefLogFormatter.initVerbose("+demo.irsdemo", "-org.apache.activemq")
val dir = Paths.get(options.valueOf(dirArg))
val configFile = dir.resolve("config")
if (!Files.exists(dir)) {
Files.createDirectory(dir)
}
val config = loadConfigFile(configFile)
val myNetAddr = HostAndPort.fromString(options.valueOf(networkAddressArg)).withDefaultPort(Node.DEFAULT_PORT)
// The timestamping node runs in the same process as the one that passes null to Node constructor.
val timestamperId = if(options.valueOf(timestamperNetAddr).equals(options.valueOf(networkAddressArg))) {
null
} else {
try {
nodeInfo(options.valueOf(timestamperNetAddr), options.valueOf(timestamperIdentityFile))
} catch (e: Exception) {
null
}
}
// The timestamping node runs in the same process as the one that passes null to Node constructor.
val rateOracleId = if(options.valueOf(rateOracleNetAddr).equals(options.valueOf(networkAddressArg))) {
null
} else {
try {
nodeInfo(options.valueOf(rateOracleNetAddr), options.valueOf(rateOracleIdentityFile))
} catch (e: Exception) {
null
}
}
val node = logElapsedTime("Node startup") { Node(dir, myNetAddr, config, timestamperId, DemoClock()).start() }
// Add self to network map
(node.services.networkMapCache as MockNetworkMapCache).partyNodes.add(node.info)
// Add rates oracle to network map
(node.services.networkMapCache as MockNetworkMapCache).ratesOracleNodes.add(rateOracleId)
val hostAndPortStrings = options.valuesOf(fakeTradeWithAddr)
val identityFiles = options.valuesOf(fakeTradeWithIdentityFile)
if(hostAndPortStrings.size != identityFiles.size) {
throw IllegalArgumentException("Different number of peer addresses (${hostAndPortStrings.size}) and identities (${identityFiles.size})")
}
for ((hostAndPortString,identityFile) in hostAndPortStrings.zip(identityFiles)) {
try {
val peerId = nodeInfo(hostAndPortString, identityFile)
(node.services.networkMapCache as MockNetworkMapCache).partyNodes.add(peerId)
} catch (e: Exception) {
}
}
// Register handlers for the demo
AutoOfferProtocol.Handler.register(node)
UpdateBusinessDayProtocol.Handler.register(node)
ExitServerProtocol.Handler.register(node)
while(true) {
Thread.sleep(1000L)
}
exitProcess(0)
}
fun nodeInfo(hostAndPortString: String, identityFile: String): NodeInfo {
try {
val addr = HostAndPort.fromString(hostAndPortString).withDefaultPort(Node.DEFAULT_PORT)
val path = Paths.get(identityFile)
val party = Files.readAllBytes(path).deserialize<Party>(includeClassName = true)
return NodeInfo(ArtemisMessagingService.makeRecipient(addr), party)
} catch (e: Exception) {
println("Could not find identify file $identityFile. If the file has just been created as part of starting the demo, please restart this node")
throw e
}
}
private fun loadConfigFile(configFile: Path): NodeConfiguration {
fun askAdminToEditConfig(configFile: Path?) {
println()
println("This is the first run, so you should edit the config file in $configFile and then start the node again.")
println()
exitProcess(1)
}
val defaultLegalName = "Global MegaCorp, Ltd."
if (!Files.exists(configFile)) {
createDefaultConfigFile(configFile, defaultLegalName)
askAdminToEditConfig(configFile)
}
System.setProperty("config.file", configFile.toAbsolutePath().toString())
val config = NodeConfigurationFromConfig(ConfigFactory.load())
// Make sure admin did actually edit at least the legal name.
if (config.myLegalName == defaultLegalName)
askAdminToEditConfig(configFile)
return config
}
private fun createDefaultConfigFile(configFile: Path?, defaultLegalName: String) {
Files.write(configFile,
"""
# Node configuration: give the buyer node the name 'Bank of Zurich' (no quotes)
# The seller node can be named whatever you like.
myLegalName = $defaultLegalName
""".trimIndent().toByteArray())
}
private fun printHelp() {
println("""
Please refer to the documentation that doesn't yet exist to learn how to run the demo.
""".trimIndent())
}

View File

@ -13,7 +13,7 @@ import core.*
import core.node.Node
import core.node.NodeConfiguration
import core.node.services.ArtemisMessagingService
import core.node.services.LegallyIdentifiableNode
import core.node.services.NodeInfo
import core.node.services.NodeInterestRates
import core.serialization.deserialize
import core.utilities.ANSIProgressRenderer
@ -60,7 +60,7 @@ fun main(args: Array<String>) {
// Load oracle stuff (in lieu of having a network map service)
val oracleAddr = ArtemisMessagingService.makeRecipient(options.valueOf(oracleAddrArg))
val oracleIdentity = Files.readAllBytes(Paths.get(options.valueOf(oracleIdentityArg))).deserialize<Party>(includeClassName = true)
val oracleNode = LegallyIdentifiableNode(oracleAddr, oracleIdentity)
val oracleNode = NodeInfo(oracleAddr, oracleIdentity)
val fixOf: FixOf = NodeInterestRates.parseFixOf(options.valueOf(fixOfArg))
val expectedRate = BigDecimal(options.valueOf(expectedRateArg))

View File

@ -20,7 +20,7 @@ import core.node.Node
import core.node.NodeConfiguration
import core.node.NodeConfigurationFromConfig
import core.node.services.ArtemisMessagingService
import core.node.services.LegallyIdentifiableNode
import core.node.services.NodeInfo
import core.node.services.NodeAttachmentService
import core.node.services.NodeWalletService
import core.protocols.ProtocolLogic
@ -85,8 +85,8 @@ fun main(args: Array<String>) {
val myNetAddr = HostAndPort.fromString(options.valueOf(networkAddressArg)).withDefaultPort(Node.DEFAULT_PORT)
val listening = options.has(serviceFakeTradesArg)
if (listening && config.myLegalName != "Bank of Zurich") {
println("The buyer node must have a legal name of 'Bank of Zurich'. Please edit the config file.")
if (listening && config.myLegalName != "Bank A") {
println("The buyer node must have a legal name of 'Bank A'. Please edit the config file.")
exitProcess(1)
}
@ -95,7 +95,7 @@ fun main(args: Array<String>) {
val addr = HostAndPort.fromString(options.valueOf(timestamperNetAddr)).withDefaultPort(Node.DEFAULT_PORT)
val path = Paths.get(options.valueOf(timestamperIdentityFile))
val party = Files.readAllBytes(path).deserialize<Party>(includeClassName = true)
LegallyIdentifiableNode(ArtemisMessagingService.makeRecipient(addr), party)
NodeInfo(ArtemisMessagingService.makeRecipient(addr), party)
} else null
val node = logElapsedTime("Node startup") { Node(dir, myNetAddr, config, timestamperId).start() }
@ -163,7 +163,7 @@ class TraderDemoProtocolBuyer(private val attachmentsPath: Path) : ProtocolLogic
progressTracker.currentStep = STARTING_BUY
send("test.junktrade", newPartnerAddr, 0, sessionID)
val tsa = serviceHub.networkMapService.timestampingNodes[0]
val tsa = serviceHub.networkMapCache.timestampingNodes[0]
val buyer = TwoPartyTradeProtocol.Buyer(newPartnerAddr, tsa.identity, 1000.DOLLARS,
CommercialPaper.State::class.java, sessionID)
val tradeTX: SignedTransaction = subProtocol(buyer)
@ -222,7 +222,7 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort,
progressTracker.currentStep = SELF_ISSUING
val tsa = serviceHub.networkMapService.timestampingNodes[0]
val tsa = serviceHub.networkMapCache.timestampingNodes[0]
val cpOwnerKey = serviceHub.keyManagementService.freshKey()
val commercialPaper = selfIssueSomeCommercialPaper(cpOwnerKey.public, tsa)
@ -236,7 +236,7 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort,
}
@Suspendable
fun selfIssueSomeCommercialPaper(ownedBy: PublicKey, tsa: LegallyIdentifiableNode): StateAndRef<CommercialPaper.State> {
fun selfIssueSomeCommercialPaper(ownedBy: PublicKey, tsa: NodeInfo): StateAndRef<CommercialPaper.State> {
// Make a fake company that's issued its own paper.
val keyPair = generateKeyPair()
val party = Party("Bank of London", keyPair.public)

View File

@ -0,0 +1,120 @@
package demos.protocols
import co.paralleluniverse.fibers.Suspendable
import com.google.common.util.concurrent.FutureCallback
import com.google.common.util.concurrent.Futures
import contracts.DealState
import core.Party
import core.SignedTransaction
import core.messaging.SingleMessageRecipient
import core.node.Node
import core.protocols.ProtocolLogic
import core.random63BitValue
import core.serialization.deserialize
import core.utilities.ANSIProgressRenderer
import core.utilities.ProgressTracker
import protocols.TwoPartyDealProtocol
/**
* This whole class is really part of a demo just to initiate the agreement of a deal with a simple
* API call from a single party without bi-directional access to the database of offers etc.
*
* In the "real world", we'd probably have the offers sitting in the platform prior to the agreement step
* or the protocol would have to reach out to external systems (or users) to verify the deals
*/
object AutoOfferProtocol {
val TOPIC = "autooffer.topic"
data class AutoOfferMessage(val otherSide: SingleMessageRecipient,
val otherSessionID: Long, val dealBeingOffered: DealState)
object Handler {
object RECEIVED : ProgressTracker.Step("Received offer")
object DEALING : ProgressTracker.Step("Starting the deal protocol")
fun tracker() = ProgressTracker(RECEIVED, DEALING).apply {
childrenFor[DEALING] = TwoPartyDealProtocol.Primary.tracker()
}
class Callback(val success: (SignedTransaction) -> Unit) : FutureCallback<SignedTransaction> {
override fun onFailure(t: Throwable?) {
// TODO handle exceptions
}
override fun onSuccess(st: SignedTransaction?) {
success(st!!)
}
}
fun register(node: Node) {
node.net.addMessageHandler("${TOPIC}.0") { msg, registration ->
val progressTracker = tracker()
ANSIProgressRenderer.progressTracker = progressTracker
progressTracker.currentStep = RECEIVED
val autoOfferMessage = msg.data.deserialize<AutoOfferMessage>()
// Put the deal onto the ledger
progressTracker.currentStep = DEALING
// TODO required as messaging layer does not currently queue messages that arrive before we expect them
Thread.sleep(100)
val seller = TwoPartyDealProtocol.Instigator(autoOfferMessage.otherSide, node.timestamperAddress!!,
autoOfferMessage.dealBeingOffered, node.services.keyManagementService.freshKey(), autoOfferMessage.otherSessionID, progressTracker.childrenFor[DEALING]!!)
val future = node.smm.add("${TwoPartyDealProtocol.DEAL_TOPIC}.seller", seller)
// This is required because we are doing child progress outside of a subprotocol. In future, we should just wrap things like this in a protocol to avoid it
Futures.addCallback(future, Callback() {
seller.progressTracker.currentStep = ProgressTracker.DONE
progressTracker.currentStep = ProgressTracker.DONE
})
}
}
}
class Requester<T>(val dealToBeOffered: DealState) : ProtocolLogic<SignedTransaction>() {
companion object {
object RECEIVED : ProgressTracker.Step("Received API call")
object ANNOUNCING : ProgressTracker.Step("Announcing to the peer node")
object DEALING : ProgressTracker.Step("Starting the deal protocol")
// We vend a progress tracker that already knows there's going to be a TwoPartyTradingProtocol involved at some
// point: by setting up the tracker in advance, the user can see what's coming in more detail, instead of being
// surprised when it appears as a new set of tasks below the current one.
fun tracker() = ProgressTracker(RECEIVED, ANNOUNCING, DEALING).apply {
childrenFor[DEALING] = TwoPartyDealProtocol.Secondary.tracker()
}
}
override val progressTracker = tracker()
init {
progressTracker.currentStep = RECEIVED
}
@Suspendable
override fun call(): SignedTransaction {
val ourSessionID = random63BitValue()
val timestampingAuthority = serviceHub.networkMapCache.timestampingNodes[0]
// need to pick which ever party is not us
val otherParty = notUs(*dealToBeOffered.parties).single()
val otherSide = (serviceHub.networkMapCache.nodeForPartyName(otherParty.name))!!.address
progressTracker.currentStep = ANNOUNCING
send(TOPIC, otherSide, 0, AutoOfferMessage(serviceHub.networkService.myAddress, ourSessionID, dealToBeOffered))
progressTracker.currentStep = DEALING
val stx = subProtocol(TwoPartyDealProtocol.Acceptor(otherSide, timestampingAuthority.identity, dealToBeOffered, ourSessionID, progressTracker.childrenFor[DEALING]!!))
return stx
}
fun notUs(vararg parties: Party): List<Party> {
val notUsParties : MutableList<Party> = arrayListOf()
for(party in parties) {
if (serviceHub.storageService.myLegalIdentity != party) {
notUsParties.add(party)
}
}
return notUsParties
}
}
}

View File

@ -0,0 +1,71 @@
package demos.protocols
import co.paralleluniverse.fibers.Suspendable
import co.paralleluniverse.strands.Strand
import core.node.Node
import core.node.services.NodeInfo
import core.node.services.MockNetworkMapCache
import core.protocols.ProtocolLogic
import core.serialization.deserialize
import java.util.concurrent.TimeUnit
object ExitServerProtocol {
val TOPIC = "exit.topic"
// Will only be enabled if you install the Handler
@Volatile private var enabled = false
data class ExitMessage(val exitCode: Int)
object Handler {
fun register(node: Node) {
node.net.addMessageHandler("${TOPIC}.0") { msg, registration ->
// Just to validate we got the message
if(enabled) {
val message = msg.data.deserialize<ExitMessage>()
System.exit(message.exitCode)
}
}
enabled = true
}
}
/**
* This takes a Java Integer rather than Kotlin Int as that is what we end up with in the calling map and currently
* we do not support coercing numeric types in the reflective search for matching constructors
*/
class Broadcast(val exitCode: Integer) : ProtocolLogic<Boolean>() {
@Suspendable
override fun call(): Boolean {
if(enabled) {
val rc = exitCode.toInt()
val message = ExitMessage(rc)
for (recipient in serviceHub.networkMapCache.partyNodes) {
doNextRecipient(recipient, message)
}
// Sleep a little in case any async message delivery to other nodes needs to happen
Strand.sleep(1, TimeUnit.SECONDS)
System.exit(rc)
}
return enabled
}
@Suspendable
private fun doNextRecipient(recipient: NodeInfo, message: ExitMessage) {
if(recipient.address is MockNetworkMapCache.MockAddress) {
// Ignore
} else {
// TODO: messaging ourselves seems to trigger a bug for the time being and we continuously receive messages
if (recipient.identity != serviceHub.storageService.myLegalIdentity) {
send(TOPIC, recipient.address, 0, message)
}
}
}
}
}

View File

@ -0,0 +1,174 @@
package demos.protocols
import co.paralleluniverse.fibers.Suspendable
import co.paralleluniverse.strands.Strand
import contracts.DealState
import contracts.InterestRateSwap
import core.StateAndRef
import core.node.Node
import core.node.services.NodeInfo
import core.node.services.MockNetworkMapCache
import core.protocols.ProtocolLogic
import core.random63BitValue
import core.serialization.deserialize
import core.utilities.ANSIProgressRenderer
import core.utilities.ProgressTracker
import demos.DemoClock
import protocols.TwoPartyDealProtocol
import java.time.LocalDate
/**
* This is a very temporary, demo-oriented way of initiating processing of temporal events and is not
* intended as the way things will necessarily be done longer term
*/
object UpdateBusinessDayProtocol {
val TOPIC = "businessday.topic"
class Updater(val date: LocalDate, val sessionID: Long,
override val progressTracker: ProgressTracker = Updater.tracker()) : ProtocolLogic<Boolean>() {
companion object {
object FETCHING : ProgressTracker.Step("Fetching deals")
object ITERATING_DEALS : ProgressTracker.Step("Interating over deals")
object ITERATING_FIXINGS : ProgressTracker.Step("Iterating over fixings")
object FIXING : ProgressTracker.Step("Fixing deal")
fun tracker() = ProgressTracker(FETCHING, ITERATING_DEALS, ITERATING_FIXINGS, FIXING)
}
@Suspendable
override fun call(): Boolean {
// Get deals
progressTracker.currentStep = FETCHING
val dealStateRefs = serviceHub.walletService.linearHeadsInstanceOf(DealState::class.java)
val otherPartyToDeals = dealStateRefs.values.groupBy { otherParty(it.state) }
// TODO we need to process these in parallel to stop there being an ordering problem across more than two nodes
val sortedParties = otherPartyToDeals.keys.sortedBy { it.identity.name }
for (party in sortedParties) {
val sortedDeals = otherPartyToDeals[party]!!.sortedBy { it.state.ref }
for(deal in sortedDeals) {
progressTracker.currentStep = ITERATING_DEALS
processDeal(party, deal, date, sessionID)
}
}
return false
}
// This assumes we definitely have one key or the other
fun otherParty(deal: DealState): NodeInfo {
val ourKeys = serviceHub.keyManagementService.keys.keys
return serviceHub.networkMapCache.nodeForPartyName(deal.parties.single { !ourKeys.contains(it.owningKey) }.name)!!
}
// TODO we should make this more object oriented when we can ask a state for it's contract
@Suspendable
fun processDeal(party: NodeInfo, deal: StateAndRef<DealState>, date: LocalDate, sessionID: Long) {
when(deal.state) {
is InterestRateSwap.State -> processInterestRateSwap(party, StateAndRef(deal.state as InterestRateSwap.State, deal.ref), date, sessionID)
}
}
// TODO and this would move to the InterestRateSwap and cope with permutations of Fixed/Floating and Floating/Floating etc
@Suspendable
fun processInterestRateSwap(party: NodeInfo, deal: StateAndRef<InterestRateSwap.State>, date: LocalDate, sessionID: Long) {
var dealStateAndRef: StateAndRef<InterestRateSwap.State>? = deal
var nextFixingDate = deal.state.calculation.nextFixingDate()
while (nextFixingDate != null && !nextFixingDate.isAfter(date)) {
progressTracker.currentStep = ITERATING_FIXINGS
/*
* Note that this choice of fixed versus floating leg is simply to assign roles in
* the fixing protocol and doesn't infer roles or responsibilities in a business sense.
* One of the parties needs to take the lead in the coordination and this is a reliable deterministic way
* to do it.
*/
if (party.identity.name == deal.state.fixedLeg.fixedRatePayer.name) {
dealStateAndRef = nextFixingFloatingLeg(dealStateAndRef!!, party, sessionID)
} else {
dealStateAndRef = nextFixingFixedLeg(dealStateAndRef!!, party, sessionID)
}
nextFixingDate = dealStateAndRef?.state?.calculation?.nextFixingDate()
}
}
@Suspendable
private fun nextFixingFloatingLeg(dealStateAndRef: StateAndRef<InterestRateSwap.State>, party: NodeInfo, sessionID: Long): StateAndRef<InterestRateSwap.State>? {
progressTracker.childrenFor[FIXING] = TwoPartyDealProtocol.Primary.tracker()
progressTracker.currentStep = FIXING
val participant = TwoPartyDealProtocol.Floater(party.address, sessionID, serviceHub.networkMapCache.timestampingNodes[0], dealStateAndRef, serviceHub.keyManagementService.freshKey(), sessionID, progressTracker.childrenFor[FIXING]!!)
Strand.sleep(100)
val result = subProtocol(participant)
return result.tx.outRef(0)
}
@Suspendable
private fun nextFixingFixedLeg(dealStateAndRef: StateAndRef<InterestRateSwap.State>, party: NodeInfo, sessionID: Long): StateAndRef<InterestRateSwap.State>? {
progressTracker.childrenFor[FIXING] = TwoPartyDealProtocol.Secondary.tracker()
progressTracker.currentStep = FIXING
val participant = TwoPartyDealProtocol.Fixer(party.address, serviceHub.networkMapCache.timestampingNodes[0].identity, dealStateAndRef, sessionID, progressTracker.childrenFor[FIXING]!!)
val result = subProtocol(participant)
return result.tx.outRef(0)
}
}
data class UpdateBusinessDayMessage(val date: LocalDate, val sessionID: Long)
object Handler {
fun register(node: Node) {
node.net.addMessageHandler("${TOPIC}.0") { msg, registration ->
// Just to validate we got the message
val updateBusinessDayMessage = msg.data.deserialize<UpdateBusinessDayMessage>()
if ((node.services.clock as DemoClock).updateDate(updateBusinessDayMessage.date)) {
val participant = Updater(updateBusinessDayMessage.date, updateBusinessDayMessage.sessionID)
ANSIProgressRenderer.progressTracker = participant.progressTracker
node.smm.add("update.business.day", participant)
}
}
}
}
class Broadcast(val date: LocalDate,
override val progressTracker: ProgressTracker = Broadcast.tracker()) : ProtocolLogic<Boolean>() {
companion object {
object NOTIFYING : ProgressTracker.Step("Notifying peer")
object LOCAL : ProgressTracker.Step("Updating locally")
fun tracker() = ProgressTracker(NOTIFYING, LOCAL).apply {
childrenFor[LOCAL] = Updater.tracker()
}
}
@Suspendable
override fun call(): Boolean {
val message = UpdateBusinessDayMessage(date, random63BitValue())
for (recipient in serviceHub.networkMapCache.partyNodes) {
progressTracker.currentStep = NOTIFYING
doNextRecipient(recipient, message)
}
if ((serviceHub.clock as DemoClock).updateDate(message.date)) {
progressTracker.currentStep = LOCAL
subProtocol(Updater(message.date, message.sessionID, progressTracker.childrenFor[LOCAL]!!))
}
return true
}
@Suspendable
private fun doNextRecipient(recipient: NodeInfo, message: UpdateBusinessDayMessage) {
if(recipient.address is MockNetworkMapCache.MockAddress) {
// Ignore
} else {
// TODO: messaging ourselves seems to trigger a bug for the time being and we continuously receive messages
if (recipient.identity != serviceHub.storageService.myLegalIdentity) {
send(TOPIC, recipient.address, 0, message)
}
}
}
}
}

View File

@ -11,8 +11,8 @@ package protocols
import co.paralleluniverse.fibers.Suspendable
import core.*
import core.crypto.DigitalSignature
import core.node.services.LegallyIdentifiableNode
import core.messaging.SingleMessageRecipient
import core.node.services.NodeInfo
import core.protocols.ProtocolLogic
import core.utilities.ProgressTracker
import java.math.BigDecimal
@ -29,7 +29,7 @@ import java.util.*
* @throws FixOutOfRange if the returned fix was further away from the expected rate by the given amount.
*/
open class RatesFixProtocol(protected val tx: TransactionBuilder,
private val oracle: LegallyIdentifiableNode,
private val oracle: NodeInfo,
private val fixOf: FixOf,
private val expectedRate: BigDecimal,
private val rateTolerance: BigDecimal) : ProtocolLogic<Unit>() {
@ -50,7 +50,7 @@ open class RatesFixProtocol(protected val tx: TransactionBuilder,
@Suspendable
override fun call() {
progressTracker.currentStep = progressTracker.steps[0]
progressTracker.currentStep = progressTracker.steps[1]
val fix = query()
progressTracker.currentStep = WORKING
checkFixIsNearExpected(fix)

View File

@ -12,9 +12,9 @@ import co.paralleluniverse.fibers.Suspendable
import core.Party
import core.WireTransaction
import core.crypto.DigitalSignature
import core.node.services.LegallyIdentifiableNode
import core.messaging.MessageRecipients
import core.messaging.StateMachineManager
import core.node.services.NodeInfo
import core.node.services.NodeTimestamperService
import core.node.services.TimestamperService
import core.protocols.ProtocolLogic
@ -31,11 +31,11 @@ import core.utilities.ProgressTracker
* the built transaction. Please be aware that this will block, meaning it should not be used on a thread that is handling
* a network message: use it only from spare application threads that don't have to respond to anything.
*/
class TimestampingProtocol(private val node: LegallyIdentifiableNode,
class TimestampingProtocol(private val node: NodeInfo,
private val wtxBytes: SerializedBytes<WireTransaction>,
override val progressTracker: ProgressTracker = TimestampingProtocol.tracker()) : ProtocolLogic<DigitalSignature.LegallyIdentifiable>() {
class Client(private val stateMachineManager: StateMachineManager, private val node: LegallyIdentifiableNode) : TimestamperService {
class Client(private val stateMachineManager: StateMachineManager, private val node: NodeInfo) : TimestamperService {
override val identity: Party = node.identity
override fun timestamp(wtxBytes: SerializedBytes<WireTransaction>): DigitalSignature.LegallyIdentifiable {

View File

@ -0,0 +1,407 @@
/*
* Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
* pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
* set forth therein.
*
* All other rights reserved.
*/
package protocols
import co.paralleluniverse.fibers.Suspendable
import contracts.DealState
import contracts.FixableDealState
import core.*
import core.crypto.DigitalSignature
import core.crypto.signWithECDSA
import core.messaging.SingleMessageRecipient
import core.node.services.NodeInfo
import core.protocols.ProtocolLogic
import core.utilities.ProgressTracker
import core.utilities.UntrustworthyData
import core.utilities.trace
import java.math.BigDecimal
import java.security.KeyPair
import java.security.PublicKey
import java.security.SignatureException
/**
* Classes for manipulating a two party deal or agreement.
*
* TODO: The subclasses should probably be broken out into individual protocols rather than making this an ever expanding collection of subclasses.
*
* TODO: Also, the term Deal is used here where we might prefer Agreement.
*
*/
object TwoPartyDealProtocol {
val DEAL_TOPIC = "platform.deal"
class DealMismatchException(val expectedDeal: ContractState, val actualDeal: ContractState) : Exception() {
override fun toString() = "The submitted deal didn't match the expected: $expectedDeal vs $actualDeal"
}
class DealRefMismatchException(val expectedDeal: StateRef, val actualDeal: StateRef) : Exception() {
override fun toString() = "The submitted deal didn't match the expected: $expectedDeal vs $actualDeal"
}
// This object is serialised to the network and is the first protocol message the seller sends to the buyer.
data class Handshake<T>(
val payload: T,
val publicKey: PublicKey,
val sessionID: Long
)
class SignaturesFromPrimary(val timestampAuthoritySig: DigitalSignature.WithKey, val sellerSig: DigitalSignature.WithKey)
/**
* Abstracted bilateral deal protocol participant that initiates communication/handshake.
*
* There's a good chance we can push at least some of this logic down into core protocol logic
* and helper methods etc.
*/
abstract class Primary<U>(val payload: U,
val otherSide: SingleMessageRecipient,
val otherSessionID: Long,
val myKeyPair: KeyPair,
val timestampingAuthority: NodeInfo,
override val progressTracker: ProgressTracker = Primary.tracker()) : ProtocolLogic<SignedTransaction>() {
companion object {
object AWAITING_PROPOSAL : ProgressTracker.Step("Awaiting transaction proposal from other")
object VERIFYING : ProgressTracker.Step("Verifying transaction proposal from other")
object SIGNING : ProgressTracker.Step("Signing transaction")
object TIMESTAMPING : ProgressTracker.Step("Timestamping transaction")
object SENDING_SIGS : ProgressTracker.Step("Sending transaction signatures to other party")
object RECORDING : ProgressTracker.Step("Recording completed transaction")
object COPYING_TO_REGULATOR : ProgressTracker.Step("Copying regulator")
fun tracker() = ProgressTracker(AWAITING_PROPOSAL, VERIFYING, SIGNING, TIMESTAMPING, SENDING_SIGS, RECORDING, COPYING_TO_REGULATOR).apply {
childrenFor[TIMESTAMPING] = TimestampingProtocol.tracker()
}
}
@Suspendable
fun getPartialTransaction(): UntrustworthyData<SignedTransaction> {
progressTracker.currentStep = AWAITING_PROPOSAL
val sessionID = random63BitValue()
// Make the first message we'll send to kick off the protocol.
val hello = Handshake<U>(payload, myKeyPair.public, sessionID)
val maybeSTX = sendAndReceive<SignedTransaction>(DEAL_TOPIC, otherSide, otherSessionID, sessionID, hello)
return maybeSTX
}
@Suspendable
fun verifyPartialTransaction(untrustedPartialTX: UntrustworthyData<SignedTransaction>): SignedTransaction {
progressTracker.currentStep = VERIFYING
untrustedPartialTX.validate {
progressTracker.nextStep()
// Check that the tx proposed by the buyer is valid.
val missingSigs = it.verify(throwIfSignaturesAreMissing = false)
if (missingSigs != setOf(myKeyPair.public, timestampingAuthority.identity.owningKey))
throw SignatureException("The set of missing signatures is not as expected: $missingSigs")
val wtx: WireTransaction = it.tx
logger.trace { "Received partially signed transaction: ${it.id}" }
checkDependencies(it)
// This verifies that the transaction is contract-valid, even though it is missing signatures.
serviceHub.verifyTransaction(wtx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments))
// There are all sorts of funny games a malicious secondary might play here, we should fix them:
//
// - This tx may attempt to send some assets we aren't intending to sell to the secondary, if
// we're reusing keys! So don't reuse keys!
// - This tx may include output states that impose odd conditions on the movement of the cash,
// once we implement state pairing.
//
// but the goal of this code is not to be fully secure (yet), but rather, just to find good ways to
// express protocol state machines on top of the messaging layer.
return it
}
}
@Suspendable
private fun checkDependencies(stx: SignedTransaction) {
// Download and check all the transactions that this transaction depends on, but do not check this
// transaction itself.
val dependencyTxIDs = stx.tx.inputs.map { it.txhash }.toSet()
subProtocol(ResolveTransactionsProtocol(dependencyTxIDs, otherSide))
}
@Suspendable
override fun call(): SignedTransaction {
val partialTX: SignedTransaction = verifyPartialTransaction(getPartialTransaction())
// These two steps could be done in parallel, in theory. Our framework doesn't support that yet though.
val ourSignature = signWithOurKey(partialTX)
val tsaSig = timestamp(partialTX)
val fullySigned = sendSignatures(partialTX, ourSignature, tsaSig)
progressTracker.currentStep = RECORDING
serviceHub.recordTransactions(listOf(fullySigned))
logger.trace { "Deal stored" }
val regulators = serviceHub.networkMapCache.regulators
if (regulators.isNotEmpty()) {
// Copy the transaction to every regulator in the network. This is obviously completely bogus, it's
// just for demo purposes.
for (regulator in regulators) {
send("regulator.all.seeing.eye", regulator.address, 0, fullySigned)
}
}
return fullySigned
}
@Suspendable
private fun timestamp(partialTX: SignedTransaction): DigitalSignature.LegallyIdentifiable {
progressTracker.childrenFor[TIMESTAMPING] = TimestampingProtocol.tracker()
progressTracker.currentStep = TIMESTAMPING
return subProtocol(TimestampingProtocol(timestampingAuthority, partialTX.txBits, progressTracker.childrenFor[TIMESTAMPING]!!))
}
@Suspendable
open fun signWithOurKey(partialTX: SignedTransaction): DigitalSignature.WithKey {
progressTracker.currentStep = SIGNING
return myKeyPair.signWithECDSA(partialTX.txBits)
}
@Suspendable
private fun sendSignatures(partialTX: SignedTransaction, ourSignature: DigitalSignature.WithKey,
tsaSig: DigitalSignature.LegallyIdentifiable): SignedTransaction {
progressTracker.currentStep = SENDING_SIGS
val fullySigned = partialTX + tsaSig + ourSignature
logger.trace { "Built finished transaction, sending back to other party!" }
send(DEAL_TOPIC, otherSide, otherSessionID, SignaturesFromPrimary(tsaSig, ourSignature))
return fullySigned
}
}
/**
* Abstracted bilateral deal protocol participant that is recipient of initial communication.
*
* There's a good chance we can push at least some of this logic down into core protocol logic
* and helper methods etc.
*/
abstract class Secondary<U>(val otherSide: SingleMessageRecipient,
val timestampingAuthority: Party,
val sessionID: Long,
override val progressTracker: ProgressTracker = Secondary.tracker()) : ProtocolLogic<SignedTransaction>() {
companion object {
object RECEIVING : ProgressTracker.Step("Waiting for deal info")
object VERIFYING : ProgressTracker.Step("Verifying deal info")
object SIGNING : ProgressTracker.Step("Generating and signing transaction proposal")
object SWAPPING_SIGNATURES : ProgressTracker.Step("Swapping signatures with the other party")
object RECORDING : ProgressTracker.Step("Recording completed transaction")
fun tracker() = ProgressTracker(RECEIVING, VERIFYING, SIGNING, SWAPPING_SIGNATURES, RECORDING)
}
@Suspendable
override fun call(): SignedTransaction {
val handshake = receiveAndValidateHandshake()
progressTracker.currentStep = SIGNING
val (ptx, additionalSigningPubKeys) = assembleSharedTX(handshake)
val stx = signWithOurKeys(additionalSigningPubKeys, ptx)
val signatures = swapSignaturesWithPrimary(stx, handshake.sessionID)
logger.trace { "Got signatures from other party, verifying ... " }
val fullySigned = stx + signatures.timestampAuthoritySig + signatures.sellerSig
fullySigned.verify()
logger.trace { "Signatures received are valid. Deal transaction complete! :-)" }
progressTracker.currentStep = RECORDING
serviceHub.recordTransactions(listOf(fullySigned))
logger.trace { "Deal transaction stored" }
return fullySigned
}
@Suspendable
private fun receiveAndValidateHandshake(): Handshake<U> {
progressTracker.currentStep = RECEIVING
// Wait for a trade request to come in on our pre-provided session ID.
val handshake = receive(DEAL_TOPIC, sessionID, Handshake::class.java)
progressTracker.currentStep = VERIFYING
handshake.validate {
return validateHandshake(it)
}
}
@Suspendable
private fun swapSignaturesWithPrimary(stx: SignedTransaction, theirSessionID: Long): SignaturesFromPrimary {
progressTracker.currentStep = SWAPPING_SIGNATURES
logger.trace { "Sending partially signed transaction to seller" }
// TODO: Protect against the seller terminating here and leaving us in the lurch without the final tx.
return sendAndReceive<SignaturesFromPrimary>(DEAL_TOPIC, otherSide, theirSessionID, sessionID, stx).validate { it }
}
private fun signWithOurKeys(signingPubKeys: List<PublicKey>, ptx: TransactionBuilder): SignedTransaction {
// Now sign the transaction with whatever keys we need to move the cash.
for (k in signingPubKeys) {
val priv = serviceHub.keyManagementService.toPrivate(k)
ptx.signWith(KeyPair(k, priv))
}
return ptx.toSignedTransaction(checkSufficientSignatures = false)
}
@Suspendable protected abstract fun validateHandshake(handshake: Handshake<*>): Handshake<U>
@Suspendable protected abstract fun assembleSharedTX(handshake: Handshake<U>): Pair<TransactionBuilder, List<PublicKey>>
}
/**
* One side of the protocol for inserting a pre-agreed deal.
*/
open class Instigator<T : DealState>(otherSide: SingleMessageRecipient,
timestampingAuthority: NodeInfo,
dealBeingOffered: T,
myKeyPair: KeyPair,
buyerSessionID: Long,
override val progressTracker: ProgressTracker = Primary.tracker()) : Primary<T>(dealBeingOffered, otherSide, buyerSessionID, myKeyPair, timestampingAuthority)
/**
* One side of the protocol for inserting a pre-agreed deal.
*/
open class Acceptor<T : DealState>(otherSide: SingleMessageRecipient,
timestampingAuthority: Party,
val dealToBuy: T,
sessionID: Long,
override val progressTracker: ProgressTracker = Secondary.tracker()) : Secondary<T>(otherSide, timestampingAuthority, sessionID) {
@Suspendable
override fun validateHandshake(handshake: Handshake<*>): Handshake<T> {
with(handshake as Handshake<T>) {
// What is the seller trying to sell us?
val deal = handshake.payload
val otherKey = handshake.publicKey
logger.trace { "Got deal request for: ${handshake.payload}" }
// Check the start message for acceptability.
check(handshake.sessionID > 0)
if (dealToBuy != deal)
throw DealMismatchException(dealToBuy, deal)
// We need to substitute in the new public keys for the Parties
val myName = serviceHub.storageService.myLegalIdentity.name
val myOldParty = deal.parties.single { it.name == myName }
val theirOldParty = deal.parties.single { it.name != myName }
val newDeal = deal.withPublicKey(myOldParty, serviceHub.keyManagementService.freshKey().public).withPublicKey(theirOldParty, otherKey) as T
return handshake.copy(payload = newDeal)
}
}
@Suspendable
override fun assembleSharedTX(handshake: Handshake<T>): Pair<TransactionBuilder, List<PublicKey>> {
val ptx = handshake.payload.generateAgreement()
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
// to have one.
ptx.setTime(serviceHub.clock.instant(), timestampingAuthority, 30.seconds)
return Pair(ptx, arrayListOf(handshake.payload.parties.single { it.name == serviceHub.storageService.myLegalIdentity.name }.owningKey))
}
}
/**
* One side of the fixing protocol for an interest rate swap, but could easily be generalised further.
*
* Do not infer too much from the name of the class. This is just to indicate that it is the "side"
* of the protocol that is run by the party with the fixed leg of swap deal, which is the basis for decided
* who does what in the protocol.
*/
open class Fixer<T : FixableDealState>(otherSide: SingleMessageRecipient,
timestampingAuthority: Party,
val dealToFix: StateAndRef<T>,
sessionID: Long,
override val progressTracker: ProgressTracker = Secondary.tracker()) : Secondary<StateRef>(otherSide, timestampingAuthority, sessionID) {
@Suspendable
override fun validateHandshake(handshake: Handshake<*>): Handshake<StateRef> {
with(handshake as Handshake<StateRef>) {
logger.trace { "Got fixing request for: ${dealToFix.state}" }
// Check the start message for acceptability.
if (dealToFix.ref != handshake.payload)
throw DealRefMismatchException(dealToFix.ref, handshake.payload)
// Check the transaction that contains the state which is being resolved.
// We only have a hash here, so if we don't know it already, we have to ask for it.
//subProtocol(ResolveTransactionsProtocol(setOf(handshake.payload.txhash), otherSide))
return handshake
}
}
@Suspendable
override fun assembleSharedTX(handshake: Handshake<StateRef>): Pair<TransactionBuilder, List<PublicKey>> {
val fixOf = dealToFix.state.nextFixingOf()!!
// TODO Do we need/want to substitute in new public keys for the Parties?
val myName = serviceHub.storageService.myLegalIdentity.name
val deal = dealToFix.state
val myOldParty = deal.parties.single { it.name == myName }
val theirOldParty = deal.parties.single { it.name != myName }
val myNewKey = serviceHub.keyManagementService.freshKey().public
val newDeal = deal.withPublicKey(myOldParty, myNewKey).withPublicKey(theirOldParty, handshake.publicKey) as T
val oldRef = dealToFix.ref
val ptx = TransactionBuilder()
val addFixing = object : RatesFixProtocol(ptx, serviceHub.networkMapCache.ratesOracleNodes[0], fixOf, BigDecimal.ZERO, BigDecimal.ONE) {
@Suspendable
override fun beforeSigning(fix: Fix) {
newDeal.generateFix(ptx, oldRef, fix)
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
// to have one.
ptx.setTime(serviceHub.clock.instant(), timestampingAuthority, 30.seconds)
}
}
subProtocol(addFixing)
return Pair(ptx, arrayListOf(myNewKey))
}
}
/**
* One side of the fixing protocol for an interest rate swap, but could easily be generalised furher
*
* As per the [Fixer], do not infer too much from this class name in terms of business roles. This
* is just the "side" of the protocol run by the party with the floating leg as a way of deciding who
* does what in the protocol.
*/
open class Floater<T : FixableDealState>(otherSide: SingleMessageRecipient,
otherSessionID: Long,
timestampingAuthority: NodeInfo,
dealToFix: StateAndRef<T>,
myKeyPair: KeyPair,
val sessionID: Long,
override val progressTracker: ProgressTracker = Primary.tracker()) : Primary<StateRef>(dealToFix.ref, otherSide, otherSessionID, myKeyPair, timestampingAuthority)
}

View File

@ -15,10 +15,9 @@ import contracts.sumCashBy
import core.*
import core.crypto.DigitalSignature
import core.crypto.signWithECDSA
import core.node.services.LegallyIdentifiableNode
import core.messaging.SingleMessageRecipient
import core.messaging.StateMachineManager
import protocols.TimestampingProtocol
import core.node.services.NodeInfo
import core.protocols.ProtocolLogic
import core.utilities.ProgressTracker
import core.utilities.trace
@ -26,7 +25,6 @@ import java.security.KeyPair
import java.security.PublicKey
import java.security.SignatureException
import java.time.Instant
import kotlin.system.exitProcess
/**
* This asset trading protocol implements a "delivery vs payment" type swap. It has two parties (B and S for buyer
@ -54,14 +52,14 @@ import kotlin.system.exitProcess
object TwoPartyTradeProtocol {
val TRADE_TOPIC = "platform.trade"
fun runSeller(smm: StateMachineManager, timestampingAuthority: LegallyIdentifiableNode,
fun runSeller(smm: StateMachineManager, timestampingAuthority: NodeInfo,
otherSide: SingleMessageRecipient, assetToSell: StateAndRef<OwnableState>, price: Amount,
myKeyPair: KeyPair, buyerSessionID: Long): ListenableFuture<SignedTransaction> {
val seller = Seller(otherSide, timestampingAuthority, assetToSell, price, myKeyPair, buyerSessionID)
return smm.add("${TRADE_TOPIC}.seller", seller)
}
fun runBuyer(smm: StateMachineManager, timestampingAuthority: LegallyIdentifiableNode,
fun runBuyer(smm: StateMachineManager, timestampingAuthority: NodeInfo,
otherSide: SingleMessageRecipient, acceptablePrice: Amount, typeToBuy: Class<out OwnableState>,
sessionID: Long): ListenableFuture<SignedTransaction> {
val buyer = Buyer(otherSide, timestampingAuthority.identity, acceptablePrice, typeToBuy, sessionID)
@ -84,7 +82,7 @@ object TwoPartyTradeProtocol {
class SignaturesFromSeller(val timestampAuthoritySig: DigitalSignature.WithKey, val sellerSig: DigitalSignature.WithKey)
open class Seller(val otherSide: SingleMessageRecipient,
val timestampingAuthority: LegallyIdentifiableNode,
val timestampingAuthority: NodeInfo,
val assetToSell: StateAndRef<OwnableState>,
val price: Amount,
val myKeyPair: KeyPair,
@ -148,7 +146,7 @@ object TwoPartyTradeProtocol {
serviceHub.verifyTransaction(wtx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments))
if (wtx.outputs.sumCashBy(myKeyPair.public) != price)
throw IllegalArgumentException("Transaction is not sending us the right amounnt of cash")
throw IllegalArgumentException("Transaction is not sending us the right amount of cash")
// There are all sorts of funny games a malicious secondary might play here, we should fix them:
//

View File

@ -0,0 +1,51 @@
# Some pretend noddy rate fixes, for the interest rate oracles.
3M USD 2016-03-16 1M = 0.678
3M USD 2016-03-16 2M = 0.655
EURIBOR 2016-03-15 1M = 0.123
EURIBOR 2016-03-15 2M = 0.111
3M USD 2016-03-08 3M = 0.0063515
3M USD 2016-06-08 3M = 0.0063520
3M USD 2016-09-08 3M = 0.0063521
3M USD 2016-12-08 3M = 0.0063515
3M USD 2017-03-08 3M = 0.0063525
3M USD 2017-06-08 3M = 0.0063530
3M USD 2017-09-07 3M = 0.0063531
3M USD 2017-12-07 3M = 0.0063532
3M USD 2018-03-08 3M = 0.0063533
3M USD 2018-06-07 3M = 0.0063534
3M USD 2018-09-06 3M = 0.0063535
3M USD 2018-12-06 3M = 0.0063536
3M USD 2019-03-07 3M = 0.0063537
3M USD 2019-06-06 3M = 0.0063538
3M USD 2019-09-06 3M = 0.0063539
3M USD 2019-12-06 3M = 0.0063540
3M USD 2020-03-06 3M = 0.0063541
3M USD 2020-06-08 3M = 0.0063542
3M USD 2020-09-08 3M = 0.0063543
3M USD 2020-12-08 3M = 0.0063544
3M USD 2021-03-08 3M = 0.0063545
3M USD 2021-06-08 3M = 0.0063546
3M USD 2021-09-08 3M = 0.0063547
3M USD 2021-12-08 3M = 0.0063548
3M USD 2022-03-08 3M = 0.0063549
3M USD 2022-06-08 3M = 0.0063550
3M USD 2022-09-08 3M = 0.0063551
3M USD 2022-12-08 3M = 0.0063553
3M USD 2023-03-08 3M = 0.0063554
3M USD 2023-06-08 3M = 0.0063555
3M USD 2023-09-07 3M = 0.0063556
3M USD 2023-12-07 3M = 0.0063557
3M USD 2024-03-07 3M = 0.0063558
3M USD 2024-06-06 3M = 0.0063559
3M USD 2024-09-06 3M = 0.0063560
3M USD 2024-12-06 3M = 0.0063561
3M USD 2025-03-06 3M = 0.0063562
3M USD 2025-06-06 3M = 0.0063563
3M USD 2025-09-08 3M = 0.0063564
3M USD 2025-12-08 3M = 0.0063565
3M USD 2026-03-06 3M = 0.0063566
3M USD 2026-06-08 3M = 0.0063567
3M USD 2026-09-08 3M = 0.0063568
3M USD 2026-12-08 3M = 0.0063569

View File

@ -0,0 +1,104 @@
{
"fixedLeg": {
"fixedRatePayer": "Bank A",
"notional": {
"pennies": 2500000000,
"currency": "USD"
},
"paymentFrequency": "SemiAnnual",
"effectiveDate": "2016-03-16",
"effectiveDateAdjustment": null,
"terminationDate": "2026-03-16",
"terminationDateAdjustment": null,
"fixedRate": {
"ratioUnit": {
"value": "0.01676"
}
},
"dayCountBasisDay": "D30",
"dayCountBasisYear": "Y360",
"rollConvention": "ModifiedFollowing",
"dayInMonth": 10,
"paymentRule": "InArrears",
"paymentDelay": 0,
"paymentCalendar": "London",
"interestPeriodAdjustment": "Adjusted"
},
"floatingLeg": {
"floatingRatePayer": "Bank B",
"notional": {
"pennies": 2500000000,
"currency": "USD"
},
"paymentFrequency": "Quarterly",
"effectiveDate": "2016-03-12",
"effectiveDateAdjustment": null,
"terminationDate": "2026-03-12",
"terminationDateAdjustment": null,
"dayCountBasisDay": "D30",
"dayCountBasisYear": "Y360",
"rollConvention": "ModifiedFollowing",
"fixingRollConvention": "ModifiedFollowing",
"dayInMonth": 10,
"resetDayInMonth": 10,
"paymentRule": "InArrears",
"paymentDelay": 0,
"paymentCalendar": [ "London" ],
"interestPeriodAdjustment": "Adjusted",
"fixingPeriod": "TWODAYS",
"resetRule": "InAdvance",
"fixingsPerPayment": "Quarterly",
"fixingCalendar": [ "NewYork" ],
"index": "3M USD",
"indexSource": "Rates Service Provider",
"indexTenor": {
"name": "3M"
}
},
"calculation": {
"expression": "( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) -(floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))",
"floatingLegPaymentSchedule": {
},
"fixedLegPaymentSchedule": {
}
},
"common": {
"baseCurrency": "EUR",
"eligibleCurrency": "EUR",
"eligibleCreditSupport": "Cash in an Eligible Currency",
"independentAmounts": {
"pennies": 0,
"currency": "EUR"
},
"threshold": {
"pennies": 0,
"currency": "EUR"
},
"minimumTransferAmount": {
"pennies": 25000000,
"currency": "EUR"
},
"rounding": {
"pennies": 1000000,
"currency": "EUR"
},
"valuationDate": "Every Local Business Day",
"notificationTime": "2:00pm London",
"resolutionTime": "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ",
"interestRate": {
"oracle": "Rates Service Provider",
"tenor": {
"name": "6M"
},
"ratioUnit": null,
"name": "EONIA"
},
"addressForTransfers": "",
"exposure": {},
"localBusinessDay": [ "London" , "NewYork" ],
"dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360",
"tradeID": "tradeXXX",
"hashLegalDocs": "put hash here"
},
"programRef": "1E6BBA305D445341F0026E51B6C7F3ACB834AFC6C2510C0EF7BC0477235EFECF"
}

View File

@ -11,12 +11,10 @@ package core
import com.codahale.metrics.MetricRegistry
import core.crypto.*
import core.messaging.MessagingService
import core.node.services.MockNetworkMapService
import core.node.services.NetworkMapService
import core.node.services.*
import core.serialization.SerializedBytes
import core.serialization.deserialize
import core.testutils.TEST_KEYS_TO_CORP_MAP
import core.testutils.MockIdentityService
import core.testutils.TEST_PROGRAM_MAP
import core.testutils.TEST_TX_TIME
import java.io.ByteArrayInputStream
@ -52,10 +50,6 @@ class DummyTimestamper(var clock: Clock = Clock.fixed(TEST_TX_TIME, ZoneId.syste
val DUMMY_TIMESTAMPER = DummyTimestamper()
object MockIdentityService : IdentityService {
override fun partyFromKey(key: PublicKey): Party? = TEST_KEYS_TO_CORP_MAP[key]
}
class MockKeyManagementService(vararg initialKeys: KeyPair) : KeyManagementService {
override val keys: MutableMap<PublicKey, PrivateKey>
@ -124,7 +118,7 @@ class MockServices(
val net: MessagingService? = null,
val identity: IdentityService? = MockIdentityService,
val storage: StorageService? = MockStorageService(),
val networkMap: NetworkMapService? = MockNetworkMapService(),
val networkMap: NetworkMapCache? = MockNetworkMapCache(),
val overrideClock: Clock? = Clock.systemUTC()
) : ServiceHub {
override val walletService: WalletService
@ -135,7 +129,7 @@ class MockServices(
get() = identity ?: throw UnsupportedOperationException()
override val networkService: MessagingService
get() = net ?: throw UnsupportedOperationException()
override val networkMapService: NetworkMapService
override val networkMapCache: NetworkMapCache
get() = networkMap ?: throw UnsupportedOperationException()
override val storageService: StorageService
get() = storage ?: throw UnsupportedOperationException()

View File

@ -12,7 +12,7 @@ import core.Attachment
import core.crypto.SecureHash
import core.crypto.sha256
import core.node.NodeConfiguration
import core.node.services.LegallyIdentifiableNode
import core.node.services.NodeInfo
import core.node.services.NodeAttachmentService
import core.serialization.OpaqueBytes
import core.testing.MockNetwork
@ -94,7 +94,7 @@ class AttachmentTests {
fun maliciousResponse() {
// Make a node that doesn't do sanity checking at load time.
val n0 = network.createNode(null, nodeFactory = object : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: LegallyIdentifiableNode?): MockNetwork.MockNode {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode {
return object : MockNetwork.MockNode(dir, config, network, timestamperAddr) {
override fun start(): MockNetwork.MockNode {
super.start()
@ -104,7 +104,7 @@ class AttachmentTests {
}
}
})
val n1 = network.createNode(n0.legallyIdentifiableAddress)
val n1 = network.createNode(n0.info)
// Insert an attachment into node zero's store directly.
val id = n0.storage.attachments.importAttachment(ByteArrayInputStream(fakeAttachment()))

View File

@ -48,7 +48,7 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() {
@Before
fun before() {
net = MockNetwork(false)
net.identities += TEST_KEYS_TO_CORP_MAP.values
net.identities += MockIdentityService.identities
BriefLogFormatter.loggingOn("platform.trade", "core.TransactionGroup", "recordingmap")
}
@ -66,7 +66,7 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() {
transactionGroupFor<ContractState> {
val (aliceNode, bobNode) = net.createTwoNodes()
(bobNode.wallet as NodeWalletService).fillWithSomeTestCash(2000.DOLLARS)
val alicesFakePaper = fillUpForSeller(false, aliceNode.legallyIdentifiableAddress.identity, null).second
val alicesFakePaper = fillUpForSeller(false, aliceNode.info.identity, null).second
insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey)
@ -74,7 +74,7 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() {
val aliceResult = TwoPartyTradeProtocol.runSeller(
aliceNode.smm,
aliceNode.legallyIdentifiableAddress,
aliceNode.info,
bobNode.net.myAddress,
lookup("alice's paper"),
1000.DOLLARS,
@ -83,7 +83,7 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() {
)
val bobResult = TwoPartyTradeProtocol.runBuyer(
bobNode.smm,
aliceNode.legallyIdentifiableAddress,
aliceNode.info,
aliceNode.net.myAddress,
1000.DOLLARS,
CommercialPaper.State::class.java,
@ -105,7 +105,7 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() {
var (aliceNode, bobNode) = net.createTwoNodes()
val aliceAddr = aliceNode.net.myAddress
val bobAddr = bobNode.net.myAddress as InMemoryMessagingNetwork.Handle
val timestamperAddr = aliceNode.legallyIdentifiableAddress
val timestamperAddr = aliceNode.info
(bobNode.wallet as NodeWalletService).fillWithSomeTestCash(2000.DOLLARS)
val alicesFakePaper = fillUpForSeller(false, timestamperAddr.identity, null).second
@ -165,7 +165,7 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() {
// ... bring the node back up ... the act of constructing the SMM will re-register the message handlers
// that Bob was waiting on before the reboot occurred.
bobNode = net.createNode(timestamperAddr, bobAddr.id, object : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: LegallyIdentifiableNode?): MockNetwork.MockNode {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode {
return object : MockNetwork.MockNode(dir, config, net, timestamperAddr, bobAddr.id) {
override fun initialiseStorageService(dir: Path): StorageService {
val ss = super.initialiseStorageService(dir)
@ -196,7 +196,7 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() {
private fun makeNodeWithTracking(name: String): MockNetwork.MockNode {
// Create a node in the mock network ...
return net.createNode(null, nodeFactory = object : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: LegallyIdentifiableNode?): MockNetwork.MockNode {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode {
return object : MockNetwork.MockNode(dir, config, network, timestamperAddr) {
// That constructs the storage service object in a customised way ...
override fun constructStorageService(attachments: NodeAttachmentService, keypair: KeyPair, identity: Party): StorageServiceImpl {
@ -212,7 +212,7 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() {
fun checkDependenciesOfSaleAssetAreResolved() {
transactionGroupFor<ContractState> {
val aliceNode = makeNodeWithTracking("alice")
val timestamperAddr = aliceNode.legallyIdentifiableAddress
val timestamperAddr = aliceNode.info
val bobNode = makeNodeWithTracking("bob")
// Insert a prospectus type attachment into the commercial paper transaction.
@ -323,7 +323,7 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() {
var (aliceNode, bobNode) = net.createTwoNodes()
val aliceAddr = aliceNode.net.myAddress
val bobAddr = bobNode.net.myAddress as InMemoryMessagingNetwork.Handle
val timestamperAddr = aliceNode.legallyIdentifiableAddress
val timestamperAddr = aliceNode.info
val bobKey = bobNode.keyManagement.freshKey()
val bobsBadCash = fillUpForBuyer(bobError, bobKey.public).second

View File

@ -11,7 +11,8 @@ package core.node
import co.paralleluniverse.fibers.Suspendable
import core.*
import core.crypto.SecureHash
import core.messaging.*
import core.messaging.StateMachineManager
import core.messaging.TestWithInMemoryNetwork
import core.node.services.*
import core.protocols.ProtocolLogic
import core.serialization.serialize
@ -57,14 +58,14 @@ class TimestamperNodeServiceTest : TestWithInMemoryNetwork() {
mockServices = MockServices(net = serviceMessaging.second, storage = MockStorageService())
val timestampingNodeID = network.setupTimestampingNode(true).first
(mockServices.networkMapService as MockNetworkMapService).timestampingNodes.add(timestampingNodeID)
(mockServices.networkMapCache as MockNetworkMapCache).timestampingNodes.add(timestampingNodeID)
serverKey = timestampingNodeID.identity.owningKey
// And a separate one to be tested directly, to make the unit tests a bit faster.
service = NodeTimestamperService(serviceMessaging.second, Party("Unit test suite", ALICE), ALICE_KEY)
}
class TestPSM(val server: LegallyIdentifiableNode, val now: Instant) : ProtocolLogic<Boolean>() {
class TestPSM(val server: NodeInfo, val now: Instant) : ProtocolLogic<Boolean>() {
@Suspendable
override fun call(): Boolean {
val ptx = TransactionBuilder().apply {
@ -85,7 +86,7 @@ class TimestamperNodeServiceTest : TestWithInMemoryNetwork() {
val psm = runNetwork {
val smm = StateMachineManager(MockServices(net = myMessaging.second), RunOnCallerThread)
val logName = NodeTimestamperService.TIMESTAMPING_PROTOCOL_TOPIC
val psm = TestPSM(mockServices.networkMapService.timestampingNodes[0], clock.instant())
val psm = TestPSM(mockServices.networkMapCache.timestampingNodes[0], clock.instant())
smm.add(logName, psm)
}
assertTrue(psm.isDone)

View File

@ -41,11 +41,19 @@ class NodeInterestRatesTest {
@Test fun `query with one success and one missing`() {
val q1 = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val q2 = NodeInterestRates.parseFixOf("LIBOR 2016-03-19 1M")
val q2 = NodeInterestRates.parseFixOf("LIBOR 2016-03-15 1M")
val e = assertFailsWith<NodeInterestRates.UnknownFix> { service.query(listOf(q1, q2)) }
assertEquals(e.fix, q2)
}
@Test fun `query successfully with one date beyond`() {
val q = NodeInterestRates.parseFixOf("LIBOR 2016-03-19 1M")
val res = service.query(listOf(q))
assertEquals(1, res.size)
assertEquals("0.678".bd, res[0].value)
assertEquals(q, res[0].of)
}
@Test fun `empty query`() {
assertFailsWith<IllegalArgumentException> { service.query(emptyList()) }
}
@ -85,7 +93,7 @@ class NodeInterestRatesTest {
val tx = TransactionBuilder()
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val protocol = RatesFixProtocol(tx, n2.legallyIdentifiableAddress, fixOf, "0.675".bd, "0.1".bd)
val protocol = RatesFixProtocol(tx, n2.info, fixOf, "0.675".bd, "0.1".bd)
BriefLogFormatter.initVerbose("rates")
val future = n1.smm.add("rates", protocol)

View File

@ -11,10 +11,7 @@ package core.serialization
import contracts.Cash
import core.*
import core.crypto.SecureHash
import core.testutils.DUMMY_PUBKEY_1
import core.testutils.MINI_CORP
import core.testutils.TEST_TX_TIME
import core.testutils.TestUtils
import core.testutils.*
import org.junit.Before
import org.junit.Test
import java.security.SignatureException

View File

@ -15,6 +15,7 @@ import contracts.*
import core.*
import core.crypto.*
import core.node.services.DummyTimestampingAuthority
import core.node.services.FixedIdentityService
import core.serialization.serialize
import core.visualiser.GraphVisualiser
import java.security.KeyPair
@ -38,8 +39,6 @@ object TestUtils {
val keypair = generateKeyPair()
val keypair2 = generateKeyPair()
val keypair3 = generateKeyPair()
val keypair4 = generateKeyPair()
val keypair5 = generateKeyPair()
}
// A dummy time at which we will be pretending test transactions are created.
val TEST_TX_TIME = Instant.parse("2015-04-17T12:00:00.00Z")
@ -51,14 +50,6 @@ val MEGA_CORP_PUBKEY = MEGA_CORP_KEY.public
val MINI_CORP_KEY = TestUtils.keypair2
val MINI_CORP_PUBKEY = MINI_CORP_KEY.public
// TODO remove once mock API is retired
val EXCALIBUR_BANK_KEY = TestUtils.keypair4
val EXCALIBUR_BANK_PUBKEY = EXCALIBUR_BANK_KEY.public
// TODO remove once mock API is retired
val A_N_OTHER_BANK_KEY = TestUtils.keypair5
val A_N_OTHER_BANK_PUBKEY = A_N_OTHER_BANK_KEY.public
val ORACLE_KEY = TestUtils.keypair3
val ORACLE_PUBKEY = ORACLE_KEY.public
@ -74,19 +65,9 @@ val BOB = BOB_KEY.public
val MEGA_CORP = Party("MegaCorp", MEGA_CORP_PUBKEY)
val MINI_CORP = Party("MiniCorp", MINI_CORP_PUBKEY)
// TODO remove once mock API is retired
val EXCALIBUR_BANK = Party("Excalibur", EXCALIBUR_BANK_PUBKEY)
val A_N_OTHER_BANK = Party("ANOther",A_N_OTHER_BANK_PUBKEY)
val ALL_TEST_KEYS = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY, DummyTimestampingAuthority.key)
val ALL_TEST_KEYS = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY, EXCALIBUR_BANK_KEY, A_N_OTHER_BANK_KEY, DummyTimestampingAuthority.key)
val TEST_KEYS_TO_CORP_MAP: Map<PublicKey, Party> = mapOf(
MEGA_CORP_PUBKEY to MEGA_CORP,
MINI_CORP_PUBKEY to MINI_CORP,
EXCALIBUR_BANK_PUBKEY to EXCALIBUR_BANK,
A_N_OTHER_BANK_PUBKEY to A_N_OTHER_BANK,
DUMMY_TIMESTAMPER.identity.owningKey to DUMMY_TIMESTAMPER.identity
)
val MockIdentityService = FixedIdentityService(listOf(MEGA_CORP, MINI_CORP, DUMMY_TIMESTAMPER.identity))
// In a real system this would be a persistent map of hash to bytecode and we'd instantiate the object as needed inside
// a sandbox. For unit tests we just have a hard-coded list.
@ -144,7 +125,7 @@ abstract class AbstractTransactionForTest {
open fun output(label: String? = null, s: () -> ContractState) = LabeledOutput(label, s()).apply { outStates.add(this) }
protected fun commandsToAuthenticatedObjects(): List<AuthenticatedObject<CommandData>> {
return commands.map { AuthenticatedObject(it.pubkeys, it.pubkeys.mapNotNull { TEST_KEYS_TO_CORP_MAP[it] }, it.data) }
return commands.map { AuthenticatedObject(it.pubkeys, it.pubkeys.mapNotNull { MockIdentityService.partyFromKey(it) }, it.data) }
}
fun attachment(attachmentID: SecureHash) {