Refactor code into clear core, contracts and node namespaces. Move services into clear implementation and api sides. Push unit tests down to lowest level of dependency hierarchy possible.

This commit is contained in:
Matthew Nesbit
2016-05-19 10:25:18 +01:00
parent c8130581a9
commit 01e9536444
680 changed files with 2727 additions and 1800 deletions

View File

@ -1,24 +0,0 @@
package core.testing
import core.crypto.Party
import core.node.services.IdentityService
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.
*/
@ThreadSafe
class MockIdentityService(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 registerIdentity(party: Party) { throw UnsupportedOperationException() }
override fun partyFromKey(key: PublicKey): Party? = keyToParties[key]
override fun partyFromName(name: String): Party? = nameToParties[name]
}

View File

@ -1,5 +1,6 @@
package api
package node.api
import node.api.StatesQuery
import core.contracts.ContractState
import core.contracts.SignedTransaction
import core.contracts.StateRef

View File

@ -1,4 +1,4 @@
package api
package node.api
/**
* Extremely rudimentary query language which should most likely be replaced with a product

View File

@ -1,15 +1,14 @@
package api
package node.core
import com.google.common.util.concurrent.ListenableFuture
import core.*
import core.contracts.*
import core.crypto.DigitalSignature
import core.crypto.SecureHash
import core.node.AbstractNode
import core.node.subsystems.linearHeadsOfType
import core.node.services.linearHeadsOfType
import core.protocols.ProtocolLogic
import core.serialization.SerializedBytes
import core.utilities.ANSIProgressRenderer
import node.api.*
import node.utilities.*
import java.time.LocalDateTime
import java.util.*
import kotlin.reflect.KParameter

View File

@ -1,25 +1,44 @@
package core.node
package node.core
import api.APIServer
import api.APIServerImpl
import com.codahale.metrics.MetricRegistry
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import core.RunOnCallerThread
import core.crypto.Party
import core.messaging.MessagingService
import core.messaging.StateMachineManager
import core.messaging.runOnNextMessage
import core.node.CityDatabase
import core.node.NodeInfo
import core.node.PhysicalLocation
import core.node.services.*
import core.node.storage.CheckpointStorage
import core.node.storage.PerFileCheckpointStorage
import core.node.subsystems.*
import core.random63BitValue
import core.seconds
import core.serialization.deserialize
import core.serialization.serialize
import core.utilities.AddOrRemove
import core.utilities.AffinityExecutor
import node.api.APIServer
import node.services.api.AcceptsFileUpload
import node.services.api.CheckpointStorage
import node.services.api.MonitoringService
import node.services.api.ServiceHubInternal
import node.services.transactions.InMemoryUniquenessProvider
import node.services.transactions.NotaryService
import node.services.transactions.TimestampChecker
import node.services.clientapi.NodeInterestRates
import node.services.config.NodeConfiguration
import node.services.identity.InMemoryIdentityService
import node.services.keys.E2ETestKeyManagementService
import node.services.network.InMemoryNetworkMapCache
import node.services.network.InMemoryNetworkMapService
import node.services.network.NetworkMapService
import node.services.network.NodeRegistration
import node.services.persistence.DataVendingService
import node.services.persistence.NodeAttachmentService
import node.services.persistence.PerFileCheckpointStorage
import node.services.persistence.StorageServiceImpl
import node.services.statemachine.StateMachineManager
import node.services.wallet.NodeWalletService
import node.utilities.AddOrRemove
import node.utilities.AffinityExecutor
import org.slf4j.Logger
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Files
@ -60,7 +79,7 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
protected val _servicesThatAcceptUploads = ArrayList<AcceptsFileUpload>()
val servicesThatAcceptUploads: List<AcceptsFileUpload> = _servicesThatAcceptUploads
val services = object : ServiceHub {
val services = object : ServiceHubInternal {
override val networkService: MessagingService get() = net
override val networkMapCache: NetworkMapCache = InMemoryNetworkMapCache()
override val storageService: StorageService get() = storage
@ -219,7 +238,7 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
protected abstract fun startMessagingService()
protected open fun initialiseStorageService(dir: Path): Pair<StorageService,CheckpointStorage> {
protected open fun initialiseStorageService(dir: Path): Pair<StorageService, CheckpointStorage> {
val attachments = makeAttachmentStorage(dir)
val checkpointStorage = PerFileCheckpointStorage(dir.resolve("checkpoints"))
_servicesThatAcceptUploads += attachments

View File

@ -1,16 +1,19 @@
package core.node
package node.core
import api.Config
import api.ResponseFilter
import com.codahale.metrics.JmxReporter
import com.google.common.net.HostAndPort
import core.messaging.MessagingService
import core.node.subsystems.ArtemisMessagingService
import core.node.NodeInfo
import core.node.services.ServiceType
import core.node.servlets.AttachmentDownloadServlet
import core.node.servlets.DataUploadServlet
import core.utilities.AffinityExecutor
import core.utilities.loggerFor
import node.api.APIServer
import node.services.config.NodeConfiguration
import node.services.messaging.ArtemisMessagingService
import node.servlets.AttachmentDownloadServlet
import node.servlets.Config
import node.servlets.DataUploadServlet
import node.servlets.ResponseFilter
import node.utilities.AffinityExecutor
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.handler.HandlerCollection
import org.eclipse.jetty.servlet.ServletContextHandler
@ -48,7 +51,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,
networkMapAddress: NodeInfo?,advertisedServices: Set<ServiceType>,
networkMapAddress: NodeInfo?, advertisedServices: Set<ServiceType>,
clock: Clock = Clock.systemUTC(),
val clientAPIs: List<Class<*>> = listOf()) : AbstractNode(dir, configuration, networkMapAddress, advertisedServices, clock) {
companion object {
@ -106,7 +109,7 @@ class Node(dir: Path, val p2pAddr: HostAndPort, configuration: NodeConfiguration
resourceConfig.register(api)
for(customAPIClass in clientAPIs) {
val customAPI = customAPIClass.getConstructor(api.APIServer::class.java).newInstance(api)
val customAPI = customAPIClass.getConstructor(APIServer::class.java).newInstance(api)
resourceConfig.register(customAPI)
}

View File

@ -1,4 +1,4 @@
package core.testing
package node.core.testing
import com.fasterxml.jackson.module.kotlin.readValue
import com.google.common.util.concurrent.FutureCallback
@ -6,12 +6,17 @@ 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.RunOnCallerThread
import core.contracts.SignedTransaction
import core.contracts.StateAndRef
import core.crypto.SecureHash
import core.node.subsystems.linearHeadsOfType
import core.utilities.JsonSupport
import core.failure
import core.node.services.linearHeadsOfType
import core.node.services.testing.MockIdentityService
import core.random63BitValue
import core.success
import node.services.network.InMemoryMessagingNetwork
import node.utilities.JsonSupport
import protocols.TwoPartyDealProtocol
import java.security.KeyPair
import java.time.LocalDate

View File

@ -1,19 +1,22 @@
package core.testing
package node.core.testing
import com.google.common.jimfs.Configuration
import com.google.common.jimfs.Jimfs
import com.google.common.util.concurrent.Futures
import core.crypto.Party
import core.messaging.MessagingService
import core.messaging.SingleMessageRecipient
import core.node.AbstractNode
import core.node.NodeConfiguration
import core.node.NodeInfo
import core.node.PhysicalLocation
import core.node.services.NetworkMapService
import core.node.services.NotaryService
import core.node.services.ServiceType
import core.utilities.AffinityExecutor
import core.node.services.testing.MockIdentityService
import core.utilities.loggerFor
import node.core.AbstractNode
import node.services.config.NodeConfiguration
import node.services.network.InMemoryMessagingNetwork
import node.services.network.NetworkMapService
import node.services.transactions.NotaryService
import node.utilities.AffinityExecutor
import org.slf4j.Logger
import java.nio.file.Files
import java.nio.file.Path
@ -36,7 +39,7 @@ import java.util.*
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 filesystem = Jimfs.newFileSystem(Configuration.unix())
val messagingNetwork = InMemoryMessagingNetwork()
val identities = ArrayList<Party>()

View File

@ -1,18 +1,19 @@
package core.testing
package node.core.testing
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import core.node.CityDatabase
import core.node.NodeConfiguration
import core.node.NodeInfo
import core.node.PhysicalLocation
import core.node.services.NetworkMapService
import core.node.services.NodeInterestRates
import core.node.services.NotaryService
import core.node.services.ServiceType
import core.protocols.ProtocolLogic
import core.then
import core.utilities.ProgressTracker
import node.services.transactions.NotaryService
import node.services.clientapi.NodeInterestRates
import node.services.config.NodeConfiguration
import node.services.network.InMemoryMessagingNetwork
import node.services.network.NetworkMapService
import rx.Observable
import rx.subjects.PublishSubject
import java.nio.file.Path

View File

@ -0,0 +1,20 @@
@file:Suppress("UNUSED_PARAMETER")
package node.testutils
import contracts.DummyContract
import core.contracts.StateRef
import core.crypto.Party
import core.testing.DUMMY_NOTARY
import core.testing.DUMMY_NOTARY_KEY
import node.core.AbstractNode
import java.util.*
fun issueState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateRef {
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), DUMMY_NOTARY)
tx.signWith(node.storage.myLegalIdentityKey)
tx.signWith(DUMMY_NOTARY_KEY)
val stx = tx.toSignedTransaction()
node.services.recordTransactions(listOf(stx))
return StateRef(stx.id, 0)
}

View File

@ -1,4 +1,4 @@
package core.testing
package node.core.testing
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
@ -6,9 +6,10 @@ import contracts.CommercialPaper
import core.contracts.DOLLARS
import core.contracts.SignedTransaction
import core.days
import core.node.subsystems.NodeWalletService
import core.random63BitValue
import core.seconds
import node.services.network.InMemoryMessagingNetwork
import node.services.wallet.NodeWalletService
import protocols.TwoPartyTradeProtocol
import java.time.Instant

View File

@ -1,8 +1,8 @@
package core.node.services
package node.services.api
import core.messaging.Message
import core.messaging.MessagingService
import core.node.subsystems.TOPIC_DEFAULT_POSTFIX
import core.node.services.TOPIC_DEFAULT_POSTFIX
import core.serialization.deserialize
import core.serialization.serialize
import protocols.AbstractRequestMessage

View File

@ -1,4 +1,4 @@
package core.node
package node.services.api
import java.io.InputStream

View File

@ -1,4 +1,4 @@
package core.node.storage
package node.services.api
import core.crypto.sha256
import core.protocols.ProtocolStateMachine

View File

@ -0,0 +1,10 @@
package node.services.api
import com.codahale.metrics.MetricRegistry
/**
* Provides access to various metrics and ways to notify monitoring services of things, for sysadmin purposes.
* This is not an interface because it is too lightweight to bother mocking out.
*/
class MonitoringService(val metrics: MetricRegistry)

View File

@ -1,4 +1,6 @@
package core.node.services
package node.services.api
import core.node.services.ServiceType
/**
* Placeholder interface for regulator services.

View File

@ -0,0 +1,7 @@
package node.services.api
import core.node.ServiceHub
interface ServiceHubInternal : ServiceHub {
val monitoringService: MonitoringService
}

View File

@ -1,6 +1,5 @@
package core.node.services
package node.services.clientapi
import core.*
import core.contracts.*
import core.crypto.DigitalSignature
import core.crypto.Party
@ -8,12 +7,10 @@ import core.crypto.signWithECDSA
import core.math.CubicSplineInterpolator
import core.math.Interpolator
import core.math.InterpolatorFactory
import core.messaging.Message
import core.messaging.MessagingService
import core.messaging.send
import core.node.AbstractNode
import core.node.AcceptsFileUpload
import core.serialization.deserialize
import core.node.services.ServiceType
import node.core.AbstractNode
import node.services.api.AbstractNodeService
import node.services.api.AcceptsFileUpload
import org.slf4j.LoggerFactory
import protocols.RatesFixProtocol
import java.io.InputStream
@ -39,9 +36,9 @@ object NodeInterestRates {
*/
class Service(node: AbstractNode) : AcceptsFileUpload, AbstractNodeService(node.services.networkService) {
val ss = node.services.storageService
val oracle = Oracle(ss.myLegalIdentity, ss.myLegalIdentityKey)
val oracle = NodeInterestRates.Oracle(ss.myLegalIdentity, ss.myLegalIdentityKey)
private val logger = LoggerFactory.getLogger(NodeInterestRates.Service::class.java)
private val logger = LoggerFactory.getLogger(Service::class.java)
init {
addMessageHandler(RatesFixProtocol.TOPIC_SIGN,
@ -128,7 +125,7 @@ object NodeInterestRates {
}
/** Fix container, for every fix name & date pair stores a tenor to interest rate map - [InterpolatingRateMap] */
class FixContainer(val fixes: List<Fix>, val factory: InterpolatorFactory = CubicSplineInterpolator.Factory) {
class FixContainer(val fixes: List<Fix>, val factory: InterpolatorFactory = CubicSplineInterpolator) {
private val container = buildContainer(fixes)
val size = fixes.size

View File

@ -1,4 +1,4 @@
package core.node
package node.services.config
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory

View File

@ -1,4 +1,4 @@
package core.node.subsystems
package node.services.identity
import core.crypto.Party
import core.node.services.IdentityService

View File

@ -1,8 +1,8 @@
package core.node.subsystems
package node.services.keys
import core.ThreadBox
import core.crypto.generateKeyPair
import core.node.subsystems.KeyManagementService
import core.node.services.KeyManagementService
import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey

View File

@ -1,10 +1,10 @@
package core.node.subsystems
package node.services.messaging
import com.google.common.net.HostAndPort
import core.RunOnCallerThread
import core.ThreadBox
import core.messaging.*
import core.node.Node
import node.core.Node
import core.utilities.loggerFor
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.TransportConfiguration

View File

@ -1,4 +1,4 @@
package core.testing
package node.services.network
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture

View File

@ -1,4 +1,4 @@
package core.node.subsystems
package node.services.network
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors
@ -10,11 +10,17 @@ import core.messaging.MessagingService
import core.messaging.runOnNextMessage
import core.messaging.send
import core.node.NodeInfo
import core.node.services.*
import core.node.services.NetworkCacheError
import core.node.services.NetworkMapCache
import core.node.services.ServiceType
import core.node.services.TOPIC_DEFAULT_POSTFIX
import core.random63BitValue
import core.serialization.deserialize
import core.serialization.serialize
import core.utilities.AddOrRemove
import node.services.api.RegulatorService
import node.services.clientapi.NodeInterestRates
import node.services.transactions.NotaryService
import node.utilities.AddOrRemove
import java.security.PublicKey
import java.security.SignatureException
import java.util.*

View File

@ -5,13 +5,12 @@
*
* All other rights reserved.
*/
package core.testing
package node.services.network
import co.paralleluniverse.common.util.VisibleForTesting
import core.crypto.Party
import core.crypto.DummyPublicKey
import core.messaging.SingleMessageRecipient
import core.node.subsystems.InMemoryNetworkMapCache
import core.node.NodeInfo
/**

View File

@ -1,25 +1,24 @@
package core.node.services
package node.services.network
import co.paralleluniverse.common.util.VisibleForTesting
import core.crypto.Party
import core.ThreadBox
import core.crypto.DigitalSignature
import core.crypto.SecureHash
import core.crypto.SignedData
import core.crypto.signWithECDSA
import core.crypto.*
import core.messaging.MessageRecipients
import core.messaging.MessagingService
import core.messaging.SingleMessageRecipient
import core.node.NodeInfo
import core.node.subsystems.NetworkMapCache
import core.node.subsystems.TOPIC_DEFAULT_POSTFIX
import core.node.services.ServiceType
import core.node.services.NetworkMapCache
import core.node.services.TOPIC_DEFAULT_POSTFIX
import core.serialization.SerializedBytes
import core.serialization.deserialize
import core.serialization.serialize
import core.utilities.AddOrRemove
import node.services.api.AbstractNodeService
import node.utilities.AddOrRemove
import org.slf4j.LoggerFactory
import protocols.AbstractRequestMessage
import java.security.PrivateKey
import java.security.SignatureException
import java.time.Instant
import java.time.Period
import java.util.*
@ -200,7 +199,7 @@ class InMemoryNetworkMapService(net: MessagingService, home: NodeRegistration, v
try {
change = req.wireReg.verified()
} catch(e: java.security.SignatureException) {
} catch(e: SignatureException) {
throw NodeMapError.InvalidSignature()
}
val node = change.node

View File

@ -1,8 +1,9 @@
package core.node.subsystems
package node.services.persistence
import core.contracts.SignedTransaction
import core.messaging.MessagingService
import core.node.services.AbstractNodeService
import core.node.services.StorageService
import node.services.api.AbstractNodeService
import core.utilities.loggerFor
import protocols.FetchAttachmentsProtocol
import protocols.FetchDataProtocol

View File

@ -1,4 +1,4 @@
package core.node.services
package node.services.persistence
import com.codahale.metrics.MetricRegistry
import com.google.common.annotations.VisibleForTesting
@ -8,10 +8,12 @@ import com.google.common.io.CountingInputStream
import core.contracts.Attachment
import core.crypto.SecureHash
import core.extractZipFile
import core.node.AcceptsFileUpload
import node.services.api.AcceptsFileUpload
import core.node.services.AttachmentStorage
import core.utilities.loggerFor
import java.io.FilterInputStream
import java.io.InputStream
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
@ -128,7 +130,7 @@ class NodeAttachmentService(val storePath: Path, val metrics: MetricRegistry) :
try {
Files.createDirectory(extractTo)
extractZipFile(finalPath, extractTo)
} catch(e: java.nio.file.FileAlreadyExistsException) {
} catch(e: FileAlreadyExistsException) {
log.trace("Did not extract attachment jar to directory because it already exists")
} catch(e: Exception) {
log.error("Failed to extract attachment jar $id, ", e)

View File

@ -1,10 +1,12 @@
package core.node.storage
package node.services.persistence
import core.serialization.SerializedBytes
import core.serialization.deserialize
import core.serialization.serialize
import core.utilities.loggerFor
import core.utilities.trace
import node.services.api.Checkpoint
import node.services.api.CheckpointStorage
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption
@ -38,7 +40,7 @@ class PerFileCheckpointStorage(val storeDir: Path) : CheckpointStorage {
override fun addCheckpoint(checkpoint: Checkpoint) {
val serialisedCheckpoint = checkpoint.serialize()
val fileName = "${serialisedCheckpoint.hash.toString().toLowerCase()}$fileExtension"
val fileName = "${serialisedCheckpoint.hash.toString().toLowerCase()}${fileExtension}"
val checkpointFile = storeDir.resolve(fileName)
atomicWrite(checkpointFile, serialisedCheckpoint)
logger.trace { "Stored $checkpoint to $checkpointFile" }

View File

@ -1,10 +1,10 @@
package core.node.subsystems
package node.services.persistence
import core.crypto.Party
import core.contracts.SignedTransaction
import core.crypto.SecureHash
import core.node.services.AttachmentStorage
import core.node.storage.CheckpointStorage
import core.node.services.StorageService
import core.utilities.RecordingMap
import org.slf4j.LoggerFactory
import java.security.KeyPair

View File

@ -1,4 +1,4 @@
package core.protocols
package node.services.statemachine
import co.paralleluniverse.fibers.Fiber
import co.paralleluniverse.fibers.FiberScheduler
@ -7,12 +7,15 @@ import co.paralleluniverse.io.serialization.kryo.KryoSerializer
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import core.messaging.MessageRecipients
import core.messaging.StateMachineManager
import node.services.statemachine.StateMachineManager
import core.node.ServiceHub
import core.protocols.ProtocolLogic
import core.protocols.ProtocolStateMachine
import core.serialization.SerializedBytes
import core.serialization.createKryo
import core.serialization.serialize
import core.utilities.UntrustworthyData
import node.services.api.ServiceHubInternal
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@ -29,7 +32,7 @@ class ProtocolStateMachineImpl<R>(val logic: ProtocolLogic<R>, scheduler: FiberS
// These fields shouldn't be serialised, so they are marked @Transient.
@Transient private var suspendAction: ((result: StateMachineManager.FiberRequest, serialisedFiber: SerializedBytes<ProtocolStateMachineImpl<*>>) -> Unit)? = null
@Transient private var resumeWithObject: Any? = null
@Transient lateinit override var serviceHub: ServiceHub
@Transient lateinit override var serviceHub: ServiceHubInternal
@Transient private var _logger: Logger? = null
override val logger: Logger get() {
@ -54,7 +57,7 @@ class ProtocolStateMachineImpl<R>(val logic: ProtocolLogic<R>, scheduler: FiberS
logic.psm = this
}
fun prepareForResumeWith(serviceHub: ServiceHub,
fun prepareForResumeWith(serviceHub: ServiceHubInternal,
withObject: Any?,
suspendAction: (StateMachineManager.FiberRequest, SerializedBytes<ProtocolStateMachineImpl<*>>) -> Unit) {
this.suspendAction = suspendAction

View File

@ -1,4 +1,4 @@
package core.messaging
package node.services.statemachine
import co.paralleluniverse.fibers.Fiber
import co.paralleluniverse.fibers.FiberExecutorScheduler
@ -7,20 +7,22 @@ import com.codahale.metrics.Gauge
import com.esotericsoftware.kryo.io.Input
import com.google.common.base.Throwables
import com.google.common.util.concurrent.ListenableFuture
import core.node.ServiceHub
import core.node.storage.Checkpoint
import core.node.storage.CheckpointStorage
import core.messaging.MessageRecipients
import core.messaging.runOnNextMessage
import core.messaging.send
import core.protocols.ProtocolLogic
import core.protocols.ProtocolStateMachine
import core.protocols.ProtocolStateMachineImpl
import core.serialization.SerializedBytes
import core.serialization.THREAD_LOCAL_KRYO
import core.serialization.createKryo
import core.serialization.deserialize
import core.then
import core.utilities.AffinityExecutor
import core.utilities.ProgressTracker
import core.utilities.trace
import node.services.api.Checkpoint
import node.services.api.CheckpointStorage
import node.services.api.ServiceHubInternal
import node.utilities.AffinityExecutor
import java.io.PrintWriter
import java.io.StringWriter
import java.util.*
@ -54,7 +56,7 @@ import javax.annotation.concurrent.ThreadSafe
* TODO: Implement stub/skel classes that provide a basic RPC framework on top of this.
*/
@ThreadSafe
class StateMachineManager(val serviceHub: ServiceHub, val checkpointStorage: CheckpointStorage, val executor: AffinityExecutor) {
class StateMachineManager(val serviceHub: ServiceHubInternal, val checkpointStorage: CheckpointStorage, val executor: AffinityExecutor) {
inner class FiberScheduler : FiberExecutorScheduler("Same thread scheduler", executor)
val scheduler = FiberScheduler()

View File

@ -1,9 +1,11 @@
package core.node.services
package node.services.transactions
import core.crypto.Party
import core.contracts.StateRef
import core.ThreadBox
import core.contracts.StateRef
import core.contracts.WireTransaction
import core.crypto.Party
import core.node.services.UniquenessException
import core.node.services.UniquenessProvider
import java.util.*
import javax.annotation.concurrent.ThreadSafe

View File

@ -1,17 +1,21 @@
package core.node.services
package node.services.transactions
import core.crypto.Party
import core.contracts.TimestampCommand
import core.contracts.WireTransaction
import core.crypto.DigitalSignature
import core.crypto.Party
import core.crypto.SignedData
import core.crypto.signWithECDSA
import core.messaging.MessagingService
import core.node.services.ServiceType
import core.node.services.UniquenessException
import core.node.services.UniquenessProvider
import core.noneOrSingle
import core.serialization.SerializedBytes
import core.serialization.deserialize
import core.serialization.serialize
import core.utilities.loggerFor
import node.services.api.AbstractNodeService
import protocols.NotaryError
import protocols.NotaryException
import protocols.NotaryProtocol

View File

@ -1,4 +1,4 @@
package core.node.services
package node.services.transactions
import core.contracts.TimestampCommand
import core.seconds

View File

@ -1,14 +1,16 @@
package core.node.subsystems
package node.services.wallet
import com.codahale.metrics.Gauge
import contracts.Cash
import core.*
import core.ThreadBox
import core.contracts.*
import core.crypto.Party
import core.crypto.SecureHash
import core.node.ServiceHub
import core.node.services.Wallet
import core.node.services.WalletService
import core.utilities.loggerFor
import core.utilities.trace
import node.services.api.ServiceHubInternal
import java.security.PublicKey
import java.util.*
import javax.annotation.concurrent.ThreadSafe
@ -19,7 +21,7 @@ import javax.annotation.concurrent.ThreadSafe
* states relevant to us into a database and once such a wallet is implemented, this scaffolding can be removed.
*/
@ThreadSafe
class NodeWalletService(private val services: ServiceHub) : WalletService {
class NodeWalletService(private val services: ServiceHubInternal) : WalletService {
private val log = loggerFor<NodeWalletService>()
// Variables inside InnerState are protected with a lock by the ThreadBox and aren't in scope unless you're

View File

@ -1,10 +1,11 @@
package core.node.subsystems
package node.services.wallet
import contracts.Cash
import core.contracts.Amount
import core.contracts.ContractState
import core.contracts.StateAndRef
import core.contracts.sumOrThrow
import core.node.services.Wallet
import java.util.*
/**

View File

@ -1,7 +1,7 @@
package core.node.servlets
package node.servlets
import core.crypto.SecureHash
import core.node.subsystems.StorageService
import core.node.services.StorageService
import core.utilities.loggerFor
import java.io.FileNotFoundException
import javax.servlet.http.HttpServlet

View File

@ -1,8 +1,8 @@
package api
package node.servlets
import com.fasterxml.jackson.databind.ObjectMapper
import core.node.ServiceHub
import core.utilities.JsonSupport
import node.utilities.JsonSupport
import javax.ws.rs.ext.ContextResolver
import javax.ws.rs.ext.Provider
@ -13,5 +13,5 @@ import javax.ws.rs.ext.Provider
@Provider
class Config(val services: ServiceHub) : ContextResolver<ObjectMapper> {
val defaultObjectMapper = JsonSupport.createDefaultMapper(services.identityService)
override fun getContext(type: java.lang.Class<*>) = defaultObjectMapper
override fun getContext(type: Class<*>) = defaultObjectMapper
}

View File

@ -1,7 +1,7 @@
package core.node.servlets
package node.servlets
import core.node.AcceptsFileUpload
import core.node.Node
import node.services.api.AcceptsFileUpload
import node.core.Node
import core.utilities.loggerFor
import org.apache.commons.fileupload.servlet.ServletFileUpload
import java.util.*

View File

@ -1,4 +1,4 @@
package api
package node.servlets
import javax.ws.rs.container.ContainerRequestContext
import javax.ws.rs.container.ContainerResponseContext

View File

@ -1,5 +1,8 @@
package core.utilities
package node.utilities
import core.utilities.BriefLogFormatter
import core.utilities.Emoji
import core.utilities.ProgressTracker
import org.fusesource.jansi.Ansi
import org.fusesource.jansi.AnsiConsole
import org.fusesource.jansi.AnsiOutputStream

View File

@ -1,4 +1,4 @@
package core.utilities
package node.utilities
/**
* Enum for when adding/removing something, for example adding or removing an entry in a directory.

View File

@ -1,6 +1,7 @@
package core.utilities
package node.utilities
import com.google.common.util.concurrent.Uninterruptibles
import core.utilities.loggerFor
import java.util.*
import java.util.concurrent.*
import java.util.function.Supplier

View File

@ -1,4 +1,4 @@
package core.utilities
package node.utilities
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParseException

View File

@ -1,458 +0,0 @@
import contracts.Cash
import contracts.DummyContract
import contracts.InsufficientBalanceException
import core.*
import core.contracts.*
import core.crypto.Party
import core.crypto.SecureHash
import core.serialization.OpaqueBytes
import core.testutils.*
import org.junit.Test
import java.security.PublicKey
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
class CashTests {
val inState = Cash.State(
deposit = MEGA_CORP.ref(1),
amount = 1000.DOLLARS,
owner = DUMMY_PUBKEY_1,
notary = DUMMY_NOTARY
)
val outState = inState.copy(owner = DUMMY_PUBKEY_2)
fun Cash.State.editDepositRef(ref: Byte) = copy(deposit = deposit.copy(reference = OpaqueBytes.of(ref)))
@Test
fun trivial() {
transaction {
input { inState }
this `fails requirement` "the amounts balance"
tweak {
output { outState.copy(amount = 2000.DOLLARS) }
this `fails requirement` "the amounts balance"
}
tweak {
output { outState }
// No command arguments
this `fails requirement` "required contracts.Cash.Commands.Move command"
}
tweak {
output { outState }
arg(DUMMY_PUBKEY_2) { Cash.Commands.Move() }
this `fails requirement` "the owning keys are the same as the signing keys"
}
tweak {
output { outState }
output { outState `issued by` MINI_CORP }
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this `fails requirement` "at least one cash input"
}
// Simple reallocation works.
tweak {
output { outState }
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this.accepts()
}
}
}
@Test
fun issueMoney() {
// Check we can't "move" money into existence.
transaction {
input { DummyContract.State(notary = DUMMY_NOTARY) }
output { outState }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
this `fails requirement` "there is at least one cash input"
}
// Check we can issue money only as long as the issuer institution is a command signer, i.e. any recognised
// institution is allowed to issue as much cash as they want.
transaction {
output { outState }
arg(DUMMY_PUBKEY_1) { Cash.Commands.Issue() }
this `fails requirement` "output deposits are owned by a command signer"
}
transaction {
output {
Cash.State(
amount = 1000.DOLLARS,
owner = DUMMY_PUBKEY_1,
deposit = MINI_CORP.ref(12, 34),
notary = DUMMY_NOTARY
)
}
tweak {
arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue(0) }
this `fails requirement` "has a nonce"
}
arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue() }
this.accepts()
}
// Test generation works.
val ptx = TransactionBuilder()
Cash().generateIssue(ptx, 100.DOLLARS, MINI_CORP.ref(12, 34), owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
assertTrue(ptx.inputStates().isEmpty())
val s = ptx.outputStates()[0] as Cash.State
assertEquals(100.DOLLARS, s.amount)
assertEquals(MINI_CORP, s.deposit.party)
assertEquals(DUMMY_PUBKEY_1, s.owner)
assertTrue(ptx.commands()[0].value is Cash.Commands.Issue)
assertEquals(MINI_CORP_PUBKEY, ptx.commands()[0].signers[0])
// Test issuance from the issuance definition
val issuanceDef = Cash.IssuanceDefinition(MINI_CORP.ref(12, 34), USD)
val templatePtx = TransactionBuilder()
Cash().generateIssue(templatePtx, issuanceDef, 100.DOLLARS.pennies, owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
assertTrue(templatePtx.inputStates().isEmpty())
assertEquals(ptx.outputStates()[0], templatePtx.outputStates()[0])
// We can consume $1000 in a transaction and output $2000 as long as it's signed by an issuer.
transaction {
input { inState }
output { inState.copy(amount = inState.amount * 2) }
// Move fails: not allowed to summon money.
tweak {
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this `fails requirement` "at issuer MegaCorp the amounts balance"
}
// Issue works.
tweak {
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
this.accepts()
}
}
// Can't use an issue command to lower the amount.
transaction {
input { inState }
output { inState.copy(amount = inState.amount / 2) }
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
this `fails requirement` "output values sum to more than the inputs"
}
// Can't have an issue command that doesn't actually issue money.
transaction {
input { inState }
output { inState }
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
this `fails requirement` "output values sum to more than the inputs"
}
// Can't have any other commands if we have an issue command (because the issue command overrules them)
transaction {
input { inState }
output { inState.copy(amount = inState.amount * 2) }
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
tweak {
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
this `fails requirement` "there is only a single issue command"
}
tweak {
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
this `fails requirement` "there is only a single issue command"
}
tweak {
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(inState.amount / 2) }
this `fails requirement` "there is only a single issue command"
}
this.accepts()
}
}
@Test
fun testMergeSplit() {
// Splitting value works.
transaction {
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
tweak {
input { inState }
for (i in 1..4) output { inState.copy(amount = inState.amount / 4) }
this.accepts()
}
// Merging 4 inputs into 2 outputs works.
tweak {
for (i in 1..4) input { inState.copy(amount = inState.amount / 4) }
output { inState.copy(amount = inState.amount / 2) }
output { inState.copy(amount = inState.amount / 2) }
this.accepts()
}
// Merging 2 inputs into 1 works.
tweak {
input { inState.copy(amount = inState.amount / 2) }
input { inState.copy(amount = inState.amount / 2) }
output { inState }
this.accepts()
}
}
}
@Test
fun zeroSizedValues() {
transaction {
input { inState }
input { inState.copy(amount = 0.DOLLARS) }
this `fails requirement` "zero sized inputs"
}
transaction {
input { inState }
output { inState }
output { inState.copy(amount = 0.DOLLARS) }
this `fails requirement` "zero sized outputs"
}
}
@Test
fun trivialMismatches() {
// Can't change issuer.
transaction {
input { inState }
output { outState `issued by` MINI_CORP }
this `fails requirement` "at issuer MegaCorp the amounts balance"
}
// Can't change deposit reference when splitting.
transaction {
input { inState }
output { outState.editDepositRef(0).copy(amount = inState.amount / 2) }
output { outState.editDepositRef(1).copy(amount = inState.amount / 2) }
this `fails requirement` "for deposit [01] at issuer MegaCorp the amounts balance"
}
// Can't mix currencies.
transaction {
input { inState }
output { outState.copy(amount = 800.DOLLARS) }
output { outState.copy(amount = 200.POUNDS) }
this `fails requirement` "the amounts balance"
}
transaction {
input { inState }
input {
inState.copy(
amount = 150.POUNDS,
owner = DUMMY_PUBKEY_2
)
}
output { outState.copy(amount = 1150.DOLLARS) }
this `fails requirement` "the amounts balance"
}
// Can't have superfluous input states from different issuers.
transaction {
input { inState }
input { inState `issued by` MINI_CORP }
output { outState }
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this `fails requirement` "at issuer MiniCorp the amounts balance"
}
// Can't combine two different deposits at the same issuer.
transaction {
input { inState }
input { inState.editDepositRef(3) }
output { outState.copy(amount = inState.amount * 2).editDepositRef(3) }
this `fails requirement` "for deposit [01]"
}
}
@Test
fun exitLedger() {
// Single input/output straightforward case.
transaction {
input { inState }
output { outState.copy(amount = inState.amount - 200.DOLLARS) }
tweak {
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(100.DOLLARS) }
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this `fails requirement` "the amounts balance"
}
tweak {
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS) }
this `fails requirement` "required contracts.Cash.Commands.Move command"
tweak {
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this.accepts()
}
}
}
// Multi-issuer case.
transaction {
input { inState }
input { inState `issued by` MINI_CORP }
output { inState.copy(amount = inState.amount - 200.DOLLARS) `issued by` MINI_CORP }
output { inState.copy(amount = inState.amount - 200.DOLLARS) }
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this `fails requirement` "at issuer MegaCorp the amounts balance"
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS) }
this `fails requirement` "at issuer MiniCorp the amounts balance"
arg(MINI_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS) }
this.accepts()
}
}
@Test
fun multiIssuer() {
transaction {
// Gather 2000 dollars from two different issuers.
input { inState }
input { inState `issued by` MINI_CORP }
// Can't merge them together.
tweak {
output { inState.copy(owner = DUMMY_PUBKEY_2, amount = 2000.DOLLARS) }
this `fails requirement` "at issuer MegaCorp the amounts balance"
}
// Missing MiniCorp deposit
tweak {
output { inState.copy(owner = DUMMY_PUBKEY_2) }
output { inState.copy(owner = DUMMY_PUBKEY_2) }
this `fails requirement` "at issuer MegaCorp the amounts balance"
}
// This works.
output { inState.copy(owner = DUMMY_PUBKEY_2) }
output { inState.copy(owner = DUMMY_PUBKEY_2) `issued by` MINI_CORP }
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this.accepts()
}
}
@Test
fun multiCurrency() {
// Check we can do an atomic currency trade tx.
transaction {
val pounds = Cash.State(MINI_CORP.ref(3, 4, 5), 658.POUNDS, DUMMY_PUBKEY_2, DUMMY_NOTARY)
input { inState `owned by` DUMMY_PUBKEY_1 }
input { pounds }
output { inState `owned by` DUMMY_PUBKEY_2 }
output { pounds `owned by` DUMMY_PUBKEY_1 }
arg(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2) { Cash.Commands.Move() }
this.accepts()
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Spend tx generation
val OUR_PUBKEY_1 = DUMMY_PUBKEY_1
val THEIR_PUBKEY_1 = DUMMY_PUBKEY_2
fun makeCash(amount: Amount, corp: Party, depositRef: Byte = 1) =
StateAndRef(
Cash.State(corp.ref(depositRef), amount, OUR_PUBKEY_1, DUMMY_NOTARY),
StateRef(SecureHash.randomSHA256(), Random().nextInt(32))
)
val WALLET = listOf(
makeCash(100.DOLLARS, MEGA_CORP),
makeCash(400.DOLLARS, MEGA_CORP),
makeCash(80.DOLLARS, MINI_CORP),
makeCash(80.SWISS_FRANCS, MINI_CORP, 2)
)
fun makeSpend(amount: Amount, dest: PublicKey): WireTransaction {
val tx = TransactionBuilder()
Cash().generateSpend(tx, amount, dest, WALLET)
return tx.toWireTransaction()
}
@Test
fun generateSimpleDirectSpend() {
val wtx = makeSpend(100.DOLLARS, THEIR_PUBKEY_1)
assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1), wtx.outputs[0])
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
}
@Test
fun generateSimpleSpendWithParties() {
val tx = TransactionBuilder()
Cash().generateSpend(tx, 80.DOLLARS, ALICE_PUBKEY, WALLET, setOf(MINI_CORP))
assertEquals(WALLET[2].ref, tx.inputStates()[0])
}
@Test
fun generateSimpleSpendWithChange() {
val wtx = makeSpend(10.DOLLARS, THEIR_PUBKEY_1)
assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 10.DOLLARS), wtx.outputs[0])
assertEquals(WALLET[0].state.copy(amount = 90.DOLLARS), wtx.outputs[1])
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
}
@Test
fun generateSpendWithTwoInputs() {
val wtx = makeSpend(500.DOLLARS, THEIR_PUBKEY_1)
assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[1].ref, wtx.inputs[1])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS), wtx.outputs[0])
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
}
@Test
fun generateSpendMixedDeposits() {
val wtx = makeSpend(580.DOLLARS, THEIR_PUBKEY_1)
assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[1].ref, wtx.inputs[1])
assertEquals(WALLET[2].ref, wtx.inputs[2])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS), wtx.outputs[0])
assertEquals(WALLET[2].state.copy(owner = THEIR_PUBKEY_1), wtx.outputs[1])
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
}
@Test
fun generateSpendInsufficientBalance() {
val e: InsufficientBalanceException = assertFailsWith("balance") {
makeSpend(1000.DOLLARS, THEIR_PUBKEY_1)
}
assertEquals((1000 - 580).DOLLARS, e.amountMissing)
assertFailsWith(InsufficientBalanceException::class) {
makeSpend(81.SWISS_FRANCS, THEIR_PUBKEY_1)
}
}
/**
* Confirm that aggregation of states is correctly modelled.
*/
@Test
fun aggregation() {
val fiveThousandDollarsFromMega = Cash.State(MEGA_CORP.ref(2), 5000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
val twoThousandDollarsFromMega = Cash.State(MEGA_CORP.ref(2), 2000.DOLLARS, MINI_CORP_PUBKEY, DUMMY_NOTARY)
val oneThousandDollarsFromMini = Cash.State(MINI_CORP.ref(3), 1000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
// Obviously it must be possible to aggregate states with themselves
assertEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.issuanceDef)
// Owner is not considered when calculating whether it is possible to aggregate states
assertEquals(fiveThousandDollarsFromMega.issuanceDef, twoThousandDollarsFromMega.issuanceDef)
// States cannot be aggregated if the deposit differs
assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, oneThousandDollarsFromMini.issuanceDef)
assertNotEquals(twoThousandDollarsFromMega.issuanceDef, oneThousandDollarsFromMini.issuanceDef)
// States cannot be aggregated if the currency differs
assertNotEquals(oneThousandDollarsFromMini.issuanceDef,
Cash.State(MINI_CORP.ref(3), 1000.POUNDS, MEGA_CORP_PUBKEY, DUMMY_NOTARY).issuanceDef)
// States cannot be aggregated if the reference differs
assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.copy(deposit = MEGA_CORP.ref(1)).issuanceDef)
assertNotEquals(fiveThousandDollarsFromMega.copy(deposit = MEGA_CORP.ref(1)).issuanceDef, fiveThousandDollarsFromMega.issuanceDef)
}
}

View File

@ -1,246 +0,0 @@
package contracts
import core.*
import core.contracts.*
import core.crypto.SecureHash
import core.testutils.*
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.time.Instant
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
interface ICommercialPaperTestTemplate {
open fun getPaper(): ICommercialPaperState
open fun getIssueCommand(): CommandData
open fun getRedeemCommand(): CommandData
open fun getMoveCommand(): CommandData
}
class JavaCommercialPaperTest() : ICommercialPaperTestTemplate {
override fun getPaper(): ICommercialPaperState = JavaCommercialPaper.State(
MEGA_CORP.ref(123),
MEGA_CORP_PUBKEY,
1000.DOLLARS,
TEST_TX_TIME + 7.days,
DUMMY_NOTARY
)
override fun getIssueCommand(): CommandData = JavaCommercialPaper.Commands.Issue()
override fun getRedeemCommand(): CommandData = JavaCommercialPaper.Commands.Redeem()
override fun getMoveCommand(): CommandData = JavaCommercialPaper.Commands.Move()
}
class KotlinCommercialPaperTest() : ICommercialPaperTestTemplate {
override fun getPaper(): ICommercialPaperState = CommercialPaper.State(
issuance = MEGA_CORP.ref(123),
owner = MEGA_CORP_PUBKEY,
faceValue = 1000.DOLLARS,
maturityDate = TEST_TX_TIME + 7.days,
notary = DUMMY_NOTARY
)
override fun getIssueCommand(): CommandData = CommercialPaper.Commands.Issue()
override fun getRedeemCommand(): CommandData = CommercialPaper.Commands.Redeem()
override fun getMoveCommand(): CommandData = CommercialPaper.Commands.Move()
}
@RunWith(Parameterized::class)
class CommercialPaperTestsGeneric {
companion object {
@Parameterized.Parameters @JvmStatic
fun data() = listOf(JavaCommercialPaperTest(), KotlinCommercialPaperTest())
}
@Parameterized.Parameter
lateinit var thisTest: ICommercialPaperTestTemplate
val attachments = MockStorageService().attachments
@Test
fun ok() {
trade().verify()
}
@Test
fun `not matured at redemption`() {
trade(redemptionTime = TEST_TX_TIME + 2.days).expectFailureOfTx(3, "must have matured")
}
@Test
fun `key mismatch at issue`() {
transactionGroup {
transaction {
output { thisTest.getPaper() }
arg(DUMMY_PUBKEY_1) { thisTest.getIssueCommand() }
timestamp(TEST_TX_TIME)
}
expectFailureOfTx(1, "signed by the claimed issuer")
}
}
@Test
fun `face value is not zero`() {
transactionGroup {
transaction {
output { thisTest.getPaper().withFaceValue(0.DOLLARS) }
arg(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
timestamp(TEST_TX_TIME)
}
expectFailureOfTx(1, "face value is not zero")
}
}
@Test
fun `maturity date not in the past`() {
transactionGroup {
transaction {
output { thisTest.getPaper().withMaturityDate(TEST_TX_TIME - 10.days) }
arg(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
timestamp(TEST_TX_TIME)
}
expectFailureOfTx(1, "maturity date is not in the past")
}
}
@Test
fun `issue cannot replace an existing state`() {
transactionGroup {
roots {
transaction(thisTest.getPaper() label "paper")
}
transaction {
input("paper")
output { thisTest.getPaper() }
arg(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
timestamp(TEST_TX_TIME)
}
expectFailureOfTx(1, "there is no input state")
}
}
@Test
fun `did not receive enough money at redemption`() {
trade(aliceGetsBack = 700.DOLLARS).expectFailureOfTx(3, "received amount equals the face value")
}
@Test
fun `paper must be destroyed by redemption`() {
trade(destroyPaperAtRedemption = false).expectFailureOfTx(3, "must be destroyed")
}
fun cashOutputsToWallet(vararg states: Cash.State): Pair<LedgerTransaction, List<StateAndRef<Cash.State>>> {
val ltx = LedgerTransaction(emptyList(), emptyList(), listOf(*states), emptyList(), SecureHash.randomSHA256())
return Pair(ltx, states.mapIndexed { index, state -> StateAndRef(state, StateRef(ltx.id, index)) })
}
@Test
fun `issue move and then redeem`() {
// MiniCorp issues $10,000 of commercial paper, to mature in 30 days, owned initially by itself.
val issueTX: LedgerTransaction = run {
val ptx = CommercialPaper().generateIssue(MINI_CORP.ref(123), 10000.DOLLARS, TEST_TX_TIME + 30.days, DUMMY_NOTARY).apply {
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
signWith(MINI_CORP_KEY)
signWith(DUMMY_NOTARY_KEY)
}
val stx = ptx.toSignedTransaction()
stx.verifyToLedgerTransaction(MockIdentityService, attachments)
}
val (alicesWalletTX, alicesWallet) = cashOutputsToWallet(
3000.DOLLARS.CASH `owned by` ALICE_PUBKEY,
3000.DOLLARS.CASH `owned by` ALICE_PUBKEY,
3000.DOLLARS.CASH `owned by` ALICE_PUBKEY
)
// Alice pays $9000 to MiniCorp to own some of their debt.
val moveTX: LedgerTransaction = run {
val ptx = TransactionBuilder()
Cash().generateSpend(ptx, 9000.DOLLARS, MINI_CORP_PUBKEY, alicesWallet)
CommercialPaper().generateMove(ptx, issueTX.outRef(0), ALICE_PUBKEY)
ptx.signWith(MINI_CORP_KEY)
ptx.signWith(ALICE_KEY)
ptx.signWith(DUMMY_NOTARY_KEY)
ptx.toSignedTransaction().verifyToLedgerTransaction(MockIdentityService, attachments)
}
// Won't be validated.
val (corpWalletTX, corpWallet) = cashOutputsToWallet(
9000.DOLLARS.CASH `owned by` MINI_CORP_PUBKEY,
4000.DOLLARS.CASH `owned by` MINI_CORP_PUBKEY
)
fun makeRedeemTX(time: Instant): LedgerTransaction {
val ptx = TransactionBuilder()
ptx.setTime(time, DUMMY_NOTARY, 30.seconds)
CommercialPaper().generateRedeem(ptx, moveTX.outRef(1), corpWallet)
ptx.signWith(ALICE_KEY)
ptx.signWith(MINI_CORP_KEY)
ptx.signWith(DUMMY_NOTARY_KEY)
return ptx.toSignedTransaction().verifyToLedgerTransaction(MockIdentityService, attachments)
}
val tooEarlyRedemption = makeRedeemTX(TEST_TX_TIME + 10.days)
val validRedemption = makeRedeemTX(TEST_TX_TIME + 31.days)
val e = assertFailsWith(TransactionVerificationException::class) {
TransactionGroup(setOf(issueTX, moveTX, tooEarlyRedemption), setOf(corpWalletTX, alicesWalletTX)).verify()
}
assertTrue(e.cause!!.message!!.contains("paper must have matured"))
TransactionGroup(setOf(issueTX, moveTX, validRedemption), setOf(corpWalletTX, alicesWalletTX)).verify()
}
// Generate a trade lifecycle with various parameters.
fun trade(redemptionTime: Instant = TEST_TX_TIME + 8.days,
aliceGetsBack: Amount = 1000.DOLLARS,
destroyPaperAtRedemption: Boolean = true): TransactionGroupDSL<ICommercialPaperState> {
val someProfits = 1200.DOLLARS
return transactionGroupFor() {
roots {
transaction(900.DOLLARS.CASH `owned by` ALICE_PUBKEY label "alice's $900")
transaction(someProfits.CASH `owned by` MEGA_CORP_PUBKEY label "some profits")
}
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
output("paper") { thisTest.getPaper() }
arg(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
timestamp(TEST_TX_TIME)
}
// The CP is sold to alice for her $900, $100 less than the face value. At 10% interest after only 7 days,
// that sounds a bit too good to be true!
transaction("Trade") {
input("paper")
input("alice's $900")
output("borrowed $900") { 900.DOLLARS.CASH `owned by` MEGA_CORP_PUBKEY }
output("alice's paper") { "paper".output `owned by` ALICE_PUBKEY }
arg(ALICE_PUBKEY) { Cash.Commands.Move() }
arg(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
}
// Time passes, and Alice redeem's her CP for $1000, netting a $100 profit. MegaCorp has received $1200
// as a single payment from somewhere and uses it to pay Alice off, keeping the remaining $200 as change.
transaction("Redemption") {
input("alice's paper")
input("some profits")
output("Alice's profit") { aliceGetsBack.CASH `owned by` ALICE_PUBKEY }
output("Change") { (someProfits - aliceGetsBack).CASH `owned by` MEGA_CORP_PUBKEY }
if (!destroyPaperAtRedemption)
output { "paper".output }
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
arg(ALICE_PUBKEY) { thisTest.getRedeemCommand() }
timestamp(redemptionTime)
}
}
}
}

View File

@ -1,160 +0,0 @@
package contracts
import core.*
import core.contracts.*
import core.crypto.SecureHash
import core.testutils.*
import org.junit.Test
import java.time.Instant
import java.util.*
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class CrowdFundTests {
val CF_1 = CrowdFund.State(
campaign = CrowdFund.Campaign(
owner = MINI_CORP_PUBKEY,
name = "kickstart me",
target = 1000.DOLLARS,
closingTime = TEST_TX_TIME + 7.days
),
closed = false,
pledges = ArrayList<CrowdFund.Pledge>(),
notary = DUMMY_NOTARY
)
val attachments = MockStorageService().attachments
@Test
fun `key mismatch at issue`() {
transactionGroup {
transaction {
output { CF_1 }
arg(DUMMY_PUBKEY_1) { CrowdFund.Commands.Register() }
timestamp(TEST_TX_TIME)
}
expectFailureOfTx(1, "the transaction is signed by the owner of the crowdsourcing")
}
}
@Test
fun `closing time not in the future`() {
transactionGroup {
transaction {
output { CF_1.copy(campaign = CF_1.campaign.copy(closingTime = TEST_TX_TIME - 1.days)) }
arg(MINI_CORP_PUBKEY) { CrowdFund.Commands.Register() }
timestamp(TEST_TX_TIME)
}
expectFailureOfTx(1, "the output registration has a closing time in the future")
}
}
@Test
fun ok() {
raiseFunds().verify()
}
private fun raiseFunds(): TransactionGroupDSL<CrowdFund.State> {
return transactionGroupFor {
roots {
transaction(1000.DOLLARS.CASH `owned by` ALICE_PUBKEY label "alice's $1000")
}
// 1. Create the funding opportunity
transaction {
output("funding opportunity") { CF_1 }
arg(MINI_CORP_PUBKEY) { CrowdFund.Commands.Register() }
timestamp(TEST_TX_TIME)
}
// 2. Place a pledge
transaction {
input ("funding opportunity")
input("alice's $1000")
output ("pledged opportunity") {
CF_1.copy(
pledges = CF_1.pledges + CrowdFund.Pledge(ALICE_PUBKEY, 1000.DOLLARS)
)
}
output { 1000.DOLLARS.CASH `owned by` MINI_CORP_PUBKEY }
arg(ALICE_PUBKEY) { Cash.Commands.Move() }
arg(ALICE_PUBKEY) { CrowdFund.Commands.Pledge() }
timestamp(TEST_TX_TIME)
}
// 3. Close the opportunity, assuming the target has been met
transaction {
input ("pledged opportunity")
output ("funded and closed") { "pledged opportunity".output.copy(closed = true) }
arg(MINI_CORP_PUBKEY) { CrowdFund.Commands.Close() }
timestamp(time = TEST_TX_TIME + 8.days)
}
}
}
fun cashOutputsToWallet(vararg states: Cash.State): Pair<LedgerTransaction, List<StateAndRef<Cash.State>>> {
val ltx = LedgerTransaction(emptyList(), emptyList(), listOf(*states), emptyList(), SecureHash.randomSHA256())
return Pair(ltx, states.mapIndexed { index, state -> StateAndRef(state, StateRef(ltx.id, index)) })
}
@Test
fun `raise more funds using output-state generation functions`() {
// MiniCorp registers a crowdfunding of $1,000, to close in 7 days.
val registerTX: LedgerTransaction = run {
// craftRegister returns a partial transaction
val ptx = CrowdFund().generateRegister(MINI_CORP.ref(123), 1000.DOLLARS, "crowd funding", TEST_TX_TIME + 7.days, DUMMY_NOTARY).apply {
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
signWith(MINI_CORP_KEY)
signWith(DUMMY_NOTARY_KEY)
}
ptx.toSignedTransaction().verifyToLedgerTransaction(MockIdentityService, attachments)
}
// let's give Alice some funds that she can invest
val (aliceWalletTX, aliceWallet) = cashOutputsToWallet(
200.DOLLARS.CASH `owned by` ALICE_PUBKEY,
500.DOLLARS.CASH `owned by` ALICE_PUBKEY,
300.DOLLARS.CASH `owned by` ALICE_PUBKEY
)
// Alice pays $1000 to MiniCorp to fund their campaign.
val pledgeTX: LedgerTransaction = run {
val ptx = TransactionBuilder()
CrowdFund().generatePledge(ptx, registerTX.outRef(0), ALICE_PUBKEY)
Cash().generateSpend(ptx, 1000.DOLLARS, MINI_CORP_PUBKEY, aliceWallet)
ptx.setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
ptx.signWith(ALICE_KEY)
ptx.signWith(DUMMY_NOTARY_KEY)
// this verify passes - the transaction contains an output cash, necessary to verify the fund command
ptx.toSignedTransaction().verifyToLedgerTransaction(MockIdentityService, attachments)
}
// Won't be validated.
val (miniCorpWalletTx, miniCorpWallet) = cashOutputsToWallet(
900.DOLLARS.CASH `owned by` MINI_CORP_PUBKEY,
400.DOLLARS.CASH `owned by` MINI_CORP_PUBKEY
)
// MiniCorp closes their campaign.
fun makeFundedTX(time: Instant): LedgerTransaction {
val ptx = TransactionBuilder()
ptx.setTime(time, DUMMY_NOTARY, 30.seconds)
CrowdFund().generateClose(ptx, pledgeTX.outRef(0), miniCorpWallet)
ptx.signWith(MINI_CORP_KEY)
ptx.signWith(DUMMY_NOTARY_KEY)
return ptx.toSignedTransaction().verifyToLedgerTransaction(MockIdentityService, attachments)
}
val tooEarlyClose = makeFundedTX(TEST_TX_TIME + 6.days)
val validClose = makeFundedTX(TEST_TX_TIME + 8.days)
val e = assertFailsWith(TransactionVerificationException::class) {
TransactionGroup(setOf(registerTX, pledgeTX, tooEarlyClose), setOf(miniCorpWalletTx, aliceWalletTX)).verify()
}
assertTrue(e.cause!!.message!!.contains("the closing date has past"))
// This verification passes
TransactionGroup(setOf(registerTX, pledgeTX, validClose), setOf(aliceWalletTX)).verify()
}
}

View File

@ -1,733 +0,0 @@
package contracts
import core.*
import core.contracts.*
import core.testutils.*
import org.junit.Test
import java.math.BigDecimal
import java.time.LocalDate
import java.util.*
fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
return when (irsSelect) {
1 -> {
val fixedLeg = InterestRateSwap.FixedLeg(
fixedRatePayer = MEGA_CORP,
notional = 15900000.DOLLARS,
paymentFrequency = Frequency.SemiAnnual,
effectiveDate = LocalDate.of(2016, 3, 10),
effectiveDateAdjustment = null,
terminationDate = LocalDate.of(2026, 3, 10),
terminationDateAdjustment = null,
fixedRate = FixedRate(PercentageRatioUnit("1.677")),
dayCountBasisDay = DayCountBasisDay.D30,
dayCountBasisYear = DayCountBasisYear.Y360,
rollConvention = DateRollConvention.ModifiedFollowing,
dayInMonth = 10,
paymentRule = PaymentRule.InArrears,
paymentDelay = 0,
paymentCalendar = BusinessCalendar.getInstance("London", "NewYork"),
interestPeriodAdjustment = AccrualAdjustment.Adjusted
)
val floatingLeg = InterestRateSwap.FloatingLeg(
floatingRatePayer = MINI_CORP,
notional = 15900000.DOLLARS,
paymentFrequency = Frequency.Quarterly,
effectiveDate = LocalDate.of(2016, 3, 10),
effectiveDateAdjustment = null,
terminationDate = LocalDate.of(2026, 3, 10),
terminationDateAdjustment = null,
dayCountBasisDay = DayCountBasisDay.D30,
dayCountBasisYear = DayCountBasisYear.Y360,
rollConvention = DateRollConvention.ModifiedFollowing,
fixingRollConvention = DateRollConvention.ModifiedFollowing,
dayInMonth = 10,
resetDayInMonth = 10,
paymentRule = PaymentRule.InArrears,
paymentDelay = 0,
paymentCalendar = BusinessCalendar.getInstance("London", "NewYork"),
interestPeriodAdjustment = AccrualAdjustment.Adjusted,
fixingPeriod = DateOffset.TWODAYS,
resetRule = PaymentRule.InAdvance,
fixingsPerPayment = Frequency.Quarterly,
fixingCalendar = BusinessCalendar.getInstance("London"),
index = "LIBOR",
indexSource = "TEL3750",
indexTenor = Tenor("3M")
)
val calculation = InterestRateSwap.Calculation (
// TODO: this seems to fail quite dramatically
//expression = "fixedLeg.notional * fixedLeg.fixedRate",
// TODO: How I want it to look
//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 = Expression("( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) -" +
"(floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))"),
floatingLegPaymentSchedule = HashMap(),
fixedLegPaymentSchedule = HashMap()
)
val EUR = currency("EUR")
val common = InterestRateSwap.Common(
baseCurrency = EUR,
eligibleCurrency = EUR,
eligibleCreditSupport = "Cash in an Eligible Currency",
independentAmounts = Amount(0, EUR),
threshold = Amount(0, EUR),
minimumTransferAmount = Amount(250000 * 100, EUR),
rounding = Amount(10000 * 100, 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 = ReferenceRate("T3270", Tenor("6M"), "EONIA"),
addressForTransfers = "",
exposure = UnknownType(),
localBusinessDay = BusinessCalendar.getInstance("London"),
tradeID = "trade1",
hashLegalDocs = "put hash here",
dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")
)
InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common, notary = DUMMY_NOTARY)
}
2 -> {
// 10y swap, we pay 1.3% fixed 30/360 semi, rec 3m usd libor act/360 Q on 25m notional (mod foll/adj on both sides)
// I did a mock up start date 10/03/2015 10/03/2025 so you have 5 cashflows on float side that have been preset the rest are unknown
val fixedLeg = InterestRateSwap.FixedLeg(
fixedRatePayer = MEGA_CORP,
notional = 25000000.DOLLARS,
paymentFrequency = Frequency.SemiAnnual,
effectiveDate = LocalDate.of(2015, 3, 10),
effectiveDateAdjustment = null,
terminationDate = LocalDate.of(2025, 3, 10),
terminationDateAdjustment = null,
fixedRate = FixedRate(PercentageRatioUnit("1.3")),
dayCountBasisDay = DayCountBasisDay.D30,
dayCountBasisYear = DayCountBasisYear.Y360,
rollConvention = DateRollConvention.ModifiedFollowing,
dayInMonth = 10,
paymentRule = PaymentRule.InArrears,
paymentDelay = 0,
paymentCalendar = BusinessCalendar.getInstance(),
interestPeriodAdjustment = AccrualAdjustment.Adjusted
)
val floatingLeg = InterestRateSwap.FloatingLeg(
floatingRatePayer = MINI_CORP,
notional = 25000000.DOLLARS,
paymentFrequency = Frequency.Quarterly,
effectiveDate = LocalDate.of(2015, 3, 10),
effectiveDateAdjustment = null,
terminationDate = LocalDate.of(2025, 3, 10),
terminationDateAdjustment = null,
dayCountBasisDay = DayCountBasisDay.DActual,
dayCountBasisYear = DayCountBasisYear.Y360,
rollConvention = DateRollConvention.ModifiedFollowing,
fixingRollConvention = DateRollConvention.ModifiedFollowing,
dayInMonth = 10,
resetDayInMonth = 10,
paymentRule = PaymentRule.InArrears,
paymentDelay = 0,
paymentCalendar = BusinessCalendar.getInstance(),
interestPeriodAdjustment = AccrualAdjustment.Adjusted,
fixingPeriod = DateOffset.TWODAYS,
resetRule = PaymentRule.InAdvance,
fixingsPerPayment = Frequency.Quarterly,
fixingCalendar = BusinessCalendar.getInstance(),
index = "USD LIBOR",
indexSource = "TEL3750",
indexTenor = Tenor("3M")
)
val calculation = InterestRateSwap.Calculation (
// TODO: this seems to fail quite dramatically
//expression = "fixedLeg.notional * fixedLeg.fixedRate",
// TODO: How I want it to look
//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 = Expression("( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) -" +
"(floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))"),
floatingLegPaymentSchedule = HashMap(),
fixedLegPaymentSchedule = HashMap()
)
val EUR = currency("EUR")
val common = InterestRateSwap.Common(
baseCurrency = EUR,
eligibleCurrency = EUR,
eligibleCreditSupport = "Cash in an Eligible Currency",
independentAmounts = Amount(0, EUR),
threshold = Amount(0, EUR),
minimumTransferAmount = Amount(250000 * 100, EUR),
rounding = Amount(10000 * 100, 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 = ReferenceRate("T3270", Tenor("6M"), "EONIA"),
addressForTransfers = "",
exposure = UnknownType(),
localBusinessDay = BusinessCalendar.getInstance("London"),
tradeID = "trade2",
hashLegalDocs = "put hash here",
dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")
)
return InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common, notary = DUMMY_NOTARY)
}
else -> TODO("IRS number $irsSelect not defined")
}
}
class IRSTests {
val attachments = MockStorageService().attachments
val exampleIRS = createDummyIRS(1)
val inState = InterestRateSwap.State(
exampleIRS.fixedLeg,
exampleIRS.floatingLeg,
exampleIRS.calculation,
exampleIRS.common,
DUMMY_NOTARY
)
val outState = inState.copy()
@Test
fun ok() {
trade().verify()
}
@Test
fun `ok with groups`() {
tradegroups().verify()
}
/**
* Generate an IRS txn - we'll need it for a few things.
*/
fun generateIRSTxn(irsSelect: Int): LedgerTransaction {
val dummyIRS = createDummyIRS(irsSelect)
val genTX: LedgerTransaction = run {
val gtx = InterestRateSwap().generateAgreement(
fixedLeg = dummyIRS.fixedLeg,
floatingLeg = dummyIRS.floatingLeg,
calculation = dummyIRS.calculation,
common = dummyIRS.common,
notary = DUMMY_NOTARY).apply {
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
signWith(MEGA_CORP_KEY)
signWith(MINI_CORP_KEY)
signWith(DUMMY_NOTARY_KEY)
}
gtx.toSignedTransaction().verifyToLedgerTransaction(MockIdentityService, attachments)
}
return genTX
}
/**
* Just make sure it's sane.
*/
@Test
fun pprintIRS() {
val irs = singleIRS()
println(irs.prettyPrint())
}
/**
* Utility so I don't have to keep typing this
*/
fun singleIRS(irsSelector: Int = 1): InterestRateSwap.State {
return generateIRSTxn(irsSelector).outputs.filterIsInstance<InterestRateSwap.State>().single()
}
/**
* Test the generate. No explicit exception as if something goes wrong, we'll find out anyway.
*/
@Test
fun generateIRS() {
// Tests aren't allowed to return things
generateIRSTxn(1)
}
/**
* Testing a simple IRS, add a few fixings and then display as CSV
*/
@Test
fun `IRS Export test`() {
// No transactions etc required - we're just checking simple maths and export functionallity
val irs = singleIRS(2)
var newCalculation = irs.calculation
val fixings = mapOf(LocalDate.of(2015, 3, 6) to "0.6",
LocalDate.of(2015, 6, 8) to "0.75",
LocalDate.of(2015, 9, 8) to "0.8",
LocalDate.of(2015, 12, 8) to "0.55",
LocalDate.of(2016, 3, 8) to "0.644")
for (it in fixings) {
newCalculation = newCalculation.applyFixing(it.key, FixedRate(PercentageRatioUnit(it.value)))
}
val newIRS = InterestRateSwap.State(irs.fixedLeg, irs.floatingLeg, newCalculation, irs.common, DUMMY_NOTARY)
println(newIRS.exportIRSToCSV())
}
/**
* Make sure it has a schedule and the schedule has some unfixed rates
*/
@Test
fun `next fixing date`() {
val irs = singleIRS(1)
println(irs.calculation.nextFixingDate())
}
/**
* Iterate through all the fix dates and add something
*/
@Test
fun generateIRSandFixSome() {
var previousTXN = generateIRSTxn(1)
var currentIRS = previousTXN.outputs.filterIsInstance<InterestRateSwap.State>().single()
println(currentIRS.prettyPrint())
while (true) {
val nextFixingDate = currentIRS.calculation.nextFixingDate() ?: break
println("\n\n\n ***** Applying a fixing to $nextFixingDate \n\n\n")
var fixTX: LedgerTransaction = run {
val tx = TransactionBuilder()
val fixing = Pair(nextFixingDate, FixedRate("0.052".percent))
InterestRateSwap().generateFix(tx, previousTXN.outRef(0), fixing)
with(tx) {
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
signWith(MEGA_CORP_KEY)
signWith(MINI_CORP_KEY)
signWith(DUMMY_NOTARY_KEY)
}
tx.toSignedTransaction().verifyToLedgerTransaction(MockIdentityService, attachments)
}
currentIRS = previousTXN.outputs.filterIsInstance<InterestRateSwap.State>().single()
println(currentIRS.prettyPrint())
previousTXN = fixTX
}
}
// Move these later as they aren't IRS specific.
@Test
fun `test some rate objects 100 * FixedRate(5%)`() {
val r1 = FixedRate(PercentageRatioUnit("5"))
assert(100 * r1 == 5)
}
@Test
fun `expression calculation testing`() {
val dummyIRS = singleIRS()
val stuffToPrint: ArrayList<String> = arrayListOf(
"fixedLeg.notional.pennies",
"fixedLeg.fixedRate.ratioUnit",
"fixedLeg.fixedRate.ratioUnit.value",
"floatingLeg.notional.pennies",
"fixedLeg.fixedRate",
"currentBusinessDate",
"calculation.floatingLegPaymentSchedule.get(currentBusinessDate)",
"fixedLeg.notional.currency.currencyCode",
"fixedLeg.notional.pennies * 10",
"fixedLeg.notional.pennies * fixedLeg.fixedRate.ratioUnit.value",
"(fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360 ",
"(fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value))"
// "calculation.floatingLegPaymentSchedule.get(context.getDate('currentDate')).rate"
// "calculation.floatingLegPaymentSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value",
//"( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) - (floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))",
// "( fixedLeg.notional * fixedLeg.fixedRate )"
)
for (i in stuffToPrint) {
println(i)
var z = dummyIRS.evaluateCalculation(LocalDate.of(2016, 9, 12), Expression(i))
println(z.javaClass)
println(z)
println("-----------")
}
// This does not throw an exception in the test itself; it evaluates the above and they will throw if they do not pass.
}
/**
* Generates a typical transactional history for an IRS.
*/
fun trade(): TransactionGroupDSL<InterestRateSwap.State> {
val ld = LocalDate.of(2016, 3, 8)
val bd = BigDecimal("0.0063518")
val txgroup: TransactionGroupDSL<InterestRateSwap.State> = transactionGroupFor() {
transaction("Agreement") {
output("irs post agreement") { singleIRS() }
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
}
transaction("Fix") {
input("irs post agreement")
output("irs post first fixing") {
"irs post agreement".output.copy(
"irs post agreement".output.fixedLeg,
"irs post agreement".output.floatingLeg,
"irs post agreement".output.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
"irs post agreement".output.common
)
}
arg(ORACLE_PUBKEY) {
InterestRateSwap.Commands.Fix()
}
arg(ORACLE_PUBKEY) {
Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)
}
timestamp(TEST_TX_TIME)
}
}
return txgroup
}
@Test
fun `ensure failure occurs when there are inbound states for an agreement command`() {
transaction {
input() { singleIRS() }
output("irs post agreement") { singleIRS() }
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
this `fails requirement` "There are no in states for an agreement"
}
}
@Test
fun `ensure failure occurs when no events in fix schedule`() {
val irs = singleIRS()
val emptySchedule = HashMap<LocalDate, FixedRatePaymentEvent>()
transaction {
output() {
irs.copy(calculation = irs.calculation.copy(fixedLegPaymentSchedule = emptySchedule))
}
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
this `fails requirement` "There are events in the fix schedule"
}
}
@Test
fun `ensure failure occurs when no events in floating schedule`() {
val irs = singleIRS()
val emptySchedule = HashMap<LocalDate, FloatingRatePaymentEvent>()
transaction {
output() {
irs.copy(calculation = irs.calculation.copy(floatingLegPaymentSchedule = emptySchedule))
}
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
this `fails requirement` "There are events in the float schedule"
}
}
@Test
fun `ensure notionals are non zero`() {
val irs = singleIRS()
transaction {
output() {
irs.copy(irs.fixedLeg.copy(notional = irs.fixedLeg.notional.copy(pennies = 0)))
}
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
this `fails requirement` "All notionals must be non zero"
}
transaction {
output() {
irs.copy(irs.fixedLeg.copy(notional = irs.floatingLeg.notional.copy(pennies = 0)))
}
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
this `fails requirement` "All notionals must be non zero"
}
}
@Test
fun `ensure positive rate on fixed leg`() {
val irs = singleIRS()
val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(fixedRate = FixedRate(PercentageRatioUnit("-0.1"))))
transaction {
output() {
modifiedIRS
}
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
this `fails requirement` "The fixed leg rate must be positive"
}
}
/**
* This will be modified once we adapt the IRS to be cross currency
*/
@Test
fun `ensure same currency notionals`() {
val irs = singleIRS()
val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.fixedLeg.notional.pennies, Currency.getInstance("JPY"))))
transaction {
output() {
modifiedIRS
}
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
this `fails requirement` "The currency of the notionals must be the same"
}
}
@Test
fun `ensure notional amounts are equal`() {
val irs = singleIRS()
val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.floatingLeg.notional.pennies + 1, irs.floatingLeg.notional.currency)))
transaction {
output() {
modifiedIRS
}
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
this `fails requirement` "All leg notionals must be the same"
}
}
@Test
fun `ensure trade date and termination date checks are done pt1`() {
val irs = singleIRS()
val modifiedIRS1 = irs.copy(fixedLeg = irs.fixedLeg.copy(terminationDate = irs.fixedLeg.effectiveDate.minusDays(1)))
transaction {
output() {
modifiedIRS1
}
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
this `fails requirement` "The effective date is before the termination date for the fixed leg"
}
val modifiedIRS2 = irs.copy(floatingLeg = irs.floatingLeg.copy(terminationDate = irs.floatingLeg.effectiveDate.minusDays(1)))
transaction {
output() {
modifiedIRS2
}
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
this `fails requirement` "The effective date is before the termination date for the floating leg"
}
}
@Test
fun `ensure trade date and termination date checks are done pt2`() {
val irs = singleIRS()
val modifiedIRS3 = irs.copy(floatingLeg = irs.floatingLeg.copy(terminationDate = irs.fixedLeg.terminationDate.minusDays(1)))
transaction {
output() {
modifiedIRS3
}
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
this `fails requirement` "The termination dates are aligned"
}
val modifiedIRS4 = irs.copy(floatingLeg = irs.floatingLeg.copy(effectiveDate = irs.fixedLeg.effectiveDate.minusDays(1)))
transaction {
output() {
modifiedIRS4
}
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
this `fails requirement` "The effective dates are aligned"
}
}
@Test
fun `various fixing tests`() {
val ld = LocalDate.of(2016, 3, 8)
val bd = BigDecimal("0.0063518")
transaction {
output("irs post agreement") { singleIRS() }
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
}
val oldIRS = singleIRS(1)
val newIRS = oldIRS.copy(oldIRS.fixedLeg,
oldIRS.floatingLeg,
oldIRS.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
oldIRS.common)
transaction {
input() {
oldIRS
}
// Templated tweak for reference. A corrent fixing applied should be ok
tweak {
arg(ORACLE_PUBKEY) {
InterestRateSwap.Commands.Fix()
}
timestamp(TEST_TX_TIME)
arg(ORACLE_PUBKEY) {
Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)
}
output() { newIRS }
this.accepts()
}
// This test makes sure that verify confirms the fixing was applied and there is a difference in the old and new
tweak {
arg(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() }
timestamp(TEST_TX_TIME)
arg(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) }
output() { oldIRS }
this`fails requirement` "There is at least one difference in the IRS floating leg payment schedules"
}
// This tests tries to sneak in a change to another fixing (which may or may not be the latest one)
tweak {
arg(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() }
timestamp(TEST_TX_TIME)
arg(ORACLE_PUBKEY) {
Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)
}
val firstResetKey = newIRS.calculation.floatingLegPaymentSchedule.keys.first()
val firstResetValue = newIRS.calculation.floatingLegPaymentSchedule[firstResetKey]
var modifiedFirstResetValue = firstResetValue!!.copy(notional = Amount(firstResetValue.notional.pennies, Currency.getInstance("JPY")))
output() {
newIRS.copy(
newIRS.fixedLeg,
newIRS.floatingLeg,
newIRS.calculation.copy(floatingLegPaymentSchedule = newIRS.calculation.floatingLegPaymentSchedule.plus(
Pair(firstResetKey, modifiedFirstResetValue))),
newIRS.common
)
}
this`fails requirement` "There is only one change in the IRS floating leg payment schedule"
}
// This tests modifies the payment currency for the fixing
tweak {
arg(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() }
timestamp(TEST_TX_TIME)
arg(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) }
val latestReset = newIRS.calculation.floatingLegPaymentSchedule.filter { it.value.rate is FixedRate }.maxBy { it.key }
var modifiedLatestResetValue = latestReset!!.value.copy(notional = Amount(latestReset.value.notional.pennies, Currency.getInstance("JPY")))
output() {
newIRS.copy(
newIRS.fixedLeg,
newIRS.floatingLeg,
newIRS.calculation.copy(floatingLegPaymentSchedule = newIRS.calculation.floatingLegPaymentSchedule.plus(
Pair(latestReset.key, modifiedLatestResetValue))),
newIRS.common
)
}
this`fails requirement` "The fix payment has the same currency as the notional"
}
}
}
/**
* This returns an example of transactions that are grouped by TradeId and then a fixing applied.
* It's important to make the tradeID different for two reasons, the hashes will be the same and all sorts of confusion will
* result and the grouping won't work either.
* In reality, the only fields that should be in common will be the next fixing date and the reference rate.
*/
fun tradegroups(): TransactionGroupDSL<InterestRateSwap.State> {
val ld1 = LocalDate.of(2016, 3, 8)
val bd1 = BigDecimal("0.0063518")
val irs = singleIRS()
val txgroup: TransactionGroupDSL<InterestRateSwap.State> = transactionGroupFor() {
transaction("Agreement") {
output("irs post agreement1") {
irs.copy(
irs.fixedLeg,
irs.floatingLeg,
irs.calculation,
irs.common.copy(tradeID = "t1")
)
}
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
}
transaction("Agreement") {
output("irs post agreement2") {
irs.copy(
irs.fixedLeg,
irs.floatingLeg,
irs.calculation,
irs.common.copy(tradeID = "t2")
)
}
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
}
transaction("Fix") {
input("irs post agreement1")
input("irs post agreement2")
output("irs post first fixing1") {
"irs post agreement1".output.copy(
"irs post agreement1".output.fixedLeg,
"irs post agreement1".output.floatingLeg,
"irs post agreement1".output.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
"irs post agreement1".output.common.copy(tradeID = "t1")
)
}
output("irs post first fixing2") {
"irs post agreement2".output.copy(
"irs post agreement2".output.fixedLeg,
"irs post agreement2".output.floatingLeg,
"irs post agreement2".output.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
"irs post agreement2".output.common.copy(tradeID = "t2")
)
}
arg(ORACLE_PUBKEY) {
InterestRateSwap.Commands.Fix()
}
arg(ORACLE_PUBKEY) {
Fix(FixOf("ICE LIBOR", ld1, Tenor("3M")), bd1)
}
timestamp(TEST_TX_TIME)
}
}
return txgroup
}
}

View File

@ -1,130 +0,0 @@
package core
import com.codahale.metrics.MetricRegistry
import core.contracts.Attachment
import core.crypto.SecureHash
import core.crypto.generateKeyPair
import core.crypto.sha256
import core.messaging.MessagingService
import core.node.ServiceHub
import core.node.storage.Checkpoint
import core.node.storage.CheckpointStorage
import core.node.subsystems.*
import core.node.services.AttachmentStorage
import core.node.services.IdentityService
import core.node.services.NetworkMapService
import core.testing.MockNetworkMapCache
import core.testutils.MockIdentityService
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.InputStream
import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey
import java.time.Clock
import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.jar.JarInputStream
import javax.annotation.concurrent.ThreadSafe
class MockKeyManagementService(vararg initialKeys: KeyPair) : KeyManagementService {
override val keys: MutableMap<PublicKey, PrivateKey>
init {
keys = initialKeys.map { it.public to it.private }.toMap(HashMap())
}
val nextKeys = LinkedList<KeyPair>()
override fun freshKey(): KeyPair {
val k = nextKeys.poll() ?: generateKeyPair()
keys[k.public] = k.private
return k
}
}
class MockAttachmentStorage : AttachmentStorage {
val files = HashMap<SecureHash, ByteArray>()
override fun openAttachment(id: SecureHash): Attachment? {
val f = files[id] ?: return null
return object : Attachment {
override fun open(): InputStream = ByteArrayInputStream(f)
override val id: SecureHash = id
}
}
override fun importAttachment(jar: InputStream): SecureHash {
// JIS makes read()/readBytes() return bytes of the current file, but we want to hash the entire container here.
require(jar !is JarInputStream)
val bytes = run {
val s = ByteArrayOutputStream()
jar.copyTo(s)
s.close()
s.toByteArray()
}
val sha256 = bytes.sha256()
if (files.containsKey(sha256))
throw FileAlreadyExistsException(File("!! MOCK FILE NAME"))
files[sha256] = bytes
return sha256
}
}
class MockCheckpointStorage : CheckpointStorage {
private val _checkpoints = ConcurrentLinkedQueue<Checkpoint>()
override val checkpoints: Iterable<Checkpoint>
get() = _checkpoints.toList()
override fun addCheckpoint(checkpoint: Checkpoint) {
_checkpoints.add(checkpoint)
}
override fun removeCheckpoint(checkpoint: Checkpoint) {
require(_checkpoints.remove(checkpoint))
}
}
@ThreadSafe
class MockStorageService : StorageServiceImpl(MockAttachmentStorage(), generateKeyPair())
class MockServices(
customWallet: WalletService? = null,
val keyManagement: KeyManagementService? = null,
val net: MessagingService? = null,
val identity: IdentityService? = MockIdentityService,
val storage: StorageService? = MockStorageService(),
val mapCache: NetworkMapCache? = MockNetworkMapCache(),
val mapService: NetworkMapService? = null,
val overrideClock: Clock? = Clock.systemUTC()
) : ServiceHub {
override val walletService: WalletService = customWallet ?: NodeWalletService(this)
override val keyManagementService: KeyManagementService
get() = keyManagement ?: throw UnsupportedOperationException()
override val identityService: IdentityService
get() = identity ?: throw UnsupportedOperationException()
override val networkService: MessagingService
get() = net ?: throw UnsupportedOperationException()
override val networkMapCache: NetworkMapCache
get() = mapCache ?: throw UnsupportedOperationException()
override val storageService: StorageService
get() = storage ?: throw UnsupportedOperationException()
override val clock: Clock
get() = overrideClock ?: throw UnsupportedOperationException()
override val monitoringService: MonitoringService = MonitoringService(MetricRegistry())
init {
if (net != null && storage != null) {
// Creating this class is sufficient, we don't have to store it anywhere, because it registers a listener
// on the networking service, so that will keep it from being collected.
DataVendingService(net, storage)
}
}
}

View File

@ -1,152 +0,0 @@
package core
import contracts.Cash
import core.contracts.*
import core.testutils.*
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals
class TransactionGroupTests {
val A_THOUSAND_POUNDS = Cash.State(MINI_CORP.ref(1, 2, 3), 1000.POUNDS, MINI_CORP_PUBKEY, DUMMY_NOTARY)
@Test
fun success() {
transactionGroup {
roots {
transaction(A_THOUSAND_POUNDS label "£1000")
}
transaction {
input("£1000")
output("alice's £1000") { A_THOUSAND_POUNDS `owned by` ALICE_PUBKEY }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
}
transaction {
input("alice's £1000")
arg(ALICE_PUBKEY) { Cash.Commands.Move() }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Exit(1000.POUNDS) }
}
verify()
}
}
@Test
fun conflict() {
transactionGroup {
val t = transaction {
output("cash") { A_THOUSAND_POUNDS }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue() }
}
val conflict1 = transaction {
input("cash")
val HALF = A_THOUSAND_POUNDS.copy(amount = 500.POUNDS) `owned by` BOB_PUBKEY
output { HALF }
output { HALF }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
}
verify()
// Alice tries to double spend back to herself.
val conflict2 = transaction {
input("cash")
val HALF = A_THOUSAND_POUNDS.copy(amount = 500.POUNDS) `owned by` ALICE_PUBKEY
output { HALF }
output { HALF }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
}
assertNotEquals(conflict1, conflict2)
val e = assertFailsWith(TransactionConflictException::class) {
verify()
}
assertEquals(StateRef(t.id, 0), e.conflictRef)
assertEquals(setOf(conflict1.id, conflict2.id), setOf(e.tx1.id, e.tx2.id))
}
}
@Test
fun disconnected() {
// Check that if we have a transaction in the group that doesn't connect to anything else, it's rejected.
val tg = transactionGroup {
transaction {
output("cash") { A_THOUSAND_POUNDS }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue() }
}
transaction {
input("cash")
output { A_THOUSAND_POUNDS `owned by` BOB_PUBKEY }
}
}
// We have to do this manually without the DSL because transactionGroup { } won't let us create a tx that
// points nowhere.
val input = generateStateRef()
tg.txns += TransactionBuilder().apply {
addInputState(input)
addOutputState(A_THOUSAND_POUNDS)
addCommand(Cash.Commands.Move(), BOB_PUBKEY)
}.toWireTransaction()
val e = assertFailsWith(TransactionResolutionException::class) {
tg.verify()
}
assertEquals(e.hash, input.txhash)
}
@Test
fun duplicatedInputs() {
// Check that a transaction cannot refer to the same input more than once.
transactionGroup {
roots {
transaction(A_THOUSAND_POUNDS label "£1000")
}
transaction {
input("£1000")
input("£1000")
output { A_THOUSAND_POUNDS.copy(amount = A_THOUSAND_POUNDS.amount * 2) }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
}
assertFailsWith(TransactionConflictException::class) {
verify()
}
}
}
@Test
fun signGroup() {
val signedTxns: List<SignedTransaction> = transactionGroup {
transaction {
output("£1000") { A_THOUSAND_POUNDS }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue() }
}
transaction {
input("£1000")
output("alice's £1000") { A_THOUSAND_POUNDS `owned by` ALICE_PUBKEY }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
}
transaction {
input("alice's £1000")
arg(ALICE_PUBKEY) { Cash.Commands.Move() }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Exit(1000.POUNDS) }
}
}.signAll()
// Now go through the conversion -> verification path with them.
val ltxns = signedTxns.map {
it.verifyToLedgerTransaction(MockIdentityService, MockStorageService().attachments)
}.toSet()
TransactionGroup(ltxns, emptySet()).verify()
}
}

View File

@ -1,258 +0,0 @@
package core.node
import contracts.DUMMY_PROGRAM_ID
import contracts.DummyContract
import core.*
import core.contracts.*
import core.crypto.Party
import core.crypto.SecureHash
import core.node.services.AttachmentStorage
import core.serialization.*
import core.testutils.DUMMY_NOTARY
import core.testutils.MEGA_CORP
import org.apache.commons.io.IOUtils
import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.net.URLClassLoader
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
interface DummyContractBackdoor {
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder
fun inspectState(state: ContractState): Int
}
class AttachmentClassLoaderTests {
companion object {
val ISOLATED_CONTRACTS_JAR_PATH = AttachmentClassLoaderTests::class.java.getResource("isolated.jar")
}
fun importJar(storage: AttachmentStorage) = ISOLATED_CONTRACTS_JAR_PATH.openStream().use { storage.importAttachment(it) }
// These ClassLoaders work together to load 'AnotherDummyContract' in a disposable way, such that even though
// the class may be on the unit test class path (due to default IDE settings, etc), it won't be loaded into the
// regular app classloader but rather than ClassLoaderForTests. This helps keep our environment clean and
// ensures we have precise control over where it's loaded.
object FilteringClassLoader : ClassLoader() {
override fun loadClass(name: String, resolve: Boolean): Class<*>? {
if ("AnotherDummyContract" in name) {
return null
} else
return super.loadClass(name, resolve)
}
}
class ClassLoaderForTests : URLClassLoader(arrayOf(ISOLATED_CONTRACTS_JAR_PATH), FilteringClassLoader)
@Test
fun `dynamically load AnotherDummyContract from isolated contracts jar`() {
val child = ClassLoaderForTests()
val contractClass = Class.forName("contracts.isolated.AnotherDummyContract", true, child)
val contract = contractClass.newInstance() as Contract
assertEquals(SecureHash.sha256("https://anotherdummy.org"), contract.legalContractReference)
}
fun fakeAttachment(filepath: String, content: String): ByteArray {
val bs = ByteArrayOutputStream()
val js = JarOutputStream(bs)
js.putNextEntry(ZipEntry(filepath))
js.writer().apply { append(content); flush() }
js.closeEntry()
js.close()
return bs.toByteArray()
}
@Test
fun `test MockAttachmentStorage open as jar`() {
val storage = MockAttachmentStorage()
val key = importJar(storage)
val attachment = storage.openAttachment(key)!!
val jar = attachment.openAsJAR()
assert(jar.nextEntry != null)
}
@Test
fun `test overlapping file exception`() {
val storage = MockAttachmentStorage()
val att0 = importJar(storage)
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file.txt", "some other data")))
assertFailsWith(AttachmentsClassLoader.OverlappingAttachments::class) {
AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! })
}
}
@Test
fun `basic`() {
val storage = MockAttachmentStorage()
val att0 = importJar(storage)
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! })
val txt = IOUtils.toString(cl.getResourceAsStream("file1.txt"))
assertEquals("some data", txt)
}
@Test
fun `loading class AnotherDummyContract`() {
val storage = MockAttachmentStorage()
val att0 = importJar(storage)
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader)
val contractClass = Class.forName("contracts.isolated.AnotherDummyContract", true, cl)
val contract = contractClass.newInstance() as Contract
assertEquals(cl, contract.javaClass.classLoader)
assertEquals(SecureHash.sha256("https://anotherdummy.org"), contract.legalContractReference)
}
@Test
fun `verify that contract DummyContract is in classPath`() {
val contractClass = Class.forName("contracts.DummyContract")
val contract = contractClass.newInstance() as Contract
assertNotNull(contract)
}
fun createContract2Cash(): Contract {
val cl = ClassLoaderForTests()
val contractClass = Class.forName("contracts.isolated.AnotherDummyContract", true, cl)
return contractClass.newInstance() as Contract
}
@Test
fun `testing Kryo with ClassLoader (with top level class name)`() {
val contract = createContract2Cash()
val bytes = contract.serialize()
val storage = MockAttachmentStorage()
val att0 = importJar(storage)
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader)
val kryo = createKryo()
kryo.classLoader = cl
val state2 = bytes.deserialize(kryo)
assert(state2.javaClass.classLoader is AttachmentsClassLoader)
assertNotNull(state2)
}
// top level wrapper
class Data(val contract: Contract)
@Test
fun `testing Kryo with ClassLoader (without top level class name)`() {
val data = Data(createContract2Cash())
assertNotNull(data.contract)
val bytes = data.serialize()
val storage = MockAttachmentStorage()
val att0 = importJar(storage)
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader)
val kryo = createKryo()
kryo.classLoader = cl
val state2 = bytes.deserialize(kryo)
assertEquals(cl, state2.contract.javaClass.classLoader)
assertNotNull(state2)
}
@Test
fun `test serialization of WireTransaction with statically loaded contract`() {
val tx = DUMMY_PROGRAM_ID.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val wireTransaction = tx.toWireTransaction()
val bytes = wireTransaction.serialize()
val copiedWireTransaction = bytes.deserialize()
assertEquals(1, copiedWireTransaction.outputs.size)
assertEquals(42, (copiedWireTransaction.outputs[0] as DummyContract.State).magicNumber)
}
@Test
fun `test serialization of WireTransaction with dynamically loaded contract`() {
val child = ClassLoaderForTests()
val contractClass = Class.forName("contracts.isolated.AnotherDummyContract", true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val storage = MockAttachmentStorage()
val kryo = createKryo()
// todo - think about better way to push attachmentStorage down to serializer
kryo.attachmentStorage = storage
val attachmentRef = importJar(storage)
tx.addAttachment(storage.openAttachment(attachmentRef)!!)
val wireTransaction = tx.toWireTransaction()
val bytes = wireTransaction.serialize(kryo)
val kryo2 = createKryo()
// use empty attachmentStorage
kryo2.attachmentStorage = storage
val copiedWireTransaction = bytes.deserialize(kryo2)
assertEquals(1, copiedWireTransaction.outputs.size)
val contract2 = copiedWireTransaction.outputs[0].contract as DummyContractBackdoor
assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0]))
}
@Test
fun `test deserialize of WireTransaction where contract cannot be found`() {
val child = ClassLoaderForTests()
val contractClass = Class.forName("contracts.isolated.AnotherDummyContract", true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val storage = MockAttachmentStorage()
val kryo = createKryo()
// todo - think about better way to push attachmentStorage down to serializer
kryo.attachmentStorage = storage
val attachmentRef = importJar(storage)
tx.addAttachment(storage.openAttachment(attachmentRef)!!)
val wireTransaction = tx.toWireTransaction()
val bytes = wireTransaction.serialize(kryo)
val kryo2 = createKryo()
// use empty attachmentStorage
kryo2.attachmentStorage = MockAttachmentStorage()
val e = assertFailsWith(MissingAttachmentsException::class) {
bytes.deserialize(kryo2)
}
assertEquals(attachmentRef, e.ids.single())
}
}

View File

@ -1,34 +0,0 @@
package core.serialization
import com.esotericsoftware.kryo.Kryo
import org.junit.Test
import java.time.Instant
import kotlin.test.assertEquals
import kotlin.test.assertNull
class KryoTests {
data class Person(val name: String, val birthday: Instant?)
private val kryo: Kryo = createKryo()
@Test
fun ok() {
val april_17th = Instant.parse("1984-04-17T00:30:00.00Z")
val mike = Person("mike", april_17th)
val bits = mike.serialize(kryo)
with(bits.deserialize<Person>(kryo)) {
assertEquals("mike", name)
assertEquals(april_17th, birthday)
}
}
@Test
fun nullables() {
val bob = Person("bob", null)
val bits = bob.serialize(kryo)
with(bits.deserialize<Person>(kryo)) {
assertEquals("bob", name)
assertNull(birthday)
}
}
}

View File

@ -1,82 +0,0 @@
package core.serialization
import contracts.Cash
import core.*
import core.contracts.*
import core.testutils.*
import org.junit.Before
import org.junit.Test
import java.security.SignatureException
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class TransactionSerializationTests {
// Simple TX that takes 1000 pounds from me and sends 600 to someone else (with 400 change).
// It refers to a fake TX/state that we don't bother creating here.
val depositRef = MINI_CORP.ref(1)
val outputState = Cash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1, DUMMY_NOTARY)
val changeState = Cash.State(depositRef, 400.POUNDS, TestUtils.keypair.public, DUMMY_NOTARY)
val fakeStateRef = generateStateRef()
lateinit var tx: TransactionBuilder
@Before
fun setup() {
tx = TransactionBuilder().withItems(
fakeStateRef, outputState, changeState, Command(Cash.Commands.Move(), arrayListOf(TestUtils.keypair.public))
)
}
@Test
fun signWireTX() {
tx.signWith(TestUtils.keypair)
val signedTX = tx.toSignedTransaction()
// Now check that the signature we just made verifies.
signedTX.verifySignatures()
// Corrupt the data and ensure the signature catches the problem.
signedTX.txBits.bits[5] = 0
assertFailsWith(SignatureException::class) {
signedTX.verifySignatures()
}
}
@Test
fun wrongKeys() {
// Can't convert if we don't have signatures for all commands
assertFailsWith(IllegalStateException::class) {
tx.toSignedTransaction()
}
tx.signWith(TestUtils.keypair)
val signedTX = tx.toSignedTransaction()
// Cannot construct with an empty sigs list.
assertFailsWith(IllegalStateException::class) {
signedTX.copy(sigs = emptyList())
}
// If the signature was replaced in transit, we don't like it.
assertFailsWith(SignatureException::class) {
val tx2 = TransactionBuilder().withItems(fakeStateRef, outputState, changeState,
Command(Cash.Commands.Move(), TestUtils.keypair2.public))
tx2.signWith(TestUtils.keypair2)
signedTX.copy(sigs = tx2.toSignedTransaction().sigs).verify()
}
}
@Test
fun timestamp() {
tx.setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
tx.signWith(TestUtils.keypair)
tx.signWith(DUMMY_NOTARY_KEY)
val stx = tx.toSignedTransaction()
val ltx = stx.verifyToLedgerTransaction(MockIdentityService, MockStorageService().attachments)
assertEquals(tx.commands().map { it.value }, ltx.commands.map { it.value })
assertEquals(tx.inputStates(), ltx.inputs)
assertEquals(tx.outputStates(), ltx.outputs)
assertEquals(TEST_TX_TIME, ltx.commands.getTimestampBy(DUMMY_NOTARY)!!.midpoint)
}
}

View File

@ -1,387 +0,0 @@
@file:Suppress("UNUSED_PARAMETER", "UNCHECKED_CAST")
package core.testutils
import com.google.common.base.Throwables
import com.google.common.net.HostAndPort
import contracts.*
import core.*
import core.contracts.*
import core.crypto.*
import core.node.AbstractNode
import core.serialization.serialize
import core.testing.MockIdentityService
import core.visualiser.GraphVisualiser
import java.net.ServerSocket
import java.security.KeyPair
import java.security.PublicKey
import java.time.Instant
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.fail
/** If an exception is thrown by the body, rethrows the root cause exception. */
inline fun <R> rootCauseExceptions(body: () -> R): R {
try {
return body()
} catch(e: Exception) {
throw Throwables.getRootCause(e)
}
}
fun freeLocalHostAndPort(): HostAndPort {
val freePort = ServerSocket(0).use { it.localPort }
return HostAndPort.fromParts("localhost", freePort)
}
object TestUtils {
val keypair = generateKeyPair()
val keypair2 = generateKeyPair()
val keypair3 = 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")
// A few dummy values for testing.
val MEGA_CORP_KEY = TestUtils.keypair
val MEGA_CORP_PUBKEY = MEGA_CORP_KEY.public
val MINI_CORP_KEY = TestUtils.keypair2
val MINI_CORP_PUBKEY = MINI_CORP_KEY.public
val ORACLE_KEY = TestUtils.keypair3
val ORACLE_PUBKEY = ORACLE_KEY.public
val DUMMY_PUBKEY_1 = DummyPublicKey("x1")
val DUMMY_PUBKEY_2 = DummyPublicKey("x2")
val ALICE_KEY = generateKeyPair()
val ALICE_PUBKEY = ALICE_KEY.public
val ALICE = Party("Alice", ALICE_PUBKEY)
val BOB_KEY = generateKeyPair()
val BOB_PUBKEY = BOB_KEY.public
val BOB = Party("Bob", BOB_PUBKEY)
val MEGA_CORP = Party("MegaCorp", MEGA_CORP_PUBKEY)
val MINI_CORP = Party("MiniCorp", MINI_CORP_PUBKEY)
val DUMMY_NOTARY_KEY = generateKeyPair()
val DUMMY_NOTARY = Party("Notary Service", DUMMY_NOTARY_KEY.public)
val ALL_TEST_KEYS = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY, DUMMY_NOTARY_KEY)
val MockIdentityService = MockIdentityService(listOf(MEGA_CORP, MINI_CORP, DUMMY_NOTARY))
// 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.
val TEST_PROGRAM_MAP: Map<Contract, Class<out Contract>> = mapOf(
CASH_PROGRAM_ID to Cash::class.java,
CP_PROGRAM_ID to CommercialPaper::class.java,
JavaCommercialPaper.JCP_PROGRAM_ID to JavaCommercialPaper::class.java,
CROWDFUND_PROGRAM_ID to CrowdFund::class.java,
DUMMY_PROGRAM_ID to DummyContract::class.java,
IRS_PROGRAM_ID to InterestRateSwap::class.java
)
fun generateState(notary: Party = DUMMY_NOTARY) = DummyContract.State(Random().nextInt(), notary)
fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0)
fun issueState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateRef {
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), DUMMY_NOTARY)
tx.signWith(node.storage.myLegalIdentityKey)
tx.signWith(DUMMY_NOTARY_KEY)
val stx = tx.toSignedTransaction()
node.services.recordTransactions(listOf(stx))
return StateRef(stx.id, 0)
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Defines a simple DSL for building pseudo-transactions (not the same as the wire protocol) for testing purposes.
//
// Define a transaction like this:
//
// transaction {
// input { someExpression }
// output { someExpression }
// arg { someExpression }
//
// tweak {
// ... same thing but works with a copy of the parent, can add inputs/outputs/args just within this scope.
// }
//
// contract.accepts() -> should pass
// contract `fails requirement` "some substring of the error message"
// }
//
// TODO: Make it impossible to forget to test either a failure or an accept for each transaction{} block
infix fun Cash.State.`owned by`(owner: PublicKey) = copy(owner = owner)
infix fun Cash.State.`issued by`(party: Party) = copy(deposit = deposit.copy(party = party))
infix fun CommercialPaper.State.`owned by`(owner: PublicKey) = this.copy(owner = owner)
infix fun ICommercialPaperState.`owned by`(new_owner: PublicKey) = this.withOwner(new_owner)
// Allows you to write 100.DOLLARS.CASH
val Amount.CASH: Cash.State get() = Cash.State(MINI_CORP.ref(1, 2, 3), this, NullPublicKey, DUMMY_NOTARY)
class LabeledOutput(val label: String?, val state: ContractState) {
override fun toString() = state.toString() + (if (label != null) " ($label)" else "")
override fun equals(other: Any?) = other is LabeledOutput && state.equals(other.state)
override fun hashCode(): Int = state.hashCode()
}
infix fun ContractState.label(label: String) = LabeledOutput(label, this)
abstract class AbstractTransactionForTest {
protected val attachments = ArrayList<SecureHash>()
protected val outStates = ArrayList<LabeledOutput>()
protected val commands = ArrayList<Command>()
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.signers, it.signers.mapNotNull { MockIdentityService.partyFromKey(it) }, it.value) }
}
fun attachment(attachmentID: SecureHash) {
attachments.add(attachmentID)
}
fun arg(vararg key: PublicKey, c: () -> CommandData) {
val keys = listOf(*key)
commands.add(Command(c(), keys))
}
fun timestamp(time: Instant) {
val data = TimestampCommand(time, 30.seconds)
timestamp(data)
}
fun timestamp(data: TimestampCommand) {
commands.add(Command(data, DUMMY_NOTARY.owningKey))
}
// Forbid patterns like: transaction { ... transaction { ... } }
@Deprecated("Cannot nest transactions, use tweak", level = DeprecationLevel.ERROR)
fun transaction(body: TransactionForTest.() -> Unit) {
}
}
// Corresponds to the args to Contract.verify
open class TransactionForTest : AbstractTransactionForTest() {
private val inStates = arrayListOf<ContractState>()
fun input(s: () -> ContractState) = inStates.add(s())
protected fun runCommandsAndVerify(time: Instant) {
val cmds = commandsToAuthenticatedObjects()
val tx = TransactionForVerification(inStates, outStates.map { it.state }, emptyList(), cmds, SecureHash.randomSHA256())
tx.verify()
}
fun accepts(time: Instant = TEST_TX_TIME) = runCommandsAndVerify(time)
fun rejects(withMessage: String? = null, time: Instant = TEST_TX_TIME) {
val r = try {
runCommandsAndVerify(time)
false
} catch (e: Exception) {
val m = e.message
if (m == null)
fail("Threw exception without a message")
else
if (withMessage != null && !m.toLowerCase().contains(withMessage.toLowerCase())) throw AssertionError("Error was actually: $m", e)
true
}
if (!r) throw AssertionError("Expected exception but didn't get one")
}
/**
* Used to confirm that the test, when (implicitly) run against the .verify() method, fails with the text of the message
*/
infix fun `fails requirement`(msg: String) = rejects(msg)
fun fails_requirement(msg: String) = this.`fails requirement`(msg)
// Use this to create transactions where the output of this transaction is automatically used as an input of
// the next.
fun chain(vararg outputLabels: String, body: TransactionForTest.() -> Unit): TransactionForTest {
val states = outStates.mapNotNull {
val l = it.label
if (l != null && outputLabels.contains(l))
it.state
else
null
}
val tx = TransactionForTest()
tx.inStates.addAll(states)
tx.body()
return tx
}
// Allow customisation of partial transactions.
fun tweak(body: TransactionForTest.() -> Unit): TransactionForTest {
val tx = TransactionForTest()
tx.inStates.addAll(inStates)
tx.outStates.addAll(outStates)
tx.commands.addAll(commands)
tx.body()
return tx
}
override fun toString(): String {
return """transaction {
inputs: $inStates
outputs: $outStates
commands $commands
}"""
}
override fun equals(other: Any?) = this === other || (other is TransactionForTest && inStates == other.inStates && outStates == other.outStates && commands == other.commands)
override fun hashCode(): Int {
var result = inStates.hashCode()
result += 31 * result + outStates.hashCode()
result += 31 * result + commands.hashCode()
return result
}
}
fun transaction(body: TransactionForTest.() -> Unit) = TransactionForTest().apply { body() }
class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
open inner class WireTransactionDSL : AbstractTransactionForTest() {
private val inStates = ArrayList<StateRef>()
fun input(label: String) {
inStates.add(label.outputRef)
}
fun toWireTransaction() = WireTransaction(inStates, attachments, outStates.map { it.state }, commands)
}
val String.output: T get() = labelToOutputs[this] ?: throw IllegalArgumentException("State with label '$this' was not found")
val String.outputRef: StateRef get() = labelToRefs[this] ?: throw IllegalArgumentException("Unknown label \"$this\"")
fun <C : ContractState> lookup(label: String) = StateAndRef(label.output as C, label.outputRef)
private inner class InternalWireTransactionDSL : WireTransactionDSL() {
fun finaliseAndInsertLabels(): WireTransaction {
val wtx = toWireTransaction()
for ((index, labelledState) in outStates.withIndex()) {
if (labelledState.label != null) {
labelToRefs[labelledState.label] = StateRef(wtx.id, index)
if (stateType.isInstance(labelledState.state)) {
labelToOutputs[labelledState.label] = labelledState.state as T
}
outputsToLabels[labelledState.state] = labelledState.label
}
}
return wtx
}
}
private val rootTxns = ArrayList<WireTransaction>()
private val labelToRefs = HashMap<String, StateRef>()
private val labelToOutputs = HashMap<String, T>()
private val outputsToLabels = HashMap<ContractState, String>()
fun labelForState(state: T): String? = outputsToLabels[state]
inner class Roots {
fun transaction(vararg outputStates: LabeledOutput) {
val outs = outputStates.map { it.state }
val wtx = WireTransaction(emptyList(), emptyList(), outs, emptyList())
for ((index, state) in outputStates.withIndex()) {
val label = state.label!!
labelToRefs[label] = StateRef(wtx.id, index)
outputsToLabels[state.state] = label
labelToOutputs[label] = state.state as T
}
rootTxns.add(wtx)
}
@Deprecated("Does not nest ", level = DeprecationLevel.ERROR)
fun roots(body: Roots.() -> Unit) {
}
@Deprecated("Use the vararg form of transaction inside roots", level = DeprecationLevel.ERROR)
fun transaction(body: WireTransactionDSL.() -> Unit) {
}
}
fun roots(body: Roots.() -> Unit) = Roots().apply { body() }
val txns = ArrayList<WireTransaction>()
private val txnToLabelMap = HashMap<SecureHash, String>()
fun transaction(label: String? = null, body: WireTransactionDSL.() -> Unit): WireTransaction {
val forTest = InternalWireTransactionDSL()
forTest.body()
val wtx = forTest.finaliseAndInsertLabels()
txns.add(wtx)
if (label != null)
txnToLabelMap[wtx.id] = label
return wtx
}
fun labelForTransaction(tx: WireTransaction): String? = txnToLabelMap[tx.id]
fun labelForTransaction(tx: LedgerTransaction): String? = txnToLabelMap[tx.id]
@Deprecated("Does not nest ", level = DeprecationLevel.ERROR)
fun transactionGroup(body: TransactionGroupDSL<T>.() -> Unit) {
}
fun toTransactionGroup() = TransactionGroup(
txns.map { it.toLedgerTransaction(MockIdentityService, MockStorageService().attachments) }.toSet(),
rootTxns.map { it.toLedgerTransaction(MockIdentityService, MockStorageService().attachments) }.toSet()
)
class Failed(val index: Int, cause: Throwable) : Exception("Transaction $index didn't verify", cause)
fun verify() {
val group = toTransactionGroup()
try {
group.verify()
} catch (e: TransactionVerificationException) {
// Let the developer know the index of the transaction that failed.
val wtx: WireTransaction = txns.find { it.id == e.tx.origHash }!!
throw Failed(txns.indexOf(wtx) + 1, e)
}
}
fun expectFailureOfTx(index: Int, message: String): Exception {
val e = assertFailsWith(Failed::class) {
verify()
}
assertEquals(index, e.index)
if (!e.cause!!.message!!.contains(message))
throw AssertionError("Exception should have said '$message' but was actually: ${e.cause.message}", e.cause)
return e
}
fun visualise() {
@Suppress("CAST_NEVER_SUCCEEDS")
GraphVisualiser(this as TransactionGroupDSL<ContractState>).display()
}
fun signAll(txnsToSign: List<WireTransaction> = txns, vararg extraKeys: KeyPair): List<SignedTransaction> {
return txnsToSign.map { wtx ->
val allPubKeys = wtx.commands.flatMap { it.signers }.toMutableSet()
val bits = wtx.serialize()
require(bits == wtx.serialized)
val sigs = ArrayList<DigitalSignature.WithKey>()
for (key in ALL_TEST_KEYS + extraKeys) {
if (allPubKeys.contains(key.public)) {
sigs += key.signWithECDSA(bits)
allPubKeys -= key.public
}
}
SignedTransaction(bits, sigs)
}
}
}
inline fun <reified T : ContractState> transactionGroupFor(body: TransactionGroupDSL<T>.() -> Unit) = TransactionGroupDSL<T>(T::class.java).apply { this.body() }
fun transactionGroup(body: TransactionGroupDSL<ContractState>.() -> Unit) = TransactionGroupDSL(ContractState::class.java).apply { this.body() }

View File

@ -1,42 +0,0 @@
package core.utilities
import core.indexOfOrThrow
import core.noneOrSingle
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class CollectionExtensionTests {
@Test
fun `noneOrSingle returns a single item`() {
val collection = listOf(1)
assertEquals(collection.noneOrSingle(), 1)
assertEquals(collection.noneOrSingle { it == 1 }, 1)
}
@Test
fun `noneOrSingle returns null if item not found`() {
val collection = emptyList<Int>()
assertEquals(collection.noneOrSingle(), null)
}
@Test
fun `noneOrSingle throws if more than one item found`() {
val collection = listOf(1, 2)
assertFailsWith<IllegalArgumentException> { collection.noneOrSingle() }
assertFailsWith<IllegalArgumentException> { collection.noneOrSingle { it > 0 } }
}
@Test
fun `indexOfOrThrow returns index of the given item`() {
val collection = listOf(1, 2)
assertEquals(collection.indexOfOrThrow(1), 0)
assertEquals(collection.indexOfOrThrow(2), 1)
}
@Test
fun `indexOfOrThrow throws if the given item is not found`() {
val collection = listOf(1)
assertFailsWith<IllegalArgumentException> { collection.indexOfOrThrow(2) }
}
}

View File

@ -1,18 +1,18 @@
package core.messaging
package node.messaging
import core.contracts.Attachment
import core.crypto.SecureHash
import core.crypto.sha256
import core.node.NodeConfiguration
import core.node.NodeInfo
import core.node.services.NetworkMapService
import core.node.services.NodeAttachmentService
import core.node.services.NotaryService
import core.node.services.ServiceType
import core.serialization.OpaqueBytes
import core.testing.MockNetwork
import core.testutils.rootCauseExceptions
import core.testing.rootCauseExceptions
import core.utilities.BriefLogFormatter
import node.core.testing.MockNetwork
import node.services.config.NodeConfiguration
import node.services.network.NetworkMapService
import node.services.persistence.NodeAttachmentService
import node.services.transactions.NotaryService
import org.junit.Before
import org.junit.Test
import protocols.FetchAttachmentsProtocol

View File

@ -1,9 +1,12 @@
@file:Suppress("UNUSED_VARIABLE")
package core.messaging
package node.messaging
import core.messaging.Message
import core.messaging.TopicStringValidator
import core.messaging.send
import core.serialization.deserialize
import core.testing.MockNetwork
import node.core.testing.MockNetwork
import org.junit.Before
import org.junit.Test
import java.util.*

View File

@ -1,28 +1,33 @@
package core.messaging
package node.messaging
import com.google.common.util.concurrent.ListenableFuture
import contracts.Cash
import contracts.CommercialPaper
import contracts.testing.CASH
import contracts.testing.`issued by`
import contracts.testing.`owned by`
import core.contracts.*
import core.crypto.Party
import core.crypto.SecureHash
import core.days
import core.node.NodeConfiguration
import core.messaging.SingleMessageRecipient
import core.node.NodeInfo
import core.node.ServiceHub
import core.node.services.NodeAttachmentService
import core.node.services.ServiceType
import core.node.subsystems.NodeWalletService
import core.node.subsystems.StorageServiceImpl
import core.node.subsystems.Wallet
import core.node.subsystems.WalletImpl
import core.node.services.Wallet
import core.random63BitValue
import core.seconds
import core.testing.InMemoryMessagingNetwork
import core.testing.MockNetwork
import core.testutils.*
import core.testing.*
import core.utilities.BriefLogFormatter
import core.utilities.RecordingMap
import node.core.testing.MockNetwork
import node.services.config.NodeConfiguration
import node.services.network.InMemoryMessagingNetwork
import node.services.persistence.NodeAttachmentService
import node.services.persistence.StorageServiceImpl
import node.services.statemachine.StateMachineManager
import node.services.wallet.NodeWalletService
import node.services.wallet.WalletImpl
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
@ -50,10 +55,24 @@ import kotlin.test.assertTrue
class TwoPartyTradeProtocolTests {
lateinit var net: MockNetwork
private fun runSeller(smm: StateMachineManager, notary: NodeInfo,
otherSide: SingleMessageRecipient, assetToSell: StateAndRef<OwnableState>, price: Amount,
myKeyPair: KeyPair, buyerSessionID: Long): ListenableFuture<SignedTransaction> {
val seller = TwoPartyTradeProtocol.Seller(otherSide, notary, assetToSell, price, myKeyPair, buyerSessionID)
return smm.add("${TwoPartyTradeProtocol.TRADE_TOPIC}.seller", seller)
}
private fun runBuyer(smm: StateMachineManager, notaryNode: NodeInfo,
otherSide: SingleMessageRecipient, acceptablePrice: Amount, typeToBuy: Class<out OwnableState>,
sessionID: Long): ListenableFuture<SignedTransaction> {
val buyer = TwoPartyTradeProtocol.Buyer(otherSide, notaryNode.identity, acceptablePrice, typeToBuy, sessionID)
return smm.add("${TwoPartyTradeProtocol.TRADE_TOPIC}.buyer", buyer)
}
@Before
fun before() {
net = MockNetwork(false)
net.identities += MockIdentityService.identities
net.identities += MOCK_IDENTITY_SERVICE.identities
BriefLogFormatter.loggingOn("platform.trade", "core.contract.TransactionGroup", "recordingmap")
}
@ -113,7 +132,7 @@ class TwoPartyTradeProtocolTests {
@Test
fun `shutdown and restore`() {
transactionGroupFor<ContractState> {
core.testing.transactionGroupFor<ContractState> {
val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val aliceNode = net.createPartyNode(notaryNode.info, ALICE.name, ALICE_KEY)
var bobNode = net.createPartyNode(notaryNode.info, BOB.name, BOB_KEY)
@ -219,22 +238,6 @@ class TwoPartyTradeProtocolTests {
}, true, name, keyPair)
}
private fun runSeller(smm: StateMachineManager, notary: NodeInfo,
otherSide: SingleMessageRecipient, assetToSell: StateAndRef<OwnableState>, price: Amount,
myKeyPair: KeyPair, buyerSessionID: Long): ListenableFuture<SignedTransaction> {
val seller = TwoPartyTradeProtocol.Seller(otherSide, notary, assetToSell, price, myKeyPair, buyerSessionID)
return smm.add("${TwoPartyTradeProtocol.TRADE_TOPIC}.seller", seller)
}
private fun runBuyer(smm: StateMachineManager, notaryNode: NodeInfo,
otherSide: SingleMessageRecipient, acceptablePrice: Amount, typeToBuy: Class<out OwnableState>,
sessionID: Long): ListenableFuture<SignedTransaction> {
val buyer = TwoPartyTradeProtocol.Buyer(otherSide, notaryNode.identity, acceptablePrice, typeToBuy, sessionID)
return smm.add("${TwoPartyTradeProtocol.TRADE_TOPIC}.buyer", buyer)
}
@Test
fun checkDependenciesOfSaleAssetAreResolved() {
transactionGroupFor<ContractState> {

View File

@ -1,8 +1,9 @@
package core.node.subsystems
package node.services
import core.messaging.Message
import core.messaging.MessageRecipients
import core.testutils.freeLocalHostAndPort
import core.testing.freeLocalHostAndPort
import node.services.messaging.ArtemisMessagingService
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before

View File

@ -1,6 +1,6 @@
package core.node.subsystems
package node.services
import core.testing.MockNetwork
import node.core.testing.MockNetwork
import org.junit.Before
import org.junit.Test

View File

@ -1,15 +1,16 @@
package core.node.services
package node.services
import co.paralleluniverse.fibers.Suspendable
import core.*
import core.crypto.SecureHash
import core.crypto.signWithECDSA
import core.node.NodeInfo
import core.protocols.ProtocolLogic
import core.serialization.serialize
import core.testing.MockNetwork
import core.utilities.AddOrRemove
import core.random63BitValue
import core.utilities.BriefLogFormatter
import node.core.testing.MockNetwork
import node.services.network.InMemoryNetworkMapService
import node.services.network.NetworkMapService
import node.services.network.NodeRegistration
import node.utilities.AddOrRemove
import org.junit.Before
import org.junit.Test
import java.security.PrivateKey

View File

@ -0,0 +1,69 @@
package node.services
import com.codahale.metrics.MetricRegistry
import core.messaging.MessagingService
import core.node.services.*
import core.node.services.testing.MockStorageService
import core.testing.MOCK_IDENTITY_SERVICE
import node.services.api.Checkpoint
import node.services.api.CheckpointStorage
import node.services.api.MonitoringService
import node.services.api.ServiceHubInternal
import node.services.network.MockNetworkMapCache
import node.services.network.NetworkMapService
import node.services.persistence.DataVendingService
import node.services.wallet.NodeWalletService
import java.time.Clock
import java.util.concurrent.ConcurrentLinkedQueue
class MockCheckpointStorage : CheckpointStorage {
private val _checkpoints = ConcurrentLinkedQueue<Checkpoint>()
override val checkpoints: Iterable<Checkpoint>
get() = _checkpoints.toList()
override fun addCheckpoint(checkpoint: Checkpoint) {
_checkpoints.add(checkpoint)
}
override fun removeCheckpoint(checkpoint: Checkpoint) {
require(_checkpoints.remove(checkpoint))
}
}
class MockServices(
customWallet: WalletService? = null,
val keyManagement: KeyManagementService? = null,
val net: MessagingService? = null,
val identity: IdentityService? = MOCK_IDENTITY_SERVICE,
val storage: StorageService? = MockStorageService(),
val mapCache: NetworkMapCache? = MockNetworkMapCache(),
val mapService: NetworkMapService? = null,
val overrideClock: Clock? = Clock.systemUTC()
) : ServiceHubInternal {
override val walletService: WalletService = customWallet ?: NodeWalletService(this)
override val keyManagementService: KeyManagementService
get() = keyManagement ?: throw UnsupportedOperationException()
override val identityService: IdentityService
get() = identity ?: throw UnsupportedOperationException()
override val networkService: MessagingService
get() = net ?: throw UnsupportedOperationException()
override val networkMapCache: NetworkMapCache
get() = mapCache ?: throw UnsupportedOperationException()
override val storageService: StorageService
get() = storage ?: throw UnsupportedOperationException()
override val clock: Clock
get() = overrideClock ?: throw UnsupportedOperationException()
override val monitoringService: MonitoringService = MonitoringService(MetricRegistry())
init {
if (net != null && storage != null) {
// Creating this class is sufficient, we don't have to store it anywhere, because it registers a listener
// on the networking service, so that will keep it from being collected.
DataVendingService(net, storage)
}
}
}

View File

@ -1,14 +1,15 @@
package core.node
package node.services
import com.codahale.metrics.MetricRegistry
import com.google.common.jimfs.Configuration
import com.google.common.jimfs.Jimfs
import core.crypto.SecureHash
import core.node.services.NodeAttachmentService
import core.use
import node.services.persistence.NodeAttachmentService
import org.junit.Before
import org.junit.Test
import java.nio.charset.Charset
import java.nio.file.FileAlreadyExistsException
import java.nio.file.FileSystem
import java.nio.file.Files
import java.nio.file.Path
@ -52,7 +53,7 @@ class NodeAttachmentStorageTest {
val testJar = makeTestJar()
val storage = NodeAttachmentService(fs.getPath("/"), MetricRegistry())
testJar.use { storage.importAttachment(it) }
assertFailsWith<java.nio.file.FileAlreadyExistsException> {
assertFailsWith<FileAlreadyExistsException> {
testJar.use { storage.importAttachment(it) }
}
}

View File

@ -1,13 +1,18 @@
package core.node.services
package node.services
import contracts.Cash
import contracts.testing.CASH
import contracts.testing.`owned by`
import core.bd
import core.contracts.DOLLARS
import core.contracts.Fix
import core.contracts.TransactionBuilder
import core.bd
import core.testing.MockNetwork
import core.testutils.*
import core.testing.ALICE_PUBKEY
import core.testing.MEGA_CORP
import core.testing.MEGA_CORP_KEY
import core.utilities.BriefLogFormatter
import node.core.testing.MockNetwork
import node.services.clientapi.NodeInterestRates
import org.junit.Assert
import org.junit.Test
import protocols.RatesFixProtocol

View File

@ -1,14 +1,16 @@
package core.node.subsystems
package node.services
import contracts.Cash
import core.*
import core.contracts.DOLLARS
import core.contracts.TransactionBuilder
import core.contracts.USD
import core.contracts.verifyToLedgerTransaction
import core.node.ServiceHub
import core.testutils.*
import core.node.services.testing.MockKeyManagementService
import core.node.services.testing.MockStorageService
import core.testing.*
import core.utilities.BriefLogFormatter
import node.services.wallet.NodeWalletService
import org.junit.After
import org.junit.Before
import org.junit.Test
@ -65,7 +67,7 @@ class NodeWalletServiceTest {
Cash().generateIssue(this, 100.DOLLARS, MEGA_CORP.ref(1), freshKey.public, DUMMY_NOTARY)
signWith(MEGA_CORP_KEY)
}.toSignedTransaction()
val myOutput = usefulTX.verifyToLedgerTransaction(MockIdentityService, MockStorageService().attachments).outRef<Cash.State>(0)
val myOutput = usefulTX.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, MockStorageService().attachments).outRef<Cash.State>(0)
// A tx that spends our money.
val spendTX = TransactionBuilder().apply {

View File

@ -1,11 +1,11 @@
package core.node.services
package node.services
import core.contracts.TransactionBuilder
import core.seconds
import core.testing.MockNetwork
import core.testutils.DUMMY_NOTARY
import core.testutils.DUMMY_NOTARY_KEY
import core.testutils.issueState
import core.testing.DUMMY_NOTARY
import core.testing.DUMMY_NOTARY_KEY
import node.core.testing.MockNetwork
import node.testutils.issueState
import org.junit.Before
import org.junit.Test
import protocols.NotaryError
@ -37,7 +37,7 @@ class NotaryServiceTests {
tx.setTime(Instant.now(), DUMMY_NOTARY, 30.seconds)
var wtx = tx.toWireTransaction()
val protocol = NotaryProtocol(wtx, NotaryProtocol.tracker())
val protocol = NotaryProtocol(wtx, NotaryProtocol.Companion.tracker())
val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol)
net.runNetwork()
@ -49,7 +49,7 @@ class NotaryServiceTests {
val inputState = issueState(clientNode)
val wtx = TransactionBuilder().withItems(inputState).toWireTransaction()
val protocol = NotaryProtocol(wtx, NotaryProtocol.tracker())
val protocol = NotaryProtocol(wtx, NotaryProtocol.Companion.tracker())
val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol)
net.runNetwork()
@ -63,7 +63,7 @@ class NotaryServiceTests {
tx.setTime(Instant.now().plusSeconds(3600), DUMMY_NOTARY, 30.seconds)
var wtx = tx.toWireTransaction()
val protocol = NotaryProtocol(wtx, NotaryProtocol.tracker())
val protocol = NotaryProtocol(wtx, NotaryProtocol.Companion.tracker())
val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol)
net.runNetwork()

View File

@ -1,9 +1,11 @@
package core.node.storage
package node.services
import com.google.common.jimfs.Configuration.unix
import com.google.common.jimfs.Jimfs
import com.google.common.primitives.Ints
import core.serialization.SerializedBytes
import node.services.api.Checkpoint
import node.services.persistence.PerFileCheckpointStorage
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.After

View File

@ -1,7 +1,8 @@
package core.node.services
package node.services
import core.contracts.TimestampCommand
import core.seconds
import node.services.transactions.TimestampChecker
import org.junit.Test
import java.time.Clock
import java.time.Instant

View File

@ -1,8 +1,10 @@
package core.node.services
package node.services
import core.contracts.TransactionBuilder
import core.testutils.MEGA_CORP
import core.testutils.generateStateRef
import core.node.services.UniquenessException
import core.testing.MEGA_CORP
import core.testing.generateStateRef
import node.services.transactions.InMemoryUniquenessProvider
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

View File

@ -1,4 +1,4 @@
package core.utilities
package node.utilities
import org.junit.After
import org.junit.Test

View File

@ -1,4 +1,4 @@
package core.visualiser
package node.visualiser
import org.graphstream.graph.Edge
import org.graphstream.graph.Element
@ -42,7 +42,7 @@ fun createGraph(name: String, styles: String): SingleGraph {
}
}
class MyViewer(graph: Graph) : Viewer(graph, Viewer.ThreadingModel.GRAPH_IN_ANOTHER_THREAD) {
class MyViewer(graph: Graph) : Viewer(graph, ThreadingModel.GRAPH_IN_ANOTHER_THREAD) {
override fun enableAutoLayout(layoutAlgorithm: Layout) {
super.enableAutoLayout(layoutAlgorithm)

View File

@ -1,9 +1,9 @@
package core.visualiser
package node.visualiser
import core.contracts.CommandData
import core.contracts.ContractState
import core.crypto.SecureHash
import core.testutils.TransactionGroupDSL
import core.testing.TransactionGroupDSL
import org.graphstream.graph.Edge
import org.graphstream.graph.Node
import org.graphstream.graph.implementations.SingleGraph

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="core.visualiser.StateViewer">
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="node.visualiser.StateViewer">
<grid id="27dc6" binding="root" layout-manager="BorderLayout" hgap="15" vgap="15">
<constraints>
<xy x="20" y="20" width="500" height="400"/>

View File

@ -1,4 +1,4 @@
package core.visualiser;
package node.visualiser;
import kotlin.*;