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:
kasiastreich 2016-12-08 10:15:33 +00:00 committed by GitHub
parent 365bc58fc2
commit 036b1b4964
3 changed files with 51 additions and 5 deletions

View File

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

View File

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

View File

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