Added a basic Notary service with protocol for signing transactions

This commit is contained in:
Andrius Dagys 2016-04-12 17:30:13 +01:00
parent 539e23a0b1
commit fa3f7e7fa6
58 changed files with 1003 additions and 840 deletions

View File

@ -3,7 +3,7 @@
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="demos.TraderDemoKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar -Dco.paralleluniverse.fibers.verifyInstrumentation" />
<option name="PROGRAM_PARAMETERS" value="--dir=seller --fake-trade-with=localhost --network-address=localhost:31327 --timestamper-identity-file=buyer/identity-public --timestamper-address=localhost" />
<option name="PROGRAM_PARAMETERS" value="--dir=seller --fake-trade-with=localhost --network-address=localhost:31340 --network-map-identity-file=buyer/identity-public --network-map-address=localhost" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />

View File

@ -9,9 +9,6 @@
package contracts.isolated
import core.*
import core.Contract
import core.ContractState
import core.TransactionForVerification
import core.crypto.SecureHash
// The dummy contract doesn't do anything useful. It exists for testing purposes.
@ -19,7 +16,7 @@ import core.crypto.SecureHash
val ANOTHER_DUMMY_PROGRAM_ID = AnotherDummyContract()
class AnotherDummyContract : Contract, core.node.DummyContractBackdoor {
class State(val magicNumber: Int = 0) : ContractState {
class State(val magicNumber: Int = 0, override val notary: Party) : ContractState {
override val contract = ANOTHER_DUMMY_PROGRAM_ID
}
@ -34,11 +31,11 @@ class AnotherDummyContract : Contract, core.node.DummyContractBackdoor {
// The "empty contract"
override val legalContractReference: SecureHash = SecureHash.sha256("https://anotherdummy.org")
override fun generateInitial(owner: PartyAndReference, magicNumber: Int) : TransactionBuilder {
val state = State(magicNumber)
return TransactionBuilder().withItems( state, Command(Commands.Create(), owner.party.owningKey) )
override fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber, notary)
return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey))
}
override fun inspectState(state: core.ContractState) : Int = (state as State).magicNumber
override fun inspectState(state: core.ContractState): Int = (state as State).magicNumber
}

View File

@ -1,7 +1,7 @@
package core.node
interface DummyContractBackdoor {
fun generateInitial(owner: core.PartyAndReference, magicNumber: Int) : core.TransactionBuilder
fun generateInitial(owner: core.PartyAndReference, magicNumber: Int, notary: core.Party): core.TransactionBuilder
fun inspectState(state: core.ContractState) : Int
fun inspectState(state: core.ContractState): Int
}

View File

@ -1,17 +1,18 @@
package contracts;
import core.*;
import core.TransactionForVerification.*;
import core.crypto.*;
import core.node.services.*;
import org.jetbrains.annotations.*;
import core.TransactionForVerification.InOutGroup;
import core.crypto.NullPublicKey;
import core.crypto.SecureHash;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.security.*;
import java.time.*;
import java.util.*;
import java.security.PublicKey;
import java.time.Instant;
import java.util.List;
import static core.ContractsDSLKt.*;
import static kotlin.collections.CollectionsKt.*;
import static core.ContractsDSLKt.requireSingleCommand;
import static kotlin.collections.CollectionsKt.single;
/**
@ -27,35 +28,37 @@ public class JavaCommercialPaper implements Contract {
private PublicKey owner;
private Amount faceValue;
private Instant maturityDate;
private Party notary;
public State() {
} // For serialization
public State(PartyAndReference issuance, PublicKey owner, Amount faceValue, Instant maturityDate) {
public State(PartyAndReference issuance, PublicKey owner, Amount faceValue, Instant maturityDate, Party notary) {
this.issuance = issuance;
this.owner = owner;
this.faceValue = faceValue;
this.maturityDate = maturityDate;
this.notary = notary;
}
public State copy() {
return new State(this.issuance, this.owner, this.faceValue, this.maturityDate);
return new State(this.issuance, this.owner, this.faceValue, this.maturityDate, this.notary);
}
public ICommercialPaperState withOwner(PublicKey newOwner) {
return new State(this.issuance, newOwner, this.faceValue, this.maturityDate);
return new State(this.issuance, newOwner, this.faceValue, this.maturityDate, this.notary);
}
public ICommercialPaperState withIssuance(PartyAndReference newIssuance) {
return new State(newIssuance, this.owner, this.faceValue, this.maturityDate);
return new State(newIssuance, this.owner, this.faceValue, this.maturityDate, this.notary);
}
public ICommercialPaperState withFaceValue(Amount newFaceValue) {
return new State(this.issuance, this.owner, newFaceValue, this.maturityDate);
return new State(this.issuance, this.owner, newFaceValue, this.maturityDate, this.notary);
}
public ICommercialPaperState withMaturityDate(Instant newMaturityDate) {
return new State(this.issuance, this.owner, this.faceValue, newMaturityDate);
return new State(this.issuance, this.owner, this.faceValue, newMaturityDate, this.notary);
}
public PartyAndReference getIssuance() {
@ -74,6 +77,12 @@ public class JavaCommercialPaper implements Contract {
return maturityDate;
}
@NotNull
@Override
public Party getNotary() {
return notary;
}
@NotNull
@Override
public Contract getContract() {
@ -91,8 +100,8 @@ public class JavaCommercialPaper implements Contract {
if (issuance != null ? !issuance.equals(state.issuance) : state.issuance != null) return false;
if (owner != null ? !owner.equals(state.owner) : state.owner != null) return false;
if (faceValue != null ? !faceValue.equals(state.faceValue) : state.faceValue != null) return false;
if (notary != null ? !notary.equals(state.notary) : state.notary != null) return false;
return !(maturityDate != null ? !maturityDate.equals(state.maturityDate) : state.maturityDate != null);
}
@Override
@ -101,11 +110,12 @@ public class JavaCommercialPaper implements Contract {
result = 31 * result + (owner != null ? owner.hashCode() : 0);
result = 31 * result + (faceValue != null ? faceValue.hashCode() : 0);
result = 31 * result + (maturityDate != null ? maturityDate.hashCode() : 0);
result = 31 * result + (notary != null ? notary.hashCode() : 0);
return result;
}
public State withoutOwner() {
return new State(issuance, NullPublicKey.INSTANCE, faceValue, maturityDate);
return new State(issuance, NullPublicKey.INSTANCE, faceValue, maturityDate, notary);
}
}
@ -158,7 +168,7 @@ public class JavaCommercialPaper implements Contract {
throw new IllegalStateException("Failed Requirement: the face value is not zero");
}
TimestampCommand timestampCommand = tx.getTimestampBy(DummyTimestampingAuthority.INSTANCE.getIdentity());
TimestampCommand timestampCommand = tx.getTimestampByName("Notary Service");
if (timestampCommand == null)
throw new IllegalArgumentException("Failed Requirement: must be timestamped");
@ -188,7 +198,7 @@ public class JavaCommercialPaper implements Contract {
!output.getMaturityDate().equals(input.getMaturityDate()))
throw new IllegalStateException("Failed requirement: the output state is the same as the input state except for owner");
} else if (cmd.getValue() instanceof JavaCommercialPaper.Commands.Redeem) {
TimestampCommand timestampCommand = tx.getTimestampBy(DummyTimestampingAuthority.INSTANCE.getIdentity());
TimestampCommand timestampCommand = tx.getTimestampByName("Notary Service");
if (timestampCommand == null)
throw new IllegalArgumentException("Failed Requirement: must be timestamped");
Instant time = timestampCommand.getBefore();
@ -215,8 +225,8 @@ public class JavaCommercialPaper implements Contract {
return SecureHash.sha256("https://en.wikipedia.org/wiki/Commercial_paper");
}
public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount faceValue, @Nullable Instant maturityDate) {
State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate);
public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount faceValue, @Nullable Instant maturityDate, @NotNull Party notary) {
State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate, notary);
return new TransactionBuilder().withItems(state, new Command(new Commands.Issue(), issuance.getParty().getOwningKey()));
}
@ -228,7 +238,7 @@ public class JavaCommercialPaper implements Contract {
public void generateMove(TransactionBuilder tx, StateAndRef<State> paper, PublicKey newOwner) {
tx.addInputState(paper.getRef());
tx.addOutputState(new State(paper.getState().getIssuance(), newOwner, paper.getState().getFaceValue(), paper.getState().getMaturityDate()));
tx.addOutputState(new State(paper.getState().getIssuance(), newOwner, paper.getState().getFaceValue(), paper.getState().getMaturityDate(), paper.getState().getNotary()));
tx.addCommand(new Command(new Commands.Move(), paper.getState().getOwner()));
}
}

View File

@ -62,7 +62,9 @@ class Cash : Contract {
override val amount: Amount,
/** There must be a MoveCommand signed by this key to claim the amount */
override val owner: PublicKey
override val owner: PublicKey,
override val notary: Party
) : CommonCashState<Cash.IssuanceDefinition> {
override val issuanceDef: Cash.IssuanceDefinition
get() = Cash.IssuanceDefinition(deposit, amount.currency)
@ -160,16 +162,16 @@ class Cash : Contract {
/**
* Puts together an issuance transaction from the given template, that starts out being owned by the given pubkey.
*/
fun generateIssue(tx: TransactionBuilder, issuanceDef: CashIssuanceDefinition, pennies: Long, owner: PublicKey)
= generateIssue(tx, Amount(pennies, issuanceDef.currency), issuanceDef.deposit, owner)
fun generateIssue(tx: TransactionBuilder, issuanceDef: CashIssuanceDefinition, pennies: Long, owner: PublicKey, notary: Party)
= generateIssue(tx, Amount(pennies, issuanceDef.currency), issuanceDef.deposit, owner, notary)
/**
* Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.
*/
fun generateIssue(tx: TransactionBuilder, amount: Amount, at: PartyAndReference, owner: PublicKey) {
fun generateIssue(tx: TransactionBuilder, amount: Amount, at: PartyAndReference, owner: PublicKey, notary: Party) {
check(tx.inputStates().isEmpty())
check(tx.outputStates().sumCashOrNull() == null)
tx.addOutputState(Cash.State(at, amount, owner))
tx.addOutputState(Cash.State(at, amount, owner, notary))
tx.addCommand(Cash.Commands.Issue(), at.party.owningKey)
}
@ -230,7 +232,7 @@ class Cash : Contract {
val states = gathered.groupBy { it.state.deposit }.map {
val (deposit, coins) = it
val totalAmount = coins.map { it.state.amount }.sumOrThrow()
State(deposit, totalAmount, to)
State(deposit, totalAmount, to, coins.first().state.notary)
}
val outputs = if (change.pennies > 0) {
@ -241,7 +243,7 @@ class Cash : Contract {
// Add a change output and adjust the last output downwards.
states.subList(0, states.lastIndex) +
states.last().let { it.copy(amount = it.amount - change) } +
State(gathered.last().state.deposit, change, changeKey)
State(gathered.last().state.deposit, change, changeKey, gathered.last().state.notary)
} else states
for (state in gathered) tx.addInputState(state.ref)

View File

@ -41,7 +41,8 @@ class CommercialPaper : Contract {
val issuance: PartyAndReference,
override val owner: PublicKey,
val faceValue: Amount,
val maturityDate: Instant
val maturityDate: Instant,
override val notary: Party
) : OwnableState, ICommercialPaperState {
override val contract = CP_PROGRAM_ID
@ -76,7 +77,7 @@ class CommercialPaper : Contract {
// Here, we match acceptable timestamp authorities by name. The list of acceptable TSAs (oracles) must be
// hard coded into the contract because otherwise we could fail to gain consensus, if nodes disagree about
// who or what is a trusted authority.
val timestamp: TimestampCommand? = tx.commands.getTimestampByName("Mock Company 0", "Timestamping Service", "Bank A")
val timestamp: TimestampCommand? = tx.commands.getTimestampByName("Mock Company 0", "Notary Service", "Bank A")
for ((inputs, outputs, key) in groups) {
when (command.value) {
@ -129,8 +130,8 @@ class CommercialPaper : Contract {
* 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: PartyAndReference, faceValue: Amount, maturityDate: Instant): TransactionBuilder {
val state = State(issuance, issuance.party.owningKey, faceValue, maturityDate)
fun generateIssue(issuance: PartyAndReference, faceValue: Amount, maturityDate: Instant, notary: Party): TransactionBuilder {
val state = State(issuance, issuance.party.owningKey, faceValue, maturityDate, notary)
return TransactionBuilder().withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
}

View File

@ -2,7 +2,6 @@ package contracts
import core.*
import core.crypto.SecureHash
import core.node.services.DummyTimestampingAuthority
import java.security.PublicKey
import java.time.Instant
import java.util.*
@ -46,6 +45,7 @@ class CrowdFund : Contract {
data class State(
val campaign: Campaign,
override val notary: Party,
val closed: Boolean = false,
val pledges: List<Pledge> = ArrayList()
) : ContractState {
@ -74,7 +74,7 @@ class CrowdFund : Contract {
val outputCrowdFund: CrowdFund.State = tx.outStates.filterIsInstance<CrowdFund.State>().single()
val outputCash: List<Cash.State> = tx.outStates.filterIsInstance<Cash.State>()
val time = tx.getTimestampBy(DummyTimestampingAuthority.identity)?.midpoint
val time = tx.commands.getTimestampByName("Notary Service")?.midpoint
if (time == null) throw IllegalArgumentException("must be timestamped")
when (command.value) {
@ -136,9 +136,9 @@ class CrowdFund : Contract {
* Returns a transaction that registers a crowd-funding campaing, owned by the issuing institution's key. Does not update
* an existing transaction because it's not possible to register multiple campaigns in a single transaction
*/
fun generateRegister(owner: PartyAndReference, fundingTarget: Amount, fundingName: String, closingTime: Instant): TransactionBuilder {
fun generateRegister(owner: PartyAndReference, fundingTarget: Amount, fundingName: String, closingTime: Instant, notary: Party): TransactionBuilder {
val campaign = Campaign(owner = owner.party.owningKey, name = fundingName, target = fundingTarget, closingTime = closingTime)
val state = State(campaign)
val state = State(campaign, notary)
return TransactionBuilder().withItems(state, Command(Commands.Register(), owner.party.owningKey))
}

View File

@ -8,7 +8,8 @@ import core.crypto.SecureHash
val DUMMY_PROGRAM_ID = DummyContract()
class DummyContract : Contract {
class State(val magicNumber: Int = 0) : ContractState {
class State(val magicNumber: Int = 0,
override val notary: Party) : ContractState {
override val contract = DUMMY_PROGRAM_ID
}
@ -23,8 +24,8 @@ class DummyContract : Contract {
// The "empty contract"
override val legalContractReference: SecureHash = SecureHash.sha256("")
fun generateInitial(owner: PartyAndReference, magicNumber: Int) : TransactionBuilder {
val state = State(magicNumber)
return TransactionBuilder().withItems( state, Command(Commands.Create(), owner.party.owningKey) )
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber, notary)
return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey))
}
}

View File

@ -492,7 +492,7 @@ class InterestRateSwap() : Contract {
val groups = tx.groupStates() { state: InterestRateSwap.State -> state.common.tradeID }
val command = tx.commands.requireSingleCommand<InterestRateSwap.Commands>()
val time = tx.commands.getTimestampByName("Mock Company 0", "Timestamping Service", "Bank A")?.midpoint
val time = tx.commands.getTimestampByName("Mock Company 0", "Notary Service", "Bank A")?.midpoint
if (time == null) throw IllegalArgumentException("must be timestamped")
for ((inputs, outputs, key) in groups) {
@ -586,7 +586,8 @@ class InterestRateSwap() : Contract {
val fixedLeg: FixedLeg,
val floatingLeg: FloatingLeg,
val calculation: Calculation,
val common: Common
val common: Common,
override val notary: Party
) : FixableDealState {
override val contract = IRS_PROGRAM_ID
@ -616,7 +617,7 @@ class InterestRateSwap() : Contract {
}
}
override fun generateAgreement(): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common)
override fun generateAgreement(): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, notary)
override fun generateFix(ptx: TransactionBuilder, oldStateRef: StateRef, fix: Fix) {
InterestRateSwap().generateFix(ptx, StateAndRef(this, oldStateRef), Pair(fix.of.forDay, Rate(RatioUnit(fix.value))))
@ -660,7 +661,8 @@ class InterestRateSwap() : Contract {
* This generates the agreement state and also the schedules from the initial data.
* Note: The day count, interest rate calculation etc are not finished yet, but they are demonstrable.
*/
fun generateAgreement(floatingLeg: FloatingLeg, fixedLeg: FixedLeg, calculation: Calculation, common: Common): TransactionBuilder {
fun generateAgreement(floatingLeg: FloatingLeg, fixedLeg: FixedLeg, calculation: Calculation,
common: Common, notary: Party): TransactionBuilder {
val fixedLegPaymentSchedule = HashMap<LocalDate, FixedRatePaymentEvent>()
var dates = BusinessCalendar.createGenericSchedule(fixedLeg.effectiveDate, fixedLeg.paymentFrequency, fixedLeg.paymentCalendar, fixedLeg.rollConvention, endDate = fixedLeg.terminationDate)
@ -709,7 +711,7 @@ class InterestRateSwap() : Contract {
val newCalculation = Calculation(calculation.expression, floatingLegPaymentSchedule, fixedLegPaymentSchedule)
// Put all the above into a new State object.
val state = State(fixedLeg, floatingLeg, newCalculation, common)
val state = State(fixedLeg, floatingLeg, newCalculation, common, notary)
return TransactionBuilder().withItems(state, Command(Commands.Agree(), listOf(state.floatingLeg.floatingRatePayer.owningKey, state.fixedLeg.fixedRatePayer.owningKey)))
}

View File

@ -23,10 +23,11 @@ interface NamedByHash {
* updated, instead, any changes must generate a new successor state.
*/
interface ContractState {
/**
* Contract by which the state belongs
*/
/** Contract by which the state belongs */
val contract: Contract
/** Identity of the notary that ensures this state is not used as an input to a transaction more than once */
val notary: Party
}
/**

View File

@ -1,15 +1,11 @@
package core
import co.paralleluniverse.fibers.Suspendable
import core.crypto.DigitalSignature
import core.crypto.SecureHash
import core.crypto.signWithECDSA
import core.node.services.TimestamperService
import core.node.services.TimestampingError
import core.serialization.serialize
import java.security.KeyPair
import java.security.PublicKey
import java.time.Clock
import java.time.Duration
import java.time.Instant
import java.util.*
@ -29,8 +25,8 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
/**
* Places a [TimestampCommand] in this transaction, removing any existing command if there is one.
* To get the right signature from the timestamping service, use the [timestamp] method after building is
* finished, or run use the [TimestampingProtocol] yourself.
* The command requires a signature from the Notary service, which acts as a Timestamp Authority.
* The signature can be obtained using [NotaryProtocol].
*
* The window of time in which the final timestamp may lie is defined as [time] +/- [timeTolerance].
* If you want a non-symmetrical time window you must add the command via [addCommand] yourself. The tolerance
@ -39,19 +35,19 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
* collaborating parties may therefore require a higher time tolerance than a transaction being built by a single
* node.
*/
fun setTime(time: Instant, authenticatedBy: Party, timeTolerance: Duration) {
fun setTime(time: Instant, authority: Party, timeTolerance: Duration) {
check(currentSigs.isEmpty()) { "Cannot change timestamp after signing" }
commands.removeAll { it.value is TimestampCommand }
addCommand(TimestampCommand(time, timeTolerance), authenticatedBy.owningKey)
addCommand(TimestampCommand(time, timeTolerance), authority.owningKey)
}
/** A more convenient way to add items to this transaction that calls the add* methods for you based on type */
fun withItems(vararg items: Any): TransactionBuilder {
for (t in items) {
when (t) {
is StateRef -> inputs.add(t)
is ContractState -> outputs.add(t)
is Command -> commands.add(t)
is StateRef -> addInputState(t)
is ContractState -> addOutputState(t)
is Command -> addCommand(t)
else -> throw IllegalArgumentException("Wrong argument type: ${t.javaClass}")
}
}
@ -63,7 +59,6 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
fun signWith(key: KeyPair) {
check(currentSigs.none { it.by == key.public }) { "This partial transaction was already signed by ${key.public}" }
check(commands.count { it.signers.contains(key.public) } > 0) { "Trying to sign with a key that isn't in any command" }
val data = toWireTransaction().serialize()
addSignatureUnchecked(key.signWithECDSA(data.bits))
}
@ -87,7 +82,7 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
* @throws IllegalArgumentException if the signature key doesn't appear in any command.
*/
fun checkSignature(sig: DigitalSignature.WithKey) {
require(commands.count { it.signers.contains(sig.by) } > 0) { "Signature key doesn't match any command" }
require(commands.any { it.signers.contains(sig.by) }) { "Signature key doesn't match any command" }
sig.verifyWithECDSA(toWireTransaction().serialized)
}
@ -96,32 +91,6 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
currentSigs.add(sig)
}
/**
* Uses the given timestamper service to request a signature over the WireTransaction be added. There must always be
* at least one such signature, but others may be added as well. You may want to have multiple redundant timestamps
* in the following cases:
*
* - Cross border contracts where local law says that only local timestamping authorities are acceptable.
* - Backup in case a TSA's signing key is compromised.
*
* The signature of the trusted timestamper merely asserts that the time field of this transaction is valid.
*/
@Suspendable
fun timestamp(timestamper: TimestamperService, clock: Clock = Clock.systemUTC()) {
val t = time ?: throw IllegalStateException("Timestamping requested but no time was inserted into the transaction")
// Obviously this is just a hard-coded dummy value for now.
val maxExpectedLatency = 5.seconds
if (Duration.between(clock.instant(), t.before) > maxExpectedLatency)
throw TimestampingError.NotOnTimeException()
// The timestamper may also throw NotOnTimeException if our clocks are desynchronised or if we are right on the
// boundary of t.notAfter and network latency pushes us over the edge. By "synchronised" here we mean relative
// to GPS time i.e. the United States Naval Observatory.
val sig = timestamper.timestamp(toWireTransaction().serialize())
addSignatureUnchecked(sig)
}
fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments),
ArrayList(outputs), ArrayList(commands))
@ -153,7 +122,6 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
fun addCommand(arg: Command) {
check(currentSigs.isEmpty())
// We should probably merge the lists of pubkeys for identical commands here.
commands.add(arg)
}

View File

@ -3,9 +3,6 @@ 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()
// TODO: Consider moving this out of the core module and providing a different way for unit tests to test contracts.
/**
@ -63,27 +60,37 @@ data class TransactionForVerification(val inStates: List<ContractState>,
override fun equals(other: Any?) = other is TransactionForVerification && other.origHash == origHash
/**
* Runs the contracts for this transaction.
* Verifies that the transaction is valid:
* - Checks that the input states and the timestamp point to the same Notary
* - Runs the contracts for this transaction. If any contract fails to verify, the whole transaction
* is considered to be invalid
*
* TODO: Move this out of the core data structure definitions, once unit tests are more cleanly separated.
*
* @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 if a contract throws an exception (the original is in the cause field)
* or the transaction has references to more than one Notary
*/
@Throws(TransactionVerificationException::class, IllegalStateException::class)
@Throws(TransactionVerificationException::class)
fun verify() {
// 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 programs = (inStates.map { it.contract } + outStates.map { it.contract }).toSet()
for (program in programs) {
verifySingleNotary()
val contracts = (inStates.map { it.contract } + outStates.map { it.contract }).toSet()
for (contract in contracts) {
try {
program.verify(this)
contract.verify(this)
} catch(e: Throwable) {
throw TransactionVerificationException(this, program, e)
throw TransactionVerificationException.ContractRejection(this, contract, e)
}
}
}
private fun verifySingleNotary() {
if (inStates.isEmpty()) return
val notary = inStates.first().notary
if (inStates.any { it.notary != notary }) throw TransactionVerificationException.MoreThanOneNotary(this)
val timestampCmd = commands.singleOrNull { it.value is TimestampCommand } ?: return
if (!timestampCmd.signers.contains(notary.owningKey)) throw TransactionVerificationException.MoreThanOneNotary(this)
}
/**
* Utilities for contract writers to incorporate into their logic.
*/
@ -99,6 +106,8 @@ data class TransactionForVerification(val inStates: List<ContractState>,
/** Simply calls [commands.getTimestampBy] as a shortcut to make code completion more intuitive. */
fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority)
/** Simply calls [commands.getTimestampByName] as a shortcut to make code completion more intuitive. */
fun getTimestampByName(vararg authorityName: String): TimestampCommand? = commands.getTimestampByName(*authorityName)
/**
* Given a type and a function that returns a grouping key, associates inputs and outputs together so that they
@ -152,5 +161,10 @@ data class TransactionForVerification(val inStates: List<ContractState>,
}
}
/** Thrown if a verification fails due to a contract rejection. */
class TransactionVerificationException(val tx: TransactionForVerification, val contract: Contract, cause: Throwable?) : Exception(cause)
class TransactionResolutionException(val hash: SecureHash) : Exception()
class TransactionConflictException(val conflictRef: StateRef, val tx1: LedgerTransaction, val tx2: LedgerTransaction) : Exception()
sealed class TransactionVerificationException(val tx: TransactionForVerification, cause: Throwable?) : Exception(cause) {
class ContractRejection(tx: TransactionForVerification, val contract: Contract, cause: Throwable?) : TransactionVerificationException(tx, cause)
class MoreThanOneNotary(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
}

View File

@ -107,7 +107,7 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
}
/**
* Verify the signatures, deserialise the wire transaction and then check that the set of signatures found matches
* Verify the signatures, deserialise the wire transaction and then check that the set of signatures found contains
* the set of pubkeys in the commands. If any signatures are missing, either throws an exception (by default) or
* returns the list of keys that have missing signatures, depending on the parameter.
*
@ -115,25 +115,34 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
*/
fun verify(throwIfSignaturesAreMissing: Boolean = true): Set<PublicKey> {
verifySignatures()
// Verify that every command key was in the set that we just verified: there should be no commands that were
// unverified.
val cmdKeys = tx.commands.flatMap { it.signers }.toSet()
val sigKeys = sigs.map { it.by }.toSet()
if (sigKeys == cmdKeys)
return emptySet()
val missing = cmdKeys - sigKeys
if (throwIfSignaturesAreMissing)
val missing = getMissingSignatures()
if (missing.isNotEmpty() && throwIfSignaturesAreMissing)
throw SignatureException("Missing signatures on transaction ${id.prefixChars()} for: ${missing.map { it.toStringShort() }}")
else
return missing
}
/** Returns the same transaction but with an additional (unchecked) signature */
fun withAdditionalSignature(sig: DigitalSignature.WithKey) = copy(sigs = sigs + sig)
fun withAdditionalSignature(sig: DigitalSignature.WithKey): SignedTransaction {
// TODO: need to make sure the Notary signs last
return copy(sigs = sigs + sig)
}
/** Alias for [withAdditionalSignature] to let you use Kotlin operator overloading. */
operator fun plus(sig: DigitalSignature.WithKey) = withAdditionalSignature(sig)
/**
* Returns the set of missing signatures - a signature must be present for every command pub key
* and the Notary (if it is specified)
*/
fun getMissingSignatures(): Set<PublicKey> {
val requiredKeys = tx.commands.flatMap { it.signers }.toSet()
val sigKeys = sigs.map { it.by }.toSet()
if (sigKeys.containsAll(requiredKeys)) return emptySet()
return requiredKeys - sigKeys
}
}
/**

View File

@ -57,4 +57,14 @@ interface ServiceHub {
txStorage.putAll(txns)
walletService.notifyAll(txs.map { it.tx })
}
/**
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState]
*
* @throws TransactionResolutionException if the [StateRef] points to a non-existent transaction
*/
fun loadState(stateRef: StateRef): ContractState {
val definingTx = storageService.validatedTransactions[stateRef.txhash] ?: throw TransactionResolutionException(stateRef.txhash)
return definingTx.tx.outputs[stateRef.index]
}
}

View File

@ -1,45 +0,0 @@
package core.node.services
import co.paralleluniverse.fibers.Suspendable
import core.Party
import core.WireTransaction
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 {
object Type : ServiceType("corda.timestamper")
@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("Mock Company 0", key.public)
}
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

@ -3,10 +3,17 @@ package core.node.subsystems
import com.google.common.util.concurrent.ListenableFuture
import core.Contract
import core.Party
import core.crypto.SecureHash
import core.messaging.MessagingService
import core.node.NodeInfo
import core.node.services.ServiceType
import core.node.services.*
import core.serialization.deserialize
import core.serialization.serialize
import org.slf4j.LoggerFactory
import java.security.PublicKey
import java.security.SignatureException
import java.util.*
import javax.annotation.concurrent.ThreadSafe
/**
* A network map contains lists of nodes on the network along with information about their identity keys, services
@ -21,23 +28,19 @@ interface NetworkMapCache {
/** A list of nodes that advertise a network map service */
val networkMapNodes: List<NodeInfo>
/** A list of nodes that advertise a timestamping service */
val timestampingNodes: List<NodeInfo>
/** A list of nodes that advertise a notary service */
val notaryNodes: List<NodeInfo>
/** A list of nodes that advertise a rates oracle service */
val ratesOracleNodes: List<NodeInfo>
/** A list of all nodes the cache is aware of */
val partyNodes: List<NodeInfo>
/** A list of nodes that advertise a regulatory service. Identifying the correct regulator for a trade is outwith
/**
* A list of nodes that advertise a regulatory service. Identifying the correct regulator for a trade is outside
* the scope of the network map service, and this is intended solely as a sanity check on configuration stored
* elsewhere.
*/
val regulators: List<NodeInfo>
/**
* Look up the node info for a party.
*/
fun nodeForPartyName(name: String): NodeInfo? = partyNodes.singleOrNull { it.identity.name == name }
/**
* Get a copy of all nodes in the map.
*/
@ -55,6 +58,16 @@ interface NetworkMapCache {
*/
fun getRecommended(type: ServiceType, contract: Contract, vararg party: Party): NodeInfo?
/**
* Look up the node info for a legal name.
*/
fun getNodeByLegalName(name: String): NodeInfo?
/**
* Look up the node info for a public key.
*/
fun getNodeByPublicKey(publicKey: PublicKey): NodeInfo?
/**
* Add a network map service; fetches a copy of the latest map from the service and subscribes to any further
* updates.

View File

@ -1,53 +0,0 @@
package protocols
import co.paralleluniverse.fibers.Suspendable
import core.WireTransaction
import core.crypto.DigitalSignature
import core.messaging.MessageRecipients
import core.node.NodeInfo
import core.protocols.ProtocolLogic
import core.random63BitValue
import core.serialization.SerializedBytes
import core.utilities.ProgressTracker
/**
* The TimestampingProtocol class is the client code that talks to a [NodeTimestamperService] on some remote node. It is a
* [ProtocolLogic], meaning it can either be a sub-protocol of some other protocol, or be driven independently.
*
* If you are not yourself authoring a protocol and want to timestamp something, the [TimestampingProtocol.Client] class
* implements the [TimestamperService] interface, meaning it can be passed to [TransactionBuilder.timestamp] to timestamp
* the built transaction. Please be aware that this will block, meaning it should not be used on a thread that is handling
* a network message: use it only from spare application threads that don't have to respond to anything.
*/
class TimestampingProtocol(private val node: NodeInfo,
private val wtxBytes: SerializedBytes<WireTransaction>,
override val progressTracker: ProgressTracker = TimestampingProtocol.tracker()) : ProtocolLogic<DigitalSignature.LegallyIdentifiable>() {
companion object {
object REQUESTING : ProgressTracker.Step("Requesting signature by timestamping service")
object VALIDATING : ProgressTracker.Step("Validating received signature from timestamping service")
fun tracker() = ProgressTracker(REQUESTING, VALIDATING)
val TOPIC = "platform.timestamping.request"
}
@Suspendable
override fun call(): DigitalSignature.LegallyIdentifiable {
progressTracker.currentStep = REQUESTING
val sessionID = random63BitValue()
val req = Request(wtxBytes, serviceHub.networkService.myAddress, sessionID)
val maybeSignature = sendAndReceive<DigitalSignature.LegallyIdentifiable>(TOPIC, node.address, 0, sessionID, req)
// Check that the timestamping authority gave us back a valid signature and didn't break somehow
progressTracker.currentStep = VALIDATING
maybeSignature.validate { sig ->
sig.verifyWithECDSA(wtxBytes)
return sig
}
}
class Request(val tx: SerializedBytes<WireTransaction>, replyTo: MessageRecipients, sessionID: Long) : AbstractRequestMessage(replyTo, sessionID)
}

View File

@ -41,7 +41,7 @@ object TwoPartyDealProtocol {
val sessionID: Long
)
class SignaturesFromPrimary(val timestampAuthoritySig: DigitalSignature.WithKey, val sellerSig: DigitalSignature.WithKey)
class SignaturesFromPrimary(val sellerSig: DigitalSignature.WithKey, val notarySig: DigitalSignature.WithKey)
/**
* Abstracted bilateral deal protocol participant that initiates communication/handshake.
@ -53,21 +53,19 @@ object TwoPartyDealProtocol {
val otherSide: SingleMessageRecipient,
val otherSessionID: Long,
val myKeyPair: KeyPair,
val timestampingAuthority: NodeInfo,
val notaryNode: NodeInfo,
override val progressTracker: ProgressTracker = Primary.tracker()) : ProtocolLogic<SignedTransaction>() {
companion object {
object AWAITING_PROPOSAL : ProgressTracker.Step("Handshaking and awaiting transaction proposal")
object VERIFYING : ProgressTracker.Step("Verifying proposed transaction")
object SIGNING : ProgressTracker.Step("Signing transaction")
object TIMESTAMPING : ProgressTracker.Step("Timestamping transaction")
object NOTARY : ProgressTracker.Step("Getting notary signature")
object SENDING_SIGS : ProgressTracker.Step("Sending transaction signatures to other party")
object RECORDING : ProgressTracker.Step("Recording completed transaction")
object COPYING_TO_REGULATOR : ProgressTracker.Step("Copying regulator")
fun tracker() = ProgressTracker(AWAITING_PROPOSAL, VERIFYING, SIGNING, TIMESTAMPING, SENDING_SIGS, RECORDING, COPYING_TO_REGULATOR).apply {
childrenFor[TIMESTAMPING] = TimestampingProtocol.tracker()
}
fun tracker() = ProgressTracker(AWAITING_PROPOSAL, VERIFYING, SIGNING, NOTARY, SENDING_SIGS, RECORDING, COPYING_TO_REGULATOR)
}
@Suspendable
@ -93,7 +91,7 @@ object TwoPartyDealProtocol {
// Check that the tx proposed by the buyer is valid.
val missingSigs = it.verify(throwIfSignaturesAreMissing = false)
if (missingSigs != setOf(myKeyPair.public, timestampingAuthority.identity.owningKey))
if (missingSigs != setOf(myKeyPair.public, notaryNode.identity.owningKey))
throw SignatureException("The set of missing signatures is not as expected: $missingSigs")
val wtx: WireTransaction = it.tx
@ -128,13 +126,13 @@ object TwoPartyDealProtocol {
@Suspendable
override fun call(): SignedTransaction {
val partialTX: SignedTransaction = verifyPartialTransaction(getPartialTransaction())
val stx: SignedTransaction = verifyPartialTransaction(getPartialTransaction())
// These two steps could be done in parallel, in theory. Our framework doesn't support that yet though.
val ourSignature = signWithOurKey(partialTX)
val tsaSig = timestamp(partialTX)
val ourSignature = signWithOurKey(stx)
val notarySignature = getNotarySignature(stx)
val fullySigned = sendSignatures(partialTX, ourSignature, tsaSig)
val fullySigned = sendSignatures(stx, ourSignature, notarySignature)
progressTracker.currentStep = RECORDING
@ -155,12 +153,11 @@ object TwoPartyDealProtocol {
}
@Suspendable
private fun timestamp(partialTX: SignedTransaction): DigitalSignature.LegallyIdentifiable {
progressTracker.currentStep = TIMESTAMPING
return subProtocol(TimestampingProtocol(timestampingAuthority, partialTX.txBits, progressTracker.childrenFor[TIMESTAMPING]!!))
private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.LegallyIdentifiable {
progressTracker.currentStep = NOTARY
return subProtocol(NotaryProtocol(stx.tx))
}
@Suspendable
open fun signWithOurKey(partialTX: SignedTransaction): DigitalSignature.WithKey {
progressTracker.currentStep = SIGNING
return myKeyPair.signWithECDSA(partialTX.txBits)
@ -168,13 +165,13 @@ object TwoPartyDealProtocol {
@Suspendable
private fun sendSignatures(partialTX: SignedTransaction, ourSignature: DigitalSignature.WithKey,
tsaSig: DigitalSignature.LegallyIdentifiable): SignedTransaction {
notarySignature: DigitalSignature.LegallyIdentifiable): SignedTransaction {
progressTracker.currentStep = SENDING_SIGS
val fullySigned = partialTX + tsaSig + ourSignature
val fullySigned = partialTX + ourSignature + notarySignature
logger.trace { "Built finished transaction, sending back to other party!" }
send(DEAL_TOPIC, otherSide, otherSessionID, SignaturesFromPrimary(tsaSig, ourSignature))
send(DEAL_TOPIC, otherSide, otherSessionID, SignaturesFromPrimary(ourSignature, notarySignature))
return fullySigned
}
}
@ -187,7 +184,7 @@ object TwoPartyDealProtocol {
* and helper methods etc.
*/
abstract class Secondary<U>(val otherSide: SingleMessageRecipient,
val timestampingAuthority: Party,
val notary: Party,
val sessionID: Long,
override val progressTracker: ProgressTracker = Secondary.tracker()) : ProtocolLogic<SignedTransaction>() {
@ -212,7 +209,7 @@ object TwoPartyDealProtocol {
val signatures = swapSignaturesWithPrimary(stx, handshake.sessionID)
logger.trace { "Got signatures from other party, verifying ... " }
val fullySigned = stx + signatures.timestampAuthoritySig + signatures.sellerSig
val fullySigned = stx + signatures.sellerSig + signatures.notarySig
fullySigned.verify()
logger.trace { "Signatures received are valid. Deal transaction complete! :-)" }
@ -264,22 +261,20 @@ object TwoPartyDealProtocol {
* One side of the protocol for inserting a pre-agreed deal.
*/
open class Instigator<T : DealState>(otherSide: SingleMessageRecipient,
timestampingAuthority: NodeInfo,
notaryNode: NodeInfo,
dealBeingOffered: T,
myKeyPair: KeyPair,
buyerSessionID: Long,
override val progressTracker: ProgressTracker = Primary.tracker()) : Primary<T>(dealBeingOffered, otherSide, buyerSessionID, myKeyPair, timestampingAuthority)
override val progressTracker: ProgressTracker = Primary.tracker()) : Primary<T>(dealBeingOffered, otherSide, buyerSessionID, myKeyPair, notaryNode)
/**
* One side of the protocol for inserting a pre-agreed deal.
*/
open class Acceptor<T : DealState>(otherSide: SingleMessageRecipient,
timestampingAuthority: Party,
notary: Party,
val dealToBuy: T,
sessionID: Long,
override val progressTracker: ProgressTracker = Secondary.tracker()) : Secondary<T>(otherSide, timestampingAuthority, sessionID) {
@Suspendable
override val progressTracker: ProgressTracker = Secondary.tracker()) : Secondary<T>(otherSide, notary, sessionID) {
override fun validateHandshake(handshake: Handshake<T>): Handshake<T> {
with(handshake) {
// What is the seller trying to sell us?
@ -307,13 +302,12 @@ object TwoPartyDealProtocol {
}
@Suspendable
override fun assembleSharedTX(handshake: Handshake<T>): Pair<TransactionBuilder, List<PublicKey>> {
val ptx = handshake.payload.generateAgreement()
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
// to have one.
ptx.setTime(serviceHub.clock.instant(), timestampingAuthority, 30.seconds)
ptx.setTime(serviceHub.clock.instant(), notary, 30.seconds)
return Pair(ptx, arrayListOf(handshake.payload.parties.single { it.name == serviceHub.storageService.myLegalIdentity.name }.owningKey))
}
@ -327,10 +321,10 @@ object TwoPartyDealProtocol {
* who does what in the protocol.
*/
open class Fixer<T : FixableDealState>(otherSide: SingleMessageRecipient,
timestampingAuthority: Party,
notary: Party,
val dealToFix: StateAndRef<T>,
sessionID: Long,
val replacementProgressTracker: ProgressTracker? = null) : Secondary<StateRef>(otherSide, timestampingAuthority, sessionID) {
val replacementProgressTracker: ProgressTracker? = null) : Secondary<StateRef>(otherSide, notary, sessionID) {
private val ratesFixTracker = RatesFixProtocol.tracker(dealToFix.state.nextFixingOf()!!.name)
override val progressTracker: ProgressTracker = replacementProgressTracker ?: createTracker()
@ -339,7 +333,6 @@ object TwoPartyDealProtocol {
childrenFor[SIGNING] = ratesFixTracker
}
@Suspendable
override fun validateHandshake(handshake: Handshake<StateRef>): Handshake<StateRef> {
with(handshake) {
logger.trace { "Got fixing request for: ${dealToFix.state}" }
@ -377,7 +370,7 @@ object TwoPartyDealProtocol {
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
// to have one.
ptx.setTime(serviceHub.clock.instant(), timestampingAuthority, 30.seconds)
ptx.setTime(serviceHub.clock.instant(), notary, 30.seconds)
}
}
subProtocol(addFixing)
@ -395,9 +388,9 @@ object TwoPartyDealProtocol {
*/
open class Floater<T : FixableDealState>(otherSide: SingleMessageRecipient,
otherSessionID: Long,
timestampingAuthority: NodeInfo,
notary: NodeInfo,
dealToFix: StateAndRef<T>,
myKeyPair: KeyPair,
val sessionID: Long,
override val progressTracker: ProgressTracker = Primary.tracker()) : Primary<StateRef>(dealToFix.ref, otherSide, otherSessionID, myKeyPair, timestampingAuthority)
override val progressTracker: ProgressTracker = Primary.tracker()) : Primary<StateRef>(dealToFix.ref, otherSide, otherSessionID, myKeyPair, notary)
}

View File

@ -99,5 +99,6 @@
"dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360",
"tradeID": "tradeXXX",
"hashLegalDocs": "put hash here"
}
},
"notary": "Bank A"
}

View File

@ -39,7 +39,7 @@ elif [[ "$mode" == "nodeB" ]]; then
do
build/install/r3prototyping/bin/irsdemo --dir=nodeB --network-address=localhost:31340 \
--fake-trade-with-address=localhost --fake-trade-with-identity=nodeA/identity-public \
-network-map-identity-file=nodeA/identity-public --network-map-address=localhost &
--network-map-identity-file=nodeA/identity-public --network-map-address=localhost &
while ! curl -F rates=@scripts/example.rates.txt http://localhost:31341/upload/interest-rates; do
echo "Retry to upload interest rates to oracle after 5 seconds"
sleep 5

View File

@ -7,15 +7,16 @@ import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors
import com.google.common.util.concurrent.SettableFuture
import core.Party
import core.crypto.generateKeyPair
import core.messaging.MessagingService
import core.messaging.StateMachineManager
import core.messaging.runOnNextMessage
import core.node.services.*
import core.node.subsystems.*
import core.node.storage.CheckpointStorage
import core.node.storage.PerFileCheckpointStorage
import core.node.subsystems.*
import core.random63BitValue
import core.seconds
import core.serialization.deserialize
import core.serialization.serialize
import core.utilities.AddOrRemove
@ -85,7 +86,7 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
lateinit var wallet: WalletService
lateinit var keyManagement: E2ETestKeyManagementService
var inNodeNetworkMapService: NetworkMapService? = null
var inNodeTimestampingService: NodeTimestamperService? = null
var inNodeNotaryService: NotaryService? = null
lateinit var identity: IdentityService
lateinit var net: MessagingService
lateinit var api: APIServer
@ -103,7 +104,7 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
// Build services we're advertising
if (NetworkMapService.Type in info.advertisedServices) makeNetworkMapService()
if (TimestamperService.Type in info.advertisedServices) makeTimestampingService()
if (NotaryService.Type in info.advertisedServices) makeNotaryService()
identity = makeIdentityService()
@ -161,8 +162,10 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
inNodeNetworkMapService = InMemoryNetworkMapService(net, reg, services.networkMapCache)
}
open protected fun makeTimestampingService() {
inNodeTimestampingService = NodeTimestamperService(net, storage.myLegalIdentity, storage.myLegalIdentityKey, platformClock)
open protected fun makeNotaryService() {
val uniquenessProvider = InMemoryUniquenessProvider()
val timestampChecker = TimestampChecker(platformClock, 30.seconds)
inNodeNotaryService = NotaryService(net, storage.myLegalIdentity, storage.myLegalIdentityKey, uniquenessProvider, timestampChecker)
}
lateinit var interestRatesService: NodeInterestRates.Service
@ -243,7 +246,9 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
}
}
private fun makeAttachmentStorage(dir: Path): NodeAttachmentService {
protected open fun generateKeyPair() = core.crypto.generateKeyPair()
protected fun makeAttachmentStorage(dir: Path): NodeAttachmentService {
val attachmentsDir = dir.resolve("attachments")
try {
Files.createDirectory(attachmentsDir)

View File

@ -1,78 +0,0 @@
package core.node.services
import co.paralleluniverse.common.util.VisibleForTesting
import core.Party
import core.TimestampCommand
import core.crypto.DigitalSignature
import core.crypto.signWithECDSA
import core.messaging.MessagingService
import core.seconds
import core.serialization.deserialize
import core.until
import org.slf4j.LoggerFactory
import protocols.TimestampingProtocol
import java.security.KeyPair
import java.time.Clock
import java.time.Duration
import javax.annotation.concurrent.ThreadSafe
/**
* 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.
*
* See the doc site to learn more about timestamping authorities (nodes) and the role they play in the data model.
*/
@ThreadSafe
class NodeTimestamperService(net: MessagingService,
val identity: Party,
val signingKey: KeyPair,
val clock: Clock = Clock.systemDefaultZone(),
val tolerance: Duration = 30.seconds) : AbstractNodeService(net) {
companion object {
private val logger = LoggerFactory.getLogger(NodeTimestamperService::class.java)
}
init {
require(identity.owningKey == signingKey.public)
addMessageHandler(TimestampingProtocol.TOPIC,
{ req: TimestampingProtocol.Request -> processRequest(req) },
{ message, e ->
if (e is TimestampingError) {
logger.warn("Failure during timestamping request due to bad request: ${e.javaClass.name}")
} else {
logger.error("Exception during timestamping", e)
}
}
)
}
@VisibleForTesting
fun processRequest(req: TimestampingProtocol.Request): DigitalSignature.LegallyIdentifiable {
// We don't bother verifying signatures anything about the transaction here: we simply don't need to see anything
// except the relevant command, and a future privacy upgrade should ensure we only get a torn-off command
// rather than the full transaction.
val tx = req.tx.deserialize()
val cmd = tx.commands.filter { it.value is TimestampCommand }.singleOrNull()
if (cmd == null)
throw TimestampingError.RequiresExactlyOneCommand()
if (!cmd.signers.contains(identity.owningKey))
throw TimestampingError.NotForMe()
val tsCommand = cmd.value as TimestampCommand
val before = tsCommand.before
val after = tsCommand.after
val now = clock.instant()
// We don't need to test for (before == null && after == null) or backwards bounds because the TimestampCommand
// constructor already checks that.
if (before != null && before until now > tolerance)
throw TimestampingError.NotOnTimeException()
if (after != null && now until after > tolerance)
throw TimestampingError.NotOnTimeException()
return signingKey.signWithECDSA(req.tx.bits, identity)
}
}

View File

@ -0,0 +1,115 @@
package core.node.services
import core.Party
import core.TimestampCommand
import core.WireTransaction
import core.crypto.DigitalSignature
import core.crypto.SignedData
import core.crypto.signWithECDSA
import core.messaging.MessagingService
import core.serialization.SerializedBytes
import core.serialization.deserialize
import core.serialization.serialize
import core.utilities.loggerFor
import protocols.NotaryProtocol
import java.security.KeyPair
/**
* A Notary service acts as the final signer of a transaction ensuring two things:
* - The (optional) timestamp of the transaction is valid
* - None of the referenced input states have previously been consumed by a transaction signed by this Notary
*
* A transaction has to be signed by a Notary to be considered valid (except for output-only transactions w/o a timestamp)
*/
class NotaryService(net: MessagingService,
val identity: Party,
val signingKey: KeyPair,
val uniquenessProvider: UniquenessProvider,
val timestampChecker: TimestampChecker) : AbstractNodeService(net) {
object Type : ServiceType("corda.notary")
private val logger = loggerFor<NotaryService>()
init {
check(identity.owningKey == signingKey.public)
addMessageHandler(NotaryProtocol.TOPIC,
{ req: NotaryProtocol.SignRequest -> processRequest(req.txBits, req.callerIdentity) },
{ message, e -> logger.error("Exception during notary service request processing", e) }
)
}
/**
* Checks that the timestamp command is valid (if present) and commits the input state, or returns a conflict
* if any of the input states have been previously committed
*
* Note that the transaction is not checked for contract-validity, as that would require fully resolving it
* into a [TransactionForVerification], for which the caller would have to reveal the whole transaction history chain.
* As a result, the Notary _will commit invalid transactions_ as well, but as it also records the identity of
* the caller, it is possible to raise a dispute and verify the validity of the transaction and subsequently
* undo the commit of the input states (the exact mechanism still needs to be worked out)
*
* TODO: the notary service should only be able to see timestamp commands and inputs
*/
fun processRequest(txBits: SerializedBytes<WireTransaction>, reqIdentity: Party): Result {
val wtx = txBits.deserialize()
try {
validateTimestamp(wtx)
commitInputStates(wtx, reqIdentity)
} catch(e: NotaryException) {
return Result.withError(e.error)
}
val sig = sign(txBits)
return Result.noError(sig)
}
private fun validateTimestamp(tx: WireTransaction) {
// Need to have at most one timestamp command
val timestampCmds = tx.commands.filter { it.value is TimestampCommand }
if (timestampCmds.count() > 1)
throw NotaryException(NotaryError.MoreThanOneTimestamp())
val timestampCmd = timestampCmds.singleOrNull() ?: return
if (!timestampCmd.signers.contains(identity.owningKey))
throw NotaryException(NotaryError.NotForMe())
if (!timestampChecker.isValid(timestampCmd.value as TimestampCommand))
throw NotaryException(NotaryError.TimestampInvalid())
}
private fun commitInputStates(tx: WireTransaction, reqIdentity: Party) {
try {
uniquenessProvider.commit(tx, reqIdentity)
} catch (e: UniquenessException) {
val conflictData = e.error.serialize()
val signedConflict = SignedData(conflictData, sign(conflictData))
throw NotaryException(NotaryError.Conflict(tx, signedConflict))
}
}
private fun <T : Any> sign(bits: SerializedBytes<T>): DigitalSignature.LegallyIdentifiable {
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

@ -0,0 +1,26 @@
package core.node.services
import core.TimestampCommand
import core.seconds
import core.until
import java.time.Clock
import java.time.Duration
/**
* Checks if the given timestamp falls within the allowed tolerance interval
*/
class TimestampChecker(val clock: Clock = Clock.systemDefaultZone(),
val tolerance: Duration = 30.seconds) {
fun isValid(timestampCommand: TimestampCommand): Boolean {
val before = timestampCommand.before
val after = timestampCommand.after
val now = clock.instant()
// We don't need to test for (before == null && after == null) or backwards bounds because the TimestampCommand
// constructor already checks that.
if (before != null && before until now > tolerance) return false
if (after != null && now until after > tolerance) return false
return true
}
}

View File

@ -0,0 +1,64 @@
package core.node.services
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 replace the transaction id with some other identifier to avoid privacy leaks
* e.g after buying an asset with some cash a party can construct a fake transaction with
* the cash outputs as inputs and find out if and where the other party had spent it
*/
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 {
/**
* For each state store the consuming transaction id along with the identity of the party
* requesting validation of the transaction
*/
private val committedStates = ThreadBox(HashMap<StateRef, UniquenessProvider.ConsumingTx>())
// TODO: the uniqueness provider shouldn't be able to see all tx outputs and commands
override fun commit(tx: WireTransaction, callerIdentity: Party) {
val inputStates = tx.inputs
committedStates.locked {
val conflictingStates = LinkedHashMap<StateRef, UniquenessProvider.ConsumingTx>()
for (inputState in inputStates) {
val consumingTx = get(inputState)
if (consumingTx != null) conflictingStates[inputState] = consumingTx
}
if (conflictingStates.isNotEmpty()) {
val conflict = UniquenessProvider.Conflict(conflictingStates)
throw UniquenessException(conflict)
} else {
inputStates.forEachIndexed { i, stateRef ->
put(stateRef, UniquenessProvider.ConsumingTx(tx.id, i, callerIdentity))
}
}
}
}
}

View File

@ -15,6 +15,7 @@ import core.random63BitValue
import core.serialization.deserialize
import core.serialization.serialize
import core.utilities.AddOrRemove
import java.security.PublicKey
import java.security.SignatureException
import java.util.*
import javax.annotation.concurrent.ThreadSafe
@ -28,8 +29,8 @@ open class InMemoryNetworkMapCache() : NetworkMapCache {
get() = get(NetworkMapService.Type)
override val regulators: List<NodeInfo>
get() = get(RegulatorService.Type)
override val timestampingNodes: List<NodeInfo>
get() = get(TimestamperService.Type)
override val notaryNodes: List<NodeInfo>
get() = get(NotaryService.Type)
override val ratesOracleNodes: List<NodeInfo>
get() = get(NodeInterestRates.Type)
override val partyNodes: List<NodeInfo>
@ -41,6 +42,8 @@ open class InMemoryNetworkMapCache() : NetworkMapCache {
override fun get() = registeredNodes.map { it.value }
override fun get(serviceType: ServiceType) = registeredNodes.filterValues { it.advertisedServices.contains(serviceType) }.map { it.value }
override fun getRecommended(type: ServiceType, contract: Contract, vararg party: Party): NodeInfo? = get(type).firstOrNull()
override fun getNodeByLegalName(name: String) = get().singleOrNull { it.identity.name == name }
override fun getNodeByPublicKey(publicKey: PublicKey) = get().singleOrNull { it.identity.owningKey == publicKey }
override fun addMapService(net: MessagingService, service: NodeInfo, subscribe: Boolean,
ifChangedSinceVer: Int?): ListenableFuture<Unit> {

View File

@ -139,8 +139,8 @@ class NodeWalletService(private val services: ServiceHub) : WalletService {
*
* TODO: Move this out of NodeWalletService
*/
fun fillWithSomeTestCash(howMuch: Amount, atLeastThisManyStates: Int = 3, atMostThisManyStates: Int = 10,
rng: Random = Random()) {
fun fillWithSomeTestCash(notary: Party, howMuch: Amount, atLeastThisManyStates: Int = 3,
atMostThisManyStates: Int = 10, rng: Random = Random()) {
val amounts = calculateRandomlySizedAmounts(howMuch, atLeastThisManyStates, atMostThisManyStates, rng)
val myIdentity = services.storageService.myLegalIdentity
@ -155,7 +155,7 @@ class NodeWalletService(private val services: ServiceHub) : WalletService {
val issuance = TransactionBuilder()
val freshKey = services.keyManagementService.freshKey()
cash.generateIssue(issuance, Amount(pennies, howMuch.currency), depositRef, freshKey.public)
cash.generateIssue(issuance, Amount(pennies, howMuch.currency), depositRef, freshKey.public, notary)
issuance.signWith(myKey)
return@map issuance.toSignedTransaction(true)

View File

@ -7,7 +7,6 @@ import com.google.common.util.concurrent.SettableFuture
import contracts.InterestRateSwap
import core.*
import core.crypto.SecureHash
import core.testing.MockIdentityService
import core.node.subsystems.linearHeadsOfType
import core.utilities.JsonSupport
import protocols.TwoPartyDealProtocol
@ -64,9 +63,9 @@ class IRSSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork
if (nextFixingDate > currentDay)
currentDay = nextFixingDate
val sideA = TwoPartyDealProtocol.Floater(node2.net.myAddress, sessionID, timestamper.info,
val sideA = TwoPartyDealProtocol.Floater(node2.net.myAddress, sessionID, notary.info,
theDealRef, node1.services.keyManagementService.freshKey(), sessionID)
val sideB = TwoPartyDealProtocol.Fixer(node1.net.myAddress, timestamper.info.identity,
val sideB = TwoPartyDealProtocol.Fixer(node1.net.myAddress, notary.info.identity,
theDealRef, sessionID)
linkConsensus(listOf(node1, node2, regulators[0]), sideB)
@ -108,9 +107,9 @@ class IRSSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork
val sessionID = random63BitValue()
val instigator = TwoPartyDealProtocol.Instigator(node2.net.myAddress, timestamper.info,
val instigator = TwoPartyDealProtocol.Instigator(node2.net.myAddress, notary.info,
irs, node1.services.keyManagementService.freshKey(), sessionID)
val acceptor = TwoPartyDealProtocol.Acceptor(node1.net.myAddress, timestamper.info.identity,
val acceptor = TwoPartyDealProtocol.Acceptor(node1.net.myAddress, notary.info.identity,
irs, sessionID)
// TODO: Eliminate the need for linkProtocolProgress

View File

@ -6,11 +6,6 @@ import com.google.common.util.concurrent.MoreExecutors
import core.ThreadBox
import core.crypto.sha256
import core.messaging.*
import core.node.NodeInfo
import core.node.services.DummyTimestampingAuthority
import core.node.services.NodeTimestamperService
import core.node.services.ServiceType
import core.node.services.TimestamperService
import core.utilities.loggerFor
import rx.Observable
import rx.subjects.PublishSubject
@ -144,18 +139,6 @@ class InMemoryMessagingNetwork {
override fun hashCode() = id.hashCode()
}
private var timestampingAdvert: NodeInfo? = null
@Synchronized
fun setupTimestampingNode(manuallyPumped: Boolean): Pair<NodeInfo, InMemoryMessaging> {
check(timestampingAdvert == null)
val (handle, builder) = createNode(manuallyPumped)
val node = builder.start().get()
NodeTimestamperService(node, DummyTimestampingAuthority.identity, DummyTimestampingAuthority.key)
timestampingAdvert = NodeInfo(handle, DummyTimestampingAuthority.identity, setOf(TimestamperService.Type))
return Pair(timestampingAdvert!!, node)
}
/**
* An [InMemoryMessaging] provides a [MessagingService] that isn't backed by any kind of network or disk storage
* system, but just uses regular queues on the heap instead. It is intended for unit testing and developer convenience

View File

@ -1,8 +1,6 @@
package core.testing
import com.google.common.jimfs.Jimfs
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import core.Party
import core.messaging.MessagingService
import core.messaging.SingleMessageRecipient
@ -11,13 +9,14 @@ import core.node.NodeConfiguration
import core.node.NodeInfo
import core.node.PhysicalLocation
import core.node.services.NetworkMapService
import core.node.services.NotaryService
import core.node.services.ServiceType
import core.node.services.TimestamperService
import core.utilities.AffinityExecutor
import core.utilities.loggerFor
import org.slf4j.Logger
import java.nio.file.Files
import java.nio.file.Path
import java.security.KeyPair
import java.time.Clock
import java.util.*
@ -47,19 +46,19 @@ class MockNetwork(private val threadPerNode: Boolean = false,
/** Allows customisation of how nodes are created. */
interface Factory {
fun create(dir: Path, config: NodeConfiguration, network: MockNetwork,
networkMapAddr: NodeInfo?, advertisedServices: Set<ServiceType>, id: Int): MockNode
fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, networkMapAddr: NodeInfo?,
advertisedServices: Set<ServiceType>, id: Int, keyPair: KeyPair?): MockNode
}
object DefaultFactory : Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork,
networkMapAddr: NodeInfo?, advertisedServices: Set<ServiceType>, id: Int): MockNode {
return MockNode(dir, config, network, networkMapAddr, advertisedServices, id)
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, networkMapAddr: NodeInfo?,
advertisedServices: Set<ServiceType>, id: Int, keyPair: KeyPair?): MockNode {
return MockNode(dir, config, network, networkMapAddr, advertisedServices, id, keyPair)
}
}
open class MockNode(dir: Path, config: NodeConfiguration, val mockNet: MockNetwork,
networkMapAddr: NodeInfo?, advertisedServices: Set<ServiceType>, val id: Int) : AbstractNode(dir, config, networkMapAddr, advertisedServices, Clock.systemUTC()) {
open class MockNode(dir: Path, config: NodeConfiguration, val mockNet: MockNetwork, networkMapAddr: NodeInfo?,
advertisedServices: Set<ServiceType>, val id: Int, val keyPair: KeyPair?) : AbstractNode(dir, config, networkMapAddr, advertisedServices, Clock.systemUTC()) {
override val log: Logger = loggerFor<MockNode>()
override val serverThread: AffinityExecutor =
if (mockNet.threadPerNode)
@ -81,6 +80,8 @@ class MockNetwork(private val threadPerNode: Boolean = false,
// Nothing to do
}
override fun generateKeyPair(): KeyPair? = keyPair ?: super.generateKeyPair()
// There is no need to slow down the unit tests by initialising CityDatabase
override fun findMyLocation(): PhysicalLocation? = null
@ -95,7 +96,7 @@ class MockNetwork(private val threadPerNode: Boolean = false,
/** Returns a started node, optionally created by the passed factory method */
fun createNode(networkMapAddress: NodeInfo? = null, forcedID: Int = -1, nodeFactory: Factory = defaultFactory,
vararg advertisedServices: ServiceType): MockNode {
legalName: String? = null, keyPair: KeyPair? = null, vararg advertisedServices: ServiceType): MockNode {
val newNode = forcedID == -1
val id = if (newNode) counter++ else forcedID
@ -103,11 +104,11 @@ class MockNetwork(private val threadPerNode: Boolean = false,
if (newNode)
Files.createDirectories(path.resolve("attachments"))
val config = object : NodeConfiguration {
override val myLegalName: String = "Mock Company $id"
override val myLegalName: String = legalName ?: "Mock Company $id"
override val exportJMXto: String = ""
override val nearestCity: String = "Atlantis"
}
val node = nodeFactory.create(path, config, this, networkMapAddress, advertisedServices.toSet(), id).start()
val node = nodeFactory.create(path, config, this, networkMapAddress, advertisedServices.toSet(), id, keyPair).start()
_nodes.add(node)
return node
}
@ -128,16 +129,19 @@ class MockNetwork(private val threadPerNode: Boolean = false,
}
/**
* Sets up a two node network, in which the first node runs network map and timestamping services and the other
* Sets up a two node network, in which the first node runs network map and notary services and the other
* doesn't.
*/
fun createTwoNodes(nodeFactory: Factory = defaultFactory): Pair<MockNode, MockNode> {
fun createTwoNodes(nodeFactory: Factory = defaultFactory, notaryKeyPair: KeyPair? = null): Pair<MockNode, MockNode> {
require(nodes.isEmpty())
return Pair(
createNode(null, -1, nodeFactory, NetworkMapService.Type, TimestamperService.Type),
createNode(nodes[0].info, -1, nodeFactory)
createNode(null, -1, nodeFactory, null, notaryKeyPair, NetworkMapService.Type, NotaryService.Type),
createNode(nodes[0].info, -1, nodeFactory, null)
)
}
fun createNotaryNode(legalName: String? = null, keyPair: KeyPair? = null) = createNode(null, -1, defaultFactory, legalName, keyPair, NetworkMapService.Type, NotaryService.Type)
fun createPartyNode(networkMapAddr: NodeInfo, legalName: String? = null, keyPair: KeyPair? = null) = createNode(networkMapAddr, -1, defaultFactory, legalName, keyPair)
fun addressToNode(address: SingleMessageRecipient): MockNode = nodes.single { it.net.myAddress == address }
}

View File

@ -7,14 +7,15 @@ import core.node.NodeInfo
import core.node.PhysicalLocation
import core.node.services.NetworkMapService
import core.node.services.NodeInterestRates
import core.node.services.NotaryService
import core.node.services.ServiceType
import core.node.services.TimestamperService
import core.protocols.ProtocolLogic
import core.then
import core.utilities.ProgressTracker
import rx.Observable
import rx.subjects.PublishSubject
import java.nio.file.Path
import java.security.KeyPair
import java.time.LocalDate
import java.util.*
@ -35,16 +36,16 @@ abstract class Simulation(val runAsync: Boolean,
// This puts together a mock network of SimulatedNodes.
open class SimulatedNode(dir: Path, config: NodeConfiguration, mockNet: MockNetwork,
networkMapAddress: NodeInfo?, advertisedServices: Set<ServiceType>, id: Int) : MockNetwork.MockNode(dir, config, mockNet, networkMapAddress, advertisedServices, id) {
open class SimulatedNode(dir: Path, config: NodeConfiguration, mockNet: MockNetwork, networkMapAddress: NodeInfo?,
advertisedServices: Set<ServiceType>, id: Int, keyPair: KeyPair?) : MockNetwork.MockNode(dir, config, mockNet, networkMapAddress, advertisedServices, id, keyPair) {
override fun findMyLocation(): PhysicalLocation? = CityDatabase[configuration.nearestCity]
}
inner class BankFactory : MockNetwork.Factory {
var counter = 0
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork,
networkMapAddr: NodeInfo?, advertisedServices: Set<ServiceType>, id: Int): MockNetwork.MockNode {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, networkMapAddr: NodeInfo?,
advertisedServices: Set<ServiceType>, id: Int, keyPair: KeyPair?): MockNetwork.MockNode {
val letter = 'A' + counter
val city = bankLocations[counter++ % bankLocations.size]
val cfg = object : NodeConfiguration {
@ -53,7 +54,7 @@ abstract class Simulation(val runAsync: Boolean,
override val exportJMXto: String = ""
override val nearestCity: String = city
}
return SimulatedNode(dir, cfg, network, networkMapAddr, advertisedServices, id)
return SimulatedNode(dir, cfg, network, networkMapAddr, advertisedServices, id, keyPair)
}
fun createAll(): List<SimulatedNode> = bankLocations.
@ -64,7 +65,7 @@ abstract class Simulation(val runAsync: Boolean,
object NetworkMapNodeFactory : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork,
networkMapAddr: NodeInfo?, advertisedServices: Set<ServiceType>, id: Int): MockNetwork.MockNode {
networkMapAddr: NodeInfo?, advertisedServices: Set<ServiceType>, id: Int, keyPair: KeyPair?): MockNetwork.MockNode {
require(advertisedServices.contains(NetworkMapService.Type))
val cfg = object : NodeConfiguration {
override val myLegalName: String = "Network Map Service Provider"
@ -72,26 +73,26 @@ abstract class Simulation(val runAsync: Boolean,
override val nearestCity: String = "Madrid"
}
return object : SimulatedNode(dir, cfg, network, networkMapAddr, advertisedServices, id) { }
return object : SimulatedNode(dir, cfg, network, networkMapAddr, advertisedServices, id, keyPair) {}
}
}
object TimestampingNodeFactory : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork,
networkMapAddr: NodeInfo?, advertisedServices: Set<ServiceType>, id: Int): MockNetwork.MockNode {
require(advertisedServices.contains(TimestamperService.Type))
object NotaryNodeFactory : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, networkMapAddr: NodeInfo?,
advertisedServices: Set<ServiceType>, id: Int, keyPair: KeyPair?): MockNetwork.MockNode {
require(advertisedServices.contains(NotaryService.Type))
val cfg = object : NodeConfiguration {
override val myLegalName: String = "Timestamping Service" // A magic string recognised by the CP contract
override val myLegalName: String = "Notary Service"
override val exportJMXto: String = ""
override val nearestCity: String = "Zurich"
}
return SimulatedNode(dir, cfg, network, networkMapAddr, advertisedServices, id)
return SimulatedNode(dir, cfg, network, networkMapAddr, advertisedServices, id, keyPair)
}
}
object RatesOracleFactory : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork,
networkMapAddr: NodeInfo?, advertisedServices: Set<ServiceType>, id: Int): MockNetwork.MockNode {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, networkMapAddr: NodeInfo?,
advertisedServices: Set<ServiceType>, id: Int, keyPair: KeyPair?): MockNetwork.MockNode {
require(advertisedServices.contains(NodeInterestRates.Type))
val cfg = object : NodeConfiguration {
override val myLegalName: String = "Rates Service Provider"
@ -99,7 +100,7 @@ abstract class Simulation(val runAsync: Boolean,
override val nearestCity: String = "Madrid"
}
return object : SimulatedNode(dir, cfg, network, networkMapAddr, advertisedServices, id) {
return object : SimulatedNode(dir, cfg, network, networkMapAddr, advertisedServices, id, keyPair) {
override fun makeInterestRatesOracleService() {
super.makeInterestRatesOracleService()
interestRatesService.upload(javaClass.getResourceAsStream("example.rates.txt"))
@ -109,15 +110,15 @@ abstract class Simulation(val runAsync: Boolean,
}
object RegulatorFactory : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork,
networkMapAddr: NodeInfo?, advertisedServices: Set<ServiceType>, id: Int): MockNetwork.MockNode {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, networkMapAddr: NodeInfo?,
advertisedServices: Set<ServiceType>, id: Int, keyPair: KeyPair?): MockNetwork.MockNode {
val cfg = object : NodeConfiguration {
override val myLegalName: String = "Regulator A"
override val exportJMXto: String = ""
override val nearestCity: String = "Paris"
}
val n = object : SimulatedNode(dir, cfg, network, networkMapAddr, advertisedServices, id) {
val n = object : SimulatedNode(dir, cfg, network, networkMapAddr, advertisedServices, id, keyPair) {
// TODO: Regulatory nodes don't actually exist properly, this is a last minute demo request.
// So we just fire a message at a node that doesn't know how to handle it, and it'll ignore it.
// But that's fine for visualisation purposes.
@ -131,11 +132,11 @@ abstract class Simulation(val runAsync: Boolean,
val regulators: List<SimulatedNode> = listOf(network.createNode(null, nodeFactory = RegulatorFactory) as SimulatedNode)
val networkMap: SimulatedNode
= network.createNode(null, nodeFactory = NetworkMapNodeFactory, advertisedServices = NetworkMapService.Type) as SimulatedNode
val timestamper: SimulatedNode
= network.createNode(null, nodeFactory = TimestampingNodeFactory, advertisedServices = TimestamperService.Type) as SimulatedNode
val notary: SimulatedNode
= network.createNode(null, nodeFactory = NotaryNodeFactory, advertisedServices = NotaryService.Type) as SimulatedNode
val ratesOracle: SimulatedNode
= network.createNode(null, nodeFactory = RatesOracleFactory, advertisedServices = NodeInterestRates.Type) as SimulatedNode
val serviceProviders: List<SimulatedNode> = listOf(timestamper, ratesOracle)
val serviceProviders: List<SimulatedNode> = listOf(notary, ratesOracle)
val banks: List<SimulatedNode> = bankFactory.createAll()
init {

View File

@ -23,24 +23,24 @@ class TradeSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwo
val buyer = banks[buyerBankIndex]
val seller = banks[sellerBankIndex]
(buyer.services.walletService as NodeWalletService).fillWithSomeTestCash(1500.DOLLARS)
(buyer.services.walletService as NodeWalletService).fillWithSomeTestCash(notary.info.identity, 1500.DOLLARS)
val issuance = run {
val tx = CommercialPaper().generateIssue(seller.info.identity.ref(1, 2, 3), 1100.DOLLARS, Instant.now() + 10.days)
tx.setTime(Instant.now(), timestamper.info.identity, 30.seconds)
tx.signWith(timestamper.storage.myLegalIdentityKey)
val tx = CommercialPaper().generateIssue(seller.info.identity.ref(1, 2, 3), 1100.DOLLARS, Instant.now() + 10.days, notary.info.identity)
tx.setTime(Instant.now(), notary.info.identity, 30.seconds)
tx.signWith(notary.storage.myLegalIdentityKey)
tx.signWith(seller.storage.myLegalIdentityKey)
tx.toSignedTransaction(true)
}
seller.services.storageService.validatedTransactions[issuance.id] = issuance
val sessionID = random63BitValue()
val buyerProtocol = TwoPartyTradeProtocol.Buyer(seller.net.myAddress, timestamper.info.identity,
val buyerProtocol = TwoPartyTradeProtocol.Buyer(seller.net.myAddress, notary.info.identity,
1000.DOLLARS, CommercialPaper.State::class.java, sessionID)
val sellerProtocol = TwoPartyTradeProtocol.Seller(buyer.net.myAddress, timestamper.info,
val sellerProtocol = TwoPartyTradeProtocol.Seller(buyer.net.myAddress, notary.info,
issuance.tx.outRef(0), 1000.DOLLARS, seller.storage.myLegalIdentityKey, sessionID)
linkConsensus(listOf(buyer, seller, timestamper), sellerProtocol)
linkConsensus(listOf(buyer, seller, notary), sellerProtocol)
linkProtocolProgress(buyer, buyerProtocol)
linkProtocolProgress(seller, sellerProtocol)

View File

@ -8,8 +8,11 @@ import core.node.Node
import core.node.NodeConfiguration
import core.node.NodeConfigurationFromConfig
import core.node.NodeInfo
import core.node.services.NetworkMapService
import core.node.services.NodeInterestRates
import core.node.services.NotaryService
import core.node.services.ServiceType
import core.node.subsystems.ArtemisMessagingService
import core.node.services.*
import core.serialization.deserialize
import core.utilities.BriefLogFormatter
import demos.protocols.AutoOfferProtocol
@ -60,13 +63,13 @@ fun main(args: Array<String>) {
val myNetAddr = HostAndPort.fromString(options.valueOf(networkAddressArg)).withDefaultPort(Node.DEFAULT_PORT)
val networkMapId = if (options.valueOf(networkMapNetAddr).equals(options.valueOf(networkAddressArg))) {
// This node provides network map and timestamping services
advertisedServices = setOf(NetworkMapService.Type, TimestamperService.Type)
// This node provides network map and notary services
advertisedServices = setOf(NetworkMapService.Type, NotaryService.Type)
null
} else {
advertisedServices = setOf(NodeInterestRates.Type)
try {
nodeInfo(options.valueOf(networkMapNetAddr), options.valueOf(networkMapIdentityFile), setOf(NetworkMapService.Type, TimestamperService.Type))
nodeInfo(options.valueOf(networkMapNetAddr), options.valueOf(networkMapIdentityFile), setOf(NetworkMapService.Type, NotaryService.Type))
} catch (e: Exception) {
null
}
@ -86,7 +89,7 @@ fun main(args: Array<String>) {
val peerId = nodeInfo(hostAndPortString, identityFile)
node.services.identityService.registerIdentity(peerId.identity)
} catch (e: Exception) {
println("Could not load peer identity file \"${identityFile}\".")
println("Could not load peer identity file \"$identityFile\".")
}
}

View File

@ -5,9 +5,9 @@ import core.*
import core.node.Node
import core.node.NodeConfiguration
import core.node.NodeInfo
import core.node.subsystems.ArtemisMessagingService
import core.node.services.NodeInterestRates
import core.node.services.ServiceType
import core.node.subsystems.ArtemisMessagingService
import core.serialization.deserialize
import core.utilities.ANSIProgressRenderer
import core.utilities.BriefLogFormatter
@ -76,9 +76,11 @@ fun main(args: Array<String>) {
val node = logElapsedTime("Node startup") { Node(dir, myNetAddr, config, networkMapAddress, advertisedServices).start() }
val notary = node.services.networkMapCache.notaryNodes[0]
// Make a garbage transaction that includes a rate fix.
val tx = TransactionBuilder()
tx.addOutputState(Cash.State(node.storage.myLegalIdentity.ref(1), 1500.DOLLARS, node.keyManagement.freshKey().public))
tx.addOutputState(Cash.State(node.storage.myLegalIdentity.ref(1), 1500.DOLLARS, node.keyManagement.freshKey().public, notary.identity))
val protocol = RatesFixProtocol(tx, oracleNode, fixOf, expectedRate, rateTolerance)
ANSIProgressRenderer.progressTracker = protocol.progressTracker
node.smm.add("demo.ratefix", protocol).get()

View File

@ -12,9 +12,12 @@ import core.node.Node
import core.node.NodeConfiguration
import core.node.NodeConfigurationFromConfig
import core.node.NodeInfo
import core.node.services.NetworkMapService
import core.node.services.NodeAttachmentService
import core.node.services.NotaryService
import core.node.services.ServiceType
import core.node.subsystems.ArtemisMessagingService
import core.node.subsystems.NodeWalletService
import core.node.services.*
import core.protocols.ProtocolLogic
import core.serialization.deserialize
import core.utilities.ANSIProgressRenderer
@ -22,14 +25,13 @@ import core.utilities.BriefLogFormatter
import core.utilities.Emoji
import core.utilities.ProgressTracker
import joptsimple.OptionParser
import protocols.TimestampingProtocol
import protocols.NotaryProtocol
import protocols.TwoPartyTradeProtocol
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.security.PublicKey
import java.time.Instant
import java.util.*
import kotlin.system.exitProcess
import kotlin.test.assertEquals
@ -88,7 +90,7 @@ fun main(args: Array<String>) {
NodeInfo(ArtemisMessagingService.makeRecipient(addr), party, setOf(NetworkMapService.Type))
} else {
// We must be the network map service
advertisedServices = setOf(NetworkMapService.Type, TimestamperService.Type)
advertisedServices = setOf(NetworkMapService.Type, NotaryService.Type)
null
}
@ -102,7 +104,7 @@ fun main(args: Array<String>) {
it.storePath
}
val buyer = TraderDemoProtocolBuyer(attachmentsPath)
val buyer = TraderDemoProtocolBuyer(attachmentsPath, node.info.identity)
ANSIProgressRenderer.progressTracker = buyer.progressTracker
node.smm.add("demo.buyer", buyer).get() // This thread will halt forever here.
} else {
@ -130,18 +132,20 @@ fun main(args: Array<String>) {
// We create a couple of ad-hoc test protocols that wrap the two party trade protocol, to give us the demo logic.
class TraderDemoProtocolBuyer(private val attachmentsPath: Path) : ProtocolLogic<Unit>() {
class TraderDemoProtocolBuyer(private val attachmentsPath: Path, val notary: Party) : ProtocolLogic<Unit>() {
companion object {
object WAITING_FOR_SELLER_TO_CONNECT : ProgressTracker.Step("Waiting for seller to connect to us")
object STARTING_BUY : ProgressTracker.Step("Seller connected, purchasing commercial paper asset")
}
override val progressTracker = ProgressTracker(WAITING_FOR_SELLER_TO_CONNECT, STARTING_BUY)
@Suspendable
override fun call() {
// Give us some cash. Note that as nodes do not currently track forward pointers, we can spend the same cash over
// and over again and the double spends will never be detected! Fixing that is the next step.
(serviceHub.walletService as NodeWalletService).fillWithSomeTestCash(1500.DOLLARS)
(serviceHub.walletService as NodeWalletService).fillWithSomeTestCash(notary, 1500.DOLLARS)
while (true) {
// Wait around until a node asks to start a trade with us. In a real system, this part would happen out of band
@ -157,8 +161,8 @@ class TraderDemoProtocolBuyer(private val attachmentsPath: Path) : ProtocolLogic
progressTracker.currentStep = STARTING_BUY
send("test.junktrade", newPartnerAddr, 0, sessionID)
val tsa = serviceHub.networkMapCache.timestampingNodes[0]
val buyer = TwoPartyTradeProtocol.Buyer(newPartnerAddr, tsa.identity, 1000.DOLLARS,
val notary = serviceHub.networkMapCache.notaryNodes[0]
val buyer = TwoPartyTradeProtocol.Buyer(newPartnerAddr, notary.identity, 1000.DOLLARS,
CommercialPaper.State::class.java, sessionID)
val tradeTX: SignedTransaction = subProtocol(buyer)
@ -197,7 +201,9 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort,
val PROSPECTUS_HASH = SecureHash.parse("decd098666b9657314870e192ced0c3519c2c9d395507a238338f8d003929de9")
object ANNOUNCING : ProgressTracker.Step("Announcing to the buyer node")
object SELF_ISSUING : ProgressTracker.Step("Got session ID back, issuing and timestamping some commercial paper")
object TRADING : ProgressTracker.Step("Starting the trade protocol")
// We vend a progress tracker that already knows there's going to be a TwoPartyTradingProtocol involved at some
@ -216,27 +222,27 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort,
progressTracker.currentStep = SELF_ISSUING
val tsa = serviceHub.networkMapCache.timestampingNodes[0]
val notary = serviceHub.networkMapCache.notaryNodes[0]
val cpOwnerKey = serviceHub.keyManagementService.freshKey()
val commercialPaper = selfIssueSomeCommercialPaper(cpOwnerKey.public, tsa)
val commercialPaper = selfIssueSomeCommercialPaper(cpOwnerKey.public, notary)
progressTracker.currentStep = TRADING
val seller = TwoPartyTradeProtocol.Seller(otherSide, tsa, commercialPaper, 1000.DOLLARS, cpOwnerKey, sessionID,
progressTracker.childrenFor[TRADING]!!)
val seller = TwoPartyTradeProtocol.Seller(otherSide, notary, commercialPaper, 1000.DOLLARS, cpOwnerKey,
sessionID, progressTracker.childrenFor[TRADING]!!)
val tradeTX: SignedTransaction = subProtocol(seller)
logger.info("Sale completed - we have a happy customer!\n\nFinal transaction is:\n\n${Emoji.renderIfSupported(tradeTX.tx)}")
}
@Suspendable
fun selfIssueSomeCommercialPaper(ownedBy: PublicKey, tsa: NodeInfo): StateAndRef<CommercialPaper.State> {
fun selfIssueSomeCommercialPaper(ownedBy: PublicKey, notaryNode: NodeInfo): StateAndRef<CommercialPaper.State> {
// Make a fake company that's issued its own paper.
val keyPair = generateKeyPair()
val party = Party("Bank of London", keyPair.public)
val issuance = run {
val tx = CommercialPaper().generateIssue(party.ref(1, 2, 3), 1100.DOLLARS, Instant.now() + 10.days)
val tx = CommercialPaper().generateIssue(party.ref(1, 2, 3), 1100.DOLLARS, Instant.now() + 10.days, notaryNode.identity)
// TODO: Consider moving these two steps below into generateIssue.
@ -244,25 +250,25 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort,
tx.addAttachment(serviceHub.storageService.attachments.openAttachment(PROSPECTUS_HASH)!!)
// Timestamp it, all CP must be timestamped.
tx.setTime(Instant.now(), tsa.identity, 30.seconds)
val tsaSig = subProtocol(TimestampingProtocol(tsa, tx.toWireTransaction().serialized))
tx.checkAndAddSignature(tsaSig)
tx.setTime(Instant.now(), notaryNode.identity, 30.seconds)
tx.signWith(keyPair)
val notarySig = subProtocol(NotaryProtocol(tx.toWireTransaction()))
tx.addSignatureUnchecked(notarySig)
tx.toSignedTransaction(true)
}
serviceHub.recordTransactions(listOf(issuance))
val move = run {
val tx = TransactionBuilder()
CommercialPaper().generateMove(tx, issuance.tx.outRef(0), ownedBy)
tx.signWith(keyPair)
val notarySig = subProtocol(NotaryProtocol(tx.toWireTransaction()))
tx.addSignatureUnchecked(notarySig)
tx.toSignedTransaction(true)
}
with(serviceHub.storageService) {
validatedTransactions[issuance.id] = issuance
validatedTransactions[move.id] = move
}
serviceHub.recordTransactions(listOf(move))
return move.tx.outRef(0)
}

View File

@ -48,7 +48,7 @@ object AutoOfferProtocol {
}
fun register(node: Node) {
node.net.addMessageHandler("${TOPIC}.0") { msg, registration ->
node.net.addMessageHandler("$TOPIC.0") { msg, registration ->
val progressTracker = tracker()
ANSIProgressRenderer.progressTracker = progressTracker
progressTracker.currentStep = RECEIVED
@ -57,7 +57,7 @@ object AutoOfferProtocol {
progressTracker.currentStep = DEALING
// TODO required as messaging layer does not currently queue messages that arrive before we expect them
Thread.sleep(100)
val seller = TwoPartyDealProtocol.Instigator(autoOfferMessage.otherSide, node.services.networkMapCache.timestampingNodes.first(),
val seller = TwoPartyDealProtocol.Instigator(autoOfferMessage.otherSide, node.services.networkMapCache.notaryNodes.first(),
autoOfferMessage.dealBeingOffered, node.services.keyManagementService.freshKey(), autoOfferMessage.otherSessionID, progressTracker.childrenFor[DEALING]!!)
val future = node.smm.add("${TwoPartyDealProtocol.DEAL_TOPIC}.seller", seller)
// This is required because we are doing child progress outside of a subprotocol. In future, we should just wrap things like this in a protocol to avoid it
@ -93,19 +93,19 @@ object AutoOfferProtocol {
@Suspendable
override fun call(): SignedTransaction {
require(serviceHub.networkMapCache.timestampingNodes.isNotEmpty()) { "No timestamping nodes registered" }
require(serviceHub.networkMapCache.notaryNodes.isNotEmpty()) { "No notary nodes registered" }
val ourSessionID = random63BitValue()
val timestampingAuthority = serviceHub.networkMapCache.timestampingNodes.first()
val notary = serviceHub.networkMapCache.notaryNodes.first()
// need to pick which ever party is not us
val otherParty = notUs(*dealToBeOffered.parties).single()
val otherNode = (serviceHub.networkMapCache.nodeForPartyName(otherParty.name))
val otherNode = (serviceHub.networkMapCache.getNodeByLegalName(otherParty.name))
requireNotNull(otherNode) { "Cannot identify other party " + otherParty.name + ", know about: " + serviceHub.networkMapCache.partyNodes.map { it.identity } }
val otherSide = otherNode!!.address
progressTracker.currentStep = ANNOUNCING
send(TOPIC, otherSide, 0, AutoOfferMessage(serviceHub.networkService.myAddress, ourSessionID, dealToBeOffered))
progressTracker.currentStep = DEALING
val stx = subProtocol(TwoPartyDealProtocol.Acceptor(otherSide, timestampingAuthority.identity, dealToBeOffered, ourSessionID, progressTracker.childrenFor[DEALING]!!))
val stx = subProtocol(TwoPartyDealProtocol.Acceptor(otherSide, notary.identity, dealToBeOffered, ourSessionID, progressTracker.childrenFor[DEALING]!!))
return stx
}

View File

@ -59,7 +59,7 @@ object UpdateBusinessDayProtocol {
// This assumes we definitely have one key or the other
fun otherParty(deal: DealState): NodeInfo {
val ourKeys = serviceHub.keyManagementService.keys.keys
return serviceHub.networkMapCache.nodeForPartyName(deal.parties.single { !ourKeys.contains(it.owningKey) }.name)!!
return serviceHub.networkMapCache.getNodeByLegalName(deal.parties.single { !ourKeys.contains(it.owningKey) }.name)!!
}
// TODO we should make this more object oriented when we can ask a state for it's contract
@ -102,7 +102,7 @@ object UpdateBusinessDayProtocol {
val deal: InterestRateSwap.State = dealStateAndRef.state
val myOldParty = deal.parties.single { it.name == myName }
val keyPair = serviceHub.keyManagementService.toKeyPair(myOldParty.owningKey)
val participant = TwoPartyDealProtocol.Floater(party.address, sessionID, serviceHub.networkMapCache.timestampingNodes[0], dealStateAndRef,
val participant = TwoPartyDealProtocol.Floater(party.address, sessionID, serviceHub.networkMapCache.notaryNodes[0], dealStateAndRef,
keyPair,
sessionID, progressTracker.childrenFor[FIXING]!!)
val result = subProtocol(participant)
@ -114,7 +114,7 @@ object UpdateBusinessDayProtocol {
progressTracker.childrenFor[FIXING] = TwoPartyDealProtocol.Secondary.tracker()
progressTracker.currentStep = FIXING
val participant = TwoPartyDealProtocol.Fixer(party.address, serviceHub.networkMapCache.timestampingNodes[0].identity, dealStateAndRef, sessionID, progressTracker.childrenFor[FIXING]!!)
val participant = TwoPartyDealProtocol.Fixer(party.address, serviceHub.networkMapCache.notaryNodes[0].identity, dealStateAndRef, sessionID, progressTracker.childrenFor[FIXING]!!)
val result = subProtocol(participant)
return result.tx.outRef(0)
}
@ -124,7 +124,7 @@ object UpdateBusinessDayProtocol {
object Handler {
fun register(node: Node) {
node.net.addMessageHandler("${TOPIC}.0") { msg, registration ->
node.net.addMessageHandler("$TOPIC.0") { msg, registration ->
// Just to validate we got the message
val updateBusinessDayMessage = msg.data.deserialize<UpdateBusinessDayMessage>()
if ((node.services.clock as DemoClock).updateDate(updateBusinessDayMessage.date)) {

View File

@ -0,0 +1,94 @@
package protocols
import co.paralleluniverse.fibers.Suspendable
import core.Party
import core.TimestampCommand
import core.WireTransaction
import core.crypto.DigitalSignature
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.protocols.ProtocolLogic
import core.random63BitValue
import core.serialization.SerializedBytes
import core.utilities.ProgressTracker
import core.utilities.UntrustworthyData
import java.security.PublicKey
/**
* A protocol to be used for obtaining a signature from a [NotaryService] ascertaining the transaction
* timestamp is correct and none of its inputs have been used in another completed transaction
*
* @throws NotaryException in case the any of the inputs to the transaction have been consumed
* by another transaction or the timestamp is invalid
*/
class NotaryProtocol(private val wtx: WireTransaction,
override val progressTracker: ProgressTracker = NotaryProtocol.tracker()) : ProtocolLogic<DigitalSignature.LegallyIdentifiable>() {
companion object {
val TOPIC = "platform.notary.request"
object REQUESTING : ProgressTracker.Step("Requesting signature by Notary service")
object VALIDATING : ProgressTracker.Step("Validating response from Notary service")
fun tracker() = ProgressTracker(REQUESTING, VALIDATING)
}
lateinit var notaryNode: NodeInfo
@Suspendable
override fun call(): DigitalSignature.LegallyIdentifiable {
progressTracker.currentStep = REQUESTING
notaryNode = findNotaryNode()
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 notaryResult = validateResponse(response)
return notaryResult.sig ?: throw NotaryException(notaryResult.error!!)
}
private fun validateResponse(response: UntrustworthyData<NotaryService.Result>): NotaryService.Result {
progressTracker.currentStep = VALIDATING
response.validate {
if (it.sig != null) validateSignature(it.sig, wtx.serialized)
else if (it.error is NotaryError.Conflict) it.error.conflict.verified()
else if (it.error !is NotaryError)
throw IllegalStateException("Received invalid result from Notary service '${notaryNode.identity}'")
return it
}
}
private fun validateSignature(sig: DigitalSignature.LegallyIdentifiable, data: SerializedBytes<WireTransaction>) {
check(sig.signer == notaryNode.identity) { "Notary result not signed by the correct service" }
sig.verifyWithECDSA(data)
}
private fun findNotaryNode(): NodeInfo {
var maybeNotaryKey: PublicKey? = null
val timestampCommand = wtx.commands.singleOrNull { it.value is TimestampCommand }
if (timestampCommand != null) maybeNotaryKey = timestampCommand.signers.first()
for (stateRef in wtx.inputs) {
val inputNotaryKey = serviceHub.loadState(stateRef).notary.owningKey
if (maybeNotaryKey != null)
check(maybeNotaryKey == inputNotaryKey) { "Input states and timestamp must have the same Notary" }
else maybeNotaryKey = inputNotaryKey
}
val notaryKey = maybeNotaryKey ?: throw IllegalStateException("Transaction does not specify a Notary")
val notaryNode = serviceHub.networkMapCache.getNodeByPublicKey(notaryKey)
return notaryNode ?: throw IllegalStateException("No Notary node can be found with the specified public key")
}
/** TODO: The caller must authenticate instead of just specifying its identity */
class SignRequest(val txBits: SerializedBytes<WireTransaction>,
val callerIdentity: Party,
replyTo: SingleMessageRecipient,
sessionID: Long) : AbstractRequestMessage(replyTo, sessionID)
}

View File

@ -16,7 +16,6 @@ import core.utilities.trace
import java.security.KeyPair
import java.security.PublicKey
import java.security.SignatureException
import java.time.Instant
/**
* This asset trading protocol implements a "delivery vs payment" type swap. It has two parties (B and S for buyer
@ -44,18 +43,18 @@ import java.time.Instant
object TwoPartyTradeProtocol {
val TRADE_TOPIC = "platform.trade"
fun runSeller(smm: StateMachineManager, timestampingAuthority: NodeInfo,
fun runSeller(smm: StateMachineManager, notary: NodeInfo,
otherSide: SingleMessageRecipient, assetToSell: StateAndRef<OwnableState>, price: Amount,
myKeyPair: KeyPair, buyerSessionID: Long): ListenableFuture<SignedTransaction> {
val seller = Seller(otherSide, timestampingAuthority, assetToSell, price, myKeyPair, buyerSessionID)
val seller = Seller(otherSide, notary, assetToSell, price, myKeyPair, buyerSessionID)
return smm.add("${TRADE_TOPIC}.seller", seller)
}
fun runBuyer(smm: StateMachineManager, timestampingAuthority: NodeInfo,
fun runBuyer(smm: StateMachineManager, notaryNode: NodeInfo,
otherSide: SingleMessageRecipient, acceptablePrice: Amount, typeToBuy: Class<out OwnableState>,
sessionID: Long): ListenableFuture<SignedTransaction> {
val buyer = Buyer(otherSide, timestampingAuthority.identity, acceptablePrice, typeToBuy, sessionID)
return smm.add("${TRADE_TOPIC}.buyer", buyer)
val buyer = Buyer(otherSide, notaryNode.identity, acceptablePrice, typeToBuy, sessionID)
return smm.add("$TRADE_TOPIC.buyer", buyer)
}
class UnacceptablePriceException(val givenPrice: Amount) : Exception()
@ -71,10 +70,11 @@ object TwoPartyTradeProtocol {
val sessionID: Long
)
class SignaturesFromSeller(val timestampAuthoritySig: DigitalSignature.WithKey, val sellerSig: DigitalSignature.WithKey)
class SignaturesFromSeller(val sellerSig: DigitalSignature.WithKey,
val notarySig: DigitalSignature.WithKey)
open class Seller(val otherSide: SingleMessageRecipient,
val timestampingAuthority: NodeInfo,
val notaryNode: NodeInfo,
val assetToSell: StateAndRef<OwnableState>,
val price: Amount,
val myKeyPair: KeyPair,
@ -83,12 +83,16 @@ object TwoPartyTradeProtocol {
companion object {
object AWAITING_PROPOSAL : ProgressTracker.Step("Awaiting transaction proposal")
object VERIFYING : ProgressTracker.Step("Verifying transaction proposal")
object SIGNING : ProgressTracker.Step("Signing transaction")
object TIMESTAMPING : ProgressTracker.Step("Timestamping transaction")
object NOTARY : ProgressTracker.Step("Getting notary signature")
object SENDING_SIGS : ProgressTracker.Step("Sending transaction signatures to buyer")
fun tracker() = ProgressTracker(AWAITING_PROPOSAL, VERIFYING, SIGNING, TIMESTAMPING, SENDING_SIGS)
fun tracker() = ProgressTracker(AWAITING_PROPOSAL, VERIFYING, SIGNING, NOTARY, SENDING_SIGS)
}
@Suspendable
@ -97,15 +101,15 @@ object TwoPartyTradeProtocol {
// These two steps could be done in parallel, in theory. Our framework doesn't support that yet though.
val ourSignature = signWithOurKey(partialTX)
val tsaSig = timestamp(partialTX)
val notarySignature = getNotarySignature(partialTX)
return sendSignatures(partialTX, ourSignature, tsaSig)
return sendSignatures(partialTX, ourSignature, notarySignature)
}
@Suspendable
private fun timestamp(partialTX: SignedTransaction): DigitalSignature.LegallyIdentifiable {
progressTracker.currentStep = TIMESTAMPING
return subProtocol(TimestampingProtocol(timestampingAuthority, partialTX.txBits))
private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.LegallyIdentifiable {
progressTracker.currentStep = NOTARY
return subProtocol(NotaryProtocol(stx.tx))
}
@Suspendable
@ -126,7 +130,7 @@ object TwoPartyTradeProtocol {
// Check that the tx proposed by the buyer is valid.
val missingSigs = it.verify(throwIfSignaturesAreMissing = false)
if (missingSigs != setOf(myKeyPair.public, timestampingAuthority.identity.owningKey))
if (missingSigs != setOf(myKeyPair.public, notaryNode.identity.owningKey))
throw SignatureException("The set of missing signatures is not as expected: $missingSigs")
val wtx: WireTransaction = it.tx
@ -162,7 +166,6 @@ object TwoPartyTradeProtocol {
subProtocol(ResolveTransactionsProtocol(dependencyTxIDs, otherSide))
}
@Suspendable
open fun signWithOurKey(partialTX: SignedTransaction): DigitalSignature.WithKey {
progressTracker.currentStep = SIGNING
return myKeyPair.signWithECDSA(partialTX.txBits)
@ -170,26 +173,29 @@ object TwoPartyTradeProtocol {
@Suspendable
private fun sendSignatures(partialTX: SignedTransaction, ourSignature: DigitalSignature.WithKey,
tsaSig: DigitalSignature.LegallyIdentifiable): SignedTransaction {
notarySignature: DigitalSignature.LegallyIdentifiable): SignedTransaction {
progressTracker.currentStep = SENDING_SIGS
val fullySigned = partialTX + tsaSig + ourSignature
val fullySigned = partialTX + ourSignature + notarySignature
logger.trace { "Built finished transaction, sending back to secondary!" }
send(TRADE_TOPIC, otherSide, buyerSessionID, SignaturesFromSeller(tsaSig, ourSignature))
send(TRADE_TOPIC, otherSide, buyerSessionID, SignaturesFromSeller(ourSignature, notarySignature))
return fullySigned
}
}
open class Buyer(val otherSide: SingleMessageRecipient,
val timestampingAuthority: Party,
val notary: Party,
val acceptablePrice: Amount,
val typeToBuy: Class<out OwnableState>,
val sessionID: Long) : ProtocolLogic<SignedTransaction>() {
object RECEIVING : ProgressTracker.Step("Waiting for seller trading info")
object VERIFYING : ProgressTracker.Step("Verifying seller assets")
object SIGNING : ProgressTracker.Step("Generating and signing transaction proposal")
object SWAPPING_SIGNATURES : ProgressTracker.Step("Swapping signatures with the seller")
override val progressTracker = ProgressTracker(RECEIVING, VERIFYING, SIGNING, SWAPPING_SIGNATURES)
@ -207,7 +213,7 @@ object TwoPartyTradeProtocol {
val signatures = swapSignaturesWithSeller(stx, tradeRequest.sessionID)
logger.trace { "Got signatures from seller, verifying ... " }
val fullySigned = stx + signatures.timestampAuthoritySig + signatures.sellerSig
val fullySigned = stx + signatures.sellerSig + signatures.notarySig
fullySigned.verify()
logger.trace { "Signatures received are valid. Trade complete! :-)" }
@ -281,7 +287,8 @@ object TwoPartyTradeProtocol {
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
// to have one.
ptx.setTime(Instant.now(), timestampingAuthority, 30.seconds)
val currentTime = serviceHub.clock.instant()
ptx.setTime(currentTime, notary, 30.seconds)
return Pair(ptx, cashSigningPubKeys)
}
}

View File

@ -18,7 +18,8 @@ class CashTests {
val inState = Cash.State(
deposit = MEGA_CORP.ref(1),
amount = 1000.DOLLARS,
owner = DUMMY_PUBKEY_1
owner = DUMMY_PUBKEY_1,
notary = DUMMY_NOTARY
)
val outState = inState.copy(owner = DUMMY_PUBKEY_2)
@ -63,7 +64,7 @@ class CashTests {
fun issueMoney() {
// Check we can't "move" money into existence.
transaction {
input { DummyContract.State() }
input { DummyContract.State(notary = DUMMY_NOTARY) }
output { outState }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
@ -82,7 +83,8 @@ class CashTests {
Cash.State(
amount = 1000.DOLLARS,
owner = DUMMY_PUBKEY_1,
deposit = MINI_CORP.ref(12, 34)
deposit = MINI_CORP.ref(12, 34),
notary = DUMMY_NOTARY
)
}
tweak {
@ -95,7 +97,7 @@ class CashTests {
// Test generation works.
val ptx = TransactionBuilder()
Cash().generateIssue(ptx, 100.DOLLARS, MINI_CORP.ref(12, 34), owner = DUMMY_PUBKEY_1)
Cash().generateIssue(ptx, 100.DOLLARS, MINI_CORP.ref(12, 34), owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
assertTrue(ptx.inputStates().isEmpty())
val s = ptx.outputStates()[0] as Cash.State
assertEquals(100.DOLLARS, s.amount)
@ -332,7 +334,7 @@ class CashTests {
fun multiCurrency() {
// Check we can do an atomic currency trade tx.
transaction {
val pounds = Cash.State(MINI_CORP.ref(3, 4, 5), 658.POUNDS, DUMMY_PUBKEY_2)
val pounds = Cash.State(MINI_CORP.ref(3, 4, 5), 658.POUNDS, DUMMY_PUBKEY_2, DUMMY_NOTARY)
input { inState `owned by` DUMMY_PUBKEY_1 }
input { pounds }
output { inState `owned by` DUMMY_PUBKEY_2 }
@ -352,7 +354,7 @@ class CashTests {
fun makeCash(amount: Amount, corp: Party, depositRef: Byte = 1) =
StateAndRef(
Cash.State(corp.ref(depositRef), amount, OUR_PUBKEY_1),
Cash.State(corp.ref(depositRef), amount, OUR_PUBKEY_1, DUMMY_NOTARY),
StateRef(SecureHash.randomSHA256(), Random().nextInt(32))
)
@ -374,13 +376,13 @@ class CashTests {
val wtx = makeSpend(100.DOLLARS, THEIR_PUBKEY_1)
assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1), wtx.outputs[0])
assertEquals(OUR_PUBKEY_1, wtx.commands[0].signers[0])
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
}
@Test
fun generateSimpleSpendWithParties() {
val tx = TransactionBuilder()
Cash().generateSpend(tx, 80.DOLLARS, ALICE, WALLET, setOf(MINI_CORP))
Cash().generateSpend(tx, 80.DOLLARS, ALICE_PUBKEY, WALLET, setOf(MINI_CORP))
assertEquals(WALLET[2].ref, tx.inputStates()[0])
}
@ -390,7 +392,7 @@ class CashTests {
assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 10.DOLLARS), wtx.outputs[0])
assertEquals(WALLET[0].state.copy(amount = 90.DOLLARS), wtx.outputs[1])
assertEquals(OUR_PUBKEY_1, wtx.commands[0].signers[0])
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
}
@Test
@ -399,7 +401,7 @@ class CashTests {
assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[1].ref, wtx.inputs[1])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS), wtx.outputs[0])
assertEquals(OUR_PUBKEY_1, wtx.commands[0].signers[0])
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
}
@Test
@ -410,7 +412,7 @@ class CashTests {
assertEquals(WALLET[2].ref, wtx.inputs[2])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS), wtx.outputs[0])
assertEquals(WALLET[2].state.copy(owner = THEIR_PUBKEY_1), wtx.outputs[1])
assertEquals(OUR_PUBKEY_1, wtx.commands[0].signers[0])
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
}
@Test

View File

@ -2,15 +2,11 @@ package contracts
import core.*
import core.crypto.SecureHash
import core.node.services.DummyTimestampingAuthority
import core.node.services.TimestampingError
import core.testutils.*
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.time.Clock
import java.time.Instant
import java.time.ZoneOffset
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
@ -26,7 +22,8 @@ class JavaCommercialPaperTest() : ICommercialPaperTestTemplate {
MEGA_CORP.ref(123),
MEGA_CORP_PUBKEY,
1000.DOLLARS,
TEST_TX_TIME + 7.days
TEST_TX_TIME + 7.days,
DUMMY_NOTARY
)
override fun getIssueCommand(): CommandData = JavaCommercialPaper.Commands.Issue()
@ -39,7 +36,8 @@ class KotlinCommercialPaperTest() : ICommercialPaperTestTemplate {
issuance = MEGA_CORP.ref(123),
owner = MEGA_CORP_PUBKEY,
faceValue = 1000.DOLLARS,
maturityDate = TEST_TX_TIME + 7.days
maturityDate = TEST_TX_TIME + 7.days,
notary = DUMMY_NOTARY
)
override fun getIssueCommand(): CommandData = CommercialPaper.Commands.Issue()
@ -108,28 +106,6 @@ class CommercialPaperTestsGeneric {
}
}
@Test
fun `timestamp out of range`() {
// Check what happens if the timestamp on the transaction itself defines a range that doesn't include true
// time as measured by a TSA (which is running five hours ahead in this test).
CommercialPaper().generateIssue(MINI_CORP.ref(123), 10000.DOLLARS, TEST_TX_TIME + 30.days).apply {
setTime(TEST_TX_TIME, DummyTimestampingAuthority.identity, 30.seconds)
signWith(MINI_CORP_KEY)
assertFailsWith(TimestampingError.NotOnTimeException::class) {
timestamp(DummyTimestamper(Clock.fixed(TEST_TX_TIME + 5.hours, ZoneOffset.UTC)))
}
}
// Check that it also fails if true time is before the threshold (we are trying to timestamp too early).
CommercialPaper().generateIssue(MINI_CORP.ref(123), 10000.DOLLARS, TEST_TX_TIME + 30.days).apply {
setTime(TEST_TX_TIME, DummyTimestampingAuthority.identity, 30.seconds)
signWith(MINI_CORP_KEY)
assertFailsWith(TimestampingError.NotOnTimeException::class) {
val tsaClock = Clock.fixed(TEST_TX_TIME - 5.hours, ZoneOffset.UTC)
timestamp(DummyTimestamper(tsaClock), Clock.fixed(TEST_TX_TIME, ZoneOffset.UTC))
}
}
}
@Test
fun `issue cannot replace an existing state`() {
transactionGroup {
@ -166,30 +142,30 @@ class CommercialPaperTestsGeneric {
fun `issue move and then redeem`() {
// MiniCorp issues $10,000 of commercial paper, to mature in 30 days, owned initially by itself.
val issueTX: LedgerTransaction = run {
val ptx = CommercialPaper().generateIssue(MINI_CORP.ref(123), 10000.DOLLARS, TEST_TX_TIME + 30.days).apply {
setTime(TEST_TX_TIME, DummyTimestampingAuthority.identity, 30.seconds)
val ptx = CommercialPaper().generateIssue(MINI_CORP.ref(123), 10000.DOLLARS, TEST_TX_TIME + 30.days, DUMMY_NOTARY).apply {
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
signWith(MINI_CORP_KEY)
timestamp(DUMMY_TIMESTAMPER)
signWith(DUMMY_NOTARY_KEY)
}
val stx = ptx.toSignedTransaction()
stx.verifyToLedgerTransaction(MockIdentityService, attachments)
}
val (alicesWalletTX, alicesWallet) = cashOutputsToWallet(
3000.DOLLARS.CASH `owned by` ALICE,
3000.DOLLARS.CASH `owned by` ALICE,
3000.DOLLARS.CASH `owned by` ALICE
3000.DOLLARS.CASH `owned by` ALICE_PUBKEY,
3000.DOLLARS.CASH `owned by` ALICE_PUBKEY,
3000.DOLLARS.CASH `owned by` ALICE_PUBKEY
)
// Alice pays $9000 to MiniCorp to own some of their debt.
val moveTX: LedgerTransaction = run {
val ptx = TransactionBuilder()
Cash().generateSpend(ptx, 9000.DOLLARS, MINI_CORP_PUBKEY, alicesWallet)
CommercialPaper().generateMove(ptx, issueTX.outRef(0), ALICE)
CommercialPaper().generateMove(ptx, issueTX.outRef(0), ALICE_PUBKEY)
ptx.signWith(MINI_CORP_KEY)
ptx.signWith(ALICE_KEY)
val stx = ptx.toSignedTransaction()
stx.verifyToLedgerTransaction(MockIdentityService, attachments)
ptx.signWith(DUMMY_NOTARY_KEY)
ptx.toSignedTransaction().verifyToLedgerTransaction(MockIdentityService, attachments)
}
// Won't be validated.
@ -200,11 +176,11 @@ class CommercialPaperTestsGeneric {
fun makeRedeemTX(time: Instant): LedgerTransaction {
val ptx = TransactionBuilder()
ptx.setTime(time, DummyTimestampingAuthority.identity, 30.seconds)
ptx.setTime(time, DUMMY_NOTARY, 30.seconds)
CommercialPaper().generateRedeem(ptx, moveTX.outRef(1), corpWallet)
ptx.signWith(ALICE_KEY)
ptx.signWith(MINI_CORP_KEY)
ptx.timestamp(DUMMY_TIMESTAMPER)
ptx.signWith(DUMMY_NOTARY_KEY)
return ptx.toSignedTransaction().verifyToLedgerTransaction(MockIdentityService, attachments)
}
@ -226,7 +202,7 @@ class CommercialPaperTestsGeneric {
val someProfits = 1200.DOLLARS
return transactionGroupFor() {
roots {
transaction(900.DOLLARS.CASH `owned by` ALICE label "alice's $900")
transaction(900.DOLLARS.CASH `owned by` ALICE_PUBKEY label "alice's $900")
transaction(someProfits.CASH `owned by` MEGA_CORP_PUBKEY label "some profits")
}
@ -243,8 +219,8 @@ class CommercialPaperTestsGeneric {
input("paper")
input("alice's $900")
output("borrowed $900") { 900.DOLLARS.CASH `owned by` MEGA_CORP_PUBKEY }
output("alice's paper") { "paper".output `owned by` ALICE }
arg(ALICE) { Cash.Commands.Move() }
output("alice's paper") { "paper".output `owned by` ALICE_PUBKEY }
arg(ALICE_PUBKEY) { Cash.Commands.Move() }
arg(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
}
@ -254,13 +230,13 @@ class CommercialPaperTestsGeneric {
input("alice's paper")
input("some profits")
output("Alice's profit") { aliceGetsBack.CASH `owned by` ALICE }
output("Alice's profit") { aliceGetsBack.CASH `owned by` ALICE_PUBKEY }
output("Change") { (someProfits - aliceGetsBack).CASH `owned by` MEGA_CORP_PUBKEY }
if (!destroyPaperAtRedemption)
output { "paper".output }
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
arg(ALICE) { thisTest.getRedeemCommand() }
arg(ALICE_PUBKEY) { thisTest.getRedeemCommand() }
timestamp(redemptionTime)
}

View File

@ -2,7 +2,6 @@ package contracts
import core.*
import core.crypto.SecureHash
import core.node.services.DummyTimestampingAuthority
import core.testutils.*
import org.junit.Test
import java.time.Instant
@ -19,7 +18,8 @@ class CrowdFundTests {
closingTime = TEST_TX_TIME + 7.days
),
closed = false,
pledges = ArrayList<CrowdFund.Pledge>()
pledges = ArrayList<CrowdFund.Pledge>(),
notary = DUMMY_NOTARY
)
val attachments = MockStorageService().attachments
@ -58,7 +58,7 @@ class CrowdFundTests {
private fun raiseFunds(): TransactionGroupDSL<CrowdFund.State> {
return transactionGroupFor {
roots {
transaction(1000.DOLLARS.CASH `owned by` ALICE label "alice's $1000")
transaction(1000.DOLLARS.CASH `owned by` ALICE_PUBKEY label "alice's $1000")
}
// 1. Create the funding opportunity
@ -74,12 +74,12 @@ class CrowdFundTests {
input("alice's $1000")
output ("pledged opportunity") {
CF_1.copy(
pledges = CF_1.pledges + CrowdFund.Pledge(ALICE, 1000.DOLLARS)
pledges = CF_1.pledges + CrowdFund.Pledge(ALICE_PUBKEY, 1000.DOLLARS)
)
}
output { 1000.DOLLARS.CASH `owned by` MINI_CORP_PUBKEY }
arg(ALICE) { Cash.Commands.Move() }
arg(ALICE) { CrowdFund.Commands.Pledge() }
arg(ALICE_PUBKEY) { Cash.Commands.Move() }
arg(ALICE_PUBKEY) { CrowdFund.Commands.Pledge() }
timestamp(TEST_TX_TIME)
}
@ -103,33 +103,31 @@ class CrowdFundTests {
// MiniCorp registers a crowdfunding of $1,000, to close in 7 days.
val registerTX: LedgerTransaction = run {
// craftRegister returns a partial transaction
val ptx = CrowdFund().generateRegister(MINI_CORP.ref(123), 1000.DOLLARS, "crowd funding", TEST_TX_TIME + 7.days).apply {
setTime(TEST_TX_TIME, DummyTimestampingAuthority.identity, 30.seconds)
val ptx = CrowdFund().generateRegister(MINI_CORP.ref(123), 1000.DOLLARS, "crowd funding", TEST_TX_TIME + 7.days, DUMMY_NOTARY).apply {
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
signWith(MINI_CORP_KEY)
timestamp(DUMMY_TIMESTAMPER)
signWith(DUMMY_NOTARY_KEY)
}
val stx = ptx.toSignedTransaction()
stx.verifyToLedgerTransaction(MockIdentityService, attachments)
ptx.toSignedTransaction().verifyToLedgerTransaction(MockIdentityService, attachments)
}
// let's give Alice some funds that she can invest
val (aliceWalletTX, aliceWallet) = cashOutputsToWallet(
200.DOLLARS.CASH `owned by` ALICE,
500.DOLLARS.CASH `owned by` ALICE,
300.DOLLARS.CASH `owned by` ALICE
200.DOLLARS.CASH `owned by` ALICE_PUBKEY,
500.DOLLARS.CASH `owned by` ALICE_PUBKEY,
300.DOLLARS.CASH `owned by` ALICE_PUBKEY
)
// Alice pays $1000 to MiniCorp to fund their campaign.
val pledgeTX: LedgerTransaction = run {
val ptx = TransactionBuilder()
CrowdFund().generatePledge(ptx, registerTX.outRef(0), ALICE)
CrowdFund().generatePledge(ptx, registerTX.outRef(0), ALICE_PUBKEY)
Cash().generateSpend(ptx, 1000.DOLLARS, MINI_CORP_PUBKEY, aliceWallet)
ptx.setTime(TEST_TX_TIME, DummyTimestampingAuthority.identity, 30.seconds)
ptx.setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
ptx.signWith(ALICE_KEY)
ptx.timestamp(DUMMY_TIMESTAMPER)
val stx = ptx.toSignedTransaction()
ptx.signWith(DUMMY_NOTARY_KEY)
// this verify passes - the transaction contains an output cash, necessary to verify the fund command
stx.verifyToLedgerTransaction(MockIdentityService, attachments)
ptx.toSignedTransaction().verifyToLedgerTransaction(MockIdentityService, attachments)
}
// Won't be validated.
@ -140,12 +138,11 @@ class CrowdFundTests {
// MiniCorp closes their campaign.
fun makeFundedTX(time: Instant): LedgerTransaction {
val ptx = TransactionBuilder()
ptx.setTime(time, DUMMY_TIMESTAMPER.identity, 30.seconds)
ptx.setTime(time, DUMMY_NOTARY, 30.seconds)
CrowdFund().generateClose(ptx, pledgeTX.outRef(0), miniCorpWallet)
ptx.signWith(MINI_CORP_KEY)
ptx.timestamp(DUMMY_TIMESTAMPER)
val stx = ptx.toSignedTransaction()
return stx.verifyToLedgerTransaction(MockIdentityService, attachments)
ptx.signWith(DUMMY_NOTARY_KEY)
return ptx.toSignedTransaction().verifyToLedgerTransaction(MockIdentityService, attachments)
}
val tooEarlyClose = makeFundedTX(TEST_TX_TIME + 6.days)

View File

@ -1,7 +1,6 @@
package contracts
import core.*
import core.node.services.DummyTimestampingAuthority
import core.testutils.*
import org.junit.Test
import java.math.BigDecimal
@ -96,7 +95,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")
)
InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common)
InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common, notary = DUMMY_NOTARY)
}
2 -> {
// 10y swap, we pay 1.3% fixed 30/360 semi, rec 3m usd libor act/360 Q on 25m notional (mod foll/adj on both sides)
@ -186,7 +185,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")
)
return InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common)
return InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common, notary = DUMMY_NOTARY)
}
else -> TODO("IRS number $irsSelect not defined")
@ -203,7 +202,8 @@ class IRSTests {
exampleIRS.fixedLeg,
exampleIRS.floatingLeg,
exampleIRS.calculation,
exampleIRS.common
exampleIRS.common,
DUMMY_NOTARY
)
val outState = inState.copy()
@ -228,15 +228,14 @@ class IRSTests {
fixedLeg = dummyIRS.fixedLeg,
floatingLeg = dummyIRS.floatingLeg,
calculation = dummyIRS.calculation,
common = dummyIRS.common).apply {
setTime(TEST_TX_TIME, DummyTimestampingAuthority.identity, 30.seconds)
common = dummyIRS.common,
notary = DUMMY_NOTARY).apply {
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
signWith(MEGA_CORP_KEY)
signWith(MINI_CORP_KEY)
timestamp(DUMMY_TIMESTAMPER)
signWith(DUMMY_NOTARY_KEY)
}
val stx = gtx.toSignedTransaction()
stx.verifyToLedgerTransaction(MockIdentityService, attachments)
gtx.toSignedTransaction().verifyToLedgerTransaction(MockIdentityService, attachments)
}
return genTX
}
@ -286,7 +285,7 @@ class IRSTests {
newCalculation = newCalculation.applyFixing(it.key, FixedRate(PercentageRatioUnit(it.value)))
}
val newIRS = InterestRateSwap.State(irs.fixedLeg, irs.floatingLeg, newCalculation, irs.common)
val newIRS = InterestRateSwap.State(irs.fixedLeg, irs.floatingLeg, newCalculation, irs.common, DUMMY_NOTARY)
println(newIRS.exportIRSToCSV())
}
@ -315,13 +314,12 @@ class IRSTests {
val fixing = Pair(nextFixingDate, FixedRate("0.052".percent))
InterestRateSwap().generateFix(tx, previousTXN.outRef(0), fixing)
with(tx) {
setTime(TEST_TX_TIME, DummyTimestampingAuthority.identity, 30.seconds)
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
signWith(MEGA_CORP_KEY)
signWith(MINI_CORP_KEY)
timestamp(DUMMY_TIMESTAMPER)
signWith(DUMMY_NOTARY_KEY)
}
val stx = tx.toSignedTransaction()
stx.verifyToLedgerTransaction(MockIdentityService, attachments)
tx.toSignedTransaction().verifyToLedgerTransaction(MockIdentityService, attachments)
}
currentIRS = previousTXN.outputs.filterIsInstance<InterestRateSwap.State>().single()
println(currentIRS.prettyPrint())

View File

@ -1,18 +1,19 @@
package core
import com.codahale.metrics.MetricRegistry
import core.crypto.*
import core.crypto.SecureHash
import core.crypto.generateKeyPair
import core.crypto.sha256
import core.messaging.MessagingService
import core.node.ServiceHub
import core.node.services.*
import core.node.storage.Checkpoint
import core.node.storage.CheckpointStorage
import core.node.subsystems.*
import core.serialization.SerializedBytes
import core.serialization.deserialize
import core.node.services.AttachmentStorage
import core.node.services.IdentityService
import core.node.services.NetworkMapService
import core.testing.MockNetworkMapCache
import core.testutils.MockIdentityService
import core.testutils.TEST_TX_TIME
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
@ -21,32 +22,11 @@ import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey
import java.time.Clock
import java.time.Duration
import java.time.ZoneId
import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.jar.JarInputStream
import javax.annotation.concurrent.ThreadSafe
/**
* A test/mock timestamping service that doesn't use any signatures or security. It timestamps with
* the provided clock which defaults to [TEST_TX_TIME], an arbitrary point on the timeline.
*/
class DummyTimestamper(var clock: Clock = Clock.fixed(TEST_TX_TIME, ZoneId.systemDefault()),
val tolerance: Duration = 30.seconds) : TimestamperService {
override val identity = DummyTimestampingAuthority.identity
override fun timestamp(wtxBytes: SerializedBytes<WireTransaction>): DigitalSignature.LegallyIdentifiable {
val wtx = wtxBytes.deserialize()
val timestamp = wtx.commands.mapNotNull { it.value as? TimestampCommand }.single()
if (timestamp.before!! until clock.instant() > tolerance)
throw TimestampingError.NotOnTimeException()
return DummyTimestampingAuthority.key.signWithECDSA(wtxBytes.bits, identity)
}
}
val DUMMY_TIMESTAMPER = DummyTimestamper()
class MockKeyManagementService(vararg initialKeys: KeyPair) : KeyManagementService {
override val keys: MutableMap<PublicKey, PrivateKey>

View File

@ -1,7 +1,6 @@
package core
import contracts.Cash
import core.crypto.SecureHash
import core.testutils.*
import org.junit.Test
import kotlin.test.assertEquals
@ -9,7 +8,7 @@ import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals
class TransactionGroupTests {
val A_THOUSAND_POUNDS = Cash.State(MINI_CORP.ref(1, 2, 3), 1000.POUNDS, MINI_CORP_PUBKEY)
val A_THOUSAND_POUNDS = Cash.State(MINI_CORP.ref(1, 2, 3), 1000.POUNDS, MINI_CORP_PUBKEY, DUMMY_NOTARY)
@Test
fun success() {
@ -20,13 +19,13 @@ class TransactionGroupTests {
transaction {
input("£1000")
output("alice's £1000") { A_THOUSAND_POUNDS `owned by` ALICE }
output("alice's £1000") { A_THOUSAND_POUNDS `owned by` ALICE_PUBKEY }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
}
transaction {
input("alice's £1000")
arg(ALICE) { Cash.Commands.Move() }
arg(ALICE_PUBKEY) { Cash.Commands.Move() }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Exit(1000.POUNDS) }
}
@ -44,7 +43,7 @@ class TransactionGroupTests {
val conflict1 = transaction {
input("cash")
val HALF = A_THOUSAND_POUNDS.copy(amount = 500.POUNDS) `owned by` BOB
val HALF = A_THOUSAND_POUNDS.copy(amount = 500.POUNDS) `owned by` BOB_PUBKEY
output { HALF }
output { HALF }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
@ -55,7 +54,7 @@ class TransactionGroupTests {
// Alice tries to double spend back to herself.
val conflict2 = transaction {
input("cash")
val HALF = A_THOUSAND_POUNDS.copy(amount = 500.POUNDS) `owned by` ALICE
val HALF = A_THOUSAND_POUNDS.copy(amount = 500.POUNDS) `owned by` ALICE_PUBKEY
output { HALF }
output { HALF }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
@ -82,23 +81,23 @@ class TransactionGroupTests {
transaction {
input("cash")
output { A_THOUSAND_POUNDS `owned by` BOB }
output { A_THOUSAND_POUNDS `owned by` BOB_PUBKEY }
}
}
// We have to do this manually without the DSL because transactionGroup { } won't let us create a tx that
// points nowhere.
val ref = StateRef(SecureHash.randomSHA256(), 0)
val input = generateStateRef()
tg.txns += TransactionBuilder().apply {
addInputState(ref)
addInputState(input)
addOutputState(A_THOUSAND_POUNDS)
addCommand(Cash.Commands.Move(), BOB)
addCommand(Cash.Commands.Move(), BOB_PUBKEY)
}.toWireTransaction()
val e = assertFailsWith(TransactionResolutionException::class) {
tg.verify()
}
assertEquals(e.hash, ref.txhash)
assertEquals(e.hash, input.txhash)
}
@Test
@ -132,19 +131,21 @@ class TransactionGroupTests {
transaction {
input("£1000")
output("alice's £1000") { A_THOUSAND_POUNDS `owned by` ALICE }
output("alice's £1000") { A_THOUSAND_POUNDS `owned by` ALICE_PUBKEY }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
}
transaction {
input("alice's £1000")
arg(ALICE) { Cash.Commands.Move() }
arg(ALICE_PUBKEY) { Cash.Commands.Move() }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Exit(1000.POUNDS) }
}
}.signAll()
// Now go through the conversion -> verification path with them.
val ltxns = signedTxns.map { it.verifyToLedgerTransaction(MockIdentityService, MockStorageService().attachments) }.toSet()
val ltxns = signedTxns.map {
it.verifyToLedgerTransaction(MockIdentityService, MockStorageService().attachments)
}.toSet()
TransactionGroup(ltxns, emptySet()).verify()
}
}

View File

@ -7,8 +7,8 @@ import core.node.NodeConfiguration
import core.node.NodeInfo
import core.node.services.NetworkMapService
import core.node.services.NodeAttachmentService
import core.node.services.NotaryService
import core.node.services.ServiceType
import core.node.services.TimestamperService
import core.serialization.OpaqueBytes
import core.testing.MockNetwork
import core.testutils.rootCauseExceptions
@ -23,6 +23,7 @@ import java.nio.ByteBuffer
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import java.security.KeyPair
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import kotlin.test.assertEquals
@ -90,8 +91,8 @@ class AttachmentTests {
// Make a node that doesn't do sanity checking at load time.
val n0 = network.createNode(null, -1, object : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, networkMapAddr: NodeInfo?,
advertisedServices: Set<ServiceType>, id: Int): MockNetwork.MockNode {
return object : MockNetwork.MockNode(dir, config, network, networkMapAddr, advertisedServices, id) {
advertisedServices: Set<ServiceType>, id: Int, keyPair: KeyPair?): MockNetwork.MockNode {
return object : MockNetwork.MockNode(dir, config, network, networkMapAddr, advertisedServices, id, keyPair) {
override fun start(): MockNetwork.MockNode {
super.start()
(storage.attachments as NodeAttachmentService).checkAttachmentsOnLoad = false
@ -99,7 +100,7 @@ class AttachmentTests {
}
}
}
}, NetworkMapService.Type, TimestamperService.Type)
}, null, null, NetworkMapService.Type, NotaryService.Type)
val n1 = network.createNode(n0.info)
// Insert an attachment into node zero's store directly.

View File

@ -64,17 +64,21 @@ class TwoPartyTradeProtocolTests {
// allow interruption half way through.
net = MockNetwork(true)
transactionGroupFor<ContractState> {
val (aliceNode, bobNode) = net.createTwoNodes()
(bobNode.wallet as NodeWalletService).fillWithSomeTestCash(2000.DOLLARS)
val alicesFakePaper = fillUpForSeller(false, aliceNode.info.identity, null).second
val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val aliceNode = net.createPartyNode(notaryNode.info, ALICE.name, ALICE_KEY)
val bobNode = net.createPartyNode(notaryNode.info, BOB.name, BOB_KEY)
insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey)
(bobNode.wallet as NodeWalletService).fillWithSomeTestCash(DUMMY_NOTARY, 2000.DOLLARS)
val alicesFakePaper = fillUpForSeller(false, aliceNode.storage.myLegalIdentity.owningKey,
notaryNode.info.identity, null).second
insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey, notaryNode.storage.myLegalIdentityKey)
val buyerSessionID = random63BitValue()
val aliceResult = TwoPartyTradeProtocol.runSeller(
aliceNode.smm,
aliceNode.info,
notaryNode.info,
bobNode.net.myAddress,
lookup("alice's paper"),
1000.DOLLARS,
@ -83,7 +87,7 @@ class TwoPartyTradeProtocolTests {
)
val bobResult = TwoPartyTradeProtocol.runBuyer(
bobNode.smm,
aliceNode.info,
notaryNode.info,
aliceNode.net.myAddress,
1000.DOLLARS,
CommercialPaper.State::class.java,
@ -105,25 +109,26 @@ class TwoPartyTradeProtocolTests {
@Test
fun `shutdown and restore`() {
transactionGroupFor<ContractState> {
var (aliceNode, bobNode) = net.createTwoNodes()
val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val aliceNode = net.createPartyNode(notaryNode.info, ALICE.name, ALICE_KEY)
var bobNode = net.createPartyNode(notaryNode.info, BOB.name, BOB_KEY)
val aliceAddr = aliceNode.net.myAddress
val bobAddr = bobNode.net.myAddress as InMemoryMessagingNetwork.Handle
val networkMapAddr = aliceNode.info
val timestamperAddr = aliceNode.info
val networkMapAddr = notaryNode.info
// Clear network map registration messages through before we start
net.runNetwork()
(bobNode.wallet as NodeWalletService).fillWithSomeTestCash(2000.DOLLARS)
val alicesFakePaper = fillUpForSeller(false, timestamperAddr.identity, null).second
net.runNetwork() // Clear network map registration messages
(bobNode.wallet as NodeWalletService).fillWithSomeTestCash(DUMMY_NOTARY, 2000.DOLLARS)
val alicesFakePaper = fillUpForSeller(false, aliceNode.storage.myLegalIdentity.owningKey,
notaryNode.info.identity, null).second
insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey)
val buyerSessionID = random63BitValue()
val aliceFuture = TwoPartyTradeProtocol.runSeller(
aliceNode.smm,
timestamperAddr,
notaryNode.info,
bobAddr,
lookup("alice's paper"),
1000.DOLLARS,
@ -132,7 +137,7 @@ class TwoPartyTradeProtocolTests {
)
TwoPartyTradeProtocol.runBuyer(
bobNode.smm,
timestamperAddr,
notaryNode.info,
aliceAddr,
1000.DOLLARS,
CommercialPaper.State::class.java,
@ -167,10 +172,10 @@ class TwoPartyTradeProtocolTests {
// that Bob was waiting on before the reboot occurred.
bobNode = net.createNode(networkMapAddr, bobAddr.id, object : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, networkMapAddr: NodeInfo?,
advertisedServices: Set<ServiceType>, id: Int): MockNetwork.MockNode {
return MockNetwork.MockNode(dir, config, network, networkMapAddr, advertisedServices, bobAddr.id)
advertisedServices: Set<ServiceType>, id: Int, keyPair: KeyPair?): MockNetwork.MockNode {
return MockNetwork.MockNode(dir, config, network, networkMapAddr, advertisedServices, bobAddr.id, BOB_KEY)
}
})
}, BOB.name, BOB_KEY)
// Find the future representing the result of this state machine again.
var bobFuture = bobNode.smm.findStateMachines(TwoPartyTradeProtocol.Buyer::class.java).single().second
@ -187,12 +192,12 @@ class TwoPartyTradeProtocolTests {
// Creates a mock node with an overridden storage service that uses a RecordingMap, that lets us test the order
// of gets and puts.
private fun makeNodeWithTracking(name: String): MockNetwork.MockNode {
private fun makeNodeWithTracking(networkMapAddr: NodeInfo?, name: String, keyPair: KeyPair): MockNetwork.MockNode {
// Create a node in the mock network ...
return net.createNode(null, nodeFactory = object : MockNetwork.Factory {
return net.createNode(networkMapAddr, -1, object : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, networkMapAddr: NodeInfo?,
advertisedServices: Set<ServiceType>, id: Int): MockNetwork.MockNode {
return object : MockNetwork.MockNode(dir, config, network, networkMapAddr, advertisedServices, id) {
advertisedServices: Set<ServiceType>, id: Int, keyPair: KeyPair?): MockNetwork.MockNode {
return object : MockNetwork.MockNode(dir, config, network, networkMapAddr, advertisedServices, id, keyPair) {
// That constructs the storage service object in a customised way ...
override fun constructStorageService(attachments: NodeAttachmentService, checkpointStorage: CheckpointStorage, keypair: KeyPair, identity: Party): StorageServiceImpl {
// To use RecordingMaps instead of ordinary HashMaps.
@ -200,15 +205,15 @@ class TwoPartyTradeProtocolTests {
}
}
}
})
}, name, keyPair)
}
@Test
fun checkDependenciesOfSaleAssetAreResolved() {
transactionGroupFor<ContractState> {
val aliceNode = makeNodeWithTracking("alice")
val timestamperAddr = aliceNode.info
val bobNode = makeNodeWithTracking("bob")
val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val aliceNode = makeNodeWithTracking(notaryNode.info, ALICE.name, ALICE_KEY)
val bobNode = makeNodeWithTracking(notaryNode.info, BOB.name, BOB_KEY)
// Insert a prospectus type attachment into the commercial paper transaction.
val stream = ByteArrayOutputStream()
@ -221,14 +226,17 @@ class TwoPartyTradeProtocolTests {
val bobsFakeCash = fillUpForBuyer(false, bobNode.keyManagement.freshKey().public).second
val bobsSignedTxns = insertFakeTransactions(bobsFakeCash, bobNode.services)
val alicesFakePaper = fillUpForSeller(false, timestamperAddr.identity, attachmentID).second
val alicesFakePaper = fillUpForSeller(false, aliceNode.storage.myLegalIdentity.owningKey,
notaryNode.info.identity, attachmentID).second
val alicesSignedTxns = insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey)
val buyerSessionID = random63BitValue()
net.runNetwork() // Clear network map registration messages
TwoPartyTradeProtocol.runSeller(
aliceNode.smm,
timestamperAddr,
notaryNode.info,
bobNode.net.myAddress,
lookup("alice's paper"),
1000.DOLLARS,
@ -237,7 +245,7 @@ class TwoPartyTradeProtocolTests {
)
TwoPartyTradeProtocol.runBuyer(
bobNode.smm,
timestamperAddr,
notaryNode.info,
aliceNode.net.myAddress,
1000.DOLLARS,
CommercialPaper.State::class.java,
@ -292,6 +300,10 @@ class TwoPartyTradeProtocolTests {
// looking up the states again.
RecordingMap.Get(bobsFakeCash[1].id),
RecordingMap.Get(bobsFakeCash[2].id),
RecordingMap.Get(alicesFakePaper[0].id),
// Alice needs to look up the input states to find out which Notary they point to
RecordingMap.Get(bobsFakeCash[1].id),
RecordingMap.Get(bobsFakeCash[2].id),
RecordingMap.Get(alicesFakePaper[0].id)
)
assertEquals(expected, records)
@ -315,23 +327,27 @@ class TwoPartyTradeProtocolTests {
private fun TransactionGroupDSL<ContractState>.runWithError(bobError: Boolean, aliceError: Boolean,
expectedMessageSubstring: String) {
var (aliceNode, bobNode) = net.createTwoNodes()
val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val aliceNode = net.createPartyNode(notaryNode.info, ALICE.name, ALICE_KEY)
val bobNode = net.createPartyNode(notaryNode.info, BOB.name, BOB_KEY)
val aliceAddr = aliceNode.net.myAddress
val bobAddr = bobNode.net.myAddress as InMemoryMessagingNetwork.Handle
val timestamperAddr = aliceNode.info
val bobKey = bobNode.keyManagement.freshKey()
val bobsBadCash = fillUpForBuyer(bobError, bobKey.public).second
val alicesFakePaper = fillUpForSeller(aliceError, timestamperAddr.identity, null).second
val alicesFakePaper = fillUpForSeller(aliceError, aliceNode.storage.myLegalIdentity.owningKey, notaryNode.info.identity, null).second
insertFakeTransactions(bobsBadCash, bobNode.services, bobNode.storage.myLegalIdentityKey, bobKey)
insertFakeTransactions(bobsBadCash, bobNode.services, bobNode.storage.myLegalIdentityKey, bobNode.storage.myLegalIdentityKey)
insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey)
val buyerSessionID = random63BitValue()
net.runNetwork() // Clear network map registration messages
val aliceResult = TwoPartyTradeProtocol.runSeller(
aliceNode.smm,
timestamperAddr,
notaryNode.info,
bobAddr,
lookup("alice's paper"),
1000.DOLLARS,
@ -340,7 +356,7 @@ class TwoPartyTradeProtocolTests {
)
val bobResult = TwoPartyTradeProtocol.runBuyer(
bobNode.smm,
timestamperAddr,
notaryNode.info,
aliceAddr,
1000.DOLLARS,
CommercialPaper.State::class.java,
@ -367,7 +383,7 @@ class TwoPartyTradeProtocolTests {
return signed.associateBy { it.id }
}
private fun TransactionGroupDSL<ContractState>.fillUpForBuyer(withError: Boolean, bobKey: PublicKey = BOB): Pair<Wallet, List<WireTransaction>> {
private fun TransactionGroupDSL<ContractState>.fillUpForBuyer(withError: Boolean, owner: PublicKey = BOB_PUBKEY): Pair<Wallet, List<WireTransaction>> {
// Bob (Buyer) has some cash he got from the Bank of Elbonia, Alice (Seller) has some commercial paper she
// wants to sell to Bob.
@ -383,13 +399,13 @@ class TwoPartyTradeProtocolTests {
// Bob gets some cash onto the ledger from BoE
val bc1 = transaction {
input("elbonian money 1")
output("bob cash 1") { 800.DOLLARS.CASH `issued by` MEGA_CORP `owned by` bobKey }
output("bob cash 1") { 800.DOLLARS.CASH `issued by` MEGA_CORP `owned by` owner }
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
}
val bc2 = transaction {
input("elbonian money 2")
output("bob cash 2") { 300.DOLLARS.CASH `issued by` MEGA_CORP `owned by` bobKey }
output("bob cash 2") { 300.DOLLARS.CASH `issued by` MEGA_CORP `owned by` owner }
output { 700.DOLLARS.CASH `issued by` MEGA_CORP `owned by` MEGA_CORP_PUBKEY } // Change output.
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
}
@ -398,14 +414,14 @@ class TwoPartyTradeProtocolTests {
return Pair(wallet, listOf(eb1, bc1, bc2))
}
private fun TransactionGroupDSL<ContractState>.fillUpForSeller(withError: Boolean, timestamper: Party, attachmentID: SecureHash?): Pair<Wallet, List<WireTransaction>> {
private fun TransactionGroupDSL<ContractState>.fillUpForSeller(withError: Boolean, owner: PublicKey, notary: Party, attachmentID: SecureHash?): Pair<Wallet, List<WireTransaction>> {
val ap = transaction {
output("alice's paper") {
CommercialPaper.State(MEGA_CORP.ref(1, 2, 3), ALICE, 1200.DOLLARS, TEST_TX_TIME + 7.days)
CommercialPaper.State(MEGA_CORP.ref(1, 2, 3), owner, 1200.DOLLARS, TEST_TX_TIME + 7.days, notary)
}
arg(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
if (!withError)
arg(timestamper.owningKey) { TimestampCommand(TEST_TX_TIME, 30.seconds) }
arg(notary.owningKey) { TimestampCommand(TEST_TX_TIME, 30.seconds) }
if (attachmentID != null)
attachment(attachmentID)
}

View File

@ -6,6 +6,7 @@ import core.*
import core.crypto.SecureHash
import core.node.services.AttachmentStorage
import core.serialization.*
import core.testutils.DUMMY_NOTARY
import core.testutils.MEGA_CORP
import org.apache.commons.io.IOUtils
import org.junit.Test
@ -19,7 +20,7 @@ import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
interface DummyContractBackdoor {
fun generateInitial(owner: PartyAndReference, magicNumber: Int): TransactionBuilder
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder
fun inspectState(state: ContractState): Int
}
@ -183,7 +184,7 @@ class AttachmentClassLoaderTests {
@Test
fun `test serialization of WireTransaction with statically loaded contract`() {
val tx = DUMMY_PROGRAM_ID.generateInitial(MEGA_CORP.ref(0), 42)
val tx = DUMMY_PROGRAM_ID.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val wireTransaction = tx.toWireTransaction()
val bytes = wireTransaction.serialize()
val copiedWireTransaction = bytes.deserialize()
@ -197,7 +198,7 @@ class AttachmentClassLoaderTests {
val child = ClassLoaderForTests()
val contractClass = Class.forName("contracts.isolated.AnotherDummyContract", true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42)
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val storage = MockAttachmentStorage()
val kryo = createKryo()
@ -228,7 +229,7 @@ class AttachmentClassLoaderTests {
val child = ClassLoaderForTests()
val contractClass = Class.forName("contracts.isolated.AnotherDummyContract", true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42)
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val storage = MockAttachmentStorage()
val kryo = createKryo()

View File

@ -61,7 +61,7 @@ class NodeInterestRatesTest {
@Test fun `refuse to sign with no relevant commands`() {
val tx = makeTX()
assertFailsWith<IllegalArgumentException> { oracle.sign(tx.toWireTransaction()) }
tx.addCommand(Cash.Commands.Move(), ALICE)
tx.addCommand(Cash.Commands.Move(), ALICE_PUBKEY)
assertFailsWith<IllegalArgumentException> { oracle.sign(tx.toWireTransaction()) }
}
@ -89,7 +89,7 @@ class NodeInterestRatesTest {
fun network() {
val net = MockNetwork()
val (n1, n2) = net.createTwoNodes()
NodeInterestRates.Service(n2).oracle.knownFixes = TEST_DATA
n2.interestRatesService.oracle.knownFixes = TEST_DATA
val tx = TransactionBuilder()
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
@ -106,5 +106,5 @@ class NodeInterestRatesTest {
assertEquals("0.678".bd, fix.value)
}
private fun makeTX() = TransactionBuilder(outputs = mutableListOf(1000.DOLLARS.CASH `owned by` ALICE))
private fun makeTX() = TransactionBuilder(outputs = mutableListOf(1000.DOLLARS.CASH `owned by` ALICE_PUBKEY))
}

View File

@ -0,0 +1,88 @@
package core.node.services
import core.TransactionBuilder
import core.seconds
import core.testing.MockNetwork
import core.testutils.DUMMY_NOTARY
import core.testutils.DUMMY_NOTARY_KEY
import core.testutils.issueState
import org.junit.Before
import org.junit.Test
import protocols.NotaryProtocol
import java.time.Instant
import java.util.concurrent.ExecutionException
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class NotaryServiceTests {
lateinit var net: MockNetwork
lateinit var notaryNode: MockNetwork.MockNode
lateinit var clientNode: MockNetwork.MockNode
@Before
fun setup() {
// TODO: Move into MockNetwork
net = MockNetwork()
notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
clientNode = net.createPartyNode(networkMapAddr = notaryNode.info)
net.runNetwork() // Clear network map registration messages
}
@Test fun `should sign a unique transaction with a valid timestamp`() {
val inputState = issueState(clientNode)
val tx = TransactionBuilder().withItems(inputState)
tx.setTime(Instant.now(), DUMMY_NOTARY, 30.seconds)
var wtx = tx.toWireTransaction()
val protocol = NotaryProtocol(wtx, NotaryProtocol.tracker())
val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol)
net.runNetwork()
val signature = future.get()
signature.verifyWithECDSA(wtx.serialized)
}
@Test fun `should sign a unique transaction without a timestamp`() {
val inputState = issueState(clientNode)
val wtx = TransactionBuilder().withItems(inputState).toWireTransaction()
val protocol = NotaryProtocol(wtx, NotaryProtocol.tracker())
val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol)
net.runNetwork()
val signature = future.get()
signature.verifyWithECDSA(wtx.serialized)
}
@Test fun `should report error for transaction with an invalid timestamp`() {
val inputState = issueState(clientNode)
val tx = TransactionBuilder().withItems(inputState)
tx.setTime(Instant.now().plusSeconds(3600), DUMMY_NOTARY, 30.seconds)
var wtx = tx.toWireTransaction()
val protocol = NotaryProtocol(wtx, NotaryProtocol.tracker())
val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol)
net.runNetwork()
val ex = assertFailsWith(ExecutionException::class) { future.get() }
val error = (ex.cause as NotaryException).error
assertTrue(error is NotaryError.TimestampInvalid)
}
@Test fun `should report conflict for a duplicate transaction`() {
val inputState = issueState(clientNode)
val wtx = TransactionBuilder().withItems(inputState).toWireTransaction()
val firstSpend = NotaryProtocol(wtx)
val secondSpend = NotaryProtocol(wtx)
clientNode.smm.add("${NotaryProtocol.TOPIC}.first", firstSpend)
val future = clientNode.smm.add("${NotaryProtocol.TOPIC}.second", secondSpend)
net.runNetwork()
val ex = assertFailsWith(ExecutionException::class) { future.get() }
val notaryError = (ex.cause as NotaryException).error as NotaryError.Conflict
assertEquals(notaryError.tx, wtx)
notaryError.conflict.verified()
}
}

View File

@ -0,0 +1,33 @@
package core.node.services
import core.TimestampCommand
import core.seconds
import org.junit.Test
import java.time.Clock
import java.time.Instant
import java.time.ZoneId
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class TimestampCheckerTests {
val clock = Clock.fixed(Instant.now(), ZoneId.systemDefault())
val timestampChecker = TimestampChecker(clock, tolerance = 30.seconds)
@Test
fun `should return true for valid timestamp`() {
val now = clock.instant()
val timestampPast = TimestampCommand(now - 60.seconds, now - 29.seconds)
val timestampFuture = TimestampCommand(now + 29.seconds, now + 60.seconds)
assertTrue { timestampChecker.isValid(timestampPast) }
assertTrue { timestampChecker.isValid(timestampFuture) }
}
@Test
fun `should return false for invalid timestamp`() {
val now = clock.instant()
val timestampPast = TimestampCommand(now - 60.seconds, now - 31.seconds)
val timestampFuture = TimestampCommand(now + 31.seconds, now + 60.seconds)
assertFalse { timestampChecker.isValid(timestampPast) }
assertFalse { timestampChecker.isValid(timestampFuture) }
}
}

View File

@ -1,128 +0,0 @@
package core.node.services
import co.paralleluniverse.fibers.Suspendable
import core.*
import core.crypto.SecureHash
import core.node.NodeInfo
import core.protocols.ProtocolLogic
import core.serialization.serialize
import core.testing.MockNetwork
import core.testutils.CASH
import core.utilities.BriefLogFormatter
import org.junit.Before
import org.junit.Test
import protocols.TimestampingProtocol
import java.time.Clock
import java.time.Instant
import java.time.ZoneId
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class TimestamperNodeServiceTest {
lateinit var network: MockNetwork
init {
BriefLogFormatter.initVerbose("dlg.timestamping.request")
}
val ptx = TransactionBuilder().apply {
addInputState(StateRef(SecureHash.randomSHA256(), 0))
addOutputState(100.DOLLARS.CASH)
}
val clock = Clock.fixed(Instant.now(), ZoneId.systemDefault())
@Before
fun setup() {
network = MockNetwork()
}
class TestPSM(val server: NodeInfo, val now: Instant) : ProtocolLogic<Boolean>() {
@Suspendable
override fun call(): Boolean {
val ptx = TransactionBuilder().apply {
addInputState(StateRef(SecureHash.randomSHA256(), 0))
addOutputState(100.DOLLARS.CASH)
}
ptx.addCommand(TimestampCommand(now - 20.seconds, now + 20.seconds), server.identity.owningKey)
val wtx = ptx.toWireTransaction()
// This line will invoke sendAndReceive to interact with the network.
val sig = subProtocol(TimestampingProtocol(server, wtx.serialized))
ptx.checkAndAddSignature(sig)
return true
}
}
@Test
fun successWithNetwork() {
val timestamperNode = network.createNode(null, advertisedServices = TimestamperService.Type)
val logName = TimestampingProtocol.TOPIC
val psm = TestPSM(timestamperNode.info, clock.instant())
val future = timestamperNode.smm.add(logName, psm)
network.runNetwork()
assertTrue(future.isDone)
}
@Test
fun wrongCommands() {
val timestamperNode = network.createNode(null, advertisedServices = TimestamperService.Type)
val timestamperKey = timestamperNode.services.storageService.myLegalIdentity.owningKey
val service = timestamperNode.inNodeTimestampingService!!
// Zero commands is not OK.
assertFailsWith(TimestampingError.RequiresExactlyOneCommand::class) {
val wtx = ptx.toWireTransaction()
service.processRequest(TimestampingProtocol.Request(wtx.serialize(), timestamperNode.info.address, Long.MIN_VALUE))
}
// More than one command is not OK.
assertFailsWith(TimestampingError.RequiresExactlyOneCommand::class) {
ptx.addCommand(TimestampCommand(clock.instant(), 30.seconds), timestamperKey)
ptx.addCommand(TimestampCommand(clock.instant(), 40.seconds), timestamperKey)
val wtx = ptx.toWireTransaction()
service.processRequest(TimestampingProtocol.Request(wtx.serialize(), timestamperNode.info.address, Long.MIN_VALUE))
}
}
@Test
fun tooEarly() {
val timestamperNode = network.createNode(null, advertisedServices = TimestamperService.Type)
val timestamperKey = timestamperNode.services.storageService.myLegalIdentity.owningKey
val service = timestamperNode.inNodeTimestampingService!!
assertFailsWith(TimestampingError.NotOnTimeException::class) {
val now = clock.instant()
ptx.addCommand(TimestampCommand(now - 60.seconds, now - 40.seconds), timestamperKey)
val wtx = ptx.toWireTransaction()
service.processRequest(TimestampingProtocol.Request(wtx.serialize(), timestamperNode.info.address, Long.MIN_VALUE))
}
}
@Test
fun tooLate() {
val timestamperNode = network.createNode(null, advertisedServices = TimestamperService.Type)
val timestamperKey = timestamperNode.services.storageService.myLegalIdentity.owningKey
val service = timestamperNode.inNodeTimestampingService!!
assertFailsWith(TimestampingError.NotOnTimeException::class) {
val now = clock.instant()
ptx.addCommand(TimestampCommand(now - 60.seconds, now - 40.seconds), timestamperKey)
val wtx = ptx.toWireTransaction()
service.processRequest(TimestampingProtocol.Request(wtx.serialize(), timestamperNode.info.address, Long.MIN_VALUE))
}
}
@Test
fun success() {
val timestamperNode = network.createNode(null, advertisedServices = TimestamperService.Type)
val timestamperKey = timestamperNode.services.storageService.myLegalIdentity.owningKey
val service = timestamperNode.inNodeTimestampingService!!
val now = clock.instant()
ptx.addCommand(TimestampCommand(now - 20.seconds, now + 20.seconds), timestamperKey)
val wtx = ptx.toWireTransaction()
val sig = service.processRequest(TimestampingProtocol.Request(wtx.serialize(), timestamperNode.info.address, Long.MIN_VALUE))
ptx.checkAndAddSignature(sig)
ptx.toSignedTransaction(false).verifySignatures()
}
}

View File

@ -0,0 +1,35 @@
package core.node.services
import core.TransactionBuilder
import core.testutils.MEGA_CORP
import core.testutils.generateStateRef
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class UniquenessProviderTests {
val identity = MEGA_CORP
@Test fun `should commit a transaction with unused inputs without exception`() {
val provider = InMemoryUniquenessProvider()
val inputState = generateStateRef()
val tx = TransactionBuilder().withItems(inputState).toWireTransaction()
provider.commit(tx, identity)
}
@Test fun `should report a conflict for a transaction with previously used inputs`() {
val provider = InMemoryUniquenessProvider()
val inputState = generateStateRef()
val tx1 = TransactionBuilder().withItems(inputState).toWireTransaction()
provider.commit(tx1, identity)
val tx2 = TransactionBuilder().withItems(inputState).toWireTransaction()
val ex = assertFailsWith<UniquenessException> { provider.commit(tx2, identity) }
val consumingTx = ex.error.stateHistory[inputState]!!
assertEquals(consumingTx.id, tx1.id)
assertEquals(consumingTx.inputIndex, tx1.inputs.indexOf(inputState))
assertEquals(consumingTx.requestingParty, identity)
}
}

View File

@ -2,7 +2,6 @@ package core.node.subsystems
import core.messaging.Message
import core.messaging.MessageRecipients
import core.node.subsystems.ArtemisMessagingService
import core.testutils.freeLocalHostAndPort
import org.assertj.core.api.Assertions.assertThat
import org.junit.After

View File

@ -2,7 +2,6 @@ package core.node.subsystems
import contracts.Cash
import core.*
import core.node.subsystems.NodeWalletService
import core.node.ServiceHub
import core.testutils.*
import core.utilities.BriefLogFormatter
@ -37,7 +36,7 @@ class NodeWalletServiceTest {
kms.nextKeys += Array(3) { ALICE_KEY }
// Fix the PRNG so that we get the same splits every time.
wallet.fillWithSomeTestCash(100.DOLLARS, 3, 3, Random(0L))
wallet.fillWithSomeTestCash(DUMMY_NOTARY, 100.DOLLARS, 3, 3, Random(0L))
val w = wallet.currentWallet
assertEquals(3, w.states.size)
@ -46,7 +45,7 @@ class NodeWalletServiceTest {
assertEquals(services.storageService.myLegalIdentity, state.deposit.party)
assertEquals(services.storageService.myLegalIdentityKey.public, state.deposit.party.owningKey)
assertEquals(29.01.DOLLARS, state.amount)
assertEquals(ALICE, state.owner)
assertEquals(ALICE_PUBKEY, state.owner)
assertEquals(33.34.DOLLARS, (w.states[2].state as Cash.State).amount)
assertEquals(35.61.DOLLARS, (w.states[1].state as Cash.State).amount)
@ -59,20 +58,20 @@ class NodeWalletServiceTest {
// A tx that sends us money.
val freshKey = services.keyManagementService.freshKey()
val usefulTX = TransactionBuilder().apply {
Cash().generateIssue(this, 100.DOLLARS, MEGA_CORP.ref(1), freshKey.public)
Cash().generateIssue(this, 100.DOLLARS, MEGA_CORP.ref(1), freshKey.public, DUMMY_NOTARY)
signWith(MEGA_CORP_KEY)
}.toSignedTransaction()
val myOutput = usefulTX.verifyToLedgerTransaction(MockIdentityService, MockStorageService().attachments).outRef<Cash.State>(0)
// A tx that spends our money.
val spendTX = TransactionBuilder().apply {
Cash().generateSpend(this, 80.DOLLARS, BOB, listOf(myOutput))
Cash().generateSpend(this, 80.DOLLARS, BOB_PUBKEY, listOf(myOutput))
signWith(freshKey)
}.toSignedTransaction()
// A tx that doesn't send us anything.
val irrelevantTX = TransactionBuilder().apply {
Cash().generateIssue(this, 100.DOLLARS, MEGA_CORP.ref(1), BOB_KEY.public)
Cash().generateIssue(this, 100.DOLLARS, MEGA_CORP.ref(1), BOB_KEY.public, DUMMY_NOTARY)
signWith(MEGA_CORP_KEY)
}.toSignedTransaction()

View File

@ -2,7 +2,6 @@ package core.serialization
import contracts.Cash
import core.*
import core.crypto.SecureHash
import core.testutils.*
import org.junit.Before
import org.junit.Test
@ -14,10 +13,10 @@ class TransactionSerializationTests {
// Simple TX that takes 1000 pounds from me and sends 600 to someone else (with 400 change).
// It refers to a fake TX/state that we don't bother creating here.
val depositRef = MINI_CORP.ref(1)
val outputState = Cash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1)
val changeState = Cash.State(depositRef, 400.POUNDS, TestUtils.keypair.public)
val outputState = Cash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1, DUMMY_NOTARY)
val changeState = Cash.State(depositRef, 400.POUNDS, TestUtils.keypair.public, DUMMY_NOTARY)
val fakeStateRef = StateRef(SecureHash.sha256("fake tx id"), 0)
val fakeStateRef = generateStateRef()
lateinit var tx: TransactionBuilder
@Before
@ -33,27 +32,18 @@ class TransactionSerializationTests {
val signedTX = tx.toSignedTransaction()
// Now check that the signature we just made verifies.
signedTX.verify()
signedTX.verifySignatures()
// Corrupt the data and ensure the signature catches the problem.
signedTX.txBits.bits[5] = 0
assertFailsWith(SignatureException::class) {
signedTX.verify()
}
}
@Test
fun tooManyKeys() {
assertFailsWith(IllegalStateException::class) {
tx.signWith(TestUtils.keypair)
tx.signWith(TestUtils.keypair2)
tx.toSignedTransaction()
signedTX.verifySignatures()
}
}
@Test
fun wrongKeys() {
// Can't convert if we don't have enough signatures.
// Can't convert if we don't have signatures for all commands
assertFailsWith(IllegalStateException::class) {
tx.toSignedTransaction()
}
@ -78,14 +68,14 @@ class TransactionSerializationTests {
@Test
fun timestamp() {
tx.setTime(TEST_TX_TIME, DUMMY_TIMESTAMPER.identity, 30.seconds)
tx.timestamp(DUMMY_TIMESTAMPER)
tx.setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
tx.signWith(TestUtils.keypair)
tx.signWith(DUMMY_NOTARY_KEY)
val stx = tx.toSignedTransaction()
val ltx = stx.verifyToLedgerTransaction(MockIdentityService, MockStorageService().attachments)
assertEquals(tx.commands().map { it.value }, ltx.commands.map { it.value })
assertEquals(tx.inputStates(), ltx.inputs)
assertEquals(tx.outputStates(), ltx.outputs)
assertEquals(TEST_TX_TIME, ltx.commands.getTimestampBy(DUMMY_TIMESTAMPER.identity)!!.midpoint)
assertEquals(TEST_TX_TIME, ltx.commands.getTimestampBy(DUMMY_NOTARY)!!.midpoint)
}
}

View File

@ -7,7 +7,7 @@ import com.google.common.net.HostAndPort
import contracts.*
import core.*
import core.crypto.*
import core.node.services.DummyTimestampingAuthority
import core.node.AbstractNode
import core.serialization.serialize
import core.testing.MockIdentityService
import core.visualiser.GraphVisualiser
@ -57,17 +57,22 @@ val DUMMY_PUBKEY_1 = DummyPublicKey("x1")
val DUMMY_PUBKEY_2 = DummyPublicKey("x2")
val ALICE_KEY = generateKeyPair()
val ALICE = ALICE_KEY.public
val ALICE_PUBKEY = ALICE_KEY.public
val ALICE = Party("Alice", ALICE_PUBKEY)
val BOB_KEY = generateKeyPair()
val BOB = BOB_KEY.public
val BOB_PUBKEY = BOB_KEY.public
val BOB = Party("Bob", BOB_PUBKEY)
val MEGA_CORP = Party("MegaCorp", MEGA_CORP_PUBKEY)
val MINI_CORP = Party("MiniCorp", MINI_CORP_PUBKEY)
val ALL_TEST_KEYS = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY, DummyTimestampingAuthority.key)
val DUMMY_NOTARY_KEY = generateKeyPair()
val DUMMY_NOTARY = Party("Notary Service", DUMMY_NOTARY_KEY.public)
val MockIdentityService = MockIdentityService(listOf(MEGA_CORP, MINI_CORP, DUMMY_TIMESTAMPER.identity))
val ALL_TEST_KEYS = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY, DUMMY_NOTARY_KEY)
val MockIdentityService = MockIdentityService(listOf(MEGA_CORP, MINI_CORP, DUMMY_NOTARY))
// In a real system this would be a persistent map of hash to bytecode and we'd instantiate the object as needed inside
// a sandbox. For unit tests we just have a hard-coded list.
@ -80,6 +85,18 @@ val TEST_PROGRAM_MAP: Map<Contract, Class<out Contract>> = mapOf(
IRS_PROGRAM_ID to InterestRateSwap::class.java
)
fun generateState(notary: Party = DUMMY_NOTARY) = DummyContract.State(Random().nextInt(), notary)
fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0)
fun issueState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateRef {
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), DUMMY_NOTARY)
tx.signWith(node.storage.myLegalIdentityKey)
tx.signWith(DUMMY_NOTARY_KEY)
val stx = tx.toSignedTransaction()
node.services.recordTransactions(listOf(stx))
return StateRef(stx.id, 0)
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Defines a simple DSL for building pseudo-transactions (not the same as the wire protocol) for testing purposes.
@ -107,7 +124,7 @@ infix fun CommercialPaper.State.`owned by`(owner: PublicKey) = this.copy(owner =
infix fun ICommercialPaperState.`owned by`(new_owner: PublicKey) = this.withOwner(new_owner)
// Allows you to write 100.DOLLARS.CASH
val Amount.CASH: Cash.State get() = Cash.State(MINI_CORP.ref(1, 2, 3), this, NullPublicKey)
val Amount.CASH: Cash.State get() = Cash.State(MINI_CORP.ref(1, 2, 3), this, NullPublicKey, DUMMY_NOTARY)
class LabeledOutput(val label: String?, val state: ContractState) {
override fun toString() = state.toString() + (if (label != null) " ($label)" else "")
@ -143,7 +160,7 @@ abstract class AbstractTransactionForTest {
}
fun timestamp(data: TimestampCommand) {
commands.add(Command(data, DUMMY_TIMESTAMPER.identity.owningKey))
commands.add(Command(data, DUMMY_NOTARY.owningKey))
}
// Forbid patterns like: transaction { ... transaction { ... } }