mirror of
https://github.com/corda/corda.git
synced 2024-12-19 04:57:58 +00:00
Fix double spending of inputs issue on ledger level in test dsl. (#15)
* Fix double spending of inputs issue on ledger level in test dsl. * Address PR comments.
This commit is contained in:
parent
365bc58fc2
commit
036b1b4964
@ -107,7 +107,6 @@ sealed class TransactionVerificationException(val tx: LedgerTransaction, cause:
|
|||||||
override val message: String?
|
override val message: String?
|
||||||
get() = "Missing required encumbrance ${missing} in ${inOut}"
|
get() = "Missing required encumbrance ${missing} in ${inOut}"
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Direction {
|
enum class Direction {
|
||||||
INPUT,
|
INPUT,
|
||||||
OUTPUT
|
OUTPUT
|
||||||
|
@ -717,4 +717,39 @@ class CashTests {
|
|||||||
// Test that summing everything fails because we're mixing units
|
// Test that summing everything fails because we're mixing units
|
||||||
states.sumCash()
|
states.sumCash()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Double spend.
|
||||||
|
@Test
|
||||||
|
fun chainCashDoubleSpendFailsWith() {
|
||||||
|
ledger {
|
||||||
|
unverifiedTransaction {
|
||||||
|
output("MEGA_CORP cash") {
|
||||||
|
Cash.State(
|
||||||
|
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||||
|
owner = MEGA_CORP_PUBKEY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction {
|
||||||
|
input("MEGA_CORP cash")
|
||||||
|
output("MEGA_CORP cash".output<Cash.State>().copy(owner = DUMMY_PUBKEY_1))
|
||||||
|
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||||
|
this.verifies()
|
||||||
|
}
|
||||||
|
|
||||||
|
tweak {
|
||||||
|
transaction {
|
||||||
|
input("MEGA_CORP cash")
|
||||||
|
// We send it to another pubkey so that the transaction is not identical to the previous one
|
||||||
|
output("MEGA_CORP cash".output<Cash.State>().copy(owner = ALICE_PUBKEY))
|
||||||
|
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||||
|
this.verifies()
|
||||||
|
}
|
||||||
|
this.fails()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.verifies()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,7 @@ sealed class EnforceVerifyOrFail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DuplicateOutputLabel(label: String) : Exception("Output label '$label' already used")
|
class DuplicateOutputLabel(label: String) : Exception("Output label '$label' already used")
|
||||||
|
class DoubleSpentInputs(ids: List<SecureHash>) : Exception("Transactions spend the same input. Conflicting transactions ids: '$ids'")
|
||||||
class AttachmentResolutionException(attachmentId: SecureHash) : Exception("Attachment with id $attachmentId not found")
|
class AttachmentResolutionException(attachmentId: SecureHash) : Exception("Attachment with id $attachmentId not found")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -167,7 +168,7 @@ data class TestLedgerDSLInterpreter private constructor (
|
|||||||
companion object {
|
companion object {
|
||||||
private fun getCallerLocation(): String? {
|
private fun getCallerLocation(): String? {
|
||||||
val stackTrace = Thread.currentThread().stackTrace
|
val stackTrace = Thread.currentThread().stackTrace
|
||||||
for (i in 1 .. stackTrace.size) {
|
for (i in 1..stackTrace.size) {
|
||||||
val stackTraceElement = stackTrace[i]
|
val stackTraceElement = stackTrace[i]
|
||||||
if (!stackTraceElement.fileName.contains("DSL")) {
|
if (!stackTraceElement.fileName.contains("DSL")) {
|
||||||
return stackTraceElement.toString()
|
return stackTraceElement.toString()
|
||||||
@ -182,8 +183,10 @@ data class TestLedgerDSLInterpreter private constructor (
|
|||||||
val transaction: WireTransaction,
|
val transaction: WireTransaction,
|
||||||
val location: String?
|
val location: String?
|
||||||
)
|
)
|
||||||
|
|
||||||
class VerifiesFailed(transactionName: String, cause: Throwable) :
|
class VerifiesFailed(transactionName: String, cause: Throwable) :
|
||||||
Exception("Transaction ($transactionName) didn't verify: $cause", cause)
|
Exception("Transaction ($transactionName) didn't verify: $cause", cause)
|
||||||
|
|
||||||
class TypeMismatch(requested: Class<*>, actual: Class<*>) :
|
class TypeMismatch(requested: Class<*>, actual: Class<*>) :
|
||||||
Exception("Actual type $actual is not a subtype of requested type $requested")
|
Exception("Actual type $actual is not a subtype of requested type $requested")
|
||||||
|
|
||||||
@ -249,7 +252,6 @@ data class TestLedgerDSLInterpreter private constructor (
|
|||||||
}
|
}
|
||||||
labelToOutputStateAndRefs[label] = wireTransaction.outRef(index)
|
labelToOutputStateAndRefs[label] = wireTransaction.outRef(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionMap[wireTransaction.id] =
|
transactionMap[wireTransaction.id] =
|
||||||
WireTransactionWithLocation(transactionLabel, wireTransaction, transactionLocation)
|
WireTransactionWithLocation(transactionLabel, wireTransaction, transactionLocation)
|
||||||
|
|
||||||
@ -279,10 +281,20 @@ data class TestLedgerDSLInterpreter private constructor (
|
|||||||
|
|
||||||
override fun verifies(): EnforceVerifyOrFail {
|
override fun verifies(): EnforceVerifyOrFail {
|
||||||
try {
|
try {
|
||||||
|
val usedInputs = mutableSetOf<StateRef>()
|
||||||
services.recordTransactions(transactionsUnverified.map { SignedTransaction(it.serialized, listOf(NullSignature), it.id) })
|
services.recordTransactions(transactionsUnverified.map { SignedTransaction(it.serialized, listOf(NullSignature), it.id) })
|
||||||
for ((key, value) in transactionWithLocations) {
|
for ((key, value) in transactionWithLocations) {
|
||||||
value.transaction.toLedgerTransaction(services).verify()
|
val wtx = value.transaction
|
||||||
services.recordTransactions(SignedTransaction(value.transaction.serialized, listOf(NullSignature), value.transaction.id))
|
val ltx = wtx.toLedgerTransaction(services)
|
||||||
|
ltx.verify()
|
||||||
|
val doubleSpend = wtx.inputs.intersect(usedInputs)
|
||||||
|
if (!doubleSpend.isEmpty()) {
|
||||||
|
val txIds = mutableListOf(wtx.id)
|
||||||
|
doubleSpend.mapTo(txIds) { it.txhash }
|
||||||
|
throw DoubleSpentInputs(txIds)
|
||||||
|
}
|
||||||
|
usedInputs.addAll(wtx.inputs)
|
||||||
|
services.recordTransactions(SignedTransaction(wtx.serialized, listOf(NullSignature), wtx.id))
|
||||||
}
|
}
|
||||||
return EnforceVerifyOrFail.Token
|
return EnforceVerifyOrFail.Token
|
||||||
} catch (exception: TransactionVerificationException) {
|
} catch (exception: TransactionVerificationException) {
|
||||||
|
Loading…
Reference in New Issue
Block a user