Added new project core.

Project Contracts depends only on core.
This commit is contained in:
Sofus Mortensen 2016-02-16 15:10:51 +01:00 committed by Mike Hearn
parent 00977c0d83
commit 994abb3edb
25 changed files with 227 additions and 201 deletions

3
.gitignore vendored
View File

@ -4,6 +4,8 @@ TODO
.gradle
/build/
/contracts/build
/core/build
/docs/build/doctrees
buyer
@ -63,4 +65,3 @@ atlassian-ide-plugin.xml
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties

View File

@ -55,6 +55,8 @@ configurations.all() {
dependencies {
testCompile 'junit:junit:4.12'
compile project(':contracts')
compile "com.google.code.findbugs:jsr305:3.0.1"
compile "org.slf4j:slf4j-jdk14:1.7.13"

View File

@ -70,5 +70,5 @@ repositories {
}
dependencies {
compile rootProject
compile project(':core')
}

View File

@ -1,160 +0,0 @@
/*
* Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
* pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
* set forth therein.
*
* All other rights reserved.
*/
package contracts
import core.*
import core.crypto.NullPublicKey
import core.crypto.SecureHash
import core.crypto.toStringShort
import core.utilities.Emoji
import java.security.PublicKey
import java.time.Instant
/**
* This is an ultra-trivial implementation of commercial paper, which is essentially a simpler version of a corporate
* bond. It can be seen as a company-specific currency. A company issues CP with a particular face value, say $100,
* but sells it for less, say $90. The paper can be redeemed for cash at a given date in the future. Thus this example
* would have a 10% interest rate with a single repayment. Commercial paper is often rolled over (the maturity date
* is adjusted as if the paper was redeemed and immediately repurchased, but without having to front the cash).
*
* This contract is not intended to realistically model CP. It is here only to act as a next step up above cash in
* the prototyping phase. It is thus very incomplete.
*
* Open issues:
* - In this model, you cannot merge or split CP. Can you do this normally? We could model CP as a specialised form
* of cash, or reuse some of the cash code? Waiting on response from Ayoub and Rajar about whether CP can always
* be split/merged or only in secondary markets. Even if current systems can't do this, would it be a desirable
* feature to have anyway?
* - The funding steps of CP is totally ignored in this model.
* - No attention is paid to the existing roles of custodians, funding banks, etc.
* - There are regional variations on the CP concept, for instance, American CP requires a special "CUSIP number"
* which may need to be tracked. That, in turn, requires validation logic (there is a bean validator that knows how
* to do this in the Apache BVal project).
*/
val CP_PROGRAM_ID = SecureHash.sha256("replace-me-later-with-bytecode-hash")
// TODO: Generalise the notion of an owned instrument into a superclass/supercontract. Consider composition vs inheritance.
class CommercialPaper : Contract {
// TODO: should reference the content of the legal agreement, not its URI
override val legalContractReference: SecureHash = SecureHash.sha256("https://en.wikipedia.org/wiki/Commercial_paper")
data class State(
val issuance: PartyReference,
override val owner: PublicKey,
val faceValue: Amount,
val maturityDate: Instant
) : OwnableState, ICommercialPaperState {
override val programRef = CP_PROGRAM_ID
fun withoutOwner() = copy(owner = NullPublicKey)
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
override fun toString() = "${Emoji.newspaper}CommercialPaper(of $faceValue redeemable on $maturityDate by '$issuance', owned by ${owner.toStringShort()})"
// Although kotlin is smart enough not to need these, as we are using the ICommercialPaperState, we need to declare them explicitly for use later,
override fun withOwner(newOwner: PublicKey): ICommercialPaperState = copy(owner = newOwner)
override fun withIssuance(newIssuance: PartyReference): ICommercialPaperState = copy(issuance = newIssuance)
override fun withFaceValue(newFaceValue: Amount): ICommercialPaperState = copy(faceValue = newFaceValue)
override fun withMaturityDate(newMaturityDate: Instant): ICommercialPaperState = copy(maturityDate = newMaturityDate)
}
interface Commands : CommandData {
class Move : TypeOnlyCommandData(), Commands
class Redeem : TypeOnlyCommandData(), Commands
// We don't need a nonce in the issue command, because the issuance.reference field should already be unique per CP.
// However, nothing in the platform enforces that uniqueness: it's up to the issuer.
class Issue : TypeOnlyCommandData(), Commands
}
override fun verify(tx: TransactionForVerification) {
// Group by everything except owner: any modification to the CP at all is considered changing it fundamentally.
val groups = tx.groupStates<State>() { it.withoutOwner() }
// There are two possible things that can be done with this CP. The first is trading it. The second is redeeming
// it for cash on or after the maturity date.
val command = tx.commands.requireSingleCommand<CommercialPaper.Commands>()
val timestamp: TimestampCommand? = tx.getTimestampBy(DummyTimestampingAuthority.identity)
for (group in groups) {
when (command.value) {
is Commands.Move -> {
val input = group.inputs.single()
requireThat {
"the transaction is signed by the owner of the CP" by (command.signers.contains(input.owner))
"the state is propagated" by (group.outputs.size == 1)
}
}
is Commands.Redeem -> {
val input = group.inputs.single()
val received = tx.outStates.sumCashBy(input.owner)
val time = timestamp?.after ?: throw IllegalArgumentException("Redemptions must be timestamped")
requireThat {
"the paper must have matured" by (time > input.maturityDate)
"the received amount equals the face value" by (received == input.faceValue)
"the paper must be destroyed" by group.outputs.isEmpty()
"the transaction is signed by the owner of the CP" by (command.signers.contains(input.owner))
}
}
is Commands.Issue -> {
val output = group.outputs.single()
val time = timestamp?.before ?: throw IllegalArgumentException("Issuances must be timestamped")
requireThat {
// Don't allow people to issue commercial paper under other entities identities.
"the issuance is signed by the claimed issuer of the paper" by
(command.signers.contains(output.issuance.party.owningKey))
"the face value is not zero" by (output.faceValue.pennies > 0)
"the maturity date is not in the past" by (time < output.maturityDate)
// Don't allow an existing CP state to be replaced by this issuance.
"there is no input state" by group.inputs.isEmpty()
}
}
// TODO: Think about how to evolve contracts over time with new commands.
else -> throw IllegalArgumentException("Unrecognised command")
}
}
}
/**
* Returns a transaction that issues commercial paper, owned by the issuing parties key. Does not update
* an existing transaction because you aren't able to issue multiple pieces of CP in a single transaction
* at the moment: this restriction is not fundamental and may be lifted later.
*/
fun generateIssue(issuance: PartyReference, faceValue: Amount, maturityDate: Instant): TransactionBuilder {
val state = State(issuance, issuance.party.owningKey, faceValue, maturityDate)
return TransactionBuilder().withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
}
/**
* Updates the given partial transaction with an input/output/command to reassign ownership of the paper.
*/
fun generateMove(tx: TransactionBuilder, paper: StateAndRef<State>, newOwner: PublicKey) {
tx.addInputState(paper.ref)
tx.addOutputState(paper.state.copy(owner = newOwner))
tx.addCommand(Commands.Move(), paper.state.owner)
}
/**
* Intended to be called by the issuer of some commercial paper, when an owner has notified us that they wish
* to redeem the paper. We must therefore send enough money to the key that owns the paper to satisfy the face
* value, and then ensure the paper is removed from the ledger.
*
* @throws InsufficientBalanceException if the wallet doesn't contain enough money to pay the redeemer
*/
@Throws(InsufficientBalanceException::class)
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, wallet: List<StateAndRef<Cash.State>>) {
// Add the cash movement using the states in our wallet.
Cash().generateSpend(tx, paper.state.faceValue, paper.state.owner, wallet)
tx.addInputState(paper.ref)
tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.owner)
}
}

23
core/build.gradle Normal file
View File

@ -0,0 +1,23 @@
group 'com.r3cev.prototyping'
version '1.0-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'kotlin'
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
compile "org.slf4j:slf4j-jdk14:1.7.13"
compile("com.google.guava:guava:19.0")
// Quasar: for the bytecode rewriting for state machines.
compile("co.paralleluniverse:quasar-core:${quasar_version}:jdk8")
// quasar("co.paralleluniverse:quasar-core:${quasar_version}:jdk8@jar")
}

View File

@ -0,0 +1,13 @@
package core
import core.Party
import java.security.PublicKey
/**
* An identity service maintains an bidirectional map of [Party]s to their associated public keys and thus supports
* lookup of a party given its key. This is obviously very incomplete and does not reflect everything a real identity
* service would provide.
*/
interface IdentityService {
fun partyFromKey(key: PublicKey): Party?
}

View File

@ -0,0 +1,29 @@
package core
import co.paralleluniverse.fibers.Suspendable
import core.crypto.DigitalSignature
import core.crypto.generateKeyPair
import core.serialization.SerializedBytes
/**
* Simple interface (for testing) to an abstract timestamping service. Note that this is not "timestamping" in the
* blockchain sense of a total ordering of transactions, but rather, a signature from a well known/trusted timestamping
* service over a transaction that indicates the timestamp in it is accurate. Such a signature may not always be
* necessary: if there are multiple parties involved in a transaction then they can cross-check the timestamp
* themselves.
*/
interface TimestamperService {
@Suspendable
fun timestamp(wtxBytes: SerializedBytes<WireTransaction>): DigitalSignature.LegallyIdentifiable
/** The name+pubkey that this timestamper will sign with. */
val identity: Party
}
// Smart contracts may wish to specify explicitly which timestamping authorities are trusted to assert the time.
// We define a dummy authority here to allow to us to develop prototype contracts in the absence of a real authority.
// The timestamper itself is implemented in the unit test part of the code (in TestUtils.kt).
object DummyTimestampingAuthority {
val key = generateKeyPair()
val identity = Party("The dummy timestamper", key.public)
}

View File

@ -0,0 +1,139 @@
/*
* Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
* pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
* set forth therein.
*
* All other rights reserved.
*/
package core
import core.crypto.SecureHash
import java.util.*
class TransactionResolutionException(val hash: SecureHash) : Exception()
class TransactionConflictException(val conflictRef: StateRef, val tx1: LedgerTransaction, val tx2: LedgerTransaction) : Exception()
/**
* A TransactionGroup defines a directed acyclic graph of transactions that can be resolved with each other and then
* verified. Successful verification does not imply the non-existence of other conflicting transactions: simply that
* this subgraph does not contain conflicts and is accepted by the involved contracts.
*
* The inputs of the provided transactions must be resolvable either within the [transactions] set, or from the
* [nonVerifiedRoots] set. Transactions in the non-verified set are ignored other than for looking up input states.
*/
class TransactionGroup(val transactions: Set<LedgerTransaction>, val nonVerifiedRoots: Set<LedgerTransaction>) {
/**
* Verifies the group and returns the set of resolved transactions.
*/
fun verify(programMap: ContractFactory): Set<TransactionForVerification> {
// Check that every input can be resolved to an output.
// Check that no output is referenced by more than one input.
// Cycles should be impossible due to the use of hashes as pointers.
check(transactions.intersect(nonVerifiedRoots).isEmpty())
val hashToTXMap: Map<SecureHash, List<LedgerTransaction>> = (transactions + nonVerifiedRoots).groupBy { it.hash }
val refToConsumingTXMap = hashMapOf<StateRef, LedgerTransaction>()
val resolved = HashSet<TransactionForVerification>(transactions.size)
for (tx in transactions) {
val inputs = ArrayList<ContractState>(tx.inputs.size)
for (ref in tx.inputs) {
val conflict = refToConsumingTXMap[ref]
if (conflict != null)
throw TransactionConflictException(ref, tx, conflict)
refToConsumingTXMap[ref] = tx
// Look up the connecting transaction.
val ltx = hashToTXMap[ref.txhash]?.single() ?: throw TransactionResolutionException(ref.txhash)
// Look up the output in that transaction by index.
inputs.add(ltx.outputs[ref.index])
}
resolved.add(TransactionForVerification(inputs, tx.outputs, tx.commands, tx.hash))
}
for (tx in resolved)
tx.verify(programMap)
return resolved
}
}
/** A transaction in fully resolved and sig-checked form, ready for passing as input to a verification function. */
data class TransactionForVerification(val inStates: List<ContractState>,
val outStates: List<ContractState>,
val commands: List<AuthenticatedObject<CommandData>>,
val origHash: SecureHash) {
override fun hashCode() = origHash.hashCode()
override fun equals(other: Any?) = other is TransactionForVerification && other.origHash == origHash
/**
* @throws TransactionVerificationException if a contract throws an exception, the original is in the cause field
* @throws IllegalStateException if a state refers to an unknown contract.
*/
@Throws(TransactionVerificationException::class, IllegalStateException::class)
fun verify(programMap: ContractFactory) {
// For each input and output state, locate the program to run. Then execute the verification function. If any
// throws an exception, the entire transaction is invalid.
val programHashes = (inStates.map { it.programRef } + outStates.map { it.programRef }).toSet()
for (hash in programHashes) {
val program: Contract = programMap[hash]
try {
program.verify(this)
} catch(e: Throwable) {
throw TransactionVerificationException(this, program, e)
}
}
}
/**
* Utilities for contract writers to incorporate into their logic.
*/
data class InOutGroup<T : ContractState>(val inputs: List<T>, val outputs: List<T>)
// A shortcut to make IDE auto-completion more intuitive for Java users.
fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority)
// For Java users.
fun <T : ContractState> groupStates(ofType: Class<T>, selector: (T) -> Any): List<InOutGroup<T>> {
val inputs = inStates.filterIsInstance(ofType)
val outputs = outStates.filterIsInstance(ofType)
val inGroups = inputs.groupBy(selector)
val outGroups = outputs.groupBy(selector)
@Suppress("DEPRECATION")
return groupStatesInternal(inGroups, outGroups)
}
// For Kotlin users: this version has nicer syntax and avoids reflection/object creation for the lambda.
inline fun <reified T : ContractState> groupStates(selector: (T) -> Any): List<InOutGroup<T>> {
val inputs = inStates.filterIsInstance<T>()
val outputs = outStates.filterIsInstance<T>()
val inGroups = inputs.groupBy(selector)
val outGroups = outputs.groupBy(selector)
@Suppress("DEPRECATION")
return groupStatesInternal(inGroups, outGroups)
}
@Deprecated("Do not use this directly: exposed as public only due to function inlining")
fun <T : ContractState> groupStatesInternal(inGroups: Map<Any, List<T>>, outGroups: Map<Any, List<T>>): List<InOutGroup<T>> {
val result = ArrayList<InOutGroup<T>>()
for ((k, v) in inGroups.entries)
result.add(InOutGroup(v, outGroups[k] ?: emptyList()))
for ((k, v) in outGroups.entries) {
if (inGroups[k] == null)
result.add(InOutGroup(emptyList(), v))
}
return result
}
}
/** Thrown if a verification fails due to a contract rejection. */
class TransactionVerificationException(val tx: TransactionForVerification, val contract: Contract, cause: Throwable?) : Exception(cause)

View File

@ -0,0 +1,15 @@
package core.node
sealed class TimestampingError : Exception() {
class RequiresExactlyOneCommand : TimestampingError()
/**
* Thrown if an attempt is made to timestamp a transaction using a trusted timestamper, but the time on the
* transaction is too far in the past or future relative to the local clock and thus the timestamper would reject
* it.
*/
class NotOnTimeException : TimestampingError()
/** Thrown if the command in the transaction doesn't list this timestamping authorities public key as a signer */
class NotForMe : TimestampingError()
}

View File

@ -1,2 +1,3 @@
rootProject.name = 'r3prototyping'
include 'contracts'
include 'core'

View File

@ -64,30 +64,6 @@ interface KeyManagementService {
fun freshKey(): KeyPair
}
/**
* An identity service maintains an bidirectional map of [Party]s to their associated public keys and thus supports
* lookup of a party given its key. This is obviously very incomplete and does not reflect everything a real identity
* service would provide.
*/
interface IdentityService {
fun partyFromKey(key: PublicKey): Party?
}
/**
* Simple interface (for testing) to an abstract timestamping service. Note that this is not "timestamping" in the
* blockchain sense of a total ordering of transactions, but rather, a signature from a well known/trusted timestamping
* service over a transaction that indicates the timestamp in it is accurate. Such a signature may not always be
* necessary: if there are multiple parties involved in a transaction then they can cross-check the timestamp
* themselves.
*/
interface TimestamperService {
@Suspendable
fun timestamp(wtxBytes: SerializedBytes<WireTransaction>): DigitalSignature.LegallyIdentifiable
/** The name+pubkey that this timestamper will sign with. */
val identity: Party
}
// Smart contracts may wish to specify explicitly which timestamping authorities are trusted to assert the time.
// We define a dummy authority here to allow to us to develop prototype contracts in the absence of a real authority.
// The timestamper itself is implemented in the unit test part of the code (in TestUtils.kt).

View File

@ -31,19 +31,6 @@ class TimestampingMessages {
data class Request(val tx: SerializedBytes<WireTransaction>, val replyTo: MessageRecipients, val replyToTopic: String)
}
sealed class TimestampingError : Exception() {
class RequiresExactlyOneCommand : TimestampingError()
/**
* Thrown if an attempt is made to timestamp a transaction using a trusted timestamper, but the time on the
* transaction is too far in the past or future relative to the local clock and thus the timestamper would reject
* it.
*/
class NotOnTimeException : TimestampingError()
/** Thrown if the command in the transaction doesn't list this timestamping authorities public key as a signer */
class NotForMe : TimestampingError()
}
/**
* This class implements the server side of the timestamping protocol, using the local clock. A future version might
* add features like checking against other NTP servers to make sure the clock hasn't drifted by too much.