Replaced programRef in ContractState with a simple reference to contract

This commit is contained in:
sofusmortensen 2016-03-23 17:51:14 +00:00
parent e6ce1e5b76
commit 1344bfd6bb
22 changed files with 100 additions and 93 deletions

View File

@ -8,6 +8,7 @@
package contracts.isolated
import core.*
import core.Contract
import core.ContractState
import core.TransactionForVerification
@ -15,17 +16,20 @@ import core.crypto.SecureHash
// The dummy contract doesn't do anything useful. It exists for testing purposes.
val ANOTHER_DUMMY_PROGRAM_ID = SecureHash.sha256("dummy")
val ANOTHER_DUMMY_PROGRAM_ID = AnotherDummyContract()
class AnotherDummyContract : Contract {
class State : ContractState {
override val programRef: SecureHash = ANOTHER_DUMMY_PROGRAM_ID
data class State(val foo: Int) : ContractState {
override val contract = ANOTHER_DUMMY_PROGRAM_ID
}
override fun verify(tx: TransactionForVerification) {
requireThat {
"justice will be served" by false
}
// Always accepts.
}
// The "empty contract"
override val legalContractReference: SecureHash = SecureHash.sha256("https://anotherdummy.org")
override val legalContractReference = SecureHash.sha256("https://anotherdummy.org")
}

View File

@ -32,7 +32,8 @@ import static kotlin.collections.CollectionsKt.single;
*
*/
public class JavaCommercialPaper implements Contract {
public static SecureHash JCP_PROGRAM_ID = SecureHash.sha256("java commercial paper (this should be a bytecode hash)");
//public static SecureHash JCP_PROGRAM_ID = SecureHash.sha256("java commercial paper (this should be a bytecode hash)");
public static Contract JCP_PROGRAM_ID = new JavaCommercialPaper();
public static class State implements ContractState, ICommercialPaperState {
private PartyReference issuance;
@ -87,8 +88,9 @@ public class JavaCommercialPaper implements Contract {
@NotNull
@Override
public SecureHash getProgramRef() {
return SecureHash.Companion.sha256("java commercial paper (this should be a bytecode hash)");
public Contract getContract() {
return JCP_PROGRAM_ID;
//return SecureHash.Companion.sha256("java commercial paper (this should be a bytecode hash)");
}
@Override

View File

@ -22,7 +22,8 @@ import java.util.*
//
// Just a fake program identifier for now. In a real system it could be, for instance, the hash of the program bytecode.
val CASH_PROGRAM_ID = SecureHash.sha256("cash")
val CASH_PROGRAM_ID = Cash()
//SecureHash.sha256("cash")
class InsufficientBalanceException(val amountMissing: Amount) : Exception()
@ -62,7 +63,7 @@ class Cash : Contract {
/** There must be a MoveCommand signed by this key to claim the amount */
override val owner: PublicKey
) : OwnableState {
override val programRef = CASH_PROGRAM_ID
override val contract = CASH_PROGRAM_ID
override fun toString() = "${Emoji.bagOfCash}Cash($amount at $deposit owned by ${owner.toStringShort()})"
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))

View File

@ -38,7 +38,7 @@ import java.time.Instant
* to do this in the Apache BVal project).
*/
val CP_PROGRAM_ID = SecureHash.sha256("replace-me-later-with-bytecode-hash")
val CP_PROGRAM_ID = CommercialPaper()
// TODO: Generalise the notion of an owned instrument into a superclass/supercontract. Consider composition vs inheritance.
class CommercialPaper : Contract {
@ -51,7 +51,7 @@ class CommercialPaper : Contract {
val faceValue: Amount,
val maturityDate: Instant
) : OwnableState, ICommercialPaperState {
override val programRef = CP_PROGRAM_ID
override val contract = CP_PROGRAM_ID
fun withoutOwner() = copy(owner = NullPublicKey)
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))

View File

@ -15,7 +15,7 @@ import java.security.PublicKey
import java.time.Instant
import java.util.*
val CROWDFUND_PROGRAM_ID = SecureHash.sha256("crowdsourcing")
val CROWDFUND_PROGRAM_ID = CrowdFund()
/**
* This is a basic crowd funding contract. It allows a party to create a funding opportunity, then for others to
@ -57,7 +57,7 @@ class CrowdFund : Contract {
val closed: Boolean = false,
val pledges: List<Pledge> = ArrayList()
) : ContractState {
override val programRef = CROWDFUND_PROGRAM_ID
override val contract = CROWDFUND_PROGRAM_ID
val pledgedAmount: Amount get() = pledges.map { it.amount }.sumOrZero(campaign.target.currency)
}

View File

@ -15,11 +15,11 @@ import core.crypto.SecureHash
// The dummy contract doesn't do anything useful. It exists for testing purposes.
val DUMMY_PROGRAM_ID = SecureHash.sha256("dummy")
val DUMMY_PROGRAM_ID = DummyContract()
class DummyContract : Contract {
class State : ContractState {
override val programRef: SecureHash = DUMMY_PROGRAM_ID
override val contract = DUMMY_PROGRAM_ID
}
override fun verify(tx: TransactionForVerification) {

View File

@ -18,7 +18,7 @@ import java.math.RoundingMode
import java.time.LocalDate
import java.util.*
val IRS_PROGRAM_ID = SecureHash.sha256("replace-me-later-with-bytecode-hash-of-irs-code")
val IRS_PROGRAM_ID = InterestRateSwap()
// This is a placeholder for some types that we haven't identified exactly what they are just yet for things still in discussion
open class UnknownType()
@ -349,7 +349,7 @@ class InterestRateSwap() : Contract {
val calculation: Calculation,
val common: Common
) : ContractState {
override val programRef = IRS_PROGRAM_ID
override val contract = IRS_PROGRAM_ID
/**
* For evaluating arbitrary java on the platform

View File

@ -32,11 +32,9 @@ interface NamedByHash {
*/
interface ContractState {
/**
* Refers to a bytecode program that has previously been published to the network. This contract program
* will be executed any time this state is used in an input. It must accept in order for the
* transaction to proceed.
* Contract by which the state belongs
*/
val programRef: SecureHash
val contract: Contract
}
interface OwnableState : ContractState {
@ -144,19 +142,6 @@ interface Contract {
val legalContractReference: SecureHash
}
/** A contract factory knows how to lazily load and instantiate contract objects. */
interface ContractFactory {
/**
* Loads, instantiates and returns a contract object from its class bytecodes, given the hash of that bytecode.
*
* @throws UnknownContractException if the hash doesn't map to any known contract.
* @throws ClassCastException if the hash mapped to a contract, but it was not of type T
*/
operator fun <T : Contract> get(hash: SecureHash): T
}
class UnknownContractException : Exception()
/**
* An attachment is a ZIP (or an optionally signed JAR) that contains one or more files. Attachments are meant to
* contain public static data which can be referenced from transactions and utilised from contracts. Good examples

View File

@ -28,7 +28,7 @@ class TransactionGroup(val transactions: Set<LedgerTransaction>, val nonVerified
/**
* Verifies the group and returns the set of resolved transactions.
*/
fun verify(programMap: ContractFactory): Set<TransactionForVerification> {
fun verify(): Set<TransactionForVerification> {
// Check that every input can be resolved to an output.
// Check that no output is referenced by more than one input.
// Cycles should be impossible due to the use of hashes as pointers.
@ -55,7 +55,7 @@ class TransactionGroup(val transactions: Set<LedgerTransaction>, val nonVerified
}
for (tx in resolved)
tx.verify(programMap)
tx.verify()
return resolved
}
@ -79,13 +79,11 @@ data class TransactionForVerification(val inStates: List<ContractState>,
* @throws IllegalStateException if a state refers to an unknown contract.
*/
@Throws(TransactionVerificationException::class, IllegalStateException::class)
fun verify(programMap: ContractFactory) {
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 programHashes = (inStates.map { it.programRef } + outStates.map { it.programRef }).toSet()
for (hash in programHashes) {
// TODO: Change this interface to ensure that attachment JARs are put on the classpath before execution.
val program: Contract = programMap[hash]
val programs = (inStates.map { it.contract } + outStates.map { it.contract }).toSet()
for (program in programs) {
try {
program.verify(this)
} catch(e: Throwable) {

View File

@ -16,8 +16,7 @@ import com.esotericsoftware.kryo.Serializer
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import com.esotericsoftware.kryo.serializers.JavaSerializer
import core.SignedTransaction
import core.WireTransaction
import core.*
import core.crypto.SecureHash
import core.crypto.generateKeyPair
import core.crypto.sha256
@ -179,6 +178,19 @@ class ImmutableClassSerializer<T : Any>(val klass: KClass<T>) : Serializer<T>()
}
}
fun Kryo.useClassLoader(cl: ClassLoader, body: () -> Unit) {
val tmp = this.classLoader
this.classLoader = cl
try {
body()
}
finally {
if (tmp != null) {
this.classLoader
}
}
}
fun createKryo(k: Kryo = Kryo()): Kryo {
return k.apply {
// Allow any class to be deserialized (this is insecure but for prototyping we don't care)
@ -201,6 +213,34 @@ fun createKryo(k: Kryo = Kryo()): Kryo {
}
})
register(WireTransaction::class.java, object : Serializer<WireTransaction>() {
override fun write(kryo: Kryo, output: Output, obj: WireTransaction) {
kryo.writeClassAndObject( output, obj.inputs )
kryo.writeClassAndObject( output, obj.attachments )
kryo.writeClassAndObject( output, obj.outputs )
kryo.writeClassAndObject( output, obj.commands )
}
override fun read(kryo: Kryo, input: Input, type: Class<WireTransaction>): WireTransaction {
var inputs = kryo.readClassAndObject( input ) as List<StateRef>
var attachments = kryo.readClassAndObject( input ) as List<SecureHash>
// had we access to AttachmentStorage here, a ClassLoader could be created
// val customClassLoader = createClassLoader( attachments )
// kryo.useClassLoader(customClassLoader) {
var outputs = kryo.readClassAndObject(input) as List<ContractState>
var commands = kryo.readClassAndObject(input) as List<Command>
return WireTransaction(inputs, attachments, outputs, commands)
// }
}
})
// Some things where the JRE provides an efficient custom serialisation.
val ser = JavaSerializer()
val keyPair = generateKeyPair()

View File

@ -66,22 +66,6 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
val legallyIdentifableAddress: LegallyIdentifiableNode get() = LegallyIdentifiableNode(net.myAddress, storage.myLegalIdentity)
// TODO: This will be obsoleted by "PLT-12: Basic module/sandbox system for contracts"
protected val contractFactory = object : ContractFactory {
private val contracts = mapOf(
CASH_PROGRAM_ID to Cash::class.java,
CP_PROGRAM_ID to CommercialPaper::class.java,
CROWDFUND_PROGRAM_ID to CrowdFund::class.java,
DUMMY_PROGRAM_ID to DummyContract::class.java
)
override fun <T : Contract> get(hash: SecureHash): T {
val c = contracts[hash] ?: throw UnknownContractException()
@Suppress("UNCHECKED_CAST")
return c.newInstance() as T
}
}
lateinit var storage: StorageService
lateinit var smm: StateMachineManager
lateinit var wallet: WalletService
@ -147,12 +131,11 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
val attachments = makeAttachmentStorage(dir)
_servicesThatAcceptUploads += attachments
val (identity, keypair) = obtainKeyPair(dir)
return constructStorageService(attachments, keypair, identity, contractFactory)
return constructStorageService(attachments, keypair, identity)
}
protected open fun constructStorageService(attachments: NodeAttachmentService, keypair: KeyPair, identity: Party,
contractFactory: ContractFactory) =
StorageServiceImpl(attachments, contractFactory, keypair, identity)
protected open fun constructStorageService(attachments: NodeAttachmentService, keypair: KeyPair, identity: Party) =
StorageServiceImpl(attachments, keypair, identity)
private fun obtainKeyPair(dir: Path): Pair<Party, KeyPair> {
// Load the private identity key, creating it if necessary. The identity key is a long term well known key that

View File

@ -6,6 +6,8 @@ import java.io.File
import java.io.FileOutputStream
import java.net.URL
import java.net.URLClassLoader
import java.security.AccessControlContext
import java.security.ProtectionDomain
import java.util.*
import java.util.jar.JarEntry
@ -28,6 +30,10 @@ class ClassLoader private constructor(val tmpFiles: List<File> )
}
}
override fun loadClass(name: String?, resolve: Boolean): Class<*>? {
return super.loadClass(name, resolve)
}
companion object {
fun create(streams: List<Attachment>) : ClassLoader {

View File

@ -116,11 +116,6 @@ interface StorageService {
/** Provides access to storage of arbitrary JAR files (which may contain only data, no code). */
val attachments: AttachmentStorage
/**
* A map of program hash->contract class type, used for verification.
*/
val contractPrograms: ContractFactory
/**
* Returns the legal identity that this node is configured with. Assumed to be initialised when the node is
* first installed.
@ -186,6 +181,6 @@ interface ServiceHub {
storageService.validatedTransactions[it.txhash] ?: throw TransactionResolutionException(it.txhash)
}
val ltxns = dependencies.map { it.verifyToLedgerTransaction(identityService, storageService.attachments) }
TransactionGroup(setOf(ltx), ltxns.toSet()).verify(storageService.contractPrograms)
TransactionGroup(setOf(ltx), ltxns.toSet()).verify()
}
}

View File

@ -1,6 +1,5 @@
package core.node.services
import core.ContractFactory
import core.Party
import core.SignedTransaction
import core.crypto.SecureHash
@ -10,7 +9,6 @@ import java.security.KeyPair
import java.util.*
open class StorageServiceImpl(attachments: AttachmentStorage,
contractFactory: ContractFactory,
keypair: KeyPair,
identity: Party = Party("Unit test party", keypair.public),
// This parameter is for unit tests that want to observe operation details.
@ -39,7 +37,6 @@ open class StorageServiceImpl(attachments: AttachmentStorage,
get() = getMapOriginal("state-machines")
override val attachments: AttachmentStorage = attachments
override val contractPrograms = contractFactory
override val myLegalIdentity = identity
override val myLegalIdentityKey = keypair
}

View File

@ -66,7 +66,7 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
}
// Run all the contracts and throw an exception if any of them reject.
TransactionGroup(toVerify, alreadyVerified).verify(serviceHub.storageService.contractPrograms)
TransactionGroup(toVerify, alreadyVerified).verify()
// Now write all the transactions we just validated back to the database for next time, including
// signatures so we can serve up these transactions to other peers when we, in turn, send one that

View File

@ -219,11 +219,11 @@ class CommercialPaperTestsGeneric {
val validRedemption = makeRedeemTX(TEST_TX_TIME + 31.days)
val e = assertFailsWith(TransactionVerificationException::class) {
TransactionGroup(setOf(issueTX, moveTX, tooEarlyRedemption), setOf(corpWalletTX, alicesWalletTX)).verify(MockContractFactory)
TransactionGroup(setOf(issueTX, moveTX, tooEarlyRedemption), setOf(corpWalletTX, alicesWalletTX)).verify()
}
assertTrue(e.cause!!.message!!.contains("paper must have matured"))
TransactionGroup(setOf(issueTX, moveTX, validRedemption), setOf(corpWalletTX, alicesWalletTX)).verify(MockContractFactory)
TransactionGroup(setOf(issueTX, moveTX, validRedemption), setOf(corpWalletTX, alicesWalletTX)).verify()
}
// Generate a trade lifecycle with various parameters.

View File

@ -160,11 +160,11 @@ class CrowdFundTests {
val validClose = makeFundedTX(TEST_TX_TIME + 8.days)
val e = assertFailsWith(TransactionVerificationException::class) {
TransactionGroup(setOf(registerTX, pledgeTX, tooEarlyClose), setOf(miniCorpWalletTx, aliceWalletTX)).verify(MockContractFactory)
TransactionGroup(setOf(registerTX, pledgeTX, tooEarlyClose), setOf(miniCorpWalletTx, aliceWalletTX)).verify()
}
assertTrue(e.cause!!.message!!.contains("the closing date has past"))
// This verification passes
TransactionGroup(setOf(registerTX, pledgeTX, validClose), setOf(aliceWalletTX)).verify(MockContractFactory)
TransactionGroup(setOf(registerTX, pledgeTX, validClose), setOf(aliceWalletTX)).verify()
}
}

View File

@ -116,15 +116,7 @@ class MockAttachmentStorage : AttachmentStorage {
}
@ThreadSafe
class MockStorageService : StorageServiceImpl(MockAttachmentStorage(), MockContractFactory, generateKeyPair()) {
}
object MockContractFactory : ContractFactory {
override operator fun <T : Contract> get(hash: SecureHash): T {
val clazz = TEST_PROGRAM_MAP[hash] ?: throw UnknownContractException()
@Suppress("UNCHECKED_CAST")
return clazz.newInstance() as T
}
class MockStorageService : StorageServiceImpl(MockAttachmentStorage(), generateKeyPair()) {
}
class MockServices(

View File

@ -153,6 +153,6 @@ class TransactionGroupTests {
// Now go through the conversion -> verification path with them.
val ltxns = signedTxns.map { it.verifyToLedgerTransaction(MockIdentityService, MockStorageService().attachments) }.toSet()
TransactionGroup(ltxns, emptySet()).verify(MockContractFactory)
TransactionGroup(ltxns, emptySet()).verify()
}
}

View File

@ -195,10 +195,9 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() {
return net.createNode(null) { path, config, net, tsNode ->
object : MockNetwork.MockNode(path, config, net, tsNode) {
// That constructs the storage service object in a customised way ...
override fun constructStorageService(attachments: NodeAttachmentService, keypair: KeyPair, identity: Party,
contractFactory: ContractFactory): StorageServiceImpl {
override fun constructStorageService(attachments: NodeAttachmentService, keypair: KeyPair, identity: Party): StorageServiceImpl {
// To use RecordingMaps instead of ordinary HashMaps.
return StorageServiceImpl(attachments, contractFactory, keypair, identity, { tableName -> name })
return StorageServiceImpl(attachments, keypair, identity, { tableName -> name })
}
}
}

View File

@ -173,4 +173,9 @@ class ClassLoaderTests {
assertNotNull(state2)
}
@Test
fun `white list serialization`() {
}
}

View File

@ -74,7 +74,7 @@ val TEST_KEYS_TO_CORP_MAP: Map<PublicKey, Party> = mapOf(
// 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.
val TEST_PROGRAM_MAP: Map<SecureHash, Class<out Contract>> = mapOf(
val TEST_PROGRAM_MAP: Map<Contract, Class<out Contract>> = mapOf(
CASH_PROGRAM_ID to Cash::class.java,
CP_PROGRAM_ID to CommercialPaper::class.java,
JavaCommercialPaper.JCP_PROGRAM_ID to JavaCommercialPaper::class.java,
@ -162,7 +162,7 @@ open class TransactionForTest : AbstractTransactionForTest() {
protected fun run(time: Instant) {
val cmds = commandsToAuthenticatedObjects()
val tx = TransactionForVerification(inStates, outStates.map { it.state }, emptyList(), cmds, SecureHash.randomSHA256())
tx.verify(MockContractFactory)
tx.verify()
}
fun accepts(time: Instant = TEST_TX_TIME) = run(time)
@ -319,7 +319,7 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
fun verify() {
val group = toTransactionGroup()
try {
group.verify(MockContractFactory)
group.verify()
} catch (e: TransactionVerificationException) {
// Let the developer know the index of the transaction that failed.
val wtx: WireTransaction = txns.find { it.id == e.tx.origHash }!!