Rearranging interfaces and implementations. Notary fix for the cash tests.

This commit is contained in:
Andrius Dagys 2016-05-13 16:34:25 +01:00
parent 7d0ce00978
commit 422d65cc54
6 changed files with 70 additions and 61 deletions

View File

@ -0,0 +1,31 @@
package core.node.services
import core.Party
import core.StateRef
import core.WireTransaction
import core.crypto.SecureHash
/**
* A service that records input states of the given transaction and provides conflict information
* if any of the inputs have already been used in another transaction
*/
interface UniquenessProvider {
/** Commits all input states of the given transaction */
fun commit(tx: WireTransaction, callerIdentity: Party)
/** Specifies the consuming transaction for every conflicting state */
data class Conflict(val stateHistory: Map<StateRef, ConsumingTx>)
/**
* Specifies the transaction id, the position of the consumed state in the inputs, and
* the caller identity requesting the commit
*
* TODO: need to do more design work to prevent privacy problems: knowing the id of a
* transaction, by the rules of our system the party can obtain it and see its contents.
* This allows a party to just submit invalid transactions with outputs it was aware of and
* find out where exactly they were spent.
*/
data class ConsumingTx(val id: SecureHash, val inputIndex: Int, val requestingParty: Party)
}
class UniquenessException(val error: UniquenessProvider.Conflict) : Exception()

View File

@ -5,11 +5,10 @@ import core.Party
import core.TimestampCommand import core.TimestampCommand
import core.WireTransaction import core.WireTransaction
import core.crypto.DigitalSignature import core.crypto.DigitalSignature
import core.crypto.SignedData
import core.messaging.SingleMessageRecipient import core.messaging.SingleMessageRecipient
import core.node.NodeInfo import core.node.NodeInfo
import core.node.services.NotaryError import core.node.services.UniquenessProvider
import core.node.services.NotaryException
import core.node.services.NotaryService
import core.protocols.ProtocolLogic import core.protocols.ProtocolLogic
import core.random63BitValue import core.random63BitValue
import core.serialization.SerializedBytes import core.serialization.SerializedBytes
@ -45,13 +44,13 @@ class NotaryProtocol(private val wtx: WireTransaction,
val sessionID = random63BitValue() val sessionID = random63BitValue()
val request = SignRequest(wtx.serialized, serviceHub.storageService.myLegalIdentity, serviceHub.networkService.myAddress, sessionID) val request = SignRequest(wtx.serialized, serviceHub.storageService.myLegalIdentity, serviceHub.networkService.myAddress, sessionID)
val response = sendAndReceive<NotaryService.Result>(TOPIC, notaryNode.address, 0, sessionID, request) val response = sendAndReceive<Result>(TOPIC, notaryNode.address, 0, sessionID, request)
val notaryResult = validateResponse(response) val notaryResult = validateResponse(response)
return notaryResult.sig ?: throw NotaryException(notaryResult.error!!) return notaryResult.sig ?: throw NotaryException(notaryResult.error!!)
} }
private fun validateResponse(response: UntrustworthyData<NotaryService.Result>): NotaryService.Result { private fun validateResponse(response: UntrustworthyData<Result>): Result {
progressTracker.currentStep = VALIDATING progressTracker.currentStep = VALIDATING
response.validate { response.validate {
@ -91,4 +90,27 @@ class NotaryProtocol(private val wtx: WireTransaction,
val callerIdentity: Party, val callerIdentity: Party,
replyTo: SingleMessageRecipient, replyTo: SingleMessageRecipient,
sessionID: Long) : AbstractRequestMessage(replyTo, sessionID) sessionID: Long) : AbstractRequestMessage(replyTo, sessionID)
data class Result private constructor(val sig: DigitalSignature.LegallyIdentifiable?, val error: NotaryError?) {
companion object {
fun withError(error: NotaryError) = Result(null, error)
fun noError(sig: DigitalSignature.LegallyIdentifiable) = Result(sig, null)
}
}
}
class NotaryException(val error: NotaryError) : Exception()
sealed class NotaryError {
class Conflict(val tx: WireTransaction, val conflict: SignedData<UniquenessProvider.Conflict>) : NotaryError() {
override fun toString() = "One or more input states for transaction ${tx.id} have been used in another transaction"
}
class MoreThanOneTimestamp : NotaryError()
/** Thrown if the timestamp command in the transaction doesn't list this Notary as a signer */
class NotForMe : NotaryError()
/** Thrown if the time specified in the timestamp command is outside the allowed tolerance */
class TimestampInvalid : NotaryError()
} }

View File

@ -4,35 +4,9 @@ import core.Party
import core.StateRef import core.StateRef
import core.ThreadBox import core.ThreadBox
import core.WireTransaction import core.WireTransaction
import core.crypto.SecureHash
import java.util.* import java.util.*
import javax.annotation.concurrent.ThreadSafe import javax.annotation.concurrent.ThreadSafe
/**
* A service that records input states of the given transaction and provides conflict information
* if any of the inputs have already been used in another transaction
*/
interface UniquenessProvider {
/** Commits all input states of the given transaction */
fun commit(tx: WireTransaction, callerIdentity: Party)
/** Specifies the consuming transaction for every conflicting state */
data class Conflict(val stateHistory: Map<StateRef, ConsumingTx>)
/**
* Specifies the transaction id, the position of the consumed state in the inputs, and
* the caller identity requesting the commit
*
* TODO: need to do more design work to prevent privacy problems: knowing the id of a
* transaction, by the rules of our system the party can obtain it and see its contents.
* This allows a party to just submit invalid transactions with outputs it was aware of and
* find out where exactly they were spent.
*/
data class ConsumingTx(val id: SecureHash, val inputIndex: Int, val requestingParty: Party)
}
class UniquenessException(val error: UniquenessProvider.Conflict) : Exception()
/** A dummy Uniqueness provider that stores the whole history of consumed states in memory */ /** A dummy Uniqueness provider that stores the whole history of consumed states in memory */
@ThreadSafe @ThreadSafe
class InMemoryUniquenessProvider() : UniquenessProvider { class InMemoryUniquenessProvider() : UniquenessProvider {

View File

@ -12,6 +12,8 @@ import core.serialization.SerializedBytes
import core.serialization.deserialize import core.serialization.deserialize
import core.serialization.serialize import core.serialization.serialize
import core.utilities.loggerFor import core.utilities.loggerFor
import protocols.NotaryError
import protocols.NotaryException
import protocols.NotaryProtocol import protocols.NotaryProtocol
import java.security.KeyPair import java.security.KeyPair
@ -51,17 +53,17 @@ class NotaryService(net: MessagingService,
* *
* TODO: the notary service should only be able to see timestamp commands and inputs * TODO: the notary service should only be able to see timestamp commands and inputs
*/ */
fun processRequest(txBits: SerializedBytes<WireTransaction>, reqIdentity: Party): Result { fun processRequest(txBits: SerializedBytes<WireTransaction>, reqIdentity: Party): NotaryProtocol.Result {
val wtx = txBits.deserialize() val wtx = txBits.deserialize()
try { try {
validateTimestamp(wtx) validateTimestamp(wtx)
commitInputStates(wtx, reqIdentity) commitInputStates(wtx, reqIdentity)
} catch(e: NotaryException) { } catch(e: NotaryException) {
return Result.withError(e.error) return NotaryProtocol.Result.withError(e.error)
} }
val sig = sign(txBits) val sig = sign(txBits)
return Result.noError(sig) return NotaryProtocol.Result.noError(sig)
} }
private fun validateTimestamp(tx: WireTransaction) { private fun validateTimestamp(tx: WireTransaction) {
@ -90,26 +92,5 @@ class NotaryService(net: MessagingService,
return signingKey.signWithECDSA(bits, identity) return signingKey.signWithECDSA(bits, identity)
} }
data class Result private constructor(val sig: DigitalSignature.LegallyIdentifiable?, val error: NotaryError?) {
companion object {
fun withError(error: NotaryError) = Result(null, error)
fun noError(sig: DigitalSignature.LegallyIdentifiable) = Result(sig, null)
}
}
} }
class NotaryException(val error: NotaryError) : Exception()
sealed class NotaryError {
class Conflict(val tx: WireTransaction, val conflict: SignedData<UniquenessProvider.Conflict>) : NotaryError() {
override fun toString() = "One or more input states for transaction ${tx.id} have been used in another transaction"
}
class MoreThanOneTimestamp : NotaryError()
/** Thrown if the timestamp command in the transaction doesn't list this Notary as a signer */
class NotForMe : NotaryError()
/** Thrown if the time specified in the timestamp command is outside the allowed tolerance */
class TimestampInvalid : NotaryError()
}

View File

@ -1,7 +1,6 @@
import contracts.Cash import contracts.Cash
import contracts.DummyContract import contracts.DummyContract
import contracts.InsufficientBalanceException import contracts.InsufficientBalanceException
import contracts.cash.CashIssuanceDefinition
import core.* import core.*
import core.crypto.SecureHash import core.crypto.SecureHash
import core.serialization.OpaqueBytes import core.serialization.OpaqueBytes
@ -109,7 +108,7 @@ class CashTests {
// Test issuance from the issuance definition // Test issuance from the issuance definition
val issuanceDef = Cash.IssuanceDefinition(MINI_CORP.ref(12, 34), USD) val issuanceDef = Cash.IssuanceDefinition(MINI_CORP.ref(12, 34), USD)
val templatePtx = TransactionBuilder() val templatePtx = TransactionBuilder()
Cash().generateIssue(templatePtx, issuanceDef, 100.DOLLARS.pennies, owner = DUMMY_PUBKEY_1) Cash().generateIssue(templatePtx, issuanceDef, 100.DOLLARS.pennies, owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
assertTrue(templatePtx.inputStates().isEmpty()) assertTrue(templatePtx.inputStates().isEmpty())
assertEquals(ptx.outputStates()[0], templatePtx.outputStates()[0]) assertEquals(ptx.outputStates()[0], templatePtx.outputStates()[0])
@ -432,9 +431,9 @@ class CashTests {
*/ */
@Test @Test
fun aggregation() { fun aggregation() {
val fiveThousandDollarsFromMega = Cash.State(MEGA_CORP.ref(2), 5000.DOLLARS, MEGA_CORP_PUBKEY) val fiveThousandDollarsFromMega = Cash.State(MEGA_CORP.ref(2), 5000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
val twoThousandDollarsFromMega = Cash.State(MEGA_CORP.ref(2), 2000.DOLLARS, MINI_CORP_PUBKEY) val twoThousandDollarsFromMega = Cash.State(MEGA_CORP.ref(2), 2000.DOLLARS, MINI_CORP_PUBKEY, DUMMY_NOTARY)
val oneThousandDollarsFromMini = Cash.State(MINI_CORP.ref(3), 1000.DOLLARS, MEGA_CORP_PUBKEY) val oneThousandDollarsFromMini = Cash.State(MINI_CORP.ref(3), 1000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
// Obviously it must be possible to aggregate states with themselves // Obviously it must be possible to aggregate states with themselves
assertEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.issuanceDef) assertEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.issuanceDef)
@ -448,7 +447,7 @@ class CashTests {
// States cannot be aggregated if the currency differs // States cannot be aggregated if the currency differs
assertNotEquals(oneThousandDollarsFromMini.issuanceDef, assertNotEquals(oneThousandDollarsFromMini.issuanceDef,
Cash.State(MINI_CORP.ref(3), 1000.POUNDS, MEGA_CORP_PUBKEY).issuanceDef) Cash.State(MINI_CORP.ref(3), 1000.POUNDS, MEGA_CORP_PUBKEY, DUMMY_NOTARY).issuanceDef)
// States cannot be aggregated if the reference differs // States cannot be aggregated if the reference differs
assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.copy(deposit = MEGA_CORP.ref(1)).issuanceDef) assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.copy(deposit = MEGA_CORP.ref(1)).issuanceDef)

View File

@ -8,6 +8,8 @@ import core.testutils.DUMMY_NOTARY_KEY
import core.testutils.issueState import core.testutils.issueState
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import protocols.NotaryError
import protocols.NotaryException
import protocols.NotaryProtocol import protocols.NotaryProtocol
import java.time.Instant import java.time.Instant
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException