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.WireTransaction
import core.crypto.DigitalSignature
import core.crypto.SignedData
import core.messaging.SingleMessageRecipient
import core.node.NodeInfo
import core.node.services.NotaryError
import core.node.services.NotaryException
import core.node.services.NotaryService
import core.node.services.UniquenessProvider
import core.protocols.ProtocolLogic
import core.random63BitValue
import core.serialization.SerializedBytes
@ -45,13 +44,13 @@ class NotaryProtocol(private val wtx: WireTransaction,
val sessionID = random63BitValue()
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)
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
response.validate {
@ -91,4 +90,27 @@ class NotaryProtocol(private val wtx: WireTransaction,
val callerIdentity: Party,
replyTo: SingleMessageRecipient,
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.ThreadBox
import core.WireTransaction
import core.crypto.SecureHash
import java.util.*
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 */
@ThreadSafe
class InMemoryUniquenessProvider() : UniquenessProvider {

View File

@ -12,6 +12,8 @@ import core.serialization.SerializedBytes
import core.serialization.deserialize
import core.serialization.serialize
import core.utilities.loggerFor
import protocols.NotaryError
import protocols.NotaryException
import protocols.NotaryProtocol
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
*/
fun processRequest(txBits: SerializedBytes<WireTransaction>, reqIdentity: Party): Result {
fun processRequest(txBits: SerializedBytes<WireTransaction>, reqIdentity: Party): NotaryProtocol.Result {
val wtx = txBits.deserialize()
try {
validateTimestamp(wtx)
commitInputStates(wtx, reqIdentity)
} catch(e: NotaryException) {
return Result.withError(e.error)
return NotaryProtocol.Result.withError(e.error)
}
val sig = sign(txBits)
return Result.noError(sig)
return NotaryProtocol.Result.noError(sig)
}
private fun validateTimestamp(tx: WireTransaction) {
@ -90,26 +92,5 @@ class NotaryService(net: MessagingService,
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.DummyContract
import contracts.InsufficientBalanceException
import contracts.cash.CashIssuanceDefinition
import core.*
import core.crypto.SecureHash
import core.serialization.OpaqueBytes
@ -109,7 +108,7 @@ class CashTests {
// Test issuance from the issuance definition
val issuanceDef = Cash.IssuanceDefinition(MINI_CORP.ref(12, 34), USD)
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())
assertEquals(ptx.outputStates()[0], templatePtx.outputStates()[0])
@ -432,9 +431,9 @@ class CashTests {
*/
@Test
fun aggregation() {
val fiveThousandDollarsFromMega = Cash.State(MEGA_CORP.ref(2), 5000.DOLLARS, MEGA_CORP_PUBKEY)
val twoThousandDollarsFromMega = Cash.State(MEGA_CORP.ref(2), 2000.DOLLARS, MINI_CORP_PUBKEY)
val oneThousandDollarsFromMini = Cash.State(MINI_CORP.ref(3), 1000.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, DUMMY_NOTARY)
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
assertEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.issuanceDef)
@ -448,7 +447,7 @@ class CashTests {
// States cannot be aggregated if the currency differs
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
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 org.junit.Before
import org.junit.Test
import protocols.NotaryError
import protocols.NotaryException
import protocols.NotaryProtocol
import java.time.Instant
import java.util.concurrent.ExecutionException