mirror of
https://github.com/corda/corda.git
synced 2025-06-22 09:08:49 +00:00
Introduce TransactionState, which wraps ContractState and holds the notary pointer.
Remove notary from ContractState. Introduce TransactionType, which specifies custom validation logic for a transaction.
This commit is contained in:
@ -1,10 +1,7 @@
|
||||
package com.r3corda.node.api
|
||||
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.node.api.StatesQuery
|
||||
import com.r3corda.core.contracts.ContractState
|
||||
import com.r3corda.core.contracts.SignedTransaction
|
||||
import com.r3corda.core.contracts.StateRef
|
||||
import com.r3corda.core.contracts.WireTransaction
|
||||
import com.r3corda.core.crypto.DigitalSignature
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.serialization.SerializedBytes
|
||||
@ -43,7 +40,7 @@ interface APIServer {
|
||||
*/
|
||||
fun queryStates(query: StatesQuery): List<StateRef>
|
||||
|
||||
fun fetchStates(states: List<StateRef>): Map<StateRef, ContractState?>
|
||||
fun fetchStates(states: List<StateRef>): Map<StateRef, TransactionState<ContractState>?>
|
||||
|
||||
/**
|
||||
* Query for immutable transactions (results can be cached indefinitely by their id/hash).
|
||||
|
@ -27,7 +27,7 @@ class APIServerImpl(val node: AbstractNode) : APIServer {
|
||||
return states.values.map { it.ref }
|
||||
} else if (query.criteria is StatesQuery.Criteria.Deal) {
|
||||
val states = node.services.walletService.linearHeadsOfType<DealState>().filterValues {
|
||||
it.state.ref == query.criteria.ref
|
||||
it.state.data.ref == query.criteria.ref
|
||||
}
|
||||
return states.values.map { it.ref }
|
||||
}
|
||||
@ -35,7 +35,7 @@ class APIServerImpl(val node: AbstractNode) : APIServer {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun fetchStates(states: List<StateRef>): Map<StateRef, ContractState?> {
|
||||
override fun fetchStates(states: List<StateRef>): Map<StateRef, TransactionState<ContractState>?> {
|
||||
return node.services.walletService.statesForRefs(states)
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,6 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
|
||||
lateinit var smm: StateMachineManager
|
||||
lateinit var wallet: WalletService
|
||||
lateinit var keyManagement: E2ETestKeyManagementService
|
||||
lateinit var notaryChangeService: NotaryChangeService
|
||||
var inNodeNetworkMapService: NetworkMapService? = null
|
||||
var inNodeNotaryService: NotaryService? = null
|
||||
lateinit var identity: IdentityService
|
||||
@ -130,9 +129,6 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
|
||||
net = makeMessagingService()
|
||||
wallet = NodeWalletService(services)
|
||||
makeInterestRatesOracleService()
|
||||
api = APIServerImpl(this)
|
||||
notaryChangeService = NotaryChangeService(net, smm)
|
||||
|
||||
identity = makeIdentityService()
|
||||
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
|
||||
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
|
||||
@ -144,6 +140,7 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
|
||||
// This object doesn't need to be referenced from this class because it registers handlers on the network
|
||||
// service and so that keeps it from being collected.
|
||||
DataVendingService(net, storage)
|
||||
NotaryChangeService(net, smm)
|
||||
|
||||
buildAdvertisedServices()
|
||||
|
||||
|
@ -85,7 +85,7 @@ class IRSSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork
|
||||
val theDealRef: StateAndRef<InterestRateSwap.State> = swaps.values.single()
|
||||
|
||||
// Do we have any more days left in this deal's lifetime? If not, return.
|
||||
val nextFixingDate = theDealRef.state.calculation.nextFixingDate() ?: return null
|
||||
val nextFixingDate = theDealRef.state.data.calculation.nextFixingDate() ?: return null
|
||||
extraNodeLabels[node1] = "Fixing event on $nextFixingDate"
|
||||
extraNodeLabels[node2] = "Fixing event on $nextFixingDate"
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.r3corda.node.internal.testing
|
||||
|
||||
import com.r3corda.core.contracts.StateAndRef
|
||||
import com.r3corda.core.contracts.DummyContract
|
||||
import com.r3corda.core.contracts.StateRef
|
||||
import com.r3corda.core.crypto.Party
|
||||
@ -10,20 +11,20 @@ import com.r3corda.node.internal.AbstractNode
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
fun issueState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateRef {
|
||||
fun issueState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateAndRef<*> {
|
||||
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary)
|
||||
tx.signWith(node.storage.myLegalIdentityKey)
|
||||
tx.signWith(DUMMY_NOTARY_KEY)
|
||||
val stx = tx.toSignedTransaction()
|
||||
node.services.recordTransactions(listOf(stx))
|
||||
return StateRef(stx.id, 0)
|
||||
return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
|
||||
}
|
||||
|
||||
fun issueInvalidState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateRef {
|
||||
fun issueInvalidState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateAndRef<*> {
|
||||
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary)
|
||||
tx.setTime(Instant.now(), notary, 30.seconds)
|
||||
tx.signWith(node.storage.myLegalIdentityKey)
|
||||
val stx = tx.toSignedTransaction(false)
|
||||
node.services.recordTransactions(listOf(stx))
|
||||
return StateRef(stx.id, 0)
|
||||
return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.r3corda.node.services
|
||||
|
||||
import com.r3corda.contracts.cash.Cash
|
||||
import com.r3corda.core.messaging.MessagingService
|
||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||
import com.r3corda.node.services.api.AbstractNodeService
|
||||
@ -41,13 +40,14 @@ class NotaryChangeService(net: MessagingService, val smm: StateMachineManager) :
|
||||
* TODO: In more difficult cases this should call for human attention to manually verify and approve the proposal
|
||||
*/
|
||||
private fun checkProposal(proposal: NotaryChangeProtocol.Proposal): Boolean {
|
||||
val state = smm.serviceHub.loadState(proposal.stateRef)
|
||||
if (state is Cash.State) return false // TODO: delete this example check
|
||||
|
||||
val newNotary = proposal.newNotary
|
||||
val isNotary = smm.serviceHub.networkMapCache.notaryNodes.any { it.identity == newNotary }
|
||||
require(isNotary) { "The proposed node $newNotary does not run a Notary service " }
|
||||
|
||||
// An example requirement
|
||||
val blacklist = listOf("Evil Notary")
|
||||
require(!blacklist.contains(newNotary.name))
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ class NodeWalletService(private val services: ServiceHubInternal) : SingletonSer
|
||||
*/
|
||||
override val linearHeads: Map<SecureHash, StateAndRef<LinearState>>
|
||||
get() = mutex.locked { wallet }.let { wallet ->
|
||||
wallet.states.filterStatesOfType<LinearState>().associateBy { it.state.thread }.mapValues { it.value }
|
||||
wallet.states.filterStatesOfType<LinearState>().associateBy { it.state.data.thread }.mapValues { it.value }
|
||||
}
|
||||
|
||||
override fun notifyAll(txns: Iterable<WireTransaction>): Wallet {
|
||||
@ -103,8 +103,8 @@ class NodeWalletService(private val services: ServiceHubInternal) : SingletonSer
|
||||
|
||||
private fun Wallet.update(tx: WireTransaction, ourKeys: Set<PublicKey>): Pair<Wallet, Wallet.Update> {
|
||||
val ourNewStates = tx.outputs.
|
||||
filter { isRelevant(it, ourKeys) }.
|
||||
map { tx.outRef<ContractState>(it) }
|
||||
filter { isRelevant(it.data, ourKeys) }.
|
||||
map { tx.outRef<ContractState>(it.data) }
|
||||
|
||||
// Now calculate the states that are being spent by this transaction.
|
||||
val consumed: Set<StateRef> = states.map { it.ref }.intersect(tx.inputs)
|
||||
|
@ -24,7 +24,7 @@ class WalletImpl(override val states: List<StateAndRef<ContractState>>) : Wallet
|
||||
*/
|
||||
override val cashBalances: Map<Currency, Amount<Currency>> get() = states.
|
||||
// Select the states we own which are cash, ignore the rest, take the amounts.
|
||||
mapNotNull { (it.state as? Cash.State)?.amount }.
|
||||
mapNotNull { (it.state.data as? Cash.State)?.amount }.
|
||||
// Turn into a Map<Currency, List<Amount>> like { GBP -> (£100, £500, etc), USD -> ($2000, $50) }
|
||||
groupBy { it.token.product }.
|
||||
// Collapse to Map<Currency, Amount> by summing all the amounts of the same currency together.
|
||||
|
@ -99,6 +99,5 @@
|
||||
"dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360",
|
||||
"tradeID": "tradeXXX",
|
||||
"hashLegalDocs": "put hash here"
|
||||
},
|
||||
"notary": "Notary Service"
|
||||
}
|
||||
}
|
||||
|
@ -468,7 +468,7 @@ class TwoPartyTradeProtocolTests {
|
||||
attachmentID: SecureHash?): Pair<Wallet, List<WireTransaction>> {
|
||||
val ap = transaction {
|
||||
output("alice's paper") {
|
||||
CommercialPaper.State(MEGA_CORP.ref(1, 2, 3), owner, amount, TEST_TX_TIME + 7.days, notary)
|
||||
CommercialPaper.State(MEGA_CORP.ref(1, 2, 3), owner, amount, TEST_TX_TIME + 7.days)
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
|
||||
if (!withError)
|
||||
|
@ -4,6 +4,7 @@ import com.r3corda.contracts.cash.Cash
|
||||
import com.r3corda.contracts.testing.CASH
|
||||
import com.r3corda.contracts.testing.`issued by`
|
||||
import com.r3corda.contracts.testing.`owned by`
|
||||
import com.r3corda.contracts.testing.`with notary`
|
||||
import com.r3corda.core.bd
|
||||
import com.r3corda.core.contracts.DOLLARS
|
||||
import com.r3corda.core.contracts.Fix
|
||||
@ -11,6 +12,7 @@ import com.r3corda.core.contracts.TransactionBuilder
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.generateKeyPair
|
||||
import com.r3corda.core.testing.ALICE_PUBKEY
|
||||
import com.r3corda.core.testing.DUMMY_NOTARY
|
||||
import com.r3corda.core.testing.MEGA_CORP
|
||||
import com.r3corda.core.testing.MEGA_CORP_KEY
|
||||
import com.r3corda.core.utilities.BriefLogFormatter
|
||||
@ -117,5 +119,5 @@ class NodeInterestRatesTest {
|
||||
assertEquals("0.678".bd, fix.value)
|
||||
}
|
||||
|
||||
private fun makeTX() = TransactionBuilder(outputs = mutableListOf(1000.DOLLARS.CASH `issued by` DUMMY_CASH_ISSUER `owned by` ALICE_PUBKEY))
|
||||
private fun makeTX() = TransactionBuilder(outputs = mutableListOf(1000.DOLLARS.CASH `issued by` DUMMY_CASH_ISSUER `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY))
|
||||
}
|
@ -51,14 +51,14 @@ class NodeWalletServiceTest {
|
||||
val w = wallet.currentWallet
|
||||
assertEquals(3, w.states.size)
|
||||
|
||||
val state = w.states[0].state as Cash.State
|
||||
val state = w.states[0].state.data as Cash.State
|
||||
val myIdentity = services.storageService.myLegalIdentity
|
||||
val myPartyRef = myIdentity.ref(ref)
|
||||
assertEquals(29.01.DOLLARS `issued by` myPartyRef, state.amount)
|
||||
assertEquals(ALICE_PUBKEY, state.owner)
|
||||
|
||||
assertEquals(33.34.DOLLARS `issued by` myPartyRef, (w.states[2].state as Cash.State).amount)
|
||||
assertEquals(35.61.DOLLARS `issued by` myPartyRef, (w.states[1].state as Cash.State).amount)
|
||||
assertEquals(33.34.DOLLARS `issued by` myPartyRef, (w.states[2].state.data as Cash.State).amount)
|
||||
assertEquals(35.61.DOLLARS `issued by` myPartyRef, (w.states[1].state.data as Cash.State).amount)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -77,12 +77,14 @@ class NodeWalletServiceTest {
|
||||
val spendTX = TransactionBuilder().apply {
|
||||
Cash().generateSpend(this, 80.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_PUBKEY, listOf(myOutput))
|
||||
signWith(freshKey)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction()
|
||||
|
||||
// A tx that doesn't send us anything.
|
||||
val irrelevantTX = TransactionBuilder().apply {
|
||||
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_KEY.public, DUMMY_NOTARY)
|
||||
signWith(MEGA_CORP_KEY)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction()
|
||||
|
||||
assertNull(wallet.cashBalances[USD])
|
||||
|
@ -1,6 +1,8 @@
|
||||
package com.r3corda.node.services
|
||||
|
||||
import com.r3corda.core.contracts.TransactionBuilder
|
||||
import com.r3corda.core.contracts.StateRef
|
||||
import com.r3corda.core.contracts.TransactionType
|
||||
import com.r3corda.core.contracts.WireTransaction
|
||||
import com.r3corda.core.node.services.UniquenessException
|
||||
import com.r3corda.core.testing.MEGA_CORP
|
||||
import com.r3corda.core.testing.generateStateRef
|
||||
@ -15,7 +17,8 @@ class UniquenessProviderTests {
|
||||
@Test fun `should commit a transaction with unused inputs without exception`() {
|
||||
val provider = InMemoryUniquenessProvider()
|
||||
val inputState = generateStateRef()
|
||||
val tx = TransactionBuilder().withItems(inputState).toWireTransaction()
|
||||
val tx = buildTransaction(inputState)
|
||||
|
||||
provider.commit(tx, identity)
|
||||
}
|
||||
|
||||
@ -23,10 +26,10 @@ class UniquenessProviderTests {
|
||||
val provider = InMemoryUniquenessProvider()
|
||||
val inputState = generateStateRef()
|
||||
|
||||
val tx1 = TransactionBuilder().withItems(inputState).toWireTransaction()
|
||||
val tx1 = buildTransaction(inputState)
|
||||
provider.commit(tx1, identity)
|
||||
|
||||
val tx2 = TransactionBuilder().withItems(inputState).toWireTransaction()
|
||||
val tx2 = buildTransaction(inputState)
|
||||
val ex = assertFailsWith<UniquenessException> { provider.commit(tx2, identity) }
|
||||
|
||||
val consumingTx = ex.error.stateHistory[inputState]!!
|
||||
@ -34,4 +37,6 @@ class UniquenessProviderTests {
|
||||
assertEquals(consumingTx.inputIndex, tx1.inputs.indexOf(inputState))
|
||||
assertEquals(consumingTx.requestingParty, identity)
|
||||
}
|
||||
|
||||
private fun buildTransaction(inputState: StateRef) = WireTransaction(listOf(inputState), emptyList(), emptyList(), emptyList(), TransactionType.Business())
|
||||
}
|
@ -2,6 +2,7 @@ package com.r3corda.node.visualiser
|
||||
|
||||
import com.r3corda.core.contracts.CommandData
|
||||
import com.r3corda.core.contracts.ContractState
|
||||
import com.r3corda.core.contracts.TransactionState
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.testing.TransactionGroupDSL
|
||||
import org.graphstream.graph.Edge
|
||||
@ -30,7 +31,7 @@ class GraphVisualiser(val dsl: TransactionGroupDSL<in ContractState>) {
|
||||
val node = graph.addNode<Node>(tx.outRef<ContractState>(outIndex).ref.toString())
|
||||
val state = tx.outputs[outIndex]
|
||||
node.label = stateToLabel(state)
|
||||
node.styleClass = stateToCSSClass(state) + ",state"
|
||||
node.styleClass = stateToCSSClass(state.data) + ",state"
|
||||
node.setAttribute("state", state)
|
||||
val edge = graph.addEdge<Edge>("tx$txIndex-out$outIndex", txNode, node, true)
|
||||
edge.weight = 0.7
|
||||
@ -55,8 +56,8 @@ class GraphVisualiser(val dsl: TransactionGroupDSL<in ContractState>) {
|
||||
return graph
|
||||
}
|
||||
|
||||
private fun stateToLabel(state: ContractState): String {
|
||||
return dsl.labelForState(state) ?: stateToTypeName(state)
|
||||
private fun stateToLabel(state: TransactionState<*>): String {
|
||||
return dsl.labelForState(state) ?: stateToTypeName(state.data)
|
||||
}
|
||||
|
||||
private fun commandToTypeName(state: CommandData) = state.javaClass.canonicalName.removePrefix("contracts.").replace('$', '.')
|
||||
|
@ -1,14 +1,12 @@
|
||||
package node.services
|
||||
|
||||
import com.r3corda.contracts.DummyContract
|
||||
import com.r3corda.core.contracts.StateAndRef
|
||||
import com.r3corda.core.contracts.StateRef
|
||||
import com.r3corda.core.contracts.TransactionBuilder
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.testing.DUMMY_NOTARY
|
||||
import com.r3corda.core.testing.DUMMY_NOTARY_KEY
|
||||
import com.r3corda.node.internal.testing.MockNetwork
|
||||
import com.r3corda.node.internal.testing.issueState
|
||||
import com.r3corda.node.services.transactions.NotaryService
|
||||
import com.r3corda.node.testutils.issueState
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import protocols.NotaryChangeProtocol
|
||||
@ -35,12 +33,12 @@ class NotaryChangeTests {
|
||||
|
||||
@Test
|
||||
fun `should change notary for a state with single participant`() {
|
||||
val state = issueState(clientNodeA, DUMMY_NOTARY)
|
||||
val ref = clientNodeA.services.loadState(state)
|
||||
val ref = issueState(clientNodeA, DUMMY_NOTARY).ref
|
||||
val state = clientNodeA.services.loadState(ref)
|
||||
|
||||
val newNotary = newNotaryNode.info.identity
|
||||
|
||||
val protocol = Instigator(StateAndRef(ref, state), newNotary)
|
||||
val protocol = Instigator(StateAndRef(state, ref), newNotary)
|
||||
val future = clientNodeA.smm.add(NotaryChangeProtocol.TOPIC_CHANGE, protocol)
|
||||
|
||||
net.runNetwork()
|
||||
@ -51,11 +49,10 @@ class NotaryChangeTests {
|
||||
|
||||
@Test
|
||||
fun `should change notary for a state with multiple participants`() {
|
||||
val state = DummyContract.MultiOwnerState(0,
|
||||
listOf(clientNodeA.info.identity.owningKey, clientNodeB.info.identity.owningKey),
|
||||
DUMMY_NOTARY)
|
||||
val state = TransactionState(DummyContract.MultiOwnerState(0,
|
||||
listOf(clientNodeA.info.identity.owningKey, clientNodeB.info.identity.owningKey)), DUMMY_NOTARY)
|
||||
|
||||
val tx = TransactionBuilder().withItems(state)
|
||||
val tx = TransactionBuilder(type = TransactionType.NotaryChange()).withItems(state)
|
||||
tx.signWith(clientNodeA.storage.myLegalIdentityKey)
|
||||
tx.signWith(clientNodeB.storage.myLegalIdentityKey)
|
||||
tx.signWith(DUMMY_NOTARY_KEY)
|
||||
|
Reference in New Issue
Block a user