mirror of
https://github.com/corda/corda.git
synced 2025-05-31 22:50:53 +00:00
Rearranging interfaces and implementations. Notary fix for the cash tests.
This commit is contained in:
parent
7d0ce00978
commit
422d65cc54
@ -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()
|
@ -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()
|
||||||
}
|
}
|
@ -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 {
|
@ -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()
|
|
||||||
}
|
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user