mirror of
https://github.com/corda/corda.git
synced 2025-05-28 21:24:24 +00:00
Merged in mnesbit-cleanup-remove-clashingthreads (pull request #281)
Mnesbit cleanup remove clashingthreads
This commit is contained in:
commit
21092e2bb6
@ -450,14 +450,18 @@ class InterestRateSwap() : Contract {
|
|||||||
fun extractCommands(tx: TransactionForContract): Collection<AuthenticatedObject<CommandData>>
|
fun extractCommands(tx: TransactionForContract): Collection<AuthenticatedObject<CommandData>>
|
||||||
= tx.commands.select<Commands>()
|
= tx.commands.select<Commands>()
|
||||||
|
|
||||||
override fun verify(tx: TransactionForContract) = verifyClauses(tx, listOf(Clause.Timestamped(), Clause.Group()), extractCommands(tx))
|
override fun verify(tx: TransactionForContract) {
|
||||||
|
verifyClauses(tx,
|
||||||
|
listOf(Clause.Timestamped(), Clause.Group(), LinearState.ClauseVerifier(State::class.java)),
|
||||||
|
extractCommands(tx))
|
||||||
|
}
|
||||||
|
|
||||||
interface Clause {
|
interface Clause {
|
||||||
/**
|
/**
|
||||||
* Common superclass for IRS contract clauses, which defines behaviour on match/no-match, and provides
|
* Common superclass for IRS contract clauses, which defines behaviour on match/no-match, and provides
|
||||||
* helper functions for the clauses.
|
* helper functions for the clauses.
|
||||||
*/
|
*/
|
||||||
abstract class AbstractIRSClause : GroupClause<State, String> {
|
abstract class AbstractIRSClause : GroupClause<State, UniqueIdentifier> {
|
||||||
override val ifMatched = MatchBehaviour.END
|
override val ifMatched = MatchBehaviour.END
|
||||||
override val ifNotMatched = MatchBehaviour.CONTINUE
|
override val ifNotMatched = MatchBehaviour.CONTINUE
|
||||||
|
|
||||||
@ -502,13 +506,13 @@ class InterestRateSwap() : Contract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Group : GroupClauseVerifier<State, String>() {
|
class Group : GroupClauseVerifier<State, UniqueIdentifier>() {
|
||||||
override val ifMatched = MatchBehaviour.END
|
override val ifMatched = MatchBehaviour.END
|
||||||
override val ifNotMatched = MatchBehaviour.ERROR
|
override val ifNotMatched = MatchBehaviour.ERROR
|
||||||
|
|
||||||
override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<State, String>>
|
override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<State, UniqueIdentifier>>
|
||||||
// Group by Trade ID for in / out states
|
// Group by Trade ID for in / out states
|
||||||
= tx.groupStates() { state -> state.common.tradeID }
|
= tx.groupStates() { state -> state.linearId }
|
||||||
|
|
||||||
override val clauses = listOf(Agree(), Fix(), Pay(), Mature())
|
override val clauses = listOf(Agree(), Fix(), Pay(), Mature())
|
||||||
}
|
}
|
||||||
@ -532,7 +536,7 @@ class InterestRateSwap() : Contract {
|
|||||||
inputs: List<State>,
|
inputs: List<State>,
|
||||||
outputs: List<State>,
|
outputs: List<State>,
|
||||||
commands: Collection<AuthenticatedObject<CommandData>>,
|
commands: Collection<AuthenticatedObject<CommandData>>,
|
||||||
token: String): Set<CommandData> {
|
token: UniqueIdentifier): Set<CommandData> {
|
||||||
val command = tx.commands.requireSingleCommand<Commands.Agree>()
|
val command = tx.commands.requireSingleCommand<Commands.Agree>()
|
||||||
val irs = outputs.filterIsInstance<State>().single()
|
val irs = outputs.filterIsInstance<State>().single()
|
||||||
requireThat {
|
requireThat {
|
||||||
@ -568,7 +572,7 @@ class InterestRateSwap() : Contract {
|
|||||||
inputs: List<State>,
|
inputs: List<State>,
|
||||||
outputs: List<State>,
|
outputs: List<State>,
|
||||||
commands: Collection<AuthenticatedObject<CommandData>>,
|
commands: Collection<AuthenticatedObject<CommandData>>,
|
||||||
token: String): Set<CommandData> {
|
token: UniqueIdentifier): Set<CommandData> {
|
||||||
val command = tx.commands.requireSingleCommand<Commands.Refix>()
|
val command = tx.commands.requireSingleCommand<Commands.Refix>()
|
||||||
val irs = outputs.filterIsInstance<State>().single()
|
val irs = outputs.filterIsInstance<State>().single()
|
||||||
val prevIrs = inputs.filterIsInstance<State>().single()
|
val prevIrs = inputs.filterIsInstance<State>().single()
|
||||||
@ -613,7 +617,7 @@ class InterestRateSwap() : Contract {
|
|||||||
inputs: List<State>,
|
inputs: List<State>,
|
||||||
outputs: List<State>,
|
outputs: List<State>,
|
||||||
commands: Collection<AuthenticatedObject<CommandData>>,
|
commands: Collection<AuthenticatedObject<CommandData>>,
|
||||||
token: String): Set<CommandData> {
|
token: UniqueIdentifier): Set<CommandData> {
|
||||||
val command = tx.commands.requireSingleCommand<Commands.Pay>()
|
val command = tx.commands.requireSingleCommand<Commands.Pay>()
|
||||||
requireThat {
|
requireThat {
|
||||||
"Payments not supported / verifiable yet" by false
|
"Payments not supported / verifiable yet" by false
|
||||||
@ -629,11 +633,12 @@ class InterestRateSwap() : Contract {
|
|||||||
inputs: List<State>,
|
inputs: List<State>,
|
||||||
outputs: List<State>,
|
outputs: List<State>,
|
||||||
commands: Collection<AuthenticatedObject<CommandData>>,
|
commands: Collection<AuthenticatedObject<CommandData>>,
|
||||||
token: String): Set<CommandData> {
|
token: UniqueIdentifier): Set<CommandData> {
|
||||||
val command = tx.commands.requireSingleCommand<Commands.Mature>()
|
val command = tx.commands.requireSingleCommand<Commands.Mature>()
|
||||||
val irs = inputs.filterIsInstance<State>().single()
|
val irs = inputs.filterIsInstance<State>().single()
|
||||||
requireThat {
|
requireThat {
|
||||||
"No more fixings to be applied" by (irs.calculation.nextFixingDate() == null)
|
"No more fixings to be applied" by (irs.calculation.nextFixingDate() == null)
|
||||||
|
"The irs is fully consumed and there is no id matched output state" by outputs.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
return setOf(command.value)
|
return setOf(command.value)
|
||||||
@ -656,11 +661,12 @@ class InterestRateSwap() : Contract {
|
|||||||
val fixedLeg: FixedLeg,
|
val fixedLeg: FixedLeg,
|
||||||
val floatingLeg: FloatingLeg,
|
val floatingLeg: FloatingLeg,
|
||||||
val calculation: Calculation,
|
val calculation: Calculation,
|
||||||
val common: Common
|
val common: Common,
|
||||||
|
override val linearId: UniqueIdentifier = UniqueIdentifier(common.tradeID)
|
||||||
) : FixableDealState, SchedulableState {
|
) : FixableDealState, SchedulableState {
|
||||||
|
|
||||||
override val contract = IRS_PROGRAM_ID
|
override val contract = IRS_PROGRAM_ID
|
||||||
override val thread = SecureHash.sha256(common.tradeID)
|
|
||||||
override val ref = common.tradeID
|
override val ref = common.tradeID
|
||||||
|
|
||||||
override val participants: List<PublicKey>
|
override val participants: List<PublicKey>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.r3corda.contracts
|
package com.r3corda.contracts
|
||||||
|
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.node.services.testing.MockServices
|
import com.r3corda.core.node.services.testing.MockServices
|
||||||
import com.r3corda.core.seconds
|
import com.r3corda.core.seconds
|
||||||
import com.r3corda.core.testing.*
|
import com.r3corda.core.testing.*
|
||||||
@ -394,9 +395,10 @@ class IRSTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ensure failure occurs when there are inbound states for an agreement command`() {
|
fun `ensure failure occurs when there are inbound states for an agreement command`() {
|
||||||
|
val irs = singleIRS()
|
||||||
transaction {
|
transaction {
|
||||||
input() { singleIRS() }
|
input() { irs }
|
||||||
output("irs post agreement") { singleIRS() }
|
output("irs post agreement") { irs }
|
||||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||||
timestamp(TEST_TX_TIME)
|
timestamp(TEST_TX_TIME)
|
||||||
this `fails with` "There are no in states for an agreement"
|
this `fails with` "There are no in states for an agreement"
|
||||||
@ -665,10 +667,11 @@ class IRSTests {
|
|||||||
transaction("Agreement") {
|
transaction("Agreement") {
|
||||||
output("irs post agreement2") {
|
output("irs post agreement2") {
|
||||||
irs.copy(
|
irs.copy(
|
||||||
irs.fixedLeg,
|
linearId = UniqueIdentifier("t2"),
|
||||||
irs.floatingLeg,
|
fixedLeg = irs.fixedLeg,
|
||||||
irs.calculation,
|
floatingLeg = irs.floatingLeg,
|
||||||
irs.common.copy(tradeID = "t2")
|
calculation = irs.calculation,
|
||||||
|
common = irs.common.copy(tradeID = "t2")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||||
|
@ -434,4 +434,22 @@ data class Commodity(val symbol: String,
|
|||||||
fun getInstance(symbol: String): Commodity?
|
fun getInstance(symbol: String): Commodity?
|
||||||
= registry[symbol]
|
= registry[symbol]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides a truly unique identifier of a trade, state, or other business object.
|
||||||
|
* @param externalId If there is an existing weak identifer e.g. trade reference id.
|
||||||
|
* This should be set here the first time a UniqueIdentifier identifier is created as part of an issue,
|
||||||
|
* or ledger on-boarding activity. This ensure that the human readable identity is paired with the strong id.
|
||||||
|
* @param id Should never be set by user code and left as default initialised.
|
||||||
|
* So that the first time a state is issued this should be given a new UUID.
|
||||||
|
* Subsequent copies and evolutions of a state should just copy the externalId and Id fields unmodified.
|
||||||
|
*/
|
||||||
|
data class UniqueIdentifier(val externalId: String? = null, val id: UUID = UUID.randomUUID()) {
|
||||||
|
override fun toString(): String {
|
||||||
|
if (externalId != null) {
|
||||||
|
return "${externalId}_${id.toString()}"
|
||||||
|
}
|
||||||
|
return id.toString()
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,5 +1,7 @@
|
|||||||
package com.r3corda.core.contracts
|
package com.r3corda.core.contracts
|
||||||
|
|
||||||
|
import com.r3corda.core.contracts.clauses.MatchBehaviour
|
||||||
|
import com.r3corda.core.contracts.clauses.SingleClause
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.crypto.toStringShort
|
import com.r3corda.core.crypto.toStringShort
|
||||||
@ -196,16 +198,41 @@ data class ScheduledStateRef(val ref: StateRef, override val scheduledAt: Instan
|
|||||||
data class ScheduledActivity(val logicRef: ProtocolLogicRef, override val scheduledAt: Instant) : Scheduled
|
data class ScheduledActivity(val logicRef: ProtocolLogicRef, override val scheduledAt: Instant) : Scheduled
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A state that evolves by superseding itself, all of which share the common "thread".
|
* A state that evolves by superseding itself, all of which share the common "linearId".
|
||||||
*
|
*
|
||||||
* This simplifies the job of tracking the current version of certain types of state in e.g. a wallet.
|
* This simplifies the job of tracking the current version of certain types of state in e.g. a wallet.
|
||||||
*/
|
*/
|
||||||
interface LinearState : ContractState {
|
interface LinearState: ContractState {
|
||||||
/** Unique thread id within the wallets of all parties */
|
/**
|
||||||
val thread: SecureHash
|
* Unique id shared by all LinearState states throughout history within the wallets of all parties.
|
||||||
|
* Verify methods should check that one input and one output share the id in a transaction,
|
||||||
|
* except at issuance/termination.
|
||||||
|
*/
|
||||||
|
val linearId: UniqueIdentifier
|
||||||
|
|
||||||
/** true if this should be tracked by our wallet(s) */
|
/**
|
||||||
|
* True if this should be tracked by our wallet(s).
|
||||||
|
* */
|
||||||
fun isRelevant(ourKeys: Set<PublicKey>): Boolean
|
fun isRelevant(ourKeys: Set<PublicKey>): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard clause to verify the LinearState safety properties.
|
||||||
|
*/
|
||||||
|
class ClauseVerifier<S: LinearState>(val stateClass: Class<S>) : SingleClause {
|
||||||
|
override val ifMatched = MatchBehaviour.CONTINUE
|
||||||
|
override val ifNotMatched = MatchBehaviour.ERROR
|
||||||
|
override val requiredCommands = emptySet<Class<out CommandData>>()
|
||||||
|
|
||||||
|
override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> {
|
||||||
|
val inputs = tx.inputs.filterIsInstance(stateClass)
|
||||||
|
val inputIds = inputs.map { it.linearId }.distinct()
|
||||||
|
require(inputIds.count() == inputs.count()) { "LinearStates cannot be merged" }
|
||||||
|
val outputs = tx.outputs.filterIsInstance(stateClass)
|
||||||
|
val outputIds = outputs.map { it.linearId }.distinct()
|
||||||
|
require(outputIds.count() == outputs.count()) { "LinearStates cannot be split" }
|
||||||
|
return emptySet()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SchedulableState : ContractState {
|
interface SchedulableState : ContractState {
|
||||||
|
@ -85,13 +85,13 @@ interface WalletService {
|
|||||||
/**
|
/**
|
||||||
* Returns a snapshot of the heads of LinearStates.
|
* Returns a snapshot of the heads of LinearStates.
|
||||||
*/
|
*/
|
||||||
val linearHeads: Map<SecureHash, StateAndRef<LinearState>>
|
val linearHeads: Map<UniqueIdentifier, StateAndRef<LinearState>>
|
||||||
|
|
||||||
// TODO: When KT-10399 is fixed, rename this and remove the inline version below.
|
// TODO: When KT-10399 is fixed, rename this and remove the inline version below.
|
||||||
|
|
||||||
/** Returns the [linearHeads] only when the type of the state would be considered an 'instanceof' the given type. */
|
/** Returns the [linearHeads] only when the type of the state would be considered an 'instanceof' the given type. */
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : LinearState> linearHeadsOfType_(stateType: Class<T>): Map<SecureHash, StateAndRef<T>> {
|
fun <T : LinearState> linearHeadsOfType_(stateType: Class<T>): Map<UniqueIdentifier, StateAndRef<T>> {
|
||||||
return linearHeads.filterValues { stateType.isInstance(it.state.data) }.mapValues { StateAndRef(it.value.state as TransactionState<T>, it.value.ref) }
|
return linearHeads.filterValues { stateType.isInstance(it.state.data) }.mapValues { StateAndRef(it.value.state as TransactionState<T>, it.value.ref) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,16 +2,30 @@ package com.r3corda.core.testing
|
|||||||
|
|
||||||
import com.r3corda.core.contracts.Contract
|
import com.r3corda.core.contracts.Contract
|
||||||
import com.r3corda.core.contracts.LinearState
|
import com.r3corda.core.contracts.LinearState
|
||||||
|
import com.r3corda.core.contracts.UniqueIdentifier
|
||||||
|
import com.r3corda.core.contracts.TransactionForContract
|
||||||
|
import com.r3corda.core.contracts.clauses.verifyClauses
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
|
||||||
class DummyLinearState(
|
class DummyLinearContract: Contract {
|
||||||
override val thread: SecureHash = SecureHash.randomSHA256(),
|
override val legalContractReference: SecureHash = SecureHash.sha256("Test")
|
||||||
override val contract: Contract = AlwaysSucceedContract(),
|
|
||||||
override val participants: List<PublicKey> = listOf(),
|
|
||||||
val nonce: SecureHash = SecureHash.randomSHA256()) : LinearState {
|
|
||||||
|
|
||||||
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
|
override fun verify(tx: TransactionForContract) {
|
||||||
return participants.any { ourKeys.contains(it) }
|
verifyClauses(tx,
|
||||||
|
listOf(LinearState.ClauseVerifier(State::class.java)),
|
||||||
|
emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class State(
|
||||||
|
override val linearId: UniqueIdentifier = UniqueIdentifier(),
|
||||||
|
override val contract: Contract = DummyLinearContract(),
|
||||||
|
override val participants: List<PublicKey> = listOf(),
|
||||||
|
val nonce: SecureHash = SecureHash.randomSHA256()) : LinearState {
|
||||||
|
|
||||||
|
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
|
||||||
|
return participants.any { ourKeys.contains(it) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,9 +22,6 @@ import javax.annotation.concurrent.ThreadSafe
|
|||||||
*/
|
*/
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
open class InMemoryWalletService(protected val services: ServiceHub) : SingletonSerializeAsToken(), WalletService {
|
open class InMemoryWalletService(protected val services: ServiceHub) : SingletonSerializeAsToken(), WalletService {
|
||||||
class ClashingThreads(threads: Set<SecureHash>, transactions: Iterable<WireTransaction>) :
|
|
||||||
Exception("There are multiple linear head states after processing transactions $transactions. The clashing thread(s): $threads")
|
|
||||||
|
|
||||||
open protected val log = loggerFor<InMemoryWalletService>()
|
open protected val log = loggerFor<InMemoryWalletService>()
|
||||||
|
|
||||||
// Variables inside InnerState are protected with a lock by the ThreadBox and aren't in scope unless you're
|
// Variables inside InnerState are protected with a lock by the ThreadBox and aren't in scope unless you're
|
||||||
@ -46,9 +43,9 @@ open class InMemoryWalletService(protected val services: ServiceHub) : Singleton
|
|||||||
/**
|
/**
|
||||||
* Returns a snapshot of the heads of LinearStates.
|
* Returns a snapshot of the heads of LinearStates.
|
||||||
*/
|
*/
|
||||||
override val linearHeads: Map<SecureHash, StateAndRef<LinearState>>
|
override val linearHeads: Map<UniqueIdentifier, StateAndRef<LinearState>>
|
||||||
get() = currentWallet.let { wallet ->
|
get() = currentWallet.let { wallet ->
|
||||||
wallet.states.filterStatesOfType<LinearState>().associateBy { it.state.data.thread }.mapValues { it.value }
|
wallet.states.filterStatesOfType<LinearState>().associateBy { it.state.data.linearId }.mapValues { it.value }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyAll(txns: Iterable<WireTransaction>): Wallet {
|
override fun notifyAll(txns: Iterable<WireTransaction>): Wallet {
|
||||||
@ -78,14 +75,6 @@ open class InMemoryWalletService(protected val services: ServiceHub) : Singleton
|
|||||||
Pair(wallet, combinedDelta)
|
Pair(wallet, combinedDelta)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: we need to remove the clashing threads concepts and support potential duplicate threads
|
|
||||||
// because two different nodes can have two different sets of threads and so currently it's possible
|
|
||||||
// for only one party to have a clash which interferes with determinism of the transactions.
|
|
||||||
val clashingThreads = walletAndNetDelta.first.clashingThreads
|
|
||||||
if (!clashingThreads.isEmpty()) {
|
|
||||||
throw ClashingThreads(clashingThreads, txns)
|
|
||||||
}
|
|
||||||
|
|
||||||
wallet = walletAndNetDelta.first
|
wallet = walletAndNetDelta.first
|
||||||
netDelta = walletAndNetDelta.second
|
netDelta = walletAndNetDelta.second
|
||||||
return@locked wallet
|
return@locked wallet
|
||||||
@ -133,23 +122,4 @@ open class InMemoryWalletService(protected val services: ServiceHub) : Singleton
|
|||||||
|
|
||||||
return Pair(Wallet(newStates), change)
|
return Pair(Wallet(newStates), change)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
// Returns the set of LinearState threads that clash in the wallet
|
|
||||||
val Wallet.clashingThreads: Set<SecureHash> get() {
|
|
||||||
val clashingThreads = HashSet<SecureHash>()
|
|
||||||
val threadsSeen = HashSet<SecureHash>()
|
|
||||||
for (linearState in states.filterStatesOfType<LinearState>()) {
|
|
||||||
val thread = linearState.state.data.thread
|
|
||||||
if (threadsSeen.contains(thread)) {
|
|
||||||
clashingThreads.add(thread)
|
|
||||||
} else {
|
|
||||||
threadsSeen.add(thread)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return clashingThreads
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -64,8 +64,8 @@ class Invoice : Contract {
|
|||||||
val owner: Party,
|
val owner: Party,
|
||||||
val buyer: Party,
|
val buyer: Party,
|
||||||
val assigned: Boolean,
|
val assigned: Boolean,
|
||||||
val props: InvoiceProperties
|
val props: InvoiceProperties,
|
||||||
|
override val linearId: UniqueIdentifier = UniqueIdentifier()
|
||||||
) : LinearState {
|
) : LinearState {
|
||||||
|
|
||||||
override val contract = INVOICE_PROGRAM_ID
|
override val contract = INVOICE_PROGRAM_ID
|
||||||
@ -84,8 +84,6 @@ class Invoice : Contract {
|
|||||||
// iterate over the goods list and sum up the price for each
|
// iterate over the goods list and sum up the price for each
|
||||||
val amount: Amount<Issued<Currency>> get() = props.amount
|
val amount: Amount<Issued<Currency>> get() = props.amount
|
||||||
|
|
||||||
override val thread = SecureHash.Companion.sha256(props.invoiceID)
|
|
||||||
|
|
||||||
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
|
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
|
||||||
return owner.owningKey in ourKeys || buyer.owningKey in ourKeys
|
return owner.owningKey in ourKeys || buyer.owningKey in ourKeys
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import com.r3corda.contracts.InterestRateSwap
|
|||||||
import com.r3corda.core.RunOnCallerThread
|
import com.r3corda.core.RunOnCallerThread
|
||||||
import com.r3corda.core.contracts.SignedTransaction
|
import com.r3corda.core.contracts.SignedTransaction
|
||||||
import com.r3corda.core.contracts.StateAndRef
|
import com.r3corda.core.contracts.StateAndRef
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.contracts.UniqueIdentifier
|
||||||
import com.r3corda.core.failure
|
import com.r3corda.core.failure
|
||||||
import com.r3corda.core.node.services.linearHeadsOfType
|
import com.r3corda.core.node.services.linearHeadsOfType
|
||||||
import com.r3corda.core.node.services.testing.MockIdentityService
|
import com.r3corda.core.node.services.testing.MockIdentityService
|
||||||
@ -80,7 +80,7 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
|
|||||||
val node1: SimulatedNode = banks[i]
|
val node1: SimulatedNode = banks[i]
|
||||||
val node2: SimulatedNode = banks[j]
|
val node2: SimulatedNode = banks[j]
|
||||||
|
|
||||||
val swaps: Map<SecureHash, StateAndRef<InterestRateSwap.State>> = node1.services.walletService.linearHeadsOfType<InterestRateSwap.State>()
|
val swaps: Map<UniqueIdentifier, StateAndRef<InterestRateSwap.State>> = node1.services.walletService.linearHeadsOfType<InterestRateSwap.State>()
|
||||||
val theDealRef: StateAndRef<InterestRateSwap.State> = swaps.values.single()
|
val theDealRef: StateAndRef<InterestRateSwap.State> = swaps.values.single()
|
||||||
|
|
||||||
// Do we have any more days left in this deal's lifetime? If not, return.
|
// Do we have any more days left in this deal's lifetime? If not, return.
|
||||||
|
@ -82,7 +82,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
|
|||||||
override val participants: List<PublicKey>
|
override val participants: List<PublicKey>
|
||||||
get() = throw UnsupportedOperationException()
|
get() = throw UnsupportedOperationException()
|
||||||
|
|
||||||
override val thread = SecureHash.sha256("does not matter but we need it to be unique ${Math.random()}")
|
override val linearId = UniqueIdentifier()
|
||||||
|
|
||||||
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean = true
|
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean = true
|
||||||
|
|
||||||
|
@ -104,56 +104,50 @@ class WalletWithCashTest {
|
|||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun branchingLinearStatesFails() {
|
fun branchingLinearStatesFailsToVerify() {
|
||||||
val freshKey = services.keyManagementService.freshKey()
|
val freshKey = services.keyManagementService.freshKey()
|
||||||
val thread = SecureHash.sha256("thread")
|
val linearId = UniqueIdentifier()
|
||||||
|
|
||||||
// Issue a linear state
|
// Issue a linear state
|
||||||
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
||||||
addOutputState(DummyLinearState(thread = thread, participants = listOf(freshKey.public)))
|
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshKey.public)))
|
||||||
signWith(freshKey)
|
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshKey.public)))
|
||||||
signWith(DUMMY_NOTARY_KEY)
|
|
||||||
}.toSignedTransaction()
|
|
||||||
|
|
||||||
wallet.notify(dummyIssue.tx)
|
|
||||||
assertEquals(1, wallet.currentWallet.states.toList().size)
|
|
||||||
|
|
||||||
// Issue another linear state of the same thread (nonce different)
|
|
||||||
val dummyIssue2 = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
|
||||||
addOutputState(DummyLinearState(thread = thread, participants = listOf(freshKey.public)))
|
|
||||||
signWith(freshKey)
|
signWith(freshKey)
|
||||||
signWith(DUMMY_NOTARY_KEY)
|
signWith(DUMMY_NOTARY_KEY)
|
||||||
}.toSignedTransaction()
|
}.toSignedTransaction()
|
||||||
|
|
||||||
assertThatThrownBy {
|
assertThatThrownBy {
|
||||||
wallet.notify(dummyIssue2.tx)
|
dummyIssue.toLedgerTransaction(services).verify()
|
||||||
}
|
}
|
||||||
assertEquals(1, wallet.currentWallet.states.toList().size)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun sequencingLinearStatesWorks() {
|
fun sequencingLinearStatesWorks() {
|
||||||
val freshKey = services.keyManagementService.freshKey()
|
val freshKey = services.keyManagementService.freshKey()
|
||||||
|
|
||||||
val thread = SecureHash.sha256("thread")
|
val linearId = UniqueIdentifier()
|
||||||
|
|
||||||
// Issue a linear state
|
// Issue a linear state
|
||||||
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
||||||
addOutputState(DummyLinearState(thread = thread, participants = listOf(freshKey.public)))
|
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshKey.public)))
|
||||||
signWith(freshKey)
|
signWith(freshKey)
|
||||||
signWith(DUMMY_NOTARY_KEY)
|
signWith(DUMMY_NOTARY_KEY)
|
||||||
}.toSignedTransaction()
|
}.toSignedTransaction()
|
||||||
|
|
||||||
|
dummyIssue.toLedgerTransaction(services).verify()
|
||||||
|
|
||||||
wallet.notify(dummyIssue.tx)
|
wallet.notify(dummyIssue.tx)
|
||||||
assertEquals(1, wallet.currentWallet.states.toList().size)
|
assertEquals(1, wallet.currentWallet.states.toList().size)
|
||||||
|
|
||||||
// Move the same state
|
// Move the same state
|
||||||
val dummyMove = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
val dummyMove = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
||||||
addOutputState(DummyLinearState(thread = thread, participants = listOf(freshKey.public)))
|
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshKey.public)))
|
||||||
addInputState(dummyIssue.tx.outRef<LinearState>(0))
|
addInputState(dummyIssue.tx.outRef<LinearState>(0))
|
||||||
signWith(DUMMY_NOTARY_KEY)
|
signWith(DUMMY_NOTARY_KEY)
|
||||||
}.toSignedTransaction()
|
}.toSignedTransaction()
|
||||||
|
|
||||||
|
dummyIssue.toLedgerTransaction(services).verify()
|
||||||
|
|
||||||
wallet.notify(dummyMove.tx)
|
wallet.notify(dummyMove.tx)
|
||||||
assertEquals(1, wallet.currentWallet.states.toList().size)
|
assertEquals(1, wallet.currentWallet.states.toList().size)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user