diff --git a/.idea/runConfigurations/All_tests.xml b/.idea/runConfigurations/All_tests.xml index 63ca225abb..1062a01a0b 100644 --- a/.idea/runConfigurations/All_tests.xml +++ b/.idea/runConfigurations/All_tests.xml @@ -1,7 +1,7 @@ - + diff --git a/contracts/src/main/kotlin/contracts/IRS.kt b/contracts/src/main/kotlin/contracts/IRS.kt index a8cf5177a8..895f05ec9f 100644 --- a/contracts/src/main/kotlin/contracts/IRS.kt +++ b/contracts/src/main/kotlin/contracts/IRS.kt @@ -8,6 +8,15 @@ 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 @@ -15,6 +24,7 @@ import org.apache.commons.jexl3.JexlBuilder import org.apache.commons.jexl3.MapContext import java.math.BigDecimal import java.math.RoundingMode +import java.security.PublicKey import java.time.LocalDate import java.util.* @@ -167,8 +177,6 @@ class InterestRateSwap() : Contract { val hashLegalDocs: String ) - data class Expression(val expr: String) - /** * The Calculation data class is "mutable" through out the life of the swap, as in, it's the only thing that contains * data that will changed from state to state (Recall that the design insists that everything is immutable, so we actually @@ -177,7 +185,7 @@ class InterestRateSwap() : Contract { data class Calculation( val expression: Expression, val floatingLegPaymentSchedule: Map, - val fixedLegpaymentSchedule: Map + val fixedLegPaymentSchedule: Map ) { /** * Gets the date of the next fixing. @@ -185,7 +193,7 @@ class InterestRateSwap() : Contract { */ fun nextFixingDate(): LocalDate? { return floatingLegPaymentSchedule. - filter { it.value.rate is OracleRetrievableReferenceRate }.// TODO - a better way to determine what fixings remain to be fixed + filter { it.value.rate is ReferenceRate }.// TODO - a better way to determine what fixings remain to be fixed minBy { it.value.fixingDate.toEpochDay() }?.value?.fixingDate } @@ -203,7 +211,7 @@ class InterestRateSwap() : Contract { val newFloatingLPS = floatingLegPaymentSchedule + (paymentEvent.date to paymentEvent.withNewRate(newRate)) return Calculation(expression = expression, floatingLegPaymentSchedule = newFloatingLPS, - fixedLegpaymentSchedule = fixedLegpaymentSchedule) + fixedLegPaymentSchedule = fixedLegPaymentSchedule) } fun exportSchedule() { @@ -297,7 +305,7 @@ class InterestRateSwap() : Contract { */ override fun verify(tx: TransactionForVerification) { val command = tx.commands.requireSingleCommand() - val time = tx.getTimestampBy(DummyTimestampingAuthority.identity)?.midpoint + val time = tx.commands.getTimestampByName("Mock Company 0", "Bank of Zurich")?.midpoint if (time == null) throw IllegalArgumentException("must be timestamped") val irs = tx.outStates.filterIsInstance().single() @@ -306,7 +314,7 @@ class InterestRateSwap() : Contract { requireThat { "There are no in states for an agreement" by tx.inStates.isEmpty() "The fixed rate is non zero" by (irs.fixedLeg.fixedRate != FixedRate(PercentageRatioUnit("0.0"))) - "There are events in the fix schedule" by (irs.calculation.fixedLegpaymentSchedule.size > 0) + "There are events in the fix schedule" by (irs.calculation.fixedLegPaymentSchedule.size > 0) "There are events in the float schedule" by (irs.calculation.floatingLegPaymentSchedule.size > 0) // "There are fixes in the schedule" by (irs.calculation.floatingLegPaymentSchedule!!.size > 0) // TODO: shortlist of other tests @@ -348,8 +356,14 @@ class InterestRateSwap() : Contract { val floatingLeg: FloatingLeg, val calculation: Calculation, val common: Common - ) : ContractState { + ) : LinearState { override val contract = IRS_PROGRAM_ID + override val thread = SecureHash.sha256(common.tradeID) + override val ref = common.tradeID + + override fun isRelevant(ourKeys: Set): Boolean { + return (fixedLeg.fixedRatePayer.owningKey in ourKeys) || (floatingLeg.floatingRatePayer.owningKey in ourKeys) + } /** * For evaluating arbitrary java on the platform @@ -408,9 +422,6 @@ class InterestRateSwap() : Contract { var floatingLegPaymentSchedule: MutableMap = HashMap() periodStartDate = floatingLeg.effectiveDate - // TODO: Temporary until implemented via Rates Oracle. - val telerate = TelerateOracle("3750") - // Now create a schedule for the floating and fixes. for (periodEndDate in dates) { val paymentEvent = FloatingRatePaymentEvent( @@ -421,8 +432,7 @@ class InterestRateSwap() : Contract { floatingLeg.dayCountBasisYear, calcFixingDate(periodStartDate, floatingLeg.fixingPeriod, floatingLeg.fixingCalendar), floatingLeg.notional, - // TODO: OracleRetrievableReferenceRate will be replaced via oracle v soon. - OracleRetrievableReferenceRate(telerate, floatingLeg.indexTenor, floatingLeg.index) + ReferenceRate(floatingLeg.indexSource, floatingLeg.indexTenor, floatingLeg.index) ) floatingLegPaymentSchedule.put(periodEndDate, paymentEvent) diff --git a/contracts/src/main/kotlin/contracts/IRSExport.kt b/contracts/src/main/kotlin/contracts/IRSExport.kt index e37009f11f..09670caa34 100644 --- a/contracts/src/main/kotlin/contracts/IRSExport.kt +++ b/contracts/src/main/kotlin/contracts/IRSExport.kt @@ -10,6 +10,6 @@ package contracts fun InterestRateSwap.State.exportIRSToCSV() : String = "Fixed Leg\n" + FixedRatePaymentEvent.CSVHeader + "\n" + - this.calculation.fixedLegpaymentSchedule.toSortedMap().values.map{ it.asCSV() }.joinToString("\n") + "\n" + + this.calculation.fixedLegPaymentSchedule.toSortedMap().values.map{ it.asCSV() }.joinToString("\n") + "\n" + "Floating Leg\n" + FloatingRatePaymentEvent.CSVHeader + "\n" + this.calculation.floatingLegPaymentSchedule.toSortedMap().values.map{ it.asCSV() }.joinToString("\n") + "\n" diff --git a/contracts/src/main/kotlin/contracts/IRSUtils.kt b/contracts/src/main/kotlin/contracts/IRSUtils.kt index 644efb73bf..f189d6a2cb 100644 --- a/contracts/src/main/kotlin/contracts/IRSUtils.kt +++ b/contracts/src/main/kotlin/contracts/IRSUtils.kt @@ -1,5 +1,13 @@ 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 java.math.BigDecimal @@ -50,43 +58,7 @@ open class FloatingRate: Rate(null) * So a reference rate is a rate that takes its value from a source at a given date * e.g. LIBOR 6M as of 17 March 2016. Hence it requires a source (name) and a value date in the getAsOf(..) method. */ -abstract class ReferenceRate(val name: String): FloatingRate() { - abstract fun getAsOf(date: LocalDate?) : RatioUnit -} - -/** - * A concrete implementation of the above for testing purposes - */ -open class TestReferenceRate(val testrate: String) : ReferenceRate(testrate) { - override fun getAsOf(date: LocalDate?) : RatioUnit { - return testrate.percent - } -} - -/** - * This represents a source of data. - */ -abstract class Oracle() { abstract fun retrieve(tenor: Tenor, date: LocalDate) : RatioUnit } - -class ReutersOracle() : Oracle() { - override fun retrieve(tenor: Tenor, date: LocalDate): RatioUnit { - TODO("Reuters Oracle retrieval") - } -} - -class TelerateOracle(page: String) : Oracle() { - override fun retrieve(tenor: Tenor, date: LocalDate): RatioUnit { - TODO("Telerate Oracle retrieval") - } -} - -/** - * A Reference rate that is retrieved via an Oracle. - */ -open class OracleRetrievableReferenceRate(val oracle: Oracle, val tenor: Tenor, referenceRate: String) : ReferenceRate(referenceRate) { - override fun getAsOf(date: LocalDate?): RatioUnit { - return oracle.retrieve(tenor,date!!) - } +class ReferenceRate(val oracle: String, val tenor: Tenor, val name: String) : FloatingRate() { override fun toString(): String = "$name - $tenor" } diff --git a/core/build.gradle b/core/build.gradle index caf2a4d3b8..75aed57d57 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -47,4 +47,7 @@ dependencies { // Apache JEXL: An embeddable expression evaluation library. // This may be temporary until we experiment with other ways to do on-the-fly contract specialisation via an API. compile "org.apache.commons:commons-jexl3:3.0" + + // For JSON + compile "com.fasterxml.jackson.core:jackson-databind:2.5.5" } diff --git a/core/src/main/kotlin/core/FinanceTypes.kt b/core/src/main/kotlin/core/FinanceTypes.kt index 3885575176..817ad3abcf 100644 --- a/core/src/main/kotlin/core/FinanceTypes.kt +++ b/core/src/main/kotlin/core/FinanceTypes.kt @@ -8,6 +8,14 @@ package core +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 java.math.BigDecimal import java.time.DayOfWeek import java.time.LocalDate @@ -82,7 +90,25 @@ data class FixOf(val name: String, val forDay: LocalDate, val ofTenor: Tenor) /** A [Fix] represents a named interest rate, on a given day, for a given duration. It can be embedded in a tx. */ data class Fix(val of: FixOf, val value: BigDecimal) : CommandData +/** + * Represents a textual expression of e.g. a formula + * + */ +@JsonDeserialize(using = ExpressionDeserializer::class) +@JsonSerialize(using = ExpressionSerializer::class) +data class Expression(val expr: String) +object ExpressionSerializer: JsonSerializer() { + override fun serialize(expr: Expression, generator: JsonGenerator, provider: SerializerProvider) { + generator.writeString(expr.expr) + } +} + +object ExpressionDeserializer: JsonDeserializer() { + override fun deserialize(parser: JsonParser, context: DeserializationContext): Expression { + return Expression(parser.text) + } +} /** * Placeholder class for the Tenor datatype - which is a standardised duration of time until maturity */ diff --git a/scripts/example.rates.txt b/scripts/example.rates.txt index a744e72352..54170698f6 100644 --- a/scripts/example.rates.txt +++ b/scripts/example.rates.txt @@ -3,4 +3,49 @@ LIBOR 2016-03-16 1M = 0.678 LIBOR 2016-03-16 2M = 0.655 EURIBOR 2016-03-15 1M = 0.123 -EURIBOR 2016-03-15 2M = 0.111 \ No newline at end of file +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 diff --git a/src/main/kotlin/api/APIServerImpl.kt b/src/main/kotlin/api/APIServerImpl.kt index 476ebf4a92..4e5aa68fca 100644 --- a/src/main/kotlin/api/APIServerImpl.kt +++ b/src/main/kotlin/api/APIServerImpl.kt @@ -7,6 +7,7 @@ import core.crypto.SecureHash import core.node.AbstractNode import core.protocols.ProtocolLogic import core.serialization.SerializedBytes +import core.utilities.ANSIProgressRenderer import java.time.LocalDateTime import java.util.* import kotlin.reflect.KParameter @@ -87,6 +88,7 @@ class APIServerImpl(val node: AbstractNode): APIServer { } // If we get here then we matched every parameter val protocol = constructor.callBy(params) as ProtocolLogic<*> + ANSIProgressRenderer.progressTracker = protocol.progressTracker val future = node.smm.add("api-call",protocol) return future } diff --git a/src/main/kotlin/api/ResponseFilter.kt b/src/main/kotlin/api/ResponseFilter.kt new file mode 100644 index 0000000000..dbf44eda1a --- /dev/null +++ b/src/main/kotlin/api/ResponseFilter.kt @@ -0,0 +1,33 @@ +package api + +import javax.ws.rs.container.ContainerRequestContext +import javax.ws.rs.container.ContainerResponseContext +import javax.ws.rs.container.ContainerResponseFilter +import javax.ws.rs.ext.Provider + +/** + * This adds headers needed for cross site scripting on API clients + */ +@Provider +class ResponseFilter: ContainerResponseFilter { + override fun filter(requestContext: ContainerRequestContext, responseContext: ContainerResponseContext) { + val headers = responseContext.headers + + /** + * TODO we need to revisit this for security reasons + * + * We don't want this scriptable from any web page anywhere, but for demo reasons + * we're making this really easy to access pending a proper security approach including + * access control and authentication at a network and software level + * + */ + headers.add("Access-Control-Allow-Origin","*") + + if(requestContext.method == "OPTIONS") { + headers.add("Access-Control-Allow-Headers", "Content-Type,Accept,Origin") + headers.add("Access-Control-Allow-Methods", "POST,PUT,GET,OPTIONS") + } + + } + +} diff --git a/src/main/kotlin/core/messaging/NetworkMapService.kt b/src/main/kotlin/core/messaging/NetworkMapService.kt deleted file mode 100644 index 9936acdda8..0000000000 --- a/src/main/kotlin/core/messaging/NetworkMapService.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.messaging - -import core.Party -import core.crypto.DummyPublicKey -import java.util.* - -/** Info about a network node that has is operated by some sort of verified identity. */ -data class LegallyIdentifiableNode(val address: SingleMessageRecipient, val identity: Party) - -/** - * A network map contains lists of nodes on the network along with information about their identity keys, services - * they provide and host names or IP addresses where they can be connected to. A reasonable architecture for the - * network map service might be one like the Tor directory authorities, where several nodes linked by RAFT or Paxos - * elect a leader and that leader distributes signed documents describing the network layout. Those documents can - * then be cached by every node and thus a network map can be retrieved given only a single successful peer connection. - * - * This interface assumes fast, synchronous access to an in-memory map. -*/ -interface NetworkMapService { - val timestampingNodes: List - val partyNodes: List - - fun nodeForPartyName(name: String): LegallyIdentifiableNode? = 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 { - - data class MockAddress(val id: String): SingleMessageRecipient - - override val timestampingNodes = Collections.synchronizedList(ArrayList()) - override val partyNodes = Collections.synchronizedList(ArrayList()) - - init { - partyNodes.add(LegallyIdentifiableNode(MockAddress("excalibur:8080"), Party("Excalibur", DummyPublicKey("Excalibur")))) - partyNodes.add(LegallyIdentifiableNode(MockAddress("another:8080"), Party("ANOther", DummyPublicKey("ANOther")))) - - } -} diff --git a/src/main/kotlin/core/messaging/StateMachineManager.kt b/src/main/kotlin/core/messaging/StateMachineManager.kt index bfbc062606..96bea1251d 100644 --- a/src/main/kotlin/core/messaging/StateMachineManager.kt +++ b/src/main/kotlin/core/messaging/StateMachineManager.kt @@ -168,10 +168,11 @@ class StateMachineManager(val serviceHub: ServiceHub, val runInThread: Executor) fun add(loggerName: String, logic: ProtocolLogic): ListenableFuture { val logger = LoggerFactory.getLogger(loggerName) val fiber = ProtocolStateMachine(logic) + // Need to add before iterating in case of immediate completion + _stateMachines.add(logic) iterateStateMachine(fiber, serviceHub.networkService, logger, null, null) { it.start() } - _stateMachines.add(logic) totalStartedProtocols.inc() return fiber.resultFuture } diff --git a/src/main/kotlin/core/node/AbstractNode.kt b/src/main/kotlin/core/node/AbstractNode.kt index 901f16e099..3c15004aab 100644 --- a/src/main/kotlin/core/node/AbstractNode.kt +++ b/src/main/kotlin/core/node/AbstractNode.kt @@ -20,10 +20,12 @@ import api.APIServer import api.APIServerImpl import com.codahale.metrics.MetricRegistry import contracts.* -import core.* +import core.Contract +import core.Party import core.crypto.SecureHash import core.crypto.generateKeyPair -import core.messaging.* +import core.messaging.MessagingService +import core.messaging.StateMachineManager import core.node.services.* import core.serialization.deserialize import core.serialization.serialize @@ -68,7 +70,11 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration, override val clock: Clock get() = platformClock } - val legallyIdentifableAddress: LegallyIdentifiableNode get() = LegallyIdentifiableNode(net.myAddress, storage.myLegalIdentity) + val legallyIdentifiableAddress: LegallyIdentifiableNode by lazy { + LegallyIdentifiableNode(net.myAddress, storage.myLegalIdentity, findMyLocation()) + } + + protected open fun findMyLocation(): PhysicalLocation? = CityDatabase[configuration.nearestCity] lateinit var storage: StorageService lateinit var smm: StateMachineManager diff --git a/src/main/kotlin/core/node/Node.kt b/src/main/kotlin/core/node/Node.kt index 04a0daa289..dbc7fa7275 100644 --- a/src/main/kotlin/core/node/Node.kt +++ b/src/main/kotlin/core/node/Node.kt @@ -9,9 +9,10 @@ package core.node import api.Config +import api.ResponseFilter import com.codahale.metrics.JmxReporter import com.google.common.net.HostAndPort -import core.messaging.LegallyIdentifiableNode +import core.node.services.LegallyIdentifiableNode import core.messaging.MessagingService import core.node.services.ArtemisMessagingService import core.node.servlets.AttachmentDownloadServlet @@ -94,6 +95,7 @@ class Node(dir: Path, val p2pAddr: HostAndPort, configuration: NodeConfiguration val resourceConfig = ResourceConfig() // Add your API provider classes (annotated for JAX-RS) here resourceConfig.register(Config(services)) + resourceConfig.register(ResponseFilter()) resourceConfig.register(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", diff --git a/src/main/kotlin/core/node/NodeConfiguration.kt b/src/main/kotlin/core/node/NodeConfiguration.kt index a61a80cd38..5c14f87fe1 100644 --- a/src/main/kotlin/core/node/NodeConfiguration.kt +++ b/src/main/kotlin/core/node/NodeConfiguration.kt @@ -15,6 +15,7 @@ import kotlin.reflect.KProperty interface NodeConfiguration { val myLegalName: String val exportJMXto: String + val nearestCity: String } // Allow the use of "String by config" syntax. TODO: Make it more flexible. @@ -23,4 +24,5 @@ operator fun Config.getValue(receiver: NodeConfigurationFromConfig, metadata: KP class NodeConfigurationFromConfig(val config: Config = ConfigFactory.load()) : NodeConfiguration { override val myLegalName: String by config override val exportJMXto: String by config + override val nearestCity: String by config } \ No newline at end of file diff --git a/src/main/kotlin/core/node/services/ArtemisMessagingService.kt b/src/main/kotlin/core/node/services/ArtemisMessagingService.kt index 8d58a42ebe..01f6a1ee3e 100644 --- a/src/main/kotlin/core/node/services/ArtemisMessagingService.kt +++ b/src/main/kotlin/core/node/services/ArtemisMessagingService.kt @@ -135,43 +135,45 @@ class ArtemisMessagingService(val directory: Path, val myHostPort: HostAndPort) session.createQueue(myHostPort.toString(), "inbound", false) inboundConsumer = session.createConsumer("inbound").setMessageHandler { message: ClientMessage -> // This code runs for every inbound message. - if (!message.containsProperty(TOPIC_PROPERTY)) { - log.warn("Received message without a ${TOPIC_PROPERTY} property, ignoring") - // TODO: Figure out whether we always need to acknowledge messages, even when invalid. - return@setMessageHandler - } - val topic = message.getStringProperty(TOPIC_PROPERTY) - // Because handlers is a COW list, the loop inside filter will operate on a snapshot. Handlers being added - // or removed whilst the filter is executing will not affect anything. - val deliverTo = handlers.filter { if (it.topic.isBlank()) true else it.topic == topic } + try { + if (!message.containsProperty(TOPIC_PROPERTY)) { + log.warn("Received message without a ${TOPIC_PROPERTY} property, ignoring") + return@setMessageHandler + } + val topic = message.getStringProperty(TOPIC_PROPERTY) + // Because handlers is a COW list, the loop inside filter will operate on a snapshot. Handlers being added + // or removed whilst the filter is executing will not affect anything. + val deliverTo = handlers.filter { if (it.topic.isBlank()) true else it.topic == topic } - if (deliverTo.isEmpty()) { - // This should probably be downgraded to a trace in future, so the protocol can evolve with new topics - // without causing log spam. - log.warn("Received message for $topic that doesn't have any registered handlers.") - return@setMessageHandler - } + if (deliverTo.isEmpty()) { + // This should probably be downgraded to a trace in future, so the protocol can evolve with new topics + // without causing log spam. + log.warn("Received message for $topic that doesn't have any registered handlers.") + return@setMessageHandler + } - val bits = ByteArray(message.bodySize) - message.bodyBuffer.readBytes(bits) + val bits = ByteArray(message.bodySize) + message.bodyBuffer.readBytes(bits) - val msg = object : Message { - override val topic = topic - override val data: ByteArray = bits - override val debugTimestamp: Instant = Instant.ofEpochMilli(message.timestamp) - override val debugMessageID: String = message.messageID.toString() - override fun serialise(): ByteArray = bits - } - for (handler in deliverTo) { - (handler.executor ?: RunOnCallerThread).execute { - try { - handler.callback(msg, handler) - } catch(e: Exception) { - log.error("Caught exception whilst executing message handler for $topic", e) + val msg = object : Message { + override val topic = topic + override val data: ByteArray = bits + override val debugTimestamp: Instant = Instant.ofEpochMilli(message.timestamp) + override val debugMessageID: String = message.messageID.toString() + override fun serialise(): ByteArray = bits + } + for (handler in deliverTo) { + (handler.executor ?: RunOnCallerThread).execute { + try { + handler.callback(msg, handler) + } catch(e: Exception) { + log.error("Caught exception whilst executing message handler for $topic", e) + } } } + } finally { + message.acknowledge() } - message.acknowledge() } session.start() diff --git a/src/main/kotlin/core/node/services/NetworkMapService.kt b/src/main/kotlin/core/node/services/NetworkMapService.kt new file mode 100644 index 0000000000..dd39bccc70 --- /dev/null +++ b/src/main/kotlin/core/node/services/NetworkMapService.kt @@ -0,0 +1,106 @@ +/* + * 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.node.services + +import core.Party +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) + +/** + * A network map contains lists of nodes on the network along with information about their identity keys, services + * they provide and host names or IP addresses where they can be connected to. A reasonable architecture for the + * network map service might be one like the Tor directory authorities, where several nodes linked by RAFT or Paxos + * elect a leader and that leader distributes signed documents describing the network layout. Those documents can + * then be cached by every node and thus a network map can be retrieved given only a single successful peer connection. + * + * This interface assumes fast, synchronous access to an in-memory map. +*/ +interface NetworkMapService { + val timestampingNodes: List + val ratesOracleNodes: List + val partyNodes: List + + fun nodeForPartyName(name: String): LegallyIdentifiableNode? = 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 { + data class MockAddress(val id: String): SingleMessageRecipient + + override val timestampingNodes = Collections.synchronizedList(ArrayList()) + override val ratesOracleNodes = Collections.synchronizedList(ArrayList()) + override val partyNodes = Collections.synchronizedList(ArrayList()) + + init { + partyNodes.add(LegallyIdentifiableNode(MockAddress("excalibur:8080"), Party("Excalibur", DummyPublicKey("Excalibur")))) + partyNodes.add(LegallyIdentifiableNode(MockAddress("another:8080"), Party("ANOther", DummyPublicKey("ANOther")))) + } +} + +/** A latitude/longitude pair. */ +data class WorldCoordinate(val latitude: Double, val longitude: Double) { + init { + require(latitude in -90..90) + require(longitude in -180..180) + } + + /** + * Convert to screen coordinates using the Mercator projection. You should have a world map image that + * you know the precise extents of for this function to work. + * + * Note that no world map ever has latitude extents of -90 to 90 because at these extremes the mapping tends + * to infinity. Google Maps, for example, uses a square map image, and square maps yield latitude extents + * of 85.0511 to -85.0511 = arctan(sinh(π)). + */ + fun project(screenWidth: Double, screenHeight: Double, topLatitude: Double, bottomLatitude: Double, + leftLongitude: Double, rightLongitude: Double): Pair { + require(latitude in bottomLatitude..topLatitude) + require(longitude in leftLongitude..rightLongitude) + + fun deg2rad(deg: Double) = deg * Math.PI / 180.0 + val leftLngRad = deg2rad(leftLongitude) + val rightLngRad = deg2rad(rightLongitude) + fun longitudeToScreenX(lng: Double) = screenWidth * (deg2rad(lng) - leftLngRad) / (rightLngRad - leftLngRad) + fun screenYRelative(latDeg: Double) = Math.log(Math.tan(latDeg / 360.0 * Math.PI + Math.PI / 4)) + val topLatRel = screenYRelative(topLatitude) + val bottomLatRel = screenYRelative(bottomLatitude) + fun latitudeToScreenY(lat: Double) = screenHeight * (screenYRelative(lat) - topLatRel) / (bottomLatRel - topLatRel) + return Pair(longitudeToScreenX(longitude), latitudeToScreenY(latitude)) + } +} + +/** + * A labelled [WorldCoordinate], where the label is human meaningful. For example, the name of the nearest city. + * Labels should not refer to non-landmarks, for example, they should not contain the names of organisations. + */ +data class PhysicalLocation(val coordinate: WorldCoordinate, val description: String) + +/** + * A simple lookup table of city names to their coordinates. Lookups are case insensitive. + */ +object CityDatabase { + private val cityMap = HashMap() + + init { + javaClass.getResourceAsStream("cities.txt").bufferedReader().useLines { lines -> + for (line in lines) { + if (line.startsWith("#")) continue + val (name, lng, lat) = line.split('\t') + cityMap[name.toLowerCase()] = PhysicalLocation(WorldCoordinate(lat.toDouble(), lng.toDouble()), name) + } + } + } + + operator fun get(name: String) = cityMap[name.toLowerCase()] +} \ No newline at end of file diff --git a/src/main/kotlin/core/node/services/Services.kt b/src/main/kotlin/core/node/services/Services.kt index 63176e111b..c25ecc9b97 100644 --- a/src/main/kotlin/core/node/services/Services.kt +++ b/src/main/kotlin/core/node/services/Services.kt @@ -13,7 +13,7 @@ import contracts.Cash import core.* import core.crypto.SecureHash import core.messaging.MessagingService -import core.messaging.NetworkMapService +import core.node.services.NetworkMapService import java.io.InputStream import java.security.KeyPair import java.security.PrivateKey diff --git a/src/main/kotlin/core/testing/InMemoryMessagingNetwork.kt b/src/main/kotlin/core/testing/InMemoryMessagingNetwork.kt index a5f220054a..1320fc7a39 100644 --- a/src/main/kotlin/core/testing/InMemoryMessagingNetwork.kt +++ b/src/main/kotlin/core/testing/InMemoryMessagingNetwork.kt @@ -11,17 +11,22 @@ package core.testing import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.MoreExecutors -import core.node.services.DummyTimestampingAuthority import core.ThreadBox import core.crypto.sha256 import core.messaging.* +import core.node.services.DummyTimestampingAuthority +import core.node.services.LegallyIdentifiableNode import core.node.services.NodeTimestamperService import core.utilities.loggerFor +import rx.Observable +import rx.subjects.PublishSubject +import java.time.Duration import java.time.Instant import java.util.* import java.util.concurrent.Executor import java.util.concurrent.LinkedBlockingQueue import javax.annotation.concurrent.ThreadSafe +import kotlin.concurrent.schedule import kotlin.concurrent.thread /** @@ -66,8 +71,33 @@ class InMemoryMessagingNetwork { return Builder(manuallyPumped, Handle(id)) } + private val _allMessages = PublishSubject.create>() + /** A stream of (sender, message, recipients) triples */ + val allMessages: Observable> = _allMessages + + interface LatencyCalculator { + fun between(sender: SingleMessageRecipient, receiver: SingleMessageRecipient): Duration + } + + /** This can be set to an object which can inject artificial latency between sender/recipient pairs. */ + @Volatile var latencyCalculator: LatencyCalculator? = null + private val timer = Timer() + @Synchronized - private fun msgSend(message: Message, recipients: MessageRecipients) { + private fun msgSend(from: InMemoryMessaging, message: Message, recipients: MessageRecipients) { + val calc = latencyCalculator + if (calc != null && recipients is SingleMessageRecipient) { + // Inject some artificial latency. + timer.schedule(calc.between(from.myAddress, recipients).toMillis()) { + msgSendInternal(from, message, recipients) + } + } else { + msgSendInternal(from, message, recipients) + } + _allMessages.onNext(Triple(from.myAddress, message, recipients)) + } + + private fun msgSendInternal(from: InMemoryMessaging, message: Message, recipients: MessageRecipients) { when (recipients) { is Handle -> getQueueForHandle(recipients).add(message) @@ -173,7 +203,7 @@ class InMemoryMessagingNetwork { Pair(handler, items) } for (it in items) - msgSend(it, handle) + msgSend(this, it, handle) return handler } @@ -184,7 +214,7 @@ class InMemoryMessagingNetwork { override fun send(message: Message, target: MessageRecipients) { check(running) - msgSend(message, target) + msgSend(this, message, target) } override fun stop() { diff --git a/src/main/kotlin/core/testing/MockNode.kt b/src/main/kotlin/core/testing/MockNode.kt index c0f6cfc913..9031627477 100644 --- a/src/main/kotlin/core/testing/MockNode.kt +++ b/src/main/kotlin/core/testing/MockNode.kt @@ -11,11 +11,13 @@ package core.testing import com.google.common.jimfs.Jimfs import com.google.common.util.concurrent.MoreExecutors import core.Party -import core.messaging.LegallyIdentifiableNode import core.messaging.MessagingService +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.PhysicalLocation import core.utilities.loggerFor import org.slf4j.Logger import java.nio.file.Files @@ -33,7 +35,8 @@ import java.util.concurrent.Executors * for message exchanges to take place (and associated handlers to run), you must call the [runNetwork] * method. */ -class MockNetwork(private val threadPerNode: Boolean = false) { +class MockNetwork(private val threadPerNode: Boolean = false, + private val defaultFactory: Factory = MockNetwork.DefaultFactory) { private var counter = 0 val filesystem = Jimfs.newFileSystem(com.google.common.jimfs.Configuration.unix()) val messagingNetwork = InMemoryMessagingNetwork() @@ -48,6 +51,19 @@ class MockNetwork(private val threadPerNode: Boolean = false) { Files.createDirectory(filesystem.getPath("/nodes")) } + /** Allows customisation of how nodes are created. */ + interface Factory { + fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, + timestamperAddr: LegallyIdentifiableNode?): MockNode + } + + object DefaultFactory : Factory { + override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, + timestamperAddr: LegallyIdentifiableNode?): 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()) { override val log: Logger = loggerFor() @@ -69,6 +85,9 @@ class MockNetwork(private val threadPerNode: Boolean = false) { override fun makeIdentityService() = FixedIdentityService(mockNet.identities) + // There is no need to slow down the unit tests by initialising CityDatabase + override fun findMyLocation(): PhysicalLocation? = null + override fun start(): MockNode { super.start() mockNet.identities.add(storage.myLegalIdentity) @@ -77,8 +96,7 @@ class MockNetwork(private val threadPerNode: Boolean = false) { } /** Returns a started node, optionally created by the passed factory method */ - fun createNode(withTimestamper: LegallyIdentifiableNode?, forcedID: Int = -1, - factory: ((Path, NodeConfiguration, network: MockNetwork, LegallyIdentifiableNode?) -> MockNode)? = null): MockNode { + fun createNode(withTimestamper: LegallyIdentifiableNode?, forcedID: Int = -1, nodeFactory: Factory = defaultFactory): MockNode { val newNode = forcedID == -1 val id = if (newNode) counter++ else forcedID @@ -88,9 +106,9 @@ class MockNetwork(private val threadPerNode: Boolean = false) { val config = object : NodeConfiguration { override val myLegalName: String = "Mock Company $id" override val exportJMXto: String = "" + override val nearestCity: String = "Atlantis" } - val fac = factory ?: { p, n, n2, l -> MockNode(p, n, n2, l, id) } - val node = fac(path, config, this, withTimestamper).start() + val node = nodeFactory.create(path, config, this, withTimestamper).start() _nodes.add(node) return node } @@ -112,8 +130,10 @@ class MockNetwork(private val threadPerNode: Boolean = false) { /** * Sets up a two node network in which the first node runs a timestamping service and the other doesn't. */ - fun createTwoNodes(factory: ((Path, NodeConfiguration, network: MockNetwork, LegallyIdentifiableNode?) -> MockNode)? = null): Pair { + fun createTwoNodes(nodeFactory: Factory = defaultFactory): Pair { require(nodes.isEmpty()) - return Pair(createNode(null, -1, factory), createNode(nodes[0].legallyIdentifableAddress, -1, factory)) + return Pair(createNode(null, -1, nodeFactory), createNode(nodes[0].legallyIdentifiableAddress, -1, nodeFactory)) } + + fun addressToNode(address: SingleMessageRecipient): MockNode = nodes.single { it.net.myAddress == address } } \ No newline at end of file diff --git a/src/main/kotlin/demos/DemoClock.kt b/src/main/kotlin/demos/DemoClock.kt new file mode 100644 index 0000000000..bea2a78830 --- /dev/null +++ b/src/main/kotlin/demos/DemoClock.kt @@ -0,0 +1,34 @@ +package demos + +import java.time.* +import javax.annotation.concurrent.ThreadSafe + +/** + * A [Clock] that can have the date advanced for use in demos + */ +@ThreadSafe +class DemoClock(private var delegateClock: Clock = Clock.systemUTC()) : Clock() { + + @Synchronized fun updateDate(date: LocalDate): Boolean { + val currentDate = LocalDate.now(this) + if(currentDate.isBefore(date)) { + // It's ok to increment + delegateClock = Clock.offset(delegateClock, Duration.between(currentDate.atStartOfDay(),date.atStartOfDay())) + return true + } + return false + } + + @Synchronized override fun instant(): Instant { + return delegateClock.instant() + } + + @Synchronized override fun withZone(zone: ZoneId): Clock { + return DemoClock(delegateClock.withZone(zone)) + } + + @Synchronized override fun getZone(): ZoneId { + return delegateClock.zone + } + +} \ No newline at end of file diff --git a/src/main/kotlin/demos/RateFixDemo.kt b/src/main/kotlin/demos/RateFixDemo.kt index a80f46c917..281ad7e366 100644 --- a/src/main/kotlin/demos/RateFixDemo.kt +++ b/src/main/kotlin/demos/RateFixDemo.kt @@ -10,10 +10,10 @@ package demos import contracts.Cash import core.* -import core.messaging.LegallyIdentifiableNode import core.node.Node import core.node.NodeConfiguration import core.node.services.ArtemisMessagingService +import core.node.services.LegallyIdentifiableNode import core.node.services.NodeInterestRates import core.serialization.deserialize import core.utilities.ANSIProgressRenderer @@ -71,6 +71,7 @@ fun main(args: Array) { val config = object : NodeConfiguration { override val myLegalName: String = "Rate fix demo node" override val exportJMXto: String = "http" + override val nearestCity: String = "Atlantis" } val node = logElapsedTime("Node startup") { Node(dir, myNetAddr, config, null).start() } diff --git a/src/main/kotlin/demos/TraderDemo.kt b/src/main/kotlin/demos/TraderDemo.kt index ea914c01fe..8705b213a6 100644 --- a/src/main/kotlin/demos/TraderDemo.kt +++ b/src/main/kotlin/demos/TraderDemo.kt @@ -15,12 +15,12 @@ import contracts.CommercialPaper import core.* import core.crypto.SecureHash import core.crypto.generateKeyPair -import core.messaging.LegallyIdentifiableNode import core.messaging.SingleMessageRecipient 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.NodeAttachmentService import core.node.services.NodeWalletService import core.protocols.ProtocolLogic diff --git a/src/main/kotlin/protocols/RatesFixProtocol.kt b/src/main/kotlin/protocols/RatesFixProtocol.kt index b919a81311..7e4a7a2e79 100644 --- a/src/main/kotlin/protocols/RatesFixProtocol.kt +++ b/src/main/kotlin/protocols/RatesFixProtocol.kt @@ -11,7 +11,7 @@ package protocols import co.paralleluniverse.fibers.Suspendable import core.* import core.crypto.DigitalSignature -import core.messaging.LegallyIdentifiableNode +import core.node.services.LegallyIdentifiableNode import core.messaging.SingleMessageRecipient import core.protocols.ProtocolLogic import core.utilities.ProgressTracker @@ -50,10 +50,13 @@ open class RatesFixProtocol(protected val tx: TransactionBuilder, @Suspendable override fun call() { + progressTracker.currentStep = progressTracker.steps[0] val fix = query() + progressTracker.currentStep = WORKING checkFixIsNearExpected(fix) tx.addCommand(fix, oracle.identity.owningKey) beforeSigning(fix) + progressTracker.currentStep = SIGNING tx.addSignatureUnchecked(sign()) } diff --git a/src/main/kotlin/protocols/TimestampingProtocol.kt b/src/main/kotlin/protocols/TimestampingProtocol.kt index 32604e31ca..1e561de280 100644 --- a/src/main/kotlin/protocols/TimestampingProtocol.kt +++ b/src/main/kotlin/protocols/TimestampingProtocol.kt @@ -12,7 +12,7 @@ import co.paralleluniverse.fibers.Suspendable import core.Party import core.WireTransaction import core.crypto.DigitalSignature -import core.messaging.LegallyIdentifiableNode +import core.node.services.LegallyIdentifiableNode import core.messaging.MessageRecipients import core.messaging.StateMachineManager import core.node.services.NodeTimestamperService @@ -20,6 +20,7 @@ import core.node.services.TimestamperService import core.protocols.ProtocolLogic import core.random63BitValue import core.serialization.SerializedBytes +import core.utilities.ProgressTracker /** * The TimestampingProtocol class is the client code that talks to a [NodeTimestamperService] on some remote node. It is a @@ -31,7 +32,8 @@ import core.serialization.SerializedBytes * a network message: use it only from spare application threads that don't have to respond to anything. */ class TimestampingProtocol(private val node: LegallyIdentifiableNode, - private val wtxBytes: SerializedBytes) : ProtocolLogic() { + private val wtxBytes: SerializedBytes, + override val progressTracker: ProgressTracker = TimestampingProtocol.tracker()) : ProtocolLogic() { class Client(private val stateMachineManager: StateMachineManager, private val node: LegallyIdentifiableNode) : TimestamperService { override val identity: Party = node.identity @@ -41,9 +43,18 @@ class TimestampingProtocol(private val node: LegallyIdentifiableNode, } } + companion object { + object REQUESTING : ProgressTracker.Step("Requesting signature by timestamping service") + object VALIDATING : ProgressTracker.Step("Validating received signature from timestamping service") + + fun tracker() = ProgressTracker(REQUESTING, VALIDATING) + } + + @Suspendable override fun call(): DigitalSignature.LegallyIdentifiable { + progressTracker.currentStep = REQUESTING val sessionID = random63BitValue() val replyTopic = "${NodeTimestamperService.TIMESTAMPING_PROTOCOL_TOPIC}.$sessionID" val req = Request(wtxBytes, serviceHub.networkService.myAddress, replyTopic) @@ -52,6 +63,7 @@ class TimestampingProtocol(private val node: LegallyIdentifiableNode, NodeTimestamperService.TIMESTAMPING_PROTOCOL_TOPIC, node.address, 0, sessionID, req) // Check that the timestamping authority gave us back a valid signature and didn't break somehow + progressTracker.currentStep = VALIDATING maybeSignature.validate { sig -> sig.verifyWithECDSA(wtxBytes) return sig diff --git a/src/main/kotlin/protocols/TwoPartyTradeProtocol.kt b/src/main/kotlin/protocols/TwoPartyTradeProtocol.kt index b05e488840..ce93d513ae 100644 --- a/src/main/kotlin/protocols/TwoPartyTradeProtocol.kt +++ b/src/main/kotlin/protocols/TwoPartyTradeProtocol.kt @@ -15,7 +15,7 @@ import contracts.sumCashBy import core.* import core.crypto.DigitalSignature import core.crypto.signWithECDSA -import core.messaging.LegallyIdentifiableNode +import core.node.services.LegallyIdentifiableNode import core.messaging.SingleMessageRecipient import core.messaging.StateMachineManager import protocols.TimestampingProtocol diff --git a/src/main/resources/core/node/services/cities.txt b/src/main/resources/core/node/services/cities.txt new file mode 100644 index 0000000000..02051a2af5 --- /dev/null +++ b/src/main/resources/core/node/services/cities.txt @@ -0,0 +1,756 @@ +# name longitude latitude +Shanghai 121.47 31.23 +Bombay 72.82 18.96 +Karachi 67.01 24.86 +Buenos Aires -58.37 -34.61 +Delhi 77.21 28.67 +Istanbul 29 41.1 +Manila 120.97 14.62 +Sao Paulo -46.63 -23.53 +Moscow 37.62 55.75 +Dhaka 90.39 23.7 +Soul 126.99 37.56 +Lagos 3.35 6.5 +Kinshasa 15.32 -4.31 +Tokyo 139.77 35.67 +Mexico City -99.14 19.43 +Jakarta 106.83 -6.18 +New York -73.94 40.67 +Tehran 51.43 35.67 +Cairo 31.25 30.06 +Lima -77.05 -12.07 +Peking 116.4 39.93 +London -0.1 51.52 +Bogota -74.09 4.63 +Lahore 74.35 31.56 +Rio de Janeiro -43.2 -22.91 +Bangkok 100.5 13.73 +Bagdad 44.44 33.33 +Bangalore 77.56 12.97 +Santiago -70.64 -33.46 +Calcutta 88.36 22.57 +Singapore 103.85 1.3 +Toronto -79.38 43.65 +Rangoon 96.15 16.79 +Ibadan 3.93 7.38 +Riyadh 46.77 24.65 +Madras 80.27 13.09 +Chongqing 106.58 29.57 +Ho Chi Minh City 106.69 10.78 +Xian 108.9 34.27 +Wuhan 114.27 30.58 +Alexandria 29.95 31.22 +Saint Petersburg 30.32 59.93 +Hyderabad 78.48 17.4 +Chengdu 104.07 30.67 +Abidjan -4.03 5.33 +Ankara 32.85 39.93 +Ahmadabad 72.58 23.03 +Los Angeles -118.41 34.11 +Tianjin 117.2 39.13 +Chattagam 91.81 22.33 +Sydney 151.21 -33.87 +Yokohama 139.62 35.47 +Melbourne 144.96 -37.81 +Shenyang 123.45 41.8 +Cape Town 18.46 -33.93 +Berlin 13.38 52.52 +Pusan 129.03 35.11 +Montreal -73.57 45.52 +Harbin 126.65 45.75 +Durban 30.99 -29.87 +Gizeh 31.21 30.01 +Nanjing 118.78 32.05 +Casablanca -7.62 33.6 +Pune 73.84 18.53 +Addis Abeba 38.74 9.03 +Pyongyang 125.75 39.02 +Surat 72.82 21.2 +Madrid -3.71 40.42 +Guangzhou 113.25 23.12 +Jiddah 39.17 21.5 +Kanpur 80.33 26.47 +Nairobi 36.82 -1.29 +Jaipur 75.8 26.92 +Dar es Salaam 39.28 -6.82 +Salvador -38.5 -12.97 +Chicago -87.68 41.84 +Taiyuan 112.55 37.87 +al-Mawsil 43.14 36.34 +Faisalabad 73.11 31.41 +Changchun 125.35 43.87 +Izmir 27.15 38.43 +Taibei 121.45 25.02 +Osaka 135.5 34.68 +Lakhnau 80.92 26.85 +Kiev 30.52 50.43 +Luanda 13.24 -8.82 +Inchon 126.64 37.48 +Rome 12.5 41.89 +Dakar -17.48 14.72 +Belo Horizonte -43.94 -19.92 +Fortaleza -38.59 -3.78 +Mashhad 59.57 36.27 +Maracaibo -71.66 10.73 +Kabul 69.17 34.53 +Santo Domingo -69.91 18.48 +Taegu 128.6 35.87 +Brasilia -47.91 -15.78 +Umm Durman 32.48 15.65 +Nagpur 79.08 21.16 +Surabaya 112.74 -7.24 +Kano 8.52 12 +Medellin -75.54 6.29 +Accra -0.2 5.56 +Nagoya 136.91 35.15 +Benin 5.62 6.34 +Shijiazhuang 114.48 38.05 +Guayaquil -79.9 -2.21 +Changsha 112.97 28.2 +Houston -95.39 29.77 +Khartoum 32.52 15.58 +Paris 2.34 48.86 +Cali -76.52 3.44 +Algiers 3.04 36.77 +Jinan 117 36.67 +Havanna -82.39 23.13 +Tashkent 69.3 41.31 +Dalian 121.65 38.92 +Jilin 126.55 43.85 +Nanchang 115.88 28.68 +Zhengzhou 113.67 34.75 +Vancouver -123.13 49.28 +Johannesburg 28.04 -26.19 +Bayrut 35.5 33.89 +Douala 9.71 4.06 +Jiulong 114.17 22.32 +Caracas -66.93 10.54 +Kaduna 7.44 10.52 +Bucharest 26.1 44.44 +Ecatepec -99.05 19.6 +Sapporo 141.34 43.06 +Port Harcourt 7.01 4.81 +Hangzhou 120.17 30.25 +Rawalpindi 73.04 33.6 +San'a 44.21 15.38 +Conakry -13.67 9.55 +Curitiba -49.29 -25.42 +al-Basrah 47.82 30.53 +Brisbane 153.02 -27.46 +Xinyang 114.07 32.13 +Medan 98.67 3.59 +Indore 75.86 22.72 +Manaus -60.02 -3.12 +Kumasi -1.63 6.69 +Hamburg 10 53.55 +Rabat -6.84 34.02 +Minsk 27.55 53.91 +Patna 85.13 25.62 +Valencia -67.98 10.23 +Bhopal 77.4 23.24 +Soweto 27.84 -26.28 +Warsaw 21.02 52.26 +Qingdao 120.32 36.07 +Vienna 16.37 48.22 +Yaounde 11.52 3.87 +Dubai 55.33 25.27 +Thana 72.97 19.2 +Aleppo 37.17 36.23 +Bekasi 106.97 -6.22 +Budapest 19.08 47.51 +Bamako -7.99 12.65 +Ludhiana 75.84 30.91 +Harare 31.05 -17.82 +Esfahan 51.68 32.68 +Pretoria 28.22 -25.73 +Barcelona 2.17 41.4 +Lubumbashi 27.48 -11.66 +Bandung 107.6 -6.91 +Guadalajara -103.35 20.67 +Tangshan 118.19 39.62 +Muqdisho 45.33 2.05 +Phoenix -112.07 33.54 +Damascus 36.32 33.5 +Quito -78.5 -0.19 +Agra 78.01 27.19 +Urumqi 87.58 43.8 +Davao 125.63 7.11 +Santa Cruz -63.21 -17.77 +Antananarivo 47.51 -18.89 +Kobe 135.17 34.68 +Juarez -106.49 31.74 +Tijuana -117.02 32.53 +Recife -34.92 -8.08 +Multan 71.45 30.2 +Ha Noi 105.84 21.03 +Gaoxiong 120.27 22.63 +Belem -48.5 -1.44 +Cordoba -64.19 -31.4 +Kampala 32.58 0.32 +Lome 1.35 6.17 +Hyderabad 68.37 25.38 +Suzhou 120.62 31.3 +Vadodara 73.18 22.31 +Gujranwala 74.18 32.16 +Bursa 29.08 40.2 +Mbuji-Mayi 23.59 -6.13 +Pimpri 73.8 18.62 +Karaj 50.97 35.8 +Kyoto 135.75 35.01 +Tangerang 106.63 -6.18 +Aba 7.35 5.1 +Kharkiv 36.22 49.98 +Puebla -98.22 19.05 +Nashik 73.78 20.01 +Kuala Lumpur 101.71 3.16 +Philadelphia -75.13 40.01 +Fukuoka 130.41 33.59 +Taejon 127.43 36.33 +Lanzhou 103.68 36.05 +Mecca 39.82 21.43 +Shantou 116.67 23.37 +Koyang 126.93 37.7 +Hefei 117.28 31.85 +Novosibirsk 82.93 55.04 +Porto Alegre -51.22 -30.04 +Adana 35.32 37 +Makasar 119.41 -5.14 +Tabriz 46.3 38.08 +Narayanganj 90.5 23.62 +Faridabad 77.3 28.38 +Fushun 123.88 41.87 +Phnum Penh 104.92 11.57 +Luoyang 112.47 34.68 +Khulna 89.56 22.84 +Depok 106.83 -6.39 +Lusaka 28.29 -15.42 +Ghaziabad 77.41 28.66 +Handan 114.48 36.58 +San Antonio -98.51 29.46 +Kawasaki 139.7 35.53 +Kwangju 126.91 35.16 +Peshawar 71.54 34.01 +Rajkot 70.79 22.31 +Suwon 127.01 37.26 +Mandalay 96.09 21.98 +Almaty 76.92 43.32 +Munich 11.58 48.14 +Mirat 77.7 28.99 +Baotou 110.05 40.6 +Milan 9.19 45.48 +Rongcheng 116.34 23.54 +Kalyan 73.16 19.25 +Montevideo -56.17 -34.87 +Xianggangdao 114.14 22.27 +Yekaterinburg 60.6 56.85 +Ouagadougou -1.53 12.37 +Guarulhos -46.49 -23.46 +Semarang 110.42 -6.97 +Xuzhou 117.18 34.27 +Perth 115.84 -31.96 +Dallas -96.77 32.79 +Stockholm 18.07 59.33 +Palembang 104.75 -2.99 +San Diego -117.14 32.81 +Goiania -49.26 -16.72 +Gaziantep 37.39 37.07 +Nizhniy Novgorod 44 56.33 +Shiraz 52.57 29.63 +Rosario -60.67 -32.94 +Fuzhou 119.3 26.08 +Nezahualcoyotl -99.03 19.41 +Saitama 139.64 35.87 +Shenzhen 114.13 22.53 +Yerevan 44.52 40.17 +Tripoli 13.18 32.87 +Anshan 122.95 41.12 +Varanasi 83.01 25.32 +Guiyang 106.72 26.58 +Baku 49.86 40.39 +Wuxi 120.3 31.58 +Prague 14.43 50.08 +Brazzaville 15.26 -4.25 +Subang Jaya 101.53 3.15 +Leon -101.69 21.12 +Hiroshima 132.44 34.39 +Amritsar 74.87 31.64 +Huainan 116.98 32.63 +Barranquilla -74.8 10.96 +Monrovia -10.8 6.31 +'Amman 35.93 31.95 +Tbilisi 44.79 41.72 +Abuja 7.49 9.06 +Aurangabad 75.32 19.89 +Sofia 23.31 42.69 +Omsk 73.4 55 +Monterrey -100.32 25.67 +Port Elizabeth 25.59 -33.96 +Navi Mumbai 73.06 19.11 +Maputo 32.57 -25.95 +Allahabad 81.84 25.45 +Samara 50.15 53.2 +Belgrade 20.5 44.83 +Campinas -47.08 -22.91 +Sholapur 75.89 17.67 +Kazan 49.13 55.75 +Irbil 44.01 36.18 +Barquisimeto -69.3 10.05 +K?benhavn 12.58 55.67 +Xianyang 108.7 34.37 +Baoding 115.48 38.87 +Guatemala -90.55 14.63 +Maceio -35.75 -9.65 +Nova Iguacu -43.47 -22.74 +Kunming 102.7 25.05 +Taizhong 120.68 24.15 +Maiduguri 13.16 11.85 +Datong 113.3 40.08 +Dublin -6.25 53.33 +Jabalpur 79.94 23.17 +Visakhapatnam 83.3 17.73 +Rostov-na-Donu 39.71 47.24 +Dnipropetrovs'k 34.98 48.45 +Shubra-El-Khema 31.25 30.11 +Srinagar 74.79 34.09 +Benxi 123.75 41.33 +Brussels 4.33 50.83 +al-Madinah 39.59 24.48 +Adelaide 138.6 -34.93 +Zapopan -103.4 20.72 +Chelyabinsk 61.43 55.15 +Haora 88.33 22.58 +Calgary -114.06 51.05 +Sendai 140.89 38.26 +Tegucigalpa -87.22 14.09 +Ranchi 85.33 23.36 +Songnam 127.15 37.44 +Ilorin 4.55 8.49 +Fez -5 34.05 +Ufa 56.04 54.78 +Klang 101.45 3.04 +Chandigarh 76.78 30.75 +Ahvaz 48.72 31.28 +Koyampattur 76.96 11.01 +Cologne 6.97 50.95 +Qom 50.95 34.65 +Odesa 30.73 46.47 +Donetsk 37.82 48 +Jodhpur 73.02 26.29 +Sao Luis -44.3 -2.5 +Sao Goncalo -43.07 -22.84 +Kitakyushu 130.86 33.88 +Huaibei 116.75 33.95 +Perm 56.25 58 +Changzhou 119.97 31.78 +Maisuru 76.65 12.31 +Guwahati 91.75 26.19 +Volgograd 44.48 48.71 +Konya 32.48 37.88 +Naples 14.27 40.85 +Vijayawada 80.63 16.52 +Ulsan 129.31 35.55 +San Jose -121.85 37.3 +Birmingham -1.91 52.48 +Chiba 140.11 35.61 +Ciudad Guayana -62.62 8.37 +Kolwezi 25.66 -10.7 +Padang 100.35 -0.95 +Managua -86.27 12.15 +Mendoza -68.83 -32.89 +Gwalior 78.17 26.23 +Biskek 74.57 42.87 +Kathmandu 85.31 27.71 +El Alto -68.17 -16.5 +Niamey 2.12 13.52 +Kigali 30.06 -1.94 +Qiqihar 124 47.35 +Ulaanbaatar 106.91 47.93 +Krasnoyarsk 93.06 56.02 +Madurai 78.12 9.92 +Edmonton -113.54 53.57 +Asgabat 58.38 37.95 +al-H?artum Bah?ri 32.52 15.64 +Arequipa -71.53 -16.39 +Marrakesh -8 31.63 +Bandar Lampung 105.27 -5.44 +Pingdingshan 113.3 33.73 +Cartagena -75.5 10.4 +Hubli 75.13 15.36 +La Paz -68.15 -16.5 +Wenzhou 120.65 28.02 +Ottawa -75.71 45.42 +Johor Bahru 103.75 1.48 +Mombasa 39.66 -4.04 +Lilongwe 33.8 -13.97 +Turin 7.68 45.08 +Duque de Caxias -43.31 -22.77 +Abu Dhabi 54.37 24.48 +Jalandhar 75.57 31.33 +Warri 5.76 5.52 +Valencia -0.39 39.48 +Oslo 10.75 59.91 +Taian 117.12 36.2 +ad-Dammam 50.1 26.43 +Mira Bhayandar 72.85 19.29 +Salem 78.16 11.67 +Pietermaritzburg 30.39 -29.61 +Naucalpan -99.23 19.48 +H?ims 36.72 34.73 +Bhubaneswar 85.84 20.27 +Hamamatsu 137.73 34.72 +Saratov 46.03 51.57 +Detroit -83.1 42.38 +Kirkuk 44.39 35.47 +Sakai 135.48 34.57 +Onitsha 6.78 6.14 +Quetta 67.02 30.21 +Aligarh 78.06 27.89 +Voronezh 39.26 51.72 +Freetown -13.24 8.49 +Tucuman -65.22 -26.83 +Bogor 106.79 -6.58 +Niigata 139.04 37.92 +Thiruvananthapuram 76.95 8.51 +Jacksonville -81.66 30.33 +Bareli 79.41 28.36 +Cebu 123.9 10.32 +Kota 75.83 25.18 +Natal -35.22 -5.8 +Shihung 126.89 37.46 +Puchon 126.77 37.48 +Tiruchchirappalli 78.69 10.81 +Trujillo -79.03 -8.11 +Sharjah 55.41 25.37 +Kermanshah 47.06 34.38 +Qinhuangdao 119.62 39.93 +Anyang 114.35 36.08 +Bhiwandi 73.05 19.3 +an-Najaf 44.34 32 +Sao Bernardo do Campo -46.54 -23.71 +Teresina -42.8 -5.1 +Nanning 108.32 22.82 +Antalya 30.71 36.89 +Campo Grande -54.63 -20.45 +Indianapolis -86.15 39.78 +Jaboatao -35.02 -8.11 +Zaporizhzhya 35.17 47.85 +Hohhot 111.64 40.82 +Marseille 5.37 43.31 +Moradabad 78.76 28.84 +Zhangjiakou 114.93 40.83 +Liuzhou 109.25 24.28 +Nouakchott -15.98 18.09 +Rajshahi 88.59 24.37 +Yantai 121.4 37.53 +Tainan 120.19 23 +Xining 101.77 36.62 +Port-au-Prince -72.34 18.54 +Hegang 130.37 47.4 +Akure 5.19 7.25 +N'Djamena 15.05 12.11 +Guadalupe -100.26 25.68 +Cracow 19.96 50.06 +Malang 112.62 -7.98 +Hengyang 112.62 26.89 +Athens 23.73 37.98 +Puyang 114.98 35.7 +San Francisco -122.45 37.77 +Jerusalem 35.22 31.78 +Amsterdam 4.89 52.37 +?odz 19.46 51.77 +Merida -89.62 20.97 +Austin -97.75 30.31 +Abeokuta 3.35 7.16 +Xinxiang 113.87 35.32 +Raipur 81.63 21.24 +Tunis 10.22 36.84 +Columbus -82.99 39.99 +Chihuahua -106.08 28.63 +L'viv 24 49.83 +Cotonou 2.44 6.36 +Pekan Baru 101.43 0.56 +Blantyre 34.99 -15.79 +La Plata -57.96 -34.92 +Bulawayo 28.58 -20.17 +Tangier -5.81 35.79 +Kayseri 35.48 38.74 +Tolyatti 49.51 53.48 +Foshan 113.12 23.03 +Ningbo 121.55 29.88 +Langfang 116.68 39.52 +Ampang Jaya 101.77 3.15 +Liaoyang 123.18 41.28 +Riga 24.13 56.97 +Changzhi 111.75 35.22 +Kryvyy Rih 33.35 47.92 +Libreville 9.45 0.39 +Chonju 127.14 35.83 +Fort Worth -97.34 32.75 +as-Sulaymaniyah 45.43 35.56 +Osasco -46.78 -23.53 +Zamboanga 122.08 6.92 +Tlalnepantla -99.19 19.54 +Gorakhpur 83.36 26.76 +San Luis Potosi -100.98 22.16 +Sevilla -5.98 37.4 +Zhuzhou 113.15 27.83 +Zagreb 15.97 45.8 +Huangshi 115.1 30.22 +Puente Alto -70.57 -33.61 +Shaoguan 113.58 24.8 +Matola 32.46 -25.97 +Guilin 110.28 25.28 +Aguascalientes -102.3 21.88 +Shizuoka 138.39 34.98 +Benghazi 20.07 32.12 +Fuxin 121.65 42.01 +Joao Pessoa -34.86 -7.12 +Ipoh 101.07 4.6 +Contagem -44.1 -19.91 +Dushanbe 68.78 38.57 +Zhanjiang 110.38 21.2 +Xingtai 114.49 37.07 +Okayama 133.92 34.67 +Yogyakarta 110.37 -7.78 +Bhilai 81.38 21.21 +Zigong 104.78 29.4 +Mudanjiang 129.6 44.58 +Wahran -0.62 35.7 +Enugu 7.51 6.44 +Santo Andre -46.53 -23.65 +Colombo 79.85 6.93 +Chimalhuacan -98.96 19.44 +Shatian 114.19 22.38 +Memphis -90.01 35.11 +Kumamoto 130.71 32.8 +Sao Jose dos Campos -45.88 -23.2 +Zhangdian 118.06 36.8 +Acapulco -99.92 16.85 +Xiangtan 112.9 27.85 +Quebec -71.23 46.82 +Dasmarinas 120.93 14.33 +Zaria 7.71 11.08 +Nantong 120.82 32.02 +Charlotte -80.83 35.2 +Pointe Noire 11.87 -4.77 +Shaoyang 111.2 27 +Queretaro -100.4 20.59 +Hamilton -79.85 43.26 +Islamabad 73.06 33.72 +Panjin 122.05 41.18 +Saltillo -101 25.42 +Ansan 126.86 37.35 +Jamshedpur 86.2 22.79 +Zaragoza -0.89 41.65 +Cancun -86.83 21.17 +Dandong 124.4 40.13 +Frankfurt 8.68 50.12 +Palermo 13.36 38.12 +Haikou 110.32 20.05 +'Adan 45.03 12.79 +Amravati 77.76 20.95 +Winnipeg -97.17 49.88 +Sagamihara 139.38 35.58 +Zhangzhou 117.67 24.52 +Gazzah 34.44 31.53 +Kataka 85.88 20.47 +El Paso -106.44 31.85 +Krasnodar 38.98 45.03 +Kuching 110.34 1.55 +Wroc?aw 17.03 51.11 +Asmara 38.94 15.33 +Zhenjiang 119.43 32.22 +Baltimore -76.61 39.3 +Benoni 28.33 -26.15 +Mersin 34.63 36.81 +Izhevsk 53.23 56.85 +Yancheng 120.12 33.39 +Hermosillo -110.97 29.07 +Yuanlong 114.02 22.44 +Uberlandia -48.28 -18.9 +Ulyanovsk 48.4 54.33 +Bouake -5.03 7.69 +Santiago -70.69 19.48 +Mexicali -115.47 32.65 +Hai Phong 106.68 20.86 +Anyang 126.92 37.39 +Dadiangas 125.25 6.1 +Morelia -101.18 19.72 +Oshogbo 4.56 7.77 +Chongju 127.5 36.64 +Jos 8.89 9.93 +al-'Ayn 55.74 24.23 +Sorocaba -47.47 -23.49 +Bikaner 73.32 28.03 +Taizhou 119.9 32.49 +Antipolo 121.18 14.59 +Xiamen 118.08 24.45 +Cochabamba -66.17 -17.38 +Culiacan -107.39 24.8 +Yingkou 122.28 40.67 +Kagoshima 130.56 31.59 +Siping 124.33 43.17 +Orumiyeh 45 37.53 +Luancheng 114.65 37.88 +Diyarbak?r 40.23 37.92 +Yaroslavl 39.87 57.62 +Mixco -90.6 14.64 +Banjarmasin 114.59 -3.33 +Chisinau 28.83 47.03 +Djibouti 43.15 11.59 +Seattle -122.35 47.62 +Stuttgart 9.19 48.79 +Khabarovsk 135.12 48.42 +Rotterdam 4.48 51.93 +Jinzhou 121.1 41.12 +Kisangani 25.19 0.53 +San Pedro Sula -88.03 15.47 +Bengbu 117.33 32.95 +Irkutsk 104.24 52.33 +Shihezi 86.03 44.3 +Maracay -67.47 10.33 +Cucuta -72.51 7.88 +Bhavnagar 72.13 21.79 +Port Said 32.29 31.26 +Denver -104.87 39.77 +Genoa 8.93 44.42 +Jiangmen 113.08 22.58 +Dortmund 7.48 51.51 +Barnaul 83.75 53.36 +Washington -77.02 38.91 +Veracruz -96.14 19.19 +Ribeirao Preto -47.8 -21.17 +Vladivostok 131.9 43.13 +Mar del Plata -57.58 -38 +Boston -71.02 42.34 +Eskisehir 30.52 39.79 +Warangal 79.58 18.01 +Zahedan 60.83 29.5 +Essen 7 51.47 +Dusseldorf 6.79 51.24 +Kaifeng 114.35 34.85 +Kingston -76.8 17.99 +Glasgow -4.27 55.87 +Funabashi 139.99 35.7 +Shah Alam 101.56 3.07 +Maoming 110.87 21.92 +Hachioji 139.33 35.66 +Meknes -5.56 33.9 +Hamhung 127.54 39.91 +Villa Nueva -90.59 14.53 +Sargodha 72.67 32.08 +Las Vegas -115.22 36.21 +Resht 49.63 37.3 +Cangzhou 116.87 38.32 +Tanggu 117.67 39 +Helsinki 24.94 60.17 +Malaga -4.42 36.72 +Milwaukee -87.97 43.06 +Nashville -86.78 36.17 +Ife 4.56 7.48 +Changde 111.68 29.03 +at-Ta'if 40.38 21.26 +Surakarta 110.82 -7.57 +Poznan 16.9 52.4 +Barcelona -64.72 10.13 +Bloemfontein 26.23 -29.15 +Lopez Mateos -99.26 19.57 +Bangui 18.56 4.36 +Reynosa -98.28 26.08 +Xigong 114.25 22.33 +Cuiaba -56.09 -15.61 +Shiliguri 88.42 26.73 +Oklahoma City -97.51 35.47 +Louisville -85.74 38.22 +Jiamusi 130.35 46.83 +Huaiyin 119.03 33.58 +Welkom 26.73 -27.97 +Kolhapur 74.22 16.7 +Ulhasnagar 73.15 19.23 +Rajpur 88.44 22.44 +Bremen 8.81 53.08 +San Salvador -89.19 13.69 +Maanshan 118.48 31.73 +Tembisa 28.22 -25.99 +Banqiao 121.44 25.02 +Toluca -99.67 19.29 +Portland -122.66 45.54 +Gold Coast 153.44 -28.07 +Kota Kinabalu 116.07 5.97 +Vilnius 25.27 54.7 +Agadir -9.61 30.42 +Ajmer 74.64 26.45 +Orenburg 55.1 51.78 +Neijiang 105.05 29.58 +Salta -65.41 -24.79 +Guntur 80.44 16.31 +Novokuznetsk 87.1 53.75 +Yangzhou 119.43 32.4 +Durgapur 87.31 23.5 +Shashi 112.23 30.32 +Asuncion -57.63 -25.3 +Aparecida de Goiania -49.24 -16.82 +Ribeirao das Neves -44.08 -19.76 +Petaling Jaya 101.62 3.1 +Sangli-Miraj 74.57 16.86 +Dehra Dun 78.05 30.34 +Maturin -63.17 9.75 +Torreon -103.43 25.55 +Jiaozuo 113.22 35.25 +Zhuhai 113.57 22.28 +Nanded 77.29 19.17 +Suez 32.54 29.98 +Tyumen 65.53 57.15 +Albuquerque -106.62 35.12 +Cagayan 124.67 8.45 +Mwanza 32.89 -2.52 +Petare -66.83 10.52 +Soledad -74.77 10.92 +Uijongbu 127.04 37.74 +Yueyang 113.1 29.38 +Feira de Santana -38.97 -12.25 +Ta'izz 44.04 13.6 +Tucson -110.89 32.2 +Naberezhnyye Chelny 52.32 55.69 +Kerman 57.08 30.3 +Matsuyama 132.77 33.84 +Garoua 13.39 9.3 +Tlaquepaque -103.32 20.64 +Tuxtla Gutierrez -93.12 16.75 +Jamnagar 70.07 22.47 +Jammu 74.85 32.71 +Gulbarga 76.82 17.34 +Chiclayo -79.84 -6.76 +Hanover 9.73 52.4 +Bucaramanga -73.13 7.13 +Bahawalpur 71.67 29.39 +Goteborg 12.01 57.72 +Zhunmen 113.98 22.41 +Bhatpara 88.42 22.89 +Ryazan 39.74 54.62 +Calamba 121.15 14.21 +Changwon 128.62 35.27 +Aracaju -37.07 -10.91 +Zunyi 106.92 27.7 +Lipetsk 39.62 52.62 +Dresden 13.74 51.05 +Saharanpur 77.54 29.97 +H?amah 36.73 35.15 +Niyala 24.89 12.06 +San Nicolas de los Garza -100.3 25.75 +Higashiosaka 135.59 34.67 +al-H?illah 44.43 32.48 +Leipzig 12.4 51.35 +Xuchang 113.82 34.02 +Wuhu 118.37 31.35 +Boma 13.05 -5.85 +Kananga 22.4 -5.89 +Mykolayiv 32 46.97 +Atlanta -84.42 33.76 +Londrina -51.18 -23.3 +Tabuk 36.57 28.39 +Cuautitlan Izcalli -99.25 19.65 +Nuremberg 11.05 49.45 +Santa Fe -60.69 -31.6 +Joinville -48.84 -26.32 +Zurich 8.55 47.36 \ No newline at end of file diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index 00fff9a9d9..65c0d10700 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -1,2 +1,3 @@ myLegalName = "Vast Global MegaCorp, Ltd" -exportJMXto = "http" \ No newline at end of file +exportJMXto = "http" +nearestCity = "The Moon" \ No newline at end of file diff --git a/src/test/kotlin/contracts/IRSTests.kt b/src/test/kotlin/contracts/IRSTests.kt index 25fb913c0a..0ed2295f49 100644 --- a/src/test/kotlin/contracts/IRSTests.kt +++ b/src/test/kotlin/contracts/IRSTests.kt @@ -74,11 +74,11 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State { //expression = "( fixedLeg.notional * (fixedLeg.fixedRate)) - (floatingLeg.notional * (rateSchedule.get(context.getDate('currentDate'))))", // How it's ended up looking, which I think is now broken but it's a WIP. - expression = InterestRateSwap.Expression("( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) -" + + expression = Expression("( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) -" + "(floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))"), floatingLegPaymentSchedule = HashMap(), - fixedLegpaymentSchedule = HashMap() + fixedLegPaymentSchedule = HashMap() ) val EUR = currency("EUR") @@ -94,13 +94,13 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State { 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 = OracleRetrievableReferenceRate(TelerateOracle("T3270"), Tenor("6M"), "EONIA"), + interestRate = ReferenceRate("T3270", Tenor("6M"), "EONIA"), addressForTransfers = "", exposure = UnknownType(), localBusinessDay = BusinessCalendar.getInstance("London"), tradeID = "trade1", hashLegalDocs = "put hash here", - dailyInterestAmount = InterestRateSwap.Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360") + dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360") ) InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common) @@ -165,11 +165,11 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State { //expression = "( fixedLeg.notional * (fixedLeg.fixedRate)) - (floatingLeg.notional * (rateSchedule.get(context.getDate('currentDate'))))", // How it's ended up looking, which I think is now broken but it's a WIP. - expression = InterestRateSwap.Expression("( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) -" + + expression = Expression("( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) -" + "(floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))"), floatingLegPaymentSchedule = HashMap(), - fixedLegpaymentSchedule = HashMap() + fixedLegPaymentSchedule = HashMap() ) val EUR = currency("EUR") @@ -185,13 +185,13 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State { 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 = OracleRetrievableReferenceRate(TelerateOracle("T3270"), Tenor("6M"), "EONIA"), + interestRate = ReferenceRate("T3270", Tenor("6M"), "EONIA"), addressForTransfers = "", exposure = UnknownType(), localBusinessDay = BusinessCalendar.getInstance("London"), tradeID = "trade1", hashLegalDocs = "put hash here", - dailyInterestAmount = InterestRateSwap.Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360") + dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360") ) return InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common) @@ -335,12 +335,6 @@ class IRSTests { // TODO: r1*r2 ? } - @Test - fun `reference rate testing`() { - val r1 = TestReferenceRate("5") - assert(100 * r1.getAsOf(null) == 5) - } - @Test fun `expression calculation testing`() { val dummyIRS = singleIRS() @@ -366,7 +360,7 @@ class IRSTests { for (i in stuffToPrint) { println(i) - var z = dummyIRS.evaluateCalculation(LocalDate.of(2016, 9, 12), InterestRateSwap.Expression(i)) + var z = dummyIRS.evaluateCalculation(LocalDate.of(2016, 9, 12), Expression(i)) println(z.javaClass) println(z) println("-----------") diff --git a/src/test/kotlin/core/MockServices.kt b/src/test/kotlin/core/MockServices.kt index 3e480298fd..9c9ad642a2 100644 --- a/src/test/kotlin/core/MockServices.kt +++ b/src/test/kotlin/core/MockServices.kt @@ -11,8 +11,8 @@ package core import com.codahale.metrics.MetricRegistry import core.crypto.* import core.messaging.MessagingService -import core.messaging.MockNetworkMapService -import core.messaging.NetworkMapService +import core.node.services.MockNetworkMapService +import core.node.services.NetworkMapService import core.node.services.* import core.serialization.SerializedBytes import core.serialization.deserialize diff --git a/src/test/kotlin/core/messaging/AttachmentTests.kt b/src/test/kotlin/core/messaging/AttachmentTests.kt index 414f76ac4c..a3afba3a50 100644 --- a/src/test/kotlin/core/messaging/AttachmentTests.kt +++ b/src/test/kotlin/core/messaging/AttachmentTests.kt @@ -8,22 +8,25 @@ package core.messaging -import protocols.FetchAttachmentsProtocol -import protocols.FetchDataProtocol import core.Attachment import core.crypto.SecureHash import core.crypto.sha256 -import core.testing.MockNetwork +import core.node.NodeConfiguration +import core.node.services.LegallyIdentifiableNode import core.node.services.NodeAttachmentService import core.serialization.OpaqueBytes +import core.testing.MockNetwork import core.testutils.rootCauseExceptions import core.utilities.BriefLogFormatter import org.junit.Before import org.junit.Test +import protocols.FetchAttachmentsProtocol +import protocols.FetchDataProtocol import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.nio.ByteBuffer import java.nio.file.Files +import java.nio.file.Path import java.nio.file.StandardOpenOption import java.util.jar.JarOutputStream import java.util.zip.ZipEntry @@ -90,16 +93,18 @@ class AttachmentTests { @Test fun maliciousResponse() { // Make a node that doesn't do sanity checking at load time. - val n0 = network.createNode(null) { path, config, mock, ts -> - object : MockNetwork.MockNode(path, config, mock, ts) { - override fun start(): MockNetwork.MockNode { - super.start() - (storage.attachments as NodeAttachmentService).checkAttachmentsOnLoad = false - return this + val n0 = network.createNode(null, nodeFactory = object : MockNetwork.Factory { + override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: LegallyIdentifiableNode?): MockNetwork.MockNode { + return object : MockNetwork.MockNode(dir, config, network, timestamperAddr) { + override fun start(): MockNetwork.MockNode { + super.start() + (storage.attachments as NodeAttachmentService).checkAttachmentsOnLoad = false + return this + } } } - } - val n1 = network.createNode(n0.legallyIdentifableAddress) + }) + val n1 = network.createNode(n0.legallyIdentifiableAddress) // Insert an attachment into node zero's store directly. val id = n0.storage.attachments.importAttachment(ByteArrayInputStream(fakeAttachment())) diff --git a/src/test/kotlin/core/messaging/TwoPartyTradeProtocolTests.kt b/src/test/kotlin/core/messaging/TwoPartyTradeProtocolTests.kt index 955cefc1de..09ee0252c1 100644 --- a/src/test/kotlin/core/messaging/TwoPartyTradeProtocolTests.kt +++ b/src/test/kotlin/core/messaging/TwoPartyTradeProtocolTests.kt @@ -12,6 +12,7 @@ import contracts.Cash import contracts.CommercialPaper import core.* import core.crypto.SecureHash +import core.node.NodeConfiguration import core.node.services.* import core.testing.InMemoryMessagingNetwork import core.testing.MockNetwork @@ -65,7 +66,7 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() { transactionGroupFor { val (aliceNode, bobNode) = net.createTwoNodes() (bobNode.wallet as NodeWalletService).fillWithSomeTestCash(2000.DOLLARS) - val alicesFakePaper = fillUpForSeller(false, aliceNode.legallyIdentifableAddress.identity, null).second + val alicesFakePaper = fillUpForSeller(false, aliceNode.legallyIdentifiableAddress.identity, null).second insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey) @@ -73,7 +74,7 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() { val aliceResult = TwoPartyTradeProtocol.runSeller( aliceNode.smm, - aliceNode.legallyIdentifableAddress, + aliceNode.legallyIdentifiableAddress, bobNode.net.myAddress, lookup("alice's paper"), 1000.DOLLARS, @@ -82,7 +83,7 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() { ) val bobResult = TwoPartyTradeProtocol.runBuyer( bobNode.smm, - aliceNode.legallyIdentifableAddress, + aliceNode.legallyIdentifiableAddress, aliceNode.net.myAddress, 1000.DOLLARS, CommercialPaper.State::class.java, @@ -104,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.legallyIdentifableAddress + val timestamperAddr = aliceNode.legallyIdentifiableAddress (bobNode.wallet as NodeWalletService).fillWithSomeTestCash(2000.DOLLARS) val alicesFakePaper = fillUpForSeller(false, timestamperAddr.identity, null).second @@ -163,16 +164,18 @@ 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) { path, nodeConfiguration, net, timestamper -> - object : MockNetwork.MockNode(path, nodeConfiguration, net, timestamper, bobAddr.id) { - override fun initialiseStorageService(dir: Path): StorageService { - val ss = super.initialiseStorageService(dir) - val smMap = ss.stateMachines - smMap.putAll(savedCheckpoints) - return ss + bobNode = net.createNode(timestamperAddr, bobAddr.id, object : MockNetwork.Factory { + override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: LegallyIdentifiableNode?): MockNetwork.MockNode { + return object : MockNetwork.MockNode(dir, config, net, timestamperAddr, bobAddr.id) { + override fun initialiseStorageService(dir: Path): StorageService { + val ss = super.initialiseStorageService(dir) + val smMap = ss.stateMachines + smMap.putAll(savedCheckpoints) + return ss + } } } - } + }) // Find the future representing the result of this state machine again. var bobFuture = bobNode.smm.findStateMachines(TwoPartyTradeProtocol.Buyer::class.java).single().second @@ -192,22 +195,24 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() { // of gets and puts. private fun makeNodeWithTracking(name: String): MockNetwork.MockNode { // Create a node in the mock network ... - return net.createNode(null) { path, config, net, tsNode -> - object : MockNetwork.MockNode(path, config, net, tsNode) { - // That constructs the storage service object in a customised way ... - override fun constructStorageService(attachments: NodeAttachmentService, keypair: KeyPair, identity: Party): StorageServiceImpl { - // To use RecordingMaps instead of ordinary HashMaps. - return StorageServiceImpl(attachments, keypair, identity, { tableName -> name }) + return net.createNode(null, nodeFactory = object : MockNetwork.Factory { + override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: LegallyIdentifiableNode?): 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 { + // To use RecordingMaps instead of ordinary HashMaps. + return StorageServiceImpl(attachments, keypair, identity, { tableName -> name }) + } } } - } + }) } @Test fun checkDependenciesOfSaleAssetAreResolved() { transactionGroupFor { val aliceNode = makeNodeWithTracking("alice") - val timestamperAddr = aliceNode.legallyIdentifableAddress + val timestamperAddr = aliceNode.legallyIdentifiableAddress val bobNode = makeNodeWithTracking("bob") // Insert a prospectus type attachment into the commercial paper transaction. @@ -318,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.legallyIdentifableAddress + val timestamperAddr = aliceNode.legallyIdentifiableAddress val bobKey = bobNode.keyManagement.freshKey() val bobsBadCash = fillUpForBuyer(bobError, bobKey.public).second diff --git a/src/test/kotlin/core/node/TimestamperNodeServiceTest.kt b/src/test/kotlin/core/node/TimestamperNodeServiceTest.kt index 34712ed225..91b9fa3d1d 100644 --- a/src/test/kotlin/core/node/TimestamperNodeServiceTest.kt +++ b/src/test/kotlin/core/node/TimestamperNodeServiceTest.kt @@ -12,9 +12,7 @@ import co.paralleluniverse.fibers.Suspendable import core.* import core.crypto.SecureHash import core.messaging.* -import core.node.services.NodeTimestamperService -import core.node.services.ServiceHub -import core.node.services.TimestampingError +import core.node.services.* import core.protocols.ProtocolLogic import core.serialization.serialize import core.testing.InMemoryMessagingNetwork diff --git a/src/test/kotlin/core/node/services/NodeInterestRatesTest.kt b/src/test/kotlin/core/node/services/NodeInterestRatesTest.kt index 8007ee31b1..69cfcad321 100644 --- a/src/test/kotlin/core/node/services/NodeInterestRatesTest.kt +++ b/src/test/kotlin/core/node/services/NodeInterestRatesTest.kt @@ -9,12 +9,11 @@ package core.node.services import contracts.Cash -import core.* -import core.testing.MockNetwork import core.DOLLARS import core.Fix import core.TransactionBuilder import core.bd +import core.testing.MockNetwork import core.testutils.* import core.utilities.BriefLogFormatter import org.junit.Test @@ -86,7 +85,7 @@ class NodeInterestRatesTest { val tx = TransactionBuilder() val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M") - val protocol = RatesFixProtocol(tx, n2.legallyIdentifableAddress, fixOf, "0.675".bd, "0.1".bd) + val protocol = RatesFixProtocol(tx, n2.legallyIdentifiableAddress, fixOf, "0.675".bd, "0.1".bd) BriefLogFormatter.initVerbose("rates") val future = n1.smm.add("rates", protocol) diff --git a/src/test/kotlin/core/testutils/TestUtils.kt b/src/test/kotlin/core/testutils/TestUtils.kt index d2d67402be..8cb802080f 100644 --- a/src/test/kotlin/core/testutils/TestUtils.kt +++ b/src/test/kotlin/core/testutils/TestUtils.kt @@ -38,6 +38,8 @@ 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") @@ -49,6 +51,14 @@ 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 @@ -64,11 +74,17 @@ val BOB = BOB_KEY.public val MEGA_CORP = Party("MegaCorp", MEGA_CORP_PUBKEY) val MINI_CORP = Party("MiniCorp", MINI_CORP_PUBKEY) -val ALL_TEST_KEYS = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY, DummyTimestampingAuthority.key) +// 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, EXCALIBUR_BANK_KEY, A_N_OTHER_BANK_KEY, DummyTimestampingAuthority.key) val TEST_KEYS_TO_CORP_MAP: Map = 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 )