From 257d00a032cf5d7531bbc660c9998129d443e88f Mon Sep 17 00:00:00 2001 From: Mic Bowman Date: Wed, 2 Oct 2024 10:49:25 -0600 Subject: [PATCH 1/7] Create a "contract" for common python modules Split out the "contracts" module from the exchange contract and put it into a new "common" contract family. While strictly speaking, this is not a contract family, the build processes incorporat module creation very easily. In addition, this provides a framework for adding common functionality (like wrappers for tests, shared methods, etc). Signed-off-by: Mic Bowman --- common-contract/CMakeLists.txt | 43 +++ common-contract/MANIFEST | 11 + common-contract/MANIFEST.in | 4 + common-contract/README.md | 267 ++++++++++++++++++ common-contract/docs/notebooks/.gitignore | 1 + common-contract/pdo/__init__.py | 15 + .../pdo/contracts/__init__.py | 0 .../pdo/contracts/common.py | 0 .../pdo/contracts/jupyter/__init__.py | 0 .../pdo/contracts/jupyter/common_widgets.py | 0 .../pdo/contracts/jupyter/groups.py | 0 .../pdo/contracts/jupyter/keys.py | 0 .../pdo/contracts/jupyter/services.py | 0 .../pdo/contracts/jupyter/utility.py | 0 common-contract/setup.py | 94 ++++++ exchange-contract/MANIFEST | 8 - exchange-contract/setup.py | 2 - 17 files changed, 435 insertions(+), 10 deletions(-) create mode 100644 common-contract/CMakeLists.txt create mode 100644 common-contract/MANIFEST create mode 100644 common-contract/MANIFEST.in create mode 100644 common-contract/README.md create mode 100644 common-contract/docs/notebooks/.gitignore create mode 100644 common-contract/pdo/__init__.py rename {exchange-contract => common-contract}/pdo/contracts/__init__.py (100%) rename {exchange-contract => common-contract}/pdo/contracts/common.py (100%) rename {exchange-contract => common-contract}/pdo/contracts/jupyter/__init__.py (100%) rename {exchange-contract => common-contract}/pdo/contracts/jupyter/common_widgets.py (100%) rename {exchange-contract => common-contract}/pdo/contracts/jupyter/groups.py (100%) rename {exchange-contract => common-contract}/pdo/contracts/jupyter/keys.py (100%) rename {exchange-contract => common-contract}/pdo/contracts/jupyter/services.py (100%) rename {exchange-contract => common-contract}/pdo/contracts/jupyter/utility.py (100%) create mode 100644 common-contract/setup.py diff --git a/common-contract/CMakeLists.txt b/common-contract/CMakeLists.txt new file mode 100644 index 0000000..c128583 --- /dev/null +++ b/common-contract/CMakeLists.txt @@ -0,0 +1,43 @@ +# Copyright 2019 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ----------------------------------------------------------------- +# Build the library of methods that are defined by the contract +# family. This simplifies sharing the methods with other contract +# families. +# ----------------------------------------------------------------- + +# ----------------------------------------------------------------- +# Build the python wheel that will install all of the contracts in the +# contract family; the contract targets must match the targets used +# to create the contracts above. +# ----------------------------------------------------------------- +INCLUDE(Python) +BUILD_WHEEL(contracts) + +# ----------------------------------------------------------------- +# install the jupyter notebooks, note that the trailing slash here +# is significant and should not be removed; it prevents the notebooks +# directory being prepended to the copied name +# ----------------------------------------------------------------- +INCLUDE(Jupyter) +FILE(GLOB_RECURSE NOTEBOOK_SOURCES docs/notebooks/*.py docs/notebooks/*.md) + +CONVERT_JUPYTEXT(COMMON_NOTEBOOKS ${NOTEBOOK_SOURCES}) + +ADD_CUSTOM_TARGET(common-notebooks ALL DEPENDS ${COMMON_NOTEBOOKS}) + +INSTALL(DIRECTORY docs/notebooks/ + DESTINATION "${PDO_JUPYTER_ROOT}/common" + FILES_MATCHING PATTERN "*.ipynb" PATTERN "*.png") diff --git a/common-contract/MANIFEST b/common-contract/MANIFEST new file mode 100644 index 0000000..403300f --- /dev/null +++ b/common-contract/MANIFEST @@ -0,0 +1,11 @@ +MANIFEST.in +setup.py +pdo/__init__.py +pdo/contracts/common.py +pdo/contracts/__init__.py +pdo/contracts/jupyter/services.py +pdo/contracts/jupyter/groups.py +pdo/contracts/jupyter/common_widgets.py +pdo/contracts/jupyter/utility.py +pdo/contracts/jupyter/keys.py +pdo/contracts/jupyter/__init__.py \ No newline at end of file diff --git a/common-contract/MANIFEST.in b/common-contract/MANIFEST.in new file mode 100644 index 0000000..dfe4b55 --- /dev/null +++ b/common-contract/MANIFEST.in @@ -0,0 +1,4 @@ +recursive-include ../build/example-contract *.b64 +recursive-include etc *.toml +recursive-include context *.toml +recursive-include scripts *.psh \ No newline at end of file diff --git a/common-contract/README.md b/common-contract/README.md new file mode 100644 index 0000000..572266a --- /dev/null +++ b/common-contract/README.md @@ -0,0 +1,267 @@ + + +**The protocols and software are for reference purposes only and not intended for production usage.** + +# Example Contract Family # + +This directory contains an example contract family that can be used as +the basis for implementing your own contract families. This file will +walk you through the process of creating a new contract family. + +Note that this description is not intended to be prescriptive. There +are many ways to build and deploy a contract. This directory simply +describes one way that we have found relatively easy to use. + +# What is a Contract Family? # + +A contract family is a collection of PDO contracts that work together +to create an "application". For example, the contracts in the Exchange +contract family, provide contracts to create digital asset like "red +marbles" with a veriable trust chain and contracts to use those assets +to purchase or exchange goods, services, or other assets. It is the +collection of contracts intentionally designed to work together that +we call a contract family. + +Operationally, a contract family consists of a set of PDO contracts +and instructions for how to build them, configuration files that help +to specify the relationship between contracts or contract objects, and +plugins that makes it easier to use the contracts through a PDO shell, +a bash shell, or a Jupyter notebook. + +## File System Layout ## + +* The root directory of the contract family generally contains + information useful for building and deploying contracts in the + family. + +* The `context` directory contains context files that describe the + relationship between contract objects and help to coordinate the + configuration of dependent contract objects. + +* The `src` directory contains the source files used to build the + contracts in the contract family. The source directory contains + several subdirectories: + + * The `common` directory contains modules that will be shared + amongst all of the contracts. + + * The `packages` directory may contain external dependencies + required for the contract. + + * The `methods` directory contains modules that implement different + groups of methods. For simple contracts there is generally one + method module per contract. More complex contracts may mix and + match methods from different modules. + + * The `contracts` directory contains the definition of the contracts + themselves. These are generally specified as a collection of + methods defined in the `methods` directory. + + * Finally, there is often a directory that contains interface + definitions that may be used by other contract families to re-use + methods. For example, this contract family defines an interface + for the counter contract in the `example` directory. + +* The `docs` directory generally contains interface specifications for + the methods in the contract. It may also contain the templates for + Jupyter notebooks used to interact with the contracts. + +* The `etc` directory contains basic configuration information. When + you customize your own contract family, you will need to update + information about the mapping between contract types and compiled + contract code. + +* The `pdo` directory is the root of the Python modules associated + with the contract family. The Python modules contain plugins for the + PDO and bash shells, and utility functions that can be invoked from + a Jupyter notebook. + +* The `scripts` directory may optionally contain PDO shell scripts + that will be installed with the deployed contracts. + +* The `test` directory defines system tests for the contract family + that will be automatically run by the contracts CI system. Tests are + broken into two parts, one for running a set of commands in a single + PDO shell invocation and one for running a series of bash shell + commands. Note that the two are similar but not the same. The PDO + shell will process transaction commits asynchronously while the bash + shell tests commit synchronously. + +# How to Create a New Contract Family Using the Example Template # + +The following instructions use the `example` contract family as a +template for building a new contract family that we'll call the +`myfirst` contract family. While each contract family can be +structured to your personal preferences, the `example` contract family +provides an easy way to get started. + +1. Copy the `example-contract` directory to a new directory. To be + picked up by the build system automatically, the new directory + should be named with `myfirst-contract`. + +```bash + cp -R example-contract myfirst-contract +``` + +2. Edit `myfirst-contract/family.cmake`. Replace `example` with + `myfirst` as the value of `CF_NAME`. + +3. Edit `setup.py`. Replace `example` with `myfirst` as the value of + the `contract_family` variable. Replace `example_counter` with + `myfirst_counter` as the value of the `contract_scripts` + variable. Update the author information for the Python package. + +4. Rename the `example` contract context file, the Python module + directory and the directory that contains exported header files. + +```bash + mv etc/example.toml etc/myfirst.toml + mv src/example src/myfirst + mv pdo/example pdo/myfirst +``` + +5. Replace references to `example` with `myfirst`. This will update + contract family references in several locations including the + contract source namespace in the `src` directory, the contract + references in the tests, and the plugin and resource references in + the Python modules. + +```bash +find . -type f -exec sed -i s/example/myfirst/g {} \; +find . -type f -exec sed -i s/Example/MyFirst/g {} \; +``` + +At this point, assuming you have a complete PDO client installation, +you should be able build and test your new contract family. To do +this, add the following line to `Local.cmake` in the PDO contracts +root directory: + +``` +SET(CONTRACT_FAMILIES myfirst-contract) +``` + +This will limit the build process to the new contract family to +simplify testing. + +Assuming that you have installed and configured a PDO client +environment (and have activated the PDO Python virtual environment), +then you can build and run the tests: + +```bash +make install +make test +``` + +# How to Add a New Contract to the Contract Family # + +A PDO contract consists of a set of methods that operate on a +persistent state. Methods are generally grouped together in a +namespace to make them easier to re-use for multiple contracts. The +`counter` methods module (see `src/methods/counter.cpp`) in the +example contract family defines two methods, one for incrementing the +counter and one getting the current value of the counter. The methods +module also includes a function that can be used to initialize the +contract state prior to using either method. These methods can be +imported into any contract so long as the contract invokes the +initialization function prior to invoking either of the counter +methods. + +The actual `counter` contract is defined in +`src/contracts/counter.cpp`. Effectively, the contract consists of an +initialization function that will be called when the contract object +is first created and a dispatch table for methods on the contract +object. The dispatch table maps an external name of the method (all +methods are invoked through a JSON RPC protocol defined in PDO) to the +code that implements the method. + +Note that in the `counter` contract, the `initialize_contract` +function calls the `counter` method module initialization function so +that the `counter` methods will work correctly. + +## How to Add a New Method ## + +To add a new method to the `counter` contract, simply add the code for +the method to `src/methods/counter.cpp` (or create a new method module +in that directory) and add the interface definition to the public +header file `src/example/counter.h`. For example, you could define a +new method for the `counter` method module called `dec_value` to +decrement the value of the counter. The implementation of the method +would be placed in the `counter` method module and the interface added +to the public header file. + +To include the new method in the contract, add it to the dispatch +table in the contract definition file `src/contracts/counter.cpp`. The +table uses the PDO macro for specifying the external name of the +method and the contract function that will be invoked through that +name. + +## How to Define a New Contract ## + +The following steps are taken to create a new type of contract: + +1. Define and implement any additional methods that are necessary in a + method module. Methods may also be imported from other contract + families. See the `CMakeLists.txt` file in the digital-asset + contract family for an example of how methods are imported from the + exchange contract family. + +2. Create a new contract definition file in `src/contracts`. The file + must contain a contract initialization function and a dispatch + table for the methods exported by the contract. + +3. Add the contract to the list of contracts in `family.cmake`. That + is, add the name of the new contract to the `CF_CONTRACTS` macro. + +4. Add the contract mapping to the context configuration file in the + `etc` directory. The name of the context file for the example + contract family is `etc/example.toml`. This mapping helps the + shells to identify the location of the compiled contract when a new + contract object is created. + +Assuming that you have installed and configured a PDO client +environment (and have activated the PDO Python virtual environment), +then you can build and install the new contract: + +```bash +make +make install +``` + +The new contract should be available in the `${PDO_HOME}/contracts` +directory. To test the contract effectively will require a new Python +plugin for the contract. + +# How to Define a New Shell Plugin # + +Shell plugins provide access to PDO contract method invocations +through the PDO shell, Bash shell, and other Python scripts using a +single implementation. + +Plugins come in two flavors. The first are operations derived from the +`pdo.client.builder.contract.contract_op_base` class. These operations +generally have a one-to-one correspondance to methods on the contract +object. The second are commands derived from the +`pdo.client.builder.command.contract_command_base` class. Commands are +generally used for complex operations that involve invocation of +multiple methods potentially across multiple contract objects. + +For example, the plugin for `counter` contract objects defines one +operation for each of the methods (`inc_value` and `get_value`) and a +command to create the counter (which creates the contract object and +initializes it). + +## How to Write a Plugin for a Contract ## + +The easiest way to define a plugin for a new type of contract object +is to simply copy the basic framework for the `counter` plugin and +implement an operation for each method on your new contract. If your +new contract imports methods from another contract family, it is +relatively straightforward to import the plugin operations as well. + +Examples for invoking the operations and commands is provided by the +scripts in the `test` directory. + +# Jupyter Plugins # diff --git a/common-contract/docs/notebooks/.gitignore b/common-contract/docs/notebooks/.gitignore new file mode 100644 index 0000000..fa65608 --- /dev/null +++ b/common-contract/docs/notebooks/.gitignore @@ -0,0 +1 @@ +*.ipynb diff --git a/common-contract/pdo/__init__.py b/common-contract/pdo/__init__.py new file mode 100644 index 0000000..25bb107 --- /dev/null +++ b/common-contract/pdo/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2018 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__import__('pkg_resources').declare_namespace('pdo') diff --git a/exchange-contract/pdo/contracts/__init__.py b/common-contract/pdo/contracts/__init__.py similarity index 100% rename from exchange-contract/pdo/contracts/__init__.py rename to common-contract/pdo/contracts/__init__.py diff --git a/exchange-contract/pdo/contracts/common.py b/common-contract/pdo/contracts/common.py similarity index 100% rename from exchange-contract/pdo/contracts/common.py rename to common-contract/pdo/contracts/common.py diff --git a/exchange-contract/pdo/contracts/jupyter/__init__.py b/common-contract/pdo/contracts/jupyter/__init__.py similarity index 100% rename from exchange-contract/pdo/contracts/jupyter/__init__.py rename to common-contract/pdo/contracts/jupyter/__init__.py diff --git a/exchange-contract/pdo/contracts/jupyter/common_widgets.py b/common-contract/pdo/contracts/jupyter/common_widgets.py similarity index 100% rename from exchange-contract/pdo/contracts/jupyter/common_widgets.py rename to common-contract/pdo/contracts/jupyter/common_widgets.py diff --git a/exchange-contract/pdo/contracts/jupyter/groups.py b/common-contract/pdo/contracts/jupyter/groups.py similarity index 100% rename from exchange-contract/pdo/contracts/jupyter/groups.py rename to common-contract/pdo/contracts/jupyter/groups.py diff --git a/exchange-contract/pdo/contracts/jupyter/keys.py b/common-contract/pdo/contracts/jupyter/keys.py similarity index 100% rename from exchange-contract/pdo/contracts/jupyter/keys.py rename to common-contract/pdo/contracts/jupyter/keys.py diff --git a/exchange-contract/pdo/contracts/jupyter/services.py b/common-contract/pdo/contracts/jupyter/services.py similarity index 100% rename from exchange-contract/pdo/contracts/jupyter/services.py rename to common-contract/pdo/contracts/jupyter/services.py diff --git a/exchange-contract/pdo/contracts/jupyter/utility.py b/common-contract/pdo/contracts/jupyter/utility.py similarity index 100% rename from exchange-contract/pdo/contracts/jupyter/utility.py rename to common-contract/pdo/contracts/jupyter/utility.py diff --git a/common-contract/setup.py b/common-contract/setup.py new file mode 100644 index 0000000..ca0e9be --- /dev/null +++ b/common-contract/setup.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python + +# Copyright 2022 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import subprocess +import warnings + +## ----------------------------------------------------------------- +# Change values of the following variables to customize your +# contract family; the contract_scripts variable is a list of +# bash commands that will be created in the python wheel. See +# ./pdo/{contract_family}/scripts/scripts.py for more information +# on building command line scripts. +## ----------------------------------------------------------------- +contract_family = 'contracts' +contract_scripts = [ ] + +author = 'Mic Bowman, Intel Labs' +author_email = 'mic.bowman@intel.com' +author_url = 'http://www.intel.com' + +## ----------------------------------------------------------------- +# Unless you change the standard behavior or layout of the contract +# there should be no changes required below +## ----------------------------------------------------------------- + +# this should only be run with python3 +if sys.version_info[0] < 3: + print('ERROR: must run with python3') + sys.exit(1) + +from setuptools import setup + +## ----------------------------------------------------------------- +# Versions are tied to tags on the repository; to compute correctly +# it is necessary to be within the repository itself hence the need +# to set the cwd for the bin/get_version command. +## ----------------------------------------------------------------- +root_dir = os.path.dirname(os.path.realpath(__file__)) +try : + pdo_contracts_version = subprocess.check_output( + 'bin/get_version', cwd=os.path.join(root_dir, os.pardir)).decode('ascii').strip() +except Exception as e : + warnings.warn(f'Failed to get pdo_contracts version, using the default; {e}') + pdo_contracts_version = '0.0.0' + +try : + pdo_client_version = subprocess.check_output( + 'bin/get_version', cwd=os.path.join(root_dir, os.pardir, 'private-data-objects')).decode('ascii').strip() +except Exception as e : + warnings.warn(f'Failed to get pdo_client version, using the default; {e}') + pdo_client_version = '0.0.0' + +## ----------------------------------------------------------------- +## ----------------------------------------------------------------- +setup( + name=f'pdo_{contract_family}', + version=pdo_contracts_version, + description='Contract and support scripts for a PDO contract', + author=author, + author_email=author_email, + url=author_url, + package_dir = { + 'pdo' : 'pdo', + }, + packages = [ + 'pdo', + f'pdo.{contract_family}', + f'pdo.{contract_family}.jupyter', + ], + include_package_data=True, + install_requires = [ + 'colorama', + 'ipywidgets', + 'pdo-client>=' + pdo_client_version, + 'pdo-common-library>=' + pdo_client_version, + ], + entry_points = { + } +) diff --git a/exchange-contract/MANIFEST b/exchange-contract/MANIFEST index 62c20da..e18d45f 100644 --- a/exchange-contract/MANIFEST +++ b/exchange-contract/MANIFEST @@ -5,14 +5,6 @@ context/order.toml context/tokens.toml etc/exchange.toml pdo/__init__.py -pdo/contracts/__init__.py -pdo/contracts/common.py -pdo/contracts/jupyter/__init__.py -pdo/contracts/jupyter/common_widgets.py -pdo/contracts/jupyter/groups.py -pdo/contracts/jupyter/keys.py -pdo/contracts/jupyter/services.py -pdo/contracts/jupyter/utility.py pdo/exchange/__init__.py pdo/exchange/jupyter/__init__.py pdo/exchange/jupyter/context.py diff --git a/exchange-contract/setup.py b/exchange-contract/setup.py index 20ad4ef..c6ed613 100644 --- a/exchange-contract/setup.py +++ b/exchange-contract/setup.py @@ -65,8 +65,6 @@ }, packages = [ 'pdo', - 'pdo.contracts', - 'pdo.contracts.jupyter', 'pdo.exchange', 'pdo.exchange.jupyter', 'pdo.exchange.plugins', From f51606aec8d83866fd800bb538df72d64697b744 Mon Sep 17 00:00:00 2001 From: Mic Bowman Date: Wed, 2 Oct 2024 11:12:59 -0600 Subject: [PATCH 2/7] Move common notebooks under the common contract folder The widget and management notebooks are part of the common contract family. Move the notebooks out of the root directory and put them into the common directory. Update references to the notebooks. Signed-off-by: Mic Bowman --- .../notebooks/documents/getting_started.py | 0 .../docs}/notebooks/documents/key_manager.py | 0 .../documents/service_groups_manager.py | 0 .../notebooks/documents/service_manager.py | 0 .../docs}/notebooks/documents/widgets.py | 0 docs/notebooks/index.md | 50 ++++++++++--------- .../docs/tests/05.token_transfer.md | 4 +- .../docs/tests/06.single_issuer_wallet.md | 4 +- .../docs/tests/07.multiple_issuer_wallet.md | 4 +- .../docs/tests/08.multiple_user_wallet.md | 6 +-- 10 files changed, 35 insertions(+), 33 deletions(-) rename {docs => common-contract/docs}/notebooks/documents/getting_started.py (100%) rename {docs => common-contract/docs}/notebooks/documents/key_manager.py (100%) rename {docs => common-contract/docs}/notebooks/documents/service_groups_manager.py (100%) rename {docs => common-contract/docs}/notebooks/documents/service_manager.py (100%) rename {docs => common-contract/docs}/notebooks/documents/widgets.py (100%) diff --git a/docs/notebooks/documents/getting_started.py b/common-contract/docs/notebooks/documents/getting_started.py similarity index 100% rename from docs/notebooks/documents/getting_started.py rename to common-contract/docs/notebooks/documents/getting_started.py diff --git a/docs/notebooks/documents/key_manager.py b/common-contract/docs/notebooks/documents/key_manager.py similarity index 100% rename from docs/notebooks/documents/key_manager.py rename to common-contract/docs/notebooks/documents/key_manager.py diff --git a/docs/notebooks/documents/service_groups_manager.py b/common-contract/docs/notebooks/documents/service_groups_manager.py similarity index 100% rename from docs/notebooks/documents/service_groups_manager.py rename to common-contract/docs/notebooks/documents/service_groups_manager.py diff --git a/docs/notebooks/documents/service_manager.py b/common-contract/docs/notebooks/documents/service_manager.py similarity index 100% rename from docs/notebooks/documents/service_manager.py rename to common-contract/docs/notebooks/documents/service_manager.py diff --git a/docs/notebooks/documents/widgets.py b/common-contract/docs/notebooks/documents/widgets.py similarity index 100% rename from docs/notebooks/documents/widgets.py rename to common-contract/docs/notebooks/documents/widgets.py diff --git a/docs/notebooks/index.md b/docs/notebooks/index.md index c85957f..0aec1fa 100644 --- a/docs/notebooks/index.md +++ b/docs/notebooks/index.md @@ -14,47 +14,49 @@ jupyter: # PDO Contracts Launch Page # - -[Hyperledger Private Data Objects](https://github.com/hyperledger-labs/private-data-objects) operates as a Hyperledger Labs project. This code is provided solely to demonstrate basic PDO mechanisms and to facilitate -collaboration to refine PDO architecture and define minimum viable product requirements. +[Hyperledger Private Data Objects](https://github.com/hyperledger-labs/private-data-objects) +operates as a Hyperledger Labs project. This code is provided solely +to demonstrate basic PDO mechanisms and to facilitate collaboration to +refine PDO architecture and define minimum viable product +requirements. **The PDO contracts code provided here is prototype code and not intended for production use.** - ## Getting Started - -There are a number of configuration options for interacting with PDO contracts. More information can be found in the [Getting Started](documents/getting_started.ipynb) notebook. - - - +There are a number of configuration options for interacting with PDO +contracts. More information can be found in the +[Getting Started](common/documents/getting_started.ipynb) notebook. ## Contract Families - ### Exchange +The Exchange contract family is a suite of contracts that demonstrate +many of the capabilities of the private data objects +technologies. Three basic contracts define the elements of the +Exchange contract family: the asset type contract, the vetting +organization contract, and the issuer contract. Three additional +contracts extend the Exchange contract family for trading potentially +confidential assets using non-fungible tokens. -The Exchange contract family is a suite of contracts that demonstrate many of the capabilities of the private data objects technologies. Three basic contracts define the elements of the Exchange contract family: the asset type contract, the vetting organization contract, and the issuer contract. Three additional contracts extend the Exchange contract family for trading potentially confidential assets using non-fungible tokens. - -To experiment with these contracts, explore the [Exchange Contract Family notebook](exchange/index.ipynb) - - +To experiment with these contracts, explore the +[Exchange Contract Family notebook](exchange/index.ipynb) ### Digital Assets - -The Digital Assets contract family implements a basic digital asset for bitmap images. The contracts extend the asset and token contracts in the Exchange contract family for sharing images with well-defined policies. - - +The Digital Assets contract family implements a basic digital asset +for bitmap images. The contracts extend the asset and token contracts +in the Exchange contract family for sharing images with well-defined +policies. ### Inference - -The Inference contract family provides contracts for creating a confidentiality -preserving policy-wrapper around the usage of a machine learning (ML) model. -The policy-wrapper specifies and helps enforce policies to be followed while -using the ML model for inferencing operations, and is implemented as a PDO contract. +The Inference contract family provides contracts for creating a +confidentiality preserving policy-wrapper around the usage of a +machine learning (ML) model. The policy-wrapper specifies and helps +enforce policies to be followed while using the ML model for +inferencing operations, and is implemented as a PDO contract. To experiment with inference contracts, explore the [Inference Contract Family notebook](inference/index.ipynb) diff --git a/exchange-contract/docs/tests/05.token_transfer.md b/exchange-contract/docs/tests/05.token_transfer.md index f0ca28b..fd127e2 100644 --- a/exchange-contract/docs/tests/05.token_transfer.md +++ b/exchange-contract/docs/tests/05.token_transfer.md @@ -26,13 +26,13 @@ will refer to the windows as `container_1` (for the user) and `container_2` (for the issuer). **Container_1: Create the keys for the token user.** Open the -notebook `documents/getting_started.ipynb` and run all cells. Navigate +notebook `common/documents/getting_started.ipynb` and run all cells. Navigate to "Create Keys" and create a new key pair for the identity "blue_user". Refresh the list of public keys and click on the "blue_user" key file to download the "blue_user" public key. **Container_2: Create the keys for the token issuer.** Open the -notebook `documents/getting_started.ipynb` and run all cells. Navigate +notebook `common/documents/getting_started.ipynb` and run all cells. Navigate to "Create Keys" and create a new key pair for the identity "blue_issuer". Refresh the public and private key lists to verify that the keys have been created. diff --git a/exchange-contract/docs/tests/06.single_issuer_wallet.md b/exchange-contract/docs/tests/06.single_issuer_wallet.md index fcabe17..3b71ce2 100644 --- a/exchange-contract/docs/tests/06.single_issuer_wallet.md +++ b/exchange-contract/docs/tests/06.single_issuer_wallet.md @@ -24,13 +24,13 @@ will refer to the windows as `container_1` (for the user) and `container_2` (for the issuer). **Container_1: Create the keys for the user.** Open the notebook -`documents/getting_started.ipynb` and run all cells. Navigate to +`common/documents/getting_started.ipynb` and run all cells. Navigate to "Create Keys" and generate a new key pair for the identity "blue_user". Refresh the list of public keys and click on the "blue_user" key file to download the "blue_user" public key. **Container_2: Create the keys for the issuer.** Open the -notebook `documents/getting_started.ipynb` and run all cells. Navigate +notebook `common/documents/getting_started.ipynb` and run all cells. Navigate to "Create Keys" and create a new key pair for the identity "blue_issuer". Refresh the public and private key lists to verify that the keys have been created. diff --git a/exchange-contract/docs/tests/07.multiple_issuer_wallet.md b/exchange-contract/docs/tests/07.multiple_issuer_wallet.md index 6b81963..235bb90 100644 --- a/exchange-contract/docs/tests/07.multiple_issuer_wallet.md +++ b/exchange-contract/docs/tests/07.multiple_issuer_wallet.md @@ -21,13 +21,13 @@ will refer to the windows as `container_1` (for the user) and `container_2` (for the issuer). **Container_1: Create the keys for the user.** Open the notebook -`documents/getting_started.ipynb` and run all cells. Navigate to +`common/documents/getting_started.ipynb` and run all cells. Navigate to "Create Keys" and create a new key pair for the identity "alice". Refresh the list of public keys and click on the "alice" key file to download the "alice" public key. **Container_2: Create the keys for the issuer.** Open the notebook -`documents/getting_started.ipynb` and run all cells. Navigate to +`common/documents/getting_started.ipynb` and run all cells. Navigate to "Create Keys" and create a new key pair for the identity "blue_issuer". Using the same steps, create a new key pair for the identity "green_issuer". Refresh the public and private key lists to diff --git a/exchange-contract/docs/tests/08.multiple_user_wallet.md b/exchange-contract/docs/tests/08.multiple_user_wallet.md index 427689e..be97a14 100644 --- a/exchange-contract/docs/tests/08.multiple_user_wallet.md +++ b/exchange-contract/docs/tests/08.multiple_user_wallet.md @@ -22,13 +22,13 @@ will refer to the windows as `container_1` (for the user) and `container_2` (for the issuer). **Container_1: Create the keys for "alice".** Open the notebook -`documents/getting_started.ipynb` and run all cells. Navigate to +`common/documents/getting_started.ipynb` and run all cells. Navigate to "Create Keys" and create a new key pair for the identity "alice". Refresh the list of public keys and click on the "alice" key file to download the "alice" public key. **Container_2: Create the keys for "bob".** Open the notebook -`documents/getting_started.ipynb` and run all cells. Navigate to +`common/documents/getting_started.ipynb` and run all cells. Navigate to "Create Keys" and create a new key pair for the identity "alice". Refresh the list of public keys and click on the "alice" key file to download the "bob" public key. @@ -44,7 +44,7 @@ downloaded earlier. Refresh the public key list to verify that the key was uploaded successfully. **Container_3: Create the keys for the issuer.** Open the notebook -`documents/getting_started.ipynb` and run all cells. Navigate to +`common/documents/getting_started.ipynb` and run all cells. Navigate to "Create Keys" and create a new key pair for the identity "blue_issuer". Using the same steps, create a new key pair for the identity "green_issuer". Refresh the public and private key lists to From ab9773e7739b431d5b812a937071425d4ba9df25 Mon Sep 17 00:00:00 2001 From: Mic Bowman Date: Wed, 2 Oct 2024 11:15:01 -0600 Subject: [PATCH 3/7] Add module dependency for the common contract module Signed-off-by: Mic Bowman --- digital-asset-contract/setup.py | 1 + exchange-contract/setup.py | 1 + inference-contract/setup.py | 1 + 3 files changed, 3 insertions(+) diff --git a/digital-asset-contract/setup.py b/digital-asset-contract/setup.py index 0b9ffeb..92937ff 100644 --- a/digital-asset-contract/setup.py +++ b/digital-asset-contract/setup.py @@ -78,6 +78,7 @@ 'pdo-client>=' + pdo_client_version, 'pdo-common-library>=' + pdo_client_version, 'pdo-sservice>=' + pdo_client_version, + 'pdo-contracts>=' + pdo_contracts_version, 'pdo-exchange>=' + pdo_contracts_version, ], entry_points = { diff --git a/exchange-contract/setup.py b/exchange-contract/setup.py index c6ed613..22fe76c 100644 --- a/exchange-contract/setup.py +++ b/exchange-contract/setup.py @@ -82,6 +82,7 @@ 'pdo-client>=' + pdo_client_version, 'pdo-common-library>=' + pdo_client_version, 'pdo-sservice>=' + pdo_client_version, + 'pdo-contracts>=' + pdo_contracts_version, ], entry_points = { 'console_scripts' : [ diff --git a/inference-contract/setup.py b/inference-contract/setup.py index bedf4f3..71e5095 100644 --- a/inference-contract/setup.py +++ b/inference-contract/setup.py @@ -94,6 +94,7 @@ 'pdo-client>=' + pdo_client_version, 'pdo-common-library>=' + pdo_client_version, 'pdo-sservice>=' + pdo_client_version, + 'pdo-contracts>=' + pdo_contracts_version, 'pdo-exchange>=' + pdo_contracts_version, ], entry_points = { From 8584400f30559fbdc71043a76bc280bded57b3f6 Mon Sep 17 00:00:00 2001 From: Mic Bowman Date: Tue, 17 Sep 2024 14:40:22 -0600 Subject: [PATCH 4/7] Code commit for example contract family Initial commit for the example contract family. The example contract family is primarily used as a means of documenting the process of writing, building, and extending a contract family. In addition, the example contract family provides a template for building a new contract family. Copying the example contract family to a new directory and editing family.cmake should be sufficient to create a new family. Signed-off-by: Mic Bowman --- example-contract/CMakeLists.txt | 75 ++++++++ example-contract/MANIFEST | 12 ++ example-contract/MANIFEST.in | 4 + example-contract/context/counter.toml | 8 + example-contract/etc/example.toml | 5 + example-contract/family.cmake | 18 ++ example-contract/methods.cmake | 36 ++++ example-contract/pdo/__init__.py | 15 ++ example-contract/pdo/example/__init__.py | 15 ++ .../pdo/example/plugins/__init__.py | 15 ++ .../pdo/example/plugins/counter.py | 168 +++++++++++++++++ .../pdo/example/resources/__init__.py | 15 ++ .../pdo/example/resources/resources.py | 18 ++ .../pdo/example/scripts/__init__.py | 15 ++ .../pdo/example/scripts/scripts.py | 24 +++ example-contract/scripts/.keep | 0 example-contract/setup.py | 104 +++++++++++ example-contract/src/common/.keep | 0 example-contract/src/contracts/counter.cpp | 35 ++++ example-contract/src/contracts/counter.h | 40 ++++ example-contract/src/example/counter.h | 37 ++++ example-contract/src/methods/counter.cpp | 85 +++++++++ example-contract/src/packages/.keep | 0 example-contract/test/.gitignore | 1 + example-contract/test/README.md | 35 ++++ example-contract/test/functional_test.psh | 92 ++++++++++ example-contract/test/run_tests.sh | 159 ++++++++++++++++ example-contract/test/script_test.sh | 171 ++++++++++++++++++ 28 files changed, 1202 insertions(+) create mode 100644 example-contract/CMakeLists.txt create mode 100644 example-contract/MANIFEST create mode 100644 example-contract/MANIFEST.in create mode 100644 example-contract/context/counter.toml create mode 100644 example-contract/etc/example.toml create mode 100644 example-contract/family.cmake create mode 100644 example-contract/methods.cmake create mode 100644 example-contract/pdo/__init__.py create mode 100644 example-contract/pdo/example/__init__.py create mode 100644 example-contract/pdo/example/plugins/__init__.py create mode 100644 example-contract/pdo/example/plugins/counter.py create mode 100644 example-contract/pdo/example/resources/__init__.py create mode 100644 example-contract/pdo/example/resources/resources.py create mode 100644 example-contract/pdo/example/scripts/__init__.py create mode 100644 example-contract/pdo/example/scripts/scripts.py create mode 100644 example-contract/scripts/.keep create mode 100644 example-contract/setup.py create mode 100644 example-contract/src/common/.keep create mode 100644 example-contract/src/contracts/counter.cpp create mode 100644 example-contract/src/contracts/counter.h create mode 100644 example-contract/src/example/counter.h create mode 100644 example-contract/src/methods/counter.cpp create mode 100644 example-contract/src/packages/.keep create mode 100644 example-contract/test/.gitignore create mode 100644 example-contract/test/README.md create mode 100755 example-contract/test/functional_test.psh create mode 100755 example-contract/test/run_tests.sh create mode 100755 example-contract/test/script_test.sh diff --git a/example-contract/CMakeLists.txt b/example-contract/CMakeLists.txt new file mode 100644 index 0000000..3f711ed --- /dev/null +++ b/example-contract/CMakeLists.txt @@ -0,0 +1,75 @@ +# Copyright 2019 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ----------------------------------------------------------------- +# If you are building an independent contract family using the +# standard paths, you should not need to change anything below. +# Information about the contract family should be set in family.cmake +# ----------------------------------------------------------------- +INCLUDE(family.cmake) +INCLUDE(methods.cmake) + +# ----------------------------------------------------------------- +# Build the library of methods that are defined by the contract +# family. This simplifies sharing the methods with other contract +# families. +# ----------------------------------------------------------------- +ADD_LIBRARY(${${CF_HANDLE}_LIB} STATIC ${${CF_HANDLE}_SOURCES}) +TARGET_INCLUDE_DIRECTORIES(${${CF_HANDLE}_LIB} PUBLIC ${${CF_HANDLE}_INCLUDES}) +TARGET_INCLUDE_DIRECTORIES(${${CF_HANDLE}_LIB} PUBLIC ${WASM_INCLUDES}) + +SET_PROPERTY(TARGET ${${CF_HANDLE}_LIB} APPEND_STRING PROPERTY COMPILE_OPTIONS "${WASM_BUILD_OPTIONS}") +SET_PROPERTY(TARGET ${${CF_HANDLE}_LIB} APPEND_STRING PROPERTY LINK_OPTIONS "${WASM_LINK_OPTIONS}") +SET_TARGET_PROPERTIES(${${CF_HANDLE}_LIB} PROPERTIES EXCLUDE_FROM_ALL TRUE) + +# ----------------------------------------------------------------- +# Add a build target for each of the contracts; to ensure uniqueness +# prepend the name of the contract family to the name of the contract +# ----------------------------------------------------------------- +FOREACH(contract ${CF_CONTRACTS}) + BUILD_CONTRACT(${CF_NAME}_${contract} src/contracts/${contract}.cpp + HEADERS ${${CF_HANDLE}_INCLUDES} + LIBRARIES ${${CF_HANDLE}_LIB} + ) +ENDFOREACH() + +# ----------------------------------------------------------------- +# Build the python wheel that will install all of the contracts in the +# contract family; the contract targets must match the targets used +# to create the contracts above. +# ----------------------------------------------------------------- +INCLUDE(Python) +LIST(TRANSFORM CF_CONTRACTS PREPEND ${CF_NAME}_ OUTPUT_VARIABLE expanded_contracts) +BUILD_WHEEL(${CF_NAME} ${expanded_contracts}) + +# ----------------------------------------------------------------- +INCLUDE(Test) +ADD_SHELL_TEST(${CF_NAME} script SCRIPT test/script_test.sh) +ADD_SHELL_TEST(${CF_NAME} functional SCRIPT test/run_tests.sh) + +# ----------------------------------------------------------------- +# install the jupyter notebooks, note that the trailing slash here +# is significant and should not be removed; it prevents the notebooks +# directory being prepended to the copied name +# ----------------------------------------------------------------- +INCLUDE(Jupyter) +FILE(GLOB_RECURSE NOTEBOOK_SOURCES docs/notebooks/*.py docs/notebooks/*.md) + +CONVERT_JUPYTEXT(EX_NOTEBOOKS ${NOTEBOOK_SOURCES}) + +ADD_CUSTOM_TARGET(${CF_NAME}-notebooks ALL DEPENDS ${EX_NOTEBOOKS}) + +INSTALL(DIRECTORY docs/notebooks/ + DESTINATION "${PDO_JUPYTER_ROOT}/${CF_NAME}" + FILES_MATCHING PATTERN "*.ipynb" PATTERN "*.png") diff --git a/example-contract/MANIFEST b/example-contract/MANIFEST new file mode 100644 index 0000000..f04b19f --- /dev/null +++ b/example-contract/MANIFEST @@ -0,0 +1,12 @@ +MANIFEST.in +setup.py +context/counter.toml +etc/example.toml +pdo/__init__.py +pdo/example/__init__.py +pdo/example/plugins/__init__.py +pdo/example/plugins/counter.py +pdo/example/resources/__init__.py +pdo/example/resources/resources.py +pdo/example/scripts/__init__.py +pdo/example/scripts/scripts.py diff --git a/example-contract/MANIFEST.in b/example-contract/MANIFEST.in new file mode 100644 index 0000000..dfe4b55 --- /dev/null +++ b/example-contract/MANIFEST.in @@ -0,0 +1,4 @@ +recursive-include ../build/example-contract *.b64 +recursive-include etc *.toml +recursive-include context *.toml +recursive-include scripts *.psh \ No newline at end of file diff --git a/example-contract/context/counter.toml b/example-contract/context/counter.toml new file mode 100644 index 0000000..848755a --- /dev/null +++ b/example-contract/context/counter.toml @@ -0,0 +1,8 @@ +# ----------------------------------------------------------------- +# counter ${counter} +# ----------------------------------------------------------------- +[counter.${counter}] +module = "pdo.example.plugins.counter" +identity = "user1" +source = "${ContractFamily.Example.counter.source}" +name = "${counter}" diff --git a/example-contract/etc/example.toml b/example-contract/etc/example.toml new file mode 100644 index 0000000..58c842a --- /dev/null +++ b/example-contract/etc/example.toml @@ -0,0 +1,5 @@ +# ----------------------------------------------------------------- +# Example contract family contract source +# ----------------------------------------------------------------- +[ContractFamily.Example] +counter = { source = "${home}/contracts/example/_example_counter.b64" } diff --git a/example-contract/family.cmake b/example-contract/family.cmake new file mode 100644 index 0000000..99beae4 --- /dev/null +++ b/example-contract/family.cmake @@ -0,0 +1,18 @@ +# Copyright 2019 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +SET(CF_NAME example) +SET(CF_CONTRACTS counter) + +STRING(TOUPPER ${CF_NAME} CF_HANDLE) diff --git a/example-contract/methods.cmake b/example-contract/methods.cmake new file mode 100644 index 0000000..eb111ee --- /dev/null +++ b/example-contract/methods.cmake @@ -0,0 +1,36 @@ +# Copyright 2022 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDE(family.cmake) + +# --------------------------------------------- +# Set up the include list +# --------------------------------------------- +SET (${CF_HANDLE}_INCLUDES ${WASM_INCLUDES}) +LIST(APPEND ${CF_HANDLE}_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src) + +# --------------------------------------------- +# Set up the default source list +# --------------------------------------------- +FILE(GLOB ${CF_HANDLE}_COMMON_SOURCE ${CMAKE_CURRENT_LIST_DIR}/src/common/*.cpp) +FILE(GLOB ${CF_HANDLE}_CONTRACT_SOURCE ${CMAKE_CURRENT_LIST_DIR}/src/methods/*.cpp) + +SET (${CF_HANDLE}_SOURCES PARENT_SCOPE) +LIST(APPEND ${CF_HANDLE}_SOURCES ${${CF_HANDLE}_COMMON_SOURCE}) +LIST(APPEND ${CF_HANDLE}_SOURCES ${${CF_HANDLE}_CONTRACT_SOURCE}) + +# --------------------------------------------- +# Build the wawaka contract common library +# --------------------------------------------- +SET(${CF_HANDLE}_LIB ww_${CF_NAME}) diff --git a/example-contract/pdo/__init__.py b/example-contract/pdo/__init__.py new file mode 100644 index 0000000..25bb107 --- /dev/null +++ b/example-contract/pdo/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2018 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__import__('pkg_resources').declare_namespace('pdo') diff --git a/example-contract/pdo/example/__init__.py b/example-contract/pdo/example/__init__.py new file mode 100644 index 0000000..a86eef8 --- /dev/null +++ b/example-contract/pdo/example/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2018 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__all__ = [ 'plugins', 'resources', 'scripts' ] diff --git a/example-contract/pdo/example/plugins/__init__.py b/example-contract/pdo/example/plugins/__init__.py new file mode 100644 index 0000000..e93a2d9 --- /dev/null +++ b/example-contract/pdo/example/plugins/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2022 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__all__ = [ 'counter' ] diff --git a/example-contract/pdo/example/plugins/counter.py b/example-contract/pdo/example/plugins/counter.py new file mode 100644 index 0000000..17214e6 --- /dev/null +++ b/example-contract/pdo/example/plugins/counter.py @@ -0,0 +1,168 @@ +# Copyright 2018 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging + +from pdo.contract import invocation_request + +import pdo.client.builder as pbuilder +import pdo.client.builder.command as pcommand +import pdo.client.builder.contract as pcontract +import pdo.client.builder.shell as pshell +import pdo.client.commands.contract as pcontract_cmd + +logger = logging.getLogger(__name__) + +__all__ = [ + 'op_inc_value', + 'op_get_value', + 'cmd_create_counter', + 'cmd_inc_value', + 'cmd_get_value', + 'do_example_counter', + 'do_example_counter_contract', + 'load_commands', +] + +logger = logging.getLogger(__name__) + +## ----------------------------------------------------------------- +class op_inc_value(pcontract.contract_op_base) : + name = "inc_value" + help = "Increment the value of the counter by 1" + + @classmethod + def invoke(cls, state, session_params, **kwargs) : + message = invocation_request('inc_value') + result = pcontract_cmd.send_to_contract(state, message, **session_params) + return result + +## ----------------------------------------------------------------- +class op_get_value(pcontract.contract_op_base) : + name = "get_value" + help = "Get the current value of the counter" + + @classmethod + def invoke(cls, state, session_params, **kwargs) : + message = invocation_request('get_value') + result = pcontract_cmd.send_to_contract(state, message, **session_params) + return result + +# ----------------------------------------------------------------- +# create a counter +# ----------------------------------------------------------------- +class cmd_create_counter(pcommand.contract_command_base) : + """Create a counter + """ + name = "create" + help = "create a counter" + + @classmethod + def add_arguments(cls, parser) : + parser.add_argument('-c', '--contract-class', help='Name of the contract class', type=str) + parser.add_argument('-e', '--eservice-group', help='Name of the enclave service group to use', type=str) + parser.add_argument('-f', '--save-file', help='File where contract data is stored', type=str) + parser.add_argument('-p', '--pservice-group', help='Name of the provisioning service group to use', type=str) + parser.add_argument('-r', '--sservice-group', help='Name of the storage service group to use', type=str) + parser.add_argument('--source', help='File that contains contract source code', type=str) + parser.add_argument('--extra', help='Extra data associated with the contract file', nargs=2, action='append') + + @classmethod + def invoke(cls, state, context, **kwargs) : + if pcontract_cmd.get_contract_from_context(state, context) : + return + + # create the counter contract + save_file = pcontract_cmd.create_contract_from_context(state, context, 'example_counter', **kwargs) + context['save_file'] = save_file + + return save_file + +# ----------------------------------------------------------------- +# increment a counter +# ----------------------------------------------------------------- +class cmd_inc_value(pcommand.contract_command_base) : + """Increment the value of a counter + """ + + name = "inc_value" + help = "increment the counter" + + @classmethod + def invoke(cls, state, context, **kwargs) : + + save_file = pcontract_cmd.get_contract_from_context(state, context) + if not save_file : + raise ValueError('counter contract must be created and initialized') + + session = pbuilder.SessionParameters(save_file=save_file) + result = pcontract.invoke_contract_op( + op_inc_value, + state, context, session, + **kwargs) + + cls.display('current value of the counter is {}'.format(result)) + return result + +# ----------------------------------------------------------------- +# get the value of a counter +# ----------------------------------------------------------------- +class cmd_get_value(pcommand.contract_command_base) : + """Get the value of a counter + """ + + name = "get_value" + help = "get the current value of the counter" + + @classmethod + def invoke(cls, state, context, **kwargs) : + + save_file = pcontract_cmd.get_contract_from_context(state, context) + if not save_file : + raise ValueError('counter contract must be created and initialized') + + session = pbuilder.SessionParameters(save_file=save_file) + result = pcontract.invoke_contract_op( + op_get_value, + state, context, session, + **kwargs) + + cls.display('current value of the counter is {}'.format(result)) + return result + + +## ----------------------------------------------------------------- +## Create the generic, shell independent version of the aggregate command +## ----------------------------------------------------------------- +__operations__ = [ + op_inc_value, + op_get_value, +] + +do_example_counter_contract = pcontract.create_shell_command('example_counter_contract', __operations__) + +__commands__ = [ + cmd_create_counter, + cmd_inc_value, + cmd_get_value, +] + +do_example_counter = pcommand.create_shell_command('example_counter', __commands__) + +## ----------------------------------------------------------------- +## Enable binding of the shell independent version to a pdo-shell command +## ----------------------------------------------------------------- +def load_commands(cmdclass) : + pshell.bind_shell_command(cmdclass, 'example_counter', do_example_counter) + pshell.bind_shell_command(cmdclass, 'example_counter_contract', do_example_counter_contract) diff --git a/example-contract/pdo/example/resources/__init__.py b/example-contract/pdo/example/resources/__init__.py new file mode 100644 index 0000000..7cf1870 --- /dev/null +++ b/example-contract/pdo/example/resources/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2023 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__all__ = ['resources'] diff --git a/example-contract/pdo/example/resources/resources.py b/example-contract/pdo/example/resources/resources.py new file mode 100644 index 0000000..8a432c2 --- /dev/null +++ b/example-contract/pdo/example/resources/resources.py @@ -0,0 +1,18 @@ +# Copyright 2023 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pdo.client.builder.installer as pinstaller + +def install_example_resources() : + pinstaller.install_plugin_resources('pdo.example.resources', 'example') diff --git a/example-contract/pdo/example/scripts/__init__.py b/example-contract/pdo/example/scripts/__init__.py new file mode 100644 index 0000000..e850a69 --- /dev/null +++ b/example-contract/pdo/example/scripts/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2022 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__all__ = [ 'scripts' ] diff --git a/example-contract/pdo/example/scripts/scripts.py b/example-contract/pdo/example/scripts/scripts.py new file mode 100644 index 0000000..37028ab --- /dev/null +++ b/example-contract/pdo/example/scripts/scripts.py @@ -0,0 +1,24 @@ +# Copyright 2022 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pdo.client.builder.shell import run_shell_command + +import warnings +warnings.catch_warnings() +warnings.simplefilter("ignore") + +# ----------------------------------------------------------------- +# ----------------------------------------------------------------- +def example_counter() : + run_shell_command('do_example_counter', 'pdo.example.plugins.counter') diff --git a/example-contract/scripts/.keep b/example-contract/scripts/.keep new file mode 100644 index 0000000..e69de29 diff --git a/example-contract/setup.py b/example-contract/setup.py new file mode 100644 index 0000000..62a189f --- /dev/null +++ b/example-contract/setup.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python + +# Copyright 2022 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import subprocess +import warnings + +## ----------------------------------------------------------------- +# Change values of the following variables to customize your +# contract family; the contract_scripts variable is a list of +# bash commands that will be created in the python wheel. See +# ./pdo/{contract_family}/scripts/scripts.py for more information +# on building command line scripts. +## ----------------------------------------------------------------- +contract_family = 'example' +contract_scripts = [ 'example_counter' ] + +author = 'Mic Bowman, Intel Labs' +author_email = 'mic.bowman@intel.com' +author_url = 'http://www.intel.com' + +## ----------------------------------------------------------------- +# Unless you change the standard behavior or layout of the contract +# there should be no changes required below +## ----------------------------------------------------------------- + +# this should only be run with python3 +if sys.version_info[0] < 3: + print('ERROR: must run with python3') + sys.exit(1) + +from setuptools import setup + +## ----------------------------------------------------------------- +# Versions are tied to tags on the repository; to compute correctly +# it is necessary to be within the repository itself hence the need +# to set the cwd for the bin/get_version command. +## ----------------------------------------------------------------- +root_dir = os.path.dirname(os.path.realpath(__file__)) +try : + pdo_contracts_version = subprocess.check_output( + 'bin/get_version', cwd=os.path.join(root_dir, os.pardir)).decode('ascii').strip() +except Exception as e : + warnings.warn(f'Failed to get pdo_contracts version, using the default; {e}') + pdo_contracts_version = '0.0.0' + +try : + pdo_client_version = subprocess.check_output( + 'bin/get_version', cwd=os.path.join(root_dir, os.pardir, 'private-data-objects')).decode('ascii').strip() +except Exception as e : + warnings.warn(f'Failed to get pdo_client version, using the default; {e}') + pdo_client_version = '0.0.0' + +## ----------------------------------------------------------------- +## ----------------------------------------------------------------- +setup( + name=f'pdo_{contract_family}', + version=pdo_contracts_version, + description='Contract and support scripts for a PDO contract', + author=author, + author_email=author_email, + url=author_url, + package_dir = { + 'pdo' : 'pdo', + f'pdo.{contract_family}.resources.etc' : 'etc', + f'pdo.{contract_family}.resources.context' : 'context', + f'pdo.{contract_family}.resources.contracts' : f'../build/{contract_family}-contract', + }, + packages = [ + 'pdo', + f'pdo.{contract_family}', + f'pdo.{contract_family}.jupyter', + f'pdo.{contract_family}.plugins', + f'pdo.{contract_family}.scripts', + f'pdo.{contract_family}.resources', + f'pdo.{contract_family}.resources.etc', + f'pdo.{contract_family}.resources.context', + f'pdo.{contract_family}.resources.contracts', + ], + include_package_data=True, + install_requires = [ + 'colorama', + 'pdo-client>=' + pdo_client_version, + 'pdo-common-library>=' + pdo_client_version, + 'pdo-contracts>=' + pdo_contracts_version, + ], + entry_points = { + 'console_scripts' : list(map(lambda s : f'{s}=pdo.{contract_family}.scripts.scripts:{s}', contract_scripts)), + } +) diff --git a/example-contract/src/common/.keep b/example-contract/src/common/.keep new file mode 100644 index 0000000..e69de29 diff --git a/example-contract/src/contracts/counter.cpp b/example-contract/src/contracts/counter.cpp new file mode 100644 index 0000000..03d7a6f --- /dev/null +++ b/example-contract/src/contracts/counter.cpp @@ -0,0 +1,35 @@ +/* Copyright 2019 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Dispatch.h" + +#include "Environment.h" +#include "Response.h" + +#include "example/counter.h" + +// ----------------------------------------------------------------- +// NAME: initialize_contract +// ----------------------------------------------------------------- +bool initialize_contract(const Environment& env, Response& rsp) +{ + return ww::example_contract::counter::initialize_contract(env, rsp); +} + +contract_method_reference_t contract_method_dispatch_table[] = { + CONTRACT_METHOD2(inc_value, ww::example_contract::counter::inc_value), + CONTRACT_METHOD2(get_value, ww::example_contract::counter::get_value), + { NULL, NULL } +}; diff --git a/example-contract/src/contracts/counter.h b/example-contract/src/contracts/counter.h new file mode 100644 index 0000000..6379b75 --- /dev/null +++ b/example-contract/src/contracts/counter.h @@ -0,0 +1,40 @@ +/* Copyright 2019 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "Environment.h" +#include "Message.h" +#include "Response.h" +#include "Util.h" + +namespace ww +{ +namespace example +{ +namespace counter +{ + bool initialize_contract(const Environment& env, Response& rsp); + + // capability handlers, these must be executed through the process + // capability interface + bool inc_value(const Message& msg, const Environment& env, Response& rsp); + bool get_value(const Message& msg, const Environment& env, Response& rsp); +} /* counter */ +} /* example */ +} /* ww + */ diff --git a/example-contract/src/example/counter.h b/example-contract/src/example/counter.h new file mode 100644 index 0000000..e1f1a70 --- /dev/null +++ b/example-contract/src/example/counter.h @@ -0,0 +1,37 @@ +/* Copyright 2019 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "Environment.h" +#include "Message.h" +#include "Response.h" + +namespace ww +{ +namespace example_contract +{ +namespace counter +{ + bool initialize_contract(const Environment& env, Response& rsp); + + bool inc_value(const Message& msg, const Environment& env, Response& rsp); + bool get_value(const Message& msg, const Environment& env, Response& rsp); +} /* counter */ +} /* example_contract */ +} /* ww + */ diff --git a/example-contract/src/methods/counter.cpp b/example-contract/src/methods/counter.cpp new file mode 100644 index 0000000..c1e6964 --- /dev/null +++ b/example-contract/src/methods/counter.cpp @@ -0,0 +1,85 @@ +/* Copyright 2019 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "Dispatch.h" + +#include "KeyValue.h" +#include "Environment.h" +#include "Message.h" +#include "Response.h" +#include "Util.h" +#include "Value.h" +#include "WasmExtensions.h" + +#include "example/counter.h" + +static KeyValueStore meta_store("meta"); +static KeyValueStore value_store("values"); + +const std::string counter_key("counter"); + +// ----------------------------------------------------------------- +// NAME: initialize_contract +// ----------------------------------------------------------------- +bool ww::example_contract::counter::initialize_contract(const Environment& env, Response& rsp) +{ + // create the value and save it to state + const uint32_t value = 0; + + if (! value_store.set(counter_key, value)) + return rsp.error("failed to create the test key"); + + return rsp.success(true); +} + +// ----------------------------------------------------------------- +// NAME: inc_value +// ----------------------------------------------------------------- +bool ww::example_contract::counter::inc_value(const Message& msg, const Environment& env, Response& rsp) +{ + ASSERT_SENDER_IS_CREATOR(env, rsp); + + // get the value and increment it + uint32_t value; + if (! value_store.get(counter_key, value)) + return rsp.error("no such key"); + + value += 1; + if (! value_store.set(counter_key, value)) + return rsp.error("failed to save the new value"); + + ww::value::Number v((double)value); + return rsp.value(v, true); +} + +// ----------------------------------------------------------------- +// NAME: get_value +// ----------------------------------------------------------------- +bool ww::example_contract::counter::get_value(const Message& msg, const Environment& env, Response& rsp) +{ + ASSERT_SENDER_IS_CREATOR(env, rsp); + + // get the value + uint32_t value; + if (! value_store.get(counter_key, value)) + return rsp.error("no such key"); + + ww::value::Number v((double)value); + return rsp.value(v, false); +} diff --git a/example-contract/src/packages/.keep b/example-contract/src/packages/.keep new file mode 100644 index 0000000..e69de29 diff --git a/example-contract/test/.gitignore b/example-contract/test/.gitignore new file mode 100644 index 0000000..eb0abc5 --- /dev/null +++ b/example-contract/test/.gitignore @@ -0,0 +1 @@ +test_context.toml diff --git a/example-contract/test/README.md b/example-contract/test/README.md new file mode 100644 index 0000000..92ee672 --- /dev/null +++ b/example-contract/test/README.md @@ -0,0 +1,35 @@ + +# Example Test Scripts + +This directory contains a number of pdo-shell scripts to test the +digital asset contract family. The scripts assume that a complete +installation of the PDO client is complete. + +## Test Scripts + +Test scripts take several parameters that can override the standard +PDO environment variables: + +* `--host` : Specify the host where the PDO services operate, override `PDO_HOSTNAME` +* `--ledger` : Specify the URL for the PDO ledger, override `PDO_LEDGER_URL` +* `--loglevel` : Specify logging verbosity +* `--logfile` : Specify the logging output file + +### `run_tests.sh` + +This script sets up and runs the functional test suite for the digital asset +contract family. The actual tests will be found in the pdo-shell script +`functional_test.psh` + +### `script_test.sh` + +This script tests the `bash` entry points for digital asset plugins. + +### `functional_test.psh` + +This script provides a functional test of the various contract +types in the digital asset contract family. In general, this should +not be invoked directly but should be called through `run-tests.sh`. diff --git a/example-contract/test/functional_test.psh b/example-contract/test/functional_test.psh new file mode 100755 index 0000000..6f12c1e --- /dev/null +++ b/example-contract/test/functional_test.psh @@ -0,0 +1,92 @@ +#! /usr/bin/env pdo-shell + +## Copyright 2022 Intel Corporation +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. + +## Two shell variables are used: +## data -- the directory where the contract objects are stored +## path -- the directory where the PSH scripts are stored +## +## $ pdo-shell -s create.psh -m path + +set --conditional -s home -v . +set --conditional -s data -v . +set --conditional -s save -v . +set --conditional -s contracts -v ${home}/contracts/example +set --conditional -s plugins -v ${home}/contracts/plugins/example + +if -e "${_identity_}" "__unknown__" + identity -n user1 +fi + +set --conditional -s _counter1_ -v ${save}/counter1.pdo +set --conditional -s _counter2_ -v ${save}/counter2.pdo + +## some definitions to make it easier to display text +set -s ENDC -v "\033[0m" +set -s BOLD -v '\033[1m' +set -s HEADER -v "\033[95m" +set -s ERROR -v "\033[91m" +set -s WARN -v "\033[93m" +set -s INFO -v "\033[92m" + +load_plugin -m pdo.example.plugins.counter + +## ----------------------------------------------------------------- +echo ${HEADER} create the counter ${ENDC} +## ----------------------------------------------------------------- +trap_error + +contract create -c example_counter --source ${contracts}/_example_counter -f ${_counter1_} +if -o ${_error_code_} 0 + echo ${ERROR} [ERROR ${_error_code_}] failed to create the counter; ${_error_message_} + exit -v ${_error_code_} +fi + +## ----------------------------------------------------------------- +echo ${HEADER} increment the counter ${ENDC} +## ----------------------------------------------------------------- +example_counter_contract inc_value -f ${_counter1_} -s _value_ +if -o ${_error_code_} 0 + echo ${ERROR} [ERROR ${_error_code_}] failed to increment the counter; ${_error_message_} + exit -v ${_error_code_} +fi + +if --not -e ${_value_} 1 + echo ${ERROR} [ERROR ${_error_code_}] invalid initial value; ${_value_} + exit -v -1 +fi + +## repeat the increment +example_counter_contract inc_value -f ${_counter1_} -s _value_ +example_counter_contract inc_value -f ${_counter1_} -s _value_ +example_counter_contract inc_value -f ${_counter1_} -s _value_ +example_counter_contract inc_value -f ${_counter1_} -s _value_ + +## ----------------------------------------------------------------- +echo ${HEADER} get the counter value ${ENDC} +## ----------------------------------------------------------------- +example_counter_contract get_value -f ${_counter1_} -s _value_ +if -o ${_error_code_} 0 + echo ${ERROR} [ERROR ${_error_code_}] failed to get the counter value; ${_error_message_} + exit -v ${_error_code_} +fi + +if --not -e ${_value_} 5 + echo ${ERROR} [ERROR ${_error_code_}] invalid counter value; ${_value_} instead of 5 + exit -v -1 +fi + +echo ${HEADER} all counter tests passed ${ENDC} +exit diff --git a/example-contract/test/run_tests.sh b/example-contract/test/run_tests.sh new file mode 100755 index 0000000..a0e51bf --- /dev/null +++ b/example-contract/test/run_tests.sh @@ -0,0 +1,159 @@ +#!/bin/bash + +# Copyright 2018 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ----------------------------------------------------------------- +# ----------------------------------------------------------------- +: "${PDO_LEDGER_URL?Missing environment variable PDO_LEDGER_URL}" +: "${PDO_HOME?Missing environment variable PDO_HOME}" +: "${PDO_SOURCE_ROOT?Missing environment variable PDO_SOURCE_ROOT}" + +# ----------------------------------------------------------------- +# ----------------------------------------------------------------- +source ${PDO_HOME}/bin/lib/common.sh +check_python_version + +if ! command -v pdo-shell &> /dev/null ; then + die unable to locate pdo-shell +fi + +# ----------------------------------------------------------------- +# ----------------------------------------------------------------- +if [ "${PDO_LEDGER_TYPE}" == "ccf" ]; then + if [ ! -f "${PDO_LEDGER_KEY_ROOT}/networkcert.pem" ]; then + die "CCF ledger keys are missing, please copy and try again" + fi +fi + +# ----------------------------------------------------------------- +# Process command line arguments +# ----------------------------------------------------------------- +SCRIPTDIR="$(dirname $(readlink --canonicalize ${BASH_SOURCE}))" +SOURCE_ROOT="$(realpath ${SCRIPTDIR}/..)" + +F_SCRIPT=$(basename ${BASH_SOURCE[-1]} ) +F_SERVICE_HOST=${PDO_HOSTNAME} +F_LEDGER_URL=${PDO_LEDGER_URL} +F_LOGLEVEL=${PDO_LOG_LEVEL:-info} +F_LOGFILE=${PDO_LOG_FILE:-__screen__} +F_CONTEXT_FILE=${SOURCE_ROOT}/test/test_context.toml +F_CONTEXT_TEMPLATES=${PDO_HOME}/contracts/example/context + +F_USAGE='--host service-host | --ledger url | --loglevel [debug|info|warn] | --logfile file' +SHORT_OPTS='h:l:' +LONG_OPTS='host:,ledger:,loglevel:,logfile:' + +TEMP=$(getopt -o ${SHORT_OPTS} --long ${LONG_OPTS} -n "${F_SCRIPT}" -- "$@") +if [ $? != 0 ] ; then echo "Usage: ${F_SCRIPT} ${F_USAGE}" >&2 ; exit 1 ; fi + +eval set -- "$TEMP" +while true ; do + case "$1" in + -h|--host) F_SERVICE_HOST="$2" ; shift 2 ;; + -1|--ledger) F_LEDGER_URL="$2" ; shift 2 ;; + --loglevel) F_LOGLEVEL="$2" ; shift 2 ;; + --logfile) F_LOGFILE="$2" ; shift 2 ;; + --help) echo "Usage: ${SCRIPT_NAME} ${F_USAGE}"; exit 0 ;; + --) shift ; break ;; + *) echo "Internal error!" ; exit 1 ;; + esac +done + +F_SERVICE_SITE_FILE=${PDO_HOME}/etc/sites/${F_SERVICE_HOST}.toml +if [ ! -f ${F_SERVICE_SITE_FILE} ] ; then + die unable to locate the service information file ${F_SERVICE_SITE_FILE}; \ + please copy the site.toml file from the service host +fi + +F_SERVICE_GROUPS_DB_FILE=${SOURCE_ROOT}/test/${F_SERVICE_HOST}_groups_db +F_SERVICE_DB_FILE=${SOURCE_ROOT}/test/${F_SERVICE_HOST}_db + +_COMMON_=("--logfile ${F_LOGFILE}" "--loglevel ${F_LOGLEVEL}") +_COMMON_+=("--ledger ${F_LEDGER_URL}") +_COMMON_+=("--groups-db ${F_SERVICE_GROUPS_DB_FILE}") +_COMMON_+=("--service-db ${F_SERVICE_DB_FILE}") +SHORT_OPTS=${_COMMON_[@]} + +_COMMON_+=("--context-file ${F_CONTEXT_FILE}") +OPTS=${_COMMON_[@]} + +# ----------------------------------------------------------------- +# Make sure the keys and eservice database are created and up to date +# ----------------------------------------------------------------- +F_KEY_FILES=() +KEYGEN=${PDO_SOURCE_ROOT}/build/__tools__/make-keys +if [ ! -f ${PDO_HOME}/keys/red_type_private.pem ]; then + yell create keys for the contracts + for color in red green blue orange purple white ; do + ${KEYGEN} --keyfile ${PDO_HOME}/keys/${color}_type --format pem + ${KEYGEN} --keyfile ${PDO_HOME}/keys/${color}_vetting --format pem + ${KEYGEN} --keyfile ${PDO_HOME}/keys/${color}_issuer --format pem + F_KEY_FILES+=(${PDO_HOME}/keys/${color}_{type,vetting,issuer}_{private,public}.pem) + done + + for color in green1 green2 green3; do + ${KEYGEN} --keyfile ${PDO_HOME}/keys/${color}_issuer --format pem + F_KEY_FILES+=(${PDO_HOME}/keys/${color}_issuer_{private,public}.pem) + done + + ${KEYGEN} --keyfile ${PDO_HOME}/keys/token_type --format pem + ${KEYGEN} --keyfile ${PDO_HOME}/keys/token_vetting --format pem + ${KEYGEN} --keyfile ${PDO_HOME}/keys/token_issuer --format pem + F_KEY_FILES+=(${PDO_HOME}/keys/token_{type,vetting,issuer}_{private,public}.pem) + for count in 1 2 3 4 5 ; do + ${KEYGEN} --keyfile ${PDO_HOME}/keys/token_holder${count} --format pem + F_KEY_FILES+=(${PDO_HOME}/keys/token_holder${count}_{private,public}.pem) + done +fi + +# ----------------------------------------------------------------- +function cleanup { + rm -f ${F_SERVICE_GROUPS_DB_FILE} ${F_SERVICE_GROUPS_DB_FILE}-lock + rm -f ${F_SERVICE_DB_FILE} ${F_SERVICE_DB_FILE}-lock + rm -f ${F_CONTEXT_FILE} + for key_file in ${F_KEY_FILES[@]} ; do + rm -f ${key_file} + done +} + +trap cleanup EXIT + +# ----------------------------------------------------------------- +# create the service and groups databases from a site file; the site +# file is assumed to exist in ${PDO_HOME}/etc/sites/${SERVICE_HOST}.toml +# +# by default, the groups will include all available services from the +# service host +# ----------------------------------------------------------------- +yell create the service and groups database for host ${F_SERVICE_HOST} +try pdo-service-db import ${SHORT_OPTS} --file ${F_SERVICE_SITE_FILE} +try pdo-eservice create_from_site ${SHORT_OPTS} --file ${F_SERVICE_SITE_FILE} --group default +try pdo-pservice create_from_site ${SHORT_OPTS} --file ${F_SERVICE_SITE_FILE} --group default +try pdo-sservice create_from_site ${SHORT_OPTS} --file ${F_SERVICE_SITE_FILE} --group default \ + --replicas 1 --duration 60 + +# ----------------------------------------------------------------- +# ----------------------------------------------------------------- +cd "${SOURCE_ROOT}" + +rm -f ${F_CONTEXT_FILE} + +yell create the context for the counter object +try pdo-context load ${OPTS} --import-file ${F_CONTEXT_TEMPLATES}/counter.toml --bind counter mycounter + +# ----------------------------------------------------------------- +# ----------------------------------------------------------------- +cd "${SOURCE_ROOT}" +try ${SCRIPTDIR}/functional_test.psh ${OPTS} diff --git a/example-contract/test/script_test.sh b/example-contract/test/script_test.sh new file mode 100755 index 0000000..7943293 --- /dev/null +++ b/example-contract/test/script_test.sh @@ -0,0 +1,171 @@ +#!/bin/bash + +# Copyright 2022 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ----------------------------------------------------------------- +# ----------------------------------------------------------------- +: "${PDO_LEDGER_URL?Missing environment variable PDO_LEDGER_URL}" +: "${PDO_HOME?Missing environment variable PDO_HOME}" +: "${PDO_SOURCE_ROOT?Missing environment variable PDO_SOURCE_ROOT}" + +# ----------------------------------------------------------------- +# ----------------------------------------------------------------- +source ${PDO_HOME}/bin/lib/common.sh +check_python_version + +if ! command -v pdo-shell &> /dev/null ; then + die unable to locate pdo-shell +fi + +# ----------------------------------------------------------------- +# ----------------------------------------------------------------- +if [ "${PDO_LEDGER_TYPE}" == "ccf" ]; then + if [ ! -f "${PDO_LEDGER_KEY_ROOT}/networkcert.pem" ]; then + die "CCF ledger keys are missing, please copy and try again" + fi +fi + +# ----------------------------------------------------------------- +# Process command line arguments +# ----------------------------------------------------------------- +SCRIPTDIR="$(dirname $(readlink --canonicalize ${BASH_SOURCE}))" +SOURCE_ROOT="$(realpath ${SCRIPTDIR}/..)" + +F_SCRIPT=$(basename ${BASH_SOURCE[-1]} ) +F_SERVICE_HOST=${PDO_HOSTNAME} +F_LEDGER_URL=${PDO_LEDGER_URL} +F_LOGLEVEL=${PDO_LOG_LEVEL:-info} +F_LOGFILE=${PDO_LOG_FILE:-__screen__} +F_CONTEXT_FILE=${SOURCE_ROOT}/test/test_context.toml +F_CONTEXT_TEMPLATES=${PDO_HOME}/contracts/example/context + +F_USAGE='--host service-host | --ledger url | --loglevel [debug|info|warn] | --logfile file' +SHORT_OPTS='h:l:' +LONG_OPTS='host:,ledger:,loglevel:,logfile:' + +TEMP=$(getopt -o ${SHORT_OPTS} --long ${LONG_OPTS} -n "${F_SCRIPT}" -- "$@") +if [ $? != 0 ] ; then echo "Usage: ${F_SCRIPT} ${F_USAGE}" >&2 ; exit 1 ; fi + +eval set -- "$TEMP" +while true ; do + case "$1" in + -h|--host) F_SERVICE_HOST="$2" ; shift 2 ;; + -1|--ledger) F_LEDGER_URL="$2" ; shift 2 ;; + --loglevel) F_LOGLEVEL="$2" ; shift 2 ;; + --logfile) F_LOGFILE="$2" ; shift 2 ;; + --help) echo "Usage: ${SCRIPT_NAME} ${F_USAGE}"; exit 0 ;; + --) shift ; break ;; + *) echo "Internal error!" ; exit 1 ;; + esac +done + +F_SERVICE_SITE_FILE=${PDO_HOME}/etc/sites/${F_SERVICE_HOST}.toml +if [ ! -f ${F_SERVICE_SITE_FILE} ] ; then + die unable to locate the service information file ${F_SERVICE_SITE_FILE}; \ + please copy the site.toml file from the service host +fi + +F_SERVICE_GROUPS_DB_FILE=${SOURCE_ROOT}/test/${F_SERVICE_HOST}_groups_db +F_SERVICE_DB_FILE=${SOURCE_ROOT}/test/${F_SERVICE_HOST}_db + +_COMMON_=("--logfile ${F_LOGFILE}" "--loglevel ${F_LOGLEVEL}") +_COMMON_+=("--ledger ${F_LEDGER_URL}") +_COMMON_+=("--groups-db ${F_SERVICE_GROUPS_DB_FILE}") +_COMMON_+=("--service-db ${F_SERVICE_DB_FILE}") +SHORT_OPTS=${_COMMON_[@]} + +_COMMON_+=("--context-file ${F_CONTEXT_FILE}") +OPTS=${_COMMON_[@]} + +# ----------------------------------------------------------------- +# Make sure the keys and eservice database are created and up to date +# ----------------------------------------------------------------- +F_KEY_FILES=() +KEYGEN=${PDO_SOURCE_ROOT}/build/__tools__/make-keys +if [ ! -f ${PDO_HOME}/keys/red_type_private.pem ]; then + yell create keys for the contracts + for color in red green blue orange purple white ; do + ${KEYGEN} --keyfile ${PDO_HOME}/keys/${color}_type --format pem + ${KEYGEN} --keyfile ${PDO_HOME}/keys/${color}_vetting --format pem + ${KEYGEN} --keyfile ${PDO_HOME}/keys/${color}_issuer --format pem + F_KEY_FILES+=(${PDO_HOME}/keys/${color}_{type,vetting,issuer}_{private,public}.pem) + done + + for color in green1 green2 green3; do + ${KEYGEN} --keyfile ${PDO_HOME}/keys/${color}_issuer --format pem + F_KEY_FILES+=(${PDO_HOME}/keys/${color}_issuer_{private,public}.pem) + done + + ${KEYGEN} --keyfile ${PDO_HOME}/keys/token_type --format pem + ${KEYGEN} --keyfile ${PDO_HOME}/keys/token_vetting --format pem + ${KEYGEN} --keyfile ${PDO_HOME}/keys/token_issuer --format pem + F_KEY_FILES+=(${PDO_HOME}/keys/token_{type,vetting,issuer}_{private,public}.pem) + for count in 1 2 3 4 5 ; do + ${KEYGEN} --keyfile ${PDO_HOME}/keys/token_holder${count} --format pem + F_KEY_FILES+=(${PDO_HOME}/keys/token_holder${count}_{private,public}.pem) + done +fi + +# ----------------------------------------------------------------- +function cleanup { + rm -f ${F_SERVICE_GROUPS_DB_FILE} ${F_SERVICE_GROUPS_DB_FILE}-lock + rm -f ${F_SERVICE_DB_FILE} ${F_SERVICE_DB_FILE}-lock + rm -f ${F_CONTEXT_FILE} + for key_file in ${F_KEY_FILES[@]} ; do + rm -f ${key_file} + done +} + +trap cleanup EXIT + +# ----------------------------------------------------------------- +# create the service and groups databases from a site file; the site +# file is assumed to exist in ${PDO_HOME}/etc/sites/${SERVICE_HOST}.toml +# +# by default, the groups will include all available services from the +# service host +# ----------------------------------------------------------------- +yell create the service and groups database for host ${F_SERVICE_HOST} +try pdo-service-db import ${SHORT_OPTS} --file ${F_SERVICE_SITE_FILE} +try pdo-eservice create_from_site ${SHORT_OPTS} --file ${F_SERVICE_SITE_FILE} --group default +try pdo-pservice create_from_site ${SHORT_OPTS} --file ${F_SERVICE_SITE_FILE} --group default +try pdo-sservice create_from_site ${SHORT_OPTS} --file ${F_SERVICE_SITE_FILE} --group default \ + --replicas 1 --duration 60 + +# ----------------------------------------------------------------- +# setup the contexts that will be used later for the tests +# ----------------------------------------------------------------- +cd "${SOURCE_ROOT}" + +rm -f ${F_CONTEXT_FILE} + +yell create the context for the counter object +try pdo-context load ${OPTS} --import-file ${F_CONTEXT_TEMPLATES}/counter.toml --bind counter mycounter + +# ----------------------------------------------------------------- +# start the tests +# ----------------------------------------------------------------- +yell create a counter +try example_counter create ${OPTS} --contract counter.mycounter + +yell increment the counter +try example_counter inc_value ${OPTS} --contract counter.mycounter +try example_counter inc_value ${OPTS} --contract counter.mycounter +try example_counter inc_value ${OPTS} --contract counter.mycounter +try example_counter inc_value ${OPTS} --contract counter.mycounter +try example_counter inc_value ${OPTS} --contract counter.mycounter + +yell get the value of the counter +try example_counter get_value ${OPTS} --contract counter.mycounter From 9dc0035e4bdbc070b3095e803b5181e32c181dd0 Mon Sep 17 00:00:00 2001 From: Mic Bowman Date: Tue, 17 Sep 2024 14:44:31 -0600 Subject: [PATCH 5/7] step by step directions to build, install, and modify the example contract family Signed-off-by: Mic Bowman --- example-contract/README.md | 267 +++++++++++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 example-contract/README.md diff --git a/example-contract/README.md b/example-contract/README.md new file mode 100644 index 0000000..572266a --- /dev/null +++ b/example-contract/README.md @@ -0,0 +1,267 @@ + + +**The protocols and software are for reference purposes only and not intended for production usage.** + +# Example Contract Family # + +This directory contains an example contract family that can be used as +the basis for implementing your own contract families. This file will +walk you through the process of creating a new contract family. + +Note that this description is not intended to be prescriptive. There +are many ways to build and deploy a contract. This directory simply +describes one way that we have found relatively easy to use. + +# What is a Contract Family? # + +A contract family is a collection of PDO contracts that work together +to create an "application". For example, the contracts in the Exchange +contract family, provide contracts to create digital asset like "red +marbles" with a veriable trust chain and contracts to use those assets +to purchase or exchange goods, services, or other assets. It is the +collection of contracts intentionally designed to work together that +we call a contract family. + +Operationally, a contract family consists of a set of PDO contracts +and instructions for how to build them, configuration files that help +to specify the relationship between contracts or contract objects, and +plugins that makes it easier to use the contracts through a PDO shell, +a bash shell, or a Jupyter notebook. + +## File System Layout ## + +* The root directory of the contract family generally contains + information useful for building and deploying contracts in the + family. + +* The `context` directory contains context files that describe the + relationship between contract objects and help to coordinate the + configuration of dependent contract objects. + +* The `src` directory contains the source files used to build the + contracts in the contract family. The source directory contains + several subdirectories: + + * The `common` directory contains modules that will be shared + amongst all of the contracts. + + * The `packages` directory may contain external dependencies + required for the contract. + + * The `methods` directory contains modules that implement different + groups of methods. For simple contracts there is generally one + method module per contract. More complex contracts may mix and + match methods from different modules. + + * The `contracts` directory contains the definition of the contracts + themselves. These are generally specified as a collection of + methods defined in the `methods` directory. + + * Finally, there is often a directory that contains interface + definitions that may be used by other contract families to re-use + methods. For example, this contract family defines an interface + for the counter contract in the `example` directory. + +* The `docs` directory generally contains interface specifications for + the methods in the contract. It may also contain the templates for + Jupyter notebooks used to interact with the contracts. + +* The `etc` directory contains basic configuration information. When + you customize your own contract family, you will need to update + information about the mapping between contract types and compiled + contract code. + +* The `pdo` directory is the root of the Python modules associated + with the contract family. The Python modules contain plugins for the + PDO and bash shells, and utility functions that can be invoked from + a Jupyter notebook. + +* The `scripts` directory may optionally contain PDO shell scripts + that will be installed with the deployed contracts. + +* The `test` directory defines system tests for the contract family + that will be automatically run by the contracts CI system. Tests are + broken into two parts, one for running a set of commands in a single + PDO shell invocation and one for running a series of bash shell + commands. Note that the two are similar but not the same. The PDO + shell will process transaction commits asynchronously while the bash + shell tests commit synchronously. + +# How to Create a New Contract Family Using the Example Template # + +The following instructions use the `example` contract family as a +template for building a new contract family that we'll call the +`myfirst` contract family. While each contract family can be +structured to your personal preferences, the `example` contract family +provides an easy way to get started. + +1. Copy the `example-contract` directory to a new directory. To be + picked up by the build system automatically, the new directory + should be named with `myfirst-contract`. + +```bash + cp -R example-contract myfirst-contract +``` + +2. Edit `myfirst-contract/family.cmake`. Replace `example` with + `myfirst` as the value of `CF_NAME`. + +3. Edit `setup.py`. Replace `example` with `myfirst` as the value of + the `contract_family` variable. Replace `example_counter` with + `myfirst_counter` as the value of the `contract_scripts` + variable. Update the author information for the Python package. + +4. Rename the `example` contract context file, the Python module + directory and the directory that contains exported header files. + +```bash + mv etc/example.toml etc/myfirst.toml + mv src/example src/myfirst + mv pdo/example pdo/myfirst +``` + +5. Replace references to `example` with `myfirst`. This will update + contract family references in several locations including the + contract source namespace in the `src` directory, the contract + references in the tests, and the plugin and resource references in + the Python modules. + +```bash +find . -type f -exec sed -i s/example/myfirst/g {} \; +find . -type f -exec sed -i s/Example/MyFirst/g {} \; +``` + +At this point, assuming you have a complete PDO client installation, +you should be able build and test your new contract family. To do +this, add the following line to `Local.cmake` in the PDO contracts +root directory: + +``` +SET(CONTRACT_FAMILIES myfirst-contract) +``` + +This will limit the build process to the new contract family to +simplify testing. + +Assuming that you have installed and configured a PDO client +environment (and have activated the PDO Python virtual environment), +then you can build and run the tests: + +```bash +make install +make test +``` + +# How to Add a New Contract to the Contract Family # + +A PDO contract consists of a set of methods that operate on a +persistent state. Methods are generally grouped together in a +namespace to make them easier to re-use for multiple contracts. The +`counter` methods module (see `src/methods/counter.cpp`) in the +example contract family defines two methods, one for incrementing the +counter and one getting the current value of the counter. The methods +module also includes a function that can be used to initialize the +contract state prior to using either method. These methods can be +imported into any contract so long as the contract invokes the +initialization function prior to invoking either of the counter +methods. + +The actual `counter` contract is defined in +`src/contracts/counter.cpp`. Effectively, the contract consists of an +initialization function that will be called when the contract object +is first created and a dispatch table for methods on the contract +object. The dispatch table maps an external name of the method (all +methods are invoked through a JSON RPC protocol defined in PDO) to the +code that implements the method. + +Note that in the `counter` contract, the `initialize_contract` +function calls the `counter` method module initialization function so +that the `counter` methods will work correctly. + +## How to Add a New Method ## + +To add a new method to the `counter` contract, simply add the code for +the method to `src/methods/counter.cpp` (or create a new method module +in that directory) and add the interface definition to the public +header file `src/example/counter.h`. For example, you could define a +new method for the `counter` method module called `dec_value` to +decrement the value of the counter. The implementation of the method +would be placed in the `counter` method module and the interface added +to the public header file. + +To include the new method in the contract, add it to the dispatch +table in the contract definition file `src/contracts/counter.cpp`. The +table uses the PDO macro for specifying the external name of the +method and the contract function that will be invoked through that +name. + +## How to Define a New Contract ## + +The following steps are taken to create a new type of contract: + +1. Define and implement any additional methods that are necessary in a + method module. Methods may also be imported from other contract + families. See the `CMakeLists.txt` file in the digital-asset + contract family for an example of how methods are imported from the + exchange contract family. + +2. Create a new contract definition file in `src/contracts`. The file + must contain a contract initialization function and a dispatch + table for the methods exported by the contract. + +3. Add the contract to the list of contracts in `family.cmake`. That + is, add the name of the new contract to the `CF_CONTRACTS` macro. + +4. Add the contract mapping to the context configuration file in the + `etc` directory. The name of the context file for the example + contract family is `etc/example.toml`. This mapping helps the + shells to identify the location of the compiled contract when a new + contract object is created. + +Assuming that you have installed and configured a PDO client +environment (and have activated the PDO Python virtual environment), +then you can build and install the new contract: + +```bash +make +make install +``` + +The new contract should be available in the `${PDO_HOME}/contracts` +directory. To test the contract effectively will require a new Python +plugin for the contract. + +# How to Define a New Shell Plugin # + +Shell plugins provide access to PDO contract method invocations +through the PDO shell, Bash shell, and other Python scripts using a +single implementation. + +Plugins come in two flavors. The first are operations derived from the +`pdo.client.builder.contract.contract_op_base` class. These operations +generally have a one-to-one correspondance to methods on the contract +object. The second are commands derived from the +`pdo.client.builder.command.contract_command_base` class. Commands are +generally used for complex operations that involve invocation of +multiple methods potentially across multiple contract objects. + +For example, the plugin for `counter` contract objects defines one +operation for each of the methods (`inc_value` and `get_value`) and a +command to create the counter (which creates the contract object and +initializes it). + +## How to Write a Plugin for a Contract ## + +The easiest way to define a plugin for a new type of contract object +is to simply copy the basic framework for the `counter` plugin and +implement an operation for each method on your new contract. If your +new contract imports methods from another contract family, it is +relatively straightforward to import the plugin operations as well. + +Examples for invoking the operations and commands is provided by the +scripts in the `test` directory. + +# Jupyter Plugins # From 1b7004dd602787968748cc96f33052ca7d2d013c Mon Sep 17 00:00:00 2001 From: Mic Bowman Date: Wed, 18 Sep 2024 08:39:04 -0600 Subject: [PATCH 6/7] Add docker support for the example contract Adds a simple, getting started target for building and running the example contract. Signed-off-by: Mic Bowman --- docker/Makefile | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docker/Makefile b/docker/Makefile index 6c69c21..f36f931 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -97,6 +97,7 @@ rebuild_contracts : repository models build_contracts : DOCKER_BUILDARGS+=--build-arg CONTRACT_FAMILIES="$(CONTRACT_FAMILIES)" build_contracts : repository models + - echo "Build contract families: $(CONTRACT_FAMILIES)" $(DOCKER_COMMAND) build $(DOCKER_ARGS) \ --tag pdo_contracts:$(CONTRACTS_VERSION) \ --file $(DOCKER_DIR)/pdo_contracts.dockerfile . @@ -240,6 +241,23 @@ test_multiuser_jupyter : clean_config clean_repository build_contracts PDO_VERSION=$(PDO_VERSION) CONTRACTS_VERSION=$(CONTRACTS_VERSION) \ $(DOCKER_COMPOSE_COMMAND) $(JUPYTER_MULTIUSER_COMPOSE_ARGS) down +# The example target builds and runs only the example contract family. This +# is intended to be a very simple way to test drive PDO contracts as a user +# and as a contract developer. +example : CONTRACT_FAMILIES=common-contract example-contract +example : clean_config clean_repository build_contracts + - PDO_VERSION=$(PDO_VERSION) CONTRACTS_VERSION=$(CONTRACTS_VERSION) \ + $(DOCKER_COMPOSE_COMMAND) $(TEST_COMPOSE_ARGS) up --abort-on-container-exit + PDO_VERSION=$(PDO_VERSION) CONTRACTS_VERSION=$(CONTRACTS_VERSION) \ + $(DOCKER_COMPOSE_COMMAND) $(TEST_COMPOSE_ARGS) down + +example_jupyter : CONTRACT_FAMILIES=common-contract example-contract +example_jupyter : clean_config clean_repository build_contracts + - PDO_VERSION=$(PDO_VERSION) CONTRACTS_VERSION=$(CONTRACTS_VERSION) \ + $(DOCKER_COMPOSE_COMMAND) $(JUPYTER_COMPOSE_ARGS) up --abort-on-container-exit + PDO_VERSION=$(PDO_VERSION) CONTRACTS_VERSION=$(CONTRACTS_VERSION) \ + $(DOCKER_COMPOSE_COMMAND) $(JUPYTER_COMPOSE_ARGS) down + # ----------------------------------------------------------------- # Cleaning is a bit interesting because the containers don't go away # unless they are told to very nicely. Until they go away they hold onto From 768421c7788d4ad0a3a7b1701a5d1f4d2b363fa1 Mon Sep 17 00:00:00 2001 From: Mic Bowman Date: Tue, 17 Sep 2024 14:45:25 -0600 Subject: [PATCH 7/7] Add Jupyter support for the example contract Adds the basic factory/template model for the example contract. This should allow for, more or less, a cut and paste for creating a new contract that preserved the counter. That is, the factory & template are for the specific contract type and would have to be replaced completely for a new contract. Signed-off-by: Mic Bowman --- example-contract/MANIFEST | 2 + example-contract/docs/notebooks/.gitignore | 1 + .../docs/notebooks/factories/counter.py | 49 ++++++++ .../docs/notebooks/templates/counter.py | 112 ++++++++++++++++++ example-contract/pdo/example/__init__.py | 2 +- .../pdo/example/jupyter/__init__.py | 18 +++ .../pdo/example/jupyter/context.py | 56 +++++++++ 7 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 example-contract/docs/notebooks/.gitignore create mode 100644 example-contract/docs/notebooks/factories/counter.py create mode 100644 example-contract/docs/notebooks/templates/counter.py create mode 100644 example-contract/pdo/example/jupyter/__init__.py create mode 100644 example-contract/pdo/example/jupyter/context.py diff --git a/example-contract/MANIFEST b/example-contract/MANIFEST index f04b19f..847af1f 100644 --- a/example-contract/MANIFEST +++ b/example-contract/MANIFEST @@ -4,6 +4,8 @@ context/counter.toml etc/example.toml pdo/__init__.py pdo/example/__init__.py +pdo/example/jupyter/__init__.py +pdo/example/jupyter/context.py pdo/example/plugins/__init__.py pdo/example/plugins/counter.py pdo/example/resources/__init__.py diff --git a/example-contract/docs/notebooks/.gitignore b/example-contract/docs/notebooks/.gitignore new file mode 100644 index 0000000..fa65608 --- /dev/null +++ b/example-contract/docs/notebooks/.gitignore @@ -0,0 +1 @@ +*.ipynb diff --git a/example-contract/docs/notebooks/factories/counter.py b/example-contract/docs/notebooks/factories/counter.py new file mode 100644 index 0000000..5b3681d --- /dev/null +++ b/example-contract/docs/notebooks/factories/counter.py @@ -0,0 +1,49 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.16.1 +# kernelspec: +# display_name: Python 3 (ipykernel) +# language: python +# name: python3 +# --- + +# %% [markdown] +# # Counter Factory +# +# This notebook simplifies the creation of counter. + +# %% +import os +import pdo.contracts.jupyter as pc_jupyter +import IPython.display as ip_display + +pc_jupyter.load_contract_families(example='example') +pc_jupyter.load_ipython_extension(get_ipython()) + +try : state +except NameError: + (state, bindings) = pc_jupyter.initialize_environment('unknown') + +# %% [markdown] +# ## Configure Counter Information +# + +# %% tags=["parameters"] +counter_name = input('Name of the counter: ') +identity = input('Identity to use to create the counter [user1]: ') or "user1" +service_group = input('Service group [default]: ') or "default" + +# %% +instance_parameters = { + 'counter_owner' : identity, + 'counter_name' : counter_name, + 'service_group' : service_group, +} + +instance_file = pc_jupyter.instantiate_notebook_from_template(counter_name, 'counter', instance_parameters) +ip_display.display(ip_display.Markdown('[Newly created counter]({})'.format(instance_file))) diff --git a/example-contract/docs/notebooks/templates/counter.py b/example-contract/docs/notebooks/templates/counter.py new file mode 100644 index 0000000..803cb63 --- /dev/null +++ b/example-contract/docs/notebooks/templates/counter.py @@ -0,0 +1,112 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.16.1 +# kernelspec: +# display_name: Python 3 (ipykernel) +# language: python +# name: python3 +# --- + +# %% [markdown] +# # Notebook to Invoke Counter Operations + +# %% [markdown] +# ## Configure Counter Information +# +# %% tags=["parameters"] +counter_owner = 'user1' +counter_name = 'counter_1' +service_group = 'default' +instance_identifier = '' + +# %% [markdown] +#
+# +# ## Initialize + +# %% +import os +import pdo.contracts.jupyter as pc_jupyter + +pc_jupyter.load_contract_families(example='example') +pc_jupyter.load_ipython_extension(get_ipython()) + +# %% [markdown] +# ### Initialize the PDO Environment +# +# Initialize the PDO environment. This assumes that a functional PDO configuration is in place and +# that the PDO virtual environment has been activated. +# +# For the most part, no modifications should be required below. +# %% +common_bindings = { + 'counter_owner' : counter_owner, + 'counter_name' : counter_name, + 'instance' : instance_identifier, +} + +try : state +except NameError : + (state, bindings) = pc_jupyter.initialize_environment(counter_owner, **common_bindings) + +print('environment initialized') + +# %% [markdown] +# ### Initialize the Contract Context +# +# The contract context defines the configuration for a collection of contract objects that interact +# with one another. In the case of the counter contract, there are no interactions and the context +# is very simple. + +# %% +context_file = bindings.expand('${etc}/context/${counter_name}_${instance}.toml') +print("using context file {}".format(context_file)) + +context_bindings = { + 'identity' : counter_owner, + 'service_group' : service_group, +} + +counter_path = 'counter.' + counter_name +context = pc_jupyter.example_jupyter.initialize_counter_context( + state, bindings, context_file, counter_path, **context_bindings) +print('context initialized') + +counter_context = pc_jupyter.pbuilder.Context(state, counter_path + '.counter') +counter_save_file = pc_jupyter.pcommand.invoke_contract_cmd( + pc_jupyter.example_counter.cmd_create_counter, state, counter_context) +pc_jupyter.pbuilder.Context.SaveContextFile(state, context_file, prefix=counter_path) +print('saved counter contract in {}'.format(counter_save_file)) + +# %% [markdown] +#
+# ## Operate on the Counter +# + +# %% [markdown] +# ### Get Counter Value +# +# %% +def get_counter_value() : + return pc_jupyter.pcommand.invoke_contract_cmd( + pc_jupyter.example_counter.cmd_get_value, state, counter_context) +# %% +# %%skip True +print("The current value of the counter is {}".format(get_counter_value())) + +# %% [markdown] +# ### Increment the Counter Value +# +# %% +def inc_counter_value() : + return pc_jupyter.pcommand.invoke_contract_cmd( + pc_jupyter.example_counter.cmd_inc_value, state, counter_context) +# %% +# %%skip True +inc_counter_value() +print("The current value of the counter is {}".format(get_counter_value())) diff --git a/example-contract/pdo/example/__init__.py b/example-contract/pdo/example/__init__.py index a86eef8..67128f4 100644 --- a/example-contract/pdo/example/__init__.py +++ b/example-contract/pdo/example/__init__.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__all__ = [ 'plugins', 'resources', 'scripts' ] +__all__ = [ 'jupyter', 'plugins', 'resources', 'scripts' ] diff --git a/example-contract/pdo/example/jupyter/__init__.py b/example-contract/pdo/example/jupyter/__init__.py new file mode 100644 index 0000000..4185650 --- /dev/null +++ b/example-contract/pdo/example/jupyter/__init__.py @@ -0,0 +1,18 @@ + +# Copyright 2024 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__all__ = [ 'context' ] + +from .context import * diff --git a/example-contract/pdo/example/jupyter/context.py b/example-contract/pdo/example/jupyter/context.py new file mode 100644 index 0000000..5aa0eac --- /dev/null +++ b/example-contract/pdo/example/jupyter/context.py @@ -0,0 +1,56 @@ +# Copyright 2024 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging + +import pdo.contracts.common as jp_common + +_logger = logging.getLogger(__name__) + +__all__ = [ + 'counter_context', + 'initialize_counter_context', +] + +# ----------------------------------------------------------------- +# set up the context +# ----------------------------------------------------------------- +counter_context = jp_common.ContextTemplate('counter', { + 'module' : 'pdo.example.plugins.counter', + 'identity' : '${..identity}', + 'source' : '${ContractFamily.Example.counter.source}', + 'eservice_group' : '${..eservice_group}', + 'pservice_group' : '${..pservice_group}', + 'sservice_group' : '${..sservice_group}', +}) + +# ----------------------------------------------------------------- +# ----------------------------------------------------------------- +def initialize_counter_context(state, bindings, context_file : str, prefix : str, **kwargs) : + """Initialize a context for counters + + @type state: pdo.client.builder.state.State + @param state: client state + @type bindings: pdo.client.builder.bindings.Bindings + @param bindings: current interpreter bindings + @param context_file: name of the context file + @param prefix: prefix for paths in the context + @keyword kwargs: dictionary of string (paths) to values that override context + @rtype: pdo.client.builder.context.Context + @return: the initialized context + """ + contexts = [ + counter_context, + ] + return jp_common.initialize_context(state, bindings, context_file, prefix, contexts, **kwargs)