mirror of
https://github.com/corda/corda.git
synced 2025-06-23 09:25:36 +00:00
Merge remote-tracking branch 'open/master' into aslemmer-merge-19-Feb
This commit is contained in:
@ -0,0 +1,6 @@
|
||||
package net.corda.core.cordapp
|
||||
|
||||
/**
|
||||
* Thrown if an exception occurs in accessing or parsing cordapp configuration
|
||||
*/
|
||||
class CordappConfigException(msg: String, e: Throwable) : Exception(msg, e)
|
70
core/src/main/kotlin/net/corda/core/cordapp/CordappConfig.kt
Normal file
70
core/src/main/kotlin/net/corda/core/cordapp/CordappConfig.kt
Normal file
@ -0,0 +1,70 @@
|
||||
package net.corda.core.cordapp
|
||||
|
||||
import net.corda.core.DoNotImplement
|
||||
|
||||
/**
|
||||
* Provides access to cordapp configuration independent of the configuration provider.
|
||||
*/
|
||||
@DoNotImplement
|
||||
interface CordappConfig {
|
||||
/**
|
||||
* Check if a config exists at path
|
||||
*/
|
||||
fun exists(path: String): Boolean
|
||||
|
||||
/**
|
||||
* Get the value of the configuration at "path".
|
||||
*
|
||||
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||
*/
|
||||
fun get(path: String): Any
|
||||
|
||||
/**
|
||||
* Get the int value of the configuration at "path".
|
||||
*
|
||||
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||
*/
|
||||
fun getInt(path: String): Int
|
||||
|
||||
/**
|
||||
* Get the long value of the configuration at "path".
|
||||
*
|
||||
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||
*/
|
||||
fun getLong(path: String): Long
|
||||
|
||||
/**
|
||||
* Get the float value of the configuration at "path".
|
||||
*
|
||||
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||
*/
|
||||
fun getFloat(path: String): Float
|
||||
|
||||
/**
|
||||
* Get the double value of the configuration at "path".
|
||||
*
|
||||
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||
*/
|
||||
fun getDouble(path: String): Double
|
||||
|
||||
/**
|
||||
* Get the number value of the configuration at "path".
|
||||
*
|
||||
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||
*/
|
||||
fun getNumber(path: String): Number
|
||||
|
||||
/**
|
||||
* Get the string value of the configuration at "path".
|
||||
*
|
||||
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||
*/
|
||||
fun getString(path: String): String
|
||||
|
||||
/**
|
||||
* Get the boolean value of the configuration at "path".
|
||||
*
|
||||
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||
*/
|
||||
fun getBoolean(path: String): Boolean
|
||||
}
|
@ -2,8 +2,6 @@ package net.corda.core.cordapp
|
||||
|
||||
import net.corda.core.crypto.SecureHash
|
||||
|
||||
// TODO: Add per app config
|
||||
|
||||
/**
|
||||
* An app context provides information about where an app was loaded from, access to its classloader,
|
||||
* and (in the included [Cordapp] object) lists of annotated classes discovered via scanning the JAR.
|
||||
@ -15,5 +13,11 @@ import net.corda.core.crypto.SecureHash
|
||||
* @property attachmentId For CorDapps containing [Contract] or [UpgradedContract] implementations this will be populated
|
||||
* with the attachment containing those class files
|
||||
* @property classLoader the classloader used to load this cordapp's classes
|
||||
* @property config Configuration for this CorDapp
|
||||
*/
|
||||
class CordappContext(val cordapp: Cordapp, val attachmentId: SecureHash?, val classLoader: ClassLoader)
|
||||
class CordappContext internal constructor(
|
||||
val cordapp: Cordapp,
|
||||
val attachmentId: SecureHash?,
|
||||
val classLoader: ClassLoader,
|
||||
val config: CordappConfig
|
||||
)
|
||||
|
101
core/src/main/kotlin/net/corda/core/flows/NotarisationRequest.kt
Normal file
101
core/src/main/kotlin/net/corda/core/flows/NotarisationRequest.kt
Normal file
@ -0,0 +1,101 @@
|
||||
package net.corda.core.flows
|
||||
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.CoreTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.SignatureException
|
||||
|
||||
/**
|
||||
* A notarisation request specifies a list of states to consume and the id of the consuming transaction. Its primary
|
||||
* purpose is for notarisation traceability – a signature over the notarisation request, [NotarisationRequestSignature],
|
||||
* allows a notary to prove that a certain party requested the consumption of a particular state.
|
||||
*
|
||||
* While the signature must be retained, the notarisation request does not need to be transferred or stored anywhere - it
|
||||
* can be built from a [SignedTransaction] or a [CoreTransaction]. The notary can recompute it from the committed states index.
|
||||
*
|
||||
* In case there is a need to prove that a party spent a particular state, the notary will:
|
||||
* 1) Locate the consuming transaction id in the index, along with all other states consumed in the same transaction.
|
||||
* 2) Build a [NotarisationRequest].
|
||||
* 3) Locate the [NotarisationRequestSignature] for the transaction id. The signature will contain the signing public key.
|
||||
* 4) Demonstrate the signature verifies against the serialized request. The provided states are always sorted internally,
|
||||
* to ensure the serialization does not get affected by the order.
|
||||
*/
|
||||
@CordaSerializable
|
||||
class NotarisationRequest(statesToConsume: List<StateRef>, val transactionId: SecureHash) {
|
||||
companion object {
|
||||
/** Sorts in ascending order first by transaction hash, then by output index. */
|
||||
private val stateRefComparator = compareBy<StateRef>({ it.txhash }, { it.index })
|
||||
}
|
||||
|
||||
private val _statesToConsumeSorted = statesToConsume.sortedWith(stateRefComparator)
|
||||
|
||||
/** States this request specifies to be consumed. Sorted to ensure the serialized form does not get affected by the state order. */
|
||||
val statesToConsume: List<StateRef> get() = _statesToConsumeSorted // Getter required for AMQP serialization
|
||||
|
||||
/** Verifies the signature against this notarisation request. Checks that the signature is issued by the right party. */
|
||||
fun verifySignature(requestSignature: NotarisationRequestSignature, intendedSigner: Party) {
|
||||
val signature = requestSignature.digitalSignature
|
||||
if (intendedSigner.owningKey != signature.by) {
|
||||
val errorMessage = "Expected a signature by ${intendedSigner.owningKey}, but received by ${signature.by}}"
|
||||
throw NotaryException(NotaryError.RequestSignatureInvalid(IllegalArgumentException(errorMessage)))
|
||||
}
|
||||
// TODO: if requestSignature was generated over an old version of NotarisationRequest, we need to be able to
|
||||
// reserialize it in that version to get the exact same bytes. Modify the serialization logic once that's
|
||||
// available.
|
||||
val expectedSignedBytes = this.serialize().bytes
|
||||
verifyCorrectBytesSigned(signature, expectedSignedBytes)
|
||||
}
|
||||
|
||||
private fun verifyCorrectBytesSigned(signature: DigitalSignature.WithKey, bytes: ByteArray) {
|
||||
try {
|
||||
signature.verify(bytes)
|
||||
} catch (e: Exception) {
|
||||
when (e) {
|
||||
is InvalidKeyException, is SignatureException -> {
|
||||
val error = NotaryError.RequestSignatureInvalid(e)
|
||||
throw NotaryException(error)
|
||||
}
|
||||
else -> throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper around a digital signature used for notarisation requests.
|
||||
*
|
||||
* The [platformVersion] is required so the notary can verify the signature against the right version of serialized
|
||||
* bytes of the [NotarisationRequest]. Otherwise, the request may be rejected.
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class NotarisationRequestSignature(val digitalSignature: DigitalSignature.WithKey, val platformVersion: Int)
|
||||
|
||||
/**
|
||||
* Container for the transaction and notarisation request signature.
|
||||
* This is the payload that gets sent by a client to a notary service for committing the input states of the [transaction].
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class NotarisationPayload(val transaction: Any, val requestSignature: NotarisationRequestSignature) {
|
||||
init {
|
||||
require(transaction is SignedTransaction || transaction is CoreTransaction) {
|
||||
"Unsupported transaction type in the notarisation payload: ${transaction.javaClass.simpleName}"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper for automatically casting the underlying [transaction] payload to a [SignedTransaction].
|
||||
* Should only be used by validating notaries.
|
||||
*/
|
||||
val signedTransaction get() = transaction as SignedTransaction
|
||||
/**
|
||||
* A helper for automatically casting the underlying [transaction] payload to a [CoreTransaction].
|
||||
* Should only be used by non-validating notaries.
|
||||
*/
|
||||
val coreTransaction get() = transaction as CoreTransaction
|
||||
}
|
@ -9,10 +9,12 @@ import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.crypto.keys
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.FetchDataFlow
|
||||
import net.corda.core.internal.generateSignature
|
||||
import net.corda.core.node.services.NotaryService
|
||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
||||
import net.corda.core.node.services.UniquenessProvider
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.CoreTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.UntrustworthyData
|
||||
@ -73,15 +75,17 @@ class NotaryFlow {
|
||||
return notaryParty
|
||||
}
|
||||
|
||||
/** Notarises the transaction with the [notaryParty], obtains the notary's signature(s). */
|
||||
@Throws(NotaryException::class)
|
||||
@Suspendable
|
||||
protected fun notarise(notaryParty: Party): UntrustworthyData<List<TransactionSignature>> {
|
||||
return try {
|
||||
val session = initiateFlow(notaryParty)
|
||||
val requestSignature = NotarisationRequest(stx.inputs, stx.id).generateSignature(serviceHub)
|
||||
if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) {
|
||||
sendAndReceiveValidating(session)
|
||||
sendAndReceiveValidating(session, requestSignature)
|
||||
} else {
|
||||
sendAndReceiveNonValidating(notaryParty, session)
|
||||
sendAndReceiveNonValidating(notaryParty, session, requestSignature)
|
||||
}
|
||||
} catch (e: NotaryException) {
|
||||
if (e.error is NotaryError.Conflict) {
|
||||
@ -92,21 +96,23 @@ class NotaryFlow {
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
protected open fun sendAndReceiveValidating(session: FlowSession): UntrustworthyData<List<TransactionSignature>> {
|
||||
subFlow(SendTransactionWithRetry(session, stx))
|
||||
private fun sendAndReceiveValidating(session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData<List<TransactionSignature>> {
|
||||
val payload = NotarisationPayload(stx, signature)
|
||||
subFlow(NotarySendTransactionFlow(session, payload))
|
||||
return session.receive()
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
protected open fun sendAndReceiveNonValidating(notaryParty: Party, session: FlowSession): UntrustworthyData<List<TransactionSignature>> {
|
||||
val tx: Any = if (stx.isNotaryChangeTransaction()) {
|
||||
private fun sendAndReceiveNonValidating(notaryParty: Party, session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData<List<TransactionSignature>> {
|
||||
val tx: CoreTransaction = if (stx.isNotaryChangeTransaction()) {
|
||||
stx.notaryChangeTx // Notary change transactions do not support filtering
|
||||
} else {
|
||||
stx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow || it == notaryParty })
|
||||
}
|
||||
return session.sendAndReceiveWithRetry(tx)
|
||||
return session.sendAndReceiveWithRetry(NotarisationPayload(tx, signature))
|
||||
}
|
||||
|
||||
/** Checks that the notary's signature(s) is/are valid. */
|
||||
protected fun validateResponse(response: UntrustworthyData<List<TransactionSignature>>, notaryParty: Party): List<TransactionSignature> {
|
||||
return response.unwrap { signatures ->
|
||||
signatures.forEach { validateSignature(it, stx.id, notaryParty) }
|
||||
@ -118,16 +124,16 @@ class NotaryFlow {
|
||||
check(sig.by in notaryParty.owningKey.keys) { "Invalid signer for the notary result" }
|
||||
sig.verify(txId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The [SendTransactionWithRetry] flow is equivalent to [SendTransactionFlow] but using [sendAndReceiveWithRetry]
|
||||
* instead of [sendAndReceive], [SendTransactionWithRetry] is intended to be use by the notary client only.
|
||||
*/
|
||||
private class SendTransactionWithRetry(otherSideSession: FlowSession, stx: SignedTransaction) : SendTransactionFlow(otherSideSession, stx) {
|
||||
@Suspendable
|
||||
override fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any): UntrustworthyData<FetchDataFlow.Request> {
|
||||
return otherSideSession.sendAndReceiveWithRetry(payload)
|
||||
/**
|
||||
* The [NotarySendTransactionFlow] flow is similar to [SendTransactionFlow], but uses [NotarisationPayload] as the
|
||||
* initial message, and retries message delivery.
|
||||
*/
|
||||
private class NotarySendTransactionFlow(otherSide: FlowSession, payload: NotarisationPayload) : DataVendingFlow(otherSide, payload) {
|
||||
@Suspendable
|
||||
override fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any): UntrustworthyData<FetchDataFlow.Request> {
|
||||
return otherSideSession.sendAndReceiveWithRetry(payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,10 +192,16 @@ class NotaryFlow {
|
||||
*/
|
||||
data class TransactionParts(val id: SecureHash, val inputs: List<StateRef>, val timestamp: TimeWindow?, val notary: Party?)
|
||||
|
||||
/**
|
||||
* Exception thrown by the notary service if any issues are encountered while trying to commit a transaction. The
|
||||
* underlying [error] specifies the cause of failure.
|
||||
*/
|
||||
class NotaryException(val error: NotaryError) : FlowException("Unable to notarise: $error")
|
||||
|
||||
/** Specifies the cause for notarisation request failure. */
|
||||
@CordaSerializable
|
||||
sealed class NotaryError {
|
||||
/** Occurs when one or more input states of transaction with [txId] have already been consumed by another transaction. */
|
||||
data class Conflict(val txId: SecureHash, val conflict: SignedData<UniquenessProvider.Conflict>) : NotaryError() {
|
||||
override fun toString() = "One or more input states for transaction $txId have been used in another transaction"
|
||||
}
|
||||
@ -199,18 +211,27 @@ sealed class NotaryError {
|
||||
override fun toString() = "Current time $currentTime is outside the time bounds specified by the transaction: $txTimeWindow"
|
||||
|
||||
companion object {
|
||||
@JvmField @Deprecated("Here only for binary compatibility purposes, do not use.")
|
||||
@JvmField
|
||||
@Deprecated("Here only for binary compatibility purposes, do not use.")
|
||||
val INSTANCE = TimeWindowInvalid(Instant.EPOCH, TimeWindow.fromOnly(Instant.EPOCH))
|
||||
}
|
||||
}
|
||||
|
||||
/** Occurs when the provided transaction fails to verify. */
|
||||
data class TransactionInvalid(val cause: Throwable) : NotaryError() {
|
||||
override fun toString() = cause.toString()
|
||||
}
|
||||
|
||||
/** Occurs when the transaction sent for notarisation is assigned to a different notary identity. */
|
||||
object WrongNotary : NotaryError()
|
||||
|
||||
data class General(val cause: String): NotaryError() {
|
||||
override fun toString() = cause
|
||||
/** Occurs when the notarisation request signature does not verify for the provided transaction. */
|
||||
data class RequestSignatureInvalid(val cause: Throwable) : NotaryError() {
|
||||
override fun toString() = "Request signature invalid: $cause"
|
||||
}
|
||||
|
||||
/** Occurs when the notary service encounters an unexpected issue or becomes temporarily unavailable. */
|
||||
data class General(val cause: Throwable) : NotaryError() {
|
||||
override fun toString() = cause.toString()
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ open class SendTransactionFlow(otherSide: FlowSession, stx: SignedTransaction) :
|
||||
*/
|
||||
open class SendStateAndRefFlow(otherSideSession: FlowSession, stateAndRefs: List<StateAndRef<*>>) : DataVendingFlow(otherSideSession, stateAndRefs)
|
||||
|
||||
sealed class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any) : FlowLogic<Void?>() {
|
||||
open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any) : FlowLogic<Void?>() {
|
||||
@Suspendable
|
||||
protected open fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any) = otherSideSession.sendAndReceive<FetchDataFlow.Request>(payload)
|
||||
|
||||
|
@ -1,14 +0,0 @@
|
||||
package net.corda.core.internal
|
||||
|
||||
import net.corda.core.node.NetworkParameters
|
||||
|
||||
// TODO: This will cause problems when we run tests in parallel, make each node have its own properties.
|
||||
object GlobalProperties {
|
||||
private var _networkParameters: NetworkParameters? = null
|
||||
|
||||
var networkParameters: NetworkParameters
|
||||
get() = checkNotNull(_networkParameters) { "Property 'networkParameters' has not been initialised." }
|
||||
set(value) {
|
||||
_networkParameters = value
|
||||
}
|
||||
}
|
@ -2,9 +2,16 @@
|
||||
|
||||
package net.corda.core.internal
|
||||
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.cordapp.CordappConfig
|
||||
import net.corda.core.cordapp.CordappContext
|
||||
import net.corda.core.cordapp.CordappProvider
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.flows.NotarisationRequest
|
||||
import net.corda.core.flows.NotarisationRequestSignature
|
||||
import net.corda.core.flows.NotaryFlow
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
@ -375,3 +382,23 @@ inline fun <T : Any> SerializedBytes<T>.sign(keyPair: KeyPair): SignedData<T> {
|
||||
}
|
||||
|
||||
fun ByteBuffer.copyBytes() = ByteArray(remaining()).also { get(it) }
|
||||
|
||||
fun createCordappContext(cordapp: Cordapp, attachmentId: SecureHash?, classLoader: ClassLoader, config: CordappConfig): CordappContext {
|
||||
return CordappContext(cordapp, attachmentId, classLoader, config)
|
||||
}
|
||||
/** Verifies that the correct notarisation request was signed by the counterparty. */
|
||||
fun NotaryFlow.Service.validateRequest(request: NotarisationRequest, signature: NotarisationRequestSignature) {
|
||||
val requestingParty = otherSideSession.counterparty
|
||||
request.verifySignature(signature, requestingParty)
|
||||
// TODO: persist the signature for traceability. Do we need to persist the request as well?
|
||||
}
|
||||
|
||||
/** Creates a signature over the notarisation request using the legal identity key. */
|
||||
fun NotarisationRequest.generateSignature(serviceHub: ServiceHub): NotarisationRequestSignature {
|
||||
val serializedRequest = this.serialize().bytes
|
||||
val signature = with(serviceHub) {
|
||||
val myLegalIdentity = myInfo.legalIdentitiesAndCerts.first().owningKey
|
||||
keyManagementService.sign(serializedRequest, myLegalIdentity)
|
||||
}
|
||||
return NotarisationRequestSignature(signature, serviceHub.myInfo.platformVersion)
|
||||
}
|
@ -26,7 +26,9 @@ data class NodeInfo(val addresses: List<NetworkHostAndPort>,
|
||||
) {
|
||||
// TODO We currently don't support multi-IP/multi-identity nodes, we only left slots in the data structures.
|
||||
init {
|
||||
require(legalIdentitiesAndCerts.isNotEmpty()) { "Node should have at least one legal identity" }
|
||||
require(addresses.isNotEmpty()) { "Node must have at least one address" }
|
||||
require(legalIdentitiesAndCerts.isNotEmpty()) { "Node must have at least one legal identity" }
|
||||
require(platformVersion > 0) { "Platform version must be at least 1" }
|
||||
}
|
||||
|
||||
@Transient private var _legalIdentities: List<Party>? = null
|
||||
|
@ -2,6 +2,7 @@ package net.corda.core.node
|
||||
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.cordapp.CordappContext
|
||||
import net.corda.core.cordapp.CordappProvider
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SignableData
|
||||
@ -60,6 +61,9 @@ interface ServicesForResolution : StateLoader {
|
||||
|
||||
/** Provides access to anything relating to cordapps including contract attachment resolution and app context */
|
||||
val cordappProvider: CordappProvider
|
||||
|
||||
/** Returns the network parameters the node is operating under. */
|
||||
val networkParameters: NetworkParameters
|
||||
}
|
||||
|
||||
/**
|
||||
@ -369,4 +373,9 @@ interface ServiceHub : ServicesForResolution {
|
||||
* node starts.
|
||||
*/
|
||||
fun registerUnloadHandler(runOnStop: () -> Unit)
|
||||
|
||||
/**
|
||||
* See [CordappProvider.getAppContext]
|
||||
*/
|
||||
fun getAppContext(): CordappContext = cordappProvider.getAppContext()
|
||||
}
|
||||
|
@ -67,12 +67,22 @@ interface NetworkMapCacheBase {
|
||||
fun track(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange>
|
||||
|
||||
/**
|
||||
* Look up the node info for a legal name.
|
||||
* Notice that when there are more than one node for a given name (in case of distributed services) first service node
|
||||
* found will be returned.
|
||||
* Return a [NodeInfo] which has the given legal name for one of its identities, or null if no such node is found.
|
||||
*
|
||||
* @throws IllegalArgumentException If more than one matching node is found, in the case of a distributed service identity
|
||||
* (such as with a notary cluster). For such a scenerio use [getNodesByLegalName] instead.
|
||||
*/
|
||||
fun getNodeByLegalName(name: CordaX500Name): NodeInfo?
|
||||
|
||||
/**
|
||||
* Return a list of [NodeInfo]s which have the given legal name for one of their identities, or an empty list if no
|
||||
* such nodes are found.
|
||||
*
|
||||
* Normally there is at most one node for a legal name, but for distributed service identities (such as with a notary
|
||||
* cluster) there can be multiple nodes sharing the same identity.
|
||||
*/
|
||||
fun getNodesByLegalName(name: CordaX500Name): List<NodeInfo>
|
||||
|
||||
/** Look up the node info for a host and port. */
|
||||
fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo?
|
||||
|
||||
@ -100,13 +110,6 @@ interface NetworkMapCacheBase {
|
||||
*/
|
||||
fun getNodesByLegalIdentityKey(identityKey: PublicKey): List<NodeInfo>
|
||||
|
||||
/**
|
||||
* Look up the node information entries for a legal name.
|
||||
* Note that normally there will be only one node for a legal name, but for clusters of nodes or distributed services there
|
||||
* can be multiple nodes.
|
||||
*/
|
||||
fun getNodesByLegalName(name: CordaX500Name): List<NodeInfo>
|
||||
|
||||
/** Returns information about the party, which may be a specific node or a service */
|
||||
fun getPartyInfo(party: Party): PartyInfo?
|
||||
|
||||
|
@ -94,7 +94,7 @@ abstract class TrustedAuthorityNotaryService : NotaryService() {
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log.error("Internal error", e)
|
||||
throw NotaryException(NotaryError.General("Service unavailable, please try again later"))
|
||||
throw NotaryException(NotaryError.General(Exception("Service unavailable, please try again later")))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,12 +3,14 @@ package net.corda.core.transactions
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
|
||||
/**
|
||||
* A transaction with the minimal amount of information required to compute the unique transaction [id], and
|
||||
* resolve a [FullTransaction]. This type of transaction, wrapped in [SignedTransaction], gets transferred across the
|
||||
* wire and recorded to storage.
|
||||
*/
|
||||
@CordaSerializable
|
||||
abstract class CoreTransaction : BaseTransaction() {
|
||||
/** The inputs of this transaction, containing state references only **/
|
||||
abstract override val inputs: List<StateRef>
|
||||
|
@ -5,7 +5,6 @@ import net.corda.core.contracts.ComponentGroupEnum.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.Emoji
|
||||
import net.corda.core.internal.GlobalProperties
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
@ -85,11 +84,12 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
*/
|
||||
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
|
||||
fun toLedgerTransaction(services: ServicesForResolution): LedgerTransaction {
|
||||
return toLedgerTransaction(
|
||||
return toLedgerTransactionInternal(
|
||||
resolveIdentity = { services.identityService.partyFromKey(it) },
|
||||
resolveAttachment = { services.attachments.openAttachment(it) },
|
||||
resolveStateRef = { services.loadState(it) },
|
||||
resolveContractAttachment = { services.cordappProvider.getContractAttachmentID(it.contract) }
|
||||
resolveContractAttachment = { services.cordappProvider.getContractAttachmentID(it.contract) },
|
||||
maxTransactionSize = services.networkParameters.maxTransactionSize
|
||||
)
|
||||
}
|
||||
|
||||
@ -100,12 +100,23 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
* @throws AttachmentResolutionException if a required attachment was not found using [resolveAttachment].
|
||||
* @throws TransactionResolutionException if an input was not found not using [resolveStateRef].
|
||||
*/
|
||||
@Deprecated("Use toLedgerTransaction(ServicesForTransaction) instead")
|
||||
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
|
||||
fun toLedgerTransaction(
|
||||
resolveIdentity: (PublicKey) -> Party?,
|
||||
resolveAttachment: (SecureHash) -> Attachment?,
|
||||
resolveStateRef: (StateRef) -> TransactionState<*>?,
|
||||
resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?
|
||||
): LedgerTransaction {
|
||||
return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, resolveStateRef, resolveContractAttachment, 10485760)
|
||||
}
|
||||
|
||||
private fun toLedgerTransactionInternal(
|
||||
resolveIdentity: (PublicKey) -> Party?,
|
||||
resolveAttachment: (SecureHash) -> Attachment?,
|
||||
resolveStateRef: (StateRef) -> TransactionState<*>?,
|
||||
resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?,
|
||||
maxTransactionSize: Int
|
||||
): LedgerTransaction {
|
||||
// Look up public keys to authenticated identities. This is just a stub placeholder and will all change in future.
|
||||
val authenticatedArgs = commands.map {
|
||||
@ -120,15 +131,15 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
// Order of attachments is important since contracts may refer to indexes so only append automatic attachments
|
||||
val attachments = (attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) } + contractAttachments).distinct()
|
||||
val ltx = LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt)
|
||||
checkTransactionSize(ltx)
|
||||
checkTransactionSize(ltx, maxTransactionSize)
|
||||
return ltx
|
||||
}
|
||||
|
||||
private fun checkTransactionSize(ltx: LedgerTransaction) {
|
||||
var remainingTransactionSize = GlobalProperties.networkParameters.maxTransactionSize
|
||||
private fun checkTransactionSize(ltx: LedgerTransaction, maxTransactionSize: Int) {
|
||||
var remainingTransactionSize = maxTransactionSize
|
||||
|
||||
fun minus(size: Int) {
|
||||
require(remainingTransactionSize > size) { "Transaction exceeded network's maximum transaction size limit : ${GlobalProperties.networkParameters.maxTransactionSize} bytes." }
|
||||
require(remainingTransactionSize > size) { "Transaction exceeded network's maximum transaction size limit : $maxTransactionSize bytes." }
|
||||
remainingTransactionSize -= size
|
||||
}
|
||||
|
||||
|
@ -58,19 +58,19 @@ class PartialMerkleTreeTest {
|
||||
hashed = nodes.map { it.serialize().sha256() }
|
||||
expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash)).hash
|
||||
merkleTree = MerkleTree.getMerkleTree(hashed)
|
||||
testLedger = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
|
||||
testLedger = MockServices(emptyList(), MEGA_CORP.name, rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
}, MEGA_CORP.name).ledger(DUMMY_NOTARY) {
|
||||
}).ledger(DUMMY_NOTARY) {
|
||||
unverifiedTransaction {
|
||||
attachments(Cash.PROGRAM_ID)
|
||||
output(Cash.PROGRAM_ID, "MEGA_CORP cash",
|
||||
Cash.State(
|
||||
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||
owner = MEGA_CORP))
|
||||
Cash.State(
|
||||
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||
owner = MEGA_CORP))
|
||||
output(Cash.PROGRAM_ID, "dummy cash 1",
|
||||
Cash.State(
|
||||
amount = 900.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||
owner = MINI_CORP))
|
||||
Cash.State(
|
||||
amount = 900.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||
owner = MINI_CORP))
|
||||
}
|
||||
transaction {
|
||||
attachments(Cash.PROGRAM_ID)
|
||||
|
@ -13,7 +13,6 @@ import net.corda.node.services.persistence.NodeAttachmentService
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import net.corda.testing.node.MockNodeParameters
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.startFlow
|
||||
|
@ -11,12 +11,13 @@ import net.corda.core.identity.groupAbstractPartyByWellKnownParty
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.internal.StartedNode
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import net.corda.testing.node.StartedMockNode
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
|
||||
import net.corda.testing.node.startFlow
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
@ -29,10 +30,10 @@ class CollectSignaturesFlowTests {
|
||||
private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
|
||||
}
|
||||
|
||||
private lateinit var mockNet: MockNetwork
|
||||
private lateinit var aliceNode: StartedMockNode
|
||||
private lateinit var bobNode: StartedMockNode
|
||||
private lateinit var charlieNode: StartedMockNode
|
||||
private lateinit var mockNet: InternalMockNetwork
|
||||
private lateinit var aliceNode: StartedNode<MockNode>
|
||||
private lateinit var bobNode: StartedNode<MockNode>
|
||||
private lateinit var charlieNode: StartedNode<MockNode>
|
||||
private lateinit var alice: Party
|
||||
private lateinit var bob: Party
|
||||
private lateinit var charlie: Party
|
||||
@ -40,7 +41,7 @@ class CollectSignaturesFlowTests {
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
|
||||
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
|
||||
aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||
bobNode = mockNet.createPartyNode(BOB_NAME)
|
||||
charlieNode = mockNet.createPartyNode(CHARLIE_NAME)
|
||||
@ -136,7 +137,7 @@ class CollectSignaturesFlowTests {
|
||||
@Test
|
||||
fun `fails when not signed by initiator`() {
|
||||
val onePartyDummyContract = DummyContract.generateInitial(1337, notary, alice.ref(1))
|
||||
val miniCorpServices = MockServices(listOf("net.corda.testing.contracts"), rigorousMock(), miniCorp)
|
||||
val miniCorpServices = MockServices(listOf("net.corda.testing.contracts"), miniCorp, rigorousMock())
|
||||
val ptx = miniCorpServices.signInitialTransaction(onePartyDummyContract)
|
||||
val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet()))
|
||||
mockNet.runNetwork()
|
||||
|
@ -7,9 +7,9 @@ import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.testing.core.DEV_ROOT_CA
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.core.getTestPartyAndCertificate
|
||||
import net.corda.testing.internal.DEV_ROOT_CA
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -8,10 +8,11 @@ import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.sequence
|
||||
import net.corda.node.internal.StartedNode
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.node.StartedMockNode
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
|
||||
import net.corda.testing.node.startFlow
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
@ -27,17 +28,17 @@ import kotlin.test.assertNull
|
||||
|
||||
// DOCSTART 3
|
||||
class ResolveTransactionsFlowTest {
|
||||
private lateinit var mockNet: MockNetwork
|
||||
private lateinit var notaryNode: StartedMockNode
|
||||
private lateinit var megaCorpNode: StartedMockNode
|
||||
private lateinit var miniCorpNode: StartedMockNode
|
||||
private lateinit var mockNet: InternalMockNetwork
|
||||
private lateinit var notaryNode: StartedNode<MockNode>
|
||||
private lateinit var megaCorpNode: StartedNode<MockNode>
|
||||
private lateinit var miniCorpNode: StartedNode<MockNode>
|
||||
private lateinit var megaCorp: Party
|
||||
private lateinit var miniCorp: Party
|
||||
private lateinit var notary: Party
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
|
||||
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
|
||||
notaryNode = mockNet.defaultNotaryNode
|
||||
megaCorpNode = mockNet.createPartyNode(CordaX500Name("MegaCorp", "London", "GB"))
|
||||
miniCorpNode = mockNet.createPartyNode(CordaX500Name("MiniCorp", "London", "GB"))
|
||||
|
@ -18,7 +18,6 @@ import net.corda.node.services.persistence.NodeAttachmentService
|
||||
import net.corda.nodeapi.internal.persistence.currentDBSession
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import net.corda.testing.node.MockNodeParameters
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
|
@ -62,8 +62,8 @@ class TransactionSerializationTests {
|
||||
val inputState = StateAndRef(TransactionState(TestCash.State(depositRef, 100.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY), fakeStateRef)
|
||||
val outputState = TransactionState(TestCash.State(depositRef, 600.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY)
|
||||
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY)
|
||||
val megaCorpServices = MockServices(listOf("net.corda.core.serialization"), rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY)
|
||||
val notaryServices = MockServices(listOf("net.corda.core.serialization"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
|
||||
val megaCorpServices = MockServices(listOf("net.corda.core.serialization"), MEGA_CORP.name, rigorousMock(), MEGA_CORP_KEY)
|
||||
val notaryServices = MockServices(listOf("net.corda.core.serialization"), DUMMY_NOTARY.name, rigorousMock(), DUMMY_NOTARY_KEY)
|
||||
lateinit var tx: TransactionBuilder
|
||||
|
||||
@Before
|
||||
@ -107,7 +107,7 @@ class TransactionSerializationTests {
|
||||
Command(TestCash.Commands.Move(), DUMMY_KEY_2.public))
|
||||
|
||||
val ptx2 = notaryServices.signInitialTransaction(tx2)
|
||||
val dummyServices = MockServices(emptyList(), rigorousMock(), MEGA_CORP.name, DUMMY_KEY_2)
|
||||
val dummyServices = MockServices(emptyList(), MEGA_CORP.name, rigorousMock(), DUMMY_KEY_2)
|
||||
val stx2 = dummyServices.addSignature(ptx2)
|
||||
|
||||
stx.copy(sigs = stx2.sigs).verifyRequiredSignatures()
|
||||
|
@ -10,6 +10,7 @@ import net.corda.core.identity.Party
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.internal.MockCordappProvider
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.junit.Before
|
||||
@ -29,14 +30,15 @@ class LedgerTransactionQueryTests {
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
private val keyPair = generateKeyPair()
|
||||
private val services = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(null).whenever(it).partyFromKey(keyPair.public)
|
||||
}, CordaX500Name("MegaCorp", "London", "GB"), keyPair)
|
||||
private val services = MockServices(emptyList(), CordaX500Name("MegaCorp", "London", "GB"),
|
||||
rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(null).whenever(it).partyFromKey(keyPair.public)
|
||||
}, keyPair)
|
||||
private val identity: Party = services.myInfo.singleIdentity()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
services.mockCordappProvider.addMockCordapp(DummyContract.PROGRAM_ID, services.attachments)
|
||||
services.addMockCordapp(DummyContract.PROGRAM_ID)
|
||||
}
|
||||
|
||||
interface Commands {
|
||||
|
@ -51,7 +51,8 @@ class TransactionEncumbranceTests {
|
||||
class DummyTimeLock : Contract {
|
||||
override fun verify(tx: LedgerTransaction) {
|
||||
val timeLockInput = tx.inputsOfType<State>().singleOrNull() ?: return
|
||||
val time = tx.timeWindow?.untilTime ?: throw IllegalArgumentException("Transactions containing time-locks must have a time-window")
|
||||
val time = tx.timeWindow?.untilTime
|
||||
?: throw IllegalArgumentException("Transactions containing time-locks must have a time-window")
|
||||
requireThat {
|
||||
"the time specified in the time-lock has passed" using (time >= timeLockInput.validFrom)
|
||||
}
|
||||
@ -64,9 +65,10 @@ class TransactionEncumbranceTests {
|
||||
}
|
||||
}
|
||||
|
||||
private val ledgerServices = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
}, MEGA_CORP.name)
|
||||
private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name,
|
||||
rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
})
|
||||
|
||||
@Test
|
||||
fun `state can be encumbered`() {
|
||||
|
Reference in New Issue
Block a user