Allow for easier conversion from Amount<Issued<T>> to Amount<T> and de-issuerify a few places.

This commit is contained in:
Mike Hearn 2016-07-28 11:51:19 +02:00
parent 309159da14
commit b40fee1512
9 changed files with 41 additions and 43 deletions

View File

@ -344,7 +344,7 @@ public class JavaCommercialPaper extends ClauseVerifier {
} }
public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, List<StateAndRef<Cash.State>> wallet) throws InsufficientBalanceException { public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, List<StateAndRef<Cash.State>> wallet) throws InsufficientBalanceException {
new Cash().generateSpend(tx, paper.getState().getData().getFaceValue(), paper.getState().getData().getOwner(), wallet); new Cash().generateSpend(tx, StructuresKt.withoutIssuer(paper.getState().getData().getFaceValue()), paper.getState().getData().getOwner(), wallet, null);
tx.addInputState(paper); tx.addInputState(paper);
tx.addCommand(new Command(new Commands.Redeem(paper.getState().getNotary()), paper.getState().getData().getOwner())); tx.addCommand(new Command(new Commands.Redeem(paper.getState().getNotary()), paper.getState().getData().getOwner()));
} }

View File

@ -211,16 +211,6 @@ class Cash : ClauseVerifier() {
tx.addCommand(Cash.Commands.Issue(), at.party.owningKey) tx.addCommand(Cash.Commands.Issue(), at.party.owningKey)
} }
/**
* Generate a transaction that consumes one or more of the given input states to move money to the given pubkey.
* Note that the wallet list is not updated: it's up to you to do that.
*/
@Throws(InsufficientBalanceException::class)
fun generateSpend(tx: TransactionBuilder, amount: Amount<Issued<Currency>>, to: PublicKey,
cashStates: List<StateAndRef<State>>): List<PublicKey> =
generateSpend(tx, Amount(amount.quantity, amount.token.product), to, cashStates,
setOf(amount.token.issuer.party))
/** /**
* Generate a transaction that consumes one or more of the given input states to move money to the given pubkey. * Generate a transaction that consumes one or more of the given input states to move money to the given pubkey.
* Note that the wallet list is not updated: it's up to you to do that. * Note that the wallet list is not updated: it's up to you to do that.
@ -301,21 +291,23 @@ class Cash : ClauseVerifier() {
/** /**
* Sums the cash states in the list belonging to a single owner, throwing an exception * Sums the cash states in the list belonging to a single owner, throwing an exception
* if there are none, or if any of the cash states cannot be added together (i.e. are * if there are none, or if any of the cash states cannot be added together (i.e. are
* different currencies). * different currencies or issuers).
*/ */
fun Iterable<ContractState>.sumCashBy(owner: PublicKey) = filterIsInstance<Cash.State>().filter { it.owner == owner }.map { it.amount }.sumOrThrow() fun Iterable<ContractState>.sumCashBy(owner: PublicKey): Amount<Issued<Currency>> = filterIsInstance<Cash.State>().filter { it.owner == owner }.map { it.amount }.sumOrThrow()
/** /**
* Sums the cash states in the list, throwing an exception if there are none, or if any of the cash * Sums the cash states in the list, throwing an exception if there are none, or if any of the cash
* states cannot be added together (i.e. are different currencies). * states cannot be added together (i.e. are different currencies).
*/ */
fun Iterable<ContractState>.sumCash() = filterIsInstance<Cash.State>().map { it.amount }.sumOrThrow() fun Iterable<ContractState>.sumCash(): Amount<Issued<Currency>> = filterIsInstance<Cash.State>().map { it.amount }.sumOrThrow()
/** Sums the cash states in the list, returning null if there are none. */ /** Sums the cash states in the list, returning null if there are none. */
fun Iterable<ContractState>.sumCashOrNull() = filterIsInstance<Cash.State>().map { it.amount }.sumOrNull() fun Iterable<ContractState>.sumCashOrNull(): Amount<Issued<Currency>>? = filterIsInstance<Cash.State>().map { it.amount }.sumOrNull()
/** Sums the cash states in the list, returning zero of the given currency if there are none. */ /** Sums the cash states in the list, returning zero of the given currency+issuer if there are none. */
fun Iterable<ContractState>.sumCashOrZero(currency: Issued<Currency>) = filterIsInstance<Cash.State>().map { it.amount }.sumOrZero<Issued<Currency>>(currency) fun Iterable<ContractState>.sumCashOrZero(currency: Issued<Currency>): Amount<Issued<Currency>> {
return filterIsInstance<Cash.State>().map { it.amount }.sumOrZero<Issued<Currency>>(currency)
}
/** /**
* Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for * Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for

View File

@ -4,7 +4,9 @@ import com.r3corda.core.contracts.*
import java.security.PublicKey import java.security.PublicKey
import java.util.* import java.util.*
class InsufficientBalanceException(val amountMissing: Amount<*>) : Exception() class InsufficientBalanceException(val amountMissing: Amount<*>) : Exception() {
override fun toString() = "Insufficient balance, missing $amountMissing"
}
/** /**
* Interface for contract states representing assets which are fungible, countable and issued by a * Interface for contract states representing assets which are fungible, countable and issued by a

View File

@ -47,7 +47,7 @@ object TwoPartyTradeProtocol {
val TOPIC = "platform.trade" val TOPIC = "platform.trade"
class UnacceptablePriceException(val givenPrice: Amount<Issued<Currency>>) : Exception() class UnacceptablePriceException(val givenPrice: Amount<Currency>) : Exception()
class AssetMismatchException(val expectedTypeName: String, val typeName: String) : Exception() { class AssetMismatchException(val expectedTypeName: String, val typeName: String) : Exception() {
override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName" override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName"
} }
@ -55,7 +55,7 @@ object TwoPartyTradeProtocol {
// This object is serialised to the network and is the first protocol message the seller sends to the buyer. // This object is serialised to the network and is the first protocol message the seller sends to the buyer.
class SellerTradeInfo( class SellerTradeInfo(
val assetForSale: StateAndRef<OwnableState>, val assetForSale: StateAndRef<OwnableState>,
val price: Amount<Issued<Currency>>, val price: Amount<Currency>,
val sellerOwnerKey: PublicKey, val sellerOwnerKey: PublicKey,
val sessionID: Long val sessionID: Long
) )
@ -66,7 +66,7 @@ object TwoPartyTradeProtocol {
open class Seller(val otherSide: Party, open class Seller(val otherSide: Party,
val notaryNode: NodeInfo, val notaryNode: NodeInfo,
val assetToSell: StateAndRef<OwnableState>, val assetToSell: StateAndRef<OwnableState>,
val price: Amount<Issued<Currency>>, val price: Amount<Currency>,
val myKeyPair: KeyPair, val myKeyPair: KeyPair,
val buyerSessionID: Long, val buyerSessionID: Long,
override val progressTracker: ProgressTracker = Seller.tracker()) : ProtocolLogic<SignedTransaction>() { override val progressTracker: ProgressTracker = Seller.tracker()) : ProtocolLogic<SignedTransaction>() {
@ -133,7 +133,7 @@ object TwoPartyTradeProtocol {
// This verifies that the transaction is contract-valid, even though it is missing signatures. // This verifies that the transaction is contract-valid, even though it is missing signatures.
serviceHub.verifyTransaction(wtx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments)) serviceHub.verifyTransaction(wtx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments))
if (wtx.outputs.map { it.data }.sumCashBy(myKeyPair.public) != price) if (wtx.outputs.map { it.data }.sumCashBy(myKeyPair.public).withoutIssuer() != price)
throw IllegalArgumentException("Transaction is not sending us the right amount of cash") throw IllegalArgumentException("Transaction is not sending us the right amount of cash")
// There are all sorts of funny games a malicious secondary might play here, we should fix them: // There are all sorts of funny games a malicious secondary might play here, we should fix them:
@ -178,7 +178,7 @@ object TwoPartyTradeProtocol {
open class Buyer(val otherSide: Party, open class Buyer(val otherSide: Party,
val notary: Party, val notary: Party,
val acceptablePrice: Amount<Issued<Currency>>, val acceptablePrice: Amount<Currency>,
val typeToBuy: Class<out OwnableState>, val typeToBuy: Class<out OwnableState>,
val sessionID: Long) : ProtocolLogic<SignedTransaction>() { val sessionID: Long) : ProtocolLogic<SignedTransaction>() {

View File

@ -150,6 +150,13 @@ data class Issued<out P>(val issuer: PartyAndReference, val product: P) {
override fun toString() = "$product issued by $issuer" override fun toString() = "$product issued by $issuer"
} }
/**
* Strips the issuer and returns an [Amount] of the raw token directly. This is useful when you are mixing code that
* cares about specific issuers with code that will accept any, or which is imposing issuer constraints via some
* other mechanism and the additional type safety is not wanted.
*/
fun <T> Amount<Issued<T>>.withoutIssuer(): Amount<T> = Amount(quantity, token.product)
/** /**
* A contract state that can have a single owner. * A contract state that can have a single owner.
*/ */

View File

@ -8,8 +8,6 @@ import com.r3corda.contracts.testing.fillWithSomeTestCash
import com.r3corda.core.contracts.DOLLARS import com.r3corda.core.contracts.DOLLARS
import com.r3corda.core.contracts.SignedTransaction import com.r3corda.core.contracts.SignedTransaction
import com.r3corda.core.contracts.`issued by` import com.r3corda.core.contracts.`issued by`
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.generateKeyPair
import com.r3corda.core.days import com.r3corda.core.days
import com.r3corda.core.random63BitValue import com.r3corda.core.random63BitValue
import com.r3corda.core.seconds import com.r3corda.core.seconds
@ -43,8 +41,7 @@ class TradeSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwo
} }
seller.services.recordTransactions(issuance) seller.services.recordTransactions(issuance)
val cashIssuerKey = generateKeyPair() val amount = 1000.DOLLARS
val amount = 1000.DOLLARS `issued by` Party("Big friendly bank", cashIssuerKey.public).ref(1)
val sessionID = random63BitValue() val sessionID = random63BitValue()
val buyerProtocol = TwoPartyTradeProtocol.Buyer( val buyerProtocol = TwoPartyTradeProtocol.Buyer(
seller.info.identity, seller.info.identity,

View File

@ -54,14 +54,14 @@ class TwoPartyTradeProtocolTests {
lateinit var net: MockNetwork lateinit var net: MockNetwork
private fun runSeller(smm: StateMachineManager, notary: NodeInfo, private fun runSeller(smm: StateMachineManager, notary: NodeInfo,
otherSide: Party, assetToSell: StateAndRef<OwnableState>, price: Amount<Issued<Currency>>, otherSide: Party, assetToSell: StateAndRef<OwnableState>, price: Amount<Currency>,
myKeyPair: KeyPair, buyerSessionID: Long): ListenableFuture<SignedTransaction> { myKeyPair: KeyPair, buyerSessionID: Long): ListenableFuture<SignedTransaction> {
val seller = TwoPartyTradeProtocol.Seller(otherSide, notary, assetToSell, price, myKeyPair, buyerSessionID) val seller = TwoPartyTradeProtocol.Seller(otherSide, notary, assetToSell, price, myKeyPair, buyerSessionID)
return smm.add("${TwoPartyTradeProtocol.TOPIC}.seller", seller) return smm.add("${TwoPartyTradeProtocol.TOPIC}.seller", seller)
} }
private fun runBuyer(smm: StateMachineManager, notaryNode: NodeInfo, private fun runBuyer(smm: StateMachineManager, notaryNode: NodeInfo,
otherSide: Party, acceptablePrice: Amount<Issued<Currency>>, typeToBuy: Class<out OwnableState>, otherSide: Party, acceptablePrice: Amount<Currency>, typeToBuy: Class<out OwnableState>,
sessionID: Long): ListenableFuture<SignedTransaction> { sessionID: Long): ListenableFuture<SignedTransaction> {
val buyer = TwoPartyTradeProtocol.Buyer(otherSide, notaryNode.identity, acceptablePrice, typeToBuy, sessionID) val buyer = TwoPartyTradeProtocol.Buyer(otherSide, notaryNode.identity, acceptablePrice, typeToBuy, sessionID)
return smm.add("${TwoPartyTradeProtocol.TOPIC}.buyer", buyer) return smm.add("${TwoPartyTradeProtocol.TOPIC}.buyer", buyer)
@ -106,7 +106,7 @@ class TwoPartyTradeProtocolTests {
bobNode.smm, bobNode.smm,
notaryNode.info, notaryNode.info,
aliceNode.info.identity, aliceNode.info.identity,
1000.DOLLARS `issued by` issuer, 1000.DOLLARS,
CommercialPaper.State::class.java, CommercialPaper.State::class.java,
buyerSessionID buyerSessionID
) )
@ -115,7 +115,7 @@ class TwoPartyTradeProtocolTests {
notaryNode.info, notaryNode.info,
bobNode.info.identity, bobNode.info.identity,
"alice's paper".outputStateAndRef(), "alice's paper".outputStateAndRef(),
1000.DOLLARS `issued by` issuer, 1000.DOLLARS,
ALICE_KEY, ALICE_KEY,
buyerSessionID buyerSessionID
) )
@ -158,7 +158,7 @@ class TwoPartyTradeProtocolTests {
notaryNode.info, notaryNode.info,
bobNode.info.identity, bobNode.info.identity,
"alice's paper".outputStateAndRef(), "alice's paper".outputStateAndRef(),
1000.DOLLARS `issued by` issuer, 1000.DOLLARS,
ALICE_KEY, ALICE_KEY,
buyerSessionID buyerSessionID
) )
@ -166,7 +166,7 @@ class TwoPartyTradeProtocolTests {
bobNode.smm, bobNode.smm,
notaryNode.info, notaryNode.info,
aliceNode.info.identity, aliceNode.info.identity,
1000.DOLLARS `issued by` issuer, 1000.DOLLARS,
CommercialPaper.State::class.java, CommercialPaper.State::class.java,
buyerSessionID buyerSessionID
) )
@ -279,7 +279,7 @@ class TwoPartyTradeProtocolTests {
notaryNode.info, notaryNode.info,
bobNode.info.identity, bobNode.info.identity,
"alice's paper".outputStateAndRef(), "alice's paper".outputStateAndRef(),
1000.DOLLARS `issued by` issuer, 1000.DOLLARS,
ALICE_KEY, ALICE_KEY,
buyerSessionID buyerSessionID
) )
@ -287,7 +287,7 @@ class TwoPartyTradeProtocolTests {
bobNode.smm, bobNode.smm,
notaryNode.info, notaryNode.info,
aliceNode.info.identity, aliceNode.info.identity,
1000.DOLLARS `issued by` issuer, 1000.DOLLARS,
CommercialPaper.State::class.java, CommercialPaper.State::class.java,
buyerSessionID buyerSessionID
) )
@ -390,7 +390,7 @@ class TwoPartyTradeProtocolTests {
notaryNode.info, notaryNode.info,
bobNode.info.identity, bobNode.info.identity,
"alice's paper".outputStateAndRef(), "alice's paper".outputStateAndRef(),
1000.DOLLARS `issued by` issuer, 1000.DOLLARS,
ALICE_KEY, ALICE_KEY,
buyerSessionID buyerSessionID
) )
@ -398,7 +398,7 @@ class TwoPartyTradeProtocolTests {
bobNode.smm, bobNode.smm,
notaryNode.info, notaryNode.info,
aliceNode.info.identity, aliceNode.info.identity,
1000.DOLLARS `issued by` issuer, 1000.DOLLARS,
CommercialPaper.State::class.java, CommercialPaper.State::class.java,
buyerSessionID buyerSessionID
) )

View File

@ -76,7 +76,7 @@ class WalletWithCashTest {
// A tx that spends our money. // A tx that spends our money.
val spendTX = TransactionType.General.Builder().apply { val spendTX = TransactionType.General.Builder().apply {
Cash().generateSpend(this, 80.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_PUBKEY, listOf(myOutput)) Cash().generateSpend(this, 80.DOLLARS, BOB_PUBKEY, listOf(myOutput))
signWith(freshKey) signWith(freshKey)
signWith(DUMMY_NOTARY_KEY) signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction() }.toSignedTransaction()

View File

@ -164,7 +164,7 @@ fun runTraderDemo(args: Array<String>): Int {
// What happens next depends on the role. The buyer sits around waiting for a trade to start. The seller role // What happens next depends on the role. The buyer sits around waiting for a trade to start. The seller role
// will contact the buyer and actually make something happen. // will contact the buyer and actually make something happen.
val amount = 1000.DOLLARS `issued by` cashIssuer.ref(0) // Note: "0" has to match the reference used in the wallet filler val amount = 1000.DOLLARS
if (role == Role.BUYER) { if (role == Role.BUYER) {
runBuyer(node, amount) runBuyer(node, amount)
} else { } else {
@ -174,7 +174,7 @@ fun runTraderDemo(args: Array<String>): Int {
return 0 return 0
} }
private fun runSeller(node: Node, amount: Amount<Issued<Currency>>, otherSide: Party) { private fun runSeller(node: Node, amount: Amount<Currency>, otherSide: Party) {
// The seller will sell some commercial paper to the buyer, who will pay with (self issued) cash. // The seller will sell some commercial paper to the buyer, who will pay with (self issued) cash.
// //
// The CP sale transaction comes with a prospectus PDF, which will tag along for the ride in an // The CP sale transaction comes with a prospectus PDF, which will tag along for the ride in an
@ -202,7 +202,7 @@ private fun runSeller(node: Node, amount: Amount<Issued<Currency>>, otherSide: P
node.stop() node.stop()
} }
private fun runBuyer(node: Node, amount: Amount<Issued<Currency>>) { private fun runBuyer(node: Node, amount: Amount<Currency>) {
// Buyer will fetch the attachment from the seller automatically when it resolves the transaction. // Buyer will fetch the attachment from the seller automatically when it resolves the transaction.
// For demo purposes just extract attachment jars when saved to disk, so the user can explore them. // For demo purposes just extract attachment jars when saved to disk, so the user can explore them.
val attachmentsPath = (node.storage.attachments as NodeAttachmentService).let { val attachmentsPath = (node.storage.attachments as NodeAttachmentService).let {
@ -236,7 +236,7 @@ val DEMO_TOPIC = "initiate.demo.trade"
private class TraderDemoProtocolBuyer(val otherSide: Party, private class TraderDemoProtocolBuyer(val otherSide: Party,
private val attachmentsPath: Path, private val attachmentsPath: Path,
val amount: Amount<Issued<Currency>>, val amount: Amount<Currency>,
override val progressTracker: ProgressTracker = ProgressTracker(STARTING_BUY)) : ProtocolLogic<Unit>() { override val progressTracker: ProgressTracker = ProgressTracker(STARTING_BUY)) : ProtocolLogic<Unit>() {
object STARTING_BUY : ProgressTracker.Step("Seller connected, purchasing commercial paper asset") object STARTING_BUY : ProgressTracker.Step("Seller connected, purchasing commercial paper asset")
@ -294,7 +294,7 @@ ${Emoji.renderIfSupported(cpIssuance)}""")
} }
private class TraderDemoProtocolSeller(val otherSide: Party, private class TraderDemoProtocolSeller(val otherSide: Party,
val amount: Amount<Issued<Currency>>, val amount: Amount<Currency>,
override val progressTracker: ProgressTracker = TraderDemoProtocolSeller.tracker()) : ProtocolLogic<SignedTransaction>() { override val progressTracker: ProgressTracker = TraderDemoProtocolSeller.tracker()) : ProtocolLogic<SignedTransaction>() {
companion object { companion object {
val PROSPECTUS_HASH = SecureHash.parse("decd098666b9657314870e192ced0c3519c2c9d395507a238338f8d003929de9") val PROSPECTUS_HASH = SecureHash.parse("decd098666b9657314870e192ced0c3519c2c9d395507a238338f8d003929de9")