Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ class ReissuanceRequestContract<T>: Contract where T: ContractState {
// state constraints
"There must be positive number of states to re-issue" using (
reissuanceRequest.stateRefsToReissue.isNotEmpty())
"Asset issuance signers must contain issuer" using(
reissuanceRequest.assetIssuanceSigners.contains(reissuanceRequest.issuer))
"Asset destroy signers must contain issuer" using(
reissuanceRequest.assetDestroySigners.contains(reissuanceRequest.issuer))
}
}

Expand All @@ -53,8 +53,8 @@ class ReissuanceRequestContract<T>: Contract where T: ContractState {
val reissuanceRequestInputs = tx.inputsOfType<ReissuanceRequest>()
val reissuanceRequestOutputs = tx.outputsOfType<ReissuanceRequest>()

val reissuanceLockInputs = tx.inputsOfType<ReissuanceLock<T>>()
val reissuanceLockOutputs = tx.outputsOfType<ReissuanceLock<T>>()
val reissuanceLockInputs = tx.inputsOfType<ReissuanceLock>()
val reissuanceLockOutputs = tx.outputsOfType<ReissuanceLock>()

requireThat {
// command constraints
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,55 @@ package com.r3.corda.lib.reissuance.states
import com.r3.corda.lib.reissuance.contracts.ReissuanceLockContract
import net.corda.core.contracts.BelongsToContract
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.SignableData
import net.corda.core.identity.AbstractParty
import net.corda.core.serialization.CordaSerializable
import java.security.PublicKey

@BelongsToContract(ReissuanceLockContract::class)
data class ReissuanceLock<T>(
data class ReissuanceLock(
val issuer: AbstractParty,
val requester: AbstractParty,
val originalStates: List<StateAndRef<T>>,
val extraAssetExitCommandSigners: List<AbstractParty>,
val status: ReissuanceLockStatus = ReissuanceLockStatus.ACTIVE
): ContractState where T: ContractState {
val txHash: SignableData,
val status: ReissuanceLockStatus = ReissuanceLockStatus.ACTIVE,
val timeWindow: TimeWindow,
override val participants: List<AbstractParty> = listOf(issuer, requester)
): ContractState {

@CordaSerializable
enum class ReissuanceLockStatus {
ACTIVE,
INACTIVE
}

override val participants: List<AbstractParty>
get() = listOf(issuer, requester)

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as ReissuanceLock<*>

other as ReissuanceLock
if (txHash != other.txHash) return false
if (issuer != other.issuer) return false
if (requester != other.requester) return false
if (originalStates != other.originalStates) return false
if (extraAssetExitCommandSigners != other.extraAssetExitCommandSigners) return false
if (timeWindow != other.timeWindow) return false
if (status != other.status) return false

if (participants != other.participants) return false
return true
}

override fun hashCode(): Int {
var result = issuer.hashCode()
result = 31 * result + requester.hashCode()
result = 31 * result + originalStates.hashCode()
result = 31 * result + extraAssetExitCommandSigners.hashCode()
result = 31 * result + txHash.hashCode()
result = 31 * result + status.hashCode()
result = 31 * result + timeWindow.hashCode()
result = 31 * result + participants.hashCode()
return result
}

}
fun getCompositeKey() : PublicKey {
return CompositeKey.Builder()
.addKeys(participants.map { it.owningKey })
.build(1)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import net.corda.core.serialization.CordaSerializable
data class ReissuanceRequest(
val issuer: AbstractParty,
val requester: AbstractParty,
val stateRefsToReissue: List<StateRef>,
val assetIssuanceCommand: CommandData,
val assetIssuanceSigners: List<AbstractParty>
val stateRefsToReissue: List<StateAndRef<ContractState>>,
val assetDestroyCommand: CommandData,
val assetDestroySigners: List<AbstractParty>
): ContractState {

override val participants: List<AbstractParty>
Expand All @@ -27,8 +27,8 @@ data class ReissuanceRequest(
if (issuer != other.issuer) return false
if (requester != other.requester) return false
if (stateRefsToReissue != other.stateRefsToReissue) return false
if (assetIssuanceCommand != other.assetIssuanceCommand) return false
if (assetIssuanceSigners != other.assetIssuanceSigners) return false
if (assetDestroyCommand != other.assetDestroyCommand) return false
if (assetDestroySigners != other.assetDestroySigners) return false

return true
}
Expand All @@ -37,8 +37,8 @@ data class ReissuanceRequest(
var result = issuer.hashCode()
result = 31 * result + requester.hashCode()
result = 31 * result + stateRefsToReissue.hashCode()
result = 31 * result + assetIssuanceCommand.hashCode()
result = 31 * result + assetIssuanceSigners.hashCode()
result = 31 * result + assetDestroyCommand.hashCode()
result = 31 * result + assetDestroySigners.hashCode()
return result
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.r3.corda.lib.reissuance.utils

import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import java.io.ByteArrayOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
Expand All @@ -23,3 +24,21 @@ fun convertSignedTransactionToByteArray(

return baos.toByteArray()
}

fun convertWireTransactionToByteArray(
wireTransaction: WireTransaction
): ByteArray {

val serializedWireTransactionBytes = wireTransaction.serialize().bytes

val baos = ByteArrayOutputStream()
ZipOutputStream(baos).use { zos ->
val entry = ZipEntry("WireTransaction${wireTransaction.id}")
zos.putNextEntry(entry)
zos.write(serializedWireTransactionBytes)
zos.closeEntry()
}
baos.close()

return baos.toByteArray()
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package com.r3.corda.lib.reissuance.dummy_states

import com.r3.corda.lib.reissuance.dummy_contracts.SimpleDummyStateContract
import com.r3.corda.lib.reissuance.states.ReissuableState
import net.corda.core.contracts.BelongsToContract
import net.corda.core.contracts.ContractState
import net.corda.core.identity.AbstractParty

@BelongsToContract(SimpleDummyStateContract::class)
data class SimpleDummyState(
val owner: AbstractParty
): ContractState {
): ContractState, ReissuableState<SimpleDummyState> {
override val participants: List<AbstractParty>
get() = listOf(owner)

Expand All @@ -27,4 +28,12 @@ data class SimpleDummyState(
return owner.hashCode()
}

override fun createReissuance(): SimpleDummyState {
return this.copy()
}

override fun isEqualForReissuance(otherState: SimpleDummyState): Boolean {
return this == otherState
}

}
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
package com.r3.corda.lib.reissuance.dummy_contracts

import com.r3.corda.lib.reissuance.contracts.ReissuanceLockContract
import com.r3.corda.lib.reissuance.contracts.ReissuanceRequestContract
import com.r3.corda.lib.reissuance.dummy_states.SimpleDummyState
import com.r3.corda.lib.reissuance.states.ReissuanceLock
import com.r3.corda.lib.reissuance.states.ReissuanceRequest
import com.r3.corda.lib.reissuance.dummy_states.SimpleDummyState
import com.r3.corda.lib.reissuance.utils.convertSignedTransactionToByteArray
import com.r3.corda.lib.reissuance.utils.convertWireTransactionToByteArray
import com.r3.corda.lib.tokens.contracts.commands.IssueTokenCommand
import com.r3.corda.lib.tokens.contracts.states.FungibleToken
import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType
import com.r3.corda.lib.tokens.contracts.types.TokenType
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.*
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.singleIdentity
Expand All @@ -22,9 +26,12 @@ import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNodeParameters
import net.corda.testing.node.internal.TestStartedNode
import net.corda.testing.node.internal.findCordapp
import net.corda.testing.node.ledger
import org.junit.After
import org.junit.Before
import java.io.ByteArrayInputStream
import java.security.PublicKey
import java.time.Instant

abstract class AbstractContractTest {

Expand Down Expand Up @@ -98,7 +105,7 @@ abstract class AbstractContractTest {
return ReissuanceRequest(
issuerParty,
aliceParty,
stateRefList,
stateRefList.map { createSimpleDummyStateAndRef(it) },
SimpleDummyStateContract.Commands.Create(),
listOf(issuerParty)
)
Expand All @@ -110,7 +117,7 @@ abstract class AbstractContractTest {
return ReissuanceRequest(
issuerParty,
aliceParty,
stateRefList,
stateRefList.map { createTokenStateAndRef(it) },
IssueTokenCommand(issuedTokenType, stateRefList.indices.toList()),
listOf(issuerParty)
)
Expand All @@ -130,17 +137,101 @@ abstract class AbstractContractTest {
return StateAndRef(dummyTransactionState, stateRef)
}

fun <T> createDummyReissuanceLock(
stateAndRefList: List<StateAndRef<T>>,
fun createDummyReissuanceLock2(
signableData: SignableData,
requiredSigners: List<AbstractParty>,
status: ReissuanceLock.ReissuanceLockStatus = ReissuanceLock.ReissuanceLockStatus.ACTIVE
): ReissuanceLock<T> where T: ContractState {
return ReissuanceLock(issuerParty, aliceParty, stateAndRefList, requiredSigners, status)
status: ReissuanceLock.ReissuanceLockStatus = ReissuanceLock.ReissuanceLockStatus.ACTIVE,
timeWindow: TimeWindow
): ReissuanceLock {
return ReissuanceLock(issuerParty, aliceParty, signableData, status, timeWindow, requiredSigners)
}

fun generateWireTransactionByteArrayInputStream(
wireTransaction: WireTransaction
): ByteArrayInputStream {
return convertWireTransactionToByteArray(wireTransaction).inputStream()
}

fun createSignableData(txId: SecureHash = SecureHash.randomSHA256(), key: PublicKey): SignableData {
return SignableData(txId, SignatureMetadata(notaryNode.services.myInfo.platformVersion, Crypto.findSignatureScheme
(key).schemeNumberID))
}

fun generateSignedTransactionByteArrayInputStream(
signedTransaction: SignedTransaction
wireTransaction: WireTransaction,
signersNodes: List<TestStartedNode>
): ByteArrayInputStream {
return convertSignedTransactionToByteArray(signedTransaction).inputStream()
val sigs = signersNodes.map {
generateTransactionSignature(it, wireTransaction.id)
}
return convertSignedTransactionToByteArray(SignedTransaction(wireTransaction, sigs)).inputStream()
}

private fun generateTransactionSignature(node: TestStartedNode, txId: SecureHash): TransactionSignature {
val signatureMetadata = SignatureMetadata(node.services.myInfo.platformVersion,
Crypto.findSignatureScheme(node.services.myInfo.legalIdentities.first().owningKey).schemeNumberID)
val signableData = SignableData(txId, signatureMetadata)
return node.services.keyManagementService.sign(signableData, node.info.singleIdentity().owningKey)
}

fun prepareReissuanceLockState(
inputContractId: String,
inputs: List<StateAndRef<ContractState>>,
reissuanceLockStatus: ReissuanceLock.ReissuanceLockStatus = ReissuanceLock.ReissuanceLockStatus.ACTIVE,
nodesToSign: List<TestStartedNode> = listOf(aliceNode),
isSigned: Boolean = false
): Pair<ReissuanceLock,
SecureHash> {

var tx: WireTransaction? = null
aliceNode.services.ledger(notary = notaryParty) {
tx = unverifiedTransaction {
inputs.forEach {
input(inputContractId, it.state.data)
}
}
}

val dummyReissuanceRequest = if (inputContractId == SimpleDummyStateContract.contractId)
createSimpleDummyStateReissuanceRequest(tx!!.inputs) else createTokensReissuanceRequest(tx!!.inputs)
val uploadedSignedTransactionSecureHash = issuerNode.services.attachments.importAttachment(
if (isSigned) {
generateSignedTransactionByteArrayInputStream(tx!!, nodesToSign)
} else {
generateWireTransactionByteArrayInputStream(tx!!)
}, aliceParty.toString(), null)

val reissuanceLock = createDummyReissuanceLock2(
signableData = createSignableData(tx!!.id, issuerParty.owningKey),
requiredSigners = listOf(issuerParty, aliceParty),
timeWindow = TimeWindow.untilOnly(Instant.now().plusSeconds(5)),
status = reissuanceLockStatus
)

issuerNode.services.ledger(notary = notaryParty) {
unverifiedTransaction {
output(ReissuanceRequestContract.contractId, dummyReissuanceRequest)
}

unverifiedTransaction {
input(ReissuanceRequestContract.contractId, dummyReissuanceRequest)
inputs.forEachIndexed { index, stateAndRef ->
output(inputContractId, reissuedStateLabel(index), contractState = stateAndRef.state.data,
encumbrance = index + 1)
}
output(
ReissuanceLockContract.contractId, reissuanceLockLabel,
contractState = reissuanceLock,
encumbrance = 0)
attachment(uploadedSignedTransactionSecureHash)
command(listOf(issuerParty.owningKey), ReissuanceRequestContract.Commands.Accept())
command(listOf(issuerParty.owningKey, aliceParty.owningKey), ReissuanceLockContract.Commands.Create())
if (inputContractId == SimpleDummyStateContract.contractId)
command(listOf(issuerParty.owningKey), SimpleDummyStateContract.Commands.Create())
else command(listOf(issuerParty.owningKey), IssueTokenCommand(issuedTokenType, inputs.indices.toList()))
timeWindow(TimeWindow.untilOnly(Instant.now().plusSeconds(5)))
}
}
return Pair(reissuanceLock, uploadedSignedTransactionSecureHash)
}
}
Loading