mirror of
https://github.com/corda/corda.git
synced 2024-12-26 16:11:12 +00:00
Merge remote-tracking branch 'open/master' into andrius-merge-02-26
This commit is contained in:
commit
cc8e38922e
@ -627,6 +627,9 @@ public static final class net.corda.core.contracts.UniqueIdentifier$Companion ex
|
||||
@org.jetbrains.annotations.NotNull public abstract String getLegacyContract()
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.ContractState upgrade(net.corda.core.contracts.ContractState)
|
||||
##
|
||||
@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.UpgradedContractWithLegacyConstraint extends net.corda.core.contracts.UpgradedContract
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.AttachmentConstraint getLegacyContractConstraint()
|
||||
##
|
||||
@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint
|
||||
public boolean isSatisfiedBy(net.corda.core.contracts.Attachment)
|
||||
public static final net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint INSTANCE
|
||||
@ -1905,16 +1908,12 @@ public @interface net.corda.core.messaging.RPCReturnsObservables
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder, java.security.PublicKey)
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.StateAndRef toStateAndRef(net.corda.core.contracts.StateRef)
|
||||
##
|
||||
@net.corda.core.DoNotImplement public interface net.corda.core.node.ServicesForResolution extends net.corda.core.node.StateLoader
|
||||
@net.corda.core.DoNotImplement public interface net.corda.core.node.ServicesForResolution
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.AttachmentStorage getAttachments()
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.cordapp.CordappProvider getCordappProvider()
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.IdentityService getIdentityService()
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NetworkParameters getNetworkParameters()
|
||||
##
|
||||
@net.corda.core.DoNotImplement public interface net.corda.core.node.StateLoader
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef)
|
||||
@org.jetbrains.annotations.NotNull public abstract Set loadStates(Set)
|
||||
##
|
||||
public final class net.corda.core.node.StatesToRecord extends java.lang.Enum
|
||||
protected <init>(String, int)
|
||||
public static net.corda.core.node.StatesToRecord valueOf(String)
|
||||
@ -2063,10 +2062,9 @@ public final class net.corda.core.node.services.TimeWindowChecker extends java.l
|
||||
@org.jetbrains.annotations.NotNull public final java.time.Clock getClock()
|
||||
public final boolean isValid(net.corda.core.contracts.TimeWindow)
|
||||
##
|
||||
@net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionStorage extends net.corda.core.node.StateLoader
|
||||
@net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionStorage
|
||||
@org.jetbrains.annotations.Nullable public abstract net.corda.core.transactions.SignedTransaction getTransaction(net.corda.core.crypto.SecureHash)
|
||||
@org.jetbrains.annotations.NotNull public abstract rx.Observable getUpdates()
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef)
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track()
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture trackTransaction(net.corda.core.crypto.SecureHash)
|
||||
##
|
||||
@ -3159,7 +3157,6 @@ public static final class net.corda.core.transactions.LedgerTransaction$InOutGro
|
||||
@org.jetbrains.annotations.NotNull public List getOutputs()
|
||||
public int hashCode()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolve(net.corda.core.node.ServiceHub, List)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolve(net.corda.core.node.StateLoader, List)
|
||||
public String toString()
|
||||
##
|
||||
@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.SignedTransaction extends java.lang.Object implements net.corda.core.transactions.TransactionWithSignatures
|
||||
@ -3182,13 +3179,11 @@ public static final class net.corda.core.transactions.LedgerTransaction$InOutGro
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.WireTransaction getTx()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializedBytes getTxBits()
|
||||
public int hashCode()
|
||||
public final boolean isNotaryChangeTransaction()
|
||||
@kotlin.Deprecated public final boolean isNotaryChangeTransaction()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction plus(Collection)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction plus(net.corda.core.crypto.TransactionSignature)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.BaseTransaction resolveBaseTransaction(net.corda.core.node.StateLoader)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolveNotaryChangeTransaction(net.corda.core.node.ServiceHub)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolveNotaryChangeTransaction(net.corda.core.node.StateLoader)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.TransactionWithSignatures resolveTransactionWithSignatures(net.corda.core.node.ServiceHub)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.TransactionWithSignatures resolveTransactionWithSignatures(net.corda.core.node.ServicesForResolution)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.LedgerTransaction toLedgerTransaction(net.corda.core.node.ServiceHub)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.LedgerTransaction toLedgerTransaction(net.corda.core.node.ServiceHub, boolean)
|
||||
@org.jetbrains.annotations.NotNull public String toString()
|
||||
@ -4115,7 +4110,7 @@ public final class net.corda.testing.node.MockNodeParameters extends java.lang.O
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters withForcedID(Integer)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters withLegalName(net.corda.core.identity.CordaX500Name)
|
||||
##
|
||||
public class net.corda.testing.node.MockServices extends java.lang.Object implements net.corda.core.node.StateLoader, net.corda.core.node.ServiceHub
|
||||
public class net.corda.testing.node.MockServices extends java.lang.Object implements net.corda.core.node.ServiceHub
|
||||
public <init>()
|
||||
public <init>(List)
|
||||
public <init>(List, net.corda.core.identity.CordaX500Name)
|
||||
@ -4218,8 +4213,6 @@ public class net.corda.testing.node.MockTransactionStorage extends net.corda.cor
|
||||
public boolean addTransaction(net.corda.core.transactions.SignedTransaction)
|
||||
@org.jetbrains.annotations.Nullable public net.corda.core.transactions.SignedTransaction getTransaction(net.corda.core.crypto.SecureHash)
|
||||
@org.jetbrains.annotations.NotNull public rx.Observable getUpdates()
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef)
|
||||
@org.jetbrains.annotations.NotNull public Set loadStates(Set)
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.messaging.DataFeed track()
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.concurrent.CordaFuture trackTransaction(net.corda.core.crypto.SecureHash)
|
||||
##
|
||||
@ -4396,9 +4389,10 @@ public static final class net.corda.testing.contracts.DummyContract$SingleOwnerS
|
||||
@net.corda.core.DoNotImplement public static interface net.corda.testing.contracts.DummyContract$State extends net.corda.core.contracts.ContractState
|
||||
public abstract int getMagicNumber()
|
||||
##
|
||||
public final class net.corda.testing.contracts.DummyContractV2 extends java.lang.Object implements net.corda.core.contracts.UpgradedContract
|
||||
public final class net.corda.testing.contracts.DummyContractV2 extends java.lang.Object implements net.corda.core.contracts.UpgradedContractWithLegacyConstraint
|
||||
public <init>()
|
||||
@org.jetbrains.annotations.NotNull public String getLegacyContract()
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.contracts.AttachmentConstraint getLegacyContractConstraint()
|
||||
@org.jetbrains.annotations.NotNull public net.corda.testing.contracts.DummyContractV2$State upgrade(net.corda.testing.contracts.DummyContract$State)
|
||||
public void verify(net.corda.core.transactions.LedgerTransaction)
|
||||
public static final net.corda.testing.contracts.DummyContractV2$Companion Companion
|
||||
|
@ -207,6 +207,10 @@ allprojects {
|
||||
if (System.getProperty("test.maxParallelForks") != null) {
|
||||
maxParallelForks = Integer.valueOf(System.getProperty("test.maxParallelForks"))
|
||||
}
|
||||
|
||||
if (project.path.startsWith(':experimental') && System.getProperty("experimental.test.enable") == null) {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
group 'com.r3.corda'
|
||||
|
@ -24,10 +24,7 @@ import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.CoreTransaction
|
||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.transactions.*
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.base58ToByteArray
|
||||
import net.corda.core.utilities.base64ToByteArray
|
||||
|
@ -53,7 +53,7 @@ object IdentitySyncFlow {
|
||||
}
|
||||
|
||||
private fun extractOurConfidentialIdentities(): Map<AbstractParty, PartyAndCertificate?> {
|
||||
val states: List<ContractState> = (tx.inputs.map { serviceHub.loadState(it) }.requireNoNulls().map { it.data } + tx.outputs.map { it.data })
|
||||
val states: List<ContractState> = (serviceHub.loadStates(tx.inputs.toSet()).map { it.state.data } + tx.outputs.map { it.data })
|
||||
val identities: Set<AbstractParty> = states.flatMap(ContractState::participants).toSet()
|
||||
// Filter participants down to the set of those not in the network map (are not well known)
|
||||
val confidentialIdentities = identities
|
||||
|
@ -48,6 +48,7 @@ data class Issued<out P : Any>(val issuer: PartyAndReference, val product: P) {
|
||||
init {
|
||||
require(issuer.reference.size <= MAX_ISSUER_REF_SIZE) { "Maximum issuer reference size is $MAX_ISSUER_REF_SIZE." }
|
||||
}
|
||||
|
||||
override fun toString() = "$product issued by $issuer"
|
||||
}
|
||||
|
||||
@ -248,12 +249,21 @@ annotation class LegalProseReference(val uri: String)
|
||||
|
||||
/**
|
||||
* Interface which can upgrade state objects issued by a contract to a new state object issued by a different contract.
|
||||
* The upgraded contract should specify the legacy contract class name, and provide an upgrade function that will convert
|
||||
* legacy contract states into states defined by this contract.
|
||||
*
|
||||
* In addition to the legacy contract class name, you can also specify the legacy contract constraint by implementing
|
||||
* [UpgradedContractWithLegacyConstraint] instead. Otherwise, the default [WhitelistedByZoneAttachmentConstraint] will
|
||||
* be used for verifying the validity of an upgrade transaction.
|
||||
*
|
||||
* @param OldState the old contract state (can be [ContractState] or other common supertype if this supports upgrading
|
||||
* more than one state).
|
||||
* @param NewState the upgraded contract state.
|
||||
*/
|
||||
interface UpgradedContract<in OldState : ContractState, out NewState : ContractState> : Contract {
|
||||
/**
|
||||
* Name of the contract this is an upgraded version of, used as part of verification of upgrade transactions.
|
||||
*/
|
||||
val legacyContract: ContractClassName
|
||||
/**
|
||||
* Upgrade contract's state object to a new state object.
|
||||
@ -264,6 +274,17 @@ interface UpgradedContract<in OldState : ContractState, out NewState : ContractS
|
||||
fun upgrade(state: OldState): NewState
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface allows specifying a custom legacy contract constraint for upgraded contracts. The default for [UpgradedContract]
|
||||
* is [WhitelistedByZoneAttachmentConstraint].
|
||||
*/
|
||||
interface UpgradedContractWithLegacyConstraint<in OldState : ContractState, out NewState : ContractState> : UpgradedContract<OldState, NewState> {
|
||||
/**
|
||||
* A validator for the legacy (pre-upgrade) contract attachments on the transaction.
|
||||
*/
|
||||
val legacyContractConstraint: AttachmentConstraint
|
||||
}
|
||||
|
||||
/**
|
||||
* A privacy salt is required to compute nonces per transaction component in order to ensure that an adversary cannot
|
||||
* use brute force techniques and reveal the content of a Merkle-leaf hashed value.
|
||||
|
@ -2,7 +2,11 @@ package net.corda.core.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SignableData
|
||||
import net.corda.core.crypto.SignatureMetadata
|
||||
import net.corda.core.internal.ContractUpgradeUtils
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
|
||||
/**
|
||||
* A flow to be used for authorising and upgrading state objects of an old contract to a new contract.
|
||||
@ -38,7 +42,6 @@ object ContractUpgradeFlow {
|
||||
serviceHub.contractUpgradeService.storeAuthorisedContractUpgrade(stateAndRef.ref, upgradedContractClass)
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -68,11 +71,13 @@ object ContractUpgradeFlow {
|
||||
|
||||
@Suspendable
|
||||
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
|
||||
val baseTx = ContractUpgradeUtils.assembleBareTx(originalState, modification, PrivacySalt())
|
||||
val tx = ContractUpgradeUtils.assembleUpgradeTx(originalState, modification, PrivacySalt(), serviceHub)
|
||||
val participantKeys = originalState.state.data.participants.map { it.owningKey }.toSet()
|
||||
// TODO: We need a much faster way of finding our key in the transaction
|
||||
val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
|
||||
val stx = serviceHub.signInitialTransaction(baseTx, myKey)
|
||||
val signableData = SignableData(tx.id, SignatureMetadata(serviceHub.myInfo.platformVersion, Crypto.findSignatureScheme(myKey).schemeNumberID))
|
||||
val mySignature = serviceHub.keyManagementService.sign(signableData, myKey)
|
||||
val stx = SignedTransaction(tx, listOf(mySignature))
|
||||
return AbstractStateReplacementFlow.UpgradeTx(stx)
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,9 @@ 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.ContractUpgradeWireTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.UntrustworthyData
|
||||
import net.corda.core.utilities.unwrap
|
||||
@ -63,7 +65,7 @@ class NotaryFlow {
|
||||
protected fun checkTransaction(): Party {
|
||||
val notaryParty = stx.notary ?: throw IllegalStateException("Transaction does not specify a Notary")
|
||||
check(serviceHub.networkMapCache.isNotary(notaryParty)) { "$notaryParty is not a notary on the network" }
|
||||
check(stx.inputs.all { stateRef -> serviceHub.loadState(stateRef).notary == notaryParty }) {
|
||||
check(serviceHub.loadStates(stx.inputs.toSet()).all { it.state.notary == notaryParty }) {
|
||||
"Input states must have the same Notary"
|
||||
}
|
||||
|
||||
@ -104,10 +106,11 @@ class NotaryFlow {
|
||||
|
||||
@Suspendable
|
||||
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 })
|
||||
val ctx = stx.coreTransaction
|
||||
val tx = when (ctx) {
|
||||
is ContractUpgradeWireTransaction -> ctx.buildFilteredTransaction()
|
||||
is WireTransaction -> ctx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow || it == notaryParty })
|
||||
else -> ctx
|
||||
}
|
||||
return session.sendAndReceiveWithRetry(NotarisationPayload(tx, signature))
|
||||
}
|
||||
@ -168,12 +171,10 @@ class NotaryFlow {
|
||||
@Suspendable
|
||||
abstract fun receiveAndVerifyTx(): TransactionParts
|
||||
|
||||
// Check if transaction is intended to be signed by this notary.
|
||||
/** Check if transaction is intended to be signed by this notary. */
|
||||
@Suspendable
|
||||
protected fun checkNotary(notary: Party?) {
|
||||
// TODO This check implies that it's OK to use the node's main identity. Shouldn't it be just limited to the
|
||||
// notary identities?
|
||||
if (notary == null || !serviceHub.myInfo.isLegalIdentity(notary)) {
|
||||
if (notary?.owningKey != service.notaryIdentityKey) {
|
||||
throw NotaryException(NotaryError.WrongNotary)
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,38 @@
|
||||
package net.corda.core.internal
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
||||
|
||||
object ContractUpgradeUtils {
|
||||
fun <OldState : ContractState, NewState : ContractState> assembleBareTx(
|
||||
stateRef: StateAndRef<OldState>,
|
||||
fun <OldState : ContractState, NewState : ContractState> assembleUpgradeTx(
|
||||
stateAndRef: StateAndRef<OldState>,
|
||||
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>,
|
||||
privacySalt: PrivacySalt
|
||||
): TransactionBuilder {
|
||||
val contractUpgrade = upgradedContractClass.newInstance()
|
||||
return TransactionBuilder(stateRef.state.notary)
|
||||
.withItems(
|
||||
stateRef,
|
||||
StateAndContract(contractUpgrade.upgrade(stateRef.state.data), upgradedContractClass.name),
|
||||
Command(UpgradeCommand(upgradedContractClass.name), stateRef.state.data.participants.map { it.owningKey }),
|
||||
privacySalt
|
||||
)
|
||||
privacySalt: PrivacySalt,
|
||||
services: ServicesForResolution
|
||||
): ContractUpgradeWireTransaction {
|
||||
require(stateAndRef.state.encumbrance == null) { "Upgrading an encumbered state is not yet supported" }
|
||||
val legacyConstraint = stateAndRef.state.constraint
|
||||
val legacyContractAttachmentId = when (legacyConstraint) {
|
||||
is HashAttachmentConstraint -> legacyConstraint.attachmentId
|
||||
else -> getContractAttachmentId(stateAndRef.state.contract, services)
|
||||
}
|
||||
val upgradedContractAttachmentId = getContractAttachmentId(upgradedContractClass.name, services)
|
||||
|
||||
val inputs = listOf(stateAndRef.ref)
|
||||
return ContractUpgradeWireTransaction(
|
||||
inputs,
|
||||
stateAndRef.state.notary,
|
||||
legacyContractAttachmentId,
|
||||
upgradedContractClass.name,
|
||||
upgradedContractAttachmentId,
|
||||
privacySalt
|
||||
)
|
||||
}
|
||||
|
||||
private fun getContractAttachmentId(name: ContractClassName, services: ServicesForResolution): AttachmentId {
|
||||
return services.cordappProvider.getContractAttachmentID(name)
|
||||
?: throw IllegalStateException("Attachment not found for contract: $name")
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,9 @@ import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowSession
|
||||
import net.corda.core.node.StatesToRecord
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.exactAdd
|
||||
import java.util.*
|
||||
|
||||
@ -157,13 +159,17 @@ class ResolveTransactionsFlow(private val txHashes: Set<SecureHash>,
|
||||
* Returns a list of all the dependencies of the given transactions, deepest first i.e. the last downloaded comes
|
||||
* first in the returned list and thus doesn't have any unverified dependencies.
|
||||
*/
|
||||
// TODO: This could be done in parallel with other fetches for extra speed.
|
||||
@Suspendable
|
||||
private fun fetchMissingAttachments(downloads: List<SignedTransaction>) {
|
||||
// TODO: This could be done in parallel with other fetches for extra speed.
|
||||
val wireTransactions = downloads.filterNot { it.isNotaryChangeTransaction() }.map { it.tx }
|
||||
val missingAttachments = wireTransactions.flatMap { wtx ->
|
||||
wtx.attachments.filter { serviceHub.attachments.openAttachment(it) == null }
|
||||
val attachments = downloads.map(SignedTransaction::coreTransaction).flatMap { tx ->
|
||||
when (tx) {
|
||||
is WireTransaction -> tx.attachments
|
||||
is ContractUpgradeWireTransaction -> listOf(tx.legacyContractAttachmentId, tx.upgradedContractAttachmentId)
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
val missingAttachments = attachments.filter { serviceHub.attachments.openAttachment(it) == null }
|
||||
if (missingAttachments.isNotEmpty())
|
||||
subFlow(FetchAttachmentsFlow(missingAttachments.toSet(), otherSide))
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
package net.corda.core.internal
|
||||
|
||||
import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.contracts.ContractClassName
|
||||
|
||||
/** Indicates that this transaction replaces the inputs contract state to another contract state */
|
||||
data class UpgradeCommand(val upgradedContractClass: ContractClassName) : CommandData
|
@ -18,37 +18,12 @@ import java.security.PublicKey
|
||||
import java.sql.Connection
|
||||
import java.time.Clock
|
||||
|
||||
/**
|
||||
* Part of [ServiceHub].
|
||||
*/
|
||||
@DoNotImplement
|
||||
interface StateLoader {
|
||||
/**
|
||||
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
|
||||
*
|
||||
* @throws TransactionResolutionException if [stateRef] points to a non-existent transaction.
|
||||
*/
|
||||
@Throws(TransactionResolutionException::class)
|
||||
fun loadState(stateRef: StateRef): TransactionState<*>
|
||||
|
||||
/**
|
||||
* Given a [Set] of [StateRef]'s loads the referenced transaction and looks up the specified output [ContractState].
|
||||
*
|
||||
* @throws TransactionResolutionException if [stateRef] points to a non-existent transaction.
|
||||
*/
|
||||
// TODO: future implementation to use a Vault state ref -> contract state BLOB table and perform single query bulk load
|
||||
// as the existing transaction store will become encrypted at some point
|
||||
@Throws(TransactionResolutionException::class)
|
||||
fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> {
|
||||
return stateRefs.map { StateAndRef(loadState(it), it) }.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subset of node services that are used for loading transactions from the wire into fully resolved, looked up
|
||||
* forms ready for verification.
|
||||
*/
|
||||
interface ServicesForResolution : StateLoader {
|
||||
@DoNotImplement
|
||||
interface ServicesForResolution {
|
||||
/**
|
||||
* An identity service maintains a directory of parties by their associated distinguished name/public keys and thus
|
||||
* supports lookup of a party given its key, or name. The service also manages the certificates linking confidential
|
||||
@ -64,6 +39,27 @@ interface ServicesForResolution : StateLoader {
|
||||
|
||||
/** Returns the network parameters the node is operating under. */
|
||||
val networkParameters: NetworkParameters
|
||||
|
||||
/**
|
||||
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
|
||||
*
|
||||
* *WARNING* Do not use this method unless you really only want a single state - any batch loading should
|
||||
* go through [loadStates] as repeatedly calling [loadState] can lead to repeat deserialsiation work and
|
||||
* severe performance degradation.
|
||||
*
|
||||
* @throws TransactionResolutionException if [stateRef] points to a non-existent transaction.
|
||||
*/
|
||||
@Throws(TransactionResolutionException::class)
|
||||
fun loadState(stateRef: StateRef): TransactionState<*>
|
||||
/**
|
||||
* Given a [Set] of [StateRef]'s loads the referenced transaction and looks up the specified output [ContractState].
|
||||
*
|
||||
* @throws TransactionResolutionException if [stateRef] points to a non-existent transaction.
|
||||
*/
|
||||
// TODO: future implementation to use a Vault state ref -> contract state BLOB table and perform single query bulk load
|
||||
// as the existing transaction store will become encrypted at some point
|
||||
@Throws(TransactionResolutionException::class)
|
||||
fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>>
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,13 +1,9 @@
|
||||
package net.corda.core.node.services
|
||||
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TransactionResolutionException
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.messaging.DataFeed
|
||||
import net.corda.core.node.StateLoader
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import rx.Observable
|
||||
|
||||
@ -15,18 +11,12 @@ import rx.Observable
|
||||
* Thread-safe storage of transactions.
|
||||
*/
|
||||
@DoNotImplement
|
||||
interface TransactionStorage : StateLoader {
|
||||
interface TransactionStorage {
|
||||
/**
|
||||
* Return the transaction with the given [id], or null if no such transaction exists.
|
||||
*/
|
||||
fun getTransaction(id: SecureHash): SignedTransaction?
|
||||
|
||||
@Throws(TransactionResolutionException::class)
|
||||
override fun loadState(stateRef: StateRef): TransactionState<*> {
|
||||
val stx = getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
|
||||
return stx.resolveBaseTransaction(this).outputs[stateRef.index]
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a synchronous Observable of updates. When observations are pushed to the Observer, the vault will already
|
||||
* incorporate the update.
|
||||
|
@ -46,8 +46,8 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
|
||||
val produced: Set<StateAndRef<U>>,
|
||||
val flowId: UUID? = null,
|
||||
/**
|
||||
* Specifies the type of update, currently supported types are general and notary change. Notary
|
||||
* change transactions only modify the notary field on states, and potentially need to be handled
|
||||
* Specifies the type of update, currently supported types are general and, contract upgrade and notary change.
|
||||
* Notary change transactions only modify the notary field on states, and potentially need to be handled
|
||||
* differently.
|
||||
*/
|
||||
val type: UpdateType = UpdateType.GENERAL
|
||||
@ -97,11 +97,6 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val NoUpdate = Update(emptySet(), emptySet(), type = Vault.UpdateType.GENERAL)
|
||||
val NoNotaryUpdate = Vault.Update(emptySet(), emptySet(), type = Vault.UpdateType.NOTARY_CHANGE)
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
enum class StateStatus {
|
||||
UNCONSUMED, CONSUMED, ALL
|
||||
@ -109,7 +104,7 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
|
||||
|
||||
@CordaSerializable
|
||||
enum class UpdateType {
|
||||
GENERAL, NOTARY_CHANGE
|
||||
GENERAL, NOTARY_CHANGE, CONTRACT_UPGRADE
|
||||
}
|
||||
|
||||
/**
|
||||
@ -141,6 +136,13 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
|
||||
val notary: AbstractParty?,
|
||||
val lockId: String?,
|
||||
val lockUpdateTime: Instant?)
|
||||
|
||||
companion object {
|
||||
@Deprecated("No longer used. The vault does not emit empty updates")
|
||||
val NoUpdate = Update(emptySet(), emptySet(), type = Vault.UpdateType.GENERAL)
|
||||
@Deprecated("No longer used. The vault does not emit empty updates")
|
||||
val NoNotaryUpdate = Vault.Update(emptySet(), emptySet(), type = Vault.UpdateType.NOTARY_CHANGE)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,183 @@
|
||||
package net.corda.core.transactions
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.crypto.serializedHash
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.AttachmentWithContext
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import java.security.PublicKey
|
||||
|
||||
// TODO: copy across encumbrances when performing contract upgrades
|
||||
// TODO: check transaction size is within limits
|
||||
|
||||
/** A special transaction for upgrading the contract of a state. */
|
||||
@CordaSerializable
|
||||
data class ContractUpgradeWireTransaction(
|
||||
override val inputs: List<StateRef>,
|
||||
override val notary: Party,
|
||||
val legacyContractAttachmentId: SecureHash,
|
||||
val upgradeContractClassName: ContractClassName,
|
||||
val upgradedContractAttachmentId: SecureHash,
|
||||
val privacySalt: PrivacySalt = PrivacySalt()
|
||||
) : CoreTransaction() {
|
||||
|
||||
init {
|
||||
check(inputs.isNotEmpty()) { "A contract upgrade transaction must have inputs" }
|
||||
}
|
||||
|
||||
/**
|
||||
* This transaction does not contain any output states, outputs can be obtained by resolving a
|
||||
* [ContractUpgradeLedgerTransaction] – outputs will be calculated on demand by applying the contract
|
||||
* upgrade operation to inputs.
|
||||
*/
|
||||
override val outputs: List<TransactionState<ContractState>>
|
||||
get() = throw UnsupportedOperationException("ContractUpgradeWireTransaction does not contain output states, " +
|
||||
"outputs can only be obtained from a resolved ContractUpgradeLedgerTransaction")
|
||||
|
||||
/** Hash of the list of components that are hidden in the [ContractUpgradeFilteredTransaction]. */
|
||||
private val hiddenComponentHash: SecureHash
|
||||
get() = serializedHash(listOf(legacyContractAttachmentId, upgradeContractClassName, privacySalt))
|
||||
|
||||
override val id: SecureHash by lazy { serializedHash(inputs + notary).hashConcat(hiddenComponentHash) }
|
||||
|
||||
/** Resolves input states and contract attachments, and builds a ContractUpgradeLedgerTransaction. */
|
||||
fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>): ContractUpgradeLedgerTransaction {
|
||||
val resolvedInputs = services.loadStates(inputs.toSet()).toList()
|
||||
val legacyContractClassName = resolvedInputs.first().state.contract
|
||||
val legacyContractAttachment = services.attachments.openAttachment(legacyContractAttachmentId)
|
||||
?: throw AttachmentResolutionException(legacyContractAttachmentId)
|
||||
val upgradedContractAttachment = services.attachments.openAttachment(upgradedContractAttachmentId)
|
||||
?: throw AttachmentResolutionException(upgradedContractAttachmentId)
|
||||
return ContractUpgradeLedgerTransaction(
|
||||
resolvedInputs,
|
||||
notary,
|
||||
ContractAttachment(legacyContractAttachment, legacyContractClassName),
|
||||
ContractAttachment(upgradedContractAttachment, upgradeContractClassName),
|
||||
id,
|
||||
privacySalt,
|
||||
sigs,
|
||||
services.networkParameters
|
||||
)
|
||||
}
|
||||
|
||||
fun buildFilteredTransaction(): ContractUpgradeFilteredTransaction {
|
||||
return ContractUpgradeFilteredTransaction(inputs, notary, hiddenComponentHash)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A filtered version of the [ContractUpgradeWireTransaction]. In comparison with a regular [FilteredTransaction], there
|
||||
* is no flexibility on what parts of the transaction to reveal – the inputs and notary field are always visible and the
|
||||
* rest of the transaction is always hidden. Its only purpose is to hide transaction data when using a non-validating notary.
|
||||
*
|
||||
* @property inputs The inputs of this transaction.
|
||||
* @property notary The notary for this transaction.
|
||||
* @property rest Hash of the hidden components of the [ContractUpgradeWireTransaction].
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class ContractUpgradeFilteredTransaction(
|
||||
override val inputs: List<StateRef>,
|
||||
override val notary: Party,
|
||||
val rest: SecureHash
|
||||
) : CoreTransaction() {
|
||||
override val id: SecureHash get() = serializedHash(inputs + notary).hashConcat(rest)
|
||||
override val outputs: List<TransactionState<ContractState>> get() = emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* A contract upgrade transaction with fully resolved inputs and signatures. Contract upgrade transactions are separate
|
||||
* to regular transactions because their validation logic is specialised; the original contract by definition cannot be
|
||||
* aware of the upgraded contract (it was written after the original contract was developed), so its validation logic
|
||||
* cannot succeed. Instead alternative verification logic is used which verifies that the outputs correspond to the
|
||||
* inputs after upgrading.
|
||||
*
|
||||
* In contrast with a regular transaction, signatures are checked against the signers specified by input states'
|
||||
* *participants* fields, so full resolution is needed for signature verification.
|
||||
*/
|
||||
data class ContractUpgradeLedgerTransaction(
|
||||
override val inputs: List<StateAndRef<ContractState>>,
|
||||
override val notary: Party,
|
||||
val legacyContractAttachment: ContractAttachment,
|
||||
val upgradedContractAttachment: ContractAttachment,
|
||||
override val id: SecureHash,
|
||||
val privacySalt: PrivacySalt,
|
||||
override val sigs: List<TransactionSignature>,
|
||||
private val networkParameters: NetworkParameters
|
||||
) : FullTransaction(), TransactionWithSignatures {
|
||||
private val upgradedContract: UpgradedContract<ContractState, *> = loadUpgradedContract()
|
||||
|
||||
init {
|
||||
// TODO: relax this constraint once upgrading encumbered states is supported
|
||||
check(inputs.all { it.state.contract == legacyContractAttachment.contract }) {
|
||||
"All input states must point to the legacy contract"
|
||||
}
|
||||
check(inputs.all { it.state.constraint.isSatisfiedBy(legacyContractAttachment) }) {
|
||||
"Legacy contract constraint does not satisfy the constraint of the input states"
|
||||
}
|
||||
verifyLegacyContractConstraint()
|
||||
}
|
||||
|
||||
private fun verifyLegacyContractConstraint() {
|
||||
check(upgradedContract.legacyContract == legacyContractAttachment.contract) {
|
||||
"Outputs' contract must be an upgraded version of the inputs' contract"
|
||||
}
|
||||
val attachmentWithContext = AttachmentWithContext(
|
||||
legacyContractAttachment,
|
||||
upgradedContract.legacyContract,
|
||||
networkParameters.whitelistedContractImplementations
|
||||
)
|
||||
val constraintCheck = if (upgradedContract is UpgradedContractWithLegacyConstraint) {
|
||||
upgradedContract.legacyContractConstraint.isSatisfiedBy(attachmentWithContext)
|
||||
} else {
|
||||
// If legacy constraint not specified, defaulting to WhitelistedByZoneAttachmentConstraint
|
||||
WhitelistedByZoneAttachmentConstraint.isSatisfiedBy(attachmentWithContext)
|
||||
}
|
||||
check(constraintCheck) {
|
||||
"Legacy contract does not satisfy the upgraded contract's constraint"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs are computed by running the contract upgrade logic on input states. This is done eagerly so that the
|
||||
* transaction is verified during construction.
|
||||
*/
|
||||
override val outputs: List<TransactionState<ContractState>> = inputs.map { input ->
|
||||
// TODO: if there are encumbrance states in the inputs, just copy them across without modifying
|
||||
val upgradedState = upgradedContract.upgrade(input.state.data)
|
||||
val inputConstraint = input.state.constraint
|
||||
val outputConstraint = when (inputConstraint) {
|
||||
is HashAttachmentConstraint -> HashAttachmentConstraint(upgradedContractAttachment.id)
|
||||
WhitelistedByZoneAttachmentConstraint -> WhitelistedByZoneAttachmentConstraint
|
||||
else -> throw IllegalArgumentException("Unsupported input contract constraint $inputConstraint")
|
||||
}
|
||||
// TODO: re-map encumbrance pointers
|
||||
input.state.copy(
|
||||
data = upgradedState,
|
||||
contract = upgradedContractAttachment.contract,
|
||||
constraint = outputConstraint
|
||||
)
|
||||
}
|
||||
|
||||
/** The required signers are the set of all input states' participants. */
|
||||
override val requiredSigningKeys: Set<PublicKey>
|
||||
get() = inputs.flatMap { it.state.data.participants }.map { it.owningKey }.toSet() + notary.owningKey
|
||||
|
||||
override fun getKeyDescriptions(keys: Set<PublicKey>): List<String> {
|
||||
return keys.map { it.toBase58String() }
|
||||
}
|
||||
|
||||
// TODO: load contract from the CorDapp classloader
|
||||
private fun loadUpgradedContract(): UpgradedContract<ContractState, *> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return this::class.java.classLoader
|
||||
.loadClass(upgradedContractAttachment.contract)
|
||||
.asSubclass(Contract::class.java)
|
||||
.getConstructor()
|
||||
.newInstance() as UpgradedContract<ContractState, *>
|
||||
}
|
||||
}
|
@ -4,13 +4,11 @@ import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.AttachmentWithContext
|
||||
import net.corda.core.internal.UpgradeCommand
|
||||
import net.corda.core.internal.castIfPossible
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.utilities.Try
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
import java.util.function.Predicate
|
||||
|
||||
@ -79,12 +77,7 @@ data class LedgerTransaction @JvmOverloads constructor(
|
||||
@Throws(TransactionVerificationException::class)
|
||||
fun verify() {
|
||||
verifyConstraints()
|
||||
// TODO: make contract upgrade transactions have a separate type
|
||||
if (commands.any { it.value is UpgradeCommand }) {
|
||||
verifyContractUpgrade()
|
||||
} else {
|
||||
verifyContracts()
|
||||
}
|
||||
verifyContracts()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -185,25 +178,6 @@ data class LedgerTransaction @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifyContractUpgrade() {
|
||||
// Contract Upgrade transaction should have 1 input, 1 output and 1 command.
|
||||
val input = inputs.single().state
|
||||
val output = outputs.single()
|
||||
val commandData = commandsOfType<UpgradeCommand>().single()
|
||||
|
||||
val command = commandData.value
|
||||
val participantKeys: Set<PublicKey> = input.data.participants.map { it.owningKey }.toSet()
|
||||
val keysThatSigned: Set<PublicKey> = commandData.signers.toSet()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val upgradedContract = javaClass.classLoader.loadClass(command.upgradedContractClass).newInstance() as UpgradedContract<ContractState, *>
|
||||
requireThat {
|
||||
"The signing keys include all participant keys" using keysThatSigned.containsAll(participantKeys)
|
||||
"Inputs state reference the legacy contract" using (input.contract == upgradedContract.legacyContract)
|
||||
"Outputs state reference the upgraded contract" using (output.contract == command.upgradedContractClass)
|
||||
"Output state must be an upgraded version of the input state" using (output.data == upgradedContract.upgrade(input.data))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a type and a function that returns a grouping key, associates inputs and outputs together so that they
|
||||
* can be processed as one. The grouping key is any arbitrary object that can act as a map key (so must implement
|
||||
@ -429,3 +403,4 @@ data class LedgerTransaction @JvmOverloads constructor(
|
||||
privacySalt: PrivacySalt
|
||||
) = copy(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, null)
|
||||
}
|
||||
|
||||
|
@ -4,11 +4,11 @@ import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.crypto.serializedHash
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.StateLoader
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
@ -27,7 +27,8 @@ data class NotaryChangeWireTransaction(
|
||||
* [NotaryChangeLedgerTransaction] and applying the notary modification to inputs.
|
||||
*/
|
||||
override val outputs: List<TransactionState<ContractState>>
|
||||
get() = emptyList()
|
||||
get() = throw UnsupportedOperationException("NotaryChangeWireTransaction does not contain output states, " +
|
||||
"outputs can only be obtained from a resolved NotaryChangeLedgerTransaction")
|
||||
|
||||
init {
|
||||
check(inputs.isNotEmpty()) { "A notary change transaction must have inputs" }
|
||||
@ -40,13 +41,14 @@ data class NotaryChangeWireTransaction(
|
||||
*/
|
||||
override val id: SecureHash by lazy { serializedHash(inputs + notary + newNotary) }
|
||||
|
||||
fun resolve(services: ServiceHub, sigs: List<TransactionSignature>) = resolve(services as StateLoader, sigs)
|
||||
fun resolve(stateLoader: StateLoader, sigs: List<TransactionSignature>): NotaryChangeLedgerTransaction {
|
||||
val resolvedInputs = inputs.map { ref ->
|
||||
stateLoader.loadState(ref).let { StateAndRef(it, ref) }
|
||||
}
|
||||
/** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */
|
||||
fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>) : NotaryChangeLedgerTransaction {
|
||||
val resolvedInputs = services.loadStates(inputs.toSet()).toList()
|
||||
return NotaryChangeLedgerTransaction(resolvedInputs, notary, newNotary, id, sigs)
|
||||
}
|
||||
|
||||
/** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */
|
||||
fun resolve(services: ServiceHub, sigs: List<TransactionSignature>) = resolve(services as ServicesForResolution, sigs)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,7 +7,7 @@ import net.corda.core.crypto.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.VisibleForTesting
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.StateLoader
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.deserialize
|
||||
@ -48,19 +48,18 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
||||
|
||||
/** Cache the deserialized form of the transaction. This is useful when building a transaction or collecting signatures. */
|
||||
@Volatile
|
||||
@Transient private var cachedTransaction: CoreTransaction? = null
|
||||
|
||||
/** Lazily calculated access to the deserialized/hashed transaction data. */
|
||||
private val transaction: CoreTransaction get() = cachedTransaction ?: txBits.deserialize().apply { cachedTransaction = this }
|
||||
@Transient
|
||||
private var cachedTransaction: CoreTransaction? = null
|
||||
|
||||
/** The id of the contained [WireTransaction]. */
|
||||
override val id: SecureHash get() = transaction.id
|
||||
override val id: SecureHash get() = coreTransaction.id
|
||||
|
||||
/** Returns the contained [WireTransaction], or throws if this is a notary change transaction. */
|
||||
val tx: WireTransaction get() = transaction as WireTransaction
|
||||
/** Lazily calculated access to the deserialised/hashed transaction data. */
|
||||
val coreTransaction: CoreTransaction
|
||||
get() = cachedTransaction ?: txBits.deserialize().apply { cachedTransaction = this }
|
||||
|
||||
/** Returns the contained [NotaryChangeWireTransaction], or throws if this is a normal transaction. */
|
||||
val notaryChangeTx: NotaryChangeWireTransaction get() = transaction as NotaryChangeWireTransaction
|
||||
/** Returns the contained [WireTransaction], or throws if this is a notary change or contract upgrade transaction. */
|
||||
val tx: WireTransaction get() = coreTransaction as WireTransaction
|
||||
|
||||
/**
|
||||
* Helper function to directly build a [FilteredTransaction] using provided filtering functions,
|
||||
@ -69,9 +68,9 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
||||
fun buildFilteredTransaction(filtering: Predicate<Any>) = tx.buildFilteredTransaction(filtering)
|
||||
|
||||
/** Helper to access the inputs of the contained transaction. */
|
||||
val inputs: List<StateRef> get() = transaction.inputs
|
||||
val inputs: List<StateRef> get() = coreTransaction.inputs
|
||||
/** Helper to access the notary of the contained transaction. */
|
||||
val notary: Party? get() = transaction.notary
|
||||
val notary: Party? get() = coreTransaction.notary
|
||||
|
||||
override val requiredSigningKeys: Set<PublicKey> get() = tx.requiredSigningKeys
|
||||
|
||||
@ -162,13 +161,27 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
||||
@JvmOverloads
|
||||
@Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class)
|
||||
fun verify(services: ServiceHub, checkSufficientSignatures: Boolean = true) {
|
||||
if (isNotaryChangeTransaction()) {
|
||||
verifyNotaryChangeTransaction(services, checkSufficientSignatures)
|
||||
} else {
|
||||
verifyRegularTransaction(services, checkSufficientSignatures)
|
||||
when (coreTransaction) {
|
||||
is NotaryChangeWireTransaction -> verifyNotaryChangeTransaction(services, checkSufficientSignatures)
|
||||
is ContractUpgradeWireTransaction -> verifyContractUpgradeTransaction(services, checkSufficientSignatures)
|
||||
else -> verifyRegularTransaction(services, checkSufficientSignatures)
|
||||
}
|
||||
}
|
||||
|
||||
/** No contract code is run when verifying notary change transactions, it is sufficient to check invariants during initialisation. */
|
||||
private fun verifyNotaryChangeTransaction(services: ServiceHub, checkSufficientSignatures: Boolean) {
|
||||
val ntx = resolveNotaryChangeTransaction(services)
|
||||
if (checkSufficientSignatures) ntx.verifyRequiredSignatures()
|
||||
else checkSignaturesAreValid()
|
||||
}
|
||||
|
||||
/** No contract code is run when verifying contract upgrade transactions, it is sufficient to check invariants during initialisation. */
|
||||
private fun verifyContractUpgradeTransaction(services: ServicesForResolution, checkSufficientSignatures: Boolean) {
|
||||
val ctx = resolveContractUpgradeTransaction(services)
|
||||
if (checkSufficientSignatures) ctx.verifyRequiredSignatures()
|
||||
else checkSignaturesAreValid()
|
||||
}
|
||||
|
||||
// TODO: Verify contract constraints here as well as in LedgerTransaction to ensure that anything being deserialised
|
||||
// from the attachment is trusted. This will require some partial serialisation work to not load the ContractState
|
||||
// objects from the TransactionState.
|
||||
@ -178,37 +191,32 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
||||
services.transactionVerifierService.verify(ltx).getOrThrow()
|
||||
}
|
||||
|
||||
private fun verifyNotaryChangeTransaction(services: ServiceHub, checkSufficientSignatures: Boolean) {
|
||||
val ntx = resolveNotaryChangeTransaction(services)
|
||||
if (checkSufficientSignatures) ntx.verifyRequiredSignatures()
|
||||
else checkSignaturesAreValid()
|
||||
}
|
||||
|
||||
fun isNotaryChangeTransaction() = transaction is NotaryChangeWireTransaction
|
||||
|
||||
/**
|
||||
* Resolves the underlying base transaction and then returns it, handling any special case transactions such as
|
||||
* [NotaryChangeWireTransaction].
|
||||
*/
|
||||
fun resolveBaseTransaction(services: StateLoader): BaseTransaction {
|
||||
return when (transaction) {
|
||||
is NotaryChangeWireTransaction -> resolveNotaryChangeTransaction(services)
|
||||
fun resolveBaseTransaction(servicesForResolution: ServicesForResolution): BaseTransaction {
|
||||
return when (coreTransaction) {
|
||||
is NotaryChangeWireTransaction -> resolveNotaryChangeTransaction(servicesForResolution)
|
||||
is ContractUpgradeWireTransaction -> resolveContractUpgradeTransaction(servicesForResolution)
|
||||
is WireTransaction -> this.tx
|
||||
is FilteredTransaction -> throw IllegalStateException("Persistence of filtered transactions is not supported.")
|
||||
else -> throw IllegalStateException("Unknown transaction type ${transaction::class.qualifiedName}")
|
||||
else -> throw IllegalStateException("Unknown transaction type ${coreTransaction::class.qualifiedName}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolves the underlying transaction with signatures and then returns it, handling any special case transactions
|
||||
* such as [NotaryChangeWireTransaction].
|
||||
*/
|
||||
fun resolveTransactionWithSignatures(services: ServiceHub): TransactionWithSignatures {
|
||||
return when (transaction) {
|
||||
fun resolveTransactionWithSignatures(services: ServicesForResolution): TransactionWithSignatures {
|
||||
return when (coreTransaction) {
|
||||
is NotaryChangeWireTransaction -> resolveNotaryChangeTransaction(services)
|
||||
is ContractUpgradeWireTransaction -> resolveContractUpgradeTransaction(services)
|
||||
is WireTransaction -> this
|
||||
is FilteredTransaction -> throw IllegalStateException("Persistence of filtered transactions is not supported.")
|
||||
else -> throw IllegalStateException("Unknown transaction type ${transaction::class.qualifiedName}")
|
||||
else -> throw IllegalStateException("Unknown transaction type ${coreTransaction::class.qualifiedName}")
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,12 +224,26 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
||||
* If [transaction] is a [NotaryChangeWireTransaction], loads the input states and resolves it to a
|
||||
* [NotaryChangeLedgerTransaction] so the signatures can be verified.
|
||||
*/
|
||||
fun resolveNotaryChangeTransaction(services: ServiceHub) = resolveNotaryChangeTransaction(services as StateLoader)
|
||||
fun resolveNotaryChangeTransaction(services: ServicesForResolution): NotaryChangeLedgerTransaction {
|
||||
val ntx = coreTransaction as? NotaryChangeWireTransaction
|
||||
?: throw IllegalStateException("Expected a ${NotaryChangeWireTransaction::class.simpleName} but found ${coreTransaction::class.simpleName}")
|
||||
return ntx.resolve(services, sigs)
|
||||
}
|
||||
|
||||
fun resolveNotaryChangeTransaction(stateLoader: StateLoader): NotaryChangeLedgerTransaction {
|
||||
val ntx = transaction as? NotaryChangeWireTransaction
|
||||
?: throw IllegalStateException("Expected a ${NotaryChangeWireTransaction::class.simpleName} but found ${transaction::class.simpleName}")
|
||||
return ntx.resolve(stateLoader, sigs)
|
||||
/**
|
||||
* If [transaction] is a [NotaryChangeWireTransaction], loads the input states and resolves it to a
|
||||
* [NotaryChangeLedgerTransaction] so the signatures can be verified.
|
||||
*/
|
||||
fun resolveNotaryChangeTransaction(services: ServiceHub) = resolveNotaryChangeTransaction(services as ServicesForResolution)
|
||||
|
||||
/**
|
||||
* If [coreTransaction] is a [ContractUpgradeWireTransaction], loads the input states and resolves it to a
|
||||
* [ContractUpgradeLedgerTransaction] so the signatures can be verified.
|
||||
*/
|
||||
fun resolveContractUpgradeTransaction(services: ServicesForResolution): ContractUpgradeLedgerTransaction {
|
||||
val ctx = coreTransaction as? ContractUpgradeWireTransaction
|
||||
?: throw IllegalStateException("Expected a ${ContractUpgradeWireTransaction::class.simpleName} but found ${coreTransaction::class.simpleName}")
|
||||
return ctx.resolve(services, sigs)
|
||||
}
|
||||
|
||||
override fun toString(): String = "${javaClass.simpleName}(id=$id)"
|
||||
@ -234,4 +256,14 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
||||
@CordaSerializable
|
||||
class SignaturesMissingException(val missing: Set<PublicKey>, val descriptions: List<String>, override val id: SecureHash)
|
||||
: NamedByHash, SignatureException(missingSignatureMsg(missing, descriptions, id)), CordaThrowable by CordaException(missingSignatureMsg(missing, descriptions, id))
|
||||
|
||||
//region Deprecated
|
||||
/** Returns the contained [NotaryChangeWireTransaction], or throws if this is a normal transaction. */
|
||||
@Deprecated("No replacement, this should not be used outside of Corda core")
|
||||
val notaryChangeTx: NotaryChangeWireTransaction
|
||||
get() = coreTransaction as NotaryChangeWireTransaction
|
||||
|
||||
@Deprecated("No replacement, this should not be used outside of Corda core")
|
||||
fun isNotaryChangeTransaction() = this.coreTransaction is NotaryChangeWireTransaction
|
||||
//endregion
|
||||
}
|
||||
|
@ -1,58 +0,0 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import com.nhaarman.mockito_kotlin.any
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.cordapp.CordappProvider
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.SecureHash.Companion.allOnesHash
|
||||
import net.corda.core.internal.UpgradeCommand
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.contracts.DummyContractV2
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* Tests for the version 2 dummy contract, to cover ensuring upgrade transactions are built correctly.
|
||||
*/
|
||||
class DummyContractV2Tests {
|
||||
private companion object {
|
||||
val ALICE = TestIdentity(ALICE_NAME, 70).party
|
||||
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
@Test
|
||||
fun `upgrade from v1`() {
|
||||
val services = rigorousMock<ServicesForResolution>().also {
|
||||
doReturn(rigorousMock<CordappProvider>().also {
|
||||
doReturn(allOnesHash).whenever(it).getContractAttachmentID(any())
|
||||
}).whenever(it).cordappProvider
|
||||
}
|
||||
val contractUpgrade = DummyContractV2()
|
||||
val v1State = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DummyContract.PROGRAM_ID, DUMMY_NOTARY, constraint = AlwaysAcceptAttachmentConstraint)
|
||||
val v1Ref = StateRef(SecureHash.randomSHA256(), 0)
|
||||
val v1StateAndRef = StateAndRef(v1State, v1Ref)
|
||||
val (tx, _) = DummyContractV2().generateUpgradeFromV1(services, v1StateAndRef)
|
||||
|
||||
assertEquals(v1Ref, tx.inputs.single())
|
||||
|
||||
val expectedOutput = TransactionState(contractUpgrade.upgrade(v1State.data), DummyContractV2.PROGRAM_ID, DUMMY_NOTARY, constraint = AlwaysAcceptAttachmentConstraint)
|
||||
val actualOutput = tx.outputs.single()
|
||||
assertEquals(expectedOutput, actualOutput)
|
||||
|
||||
val actualCommand = tx.commands.map { it.value }.single()
|
||||
assertTrue((actualCommand as UpgradeCommand).upgradedContractClass == DummyContractV2::class.java.name)
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.node.services.queryBy
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.finance.USD
|
||||
@ -101,20 +102,12 @@ class ContractUpgradeFlowTest {
|
||||
val result = resultFuture.getOrThrow()
|
||||
|
||||
fun check(node: StartedNode<MockNode>) {
|
||||
val nodeStx = node.database.transaction {
|
||||
node.services.validatedTransactions.getTransaction(result.ref.txhash)
|
||||
val upgradeTx = node.database.transaction {
|
||||
val wtx = node.services.validatedTransactions.getTransaction(result.ref.txhash)
|
||||
wtx!!.resolveContractUpgradeTransaction(node.services)
|
||||
}
|
||||
requireNotNull(nodeStx)
|
||||
|
||||
// Verify inputs.
|
||||
val input = node.database.transaction {
|
||||
node.services.validatedTransactions.getTransaction(nodeStx!!.tx.inputs.single().txhash)
|
||||
}
|
||||
requireNotNull(input)
|
||||
assertTrue(input!!.tx.outputs.single().data is DummyContract.State)
|
||||
|
||||
// Verify outputs.
|
||||
assertTrue(nodeStx!!.tx.outputs.single().data is DummyContractV2.State)
|
||||
assertTrue(upgradeTx.inputs.single().state.data is DummyContract.State)
|
||||
assertTrue(upgradeTx.outputs.single().data is DummyContractV2.State)
|
||||
}
|
||||
check(aliceNode)
|
||||
check(bobNode)
|
||||
@ -192,16 +185,12 @@ class ContractUpgradeFlowTest {
|
||||
val result = resultFuture.getOrThrow()
|
||||
// Check results.
|
||||
listOf(aliceNode, bobNode).forEach {
|
||||
val signedTX = aliceNode.database.transaction { aliceNode.services.validatedTransactions.getTransaction(result.ref.txhash) }
|
||||
requireNotNull(signedTX)
|
||||
|
||||
// Verify inputs.
|
||||
val input = aliceNode.database.transaction { aliceNode.services.validatedTransactions.getTransaction(signedTX!!.tx.inputs.single().txhash) }
|
||||
requireNotNull(input)
|
||||
assertTrue(input!!.tx.outputs.single().data is DummyContract.State)
|
||||
|
||||
// Verify outputs.
|
||||
assertTrue(signedTX!!.tx.outputs.single().data is DummyContractV2.State)
|
||||
val upgradeTx = aliceNode.database.transaction {
|
||||
val wtx = aliceNode.services.validatedTransactions.getTransaction(result.ref.txhash)
|
||||
wtx!!.resolveContractUpgradeTransaction(aliceNode.services)
|
||||
}
|
||||
assertTrue(upgradeTx.inputs.single().state.data is DummyContract.State)
|
||||
assertTrue(upgradeTx.outputs.single().data is DummyContractV2.State)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -222,14 +211,34 @@ class ContractUpgradeFlowTest {
|
||||
mockNet.runNetwork()
|
||||
upgradeResult.getOrThrow()
|
||||
// Get contract state from the vault.
|
||||
val firstState = aliceNode.database.transaction { aliceNode.services.vaultService.queryBy<ContractState>().states.single() }
|
||||
assertTrue(firstState.state.data is CashV2.State, "Contract state is upgraded to the new version.")
|
||||
assertEquals(Amount(1000000, USD).`issued by`(chosenIdentity.ref(1)), (firstState.state.data as CashV2.State).amount, "Upgraded cash contain the correct amount.")
|
||||
assertEquals<Collection<AbstractParty>>(listOf(anonymisedRecipient), (firstState.state.data as CashV2.State).owners, "Upgraded cash belongs to the right owner.")
|
||||
val upgradedStateFromVault = aliceNode.database.transaction { aliceNode.services.vaultService.queryBy<CashV2.State>().states.single() }
|
||||
assertEquals(Amount(1000000, USD).`issued by`(chosenIdentity.ref(1)), upgradedStateFromVault.state.data.amount, "Upgraded cash contain the correct amount.")
|
||||
assertEquals<Collection<AbstractParty>>(listOf(anonymisedRecipient), upgradedStateFromVault.state.data.owners, "Upgraded cash belongs to the right owner.")
|
||||
// Make sure the upgraded state can be spent
|
||||
val movedState = upgradedStateFromVault.state.data.copy(amount = upgradedStateFromVault.state.data.amount.times(2))
|
||||
val spendUpgradedTx = aliceNode.services.signInitialTransaction(
|
||||
TransactionBuilder(notary)
|
||||
.addInputState(upgradedStateFromVault)
|
||||
.addOutputState(
|
||||
upgradedStateFromVault.state.copy(data = movedState)
|
||||
)
|
||||
.addCommand(CashV2.Move(), alice.owningKey)
|
||||
|
||||
)
|
||||
aliceNode.services.startFlow(FinalityFlow(spendUpgradedTx)).apply {
|
||||
mockNet.runNetwork()
|
||||
get()
|
||||
}
|
||||
val movedStateFromVault = aliceNode.database.transaction { aliceNode.services.vaultService.queryBy<CashV2.State>().states.single() }
|
||||
assertEquals(movedState, movedStateFromVault.state.data)
|
||||
}
|
||||
|
||||
class CashV2 : UpgradedContract<Cash.State, CashV2.State> {
|
||||
class CashV2 : UpgradedContractWithLegacyConstraint<Cash.State, CashV2.State> {
|
||||
override val legacyContract = Cash.PROGRAM_ID
|
||||
override val legacyContractConstraint: AttachmentConstraint
|
||||
get() = AlwaysAcceptAttachmentConstraint
|
||||
|
||||
class Move : TypeOnlyCommandData()
|
||||
|
||||
data class State(override val amount: Amount<Issued<Currency>>, val owners: List<AbstractParty>) : FungibleAsset<Currency> {
|
||||
override val owner: AbstractParty = owners.first()
|
||||
|
@ -16,6 +16,7 @@ class VaultUpdateTests {
|
||||
private companion object {
|
||||
val DUMMY_PROGRAM_ID = "net.corda.core.node.VaultUpdateTests.DummyContract"
|
||||
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
|
||||
val emptyUpdate = Vault.Update(emptySet(), emptySet(), type = Vault.UpdateType.GENERAL)
|
||||
}
|
||||
|
||||
object DummyContract : Contract {
|
||||
@ -42,21 +43,21 @@ class VaultUpdateTests {
|
||||
|
||||
@Test
|
||||
fun `nothing plus nothing is nothing`() {
|
||||
val before = Vault.NoUpdate
|
||||
val after = before + Vault.NoUpdate
|
||||
val before = emptyUpdate
|
||||
val after = before + emptyUpdate
|
||||
assertEquals(before, after)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `something plus nothing is something`() {
|
||||
val before = Vault.Update<ContractState>(setOf(stateAndRef0, stateAndRef1), setOf(stateAndRef2, stateAndRef3))
|
||||
val after = before + Vault.NoUpdate
|
||||
val after = before + emptyUpdate
|
||||
assertEquals(before, after)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `nothing plus something is something`() {
|
||||
val before = Vault.NoUpdate
|
||||
val before = emptyUpdate
|
||||
val after = before + Vault.Update<ContractState>(setOf(stateAndRef0, stateAndRef1), setOf(stateAndRef2, stateAndRef3))
|
||||
val expected = Vault.Update<ContractState>(setOf(stateAndRef0, stateAndRef1), setOf(stateAndRef2, stateAndRef3))
|
||||
assertEquals(expected, after)
|
||||
|
@ -267,13 +267,16 @@ at boot, and means the Corda service stays running with no users connected to th
|
||||
* Set the amount of Java heap memory available to this node by modifying the -Xmx argument
|
||||
* Set an informative description
|
||||
|
||||
10. Run the batch file by clicking on it or from a command prompt
|
||||
10. Provision the required certificates to your node. Contact the network permissioning service or see
|
||||
:doc:`permissioning`
|
||||
|
||||
11. Run ``services.msc`` and verify that a service called ``cordanode1`` is present and running
|
||||
11. Run the batch file by clicking on it or from a command prompt
|
||||
|
||||
12. Run ``netstat -ano`` and check for the ports you configured in ``node.conf``
|
||||
12. Run ``services.msc`` and verify that a service called ``cordanode1`` is present and running
|
||||
|
||||
13. You may need to open the ports on the Windows firewall
|
||||
13. Run ``netstat -ano`` and check for the ports you configured in ``node.conf``
|
||||
|
||||
* You may need to open the ports on the Windows firewall
|
||||
|
||||
Testing your installation
|
||||
-------------------------
|
||||
|
@ -320,6 +320,23 @@ upgraded contracts must implement the ``UpgradedContract`` interface. This inter
|
||||
The ``upgrade`` method describes how the old state type is upgraded to the new state type. When the state isn't being
|
||||
upgraded, the same state type can be used for both the old and new state type parameters.
|
||||
|
||||
By default this new contract will only be able to upgrade legacy states which are constrained by the zone whitelist (see :doc:`api-contract-constraints`).
|
||||
If hash or other constraint types are used, the new contract should implement ``UpgradedContractWithLegacyConstraint``
|
||||
instead, and specify the constraint explicitly:
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
interface UpgradedContractWithLegacyConstraint<in OldState : ContractState, out NewState : ContractState> : UpgradedContract<OldState, NewState> {
|
||||
val legacyContractConstraint: AttachmentConstraint
|
||||
}
|
||||
|
||||
For example, in case of hash constraints the hash of the legacy JAR file should be provided:
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
override val legacyContractConstraint: AttachmentConstraint
|
||||
get() = HashAttachmentConstraint(SecureHash.parse("E02BD2B9B010BBCE49C0D7C35BECEF2C79BEB2EE80D902B54CC9231418A4FA0C"))
|
||||
|
||||
Authorising the upgrade
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Once the new states and contracts are on the classpath for all the relevant nodes, the next step is for all nodes to
|
||||
|
@ -7,3 +7,6 @@ either be moved into the main modules and go through code review, or be deleted.
|
||||
Code placed here can be committed to directly onto master at any time as long as it doesn't break the build
|
||||
(no compile failures or unit test failures). Any commits here that break the build will simply be rolled back.
|
||||
|
||||
To help reduce the build times, unit tests for experimental projects have been disabled and will NOT run alongside
|
||||
the whole project tests run via Gradle. Add parameter ```experimental.test.enable``` (example command is ```gradlew test -Dexperimental.test.enable```
|
||||
to enable those tests.
|
@ -97,7 +97,7 @@ object TwoPartyTradeFlow {
|
||||
val signTransactionFlow = object : SignTransactionFlow(otherSideSession, VERIFYING_AND_SIGNING.childProgressTracker()) {
|
||||
override fun checkTransaction(stx: SignedTransaction) {
|
||||
// Verify that we know who all the participants in the transaction are
|
||||
val states: Iterable<ContractState> = stx.tx.inputs.map { serviceHub.loadState(it).data } + stx.tx.outputs.map { it.data }
|
||||
val states: Iterable<ContractState> = serviceHub.loadStates(stx.tx.inputs.toSet()).map { it.state.data } + stx.tx.outputs.map { it.data }
|
||||
states.forEach { state ->
|
||||
state.participants.forEach { anon ->
|
||||
require(serviceHub.identityService.wellKnownPartyFromAnonymous(anon) != null) {
|
||||
|
@ -22,6 +22,7 @@ import net.corda.core.serialization.MissingAttachmentsException
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
@ -127,6 +128,7 @@ object DefaultKryoCustomizer {
|
||||
|
||||
register(java.lang.invoke.SerializedLambda::class.java)
|
||||
register(ClosureSerializer.Closure::class.java, CordaClosureBlacklistSerializer)
|
||||
register(ContractUpgradeWireTransaction::class.java, ContractUpgradeWireTransactionSerializer)
|
||||
|
||||
for (whitelistProvider in serializationWhitelists) {
|
||||
val types = whitelistProvider.whitelist
|
||||
|
@ -8,9 +8,12 @@ import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer
|
||||
import com.esotericsoftware.kryo.serializers.FieldSerializer
|
||||
import com.esotericsoftware.kryo.util.MapReferenceResolver
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.PrivacySalt
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
@ -269,6 +272,29 @@ object NotaryChangeWireTransactionSerializer : Serializer<NotaryChangeWireTransa
|
||||
}
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
object ContractUpgradeWireTransactionSerializer : Serializer<ContractUpgradeWireTransaction>() {
|
||||
override fun write(kryo: Kryo, output: Output, obj: ContractUpgradeWireTransaction) {
|
||||
kryo.writeClassAndObject(output, obj.inputs)
|
||||
kryo.writeClassAndObject(output, obj.notary)
|
||||
kryo.writeClassAndObject(output, obj.legacyContractAttachmentId)
|
||||
kryo.writeClassAndObject(output, obj.upgradeContractClassName)
|
||||
kryo.writeClassAndObject(output, obj.upgradedContractAttachmentId)
|
||||
kryo.writeClassAndObject(output, obj.privacySalt)
|
||||
}
|
||||
|
||||
override fun read(kryo: Kryo, input: Input, type: Class<ContractUpgradeWireTransaction>): ContractUpgradeWireTransaction {
|
||||
val inputs: List<StateRef> = uncheckedCast(kryo.readClassAndObject(input))
|
||||
val notary = kryo.readClassAndObject(input) as Party
|
||||
val legacyContractAttachment = kryo.readClassAndObject(input) as SecureHash
|
||||
val upgradeContractClassName = kryo.readClassAndObject(input) as String
|
||||
val upgradedContractAttachment = kryo.readClassAndObject(input) as SecureHash
|
||||
val privacySalt = kryo.readClassAndObject(input) as PrivacySalt
|
||||
|
||||
return ContractUpgradeWireTransaction(inputs, notary, legacyContractAttachment, upgradeContractClassName, upgradedContractAttachment, privacySalt)
|
||||
}
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
object SignedTransactionSerializer : Serializer<SignedTransaction>() {
|
||||
override fun write(kryo: Kryo, output: Output, obj: SignedTransaction) {
|
||||
|
@ -2,10 +2,7 @@ package net.corda.node.services
|
||||
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.PartyAndReference
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.cordapp.CordappProvider
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.UnexpectedFlowEndException
|
||||
@ -87,6 +84,7 @@ class AttachmentLoadingTests : IntegrationTest() {
|
||||
|
||||
private val services = object : ServicesForResolution {
|
||||
override fun loadState(stateRef: StateRef): TransactionState<*> = throw NotImplementedError()
|
||||
override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> = throw NotImplementedError()
|
||||
override val identityService = rigorousMock<IdentityService>().apply {
|
||||
doReturn(null).whenever(this).partyFromKey(DUMMY_BANK_A.owningKey)
|
||||
}
|
||||
|
@ -206,17 +206,24 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries).start(), identityService)
|
||||
val (keyPairs, nodeInfo) = initNodeInfo(networkMapCache, identity, identityKeyPair)
|
||||
identityService.loadIdentities(nodeInfo.legalIdentitiesAndCerts)
|
||||
val metrics = MetricRegistry()
|
||||
val transactionStorage = makeTransactionStorage(database, configuration.transactionCacheSizeBytes)
|
||||
attachments = NodeAttachmentService(metrics, configuration.attachmentContentCacheSizeBytes, configuration.attachmentCacheBound)
|
||||
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(), attachments, networkParameters.whitelistedContractImplementations)
|
||||
val servicesForResolution = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParameters, transactionStorage)
|
||||
val nodeProperties = NodePropertiesPersistentStore(StubbedNodeUniqueIdProvider::value, database)
|
||||
val nodeServices = makeServices(
|
||||
keyPairs,
|
||||
schemaService,
|
||||
transactionStorage,
|
||||
metrics,
|
||||
servicesForResolution,
|
||||
database,
|
||||
nodeInfo,
|
||||
identityService,
|
||||
networkMapCache,
|
||||
nodeProperties,
|
||||
cordappProvider,
|
||||
networkParameters)
|
||||
val mutualExclusionConfiguration = configuration.enterpriseConfiguration.mutualExclusionConfiguration
|
||||
if (mutualExclusionConfiguration.on) {
|
||||
@ -232,7 +239,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
platformClock,
|
||||
database,
|
||||
flowStarter,
|
||||
transactionStorage,
|
||||
servicesForResolution,
|
||||
unfinishedSchedules = busyNodeLatch,
|
||||
flowLogicRefFactory = flowLogicRefFactory,
|
||||
drainingModePollPeriod = configuration.drainingModePollPeriod,
|
||||
@ -544,16 +551,17 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
private fun makeServices(keyPairs: Set<KeyPair>,
|
||||
schemaService: SchemaService,
|
||||
transactionStorage: WritableTransactionStorage,
|
||||
metrics: MetricRegistry,
|
||||
servicesForResolution: ServicesForResolution,
|
||||
database: CordaPersistence,
|
||||
nodeInfo: NodeInfo,
|
||||
identityService: IdentityServiceInternal,
|
||||
networkMapCache: NetworkMapCacheInternal,
|
||||
nodeProperties: NodePropertiesStore,
|
||||
cordappProvider: CordappProviderInternal,
|
||||
networkParameters: NetworkParameters): MutableList<Any> {
|
||||
checkpointStorage = DBCheckpointStorage()
|
||||
val metrics = MetricRegistry()
|
||||
attachments = NodeAttachmentService(metrics, configuration.attachmentContentCacheSizeBytes, configuration.attachmentCacheBound)
|
||||
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(), attachments, networkParameters.whitelistedContractImplementations)
|
||||
|
||||
val keyManagementService = makeKeyManagementService(identityService, keyPairs)
|
||||
_services = ServiceHubInternalImpl(
|
||||
identityService,
|
||||
@ -566,7 +574,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
nodeInfo,
|
||||
networkMapCache,
|
||||
nodeProperties,
|
||||
networkParameters)
|
||||
networkParameters,
|
||||
servicesForResolution)
|
||||
network = makeMessagingService(database, nodeInfo, nodeProperties, networkParameters)
|
||||
val tokenizableServices = mutableListOf(attachments, network, services.vaultService,
|
||||
services.keyManagementService, services.identityService, platformClock,
|
||||
@ -767,8 +776,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
}
|
||||
|
||||
protected open fun generateKeyPair() = cryptoGenerateKeyPair()
|
||||
protected open fun makeVaultService(keyManagementService: KeyManagementService, stateLoader: StateLoader, hibernateConfig: HibernateConfiguration): VaultServiceInternal {
|
||||
return NodeVaultService(platformClock, keyManagementService, stateLoader, hibernateConfig)
|
||||
protected open fun makeVaultService(keyManagementService: KeyManagementService, services: ServicesForResolution, hibernateConfig: HibernateConfiguration): VaultServiceInternal {
|
||||
return NodeVaultService(platformClock, keyManagementService, services, hibernateConfig)
|
||||
}
|
||||
|
||||
/** Load configured JVM agents */
|
||||
@ -801,13 +810,14 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
override val myInfo: NodeInfo,
|
||||
override val networkMapCache: NetworkMapCacheInternal,
|
||||
override val nodeProperties: NodePropertiesStore,
|
||||
override val networkParameters: NetworkParameters
|
||||
) : SingletonSerializeAsToken(), ServiceHubInternal, StateLoader by validatedTransactions {
|
||||
override val networkParameters: NetworkParameters,
|
||||
private val servicesForResolution: ServicesForResolution
|
||||
) : SingletonSerializeAsToken(), ServiceHubInternal, ServicesForResolution by servicesForResolution {
|
||||
override val rpcFlows = ArrayList<Class<out FlowLogic<*>>>()
|
||||
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage()
|
||||
override val auditService = DummyAuditService()
|
||||
override val transactionVerifierService by lazy { makeTransactionVerifierService() }
|
||||
override val vaultService by lazy { makeVaultService(keyManagementService, validatedTransactions, database.hibernateConfig) }
|
||||
override val vaultService by lazy { makeVaultService(keyManagementService, servicesForResolution, database.hibernateConfig) }
|
||||
override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() }
|
||||
override val attachments: AttachmentStorage get() = this@AbstractNode.attachments
|
||||
override val networkService: MessagingService get() = network
|
||||
|
@ -0,0 +1,32 @@
|
||||
package net.corda.node.internal
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.cordapp.CordappProvider
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.services.AttachmentStorage
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.TransactionStorage
|
||||
|
||||
data class ServicesForResolutionImpl(
|
||||
override val identityService: IdentityService,
|
||||
override val attachments: AttachmentStorage,
|
||||
override val cordappProvider: CordappProvider,
|
||||
override val networkParameters: NetworkParameters,
|
||||
private val validatedTransactions: TransactionStorage
|
||||
) : ServicesForResolution {
|
||||
@Throws(TransactionResolutionException::class)
|
||||
override fun loadState(stateRef: StateRef): TransactionState<*> {
|
||||
val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
|
||||
return stx.resolveBaseTransaction(this).outputs[stateRef.index]
|
||||
}
|
||||
|
||||
@Throws(TransactionResolutionException::class)
|
||||
override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> {
|
||||
return stateRefs.groupBy { it.txhash }.map {
|
||||
val stx = validatedTransactions.getTransaction(it.key) ?: throw TransactionResolutionException(it.key)
|
||||
val baseTx = stx.resolveBaseTransaction(this)
|
||||
it.value.map { StateAndRef(baseTx.outputs[it.index], it) }
|
||||
}.flatMap { it }.toSet()
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
|
||||
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.UpgradedContract
|
||||
import net.corda.core.contracts.UpgradedContractWithLegacyConstraint
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.internal.*
|
||||
@ -241,7 +242,13 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
|
||||
}
|
||||
|
||||
private fun findContractClassNames(scanResult: RestrictedScanResult): List<String> {
|
||||
return (scanResult.getNamesOfClassesImplementing(Contract::class) + scanResult.getNamesOfClassesImplementing(UpgradedContract::class)).distinct()
|
||||
return (scanResult.getNamesOfClassesImplementing(Contract::class) +
|
||||
scanResult.getNamesOfClassesImplementing(UpgradedContract::class) +
|
||||
// Even though UpgradedContractWithLegacyConstraint implements UpgradedContract
|
||||
// we need to specify it separately. Otherwise, classes implementing UpgradedContractWithLegacyConstraint
|
||||
// don't get picked up.
|
||||
scanResult.getNamesOfClassesImplementing(UpgradedContractWithLegacyConstraint::class))
|
||||
.distinct()
|
||||
}
|
||||
|
||||
private fun findPlugins(cordappJarPath: RestrictedURL): List<SerializationWhitelist> {
|
||||
|
@ -7,6 +7,7 @@ import net.corda.core.contracts.requireThat
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.ContractUpgradeUtils
|
||||
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
||||
import net.corda.core.node.StatesToRecord
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
|
||||
@ -56,13 +57,13 @@ class ContractUpgradeHandler(otherSide: FlowSession) : AbstractStateReplacementF
|
||||
val oldStateAndRef = ourSTX!!.tx.outRef<ContractState>(proposal.stateRef.index)
|
||||
val authorisedUpgrade = serviceHub.contractUpgradeService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?:
|
||||
throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}")
|
||||
val proposedTx = stx.tx
|
||||
val expectedTx = ContractUpgradeUtils.assembleBareTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt).toWireTransaction(serviceHub)
|
||||
val proposedTx = stx.coreTransaction as ContractUpgradeWireTransaction
|
||||
val expectedTx = ContractUpgradeUtils.assembleUpgradeTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt, serviceHub)
|
||||
requireThat {
|
||||
"The instigator is one of the participants" using (initiatingSession.counterparty in oldStateAndRef.state.data.participants)
|
||||
"The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification.name == authorisedUpgrade)
|
||||
"The proposed tx matches the expected tx for this upgrade" using (proposedTx == expectedTx)
|
||||
}
|
||||
proposedTx.toLedgerTransaction(serviceHub).verify()
|
||||
proposedTx.resolve(serviceHub, stx.sigs)
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +81,6 @@ interface ServiceHubInternal : ServiceHub {
|
||||
}
|
||||
|
||||
if (statesToRecord != StatesToRecord.NONE) {
|
||||
val toNotify = recordedTransactions.map { if (it.isNotaryChangeTransaction()) it.notaryChangeTx else it.tx }
|
||||
// When the user has requested StatesToRecord.ALL we may end up recording and relationally mapping states
|
||||
// that do not involve us and that we cannot sign for. This will break coin selection and thus a warning
|
||||
// is present in the documentation for this feature (see the "Observer nodes" tutorial on docs.corda.net).
|
||||
@ -116,7 +115,7 @@ interface ServiceHubInternal : ServiceHub {
|
||||
//
|
||||
// Because the primary use case for recording irrelevant states is observer/regulator nodes, who are unlikely
|
||||
// to make writes to the ledger very often or at all, we choose to punt this issue for the time being.
|
||||
vaultService.notifyAll(statesToRecord, toNotify)
|
||||
vaultService.notifyAll(statesToRecord, txs.map { it.coreTransaction })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ import net.corda.core.internal.VisibleForTesting
|
||||
import net.corda.core.internal.concurrent.flatMap
|
||||
import net.corda.core.internal.join
|
||||
import net.corda.core.internal.until
|
||||
import net.corda.core.node.StateLoader
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.schemas.PersistentStateRef
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.utilities.contextLogger
|
||||
@ -58,7 +58,7 @@ import com.google.common.util.concurrent.SettableFuture as GuavaSettableFuture
|
||||
class NodeSchedulerService(private val clock: CordaClock,
|
||||
private val database: CordaPersistence,
|
||||
private val flowStarter: FlowStarter,
|
||||
private val stateLoader: StateLoader,
|
||||
private val servicesForResolution: ServicesForResolution,
|
||||
private val unfinishedSchedules: ReusableLatch = ReusableLatch(),
|
||||
private val flowLogicRefFactory: FlowLogicRefFactory,
|
||||
private val nodeProperties: NodePropertiesStore,
|
||||
@ -308,7 +308,7 @@ class NodeSchedulerService(private val clock: CordaClock,
|
||||
}
|
||||
|
||||
private fun getScheduledActivity(scheduledState: ScheduledStateRef): ScheduledActivity? {
|
||||
val txState = stateLoader.loadState(scheduledState.ref)
|
||||
val txState = servicesForResolution.loadState(scheduledState.ref)
|
||||
val state = txState.data as SchedulableState
|
||||
return try {
|
||||
// This can throw as running contract code.
|
||||
|
@ -10,6 +10,7 @@ import net.corda.core.flows.NotarisationRequest
|
||||
import net.corda.core.internal.validateRequest
|
||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
||||
import net.corda.core.transactions.CoreTransaction
|
||||
import net.corda.core.transactions.ContractUpgradeFilteredTransaction
|
||||
import net.corda.core.transactions.FilteredTransaction
|
||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||
import net.corda.core.utilities.unwrap
|
||||
@ -44,6 +45,7 @@ class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAut
|
||||
val notary = tx.notary
|
||||
TransactionParts(tx.id, tx.inputs, tx.timeWindow, notary)
|
||||
}
|
||||
is ContractUpgradeFilteredTransaction -> TransactionParts(tx.id, tx.inputs, null, tx.notary)
|
||||
is NotaryChangeWireTransaction -> TransactionParts(tx.id, tx.inputs, null, tx.notary)
|
||||
else -> {
|
||||
throw IllegalArgumentException("Received unexpected transaction type: ${tx::class.java.simpleName}," +
|
||||
|
@ -4,13 +4,12 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.TimeWindow
|
||||
import net.corda.core.contracts.TransactionVerificationException
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.flows.NotarisationPayload
|
||||
import net.corda.core.flows.NotarisationRequest
|
||||
import net.corda.core.internal.ResolveTransactionsFlow
|
||||
import net.corda.core.internal.validateRequest
|
||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionWithSignatures
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.unwrap
|
||||
import java.security.SignatureException
|
||||
|
||||
@ -31,12 +30,9 @@ class ValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAuthor
|
||||
val stx = receiveTransaction()
|
||||
val notary = stx.notary
|
||||
checkNotary(notary)
|
||||
val timeWindow: TimeWindow? = if (stx.isNotaryChangeTransaction())
|
||||
null
|
||||
else
|
||||
stx.tx.timeWindow
|
||||
resolveAndContractVerify(stx)
|
||||
verifySignatures(stx)
|
||||
val timeWindow: TimeWindow? = if (stx.coreTransaction is WireTransaction) stx.tx.timeWindow else null
|
||||
return TransactionParts(stx.id, stx.inputs, timeWindow, notary!!)
|
||||
} catch (e: Exception) {
|
||||
throw when (e) {
|
||||
|
@ -6,15 +6,13 @@ import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.messaging.DataFeed
|
||||
import net.corda.core.node.StateLoader
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.StatesToRecord
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.node.services.vault.*
|
||||
import net.corda.core.schemas.PersistentStateRef
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.transactions.CoreTransaction
|
||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.transactions.*
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.node.services.api.VaultServiceInternal
|
||||
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||
@ -49,7 +47,7 @@ private fun CriteriaBuilder.executeUpdate(session: Session, configure: Root<*>.(
|
||||
class NodeVaultService(
|
||||
private val clock: Clock,
|
||||
private val keyManagementService: KeyManagementService,
|
||||
private val stateLoader: StateLoader,
|
||||
private val servicesForResolution: ServicesForResolution,
|
||||
hibernateConfig: HibernateConfiguration
|
||||
) : SingletonSerializeAsToken(), VaultServiceInternal {
|
||||
private companion object {
|
||||
@ -108,41 +106,29 @@ class NodeVaultService(
|
||||
override val updates: Observable<Vault.Update<ContractState>>
|
||||
get() = concurrentBox.content._updatesInDbTx
|
||||
|
||||
/** Groups adjacent transactions into batches to generate separate net updates per transaction type. */
|
||||
override fun notifyAll(statesToRecord: StatesToRecord, txns: Iterable<CoreTransaction>) {
|
||||
if (statesToRecord == StatesToRecord.NONE)
|
||||
return
|
||||
if (statesToRecord == StatesToRecord.NONE || !txns.any()) return
|
||||
val batch = mutableListOf<CoreTransaction>()
|
||||
|
||||
// It'd be easier to just group by type, but then we'd lose ordering.
|
||||
val regularTxns = mutableListOf<WireTransaction>()
|
||||
val notaryChangeTxns = mutableListOf<NotaryChangeWireTransaction>()
|
||||
|
||||
for (tx in txns) {
|
||||
when (tx) {
|
||||
is WireTransaction -> {
|
||||
regularTxns.add(tx)
|
||||
if (notaryChangeTxns.isNotEmpty()) {
|
||||
notifyNotaryChange(notaryChangeTxns.toList(), statesToRecord)
|
||||
notaryChangeTxns.clear()
|
||||
}
|
||||
}
|
||||
is NotaryChangeWireTransaction -> {
|
||||
notaryChangeTxns.add(tx)
|
||||
if (regularTxns.isNotEmpty()) {
|
||||
notifyRegular(regularTxns.toList(), statesToRecord)
|
||||
regularTxns.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
fun flushBatch() {
|
||||
val updates = makeUpdates(batch, statesToRecord)
|
||||
processAndNotify(updates)
|
||||
batch.clear()
|
||||
}
|
||||
|
||||
if (regularTxns.isNotEmpty()) notifyRegular(regularTxns.toList(), statesToRecord)
|
||||
if (notaryChangeTxns.isNotEmpty()) notifyNotaryChange(notaryChangeTxns.toList(), statesToRecord)
|
||||
for (tx in txns) {
|
||||
if (batch.isNotEmpty() && tx.javaClass != batch.last().javaClass) {
|
||||
flushBatch()
|
||||
}
|
||||
batch.add(tx)
|
||||
}
|
||||
flushBatch()
|
||||
}
|
||||
|
||||
private fun notifyRegular(txns: Iterable<WireTransaction>, statesToRecord: StatesToRecord) {
|
||||
fun makeUpdate(tx: WireTransaction): Vault.Update<ContractState> {
|
||||
private fun makeUpdates(batch: Iterable<CoreTransaction>, statesToRecord: StatesToRecord): List<Vault.Update<ContractState>> {
|
||||
fun makeUpdate(tx: WireTransaction): Vault.Update<ContractState>? {
|
||||
val myKeys = keyManagementService.filterMyKeys(tx.outputs.flatMap { it.data.participants.map { it.owningKey } })
|
||||
|
||||
val ourNewStates = when (statesToRecord) {
|
||||
StatesToRecord.NONE -> throw AssertionError("Should not reach here")
|
||||
StatesToRecord.ONLY_RELEVANT -> tx.outputs.filter { isRelevant(it.data, myKeys.toSet()) }
|
||||
@ -155,45 +141,48 @@ class NodeVaultService(
|
||||
// Is transaction irrelevant?
|
||||
if (consumedStates.isEmpty() && ourNewStates.isEmpty()) {
|
||||
log.trace { "tx ${tx.id} was irrelevant to this vault, ignoring" }
|
||||
return Vault.NoUpdate
|
||||
return null
|
||||
}
|
||||
|
||||
return Vault.Update(consumedStates.toSet(), ourNewStates.toSet())
|
||||
}
|
||||
|
||||
val netDelta = txns.fold(Vault.NoUpdate) { netDelta, txn -> netDelta + makeUpdate(txn) }
|
||||
processAndNotify(netDelta)
|
||||
}
|
||||
|
||||
private fun notifyNotaryChange(txns: Iterable<NotaryChangeWireTransaction>, statesToRecord: StatesToRecord) {
|
||||
fun makeUpdate(tx: NotaryChangeWireTransaction): Vault.Update<ContractState> {
|
||||
fun resolveAndMakeUpdate(tx: CoreTransaction): Vault.Update<ContractState>? {
|
||||
// We need to resolve the full transaction here because outputs are calculated from inputs
|
||||
// We also can't do filtering beforehand, since output encumbrance pointers get recalculated based on
|
||||
// input positions
|
||||
val ltx = tx.resolve(stateLoader, emptyList())
|
||||
// We also can't do filtering beforehand, since for notary change transactions output encumbrance pointers
|
||||
// get recalculated based on input positions.
|
||||
val ltx: FullTransaction = when (tx) {
|
||||
is NotaryChangeWireTransaction -> tx.resolve(servicesForResolution, emptyList())
|
||||
is ContractUpgradeWireTransaction -> tx.resolve(servicesForResolution, emptyList())
|
||||
else -> throw IllegalArgumentException("Unsupported transaction type: ${tx.javaClass.name}")
|
||||
}
|
||||
val myKeys = keyManagementService.filterMyKeys(ltx.outputs.flatMap { it.data.participants.map { it.owningKey } })
|
||||
val (consumedStateAndRefs, producedStates) = ltx.inputs.
|
||||
zip(ltx.outputs).
|
||||
filter { (_, output) ->
|
||||
if (statesToRecord == StatesToRecord.ONLY_RELEVANT)
|
||||
isRelevant(output.data, myKeys.toSet())
|
||||
else
|
||||
true
|
||||
if (statesToRecord == StatesToRecord.ONLY_RELEVANT) isRelevant(output.data, myKeys.toSet())
|
||||
else true
|
||||
}.
|
||||
unzip()
|
||||
|
||||
val producedStateAndRefs = producedStates.map { ltx.outRef<ContractState>(it.data) }
|
||||
|
||||
if (consumedStateAndRefs.isEmpty() && producedStateAndRefs.isEmpty()) {
|
||||
log.trace { "tx ${tx.id} was irrelevant to this vault, ignoring" }
|
||||
return Vault.NoNotaryUpdate
|
||||
return null
|
||||
}
|
||||
|
||||
return Vault.Update(consumedStateAndRefs.toHashSet(), producedStateAndRefs.toHashSet(), null, Vault.UpdateType.NOTARY_CHANGE)
|
||||
val updateType = if (tx is ContractUpgradeWireTransaction) {
|
||||
Vault.UpdateType.CONTRACT_UPGRADE
|
||||
} else {
|
||||
Vault.UpdateType.NOTARY_CHANGE
|
||||
}
|
||||
return Vault.Update(consumedStateAndRefs.toSet(), producedStateAndRefs.toSet(), null, updateType)
|
||||
}
|
||||
|
||||
val netDelta = txns.fold(Vault.NoNotaryUpdate) { netDelta, txn -> netDelta + makeUpdate(txn) }
|
||||
processAndNotify(netDelta)
|
||||
|
||||
return batch.mapNotNull {
|
||||
if (it is WireTransaction) makeUpdate(it) else resolveAndMakeUpdate(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadStates(refs: Collection<StateRef>): Collection<StateAndRef<ContractState>> {
|
||||
@ -202,13 +191,15 @@ class NodeVaultService(
|
||||
else emptySet()
|
||||
}
|
||||
|
||||
private fun processAndNotify(update: Vault.Update<ContractState>) {
|
||||
if (!update.isEmpty()) {
|
||||
recordUpdate(update)
|
||||
private fun processAndNotify(updates: List<Vault.Update<ContractState>>) {
|
||||
if (updates.isEmpty()) return
|
||||
val netUpdate = updates.reduce { update1, update2 -> update1 + update2 }
|
||||
if (!netUpdate.isEmpty()) {
|
||||
recordUpdate(netUpdate)
|
||||
concurrentBox.concurrent {
|
||||
// flowId required by SoftLockManager to perform auto-registration of soft locks for new states
|
||||
val uuid = (Strand.currentStrand() as? FlowStateMachineImpl<*>)?.id?.uuid
|
||||
val vaultUpdate = if (uuid != null) update.copy(flowId = uuid) else update
|
||||
val vaultUpdate = if (uuid != null) netUpdate.copy(flowId = uuid) else netUpdate
|
||||
updatesPublisher.onNext(vaultUpdate)
|
||||
}
|
||||
}
|
||||
@ -457,7 +448,7 @@ class NodeVaultService(
|
||||
}
|
||||
}
|
||||
if (stateRefs.isNotEmpty())
|
||||
statesAndRefs.addAll(stateLoader.loadStates(stateRefs) as Collection<StateAndRef<T>>)
|
||||
statesAndRefs.addAll(servicesForResolution.loadStates(stateRefs) as Collection<StateAndRef<T>>)
|
||||
|
||||
return Vault.Page(states = statesAndRefs, statesMetadata = statesMeta, stateTypes = criteriaParser.stateTypes, totalStatesAvailable = totalStates, otherResults = otherResults)
|
||||
} catch (e: java.lang.Exception) {
|
||||
|
@ -8,7 +8,7 @@ import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.internal.FlowStateMachine
|
||||
import net.corda.core.internal.concurrent.openFuture
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.node.StateLoader
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.node.services.api.FlowStarter
|
||||
import net.corda.node.services.api.NodePropertiesStore
|
||||
@ -47,7 +47,7 @@ class NodeSchedulerServiceTest {
|
||||
doReturn(flowsDraingMode).whenever(it).flowsDrainingMode
|
||||
}
|
||||
private val transactionStates = mutableMapOf<StateRef, TransactionState<*>>()
|
||||
private val stateLoader = rigorousMock<StateLoader>().also {
|
||||
private val servicesForResolution = rigorousMock<ServicesForResolution>().also {
|
||||
doLookup(transactionStates).whenever(it).loadState(any())
|
||||
}
|
||||
private val flows = mutableMapOf<FlowLogicRef, FlowLogic<*>>()
|
||||
@ -62,7 +62,7 @@ class NodeSchedulerServiceTest {
|
||||
testClock,
|
||||
database,
|
||||
flowStarter,
|
||||
stateLoader,
|
||||
servicesForResolution,
|
||||
flowLogicRefFactory = flowLogicRefFactory,
|
||||
nodeProperties = nodeProperties,
|
||||
drainingModePollPeriod = Duration.ofSeconds(5),
|
||||
|
@ -119,7 +119,7 @@ class HibernateConfigurationTest {
|
||||
services = object : MockServices(cordappPackages, BOB_NAME, rigorousMock<IdentityServiceInternal>().also {
|
||||
doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == BOB_NAME })
|
||||
}, generateKeyPair(), dummyNotary.keyPair) {
|
||||
override val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, validatedTransactions, hibernateConfig)
|
||||
override val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, servicesForResolution, hibernateConfig)
|
||||
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
||||
for (stx in txs) {
|
||||
validatedTransactions.addTransaction(stx)
|
||||
|
@ -11,7 +11,7 @@ import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.internal.FlowStateMachine
|
||||
import net.corda.core.internal.packageName
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.node.StateLoader
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.node.services.queryBy
|
||||
import net.corda.core.node.services.vault.QueryCriteria.SoftLockingCondition
|
||||
@ -83,8 +83,8 @@ class VaultSoftLockManagerTest {
|
||||
}
|
||||
private val mockNet = InternalMockNetwork(cordappPackages = listOf(ContractImpl::class.packageName), defaultFactory = { args ->
|
||||
object : InternalMockNetwork.MockNode(args) {
|
||||
override fun makeVaultService(keyManagementService: KeyManagementService, stateLoader: StateLoader, hibernateConfig: HibernateConfiguration): VaultServiceInternal {
|
||||
val realVault = super.makeVaultService(keyManagementService, stateLoader, hibernateConfig)
|
||||
override fun makeVaultService(keyManagementService: KeyManagementService, services: ServicesForResolution, hibernateConfig: HibernateConfiguration): VaultServiceInternal {
|
||||
val realVault = super.makeVaultService(keyManagementService, services, hibernateConfig)
|
||||
return object : VaultServiceInternal by realVault {
|
||||
override fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet<StateRef>?) {
|
||||
mockVault.softLockRelease(lockId, stateRefs) // No need to also call the real one for these tests.
|
||||
|
@ -11,8 +11,10 @@ import net.corda.core.internal.validateRequest
|
||||
import net.corda.core.node.AppServiceHub
|
||||
import net.corda.core.node.services.CordaService
|
||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
||||
import net.corda.core.transactions.CoreTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionWithSignatures
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.unwrap
|
||||
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
||||
import java.security.PublicKey
|
||||
@ -49,12 +51,9 @@ class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidating
|
||||
val stx = receiveTransaction()
|
||||
val notary = stx.notary
|
||||
checkNotary(notary)
|
||||
val timeWindow: TimeWindow? = if (stx.isNotaryChangeTransaction())
|
||||
null
|
||||
else
|
||||
stx.tx.timeWindow
|
||||
resolveAndContractVerify(stx)
|
||||
verifySignatures(stx)
|
||||
resolveAndContractVerify(stx)
|
||||
val timeWindow: TimeWindow? = if (stx.coreTransaction is WireTransaction) stx.tx.timeWindow else null
|
||||
return TransactionParts(stx.id, stx.inputs, timeWindow, notary!!)
|
||||
} catch (e: Exception) {
|
||||
throw when (e) {
|
||||
|
@ -6,6 +6,9 @@ import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigParseOptions
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.contracts.ContractClassName
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.cordapp.CordappProvider
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.flows.FlowLogic
|
||||
@ -23,6 +26,7 @@ import net.corda.core.toFuture
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.VersionInfo
|
||||
import net.corda.node.internal.ServicesForResolutionImpl
|
||||
import net.corda.node.internal.configureDatabase
|
||||
import net.corda.node.internal.cordapp.CordappLoader
|
||||
import net.corda.node.services.api.SchemaService
|
||||
@ -81,8 +85,7 @@ open class MockServices private constructor(
|
||||
final override val networkParameters: NetworkParameters,
|
||||
private val initialIdentity: TestIdentity,
|
||||
private val moreKeys: Array<out KeyPair>
|
||||
) : ServiceHub, StateLoader by validatedTransactions {
|
||||
|
||||
) : ServiceHub {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val MOCK_VERSION_INFO = VersionInfo(1, "Mock release", "Mock revision", "Mock Vendor")
|
||||
@ -148,7 +151,7 @@ open class MockServices private constructor(
|
||||
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
||||
super.recordTransactions(statesToRecord, txs)
|
||||
// Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions.
|
||||
vaultService.notifyAll(statesToRecord, txs.map { it.tx })
|
||||
vaultService.notifyAll(statesToRecord, txs.map { it.coreTransaction })
|
||||
}
|
||||
|
||||
override fun jdbcSession(): Connection = database.createSession()
|
||||
@ -207,7 +210,7 @@ open class MockServices private constructor(
|
||||
/**
|
||||
* Create a mock [ServiceHub] that can't load CorDapp code, and which uses a default service identity.
|
||||
*/
|
||||
constructor(cordappPackages: List<String>): this(cordappPackages, CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService())
|
||||
constructor(cordappPackages: List<String>) : this(cordappPackages, CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService())
|
||||
|
||||
/**
|
||||
* Create a mock [ServiceHub] which uses the package of the caller to find CorDapp code. It uses the provided identity service
|
||||
@ -242,7 +245,8 @@ open class MockServices private constructor(
|
||||
* Create a mock [ServiceHub] which uses the package of the caller to find CorDapp code. It uses a default service
|
||||
* identity.
|
||||
*/
|
||||
constructor(): this(listOf(getCallerPackage()), CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService())
|
||||
constructor() : this(listOf(getCallerPackage()), CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService())
|
||||
|
||||
|
||||
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
||||
txs.forEach {
|
||||
@ -264,8 +268,10 @@ open class MockServices private constructor(
|
||||
private val mockCordappProvider: MockCordappProvider = MockCordappProvider(cordappLoader, attachments, networkParameters.whitelistedContractImplementations)
|
||||
override val cordappProvider: CordappProvider get() = mockCordappProvider
|
||||
|
||||
protected val servicesForResolution: ServicesForResolution get() = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParameters, validatedTransactions)
|
||||
|
||||
internal fun makeVaultService(hibernateConfig: HibernateConfiguration, schemaService: SchemaService): VaultServiceInternal {
|
||||
val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, validatedTransactions, hibernateConfig)
|
||||
val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, servicesForResolution, hibernateConfig)
|
||||
HibernateObserver.install(vaultService.rawUpdates, hibernateConfig, schemaService)
|
||||
return vaultService
|
||||
}
|
||||
@ -284,6 +290,9 @@ open class MockServices private constructor(
|
||||
fun addMockCordapp(contractClassName: ContractClassName) {
|
||||
mockCordappProvider.addMockCordapp(contractClassName, attachments)
|
||||
}
|
||||
|
||||
override fun loadState(stateRef: StateRef) = servicesForResolution.loadState(stateRef)
|
||||
override fun loadStates(stateRefs: Set<StateRef>) = servicesForResolution.loadStates(stateRefs)
|
||||
}
|
||||
|
||||
class MockKeyManagementService(val identityService: IdentityService,
|
||||
|
@ -1,12 +1,9 @@
|
||||
package net.corda.testing.contracts
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.internal.UpgradeCommand
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
|
||||
// The dummy contract doesn't do anything useful. It exists for testing purposes.
|
||||
|
||||
@ -14,12 +11,13 @@ import net.corda.core.transactions.WireTransaction
|
||||
* Dummy contract state for testing of the upgrade process.
|
||||
*/
|
||||
// DOCSTART 1
|
||||
class DummyContractV2 : UpgradedContract<DummyContract.State, DummyContractV2.State> {
|
||||
class DummyContractV2 : UpgradedContractWithLegacyConstraint<DummyContract.State, DummyContractV2.State> {
|
||||
companion object {
|
||||
const val PROGRAM_ID: ContractClassName = "net.corda.testing.contracts.DummyContractV2"
|
||||
}
|
||||
|
||||
override val legacyContract: String = DummyContract::class.java.name
|
||||
override val legacyContractConstraint: AttachmentConstraint = AlwaysAcceptAttachmentConstraint
|
||||
|
||||
data class State(val magicNumber: Int = 0, val owners: List<AbstractParty>) : ContractState {
|
||||
override val participants: List<AbstractParty> = owners
|
||||
@ -38,25 +36,4 @@ class DummyContractV2 : UpgradedContract<DummyContract.State, DummyContractV2.St
|
||||
// Other verifications.
|
||||
}
|
||||
// DOCEND 1
|
||||
/**
|
||||
* Generate an upgrade transaction from [DummyContract].
|
||||
*
|
||||
* Note: This is a convenience helper method used for testing only.
|
||||
*
|
||||
* @param services Services required to resolve the wire transaction
|
||||
* @return a pair of wire transaction, and a set of those who should sign the transaction for it to be valid.
|
||||
*/
|
||||
fun generateUpgradeFromV1(services: ServicesForResolution, vararg states: StateAndRef<DummyContract.State>): Pair<WireTransaction, Set<AbstractParty>> {
|
||||
val notary = states.map { it.state.notary }.single()
|
||||
require(states.isNotEmpty())
|
||||
|
||||
val signees: Set<AbstractParty> = states.flatMap { it.state.data.participants }.distinct().toSet()
|
||||
return Pair(TransactionBuilder(notary).apply {
|
||||
states.forEach {
|
||||
addInputState(it)
|
||||
addOutputState(upgrade(it.state.data), DummyContractV2.PROGRAM_ID, it.state.constraint)
|
||||
addCommand(UpgradeCommand(DummyContractV2.PROGRAM_ID), signees.map { it.owningKey }.toList())
|
||||
}
|
||||
}.toWireTransaction(services), signees)
|
||||
}
|
||||
}
|
||||
|
@ -77,6 +77,9 @@ data class TestTransactionDSLInterpreter private constructor(
|
||||
|
||||
val services = object : ServicesForResolution by ledgerInterpreter.services {
|
||||
override fun loadState(stateRef: StateRef) = ledgerInterpreter.resolveStateRef<ContractState>(stateRef)
|
||||
override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> {
|
||||
return stateRefs.map { StateAndRef(loadState(it), it) }.toSet()
|
||||
}
|
||||
override val cordappProvider: CordappProvider = ledgerInterpreter.services.cordappProvider
|
||||
}
|
||||
|
||||
|
@ -50,6 +50,13 @@ data class GeneratedLedger(
|
||||
override fun loadState(stateRef: StateRef): TransactionState<*> {
|
||||
return hashTransactionMap[stateRef.txhash]?.outputs?.get(stateRef.index) ?: throw TransactionResolutionException(stateRef.txhash)
|
||||
}
|
||||
|
||||
override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> {
|
||||
return stateRefs.groupBy { it.txhash }.flatMap {
|
||||
val outputs = hashTransactionMap[it.key]?.outputs ?: throw TransactionResolutionException(it.key)
|
||||
it.value.map { StateAndRef(outputs[it.index], it) }
|
||||
}.toSet()
|
||||
}
|
||||
override val identityService = rigorousMock<IdentityService>().apply {
|
||||
doAnswer { identityMap[it.arguments[0]] }.whenever(this).partyFromKey(any())
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.nodeapi.internal.config.NodeSSLConfiguration
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.nodeapi.internal.config.getValue
|
||||
import net.corda.nodeapi.internal.config.parseAs
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
@ -25,5 +26,16 @@ class WebServerConfig(override val baseDirectory: Path, val config: Config) : No
|
||||
throw Exception("Missing rpc address property. Either 'rpcSettings' or 'rpcAddress' must be specified.")
|
||||
}
|
||||
val webAddress: NetworkHostAndPort by config
|
||||
val rpcUsers: List<User> by config
|
||||
val runAs: User
|
||||
|
||||
init {
|
||||
// TODO: replace with credentials supplied by a user
|
||||
val users = if (config.hasPath("rpcUsers")) {
|
||||
// TODO: remove this once config format is updated
|
||||
config.getConfigList("rpcUsers")
|
||||
} else {
|
||||
config.getConfigList("security.authService.dataSource.users")
|
||||
}
|
||||
runAs = users.first().parseAs<User>()
|
||||
}
|
||||
}
|
||||
|
@ -189,10 +189,9 @@ class NodeWebServer(val config: WebServerConfig) {
|
||||
}
|
||||
|
||||
private fun connectLocalRpcAsNodeUser(): CordaRPCOps {
|
||||
val rpcUser = config.rpcUsers.firstOrNull() ?: throw IllegalArgumentException("The node config has not specified any RPC users")
|
||||
log.info("Connecting to node at ${config.rpcAddress} as $rpcUser")
|
||||
log.info("Connecting to node at ${config.rpcAddress} as ${config.runAs}")
|
||||
val client = CordaRPCClient(config.rpcAddress)
|
||||
val connection = client.start(rpcUser.username, rpcUser.password)
|
||||
val connection = client.start(config.runAs.username, config.runAs.password)
|
||||
return connection.proxy
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user