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:
Andrius Dagys
2016-06-07 10:42:50 +01:00
parent 3b1e020082
commit 70495a021e
48 changed files with 482 additions and 449 deletions

View File

@ -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).

View File

@ -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)
}

View File

@ -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()

View File

@ -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"

View File

@ -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))
}

View File

@ -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
}
}

View File

@ -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)

View File

@ -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.

View File

@ -99,6 +99,5 @@
"dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360",
"tradeID": "tradeXXX",
"hashLegalDocs": "put hash here"
},
"notary": "Notary Service"
}
}

View File

@ -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)

View File

@ -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))
}

View File

@ -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])

View File

@ -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())
}

View File

@ -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('$', '.')

View File

@ -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)