Merge branch 'master' into dynamic-loading

This commit is contained in:
Mike Hearn 2016-04-18 17:56:31 +02:00
commit 800151b774
4 changed files with 32 additions and 7 deletions

2
.gitignore vendored
View File

@ -22,6 +22,8 @@ lib/dokka.jar
buyer buyer
seller seller
rate-fix-demo-data rate-fix-demo-data
nodeA
nodeB
### JetBrains template ### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio

View File

@ -88,12 +88,32 @@ data class TransactionForVerification(val inStates: List<ContractState>,
* Utilities for contract writers to incorporate into their logic. * Utilities for contract writers to incorporate into their logic.
*/ */
/**
* A set of related inputs and outputs that are connected by some common attributes. An InOutGroup is calculated
* using [groupStates] and is useful for handling cases where a transaction may contain similar but unrelated
* state evolutions, for example, a transaction that moves cash in two different currencies. The numbers must add
* up on both sides of the transaction, but the values must be summed independently per currency. Grouping can
* be used to simplify this logic.
*/
data class InOutGroup<T : ContractState>(val inputs: List<T>, val outputs: List<T>) data class InOutGroup<T : ContractState>(val inputs: List<T>, val outputs: List<T>)
// A shortcut to make IDE auto-completion more intuitive for Java users. /** Simply calls [commands.getTimestampBy] as a shortcut to make code completion more intuitive. */
fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority) fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority)
// For Java users. /**
* Given a type and a function that returns a grouping key, associates inputs and outputs together so that they
* can be processed as one. The grouping key is any arbitrary object that can act as a map key (so must implement
* equals and hashCode).
*
* The purpose of this function is to simplify the writing of verification logic for transactions that may contain
* similar but unrelated state evolutions which need to be checked independently. Consider a transaction that
* simultaneously moves both dollars and euros (e.g. is an atomic FX trade). There may be multiple dollar inputs and
* multiple dollar outputs, depending on things like how fragmented the owners wallet is and whether various privacy
* techniques are in use. The quantity of dollars on the output side must sum to the same as on the input side, to
* ensure no money is being lost track of. This summation and checking must be repeated independently for each
* currency. To solve this, you would use groupStates with a type of Cash.State and a selector that returns the
* currency field: the resulting list can then be iterated over to perform the per-currency calculation.
*/
fun <T : ContractState> groupStates(ofType: Class<T>, selector: (T) -> Any): List<InOutGroup<T>> { fun <T : ContractState> groupStates(ofType: Class<T>, selector: (T) -> Any): List<InOutGroup<T>> {
val inputs = inStates.filterIsInstance(ofType) val inputs = inStates.filterIsInstance(ofType)
val outputs = outStates.filterIsInstance(ofType) val outputs = outStates.filterIsInstance(ofType)
@ -105,7 +125,7 @@ data class TransactionForVerification(val inStates: List<ContractState>,
return groupStatesInternal(inGroups, outGroups) return groupStatesInternal(inGroups, outGroups)
} }
// For Kotlin users: this version has nicer syntax and avoids reflection/object creation for the lambda. /** See the documentation for the reflection-based version of [groupStates] */
inline fun <reified T : ContractState> groupStates(selector: (T) -> Any): List<InOutGroup<T>> { inline fun <reified T : ContractState> groupStates(selector: (T) -> Any): List<InOutGroup<T>> {
val inputs = inStates.filterIsInstance<T>() val inputs = inStates.filterIsInstance<T>()
val outputs = outStates.filterIsInstance<T>() val outputs = outStates.filterIsInstance<T>()

View File

@ -72,8 +72,6 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
lateinit var api: APIServer lateinit var api: APIServer
open fun start(): AbstractNode { open fun start(): AbstractNode {
require(timestamperAddress == null || timestamperAddress.advertisedServices.contains(TimestamperService.Type))
{"Timestamper address must indicate a node that provides timestamping services"}
log.info("Node starting up ...") log.info("Node starting up ...")
storage = initialiseStorageService(dir) storage = initialiseStorageService(dir)
@ -98,6 +96,10 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
// given the details, the timestamping node is somewhere else. Otherwise, we do our own timestamping. // given the details, the timestamping node is somewhere else. Otherwise, we do our own timestamping.
val tsid = if (timestamperAddress != null) { val tsid = if (timestamperAddress != null) {
inNodeTimestampingService = null inNodeTimestampingService = null
require(TimestamperService.Type in timestamperAddress.advertisedServices) {
"Timestamper address must indicate a node that provides timestamping services, actually " +
"has ${timestamperAddress.advertisedServices}"
}
timestamperAddress timestamperAddress
} else { } else {
inNodeTimestampingService = NodeTimestamperService(net, storage.myLegalIdentity, storage.myLegalIdentityKey, platformClock) inNodeTimestampingService = NodeTimestamperService(net, storage.myLegalIdentity, storage.myLegalIdentityKey, platformClock)

View File

@ -11,10 +11,11 @@ import core.messaging.SingleMessageRecipient
import core.node.Node import core.node.Node
import core.node.NodeConfiguration import core.node.NodeConfiguration
import core.node.NodeConfigurationFromConfig import core.node.NodeConfigurationFromConfig
import core.node.services.ArtemisMessagingService
import core.node.NodeInfo import core.node.NodeInfo
import core.node.services.ArtemisMessagingService
import core.node.services.NodeAttachmentService import core.node.services.NodeAttachmentService
import core.node.services.NodeWalletService import core.node.services.NodeWalletService
import core.node.services.TimestamperService
import core.protocols.ProtocolLogic import core.protocols.ProtocolLogic
import core.serialization.deserialize import core.serialization.deserialize
import core.utilities.ANSIProgressRenderer import core.utilities.ANSIProgressRenderer
@ -87,7 +88,7 @@ fun main(args: Array<String>) {
val addr = HostAndPort.fromString(options.valueOf(timestamperNetAddr)).withDefaultPort(Node.DEFAULT_PORT) val addr = HostAndPort.fromString(options.valueOf(timestamperNetAddr)).withDefaultPort(Node.DEFAULT_PORT)
val path = Paths.get(options.valueOf(timestamperIdentityFile)) val path = Paths.get(options.valueOf(timestamperIdentityFile))
val party = Files.readAllBytes(path).deserialize<Party>(includeClassName = true) val party = Files.readAllBytes(path).deserialize<Party>(includeClassName = true)
NodeInfo(ArtemisMessagingService.makeRecipient(addr), party) NodeInfo(ArtemisMessagingService.makeRecipient(addr), party, advertisedServices = setOf(TimestamperService.Type))
} else null } else null
val node = logElapsedTime("Node startup") { Node(dir, myNetAddr, config, timestamperId).start() } val node = logElapsedTime("Node startup") { Node(dir, myNetAddr, config, timestamperId).start() }