Skip to content

Commit

Permalink
Move to standard tools for in-memory contract object cache
Browse files Browse the repository at this point in the history
Transition from very simple cache for contract objects to a more
standard cache implementation from cachetools. The transition enables
both more functionality (explicit client control of when the cache is
flushed) and better behavior (automatic flushing of older contract
objects).

Several other clean ups of imports and other unused variables.

Signed-off-by: Mic Bowman <[email protected]>
  • Loading branch information
cmickeyb authored and prakashngit committed May 9, 2024
1 parent 5fa37a1 commit da7fddb
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 27 deletions.
1 change: 0 additions & 1 deletion build/python_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ importlib_resources>=6.0.0
lmdb>=1.4.0
loguru>=0.6.0
mergedeep>=1.3.4
pyparsing>=3.0.9
requests>=2.28.2
requests-toolbelt>=0.10.1
secp256k1==0.13.2
Expand Down
59 changes: 34 additions & 25 deletions client/pdo/client/commands/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
import cachetools.func
import logging
import os
import random

import pdo.client.builder.shell as pshell
import pdo.client.builder.script as pscript
import pdo.client.commands.sservice as sservice
Expand All @@ -26,42 +26,56 @@
import pdo.common.crypto as pcrypto
import pdo.contract as pcontract
from pdo.common.keys import ServiceKeys
from pdo.common.utility import valid_service_url
from pdo.submitter.create import create_submitter

logger = logging.getLogger(__name__)

__all__ = [
'flush_contract_cache',
'get_contract',
'get_contract_from_context',
'create_contract',
'send_to_contract',
'do_contract',
'load_commands',
]
]

## -----------------------------------------------------------------
## get_contract
## -----------------------------------------------------------------
__contract_cache__ = {}
class __hashabledict__(dict) :
"""Hashable dictionary
def get_contract(state, save_file) :
"""Get contract object using the save_file.
This is a relatively simple hack to make a dictionary hashable
so that the cache can work. It assumes that the dictionary does
not change. This is sufficent for the ledger configuration.
"""
def __hash__(self) :
return hash(frozenset(self))

global __contract_cache__
@cachetools.func.ttl_cache(maxsize=32, ttl=30)
def __get_contract__(ledger_config, save_file, data_directory) :
logger.debug(f'load contract from file: {save_file}')
return pcontract.Contract.read_from_file(ledger_config, save_file, data_dir=data_directory)

def get_contract(state, save_file) :
"""Retrieve a contract object associated with the named save file
"""
data_directory = state.get(['Contract', 'DataDirectory'])
ledger_config = __hashabledict__(state.get(['Ledger']))

if save_file not in __contract_cache__ :
try :
data_directory = state.get(['Contract', 'DataDirectory'])
ledger_config = state.get(['Ledger'])
# expiration from the cache really means that we want the
# contract re-read, so force any expired contracts out of
# the cache before we read it
__get_contract__.cache.expire()

__contract_cache__[save_file] = pcontract.Contract.read_from_file(
ledger_config, save_file, data_dir=data_directory)
except Exception as e :
raise Exception('unable to load the contract; {0}'.format(str(e)))
return __get_contract__(ledger_config, save_file, data_directory)

return __contract_cache__[save_file]
def flush_contract_cache() :
"""Explicitly flush the contract cache
"""
__get_contract__.cache.expire()
__get_contract__.cache.clear()

# -----------------------------------------------------------------
# -----------------------------------------------------------------
Expand All @@ -81,7 +95,7 @@ def get_contract_from_context(state, context) :
# will also cache the contract object which we will probably be
# using again later
try :
contract = get_contract(state, save_file)
_ = get_contract(state, save_file)
logger.debug('contract found in file {}'.format(save_file))
except Exception as e :
logger.info("contract save file specified in context, but load failed; {}".format(e))
Expand All @@ -97,7 +111,6 @@ def __add_enclave_secrets__(ledger_config, contract_id, client_keys, enclaveclie
enclaves that will be provisioned for this contract.
"""

secrets = {}
encrypted_state_encryption_keys = {}
for enclaveclient in enclaveclients:
psecrets = []
Expand Down Expand Up @@ -327,7 +340,7 @@ def send_to_contract(state, message, save_file, **kwargs) :
try :
contract = get_contract(state, save_file)
except Exception as e :
raise Exception('unable to load the contract')
raise Exception('unable to load the contract; {}', str(e))

# ---------- set up the enclave service ----------
eservice_client = eservice.get_eservice_from_contract(state, save_file, eservice_url)
Expand All @@ -342,7 +355,6 @@ def send_to_contract(state, message, save_file, **kwargs) :
if not update_response.status :
raise ValueError(update_response.invocation_response)

data_directory = state.get(['Contract', 'DataDirectory'])
ledger_config = state.get(['Ledger'])

if update_response.state_changed and commit :
Expand Down Expand Up @@ -503,8 +515,6 @@ def add_arguments(cls, subparser) :

@classmethod
def invoke(cls, state, bindings, method, save_file, **kwargs) :
waitflag = kwargs.get('wait', False)

pparams = kwargs.get('positional') or []

kparams = dict()
Expand All @@ -526,13 +536,12 @@ def invoke(cls, state, bindings, method, save_file, **kwargs) :
# parameters must not contain an '='
if kwargs.get('params') :
for p in kwargs.get('params') :
kvsplit = p.split('=',1)
kvsplit = p.split('=', 1)
if len(kvsplit) == 1 :
pparams += kvsplit[0]
elif len(kvsplit) == 2 :
kparams[kvsplit[0]] = kvsplit[1]


message = pcontract.invocation_request(method, *pparams, **kparams)
result = send_to_contract(state, message, save_file, **kwargs)

Expand Down
3 changes: 2 additions & 1 deletion client/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@
packages = find_packages(),
namespace_packages=['pdo'],
install_requires = [
'pyparsing',
'cachetools',
'colorama',
'toml',
],
data_files = data_files,
Expand Down

0 comments on commit da7fddb

Please sign in to comment.