Skip to content

Commit 98bddae

Browse files
authored
R3SOL-535 Extract verification logic to the ledger library (#6376)
Extract Block structural verification logic to the ledger library
1 parent 69bc093 commit 98bddae

File tree

15 files changed

+283
-177
lines changed

15 files changed

+283
-177
lines changed

components/ledger/ledger-verification/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ dependencies {
4848
implementation project(':libs:utilities')
4949
implementation project(':libs:virtual-node:sandbox-group-context')
5050
implementation project(':libs:membership:membership-common')
51+
implementation project(':libs:ledger-lib-verification')
5152

5253
runtimeOnly project(':components:virtual-node:sandbox-amqp')
5354
runtimeOnly project(':components:virtual-node:sandbox-json')

components/ledger/ledger-verification/src/main/kotlin/net/corda/ledger/verification/processor/impl/VerificationRequestHandlerImpl.kt

+14-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package net.corda.ledger.verification.processor.impl
22

33
import net.corda.flow.external.events.responses.factory.ExternalEventResponseFactory
4+
import net.corda.ledger.libs.verification.impl.UtxoLedgerTransactionVerifierImpl
45
import net.corda.ledger.utxo.data.transaction.TransactionVerificationResult
56
import net.corda.ledger.utxo.data.transaction.TransactionVerificationStatus
67
import net.corda.ledger.utxo.data.transaction.UtxoLedgerTransactionContainer
78
import net.corda.ledger.utxo.data.transaction.UtxoLedgerTransactionImpl
89
import net.corda.ledger.utxo.data.transaction.WrappedUtxoWireTransaction
9-
import net.corda.ledger.utxo.transaction.verifier.UtxoLedgerTransactionVerifier
10+
import net.corda.ledger.utxo.data.transaction.verifier.verifyMetadata
11+
import net.corda.ledger.utxo.transaction.verifier.verifyContracts
1012
import net.corda.ledger.verification.metrics.VerificationMetricsFactory
1113
import net.corda.ledger.verification.processor.VerificationRequestHandler
1214
import net.corda.ledger.verification.sandbox.impl.getSerializationService
@@ -40,12 +42,17 @@ class VerificationRequestHandlerImpl(private val responseFactory: ExternalEventR
4042
}
4143

4244
return try {
43-
UtxoLedgerTransactionVerifier(
44-
transactionFactory,
45-
transaction,
46-
injectorService,
47-
VerificationMetricsFactory(sandbox.virtualNodeContext.holdingIdentity),
48-
).verify()
45+
UtxoLedgerTransactionVerifierImpl(
46+
transaction
47+
) { utxoLedgerTransaction ->
48+
verifyMetadata(utxoLedgerTransaction.metadata)
49+
verifyContracts(
50+
transactionFactory,
51+
utxoLedgerTransaction,
52+
injectorService,
53+
VerificationMetricsFactory(sandbox.virtualNodeContext.holdingIdentity)
54+
)
55+
}.verify()
4956

5057
responseFactory.success(
5158
request.flowExternalEventContext,

libs/ledger-lib-utxo-flow/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ dependencies {
1717
implementation project(':libs:ledger:ledger-utxo-transaction-verifier')
1818
implementation project(':libs:serialization:json-validator')
1919
implementation project(':libs:serialization:json-validator-lib')
20+
implementation project(':libs:ledger-lib-verification')
2021

2122
implementation 'net.corda:corda-ledger-utxo'
2223
implementation 'net.corda:corda-base'

libs/ledger-lib-utxo-flow/src/main/kotlin/net/corda/ledger/lib/utxo/flow/impl/transaction/verifier/UtxoTransactionBuilderVerifier.kt

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package net.corda.ledger.lib.utxo.flow.impl.transaction.verifier
22

33
import net.corda.ledger.lib.utxo.flow.impl.transaction.UtxoTransactionBuilderInternal
4-
import net.corda.ledger.utxo.transaction.verifier.UtxoTransactionVerifier
4+
import net.corda.ledger.libs.verification.UtxoTransactionVerifier
5+
import net.corda.ledger.libs.verification.impl.VerificationUtils.verifyCommands
6+
import net.corda.ledger.libs.verification.impl.VerificationUtils.verifyInputsAndOutputs
7+
import net.corda.ledger.libs.verification.impl.VerificationUtils.verifyNoDuplicateInputsOrReferences
8+
import net.corda.ledger.libs.verification.impl.VerificationUtils.verifyNoInputAndReferenceOverlap
9+
import net.corda.ledger.libs.verification.impl.VerificationUtils.verifySignatories
510
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder
611

712
/**
@@ -11,11 +16,13 @@ import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder
1116
*/
1217
class UtxoTransactionBuilderVerifier(
1318
private val transactionBuilder: UtxoTransactionBuilderInternal
14-
) :
15-
UtxoTransactionVerifier() {
16-
override val subjectClass: String = UtxoTransactionBuilder::class.simpleName!!
19+
) : UtxoTransactionVerifier {
1720

18-
fun verify() {
21+
private companion object {
22+
val subjectClass: String = UtxoTransactionBuilder::class.simpleName!!
23+
}
24+
25+
override fun verify() {
1926
/**
2027
* These checks are unique to [UtxoTransactionBuilder].
2128
* The related fields are not nullable or do not exist in [UtxoLedgerTransaction].
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
plugins {
2+
id 'corda.common-publishing'
3+
id 'corda.common-library'
4+
id 'org.jetbrains.kotlin.jvm'
5+
}
6+
7+
description 'Corda Ledger verification library'
8+
9+
ext.cordaEnableFormatting = true
10+
11+
dependencies {
12+
compileOnly 'org.jetbrains.kotlin:kotlin-stdlib'
13+
14+
compileOnly "org.osgi:osgi.annotation"
15+
compileOnly "co.paralleluniverse:quasar-osgi-annotations:$quasarVersion"
16+
17+
implementation 'net.corda:corda-ledger-utxo'
18+
19+
implementation platform("net.corda:corda-api:$cordaApiVersion")
20+
21+
testImplementation project(":libs:crypto:crypto-core")
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@Export
2+
@QuasarIgnoreAllPackages
3+
package net.corda.ledger.libs.verification.impl;
4+
5+
import co.paralleluniverse.quasar.annotations.QuasarIgnoreAllPackages;
6+
import org.osgi.annotation.bundle.Export;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@Export
2+
@QuasarIgnoreAllPackages
3+
package net.corda.ledger.libs.verification;
4+
5+
import co.paralleluniverse.quasar.annotations.QuasarIgnoreAllPackages;
6+
import org.osgi.annotation.bundle.Export;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package net.corda.ledger.libs.verification
2+
3+
/**
4+
* This interface is used to verify an Utxo Ledger Transaction or a transaction builder.
5+
* It has a single method called [verify] which contains all the logic needed to properly
6+
* verify the transaction or the builder.
7+
*/
8+
interface UtxoTransactionVerifier {
9+
fun verify()
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package net.corda.ledger.libs.verification.impl
2+
3+
import net.corda.ledger.libs.verification.UtxoTransactionVerifier
4+
import net.corda.ledger.libs.verification.impl.VerificationUtils.verifyCommands
5+
import net.corda.ledger.libs.verification.impl.VerificationUtils.verifyInputsAndOutputs
6+
import net.corda.ledger.libs.verification.impl.VerificationUtils.verifyNoDuplicateInputsOrReferences
7+
import net.corda.ledger.libs.verification.impl.VerificationUtils.verifyNoInputAndReferenceOverlap
8+
import net.corda.ledger.libs.verification.impl.VerificationUtils.verifySignatories
9+
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction
10+
11+
class UtxoLedgerTransactionVerifierImpl(
12+
private val transaction: UtxoLedgerTransaction,
13+
private val verify: (UtxoLedgerTransaction) -> Unit,
14+
) : UtxoTransactionVerifier {
15+
16+
override fun verify() {
17+
verifySignatories(transaction.signatories)
18+
verifyInputsAndOutputs(transaction.inputStateRefs, transaction.outputContractStates)
19+
verifyNoDuplicateInputsOrReferences(transaction.inputStateRefs, transaction.referenceStateRefs)
20+
verifyNoInputAndReferenceOverlap(transaction.inputStateRefs, transaction.referenceStateRefs)
21+
verifyCommands(transaction.commands)
22+
23+
verifyInputNotaries()
24+
verifyInputsAreOlderThanOutputs()
25+
26+
verify(transaction)
27+
}
28+
29+
private fun verifyInputNotaries() {
30+
val allInputs = transaction.inputTransactionStates + transaction.referenceTransactionStates
31+
if (allInputs.isEmpty()) {
32+
return
33+
}
34+
check(allInputs.map { it.notaryName }.distinct().size == 1) {
35+
"Input and reference states' notaries need to be the same."
36+
}
37+
check(allInputs.first().notaryName == transaction.notaryName) {
38+
"Input and reference states' notaries need to be the same as the transaction's notary."
39+
}
40+
}
41+
42+
private fun verifyInputsAreOlderThanOutputs() {
43+
// TODO CORE-8957 (needs to access the previous transactions from the backchain somehow)
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,25 @@
1-
package net.corda.ledger.utxo.transaction.verifier
1+
package net.corda.ledger.libs.verification.impl
22

33
import net.corda.v5.ledger.utxo.Command
44
import net.corda.v5.ledger.utxo.StateRef
55
import java.security.PublicKey
66

7-
/*
8-
* Shared verification for [UtxoTransactionBuilder] and [UtxoLedgerTransaction].
9-
*/
10-
abstract class UtxoTransactionVerifier {
11-
protected open val subjectClass: String = "transaction"
7+
object VerificationUtils {
128

13-
protected fun verifySignatories(signatories: List<PublicKey>) {
9+
fun verifySignatories(signatories: List<PublicKey>) {
1410
check(signatories.isNotEmpty()) {
15-
"At least one signatory signing key must be applied to the current $subjectClass" +
11+
"At least one signatory signing key must be applied to the current transaction" +
1612
" in order to create a signed transaction."
1713
}
1814
}
1915

20-
protected fun verifyInputsAndOutputs(inputStateRefs: List<StateRef>, outputStates: List<*>) {
16+
fun verifyInputsAndOutputs(inputStateRefs: List<StateRef>, outputStates: List<*>) {
2117
check(inputStateRefs.isNotEmpty() || outputStates.isNotEmpty()) {
22-
"At least one input state, or one output state must be applied to the current $subjectClass."
18+
"At least one input state, or one output state must be applied to the current transaction."
2319
}
2420
}
2521

26-
protected fun verifyNoDuplicateInputsOrReferences(inputStateRefs: List<StateRef>, referenceStateRefs: List<StateRef>) {
22+
fun verifyNoDuplicateInputsOrReferences(inputStateRefs: List<StateRef>, referenceStateRefs: List<StateRef>) {
2723
// The input states part of this check may be repeated later in
2824
// net.corda.ledger.utxo.transaction.verifier.UtxoTransactionEncumbranceVerifierKt
2925
// checkEncumbranceGroup if there is state encumbrance on this transaction.
@@ -36,17 +32,17 @@ abstract class UtxoTransactionVerifier {
3632
check(duplicateReferences.isEmpty()) { "Duplicate reference states detected: ${duplicateReferences.keys}" }
3733
}
3834

39-
protected fun verifyNoInputAndReferenceOverlap(inputStateRefs: List<StateRef>, referenceStateRefs: List<StateRef>) {
35+
fun verifyNoInputAndReferenceOverlap(inputStateRefs: List<StateRef>, referenceStateRefs: List<StateRef>) {
4036
val intersection = inputStateRefs intersect referenceStateRefs.toSet()
4137
check(intersection.isEmpty()) {
4238
"A state cannot be both an input and a reference input in the same transaction. Offending " +
4339
"states: $intersection"
4440
}
4541
}
4642

47-
protected fun verifyCommands(commands: List<Command>) {
43+
fun verifyCommands(commands: List<Command>) {
4844
check(commands.isNotEmpty()) {
49-
"At least one command must be applied to the current $subjectClass."
45+
"At least one command must be applied to the current transaction."
5046
}
5147
}
5248
}
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,25 @@
1-
package net.corda.ledger.utxo.transaction.verifier
1+
package net.corda.ledger.libs.verification.impl
22

3-
import io.micrometer.core.instrument.Timer
43
import net.corda.crypto.core.SecureHashImpl
5-
import net.corda.ledger.common.testkit.anotherPublicKeyExample
6-
import net.corda.ledger.common.testkit.publicKeyExample
7-
import net.corda.ledger.utxo.data.state.StateAndRefImpl
8-
import net.corda.ledger.utxo.data.transaction.UtxoLedgerTransactionImpl
9-
import net.corda.ledger.utxo.testkit.anotherNotaryX500Name
10-
import net.corda.ledger.utxo.testkit.notaryX500Name
114
import net.corda.v5.base.types.MemberX500Name
12-
import net.corda.v5.crypto.SecureHash
135
import net.corda.v5.ledger.common.transaction.TransactionMetadata
146
import net.corda.v5.ledger.utxo.Command
15-
import net.corda.v5.ledger.utxo.Contract
167
import net.corda.v5.ledger.utxo.ContractState
17-
import net.corda.v5.ledger.utxo.ContractVerificationException
18-
import net.corda.v5.ledger.utxo.EncumbranceGroup
19-
import net.corda.v5.ledger.utxo.StateAndRef
208
import net.corda.v5.ledger.utxo.StateRef
219
import net.corda.v5.ledger.utxo.TransactionState
2210
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction
2311
import org.assertj.core.api.Assertions.assertThatThrownBy
2412
import org.junit.jupiter.api.BeforeEach
2513
import org.junit.jupiter.api.Test
2614
import org.junit.jupiter.api.assertDoesNotThrow
27-
import org.mockito.kotlin.any
28-
import org.mockito.kotlin.argumentCaptor
29-
import org.mockito.kotlin.doAnswer
30-
import org.mockito.kotlin.doReturn
3115
import org.mockito.kotlin.mock
3216
import org.mockito.kotlin.whenever
17+
import java.security.KeyPair
18+
import java.security.KeyPairGenerator
3319
import java.security.PublicKey
34-
import java.util.concurrent.Callable
20+
import java.security.spec.ECGenParameterSpec
3521

36-
class UtxoLedgerTransactionVerifierTest {
22+
class UtxoLedgerTransactionVerifierImplTest {
3723

3824
private val transaction = mock<UtxoLedgerTransaction>()
3925
private val signatory = mock<PublicKey>()
@@ -43,23 +29,26 @@ class UtxoLedgerTransactionVerifierTest {
4329
private val inputTransactionState = mock<TransactionState<ContractState>>()
4430
private val referenceTransactionState = mock<TransactionState<ContractState>>()
4531
private val metadata = mock<TransactionMetadata>()
46-
private val injectionService = mock<(Contract) -> Unit>()
47-
private val callable = argumentCaptor<Callable<Any>>()
48-
private val timer = mock<Timer> {
49-
on { recordCallable(callable.capture()) } doAnswer { callable.lastValue.call() }
32+
33+
private val verifier = UtxoLedgerTransactionVerifierImpl(transaction) {
34+
// NO-OP contract verification
5035
}
51-
private val metricFactory = mock<ContractVerificationMetricFactory> {
52-
on { getContractVerificationTimeMetric() } doReturn timer
53-
on { getContractVerificationContractCountMetric() } doReturn mock()
54-
on { getContractVerificationContractTime(any()) } doReturn timer
36+
37+
private companion object {
38+
val notaryX500Name = MemberX500Name.parse("O=ExampleNotaryService, L=London, C=GB")
39+
val anotherNotaryX500Name = MemberX500Name.parse("O=AnotherExampleNotaryService, L=London, C=GB")
40+
41+
val kpg = KeyPairGenerator.getInstance("EC")
42+
.apply { initialize(ECGenParameterSpec("secp256r1")) }
43+
44+
val keyPairExample: KeyPair = kpg.generateKeyPair()
45+
val publicKeyExample: PublicKey = keyPairExample.public
46+
val anotherPublicKeyExample: PublicKey = kpg
47+
.generateKeyPair().public
5548
}
56-
private val verifier = UtxoLedgerTransactionVerifier({ transaction }, transaction, injectionService, metricFactory)
5749

5850
@BeforeEach
5951
fun beforeEach() {
60-
whenever(metadata.getLedgerModel()).thenReturn(UtxoLedgerTransactionImpl::class.java.name)
61-
whenever(metadata.getTransactionSubtype()).thenReturn("GENERAL")
62-
6352
whenever(transaction.id).thenReturn(SecureHashImpl("SHA", byteArrayOf(1, 1, 1, 1)))
6453
whenever(transaction.signatories).thenReturn(listOf(signatory))
6554
whenever(transaction.inputStateRefs).thenReturn(listOf(stateRef))
@@ -169,7 +158,7 @@ class UtxoLedgerTransactionVerifierTest {
169158
whenever(referenceTransactionState.notaryName).thenReturn(anotherNotaryX500Name)
170159
assertThatThrownBy { verifier.verify() }
171160
.isExactlyInstanceOf(IllegalStateException::class.java)
172-
.hasMessageContaining("Input and reference states' notaries need to be the same as the UtxoLedgerTransaction's notary")
161+
.hasMessageContaining("Input and reference states' notaries need to be the same as the transaction's notary")
173162
}
174163

175164
@Test
@@ -183,58 +172,4 @@ class UtxoLedgerTransactionVerifierTest {
183172
fun `throws an exception if input states are older than output states`() {
184173
// TODO CORE-8957 (needs to access the previous transactions from the backchain somehow)
185174
}
186-
187-
@Test
188-
fun `catches exceptions from contract verification and outputs them as failure reasons`() {
189-
val validContractAState = stateAndRef<MyInvalidContractA>(
190-
SecureHashImpl("SHA", byteArrayOf(1, 1, 1, 1)),
191-
0
192-
)
193-
whenever(transaction.inputStateAndRefs).thenReturn(listOf(validContractAState))
194-
whenever(transaction.outputStateAndRefs).thenReturn(emptyList())
195-
assertThatThrownBy { verifier.verify() }
196-
.isExactlyInstanceOf(ContractVerificationException::class.java)
197-
.hasMessageContainingAll("I have failed")
198-
}
199-
200-
private inline fun <reified C : Contract> stateAndRef(
201-
transactionId: SecureHash,
202-
index: Int
203-
): StateAndRef<UtxoLedgerTransactionContractVerifierTest.MyState> {
204-
val state = UtxoLedgerTransactionContractVerifierTest.MyState()
205-
return StateAndRefImpl(
206-
object : TransactionState<UtxoLedgerTransactionContractVerifierTest.MyState> {
207-
override fun getContractState(): UtxoLedgerTransactionContractVerifierTest.MyState {
208-
return state
209-
}
210-
211-
override fun getContractStateType(): Class<UtxoLedgerTransactionContractVerifierTest.MyState> {
212-
return state.javaClass
213-
}
214-
215-
override fun getContractType(): Class<out Contract> {
216-
return C::class.java
217-
}
218-
219-
override fun getNotaryName(): MemberX500Name {
220-
return notaryX500Name
221-
}
222-
223-
override fun getNotaryKey(): PublicKey {
224-
return publicKeyExample
225-
}
226-
227-
override fun getEncumbranceGroup(): EncumbranceGroup? {
228-
return null
229-
}
230-
},
231-
StateRef(transactionId, index)
232-
)
233-
}
234-
235-
class MyInvalidContractA : Contract {
236-
override fun verify(transaction: UtxoLedgerTransaction) {
237-
throw IllegalStateException("I have failed")
238-
}
239-
}
240175
}

libs/ledger/ledger-utxo-transaction-verifier/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ dependencies {
1717

1818
implementation project(':libs:ledger:ledger-common-data')
1919
implementation project(':libs:ledger:ledger-utxo-data')
20+
testImplementation project(':libs:ledger-lib-verification')
2021
implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
2122
api(libs.micrometer.core) {
2223
// we don't need these in classpath, so excluding them to reduce dependencies.

0 commit comments

Comments
 (0)