mirror of
https://github.com/corda/corda.git
synced 2025-04-07 19:34:41 +00:00
New gradle task for packaging certificate singing request utility jar.
This commit is contained in:
parent
6b97fbb79f
commit
c57229a944
11
build.gradle
11
build.gradle
@ -198,7 +198,7 @@ applicationDistribution.into("bin") {
|
||||
fileMode = 0755
|
||||
}
|
||||
|
||||
task buildCordaJAR(type: FatCapsule, dependsOn: 'quasarScan') {
|
||||
task buildCordaJAR(type: FatCapsule, dependsOn: ['quasarScan', 'buildCertSigningRequestUtilityJAR']) {
|
||||
applicationClass 'com.r3corda.node.MainKt'
|
||||
archiveName 'corda.jar'
|
||||
applicationSource = files(project.tasks.findByName('jar'), 'build/classes/main/CordaCaplet.class')
|
||||
@ -212,6 +212,15 @@ task buildCordaJAR(type: FatCapsule, dependsOn: 'quasarScan') {
|
||||
}
|
||||
}
|
||||
|
||||
task buildCertSigningRequestUtilityJAR(type: FatCapsule, dependsOn: project.jar) {
|
||||
applicationClass 'com.r3corda.node.utilities.certsigning.CertificateSignerKt'
|
||||
archiveName 'certSigningRequestUtility.jar'
|
||||
capsuleManifest {
|
||||
systemProperties['log4j.configuration'] = 'log4j2.xml'
|
||||
minJavaVersion = '1.8.0'
|
||||
}
|
||||
}
|
||||
|
||||
task installTemplateNodes(dependsOn: 'buildCordaJAR') << {
|
||||
copy {
|
||||
from buildCordaJAR.outputs.getFiles()
|
||||
|
@ -0,0 +1,12 @@
|
||||
package com.r3corda.contracts.tradefinance
|
||||
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A notice which can be attached to a receivable.
|
||||
*/
|
||||
sealed class Notice(val id: UUID, val owner: PublicKey) {
|
||||
class OwnershipInterest(id: UUID, owner: PublicKey) : Notice(id, owner)
|
||||
class Objection(id: UUID, owner: PublicKey) : Notice(id, owner)
|
||||
}
|
@ -0,0 +1,305 @@
|
||||
package com.r3corda.contracts.tradefinance
|
||||
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.contracts.clauses.*
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.isOrderedAndUnique
|
||||
import com.r3corda.core.random63BitValue
|
||||
import com.r3corda.core.serialization.OpaqueBytes
|
||||
import com.r3corda.core.utilities.NonEmptySet
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Contract for managing lifecycle of a receivable which is recorded on the distributed ledger. These are entered by
|
||||
* a third party (typically a potential creditor), and then shared by the trade finance registry, allowing others to
|
||||
* attach/detach notices of ownership interest/objection.
|
||||
*
|
||||
* States of this contract *are not* fungible, and as such special rules apply. States must be unique within the
|
||||
* inputs/outputs, and strictly ordered, in order to make it easy to verify that outputs match the inputs except where
|
||||
* commands mean there are changes.
|
||||
*/
|
||||
class Receivable : Contract {
|
||||
data class State(override val linearId: UniqueIdentifier = UniqueIdentifier(),
|
||||
val created: ZonedDateTime, // When the underlying receivable was raised
|
||||
val registered: Instant, // When the receivable was added to the registry
|
||||
val payer: Party,
|
||||
val payee: Party,
|
||||
val payerRef: OpaqueBytes?,
|
||||
val payeeRef: OpaqueBytes?,
|
||||
val value: Amount<Issued<Currency>>,
|
||||
val attachments: Set<SecureHash>,
|
||||
val notices: List<Notice>,
|
||||
override val owner: PublicKey) : OwnableState, LinearState {
|
||||
override val contract: Contract = Receivable()
|
||||
override val participants: List<PublicKey> = listOf(owner)
|
||||
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean
|
||||
= ourKeys.contains(payer.owningKey) || ourKeys.contains(payee.owningKey) || ourKeys.contains(owner)
|
||||
override fun withNewOwner(newOwner: PublicKey): Pair<CommandData, OwnableState>
|
||||
= Pair(Commands.Move(null, mapOf(Pair(linearId, newOwner))), copy(owner = newOwner))
|
||||
}
|
||||
|
||||
interface Commands : CommandData {
|
||||
val changed: Iterable<UniqueIdentifier>
|
||||
data class Issue(override val changed: NonEmptySet<UniqueIdentifier>,
|
||||
override val nonce: Long = random63BitValue()) : IssueCommand, Commands
|
||||
data class Move(override val contractHash: SecureHash?, val changes: Map<UniqueIdentifier, PublicKey>) : MoveCommand, Commands {
|
||||
override val changed: Iterable<UniqueIdentifier> = changes.keys
|
||||
}
|
||||
data class Note(val changes: Map<UniqueIdentifier, Diff<Notice>>) : Commands {
|
||||
override val changed: Iterable<UniqueIdentifier> = changes.keys
|
||||
}
|
||||
// TODO: Write Amend clause, possibly to merge into Move
|
||||
/* data class Amend(val id: UniqueIdentifier,
|
||||
val payer: Party,
|
||||
val payee: Party,
|
||||
val payerRef: OpaqueBytes?,
|
||||
val payeeRef: OpaqueBytes?,
|
||||
val value: Amount<Issued<Currency>>,
|
||||
val attachments: Set<SecureHash>) : Commands */
|
||||
data class Exit(override val changed: NonEmptySet<UniqueIdentifier>) : Commands
|
||||
}
|
||||
|
||||
data class Diff<T : Any>(val added: List<T>, val removed: List<T>)
|
||||
|
||||
interface Clauses {
|
||||
/**
|
||||
* Assert that each input/output state is unique within that list of states, and that states are ordered. There
|
||||
* should never be the same receivable twice in a transaction. Uniqueness is also enforced by the notary,
|
||||
* but we get the check as a side-effect of comparing states, so the duplication is acceptable.
|
||||
*/
|
||||
class StatesAreOrderedAndUnique : Clause<State, Commands, Unit>() {
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: Unit?): Set<Commands> {
|
||||
// Enforce that states are ordered, so that the transaction can only be assembled in one way
|
||||
requireThat {
|
||||
"input receivables are ordered and unique" by inputs.isOrderedAndUnique { linearId }
|
||||
"output receivables are ordered and unique" by outputs.isOrderedAndUnique { linearId }
|
||||
}
|
||||
return emptySet()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that all inputs are present as outputs, and that all owners for new outputs have signed the command.
|
||||
*/
|
||||
class Issue : Clause<State, Commands, Unit>() {
|
||||
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Issue::class.java)
|
||||
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: Unit?): Set<Commands> {
|
||||
require(groupingKey == null)
|
||||
// TODO: Take in matched commands as a parameter
|
||||
val command = commands.requireSingleCommand<Commands.Issue>()
|
||||
val timestamp = tx.timestamp
|
||||
|
||||
// Records for receivables are never fungible, so we just want to make sure all inputs exist as
|
||||
// outputs, and there are new outputs.
|
||||
requireThat {
|
||||
"there are more output states than input states" by (outputs.size > inputs.size)
|
||||
// TODO: Should timestamps perhaps be enforced on all receivable transactions?
|
||||
"the transaction has a timestamp" by (timestamp != null)
|
||||
}
|
||||
|
||||
val expectedOutputs = ArrayList(inputs)
|
||||
val keysThatSigned = command.signers
|
||||
val owningPubKeys = HashSet<PublicKey>()
|
||||
outputs
|
||||
.filter { it.linearId in command.value.changed }
|
||||
.forEach { state ->
|
||||
val registrationInLocalZone = state.registered.atZone(state.created.zone)
|
||||
requireThat {
|
||||
"the receivable is registered after it was created" by (state.created < registrationInLocalZone)
|
||||
// TODO: Should narrow the window on how long ago the registration can be compared to the transaction
|
||||
"the receivable is registered before the transaction date" by (state.registered < timestamp?.before)
|
||||
}
|
||||
owningPubKeys.add(state.owner)
|
||||
expectedOutputs.add(state)
|
||||
}
|
||||
// Re-sort the outputs now we've finished changing them
|
||||
expectedOutputs.sortBy { state -> state.linearId }
|
||||
requireThat {
|
||||
"the owning keys are the same as the signing keys" by keysThatSigned.containsAll(owningPubKeys)
|
||||
"outputs match inputs with expected changes applied" by outputs.equals(expectedOutputs)
|
||||
}
|
||||
|
||||
return setOf(command.value as Commands)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that inputs and outputs are exactly the same, except for ownership changes specified in the command.
|
||||
* The command must be signed by the previous owners of all changed input states.
|
||||
*/
|
||||
class Move : Clause<State, Commands, Unit>() {
|
||||
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Move::class.java)
|
||||
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: Unit?): Set<Commands> {
|
||||
require(groupingKey == null)
|
||||
// TODO: Take in matched commands as a parameter
|
||||
val moveCommand = commands.requireSingleCommand<Commands.Move>()
|
||||
val changes = moveCommand.value.changes
|
||||
// Rebuild the outputs we expect, then compare. Receivables are not fungible, so inputs and outputs
|
||||
// must match one to one
|
||||
val expectedOutputs: List<State> = inputs.map { input ->
|
||||
val newOwner = changes[input.linearId]
|
||||
if (newOwner != null) {
|
||||
input.copy(owner = newOwner)
|
||||
} else {
|
||||
input
|
||||
}
|
||||
}
|
||||
requireThat {
|
||||
"inputs are not empty" by inputs.isNotEmpty()
|
||||
"outputs match inputs with expected changes applied" by outputs.equals(expectedOutputs)
|
||||
}
|
||||
// Do standard move command checks including the signature checks
|
||||
verifyMoveCommand<Commands.Move>(inputs, commands)
|
||||
return setOf(moveCommand.value as Commands)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add and/or remove notices on receivables. All input states must match output states, except for the
|
||||
* changed notices.
|
||||
*/
|
||||
class Note : Clause<State, Commands, Unit>() {
|
||||
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Note::class.java)
|
||||
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: Unit?): Set<Commands> {
|
||||
require(groupingKey == null)
|
||||
// TODO: Take in matched commands as a parameter
|
||||
val command = commands.requireSingleCommand<Commands.Note>()
|
||||
// Rebuild the outputs we expect, then compare. Receivables are not fungible, so inputs and outputs
|
||||
// must match one to one
|
||||
val (expectedOutputs, owningPubKeys) = deriveOutputStates(inputs, command)
|
||||
val keysThatSigned = command.signers
|
||||
requireThat {
|
||||
"inputs are not empty" by inputs.isNotEmpty()
|
||||
"outputs match inputs with expected changes applied" by outputs.equals(expectedOutputs)
|
||||
"the owning keys are the same as the signing keys" by keysThatSigned.containsAll(owningPubKeys)
|
||||
}
|
||||
return setOf(command.value as Commands)
|
||||
}
|
||||
|
||||
fun deriveOutputStates(inputs: List<State>,
|
||||
command: AuthenticatedObject<Commands.Note>): Pair<List<State>, Set<PublicKey>> {
|
||||
val changes = command.value.changes
|
||||
val seenNotices = HashSet<Notice>()
|
||||
val outputs = inputs.map { input ->
|
||||
val stateChanges = changes[input.linearId]
|
||||
if (stateChanges != null) {
|
||||
val notices = ArrayList<Notice>(input.notices)
|
||||
stateChanges.added.forEach { notice ->
|
||||
require(!seenNotices.contains(notice)) { "Notices can only appear once in the add and/or remove lists" }
|
||||
require(!notices.contains(notice)) { "Notice is already present on the receivable" }
|
||||
seenNotices.add(notice)
|
||||
notices.add(notice)
|
||||
}
|
||||
stateChanges.removed.forEach { notice ->
|
||||
require(!seenNotices.contains(notice)) { "Notices can only appear once in the add and/or remove lists" }
|
||||
require(notices.remove(notice)) { "Notice is not present on the receivable" }
|
||||
seenNotices.add(notice)
|
||||
}
|
||||
input.copy(notices = notices)
|
||||
} else {
|
||||
input
|
||||
}
|
||||
}
|
||||
return Pair(outputs, seenNotices.map { it.owner }.toSet() )
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a receivable from the ledger. This can only be done once all notices have been removed.
|
||||
*/
|
||||
class Exit : Clause<State, Commands, Unit>() {
|
||||
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Exit::class.java)
|
||||
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: Unit?): Set<Commands> {
|
||||
require(groupingKey == null)
|
||||
// TODO: Take in matched commands as a parameter
|
||||
val command = commands.requireSingleCommand<Commands.Exit>()
|
||||
val unmatchedIds = HashSet<UniqueIdentifier>(command.value.changed)
|
||||
val owningPubKeys = HashSet<PublicKey>()
|
||||
val expectedOutputs = inputs.filter { input ->
|
||||
if (unmatchedIds.contains(input.linearId)) {
|
||||
requireThat {
|
||||
"there are no notices on receivables to be removed from the ledger" by input.notices.isEmpty()
|
||||
}
|
||||
unmatchedIds.remove(input.linearId)
|
||||
owningPubKeys.add(input.owner)
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
val keysThatSigned = command.signers
|
||||
requireThat {
|
||||
"inputs are not empty" by inputs.isNotEmpty()
|
||||
"outputs match inputs with expected changes applied" by outputs.equals(expectedOutputs)
|
||||
"the owning keys are the same as the signing keys" by keysThatSigned.containsAll(owningPubKeys)
|
||||
}
|
||||
return setOf(command.value as Commands)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Amend clause, which replaces the Move clause
|
||||
|
||||
/**
|
||||
* Default clause, which checks the inputs and outputs match. Normally this wouldn't be expected to trigger,
|
||||
* as other commands would handle the transaction, but this exists in case the states need to be witnessed by
|
||||
* other contracts within the transaction but not modified.
|
||||
*/
|
||||
class InputsAndOutputsMatch : Clause<State, Commands, Unit>() {
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: Unit?): Set<Commands> {
|
||||
require(groupingKey == null)
|
||||
require(inputs.equals(outputs)) { "Inputs and outputs must match unless commands indicate otherwise" }
|
||||
return emptySet()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val legalContractReference: SecureHash = SecureHash.sha256("https://www.big-book-of-banking-law.gov/receivables.html")
|
||||
fun extractCommands(commands: Collection<AuthenticatedObject<CommandData>>): List<AuthenticatedObject<Commands>>
|
||||
= commands.select<Commands>()
|
||||
override fun verify(tx: TransactionForContract)
|
||||
= verifyClause(tx, FilterOn<State, Commands, Unit>(
|
||||
AllComposition(
|
||||
Clauses.StatesAreOrderedAndUnique(), // TODO: This is varient of the LinearState.ClauseVerifier, and we should move it up there
|
||||
FirstComposition(
|
||||
Clauses.Issue(),
|
||||
Clauses.Exit(),
|
||||
Clauses.Note(),
|
||||
Clauses.Move(),
|
||||
Clauses.InputsAndOutputsMatch()
|
||||
)
|
||||
), { states -> states.filterIsInstance<State>() }),
|
||||
extractCommands(tx.commands))
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
package com.r3corda.contracts.tradefinance
|
||||
|
||||
import com.r3corda.contracts.asset.DUMMY_CASH_ISSUER
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.serialization.OpaqueBytes
|
||||
import com.r3corda.core.utilities.NonEmptySet
|
||||
import com.r3corda.core.utilities.TEST_TX_TIME
|
||||
import com.r3corda.testing.*
|
||||
import org.junit.Test
|
||||
import java.time.Duration
|
||||
import java.time.ZoneId
|
||||
import java.util.*
|
||||
|
||||
class ReceivableTests {
|
||||
val inStates = arrayOf(
|
||||
Receivable.State(
|
||||
UniqueIdentifier.fromString("9e688c58-a548-3b8e-af69-c9e1005ad0bf"),
|
||||
(TEST_TX_TIME - Duration.ofDays(2)).atZone(ZoneId.of("UTC")),
|
||||
TEST_TX_TIME - Duration.ofDays(1),
|
||||
ALICE,
|
||||
BOB,
|
||||
OpaqueBytes(ByteArray(1, { 1 })),
|
||||
OpaqueBytes(ByteArray(1, { 2 })),
|
||||
Amount<Issued<Currency>>(1000L, USD `issued by` DUMMY_CASH_ISSUER),
|
||||
emptySet(),
|
||||
emptyList(),
|
||||
MEGA_CORP_PUBKEY
|
||||
),
|
||||
Receivable.State(
|
||||
UniqueIdentifier.fromString("55a54008-ad1b-3589-aa21-0d2629c1df41"),
|
||||
(TEST_TX_TIME - Duration.ofDays(2)).atZone(ZoneId.of("UTC")),
|
||||
TEST_TX_TIME - Duration.ofDays(1),
|
||||
ALICE,
|
||||
BOB,
|
||||
OpaqueBytes(ByteArray(1, { 3 })),
|
||||
OpaqueBytes(ByteArray(1, { 4 })),
|
||||
Amount<Issued<Currency>>(2000L, GBP `issued by` DUMMY_CASH_ISSUER),
|
||||
emptySet(),
|
||||
emptyList(),
|
||||
MEGA_CORP_PUBKEY
|
||||
)
|
||||
)
|
||||
|
||||
@Test
|
||||
fun trivial() {
|
||||
transaction {
|
||||
input { inStates[0] }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "Inputs and outputs must match unless commands indicate otherwise"
|
||||
|
||||
tweak {
|
||||
output { inStates[0] }
|
||||
verifies()
|
||||
}
|
||||
|
||||
tweak {
|
||||
output { inStates[1] }
|
||||
this `fails with` "Inputs and outputs must match unless commands indicate otherwise"
|
||||
}
|
||||
}
|
||||
|
||||
transaction {
|
||||
output { inStates[0] }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "Inputs and outputs must match unless commands indicate otherwise"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `order and uniqueness is enforced`() {
|
||||
transaction {
|
||||
input { inStates[0] }
|
||||
input { inStates[1] }
|
||||
output { inStates[0] }
|
||||
output { inStates[1] }
|
||||
timestamp(TEST_TX_TIME)
|
||||
verifies()
|
||||
}
|
||||
|
||||
transaction {
|
||||
input { inStates[1] }
|
||||
input { inStates[0] }
|
||||
output { inStates[0] }
|
||||
output { inStates[1] }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "receivables are ordered and unique"
|
||||
}
|
||||
|
||||
transaction {
|
||||
input { inStates[0] }
|
||||
input { inStates[0] }
|
||||
output { inStates[0] }
|
||||
output { inStates[0] }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "receivables are ordered and unique"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `issue`() {
|
||||
// Testing that arbitrary new outputs are rejected is covered in trivial()
|
||||
transaction {
|
||||
output { inStates[0] }
|
||||
command(MEGA_CORP_PUBKEY, Receivable.Commands.Issue(NonEmptySet(inStates[0].linearId)))
|
||||
timestamp(TEST_TX_TIME)
|
||||
verifies()
|
||||
}
|
||||
transaction {
|
||||
output { inStates[0] }
|
||||
command(ALICE_PUBKEY, Receivable.Commands.Issue(NonEmptySet(inStates[0].linearId)))
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "the owning keys are the same as the signing keys"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `move`() {
|
||||
transaction {
|
||||
input { inStates[0] }
|
||||
output { inStates[0].copy(owner = MINI_CORP_PUBKEY) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "Inputs and outputs must match unless commands indicate otherwise"
|
||||
tweak {
|
||||
command(MEGA_CORP_PUBKEY, Receivable.Commands.Move(null, mapOf(Pair(inStates[0].linearId, MINI_CORP_PUBKEY))))
|
||||
verifies()
|
||||
}
|
||||
// Test that moves enforce the correct new owner
|
||||
tweak {
|
||||
command(MEGA_CORP_PUBKEY, Receivable.Commands.Move(null, mapOf(Pair(inStates[0].linearId, ALICE_PUBKEY))))
|
||||
this `fails with` "outputs match inputs with expected changes applied"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `exit`() {
|
||||
// Testing that arbitrary disappearing outputs are rejected is covered in trivial()
|
||||
transaction {
|
||||
input { inStates[0] }
|
||||
timestamp(TEST_TX_TIME)
|
||||
command(MEGA_CORP_PUBKEY, Receivable.Commands.Exit(NonEmptySet(inStates[0].linearId)))
|
||||
verifies()
|
||||
}
|
||||
transaction {
|
||||
input { inStates[0] }
|
||||
timestamp(TEST_TX_TIME)
|
||||
command(ALICE_PUBKEY, Receivable.Commands.Exit(NonEmptySet(inStates[0].linearId)))
|
||||
this `fails with` "the owning keys are the same as the signing keys"
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Test adding and removing notices
|
||||
}
|
@ -276,4 +276,21 @@ fun <T> Observable<T>.bufferUntilSubscribed(): Observable<T> {
|
||||
val subject = UnicastSubject.create<T>()
|
||||
val subscription = subscribe(subject)
|
||||
return subject.doOnUnsubscribe { subscription.unsubscribe() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an iterable data type's contents are ordered and unique, based on their [Comparable].compareTo
|
||||
* function.
|
||||
*/
|
||||
fun <T, I: Comparable<I>> Iterable<T>.isOrderedAndUnique(extractId: T.() -> I): Boolean {
|
||||
var last: I? = null
|
||||
return all { it ->
|
||||
val lastLast = last
|
||||
last = extractId(it)
|
||||
if (lastLast == null) {
|
||||
true
|
||||
} else {
|
||||
lastLast.compareTo(extractId(it)) < 0
|
||||
}
|
||||
}
|
||||
}
|
@ -448,13 +448,38 @@ data class Commodity(val commodityCode: String,
|
||||
|
||||
/**
|
||||
* This class provides a truly unique identifier of a trade, state, or other business object.
|
||||
* @param externalId If there is an existing weak identifer e.g. trade reference id.
|
||||
*
|
||||
* @param externalId If there is an existing weak identifier e.g. trade reference id.
|
||||
* This should be set here the first time a UniqueIdentifier identifier is created as part of an issue,
|
||||
* or ledger on-boarding activity. This ensure that the human readable identity is paired with the strong id.
|
||||
* @param id Should never be set by user code and left as default initialised.
|
||||
* So that the first time a state is issued this should be given a new UUID.
|
||||
* Subsequent copies and evolutions of a state should just copy the externalId and Id fields unmodified.
|
||||
*/
|
||||
data class UniqueIdentifier(val externalId: String? = null, val id: UUID = UUID.randomUUID()) {
|
||||
data class UniqueIdentifier(val externalId: String? = null, val id: UUID = UUID.randomUUID()) : Comparable<UniqueIdentifier> {
|
||||
override fun toString(): String = if (externalId != null) "${externalId}_$id" else id.toString()
|
||||
}
|
||||
companion object {
|
||||
fun fromString(name: String) : UniqueIdentifier
|
||||
= UniqueIdentifier(null, UUID.fromString(name))
|
||||
}
|
||||
|
||||
override fun compareTo(other: UniqueIdentifier): Int {
|
||||
val idCompare = id.compareTo(other.id)
|
||||
return if (idCompare == 0)
|
||||
compareExternalIds(other)
|
||||
else
|
||||
idCompare
|
||||
}
|
||||
|
||||
private fun compareExternalIds(other: UniqueIdentifier): Int
|
||||
= if (other.externalId == null)
|
||||
if (externalId == null)
|
||||
0
|
||||
else
|
||||
1
|
||||
else
|
||||
if (externalId == null)
|
||||
-1
|
||||
else
|
||||
externalId.compareTo(externalId)
|
||||
}
|
||||
|
@ -238,18 +238,18 @@ interface LinearState: ContractState {
|
||||
/**
|
||||
* Standard clause to verify the LinearState safety properties.
|
||||
*/
|
||||
class ClauseVerifier<S : LinearState>(val stateClass: Class<S>) : Clause<ContractState, CommandData, Unit>() {
|
||||
class ClauseVerifier<S : LinearState, C : CommandData>() : Clause<S, C, Unit>() {
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<ContractState>,
|
||||
outputs: List<ContractState>,
|
||||
commands: List<AuthenticatedObject<CommandData>>,
|
||||
groupingKey: Unit?): Set<CommandData> {
|
||||
val filteredInputs = inputs.filterIsInstance(stateClass)
|
||||
val inputIds = filteredInputs.map { it.linearId }.distinct()
|
||||
require(inputIds.count() == filteredInputs.count()) { "LinearStates cannot be merged" }
|
||||
val filteredOutputs = outputs.filterIsInstance(stateClass)
|
||||
val outputIds = filteredOutputs.map { it.linearId }.distinct()
|
||||
require(outputIds.count() == filteredOutputs.count()) { "LinearStates cannot be split" }
|
||||
inputs: List<S>,
|
||||
outputs: List<S>,
|
||||
commands: List<AuthenticatedObject<C>>,
|
||||
groupingKey: Unit?): Set<C> {
|
||||
val inputIds = inputs.map { it.linearId }.distinct()
|
||||
val outputIds = outputs.map { it.linearId }.distinct()
|
||||
requireThat {
|
||||
"LinearStates are not merged" by (inputIds.count() == inputs.count())
|
||||
"LinearStates are not split" by (outputIds.count() == outputs.count())
|
||||
}
|
||||
return emptySet()
|
||||
}
|
||||
}
|
||||
|
@ -24,8 +24,10 @@ class FirstComposition<S : ContractState, C : CommandData, K : Any>(val firstCla
|
||||
clauses.addAll(remainingClauses)
|
||||
}
|
||||
|
||||
override fun verify(tx: TransactionForContract, inputs: List<S>, outputs: List<S>, commands: List<AuthenticatedObject<C>>, groupingKey: K?): Set<C>
|
||||
= matchedClauses(commands).single().verify(tx, inputs, outputs, commands, groupingKey)
|
||||
override fun verify(tx: TransactionForContract, inputs: List<S>, outputs: List<S>, commands: List<AuthenticatedObject<C>>, groupingKey: K?): Set<C> {
|
||||
val clause = matchedClauses(commands).singleOrNull() ?: throw IllegalStateException("No delegate clause matched in first composition")
|
||||
return clause.verify(tx, inputs, outputs, commands, groupingKey)
|
||||
}
|
||||
|
||||
override fun toString() = "First: ${clauses.toList()}"
|
||||
}
|
@ -115,7 +115,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val networkMap
|
||||
return smm.add(loggerName, logic).resultFuture
|
||||
}
|
||||
|
||||
override fun <P : ProtocolLogic<*>> registerProtocolInitiator(markerClass: KClass<out P>, protocolFactory: (Party) -> ProtocolLogic<*>) {
|
||||
override fun registerProtocolInitiator(markerClass: KClass<*>, protocolFactory: (Party) -> ProtocolLogic<*>) {
|
||||
require(markerClass !in protocolFactories) { "${markerClass.java.name} has already been used to register a protocol" }
|
||||
log.debug { "Registering ${markerClass.java.name}" }
|
||||
protocolFactories[markerClass.java] = protocolFactory
|
||||
|
@ -76,12 +76,12 @@ abstract class ServiceHubInternal : ServiceHub {
|
||||
* marker class has been registered then the corresponding factory will be used to create the protocol which will
|
||||
* communicate with the other side. If there is no mapping then the session attempt is rejected.
|
||||
* @param markerClass The marker [KClass] present in a session initiation attempt, which is a 1:1 mapping to a [Class]
|
||||
* using the <pre>::class</pre> construct. Any marker class can be used, with the default being the class of the initiating
|
||||
* protocol. This enables the registration to be of the form: registerProtocolInitiator(InitiatorProtocol::class, ::InitiatedProtocol)
|
||||
* using the <pre>::class</pre> construct. Conventionally this is a [ProtocolLogic] subclass, however any class can
|
||||
* be used, with the default being the class of the initiating protocol. This enables the registration to be of the
|
||||
* form: registerProtocolInitiator(InitiatorProtocol::class, ::InitiatedProtocol)
|
||||
* @param protocolFactory The protocol factory generating the initiated protocol.
|
||||
* @param R the return type of the protocol logic
|
||||
*/
|
||||
abstract fun <P : ProtocolLogic<*>> registerProtocolInitiator(markerClass: KClass<out P>, protocolFactory: (Party) -> ProtocolLogic<*>)
|
||||
abstract fun registerProtocolInitiator(markerClass: KClass<*>, protocolFactory: (Party) -> ProtocolLogic<*>)
|
||||
|
||||
/**
|
||||
* Return the protocol factory that has been registered with [markerClass], or null if no factory is found.
|
||||
|
@ -15,6 +15,7 @@ import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigParseOptions
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.net.URL
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
@ -60,7 +61,6 @@ interface NodeConfiguration : NodeSSLConfiguration {
|
||||
val exportJMXto: String
|
||||
val dataSourceProperties: Properties get() = Properties()
|
||||
val devMode: Boolean
|
||||
val certificateSigningService: HostAndPort
|
||||
|
||||
companion object {
|
||||
val log = LoggerFactory.getLogger("NodeConfiguration")
|
||||
@ -96,6 +96,7 @@ operator fun <T> Config.getValue(receiver: Any, metadata: KProperty<*>): T {
|
||||
Instant::class.java -> Instant.parse(getString(metadata.name)) as T
|
||||
HostAndPort::class.java -> HostAndPort.fromString(getString(metadata.name)) as T
|
||||
Path::class.java -> Paths.get(getString(metadata.name)) as T
|
||||
URL::class.java -> URL(getString(metadata.name)) as T
|
||||
Properties::class.java -> getProperties(metadata.name) as T
|
||||
else -> throw IllegalArgumentException("Unsupported type ${metadata.returnType}")
|
||||
}
|
||||
@ -133,7 +134,6 @@ class NodeConfigurationFromConfig(val config: Config = ConfigFactory.load()) : N
|
||||
override val trustStorePassword: String by config
|
||||
override val dataSourceProperties: Properties by config
|
||||
override val devMode: Boolean by config.getOrElse { false }
|
||||
override val certificateSigningService: HostAndPort by config
|
||||
}
|
||||
|
||||
class FullNodeConfiguration(conf: Config) : NodeConfiguration {
|
||||
@ -146,7 +146,6 @@ class FullNodeConfiguration(conf: Config) : NodeConfiguration {
|
||||
override val trustStorePassword: String by conf
|
||||
override val dataSourceProperties: Properties by conf
|
||||
override val devMode: Boolean by conf.getOrElse { false }
|
||||
override val certificateSigningService: HostAndPort by conf
|
||||
val useHTTPS: Boolean by conf
|
||||
val artemisAddress: HostAndPort by conf
|
||||
val webAddress: HostAndPort by conf
|
||||
|
@ -9,9 +9,11 @@ import com.r3corda.core.crypto.X509Utilities.addOrReplaceKey
|
||||
import com.r3corda.core.div
|
||||
import com.r3corda.core.minutes
|
||||
import com.r3corda.core.utilities.loggerFor
|
||||
import com.r3corda.node.services.config.FullNodeConfiguration
|
||||
import com.r3corda.node.services.config.NodeConfiguration
|
||||
import com.r3corda.node.services.config.NodeConfigurationFromConfig
|
||||
import com.r3corda.node.services.config.getValue
|
||||
import joptsimple.OptionParser
|
||||
import java.net.URL
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.security.KeyPair
|
||||
@ -113,8 +115,8 @@ class CertificateSigner(val config: NodeConfiguration, val certService: Certific
|
||||
|
||||
object ParamsSpec {
|
||||
val parser = OptionParser()
|
||||
val baseDirectoryArg = parser.accepts("base-dir", "The directory to put all key stores under").withRequiredArg()
|
||||
val configFileArg = parser.accepts("config-file", "The path to the config file").withRequiredArg()
|
||||
val baseDirectoryArg = parser.accepts("base-dir", "Working directory of Corda Node.").withRequiredArg().defaultsTo(".")
|
||||
val configFileArg = parser.accepts("config-file", "The path to the config file.").withRequiredArg()
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
@ -122,12 +124,19 @@ fun main(args: Array<String>) {
|
||||
ParamsSpec.parser.parse(*args)
|
||||
} catch (ex: Exception) {
|
||||
CertificateSigner.log.error("Unable to parse args", ex)
|
||||
ParamsSpec.parser.printHelpOn(System.out)
|
||||
exitProcess(1)
|
||||
}
|
||||
val baseDirectoryPath = Paths.get(cmdlineOptions.valueOf(ParamsSpec.baseDirectoryArg) ?: throw IllegalArgumentException("Please provide Corda node base directory path"))
|
||||
val baseDirectoryPath = Paths.get(cmdlineOptions.valueOf(ParamsSpec.baseDirectoryArg))
|
||||
val configFile = if (cmdlineOptions.has(ParamsSpec.configFileArg)) Paths.get(cmdlineOptions.valueOf(ParamsSpec.configFileArg)) else null
|
||||
val conf = FullNodeConfiguration(NodeConfiguration.loadConfig(baseDirectoryPath, configFile, allowMissingConfig = true))
|
||||
|
||||
val config = NodeConfiguration.loadConfig(baseDirectoryPath, configFile, allowMissingConfig = true).let { config ->
|
||||
object : NodeConfiguration by NodeConfigurationFromConfig(config) {
|
||||
val certificateSigningService: URL by config
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use HTTPS instead
|
||||
CertificateSigner(conf, HTTPCertificateSigningService(conf.certificateSigningService)).buildKeyStore()
|
||||
CertificateSigner(config, HTTPCertificateSigningService(config.certificateSigningService)).buildKeyStore()
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.r3corda.node.utilities.certsigning
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||
import java.io.IOException
|
||||
@ -11,7 +10,7 @@ import java.security.cert.CertificateFactory
|
||||
import java.util.*
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
class HTTPCertificateSigningService(val server: HostAndPort) : CertificateSigningService {
|
||||
class HTTPCertificateSigningService(val server: URL) : CertificateSigningService {
|
||||
companion object {
|
||||
// TODO: Propagate version information from gradle
|
||||
val clientVersion = "1.0"
|
||||
@ -19,7 +18,7 @@ class HTTPCertificateSigningService(val server: HostAndPort) : CertificateSignin
|
||||
|
||||
override fun retrieveCertificates(requestId: String): Array<Certificate>? {
|
||||
// Poll server to download the signed certificate once request has been approved.
|
||||
val url = URL("http://$server/api/certificate/$requestId")
|
||||
val url = URL("$server/api/certificate/$requestId")
|
||||
|
||||
val conn = url.openConnection() as HttpURLConnection
|
||||
conn.requestMethod = "GET"
|
||||
@ -42,7 +41,7 @@ class HTTPCertificateSigningService(val server: HostAndPort) : CertificateSignin
|
||||
|
||||
override fun submitRequest(request: PKCS10CertificationRequest): String {
|
||||
// Post request to certificate signing server via http.
|
||||
val conn = URL("http://$server/api/certificate").openConnection() as HttpURLConnection
|
||||
val conn = URL("$server/api/certificate").openConnection() as HttpURLConnection
|
||||
conn.doOutput = true
|
||||
conn.requestMethod = "POST"
|
||||
conn.setRequestProperty("Content-Type", "application/octet-stream")
|
||||
|
@ -11,5 +11,5 @@ dataSourceProperties = {
|
||||
"dataSource.password" = ""
|
||||
}
|
||||
devMode = true
|
||||
certificateSigningService = "localhost:0"
|
||||
certificateSigningService = "https://cordaci-netperm.corda.r3cev.com"
|
||||
useHTTPS = false
|
@ -51,8 +51,6 @@ class ArtemisMessagingTests {
|
||||
override val exportJMXto: String = ""
|
||||
override val keyStorePassword: String = "testpass"
|
||||
override val trustStorePassword: String = "trustpass"
|
||||
override val certificateSigningService: HostAndPort = HostAndPort.fromParts("localhost", 0)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,7 @@ open class MockServiceHubInternal(
|
||||
return smm.add(loggerName, logic).resultFuture
|
||||
}
|
||||
|
||||
override fun <P : ProtocolLogic<*>> registerProtocolInitiator(markerClass: KClass<out P>, protocolFactory: (Party) -> ProtocolLogic<*>) {
|
||||
override fun registerProtocolInitiator(markerClass: KClass<*>, protocolFactory: (Party) -> ProtocolLogic<*>) {
|
||||
protocolFactories[markerClass.java] = protocolFactory
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.r3corda.node.utilities.certsigning
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import com.nhaarman.mockito_kotlin.any
|
||||
import com.nhaarman.mockito_kotlin.eq
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
@ -46,7 +45,6 @@ class CertificateSignerTest {
|
||||
override val exportJMXto: String = ""
|
||||
override val keyStorePassword: String = "testpass"
|
||||
override val trustStorePassword: String = "trustpass"
|
||||
override val certificateSigningService: HostAndPort = HostAndPort.fromParts("localhost", 0)
|
||||
}
|
||||
|
||||
assertFalse(Files.exists(config.keyStorePath))
|
||||
|
@ -71,7 +71,6 @@ fun main(args: Array<String>) {
|
||||
override val keyStorePassword: String = "cordacadevpass"
|
||||
override val trustStorePassword: String = "trustpass"
|
||||
override val dataSourceProperties: Properties = makeTestDataSourceProperties()
|
||||
override val certificateSigningService: HostAndPort = HostAndPort.fromParts("localhost", 0)
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.r3corda.simulation
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.r3corda.core.crypto.generateKeyPair
|
||||
@ -70,7 +69,6 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
|
||||
override val exportJMXto: String = ""
|
||||
override val keyStorePassword: String = "dummy"
|
||||
override val trustStorePassword: String = "trustpass"
|
||||
override val certificateSigningService: HostAndPort = HostAndPort.fromParts("localhost", 0)
|
||||
override val dataSourceProperties = makeTestDataSourceProperties()
|
||||
}
|
||||
return SimulatedNode(cfg, network, networkMapAddr, advertisedServices, id, keyPair)
|
||||
@ -100,7 +98,6 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
|
||||
override val exportJMXto: String = ""
|
||||
override val keyStorePassword: String = "dummy"
|
||||
override val trustStorePassword: String = "trustpass"
|
||||
override val certificateSigningService: HostAndPort = HostAndPort.fromParts("localhost", 0)
|
||||
override val dataSourceProperties = makeTestDataSourceProperties()
|
||||
}
|
||||
|
||||
@ -123,7 +120,6 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
|
||||
override val exportJMXto: String = ""
|
||||
override val keyStorePassword: String = "dummy"
|
||||
override val trustStorePassword: String = "trustpass"
|
||||
override val certificateSigningService: HostAndPort = HostAndPort.fromParts("localhost", 0)
|
||||
override val dataSourceProperties = makeTestDataSourceProperties()
|
||||
}
|
||||
return SimulatedNode(cfg, network, networkMapAddr, advertisedServices, id, keyPair)
|
||||
@ -145,7 +141,6 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
|
||||
override val exportJMXto: String = ""
|
||||
override val keyStorePassword: String = "dummy"
|
||||
override val trustStorePassword: String = "trustpass"
|
||||
override val certificateSigningService: HostAndPort = HostAndPort.fromParts("localhost", 0)
|
||||
override val dataSourceProperties = makeTestDataSourceProperties()
|
||||
}
|
||||
|
||||
@ -173,7 +168,6 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
|
||||
override val exportJMXto: String = ""
|
||||
override val keyStorePassword: String = "dummy"
|
||||
override val trustStorePassword: String = "trustpass"
|
||||
override val certificateSigningService: HostAndPort = HostAndPort.fromParts("localhost", 0)
|
||||
override val dataSourceProperties = makeTestDataSourceProperties()
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package com.r3corda.testing
|
||||
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.contracts.clauses.Clause
|
||||
import com.r3corda.core.contracts.clauses.FilterOn
|
||||
import com.r3corda.core.contracts.clauses.verifyClause
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import java.security.PublicKey
|
||||
@ -9,9 +10,9 @@ import java.security.PublicKey
|
||||
class DummyLinearContract: Contract {
|
||||
override val legalContractReference: SecureHash = SecureHash.sha256("Test")
|
||||
|
||||
val clause: Clause<ContractState, CommandData, Unit> = LinearState.ClauseVerifier(State::class.java)
|
||||
val clause: Clause<State, CommandData, Unit> = LinearState.ClauseVerifier()
|
||||
override fun verify(tx: TransactionForContract) = verifyClause(tx,
|
||||
clause,
|
||||
FilterOn(clause, { states -> states.filterIsInstance<State>() }),
|
||||
emptyList())
|
||||
|
||||
class State(
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.r3corda.testing.node
|
||||
|
||||
import com.google.common.jimfs.Jimfs
|
||||
import com.google.common.net.HostAndPort
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.r3corda.core.crypto.Party
|
||||
@ -194,7 +193,6 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
|
||||
override val keyStorePassword: String = "dummy"
|
||||
override val trustStorePassword: String = "trustpass"
|
||||
override val dataSourceProperties: Properties get() = makeTestDataSourceProperties("node_${id}_net_$networkId")
|
||||
override val certificateSigningService: HostAndPort = HostAndPort.fromParts("localhost", 0)
|
||||
}
|
||||
val node = nodeFactory.create(config, this, networkMapAddress, advertisedServices.toSet(), id, keyPair)
|
||||
if (start) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user