├── docs ├── __init__.py ├── media │ ├── threads.jpg │ ├── origin-ecb.png │ ├── classes_energyweb.png │ └── core-class-diagram.png ├── example.py ├── example.config.yml ├── EumelXMLv2.1.1.xml ├── EumelXMLv1.xml ├── logger.py ├── personal_module.py ├── api_contract.yaml └── eth_module.py ├── energyweb ├── eds │ ├── __init__.py │ ├── simulator.py │ ├── interfaces.py │ ├── eumel.py │ └── energy_api.py ├── database │ ├── __init__.py │ ├── memorydao.py │ ├── elasticdao.py │ └── dao.py ├── smart_contract │ ├── __init__.py │ ├── usn │ │ ├── __init__.py │ │ └── rent_v1.py │ ├── origin │ │ ├── __init__.py │ │ ├── asset_reg_v1.py │ │ ├── consumer_v1.py │ │ └── certificate_v1.py │ ├── interfaces.py │ └── origin_v1.py ├── __init__.py ├── log.py ├── storage.py ├── dispatcher.py ├── interfaces.py ├── carbonemission.py └── config.py ├── twine_upload.sh ├── Pipfile ├── setup.py ├── .gitignore ├── generate_server_stub.sh ├── test └── threadtest.py ├── README.md └── LICENSE /docs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /energyweb/eds/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /energyweb/database/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /energyweb/smart_contract/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /energyweb/smart_contract/usn/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /twine_upload.sh: -------------------------------------------------------------------------------- 1 | rm -rf ./dist/* 2 | python3 setup.py sdist bdist_wheel 3 | twine upload dist/* 4 | -------------------------------------------------------------------------------- /docs/media/threads.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/energywebfoundation/ew-link-bond/HEAD/docs/media/threads.jpg -------------------------------------------------------------------------------- /docs/media/origin-ecb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/energywebfoundation/ew-link-bond/HEAD/docs/media/origin-ecb.png -------------------------------------------------------------------------------- /docs/media/classes_energyweb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/energywebfoundation/ew-link-bond/HEAD/docs/media/classes_energyweb.png -------------------------------------------------------------------------------- /docs/media/core-class-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/energywebfoundation/ew-link-bond/HEAD/docs/media/core-class-diagram.png -------------------------------------------------------------------------------- /energyweb/smart_contract/origin/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | The origin library contains data related to the Certificate of Origin project only 3 | """ 4 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | ipython = "*" 8 | pylint = "*" 9 | 10 | [packages] 11 | colorlog = "*" 12 | web3 = "<5.0.0,>=4.8.0" 13 | base58 = "*" 14 | requests = "*" 15 | 16 | [requires] 17 | python_version = "3.7" 18 | -------------------------------------------------------------------------------- /docs/example.py: -------------------------------------------------------------------------------- 1 | import energyweb 2 | import datetime 3 | 4 | class MyTask(energyweb.Task): 5 | """ 6 | Example Task 7 | """ 8 | 9 | def coroutine(self): 10 | print('Task {}: {}\n'.format(self.interval, datetime.datetime.now())) 11 | 12 | 13 | class MyApp(energyweb.App): 14 | """ 15 | Example Application 16 | """ 17 | 18 | def prepare(self): 19 | print('{} Prepared'.format(self.__class__.__name__)) 20 | 21 | def configure(self): 22 | t1 = MyTask(interval=energyweb.LifeCycle.FIVE_SECONDS) 23 | t2 = MyTask(interval=energyweb.LifeCycle.ONE_MINUTE, is_eager=False) 24 | [self.add_task(t) for t in [t2, t1]] 25 | 26 | def finish(self): 27 | print('{} Finished'.format(self.__class__.__name__)) 28 | 29 | 30 | app = MyApp() 31 | 32 | """ 33 | Test loop 34 | """ 35 | if __name__ == '__main__': 36 | app.run() -------------------------------------------------------------------------------- /docs/example.config.yml: -------------------------------------------------------------------------------- 1 | energyweb: 1.0 2 | info: 3 | title: App Title 4 | description: My very nice energyweb embedded app 5 | version: 1.0 alpha 6 | config: 7 | debugger: on 8 | verbosity: low 9 | tasks: 10 | - Consumer Asset 0: 11 | module: ConsumerTask 12 | asset: 13 | id: 0 14 | wallet: 0x0088fF114071cD11D910A3C4C999215DbbF320fF 15 | wallet_pk": 8cc3187a123770c8b6b89ccee765fbfafc36d64d80a6f33d7e4ffc4ff638097f 16 | smart_meter: 17 | module: OriginAPI 18 | url: http://example.com 19 | source: consumed 20 | device_id": 0 21 | - Producer Asset 1: 22 | module: ProducerTask 23 | task_interval: 60 24 | task_is_eager: true 25 | asset: 26 | id: 0 27 | wallet: 0x0088fF114071cD11D910A3C4C999215DbbF320fF 28 | wallet_pk": 8cc3187a123770c8b6b89ccee765fbfafc36d64d80a6f33d7e4ffc4ff638097f 29 | smart_meter: 30 | module: OriginAPI 31 | url: http://example.com 32 | source: produced 33 | device_id": 1 -------------------------------------------------------------------------------- /energyweb/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | _ 3 | | | | 4 | | | __ _ _ __| 5 | |/ \_/ \_/ |/ | / | 6 | \_/ \__/ | |_/\_/|_/ 7 | 8 | Bond - Your favorite library for logging energy data on the blockchain 9 | """ 10 | 11 | import energyweb.config as config 12 | import energyweb.database.dao as dao 13 | import energyweb.smart_contract.usn.rent_v1 as iotlayer 14 | import energyweb.smart_contract.origin_v1 as origin 15 | 16 | from energyweb.interfaces import Serializable, ExternalData, IntegrationPoint, BlockchainClient 17 | from energyweb.log import Logger 18 | from energyweb.dispatcher import App, Task 19 | from energyweb.carbonemission import CarbonEmissionData 20 | from energyweb.eds.interfaces import EnergyUnit, EnergyData, EnergyDevice 21 | from energyweb.smart_contract.interfaces import EVMSmartContractClient 22 | from energyweb.storage import OnDiskChain 23 | from energyweb.database.memorydao import MemoryDAO, MemoryDAOFactory 24 | from energyweb.database.elasticdao import ElasticSearchDAO, ElasticSearchDAOFactory 25 | 26 | 27 | __name__ = 'energyweb' 28 | __author__ = 'Paul Depraz ' 29 | __repository__ = 'github.com/energywebfoundation/ew-link-bond' 30 | __status__ = "pre-alpha" 31 | __version__ = "0.4.3" 32 | __date__ = "14 December 2018" 33 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | https://pypi.org/ 3 | https://pypi.org/classifiers/ 4 | """ 5 | import pathlib 6 | import setuptools 7 | 8 | # The directory containing this file 9 | HERE = pathlib.Path(__file__).parent 10 | 11 | # The text of the README file 12 | README = (HERE / "README.md").read_text() 13 | 14 | setuptools.setup( 15 | name="energyweb", 16 | version="0.4.3", 17 | author="github.com/cerealkill", 18 | author_email="paul.depraz@energyweb.org", 19 | description="Energy utility data interface for blockchain smart contracts", 20 | long_description=README, 21 | long_description_content_type="text/markdown", 22 | url="https://github.com/energywebfoundation/ew-link-bond", 23 | packages=setuptools.find_packages(exclude=["docs", "tests"]), 24 | install_requires=['web3>=4.8.0,<5.0.0', 'colorlog>=3.1.4', 'base58>=1.0.3'], 25 | keywords=['ethereum', 'blockchain', 'energy-web', 'energy', 'smart-energy_meter'], 26 | classifiers=[ 27 | "Programming Language :: Python :: 3", 28 | "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", 29 | "Operating System :: OS Independent", 30 | "Natural Language :: English", 31 | "Intended Audience :: Science/Research", 32 | "Environment :: Console", 33 | "Development Status :: 2 - Pre-Alpha" 34 | ], 35 | ) 36 | -------------------------------------------------------------------------------- /energyweb/eds/simulator.py: -------------------------------------------------------------------------------- 1 | """ 2 | Library containing the implementations of smart-energy_meter simulator integration classes 3 | """ 4 | import time 5 | import random 6 | 7 | from energyweb.eds.interfaces import EnergyDevice, EnergyData 8 | 9 | class EnergyMeterSimulator(EnergyDevice): 10 | """ 11 | Data logger simulator. It will return a pseudo-random incremental number in every iteration 12 | """ 13 | def __init__(self): 14 | self.memory = random.randint(1, 20) 15 | 16 | def read_state(self) -> EnergyData: 17 | access_epoch = int(time.time()) 18 | device = EnergyDevice( 19 | manufacturer='Slock.it', 20 | model='Virtual Energy Meter', 21 | serial_number='0001000', 22 | latitude='1.123', 23 | longitude='1.321') 24 | kwh_power = random.randint(self.memory, (self.memory + 1) + 20) 25 | measurement_epoch = int(time.time()) 26 | device_str = device.manufacturer + device.model + device.serial_number 27 | raw = str(device_str + str(access_epoch) + str(kwh_power) + str(measurement_epoch)) 28 | return EnergyData(device=device, access_epoch=access_epoch, raw=raw, energy=kwh_power, 29 | measurement_epoch=measurement_epoch) 30 | 31 | def write_state(self, *args, **kwargs): 32 | raise NotImplementedError 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | .DS_Store 107 | 108 | ./tests/tobalaba 109 | ./tests/prosumer.json 110 | 111 | .idea 112 | /ew-link-bond/.idea 113 | .vscode 114 | -------------------------------------------------------------------------------- /energyweb/log.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import colorlog 4 | 5 | 6 | class Logger: 7 | """ 8 | Instantiate loggers for general purpose 9 | """ 10 | 11 | def __init__(self, log_name: str, store: str = None, enable_debug: bool = False): 12 | """ 13 | :param log_name: File name for the log. Will create log entries and file name ie. my_app.log 14 | :param store: Path to folder where the log files will be stored in disk. Please note that disk can get full and prevent OS boot. 15 | :param enable_debug: Enabling debug creates a log for errors. Needs storage. Please manually delete it. 16 | """ 17 | 18 | tty_handler = colorlog.StreamHandler() 19 | tty_handler.setFormatter(colorlog.ColoredFormatter('%(log_color)s%(message)s')) 20 | 21 | console = colorlog.getLogger(log_name) 22 | formatter = logging.Formatter('%(asctime)s [%(name)s][%(process)d][%(levelname)s]%(message)s') 23 | if store: 24 | if not os.path.exists(store): 25 | os.makedirs(store) 26 | file_handler = logging.FileHandler(os.path.join(store, f'{log_name}.log')) 27 | file_handler.setFormatter(formatter) 28 | console.addHandler(file_handler) 29 | console.addHandler(tty_handler) 30 | console.setLevel(logging.DEBUG) 31 | error_log = None 32 | if enable_debug: 33 | if not store: 34 | raise Exception('Needs storage path in store parameter defined.') 35 | error_log = logging.getLogger(log_name + '.error') 36 | error_file_handler = logging.FileHandler(os.path.join(store, f'{log_name}.error.log')) 37 | error_file_handler.setFormatter(formatter) 38 | error_log.addHandler(error_file_handler) 39 | error_log.setLevel(logging.WARNING) 40 | 41 | self.store = store 42 | self.console = console 43 | self.error_log = error_log 44 | self.enable_debug = enable_debug 45 | -------------------------------------------------------------------------------- /energyweb/database/memorydao.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | 3 | import tasks.database.dao as dao 4 | 5 | 6 | class MemoryDAO(dao.DAO): 7 | """ 8 | Store values in memory instead of any persistence 9 | Usually used in tests as a test fixture to raise coverage 10 | """ 11 | 12 | def __init__(self): 13 | dao.DAO.register(MemoryDAO) 14 | self._stack = {} 15 | 16 | def cls(self, obj): 17 | return obj.__class__.__name__ 18 | 19 | def create(self, obj): 20 | self._stack[obj.reg_id] = deepcopy(obj) 21 | 22 | def retrieve(self, reg_id): 23 | if reg_id not in self._stack.keys(): 24 | raise FileNotFoundError 25 | obj = self._stack[reg_id] 26 | return deepcopy(obj) 27 | 28 | def retrieve_all(self): 29 | return [deepcopy(self._stack[i]) for i in self._stack] 30 | 31 | def update(self, obj): 32 | if obj.reg_id in self._stack.keys(): 33 | self._stack[obj.reg_id] = deepcopy(obj) 34 | else: 35 | raise FileNotFoundError 36 | 37 | def delete(self, obj): 38 | del self._stack[obj.reg_id] 39 | 40 | def find_by(self, attributes: dict): 41 | result = [] 42 | if len(self._stack) == 0: 43 | return result 44 | sample = list(self._stack.items())[0][1] 45 | intersect_keys = set(attributes).intersection(set(sample.__dict__)) 46 | for k, reg in self._stack.items(): 47 | for key in intersect_keys: 48 | stack_item = reg.__dict__ 49 | if stack_item[key] == attributes[key]: 50 | result.append(deepcopy(reg)) 51 | if len(result) < 1: 52 | raise FileNotFoundError 53 | return result 54 | 55 | 56 | class MemoryDAOFactory(dao.DAOFactory): 57 | 58 | def __init__(self): 59 | super().__init__() 60 | self.__instances = {} 61 | 62 | def get_instance(self, cls) -> MemoryDAO: 63 | if id(cls) in list(self.__instances.keys()): 64 | return self.__instances[id(cls)] 65 | self.__instances[id(cls)] = MemoryDAO() 66 | return self.__instances[id(cls)] 67 | -------------------------------------------------------------------------------- /docs/EumelXMLv2.1.1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

VERBUND

5 |

Power-Meter

6 |

EUMEL 1.0

7 |

2.1.1

8 |

A8404118ABD2

9 |

1

10 |
11 | 12 |

0.01

13 |

0.20

14 |

0.02

15 |

0.12

16 |

75.26

17 |

0.05

18 |

0.07

19 |

225.67

20 |

150.42

21 |

0.02

22 |

225.61

23 |

225.63

24 |

49.99

25 |

2.31

26 |

-0.00

27 |

-0.00

28 |

2.32

29 |

26.64

30 |

0.00

31 |

0.00

32 |

26.64

33 |

5.69

34 |

0.00

35 |

0.00

36 |

5.70

37 |

0.09

38 |

-0.25

39 |

-0.40

40 |

0.09

41 |

11277.41

42 |

0.50

43 |

0.19

44 |

11276.72

45 |

336.12

46 |

0.00

47 |

0.00

48 |

336.12

49 |

13708.56

50 |

0.00

51 |

0.00

52 |

13707.19

53 |

17806.06

54 |

1.91

55 |

0.44

56 |

17803.62

57 |

888.41

58 |

0.00

59 |

0.00

60 |

889.31

61 |

2898.62

62 |

0.00

63 |

0.00

64 |

2897.88

65 |

0.00

66 |

0.00

67 |

0.00

68 |

0.00

69 |

0.00

70 |

0.03

71 |

0.06

72 |

0.00

73 |

0

74 |

21.00

75 |
76 |
77 |
-------------------------------------------------------------------------------- /docs/EumelXMLv1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

0.03

5 |

0.02

6 |

0.02

7 |

2.15

8 |

75.36

9 |

0.08

10 |

0.10

11 |

225.89

12 |

150.55

13 |

0.03

14 |

225.80

15 |

225.82

16 |

49.97

17 |

319.19

18 |

-0.00

19 |

-0.00

20 |

319.19

21 |

484.00

22 |

0.00

23 |

0.00

24 |

485.01

25 |

135.08

26 |

-0.00

27 |

-0.00

28 |

135.08

29 |

0.66

30 |

-0.20

31 |

-0.40

32 |

0.66

33 |

0.00

34 |

0.00

35 |

0.00

36 |

0.00

37 |

123.45

38 |

0.00

39 |

0.00

40 |

330.72

41 |

0.00

42 |

0.00

43 |

0.00

44 |

0.00

45 |

123.06

46 |

0.00

47 |

0.00

48 |

467.06

49 |

106.00

50 |

0.00

51 |

0.00

52 |

0.00

53 |

0.00

54 |

0.00

55 |

0.00

56 |

0.00

57 |

0.00

58 |

0.00

59 |

0.00

60 |

0.00

61 |

32.00

62 |

0.00

63 |

0.00

64 |

0.00

65 |

0

66 |

32.00

67 |
68 |
69 |
-------------------------------------------------------------------------------- /docs/logger.py: -------------------------------------------------------------------------------- 1 | import json 2 | import time 3 | from xml.etree import ElementTree 4 | 5 | import requests 6 | import web3 7 | from core import Spinner, EnergyAsset, LogEntry 8 | 9 | ACCOUNT = '0x00E27b1BB824D66d8ec926f23b04913Fe9b1Bd77' 10 | CONTRACT = '0x122A00cAef700037Bb74D269468c82b21629507F' 11 | PWD = '48qzjbhPdZnw' 12 | 13 | EUMEL_XML = 'test_examples/EumelXMLOutput.xml' 14 | EUMEL_IP = '' 15 | EUMEL_ENDPOINT = EUMEL_IP + '/rest' 16 | 17 | ABI = json.load(open('abi.json', 'r')) 18 | 19 | w3 = web3.Web3(web3.HTTPProvider('http://localhost:8545')) 20 | 21 | # Contract instance in concise mode 22 | contract_instance = w3.eth.contract(ABI, CONTRACT, ContractFactoryClass=web3.contract.ConciseContract) 23 | 24 | 25 | def get_eumel_xml(): 26 | http_request = requests.get(EUMEL_ENDPOINT, auth=('admin', 'aA123456!')) 27 | 28 | 29 | def parse_eumel_xml(): 30 | #http_packet = requests.get(EUMEL_ENDPOINT, auth=('admin', 'aA123456!')) 31 | 32 | tree = ElementTree.parse(EUMEL_XML) 33 | tree_root = tree.getroot() 34 | tree_header = tree_root[0].attrib 35 | tree_leaves = {child.attrib['id']: child.text for child in tree_root[0][0]} 36 | parsed_device = EnergyAsset(manufacturer=tree_header['man'], model=tree_header['mod'], serial_number=tree_header['sn']) 37 | time_format = '%Y-%m-%dT%H:%M:%SZ' 38 | converted_epoch_time = int(time.mktime(time.strptime(tree_header['t'], time_format))) 39 | accumulated_measurement_in_watts = int(tree_leaves['TotWhImp'].replace('.', '')) 40 | parsed_log_entry = LogEntry(epoch=converted_epoch_time, value=accumulated_measurement_in_watts) 41 | return parsed_device, parsed_log_entry 42 | 43 | 44 | def convert_log_entry(epoch, reading): 45 | pretty_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(epoch)) 46 | pretty_reading = float(reading) / 100 47 | return '{} - {:,} Whr'.format(pretty_time, pretty_reading) 48 | 49 | 50 | def log_values(epoch, reading): 51 | print('Inserting new values') 52 | w3.personal.unlockAccount(account=ACCOUNT, passphrase=PWD) 53 | tx_hash = contract_instance.log(epoch, reading, transact={'from': ACCOUNT}) 54 | print("Waiting Tx to be mined ") 55 | spinner = Spinner() 56 | spinner.start() 57 | for _ in range(100): 58 | # Get tx receipt to get contract address 59 | tx_receipt = w3.eth.getTransactionReceipt(tx_hash) 60 | if tx_receipt and tx_receipt['blockNumber']: 61 | break 62 | time.sleep(2) 63 | spinner.stop() 64 | 65 | 66 | def print_log_history(): 67 | print('Log History:') 68 | log_size = contract_instance.getLogSize() 69 | if log_size > 0: 70 | for entry in range(log_size): 71 | epoch, reading = contract_instance.registry(entry) 72 | print('Pretty printing: ' + convert_log_entry(epoch, reading)) 73 | 74 | 75 | try: 76 | device, log_entry = parse_eumel_xml() 77 | log_values(log_entry.epoch, log_entry.value) 78 | print(device.manufacturer, device.model, device.serial_number) 79 | print(convert_log_entry(log_entry.epoch, log_entry.value)) 80 | print('------------------') 81 | print_log_history() 82 | 83 | 84 | except Exception as e: 85 | print(e) 86 | pass 87 | -------------------------------------------------------------------------------- /generate_server_stub.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # author: PFigs 4 | 5 | set -o nounset 6 | set -o errexit 7 | set -o errtrace 8 | 9 | trap 'echo "Aborting due to errexit on line $LINENO. Exit code: $?" >&2' ERR 10 | #trap _finish EXIT 11 | 12 | 13 | DEFAULT_IFS="${IFS}" 14 | SAFER_IFS=$'\n\t' 15 | IFS="${SAFER_IFS}" 16 | 17 | _ME=$(basename "${0}") 18 | 19 | 20 | 21 | function _defaults 22 | { 23 | GENERATE_IN="docker" 24 | LANGUAGE_SELECT="py3" 25 | TARGET_LANGUAGE="-l python-flask" 26 | TARGET_LANGUAGE_FLAG="-D supportPython3=true" 27 | TARGET_LANGUAGE_OUTPUT="-o /local/out/python3" 28 | } 29 | 30 | function _print_help 31 | { 32 | cat <] 41 | ${_ME} -h | --help 42 | ${_ME} --language python2 | python3 | py2 | py3 43 | 44 | Options: 45 | -h --help Show this screen 46 | --language Specify server stub generation language (default: ${LANGUAGE_SELECT}) 47 | HEREDOC 48 | } 49 | 50 | function _parse 51 | { 52 | # Gather commands 53 | while (( "${#}" )) 54 | do 55 | case "${1}" in 56 | --language) 57 | LANGUAGE_SELECT="$2" 58 | shift # past argument 59 | shift # past value 60 | ;; 61 | *|-*|--*=) # unsupported flags 62 | echo "Unknown ${1}" 63 | exit 1 64 | ;; 65 | esac 66 | done 67 | } 68 | 69 | function _generate_stub 70 | { 71 | 72 | if [ "${LANGUAGE_SELECT}" == "py2" ] || [ "${LANGUAGE_SELECT}" == "python2" ] 73 | then 74 | 75 | TARGET_LANGUAGE="python-flask" 76 | TARGET_LANGUAGE_FLAG="supportPython2=true" 77 | TARGET_LANGUAGE_OUTPUT="/local/server_stub/python2" 78 | 79 | elif [ "${LANGUAGE_SELECT}" == "py3" ] || [ "${LANGUAGE_SELECT}" == "python3" ] 80 | then 81 | 82 | TARGET_LANGUAGE="python-flask" 83 | TARGET_LANGUAGE_FLAG="supportPython3=true" 84 | TARGET_LANGUAGE_OUTPUT="/local/server_stub/python3" 85 | fi 86 | 87 | echo "generation in ${GENERATE_IN} with ${TARGET_LANGUAGE} ${TARGET_LANGUAGE_FLAG} ${TARGET_LANGUAGE_OUTPUT}" 88 | 89 | if [ "${GENERATE_IN}" == "docker" ] 90 | then 91 | sudo docker run --rm \ 92 | --user $(id -u):$(id -g) \ 93 | -v $(pwd):/local \ 94 | swaggerapi/swagger-codegen-cli generate \ 95 | -i /local/docs/api_contract.yaml \ 96 | -l ${TARGET_LANGUAGE} \ 97 | -D ${TARGET_LANGUAGE_FLAG} \ 98 | -o ${TARGET_LANGUAGE_OUTPUT} 99 | 100 | elif [[ "${GENERATE_IN}" == "native" ]] 101 | then 102 | echo "native support not available" 103 | 104 | else 105 | echo "genaration not support in ${GENERATE_IN}" 106 | fi 107 | } 108 | 109 | #docker run --rm -v ${PWD}:/local \ 110 | #swaggerapi/swagger-codegen-cli generate \ 111 | #-i /local/docs/api_contract.yaml \ 112 | #-l python-flask \ 113 | #-D supportPython2=true \ 114 | #-o /local/out/python 115 | 116 | # main execution loop 117 | _main() 118 | { 119 | _defaults 120 | 121 | if [[ "${1:-}" =~ ^-h|--help$ ]] 122 | then 123 | _print_help 124 | exit 1 125 | fi 126 | 127 | _parse "$@" 128 | _generate_stub 129 | 130 | echo "done" 131 | } 132 | 133 | _main "$@" 134 | -------------------------------------------------------------------------------- /docs/personal_module.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from eth_utils import ( 4 | is_address, 5 | is_list_like, 6 | is_same_address, 7 | ) 8 | 9 | 10 | PRIVATE_KEY_HEX = '0x56ebb41875ceedd42e395f730e03b5c44989393c9f0484ee6bc05f933673458f' 11 | PASSWORD = 'web3-testing' 12 | ADDRESS = '0x844b417c0c58b02c2224306047b9fb0d3264fe8c' 13 | 14 | 15 | PRIVATE_KEY_FOR_UNLOCK = '0x392f63a79b1ff8774845f3fa69de4a13800a59e7083f5187f1558f0797ad0f01' 16 | ACCOUNT_FOR_UNLOCK = '0x12efdc31b1a8fa1a1e756dfd8a1601055c971e13' 17 | 18 | 19 | class PersonalModuleTest(object): 20 | def test_personal_importRawKey(self, web3): 21 | actual = web3.personal.importRawKey(PRIVATE_KEY_HEX, PASSWORD) 22 | assert is_same_address(actual, ADDRESS) 23 | 24 | def test_personal_listAccounts(self, web3): 25 | accounts = web3.personal.listAccounts 26 | assert is_list_like(accounts) 27 | assert len(accounts) > 0 28 | assert all(( 29 | is_address(item) 30 | for item 31 | in accounts 32 | )) 33 | 34 | def test_personal_lockAccount(self, web3, unlocked_account): 35 | # TODO: how do we test this better? 36 | web3.personal.lockAccount(unlocked_account) 37 | 38 | def test_personal_unlockAccount_success(self, 39 | web3, 40 | unlockable_account, 41 | unlockable_account_pw): 42 | result = web3.personal.unlockAccount(unlockable_account, unlockable_account_pw) 43 | assert result is True 44 | 45 | def test_personal_unlockAccount_failure(self, 46 | web3, 47 | unlockable_account): 48 | result = web3.personal.unlockAccount(unlockable_account, 'bad-password') 49 | assert result is False 50 | 51 | def test_personal_newAccount(self, web3): 52 | new_account = web3.personal.newAccount(PASSWORD) 53 | assert is_address(new_account) 54 | 55 | def test_personal_sendTransaction(self, 56 | web3, 57 | unlockable_account, 58 | unlockable_account_pw): 59 | assert web3.eth.getBalance(unlockable_account) > web3.toWei(1, 'ether') 60 | txn_params = { 61 | 'from': unlockable_account, 62 | 'to': unlockable_account, 63 | 'gas': 21000, 64 | 'value': 1, 65 | 'gasPrice': web3.toWei(1, 'gwei'), 66 | } 67 | txn_hash = web3.personal.sendTransaction(txn_params, unlockable_account_pw) 68 | assert txn_hash 69 | transaction = web3.eth.getTransaction(txn_hash) 70 | assert is_same_address(transaction['from'], txn_params['from']) 71 | assert is_same_address(transaction['to'], txn_params['to']) 72 | assert transaction['gas'] == txn_params['gas'] 73 | assert transaction['value'] == txn_params['value'] 74 | assert transaction['gasPrice'] == txn_params['gasPrice'] 75 | 76 | def test_personal_sign_and_ecrecover(self, 77 | web3, 78 | unlockable_account, 79 | unlockable_account_pw): 80 | message = 'test-web3-personal-sign' 81 | signature = web3.personal.sign(message, unlockable_account, unlockable_account_pw) 82 | signer = web3.personal.ecRecover(message, signature) 83 | assert is_same_address(signer, unlockable_account) 84 | -------------------------------------------------------------------------------- /energyweb/storage.py: -------------------------------------------------------------------------------- 1 | """ 2 | __Storage__ supports EWF's Origin release A log storage, designed to record a sequence of _off-chain_ files by updating the previous file contents SHA hash with the next. It is particularly useful to enforce data integrity, by comparing the sequence of raw smart-energy_meter readings with the sequence registered _on-chain_. 3 | """ 4 | import os 5 | import json 6 | import pickle 7 | import hashlib 8 | import datetime 9 | import base58 10 | 11 | from energyweb.interfaces import Serializable 12 | 13 | 14 | class ChainFile: 15 | """ 16 | List element 17 | """ 18 | def __init__(self, file: str, timestamp: datetime.datetime): 19 | self.file = file 20 | self.timestamp = timestamp 21 | 22 | 23 | class ChainLink: 24 | """ 25 | List link 26 | """ 27 | def __init__(self, data: ChainFile, last_link: object): 28 | self.data = data 29 | self.last_link = last_link 30 | 31 | def __next__(self): 32 | return self.last_link 33 | 34 | 35 | class OnDiskChain: 36 | """ 37 | Saves a pickle with the data in chain format. 38 | """ 39 | def __init__(self, chain_file_name: str, path_to_files: str): 40 | """ 41 | :param chain_file_name: 42 | :param path_to_files: 43 | """ 44 | self.chain_file = os.path.join(path_to_files, chain_file_name) 45 | self.path = path_to_files 46 | os.makedirs(path_to_files, exist_ok=True) 47 | if not os.path.exists(self.chain_file): 48 | self.__memory = None 49 | return 50 | try: 51 | self.__memory = pickle.load(open(self.chain_file, 'rb')) 52 | except EOFError: 53 | self.__memory = None 54 | 55 | @property 56 | def chain(self) -> ChainLink: 57 | return self.__memory 58 | 59 | @chain.setter 60 | def chain(self, chain_link: ChainLink): 61 | if chain_link is not None: 62 | raise AttributeError 63 | self._chain_append(chain_link) 64 | 65 | def add_to_chain(self, data: Serializable) -> str: 66 | """ 67 | Add new file to chain. 68 | :param data: Data to store 69 | :return: File name string 70 | """ 71 | data_file_name = self._save_file(data) 72 | chain_data = ChainFile(data_file_name, datetime.datetime.now()) 73 | new_link = ChainLink(data=chain_data, last_link=self.chain) 74 | self._chain_append(new_link) 75 | self._save_memory() 76 | return data_file_name 77 | 78 | def get_last_hash(self) -> str: 79 | """ 80 | Get hash of the last chain file. 81 | :return: Base58 hash string 82 | """ 83 | if self.chain: 84 | # sha3 = hashlib.sha3_256() 85 | sha3 = hashlib.sha1() 86 | sha3.update(open(self.chain.data.file, 'rb').read()) 87 | base58_digest = base58.b58encode(sha3.digest()) 88 | return 'Qm' + base58_digest.decode() 89 | else: 90 | return '0x0' 91 | 92 | def _chain_append(self, chain_link: ChainLink): 93 | self.__memory = chain_link 94 | self._save_memory() 95 | 96 | def _save_memory(self): 97 | pickle.dump(self.__memory, open(self.chain_file, 'wb'), protocol=pickle.HIGHEST_PROTOCOL) 98 | 99 | def _save_file(self, data): 100 | if not os.path.exists(self.path): 101 | os.makedirs(self.path) 102 | file_name_mask = os.path.join(self.path, '%Y-%m-%d-%H:%M:%S.json') 103 | file_name = datetime.datetime.now().strftime(file_name_mask) 104 | with open(file_name, 'w+') as file: 105 | json.dump(data.to_dict(), file) 106 | return file_name 107 | -------------------------------------------------------------------------------- /energyweb/database/elasticdao.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import elasticsearch as es 4 | 5 | from tasks.database import dao 6 | from tasks.ocpp16.protocol import ChargingStation 7 | 8 | 9 | class ElasticSearchDAO(dao.DAO): 10 | 11 | def __init__(self, id_att_name: str, cls: dao.Model, *service_urls: str): 12 | """ 13 | :param id_att_name: Class id attribute name 14 | :param cls: Class to instantiate 15 | :param service_urls: i.e. 'http://localhost:9200', 'https://remotehost:9000' 16 | """ 17 | self._index = id_att_name 18 | self._cls = cls 19 | self._doc_type = cls.__name__ 20 | self._db = es.Elasticsearch(service_urls) 21 | # suppress warnings 22 | es_logger = logging.getLogger('elasticsearch') 23 | es_logger.setLevel(logging.ERROR) 24 | 25 | def create(self, obj: dao.Model): 26 | res = self._db.index(index=self._index, doc_type=self._doc_type, body=obj.to_dict(), id=obj.reg_id, 27 | refresh=True) 28 | if not res['result'] in ('created', 'updated'): 29 | raise es.ElasticsearchException('Fail creating or updating the object in the database') 30 | 31 | def retrieve(self, _id): 32 | self._db.indices.refresh(self._index) 33 | res = self._db.get(self._index, self._doc_type, id=_id) 34 | if not res['found']: 35 | raise es.ElasticsearchException('Object not found.') 36 | obj = self._cls.from_dict(res['_source']) 37 | obj.reg_id = res['_id'] 38 | return obj 39 | 40 | def retrieve_all(self): 41 | self._db.indices.refresh(self._index) 42 | res = self._db.search(self._index, self._doc_type, body={"query": {"match_all": {}}}) 43 | objs = [] 44 | for hit in res['hits']['hits']: 45 | obj = self._cls.from_dict(hit['_source']) 46 | obj.reg_id = hit['_id'] 47 | objs.append(obj) 48 | return objs 49 | 50 | def update(self, obj: dao.Model): 51 | self.create(obj) 52 | 53 | def delete(self, obj: dao.Model): 54 | response = self._db.delete(index=self._index, doc_type=self._doc_type, id=obj.reg_id) 55 | if not response['result'] == 'deleted': 56 | raise es.ElasticsearchException('Object not found.') 57 | 58 | def find_by(self, attributes: [dict]) -> [dict]: 59 | self._db.indices.refresh(self._index) 60 | query = {"query": {"bool": {"must": [{"match": {k: attributes[k]}} for k in attributes]}}} 61 | res = self._db.search(self._index, self._doc_type, body=query) 62 | objs = [] 63 | for hit in res['hits']['hits']: 64 | obj = self._cls.from_dict(hit['_source']) 65 | obj.reg_id = hit['_id'] 66 | objs.append(obj) 67 | return objs 68 | 69 | def delete_all(self): 70 | self._db.delete_by_query(self._index, doc_type=self._doc_type, body={"query": {"match_all": {}}}) 71 | 72 | def delete_all_blank(self, field: str): 73 | self._db.delete_by_query(self._index, doc_type=self._doc_type, body={"bool": {"must_not": {"exists": {"field": field}}}}) 74 | 75 | def query(self, query: dict) -> [dict]: 76 | """ 77 | :param query: https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-filter-context.html 78 | :return: dict 79 | """ 80 | self._db.indices.refresh(self._index) 81 | res = self._db.search(self._index, self._doc_type, body={"query": query}) 82 | objs = [] 83 | for hit in res['hits']['hits']: 84 | obj = self._cls.from_dict(hit['_source']) 85 | obj.reg_id = hit['_id'] 86 | objs.append(obj) 87 | return objs 88 | 89 | 90 | class ElasticSearchDAOFactory(dao.DAOFactory): 91 | 92 | def __init__(self, index_name: str, *service_urls: str): 93 | super().__init__() 94 | self._instances = {} 95 | self._service_urls = service_urls 96 | self._index = index_name 97 | 98 | def get_instance(self, cls) -> ElasticSearchDAO: 99 | if id(cls) in list(self._instances.keys()): 100 | return self._instances[id(cls)] 101 | self._instances[id(cls)] = ElasticSearchDAO(self._index, cls, *self._service_urls) 102 | return self._instances[id(cls)] 103 | -------------------------------------------------------------------------------- /energyweb/eds/interfaces.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | 3 | from energyweb.interfaces import ExternalData, IntegrationPoint 4 | 5 | 6 | class EnergyUnit(IntEnum): 7 | """ 8 | Possible units for effort and billing purposes. Determines the convertion algorithms. 9 | """ 10 | JOULES = 0 11 | WATT_HOUR = 1 12 | KILOWATT_HOUR = 2 13 | MEGAWATT_HOUR = 3 14 | GIGAWATT_HOUR = 4 15 | 16 | 17 | class EnergyData(ExternalData): 18 | """ 19 | Standard for collected energy and power data, to be transformed into mintable data. 20 | """ 21 | 22 | def __init__(self, device, access_epoch, raw, energy, measurement_epoch): 23 | """ 24 | Minimum set of data for power measurement logging. 25 | :param device: Metadata about the measurement device. EnergyDevice 26 | :param access_epoch: Time the external API was accessed 27 | :param raw: Raw data collected 28 | :param energy: Measured energy at the source converted to watt-hours 29 | :param measurement_epoch: Time of measurement at the source 30 | """ 31 | self.device = device 32 | self.measurement_epoch = measurement_epoch 33 | self.energy = energy 34 | ExternalData.__init__(self, access_epoch, raw) 35 | 36 | 37 | class EnergyDevice(IntegrationPoint): 38 | """ 39 | Energy device or api abstraction. Can be smart-meters, battery controllers, inverters, gateways, clouds, so on. 40 | """ 41 | 42 | def __init__(self, manufacturer, model, serial_number, energy_unit, is_accumulated, latitude=None, longitude=None): 43 | """ 44 | :param manufacturer: EnergyAsset Manufacturer 45 | :param model: EnergyAsset model 46 | :param serial_number: EnergyAsset Serial Number 47 | :param latitude: EnergyAsset geolocation latitude 48 | :param longitude: EnergyAsset geolocation longitude 49 | :param energy_unit: Energy unity 50 | :param is_accumulated: Flags if the api provides accumulated power or hourly production 51 | """ 52 | self.manufacturer = manufacturer 53 | self.model = model 54 | self.serial_number = serial_number 55 | self.latitude = latitude 56 | self.longitude = longitude 57 | self.energy_unit = EnergyUnit[energy_unit.upper()] 58 | self.is_accumulated = is_accumulated 59 | 60 | def read_state(self, *args, **kwargs) -> EnergyData: 61 | """ 62 | Establishes a connection to the integration medium and returns the latest state 63 | :rtype: EnergyData 64 | """ 65 | raise NotImplementedError 66 | 67 | def write_state(self, *args, **kwargs) -> EnergyData: 68 | """ 69 | Establishes a connection to the integration medium and returns the latest state 70 | :rtype: EnergyData 71 | """ 72 | raise NotImplementedError 73 | 74 | @staticmethod 75 | def to_mwh(energy: int, unit): 76 | """ 77 | Converts energy measured value in predefined unit to Megawatt-Hour 78 | :param unit: EnergyUnit 79 | :param energy: Value measured at the source 80 | :return: Value converted to MWh 81 | :rtype: float 82 | """ 83 | convert = lambda x: float(energy) / x 84 | return { 85 | EnergyUnit.WATT_HOUR: convert(10 ** 6), 86 | EnergyUnit.KILOWATT_HOUR: convert(10 ** 3), 87 | EnergyUnit.MEGAWATT_HOUR: convert(1), 88 | EnergyUnit.GIGAWATT_HOUR: convert(1 * 10 ** -3), 89 | EnergyUnit.JOULES: convert(1 * 2.77778 * 10 ** -10), 90 | }.get(unit) 91 | 92 | @staticmethod 93 | def to_wh(energy: int, unit: EnergyUnit): 94 | """ 95 | Converts energy measured value in predefined unit to Watt-Hour 96 | :param unit: EnergyUnit 97 | :param energy: Value measured at the source 98 | :return: Value converted to MWh 99 | :rtype: int 100 | """ 101 | convert = lambda x: int(float(energy * x)) 102 | return { 103 | EnergyUnit.WATT_HOUR: convert(1), 104 | EnergyUnit.KILOWATT_HOUR: convert(10 ** 3), 105 | EnergyUnit.MEGAWATT_HOUR: convert(10 ** 6), 106 | EnergyUnit.GIGAWATT_HOUR: convert(10 ** 9), 107 | EnergyUnit.JOULES: convert(1 * 2.77778 * 10 ** -1), 108 | }.get(unit) 109 | -------------------------------------------------------------------------------- /energyweb/eds/eumel.py: -------------------------------------------------------------------------------- 1 | """ 2 | input.eumel 3 | 4 | Library containing the implementations of Eumel DataLogger integration classes 5 | """ 6 | import time 7 | import requests 8 | 9 | from xml.etree import ElementTree 10 | from energyweb.eds import EnergyUnit, EnergyData 11 | from energyweb.eds.interfaces import EnergyDevice 12 | 13 | 14 | class DataLoggerV1(EnergyDevice): 15 | """ 16 | Eumel DataLogger api v1.0 access implementation 17 | """ 18 | 19 | def __init__(self, ip, user, password): 20 | """ 21 | :param ip: Data loggers network IP 22 | :param user: User configured on the devices 23 | :param password: Password for this user 24 | """ 25 | self.eumel_api_url = ip + '/rest' 26 | self.auth = (user, password) 27 | super().__init__(manufacturer='Verbund', model='Eumel v1', serial_number=None, energy_unit=EnergyUnit.WATT_HOUR, 28 | is_accumulated=True) 29 | 30 | def read_state(self, path=None) -> EnergyData: 31 | if path: 32 | tree = ElementTree.parse(path) 33 | with open(path) as file: 34 | raw = file.read() 35 | else: 36 | http_packet = requests.get(self.eumel_api_url, auth=self.auth) 37 | raw = http_packet.content.decode() 38 | tree = ElementTree.parse(raw) 39 | tree_root = tree.getroot() 40 | tree_header = tree_root[0].attrib 41 | tree_leaves = {child.attrib['id']: child.text for child in tree_root[0][0]} 42 | device = EnergyDevice( 43 | manufacturer=tree_header['man'], 44 | model=tree_header['mod'], 45 | serial_number=tree_header['sn'], 46 | energy_unit=self.energy_unit, 47 | is_accumulated=self.is_accumulated) 48 | access_epoch = int(time.time()) 49 | time_format = '%Y-%m-%dT%H:%M:%SZ' 50 | energy = float(tree_leaves['TotWhImp'].replace('.', '')) 51 | mwh_energy = self.to_mwh(energy, self.energy_unit) 52 | measurement_epoch = int(time.mktime(time.strptime(tree_header['t'], time_format))) 53 | return EnergyData(asset=device, access_epoch=access_epoch, raw=raw, measurement_epoch=measurement_epoch, 54 | energy=mwh_energy) 55 | 56 | def write_state(self, *args, **kwargs): 57 | raise NotImplementedError 58 | 59 | 60 | class DataLoggerV2d1d1(EnergyDevice): 61 | """ 62 | Eumel DataLogger api v2.1.1 access implementation 63 | """ 64 | 65 | def __init__(self, ip, user, password): 66 | """ 67 | :param ip: Data loggers network IP 68 | :param user: User configured on the devices 69 | :param password: Password for this user 70 | """ 71 | self.eumel_api_url = ip + '/wizard/public/api/rest' 72 | self.auth = (user, password) 73 | super().__init__(manufacturer='Verbund', model='Eumel v1', serial_number=None, energy_unit=EnergyUnit.WATT_HOUR, 74 | is_accumulated=True) 75 | 76 | def read_state(self, path=None) -> EnergyData: 77 | if path: 78 | tree = ElementTree.parse(path) 79 | with open(path) as file: 80 | raw = file.read() 81 | else: 82 | http_packet = requests.get(self.eumel_api_url, auth=self.auth) 83 | raw = http_packet.content.decode() 84 | tree = ElementTree.ElementTree(ElementTree.fromstring(raw)) 85 | tree_root = tree.getroot() 86 | tree_header = tree_root[0].attrib 87 | tree_leaves = {child.attrib['id']: child.text for child in tree_root[0][1]} 88 | device = EnergyDevice( 89 | manufacturer=tree_header['man'], 90 | model=tree_header['mod'], 91 | serial_number=tree_header['sn'], 92 | energy_unit=self.energy_unit, 93 | is_accumulated=self.is_accumulated) 94 | access_epoch = int(time.time()) 95 | time_format = '%Y-%m-%dT%H:%M:%SZ' 96 | energy = float(tree_leaves['TotWhImp']) 97 | mwh_energy = self.to_mwh(energy, self.energy_unit) 98 | measurement_epoch = int(time.mktime(time.strptime(tree_header['t'], time_format))) 99 | return EnergyData(asset=device, access_epoch=access_epoch, raw=raw, measurement_epoch=measurement_epoch, 100 | energy=mwh_energy) 101 | 102 | def write_state(self, *args, **kwargs): 103 | raise NotImplementedError 104 | -------------------------------------------------------------------------------- /energyweb/dispatcher.py: -------------------------------------------------------------------------------- 1 | """ 2 | Asynchronous event watcher loop 3 | """ 4 | import asyncio 5 | import datetime 6 | 7 | 8 | class Task: 9 | """ 10 | Tasks are routines that run from time to time respecting an interval and spawn coroutines. 11 | These routines may only execute if a trigger condition is fired. 12 | """ 13 | def __init__(self, queue: {str: asyncio.Queue}, polling_interval: datetime.timedelta = None, eager: bool = False, run_forever: bool = True): 14 | """ 15 | :param polling_interval: in seconds 16 | :param queue: app asyncio queues for messages exchange between threads 17 | :param eager: if main waits polling time first or is eager to start 18 | """ 19 | self.polling_interval = polling_interval 20 | self.queue = queue 21 | self.run_forever = run_forever 22 | self.eager = eager 23 | 24 | async def _prepare(self): 25 | """ 26 | Perform steps required prior to running the main task 27 | run exactly once 28 | """ 29 | raise NotImplementedError 30 | 31 | async def _main(self, *args): 32 | """ 33 | The main task 34 | """ 35 | raise NotImplementedError 36 | 37 | async def _finish(self): 38 | """ 39 | Perform steps required after running the main task 40 | run exactly once 41 | """ 42 | raise NotImplementedError 43 | 44 | def _handle_exception(self, e: Exception): 45 | """ 46 | Handle exceptions when they occur 47 | """ 48 | raise NotImplementedError 49 | 50 | async def run(self, *args): 51 | """ 52 | run all steps of the task 53 | """ 54 | async def main_loop(): 55 | if self.polling_interval and not self.eager: 56 | await asyncio.sleep(self.polling_interval.total_seconds()) 57 | await self._main(*args) 58 | if self.polling_interval and self.eager: 59 | await asyncio.sleep(self.polling_interval.total_seconds()) 60 | try: 61 | await self._prepare() 62 | try: 63 | await main_loop() 64 | while self.run_forever: 65 | await main_loop() 66 | except Exception as e: 67 | self._handle_exception(e) 68 | finally: 69 | await self._finish() 70 | if self.run_forever: 71 | await self.run(*args) 72 | except Exception as e: 73 | self._handle_exception(e) 74 | 75 | 76 | class App: 77 | """ 78 | General application abstraction 79 | """ 80 | def __init__(self): 81 | self.tasks: [asyncio.tasks] = [] 82 | self.queue: {str: asyncio.Queue} = {} 83 | self.loop = asyncio.get_event_loop() 84 | self._configure() 85 | 86 | def _configure(self): 87 | """ 88 | Configuration phase of the app. 89 | """ 90 | raise NotImplementedError 91 | 92 | def _clean_up(self): 93 | """ 94 | Overwrite this method to implement clean up logic like closing sockets and streams. 95 | """ 96 | raise NotImplementedError 97 | 98 | def _handle_exception(self, e: Exception): 99 | """ 100 | Handle exceptions when they occur 101 | """ 102 | raise NotImplementedError 103 | 104 | def _register_task(self, task: Task, *args): 105 | """ 106 | Add task to be executed in run time 107 | """ 108 | if not task: 109 | raise Exception('Please add a Task type with callable task named method.') 110 | self.tasks.append((task, args)) 111 | 112 | def _register_queue(self, queue_id: str, max_size: int = 0): 113 | """ 114 | Creates a new asyncio Queue for messages exchange between threads 115 | :param queue_id: Name of queue index 116 | :param max_size: Maximum number of messages 117 | """ 118 | self.queue[queue_id] = asyncio.Queue(maxsize=max_size) 119 | 120 | def run(self): 121 | """ 122 | execute all tasks in task list in their own thread 123 | """ 124 | try: 125 | self.loop.run_until_complete(asyncio.gather(*[task.run(*args) for task, args in self.tasks])) 126 | except KeyboardInterrupt: 127 | self._clean_up() 128 | if self.loop.is_running(): 129 | self.loop.close() 130 | except Exception as e: 131 | self._handle_exception(e) 132 | finally: 133 | self._clean_up() 134 | -------------------------------------------------------------------------------- /test/threadtest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import asyncio 4 | import datetime 5 | import time 6 | import energyweb 7 | import urllib 8 | 9 | 10 | class PrintTask(energyweb.dispatcher.Task): 11 | 12 | async def _finish(self): 13 | print(f'Task {self.__class__.__name__} finished') 14 | 15 | async def _prepare(self): 16 | print(f'Task {self.__class__.__name__} prepared') 17 | 18 | def _handle_exception(self, e: Exception): 19 | print(f'Task {self.__class__.__name__} failed because {e.with_traceback(e.__traceback__)}') 20 | 21 | async def _main(self, duration, character): 22 | for _ in range(duration): 23 | print(character, end='', flush=True) 24 | time.sleep(1) 25 | print('\n') 26 | 27 | 28 | class PostManTask(energyweb.dispatcher.Task): 29 | 30 | def __init__(self, queue, send_interval, messages: [str]): 31 | self.messages = messages 32 | super().__init__(queue, send_interval, run_forever=False) 33 | 34 | async def _finish(self): 35 | print(f'Task {self.__class__.__name__} finished') 36 | 37 | async def _prepare(self): 38 | import random 39 | random.shuffle(self.messages, random.random) 40 | print(f'Task {self.__class__.__name__} prepared') 41 | 42 | def _handle_exception(self, e: Exception): 43 | print(f'Task {self.__class__.__name__} failed because {e.with_traceback(e.__traceback__)}') 44 | 45 | async def _main(self): 46 | for msg in self.messages: 47 | await self.queue['mail_box'].put(msg) 48 | await asyncio.sleep(self.polling_interval.total_seconds()) 49 | raise AttributeError(f'Task {self.__class__.__name__} ended delivering messages.') 50 | 51 | 52 | class MailCheckerTask(energyweb.dispatcher.Task): 53 | 54 | async def _finish(self): 55 | print(f'Task {self.__class__.__name__} finished') 56 | 57 | async def _prepare(self): 58 | print(f'Task {self.__class__.__name__} prepared') 59 | 60 | def _handle_exception(self, e: Exception): 61 | print(f'Task {self.__class__.__name__} failed because {e.with_traceback(e.__traceback__)}') 62 | 63 | async def _main(self): 64 | messages = [] 65 | while not self.queue['mail_box'].empty(): 66 | messages.append(self.queue['mail_box'].get_nowait()) 67 | if len(messages) > 0: 68 | [print(msg) for msg in messages] 69 | 70 | 71 | class NetworkTask(energyweb.dispatcher.Task): 72 | """ 73 | Example Task reading and writing network 74 | """ 75 | 76 | def __init__(self, queue, polling_interval): 77 | self.net = None 78 | super().__init__(queue, polling_interval, eager=True, run_forever=False) 79 | 80 | def _handle_exception(self, e: Exception): 81 | print(f'Task {self.__class__.__name__} failed because {e.with_traceback(e.__traceback__)}') 82 | 83 | async def _prepare(self): 84 | print('Net try open') 85 | try: 86 | self.net = urllib.request.urlopen(f'http://localhost:8000') 87 | except urllib.error.URLError: 88 | print('Net unavailable') 89 | 90 | async def _main(self): 91 | if self.net: 92 | response = self.net.read().decode().strip() 93 | print(response) 94 | self.queue['network_status'].put({'online': True}) 95 | else: 96 | raise urllib.error.URLError('Net Unavailable.') 97 | 98 | async def _finish(self): 99 | print('Net close') 100 | await self.queue['network_status'].put({'online': False}) 101 | 102 | class MyApp(energyweb.dispatcher.App): 103 | 104 | def _handle_exception(self, e: Exception): 105 | print('==== APP ERROR ====') 106 | print(f'{e.with_traceback(e.__traceback__)}') 107 | 108 | def _clean_up(self): 109 | print('==== App finished, cleaning up. ====') 110 | 111 | def _configure(self): 112 | print('==== App reading configuration ====') 113 | self._register_queue('mail_box') 114 | self._register_queue('network_status', 1) 115 | self._register_task(NetworkTask(self.queue, datetime.timedelta(seconds=20))) 116 | messages = ['Hello Mike', 'Don\'t forget my bday', 'Have a nice day'] 117 | self._register_task(PostManTask(self.queue, datetime.timedelta(seconds=10), messages)) 118 | self._register_task(MailCheckerTask(self.queue, datetime.timedelta(seconds=20))) 119 | self._register_task(PrintTask(self.queue, datetime.timedelta(minutes=2)), 3, '>') 120 | 121 | 122 | if __name__ == '__main__': 123 | app = MyApp() 124 | app.run() 125 | 126 | -------------------------------------------------------------------------------- /energyweb/database/dao.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import datetime 3 | import inspect 4 | 5 | import energyweb 6 | 7 | 8 | class Model(energyweb.Serializable): 9 | """ MVC Concrete Model """ 10 | 11 | def __init__(self, reg_id=None): 12 | """ 13 | :param reg_id: Registry ID 14 | :return: 15 | """ 16 | self.reg_id = reg_id 17 | 18 | def __eq__(self, other): 19 | return isinstance(other, self.__class__) and self.reg_id == other.reg_id 20 | 21 | def __ne__(self, other): 22 | return not self.__eq__(other) 23 | 24 | def __hash__(self): 25 | if self.reg_id is None: 26 | raise AssertionError('Object is not in sync with the object in db session.') 27 | return hash(''.join(dir(self))) ^ hash(self.reg_id) 28 | 29 | def __repr__(self): 30 | return '%s(%s)' % (self.__class__.__name__, self.reg_id) 31 | 32 | def __self__(self): 33 | return self 34 | 35 | def to_dict(self): 36 | def to_dict_or_self(obj): 37 | to_dict = getattr(obj, 'to_dict', None) 38 | if to_dict: 39 | return to_dict() 40 | else: 41 | return obj 42 | 43 | result = {} 44 | init = getattr(self, '__init__') 45 | for parameter in inspect.signature(init).parameters: 46 | att = getattr(self, parameter) 47 | if isinstance(att, list) or isinstance(att, set): 48 | att = [self.to_dict_or_self(o) for o in att] 49 | elif isinstance(att, dict): 50 | att = {self.to_dict_or_self(k): self.to_dict_or_self(v) for k, v in att.items()} 51 | elif isinstance(att, (datetime.datetime, datetime.date)): 52 | att = att.isoformat() 53 | result[parameter] = to_dict_or_self(att) 54 | return result 55 | 56 | @staticmethod 57 | def from_dict(obj_dict: dict): 58 | raise NotImplementedError 59 | 60 | 61 | class ABCSingleton(abc.ABCMeta): 62 | instance = None 63 | 64 | def __call__(cls, *args, **kw): 65 | if not cls.instance: 66 | cls.instance = super().__call__(*args, **kw) 67 | return cls.instance 68 | 69 | 70 | class DAO(metaclass=abc.ABCMeta): 71 | """ 72 | Data Access Object is an Abstract Class 73 | Must be initialized using 'DAO.register()' 74 | """ 75 | 76 | @abc.abstractmethod 77 | def create(self, obj): pass 78 | 79 | @abc.abstractmethod 80 | def retrieve(self, _id): pass 81 | 82 | @abc.abstractmethod 83 | def retrieve_all(self): pass 84 | 85 | @abc.abstractmethod 86 | def update(self, obj): pass 87 | 88 | @abc.abstractmethod 89 | def delete(self, obj): pass 90 | 91 | @abc.abstractmethod 92 | def find_by(self, attributes: dict): pass 93 | 94 | 95 | class DAOSingleton(metaclass=ABCSingleton): 96 | """ 97 | Data Access Object is an Abstract Class 98 | Must be initialized using 'DAO.register()' 99 | """ 100 | 101 | @abc.abstractmethod 102 | def create(self, obj): pass 103 | 104 | @abc.abstractmethod 105 | def retrieve(self, _id): pass 106 | 107 | @abc.abstractmethod 108 | def retrieve_all(self): pass 109 | 110 | @abc.abstractmethod 111 | def update(self, obj): pass 112 | 113 | @abc.abstractmethod 114 | def delete(self, obj): pass 115 | 116 | @abc.abstractmethod 117 | def find_by(self, attributes: dict): pass 118 | 119 | 120 | class DAOFactory(metaclass=ABCSingleton): 121 | 122 | @abc.abstractmethod 123 | def __init__(self): pass 124 | 125 | @abc.abstractmethod 126 | def get_instance(self, cls) -> DAO: pass 127 | 128 | 129 | class DAOI(DAO): 130 | """ 131 | Generic interface for all DAO implementations 132 | This class garantees an interface between data objects and odbcs or orms connectors 133 | """ 134 | 135 | def __init__(self, dao): 136 | if not isinstance(dao, DAO): 137 | raise 138 | self._dao = dao 139 | 140 | def create(self, obj): 141 | if not isinstance(obj, Model): 142 | raise 143 | return self._dao.create(obj) 144 | 145 | def retrieve(self, _id=None) -> object: 146 | return self._dao.retrieve(_id) 147 | 148 | def retrieve_all(self) -> list: 149 | return self._dao.retrieve_all() 150 | 151 | def update(self, obj): 152 | if not isinstance(obj, Model): 153 | raise 154 | return self._dao.update(obj) 155 | 156 | def delete(self, obj): 157 | if not isinstance(obj, Model): 158 | raise 159 | return self._dao.delete(obj) 160 | -------------------------------------------------------------------------------- /energyweb/interfaces.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import inspect 3 | 4 | 5 | class Serializable(object): 6 | """ 7 | Object serialization helper 8 | """ 9 | def __iter__(self): 10 | for attr, value in self.__dict__.items(): 11 | if isinstance(value, datetime.datetime): 12 | iso = value.isoformat() 13 | yield attr, iso 14 | elif hasattr(value, '__iter__'): 15 | if hasattr(value, 'pop'): 16 | a = [] 17 | for item in value: 18 | if hasattr(item, '__iter__'): 19 | a.append(dict(item)) 20 | else: 21 | a.append(item) 22 | yield attr, a 23 | else: 24 | yield attr, dict(value) 25 | else: 26 | yield attr, value 27 | 28 | def to_dict(self): 29 | result = {} 30 | init = getattr(self, '__init__') 31 | for parameter in inspect.signature(init).parameters: 32 | att = getattr(self, parameter) 33 | if isinstance(att, list) or isinstance(att, set): 34 | att = [self.to_dict_or_self(o) for o in att] 35 | if isinstance(att, (datetime.datetime, datetime.date)): 36 | att = att.isoformat() 37 | result[parameter] = self.to_dict_or_self(att) 38 | return result 39 | 40 | @staticmethod 41 | def to_dict_or_self(obj): 42 | to_dict = getattr(obj, 'to_dict', None) 43 | if to_dict: 44 | return to_dict() 45 | else: 46 | return obj 47 | 48 | 49 | class ExternalData(Serializable): 50 | """ 51 | Encapsulates collected data in a traceable fashion 52 | """ 53 | 54 | def __init__(self, access_epoch, raw): 55 | """ 56 | :param access_epoch: Time the external API was accessed 57 | :param raw: Raw data collected in the device 58 | """ 59 | self.access_epoch = access_epoch 60 | self.raw = raw 61 | 62 | 63 | class IntegrationPoint: 64 | """ 65 | Interface to enforce correct return type and standardized naming 66 | """ 67 | def read_state(self, *args, **kwargs) -> ExternalData: 68 | """ 69 | Establishes a connection to the integration medium and returns the latest state 70 | :rtype: ExternalData 71 | """ 72 | raise NotImplementedError 73 | 74 | def write_state(self, *args, **kwargs) -> ExternalData: 75 | """ 76 | Establishes a connection to the integration medium and persists data 77 | :rtype: ExternalData 78 | """ 79 | raise NotImplementedError 80 | 81 | 82 | class BlockchainClient: 83 | """ 84 | Abstract blockchain client abstraction 85 | """ 86 | 87 | def is_synced(self) -> bool: 88 | """ 89 | Compares latest block from peers with client's last synced block. 90 | :return: Synced status 91 | :rtype: bool 92 | """ 93 | raise NotImplementedError 94 | 95 | def call(self, address: str, contract_name: str, method_name: str, password: str, args=None) -> dict: 96 | """ 97 | Calls a method in a smart-contract 98 | Sends a transaction to the Blockchain and awaits for mining until a receipt is returned. 99 | :param address: Contract address 100 | :param contract_name: Name of the contract in contracts 101 | :param method_name: Use the same name as found in the contract abi 102 | :param password: String of the raw password 103 | :param args: Method parameters 104 | :return: Transaction receipt 105 | :rtype: dict 106 | """ 107 | raise NotImplementedError 108 | 109 | def send(self, address: str, contract_name: str, method_name: str, password: str, args=None) -> dict: 110 | """ 111 | Send a transaction to execute a method in a smart-contract 112 | Sends a transaction to the Blockchain and awaits for mining until a receipt is returned. 113 | :param address: Contract address 114 | :param contract_name: Name of the contract in contracts 115 | :param method_name: Use the same name as found in the contract abi 116 | :param password: String of the raw password 117 | :param args: Method parameters 118 | :return: Transaction receipt 119 | :rtype: dict 120 | """ 121 | raise NotImplementedError 122 | 123 | def mint(self, energy: ExternalData) -> dict: 124 | """ 125 | Mint the measured energy in the blockchain smart-contract 126 | """ 127 | raise NotImplementedError 128 | 129 | -------------------------------------------------------------------------------- /energyweb/smart_contract/origin/asset_reg_v1.py: -------------------------------------------------------------------------------- 1 | """ 2 | Asset Registry Abstract Smart-Contract interface 3 | """ 4 | contract = { 5 | "address": "0x", 6 | "abi": [ 7 | {'constant': True, 'inputs': [], 'name': 'db', 'outputs': [{'name': '', 'type': 'address'}], 'payable': False, 8 | 'stateMutability': 'view', 'type': 'function'}, 9 | {'constant': True, 'inputs': [], 'name': 'cooContract', 'outputs': [{'name': '', 'type': 'address'}], 10 | 'payable': False, 'stateMutability': 'view', 'type': 'function'}, 11 | {'constant': True, 'inputs': [{'name': '_role', 'type': 'uint8'}, {'name': '_caller', 'type': 'address'}], 12 | 'name': 'isRole', 'outputs': [{'name': '', 'type': 'bool'}], 'payable': False, 'stateMutability': 'view', 13 | 'type': 'function'}, {'anonymous': False, 'inputs': [{'indexed': False, 'name': 'sender', 'type': 'address'}, 14 | {'indexed': True, 'name': '_assetId', 'type': 'uint256'}], 15 | 'name': 'LogAssetCreated', 'type': 'event'}, 16 | {'anonymous': False, 'inputs': [{'indexed': True, 'name': '_assetId', 'type': 'uint256'}], 17 | 'name': 'LogAssetFullyInitialized', 'type': 'event'}, 18 | {'anonymous': False, 'inputs': [{'indexed': True, 'name': '_assetId', 'type': 'uint256'}], 19 | 'name': 'LogAssetSetActive', 'type': 'event'}, 20 | {'anonymous': False, 'inputs': [{'indexed': True, 'name': '_assetId', 'type': 'uint256'}], 21 | 'name': 'LogAssetSetInactive', 'type': 'event'}, 22 | {'constant': False, 'inputs': [], 'name': 'createAsset', 'outputs': [], 'payable': False, 23 | 'stateMutability': 'nonpayable', 'type': 'function'}, 24 | {'constant': False, 'inputs': [{'name': '_dbAddress', 'type': 'address'}], 'name': 'init', 'outputs': [], 25 | 'payable': False, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': False, 'inputs': [ 26 | {'name': '_assetId', 'type': 'uint256'}, {'name': '_country', 'type': 'bytes32'}, 27 | {'name': '_region', 'type': 'bytes32'}, {'name': '_zip', 'type': 'bytes32'}, 28 | {'name': '_city', 'type': 'bytes32'}, {'name': '_street', 'type': 'bytes32'}, 29 | {'name': '_houseNumber', 'type': 'bytes32'}, {'name': '_gpsLatitude', 'type': 'bytes32'}, 30 | {'name': '_gpsLongitude', 'type': 'bytes32'}], 'name': 'initLocation', 'outputs': [], 'payable': False, 31 | 'stateMutability': 'nonpayable', 32 | 'type': 'function'}, 33 | {'constant': False, 'inputs': [{'name': '_assetId', 'type': 'uint256'}, {'name': '_active', 'type': 'bool'}], 34 | 'name': 'setActive', 'outputs': [], 'payable': False, 'stateMutability': 'nonpayable', 'type': 'function'}, 35 | {'constant': False, 'inputs': [{'name': '_newLogic', 'type': 'address'}], 'name': 'update', 'outputs': [], 36 | 'payable': False, 'stateMutability': 'nonpayable', 'type': 'function'}, 37 | {'constant': True, 'inputs': [{'name': '_assetId', 'type': 'uint256'}], 'name': 'getActive', 38 | 'outputs': [{'name': '', 'type': 'bool'}], 'payable': False, 'stateMutability': 'view', 'type': 'function'}, 39 | {'constant': True, 'inputs': [{'name': '_assetId', 'type': 'uint256'}], 'name': 'getLastSmartMeterReadFileHash', 40 | 'outputs': [{'name': 'datalog', 'type': 'bytes32'}], 'payable': False, 'stateMutability': 'view', 41 | 'type': 'function'}, 42 | {'constant': True, 'inputs': [], 'name': 'getAssetListLength', 'outputs': [{'name': '', 'type': 'uint256'}], 43 | 'payable': False, 'stateMutability': 'view', 'type': 'function'}, 44 | {'constant': True, 'inputs': [{'name': '_assetId', 'type': 'uint256'}], 'name': 'getAssetLocation', 45 | 'outputs': [{'name': 'country', 'type': 'bytes32'}, {'name': 'region', 'type': 'bytes32'}, 46 | {'name': 'zip', 'type': 'bytes32'}, {'name': 'city', 'type': 'bytes32'}, 47 | {'name': 'street', 'type': 'bytes32'}, {'name': 'houseNumber', 'type': 'bytes32'}, 48 | {'name': 'gpsLatitude', 'type': 'bytes32'}, {'name': 'gpsLongitude', 'type': 'bytes32'}], 49 | 'payable': False, 'stateMutability': 'view', 'type': 'function'}, {'constant': False, 'inputs': [ 50 | {'name': '_assetId', 'type': 'uint256'}, {'name': '_newSmartMeter', 'type': 'address'}], 51 | 'name': 'updateSmartMeter', 'outputs': [], 52 | 'payable': False, 53 | 'stateMutability': 'nonpayable', 54 | 'type': 'function'}], 55 | "bytecode": '0x' 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Energyweb 2 | 3 | [![](https://img.shields.io/pypi/v/ew-link-bond.svg)](https://warehouse.python.org/project/ew-link-bond/) 4 | [![](https://img.shields.io/pypi/l/ew-link-bond.svg)](https://warehouse.python.org/project/ew-link-bond/) 5 | 6 | ## Summary 7 | 8 | Energyweb is an Application framework designed to empower energy prosumers and utilities to leverage Blockchain and other cutting edge cryptographic solutions. Just install it as a dependency with `pip3 install energyweb`. Create a python script and create your own **App** and **Tasks** importing classes from `energyweb.dispatcher` module. 9 | 10 | Further development and contribution is much welcome, please contribute with issues and pull requests. :) 11 | 12 | ## Features 13 | - Raw transactions signing 14 | - Extension and reusability through OOP 15 | - Event-loop logic with asynchronous I/O thread pool control 16 | - General application abstraction 17 | - General Ethereum VM based network client abstraction 18 | - Tested on [_Parity_](https://www.parity.io/ethereum/) and [_Geth_](https://github.com/ethereum/go-ethereum/wiki/geth) 19 | - [EWF's Origin](https://github.com/energywebfoundation/ew-origin) release A smart-contract support for energy consumption and production registry for [REC](https://en.wikipedia.org/wiki/Renewable_Energy_Certificate_(United_States)) generation 20 | 21 | #### Supported Energy Data Sources 22 | 23 | - [Verbund Eumel v1.0](https://www.verbund.com/de-at/privatkunden/themenwelten/wiki/smart-meter) 24 | - [Verbund Eumel v2.1.1](https://www.verbund.com/de-at/privatkunden/themenwelten/wiki/smart-meter) 25 | - [EWF's Energy API](https://github.com/energywebfoundation/ew-link-bond/blob/master/docs/api_contract.yaml) 26 | - [Wattime CO2 Emission API v1.0](https://api.watttime.org/docs/) 27 | - [Wattime CO2 Emission API v2.0](https://api.watttime.org/docs/) 28 | 29 | ### Roadmap 30 | - General EVM Smart-Contracts Event listener task trigger 31 | - Remote logging in cloud platform. Check a list [here](https://www.capterra.com/sem-compare/log-management-software). 32 | - Message Queue for off-line resilience 33 | - Merkle-tree proofs for collected data. Check [precise proofs](https://medium.com/centrifuge/introducing-precise-proofs-create-validate-field-level-merkle-proofs-a31af9220df0) and [typescript implementation](https://github.com/slockit/precise-proofs). 34 | - Field-level validation 35 | - Document integrity validation 36 | - Document structure enforcement 37 | - IPFS storage 38 | - UUID pre-calculation and validation for smart-contract integration 39 | - Enforce TLS/SSL over http 40 | - ARM TEE support: 41 | - Offloaded cryptography to hardware accelerators 42 | - Storage and access of the configuration file in secure enclave 43 | - Private key generation and signature in CryptoCell 44 | 45 | #### Energy Assets Roadmap 46 | - [Gridx Gridbox](https://gridx.de/produkt/gridbox/) 47 | - [Loxone Miniserver](https://www.loxone.com/enen/products/miniserver-extensions/) 48 | 49 | #### Smart-Contracts Roadmap 50 | - [EWF's Origin](https://github.com/energywebfoundation/ew-origin) release B 51 | - Universal Sharing Network 52 | - EWF's User and Assets Registry 53 | - Your project here - We are open for suggestions! 54 | 55 | ## Framework Architecture 56 | 57 | The application consists of dynamically loading modules from the configuration file. After loading the modules, the main thread will spawn task threads when a trigger event occurs. In case the main thread dies or become a zombie, it must be restarted from an external command. It is the system administrator task to maintain services health, therefore no mitigation technique is applied. 58 | 59 | A __task__ can be of any nature, although it is a best practice that its execution is finite and its resource usage is predictable. This will avoid _concurrence_ between tasks and possible _deadlocks_. 60 | 61 | ### Modules 62 | 63 | A list of short explanations about the modules and libraries that compose the framework. 64 | 65 | __EDS__ **E**nergy **D**ata **S**ources library has modules for supported smart-meter _APIs_ including _energyweb's_ specification written in [Swagger](https://editor.swagger.io), with this any utility or solar/wind farm could bundle many smart-meters and provide a simple [_Restful_](https://en.wikipedia.org/wiki/Representational_state_transfer) API using community provided code in many programming languages. Or even abstract legacy protocols instead of being forced to write a python module. 66 | 67 | __Energyweb__ module contains all abstract classes and interfaces to be inherited and implemented by concrete classes. It is the framework skeleton. 68 | 69 | __Smart_Contract__ library bundles all integration modules and assets to persist and query data on [_EVM_](https://en.wikipedia.org/wiki/Ethereum#Virtual_Machine) based _Blockchains_. Most common assets are *json* files describing _smart-contract_ [_ABI_](https://en.wikipedia.org/wiki/Application_binary_interface) s. 70 | 71 | __Base58__ module is a helper for parsing [Bitcoin](https://github.com/bitcoin/bitcoin) addresses [IPFS](https://github.com/ipfs/ipfs) file hashes. 72 | 73 | __Config__ module has _json_ and _yaml_ formatted application configuration files parsers. App configuration files add better deployment, management, credentials safety and extension capabilities. This module also performs dynamic python module loading, allowing any registered class to be instantiated and parametrized by demand. This feature combined with OOP allows for the same device to be able to change smart-meters and smart-contracts seamlessly. 74 | 75 | __Log__ writes a stream of characters to `stdout` and to files. 76 | 77 | __Storage__ supports EWF's Origin release A log storage, designed to record a sequence of _off-chain_ files by updating the previous file contents SHA hash with the next. It is particularly useful to enforce data integrity, by comparing the sequence of raw smart-meter readings with the sequence registered _on-chain_. 78 | 79 | __Dispatcher__ module is helper for handling asynchronous non I/O blocking threads of event triggered tasks. Also know as or [event loop](https://en.wikipedia.org/wiki/Event_loop) it is the framework's main loop skeleton. 80 | 81 | Event loop abstraction: 82 | 83 | ![Event Loop](https://github.com/energywebfoundation/ew-link-bond/blob/master/docs/media/threads.jpg) 84 | 85 | ## Example App 86 | ```python 87 | import energyweb 88 | import datetime 89 | 90 | class MyTask(energyweb.Task): 91 | """ 92 | Example Task 93 | """ 94 | 95 | def coroutine(self): 96 | print('Task {}: {}\n'.format(self.interval, datetime.datetime.now())) 97 | 98 | 99 | class MyApp(energyweb.App): 100 | """ 101 | Example Application 102 | """ 103 | 104 | def prepare(self): 105 | print('{} Prepared'.format(self.__class__.__name__)) 106 | 107 | def configure(self): 108 | t1 = MyTask(interval=energyweb.LifeCycle.FIVE_SECONDS) 109 | t2 = MyTask(interval=energyweb.LifeCycle.ONE_MINUTE, is_eager=False) 110 | [self.add_task(t) for t in [t2, t1]] 111 | 112 | def finish(self): 113 | print('{} Finished'.format(self.__class__.__name__)) 114 | 115 | 116 | app = MyApp() 117 | 118 | """ 119 | Test loop 120 | """ 121 | if __name__ == '__main__': 122 | app.run() 123 | ``` 124 | -------------------------------------------------------------------------------- /energyweb/eds/energy_api.py: -------------------------------------------------------------------------------- 1 | """ 2 | Interface for the Bond API 3 | - Bond API delivers prosumer data 4 | """ 5 | import calendar 6 | import datetime 7 | import json 8 | import requests 9 | 10 | from energyweb.eds.interfaces import EnergyDevice, EnergyData 11 | 12 | 13 | class BondAPIv1(EnergyDevice): 14 | 15 | def __init__(self, base_url, source, device_id, user=None, password=None): 16 | """ 17 | Bond api spec v1.x module 18 | 19 | :param base_url: Must start with protocol. ie. https://my-url 20 | :param source: Must be 'produced' or 'consumed' 21 | :param device_id: Please mind it will be url encoded. 22 | :param user: User credential. 23 | :param password: Password for basic authentication method. 24 | """ 25 | if source not in ('produced', 'consumed'): 26 | raise AssertionError 27 | self.base_url = base_url 28 | self.api_url = '{}/{}/{}'.format(base_url, source, device_id) 29 | self.auth = (user, password) 30 | super().__init__( 31 | manufacturer='Slock.it', 32 | model='Virtual Energy Meter', 33 | serial_number='0001000', 34 | energy_unit='KILOWATT_HOUR', 35 | is_accumulated=False) 36 | # TODO get from device metadata from API call 37 | 38 | def read_state(self, start=None, end=None) -> EnergyData: 39 | # raw 40 | raw, data, measurement_list = self._reach_source(self.api_url, start, end) 41 | # device 42 | device_meta = data[-1]['device'] 43 | device = EnergyDevice(**device_meta) 44 | # accumulated energy in Wh 45 | if device.is_accumulated: 46 | energy = self.to_wh(measurement_list[-1]['energy'], device.energy_unit) 47 | else: 48 | energy = self.to_wh(sum(i['energy'] for i in measurement_list), device.energy_unit) 49 | # access_epoch 50 | now = datetime.datetime.now().astimezone() 51 | access_epoch = calendar.timegm(now.timetuple()) 52 | # measurement epoch 53 | measurement_time = datetime.datetime.strptime(measurement_list[-1]['measurement_time'], "%Y-%m-%dT%H:%M:%S%z") 54 | measurement_epoch = calendar.timegm(measurement_time.timetuple()) 55 | return EnergyData(device=device, access_epoch=access_epoch, raw=raw, energy=energy, 56 | measurement_epoch=measurement_epoch) 57 | 58 | def write_state(self, *args, **kwargs) -> EnergyData: 59 | raise NotImplementedError 60 | 61 | def _reach_source(self, url, start=None, end=None, have_next=False) -> (str, dict): 62 | marginal_query = None 63 | if not have_next: 64 | # calculate the current time and one hour back from there 65 | end_date = datetime.datetime.now(datetime.timezone.utc) if not end else end 66 | start_date = end_date - datetime.timedelta(hours=1, minutes=15) if not start else start 67 | marginal_query = { 68 | 'start': start_date.astimezone().isoformat(), # expects year-month-day 69 | 'end': end_date.isoformat(), # the hour of day 70 | 'limit': 10 71 | } 72 | http_packet = requests.get(url=url, params=marginal_query, auth=self.auth) 73 | raw = http_packet.content.decode() 74 | if not http_packet.ok: 75 | raise EnvironmentError 76 | data = http_packet.json() 77 | return self._parse_source(raw, data) 78 | 79 | def _parse_source(self, raw: str, data: dict): 80 | measurement_list = data['measuredEnergy'] 81 | if 'next' in data and data['next']: 82 | _, __, ___ = self._reach_source(self.base_url + data['next'], have_next=True) 83 | return tuple(x + y for x, y in zip(([raw], [data], measurement_list), (_, __, ___))) 84 | return [raw], [data], measurement_list 85 | 86 | 87 | # TODO: Add Tox tests for this module 88 | class BondAPIv1TestDevice1(BondAPIv1): 89 | """ 90 | Data parsing test fixture 91 | """ 92 | 93 | def _reach_source(self, url, start=None, end=None, have_next=False) -> (str, dict): 94 | raw = { 95 | "base_url/produced/0": '{"count": 0, "previous": null, "next": "/second", "device": {"manufacturer": "Siemens", "model": "ABC-123", "serial_number": "345345345", "latitude": "54.443567", "longitude": "-23.312543", "energy_unit": "kilowatt_hour", "is_accumulated": true }, "measuredEnergy": [{"energy": 100, "measurement_time": "2018-03-15T10:30:00+00:00"}, {"energy": 200, "measurement_time": "2018-03-15T12:30:00+00:00"}, {"energy": 250, "measurement_time": "2018-03-15T14:30:00+00:00"} ] }', 96 | "base_url/second": '{"count": 1, "previous": "first", "next": null, "device": {"manufacturer": "Siemens", "model": "ABC-123", "serial_number": "345345345", "latitude": "54.443567", "longitude": "-23.312543", "energy_unit": "kilowatt_hour", "is_accumulated": true }, "measuredEnergy": [{"energy": 390, "measurement_time": "2018-03-15T16:30:00+00:00"}, {"energy": 400, "measurement_time": "2018-03-15T18:30:00+00:00"} ] }' 97 | } 98 | data = json.loads(raw[url]) 99 | return self._parse_source(str(raw), data) 100 | 101 | 102 | class BondAPIv1TestDevice2(BondAPIv1): 103 | """ 104 | Data parsing test fixture 105 | """ 106 | 107 | def _reach_source(self, url, start=None, end=None, have_next=False) -> (str, dict): 108 | raw = { 109 | "base_url/produced/0": '{"count": 0, "previous": null, "next": "/second", "device": {"manufacturer": "Siemens", "model": "ABC-123", "serial_number": "345345345", "latitude": "54.443567", "longitude": "-23.312543", "energy_unit": "watt_hour", "is_accumulated": false }, "measuredEnergy": [{"energy": 12304, "measurement_time": "2018-03-15T10:30:00+00:00"}, {"energy": 8568, "measurement_time": "2018-03-15T12:30:00+00:00"}, {"energy": 63456, "measurement_time": "2018-03-15T14:30:00+00:00"} ] }', 110 | "base_url/second": '{"count": 1, "previous": "/first", "next": null, "device": {"manufacturer": "Siemens", "model": "ABC-123", "serial_number": "345345345", "latitude": "54.443567", "longitude": "-23.312543", "energy_unit": "watt_hour", "is_accumulated": false }, "measuredEnergy": [{"energy": 0, "measurement_time": "2018-03-15T16:30:00+00:00"}, {"energy": 265, "measurement_time": "2018-03-15T18:30:00+00:00"} ] }' 111 | } 112 | data = json.loads(raw[url]) 113 | return self._parse_source(str(raw), data) 114 | 115 | 116 | class BondAPIv1TestDevice3(BondAPIv1): 117 | """ 118 | Data parsing test fixture 119 | """ 120 | 121 | def _reach_source(self, url, start=None, end=None, have_next=False) -> (str, dict): 122 | raw = { 123 | "base_url/produced/0": '{"count": 0, "previous": null, "next": null, "device": {"manufacturer": "Siemens", "model": "ABC-123", "serial_number": "345345345", "latitude": "54.443567", "longitude": "-23.312543", "energy_unit": "megawatt_hour", "is_accumulated": false }, "measuredEnergy": [{"energy": 12304, "measurement_time": "2018-03-15T10:30:00+00:00"}, {"energy": 8568, "measurement_time": "2018-03-15T12:30:00+00:00"}, {"energy": 6, "measurement_time": "2018-03-15T14:30:00+00:00"} ] }' 124 | } 125 | data = json.loads(raw[url]) 126 | return self._parse_source(str(raw), data) 127 | 128 | 129 | if __name__ == '__main__': 130 | 131 | d1 = BondAPIv1TestDevice1("base_url", "produced", 0) 132 | print(d1.read_state().to_dict()) 133 | 134 | d1 = BondAPIv1TestDevice2("base_url", "produced", 0) 135 | print(d1.read_state().to_dict()) 136 | 137 | d1 = BondAPIv1TestDevice3("base_url", "produced", 0) 138 | print(d1.read_state().to_dict()) 139 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /energyweb/carbonemission.py: -------------------------------------------------------------------------------- 1 | """ 2 | Library containing the implementations of CO2 oracles integration classes 3 | """ 4 | import calendar 5 | import datetime 6 | import requests 7 | 8 | from energyweb.interfaces import ExternalData, IntegrationPoint 9 | 10 | 11 | class CarbonEmissionData(ExternalData): 12 | """ 13 | Standard for collected carbon emission data, to be transformed into mintable data. 14 | """ 15 | 16 | def __init__(self, access_epoch, raw, accumulated_co2, measurement_epoch): 17 | """ 18 | Minimum set of data for power measurement logging. 19 | :param access_epoch: Time the external API was accessed 20 | :param raw: Raw data collected 21 | :param accumulated_co2: Registered in kg of carbon dioxide 22 | :param measurement_epoch: Time of measurement at the source 23 | """ 24 | self.accumulated_co2 = accumulated_co2 25 | self.measurement_epoch = measurement_epoch 26 | ExternalData.__init__(self, access_epoch, raw) 27 | 28 | 29 | class CarbonEmissionAPI(IntegrationPoint): 30 | """ 31 | Carbon emission endpoint data interface 32 | """ 33 | def read_state(self, *args, **kwargs) -> CarbonEmissionData: 34 | """ 35 | Establishes a connection to the integration medium and returns the latest state 36 | :rtype: ExternalData 37 | """ 38 | raise NotImplementedError 39 | 40 | def write_state(self, *args, **kwargs) -> ExternalData: 41 | """ 42 | Establishes a connection to the integration medium and persists data 43 | :rtype: ExternalData 44 | """ 45 | raise NotImplementedError 46 | 47 | 48 | class WattimeV1(CarbonEmissionAPI): 49 | 50 | def __init__(self, usr: str, pwd: str, ba: str, hours_from_now: int = 2): 51 | """ 52 | Wattime API credentials. http://watttime.org/ 53 | :param usr: Username used for login 54 | :param pwd: Users password 55 | :param ba: Balancing Authority. https://api.watttime.org/tutorials/#ba 56 | :param hours_from_now: Hours from the current time to check for CO emission. If none provided, will \ 57 | get current day. 58 | """ 59 | self.credentials = {'username': usr, 'password': pwd} 60 | self.api_url = 'https://api.watttime.org/api/v1/' 61 | self.ba = ba 62 | self.hours_from_now = hours_from_now 63 | 64 | def read_state(self) -> CarbonEmissionData: 65 | """ 66 | Reach wattime api, parse and convert to CarbonEmissionData. 67 | """ 68 | auth_token = self.__get_auth_token() 69 | # 2. Fetch marginal data 70 | raw = self.__get_marginal(auth_token) 71 | # 3. Converts lb/MW to kg/W 72 | accumulated_co2 = raw['marginal_carbon']['value'] * 0.453592 * pow(10, -6) 73 | # 4. Converts time stamps to epoch 74 | now = datetime.datetime.now() 75 | access_epoch = calendar.timegm(now.timetuple()) 76 | measurement_timestamp = datetime.datetime.strptime(raw['timestamp'], "%Y-%m-%dT%H:%M:%SZ") 77 | measurement_epoch = calendar.timegm(measurement_timestamp.timetuple()) 78 | return CarbonEmissionData(access_epoch, raw, accumulated_co2, measurement_epoch) 79 | 80 | def write_state(self, *args, **kwargs) -> ExternalData: 81 | raise NotImplementedError 82 | 83 | def __get_auth_token(self) -> str: 84 | """ 85 | Exchange credentials for an access token. 86 | :return: Access token string suitable for passing as arg in other methods. 87 | """ 88 | endpoint = self.api_url + 'obtain-token-auth/' 89 | r = requests.post(endpoint, data=self.credentials) 90 | if not r.status_code == 200: 91 | raise AttributeError('Failed getting a new token.') 92 | ans = r.json() 93 | if len(ans['token']) < 5: 94 | raise AttributeError('Failed getting a new token.') 95 | return ans['token'] 96 | 97 | def __get_marginal(self, auth_token: str) -> dict: 98 | """ 99 | Gets marginal carbon emission based on real time energy source mix of the grid. 100 | :param auth_token: authentication token 101 | :return: Measured data in lb/MW plus other relevant raw metadata. 102 | """ 103 | base_time = datetime.datetime.now() 104 | if self.hours_from_now: 105 | start_time = base_time - datetime.timedelta(hours=self.hours_from_now) 106 | start_at = start_time.strftime("%Y-%m-%dT%H:00:00") 107 | end_at = base_time.strftime("%Y-%m-%dT%H:%M:%S") 108 | else: 109 | start_at = base_time.strftime("%Y-%m-%dT00:00:00") 110 | end_at = base_time.strftime("%Y-%m-%dT23:59:59") 111 | 112 | marginal_query = { 113 | 'ba': self.ba, 114 | 'start_at': start_at, 115 | 'end_at': end_at, 116 | 'page_size': 1, 117 | 'market': 'RTHR' 118 | } 119 | endpoint = self.api_url + 'marginal/' 120 | h = {'Authorization': 'Token ' + auth_token} 121 | r = requests.get(endpoint, headers=h, params=marginal_query) 122 | ans = r.json() 123 | if 'count' not in ans.keys() and 'detail' in ans.keys(): 124 | raise AttributeError('Failed to login on api.') 125 | if ans['count'] < 1: 126 | raise AttributeError('Empty response from api.') 127 | return ans['results'][0] 128 | 129 | def get_ba(self, lon, lat, auth_token) -> str: 130 | """ 131 | Fetch Balancing Authority data based on geo spatial coordinates. 132 | :param lon: longitude 133 | :param lat: latitude 134 | :param auth_token: authentication token 135 | :return: Abbreviated ba name suitable for marginal requests. 136 | """ 137 | geo_query = { 138 | 'type': 'Point', 139 | 'coordinates': [lon, lat] 140 | } 141 | endpoint = self.api_url + 'balancing_authorities/' 142 | h = {'token': auth_token} 143 | r = requests.get(endpoint, headers=h, params=geo_query) 144 | ans = r.json() 145 | return ans['abbrev'] 146 | 147 | 148 | class WattimeV2(CarbonEmissionAPI): 149 | 150 | def __init__(self, usr: str, pwd: str, ba: str): 151 | """ 152 | Wattime API credentials. http://watttime.org/ 153 | :param usr: Username used for login 154 | :param pwd: Users password 155 | :param ba: Balancing Authority. https://api.watttime.org/tutorials/#ba 156 | """ 157 | self.credentials = (usr, pwd) 158 | self.api_url = 'https://api2.watttime.org/v2test/' 159 | self.ba = ba 160 | 161 | def read_state(self) -> CarbonEmissionData: 162 | """ 163 | Reach wattime api, parse and convert to CarbonEmissionData. 164 | """ 165 | auth_token = self.__get_auth_token() 166 | # 2. Fetch marginal data 167 | raw = self.__get_marginal(auth_token) 168 | # 3. Converts lb/MW to kg/W 169 | accumulated_co2 = raw['avg'] * 0.453592 * pow(10, -6) 170 | # 4. Converts time stamps to epoch 171 | now = datetime.datetime.now() 172 | access_epoch = calendar.timegm(now.timetuple()) 173 | measurement_timestamp = now 174 | measurement_epoch = calendar.timegm(measurement_timestamp.timetuple()) 175 | return CarbonEmissionData(access_epoch, raw, accumulated_co2, measurement_epoch) 176 | 177 | def write_state(self, *args, **kwargs) -> ExternalData: 178 | raise NotImplementedError 179 | 180 | def __get_auth_token(self) -> str: 181 | """ 182 | Exchange credentials for an access token. 183 | :return: Access token string suitable for passing as arg in other methods. 184 | """ 185 | endpoint = self.api_url + 'login' 186 | r = requests.get(endpoint, auth=self.credentials) 187 | if not r.status_code == 200: 188 | raise AttributeError('Failed getting a new token.') 189 | ans = r.json() 190 | if len(ans['token']) < 5: 191 | raise AttributeError('Failed getting a new token.') 192 | return ans['token'] 193 | 194 | def __get_marginal(self, auth_token: str) -> dict: 195 | """ 196 | Gets marginal carbon emission based on real time energy source mix of the grid. 197 | :param auth_token: authentication token 198 | :return: Measured data in lb/MW plus other relevant raw metadata. 199 | """ 200 | marginal_query = { 201 | 'ba': self.ba 202 | } 203 | endpoint = self.api_url + 'insight/' 204 | h = {'Authorization': 'Bearer ' + auth_token} 205 | r = requests.get(endpoint, headers=h, params=marginal_query) 206 | if not r.status_code == 200: 207 | raise AttributeError('Failed to login on api.') 208 | return r.json() 209 | -------------------------------------------------------------------------------- /energyweb/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Configuration file parser and app descriptor 3 | """ 4 | import importlib 5 | from collections import namedtuple 6 | from enum import Enum 7 | 8 | from energyweb.carbonemission import CarbonEmissionAPI 9 | from energyweb.eds.interfaces import EnergyDevice 10 | from energyweb.smart_contract.interfaces import EVMSmartContractClient 11 | 12 | Module = namedtuple('Module', ['module', 'class_name', 'parameters']) 13 | 14 | 15 | class MODULES(Enum): 16 | OriginAPIv1 = Module('energyweb.smart_meter.energy_api', 'OriginAPIv1', {}) 17 | OriginAPIv1Test = Module('energyweb.smart_meter.energy_api', 'OriginAPIv1', {'url': '', 'source': 'consumed', 'device_id': 0}) 18 | ConsumerTask = Module('energyweb.smart_meter.energy_api', 'OriginAPIv1', {}) 19 | ProducerTask = Module('energyweb.smart_meter.energy_api', 'OriginAPIv1', {}) 20 | 21 | 22 | class EnergyAssetConfiguration: 23 | """ 24 | Represent Energy Asset configurations for producer and consumers 25 | """ 26 | def __init__(self, name: str, energy_meter: EnergyDevice, asset: EVMSmartContractClient): 27 | if not isinstance(energy_meter, EnergyDevice): 28 | raise ConfigurationFileError('Energy_meter must be of an EnergyDevice class or subclass.') 29 | if len(name) < 2: 30 | raise ConfigurationFileError('Name must be longer than two characters.') 31 | self.name = name 32 | self.meter = energy_meter 33 | self.asset = asset 34 | 35 | 36 | class GreenEnergyProducerConfiguration(EnergyAssetConfiguration): 37 | """ 38 | Represents special case of energy producer 39 | """ 40 | def __init__(self, name: str, energy_meter: EnergyDevice, carbon_emission: CarbonEmissionAPI, 41 | asset: EVMSmartContractClient): 42 | if not isinstance(carbon_emission, CarbonEmissionAPI): 43 | raise ConfigurationFileError('Carbon_emission must be of an CarbonEmissionAPI class or subclass.') 44 | self.carbon_emission = carbon_emission 45 | super().__init__(name=name, energy_meter=energy_meter, asset=asset) 46 | 47 | 48 | class BondConfiguration: 49 | """ 50 | Bond configuration file format 51 | :version v0.3.7 52 | energyweb: 1.0 53 | info: 54 | title: App Title 55 | command: My very nice energyweb embedded app 56 | version: 1.0 alpha 57 | task_config: 58 | debugger: on 59 | verbosity: low 60 | tasks: 61 | """ 62 | class AppInfo: 63 | def __init__(self, title: str, description: str, version: str): 64 | self.title = title 65 | self.description = description 66 | self.version = version 67 | 68 | class AppConfig: 69 | def __init__(self, debugger: str, verbosity: str, **kwargs): 70 | self.debugger = debugger 71 | self.verbosity = verbosity 72 | self.__dict__.update(kwargs) 73 | 74 | class Submodule: 75 | def __init__(self, module: str, **kwargs): 76 | self.module = module 77 | self.__dict__.update(kwargs) 78 | 79 | def __init__(self, energyweb: str, info: dict, config: dict, tasks: [dict]): 80 | if len(tasks) < 1: 81 | raise ConfigurationFileError('Should contain at least one valid task.') 82 | self.energyweb = energyweb 83 | self.tasks = tasks 84 | self.config = self.AppConfig(config) 85 | self.info = self.AppInfo(info) 86 | 87 | # TODO: Refactor origin config and decide if it lives here or in origin app 88 | # def parse_bond_app(configuration_file_path: str) -> BondConfiguration: 89 | # """ 90 | # :param configuration_file_path: Configuration file parsed as a dictionary. 91 | # :return: BondConfiguration 92 | # """ 93 | # from ruamel.yaml import YAML 94 | # yaml = YAML() # default, if not specfied, is 'rt' (round-trip) 95 | # try: 96 | # config = yaml.load(open(configuration_file_path)) 97 | # 98 | # version = config['bond_version'] 99 | # tasks = [__parse_instance(task) for task in config['tasks']] 100 | # configuration = BondConfiguration(bond_version=version, tasks=tasks) 101 | # except Exception: 102 | # raise ConfigurationFileError 103 | # return configuration 104 | 105 | 106 | class ConfigurationFileError(Exception): 107 | """ 108 | Custom Exception to deal with non-conforming configuration files 109 | """ 110 | def __init__(self, msg: str = None): 111 | if not msg: 112 | super().__init__('Configuration file contain errors. Please provide valid Consumer and/or Producer data.') 113 | super().__init__(msg) 114 | 115 | 116 | class EnergywebAppConfiguration: 117 | """ 118 | Abstract class 119 | """ 120 | pass 121 | 122 | 123 | class CooV1ConsumerConfiguration(EnergywebAppConfiguration): 124 | """ 125 | Represent Energy Asset configurations for producer and consumers 126 | """ 127 | def __init__(self, name: str, energy_meter: EnergyDevice, smart_contract: EVMSmartContractClient): 128 | if not isinstance(energy_meter, EnergyDevice): 129 | raise ConfigurationFileError('Energy_meter must be of an EnergyDevice class or subclass.') 130 | if len(name) < 2: 131 | raise ConfigurationFileError('Name must be longer than two characters.') 132 | self.name = name 133 | self.energy_meter = energy_meter 134 | self.smart_contract = smart_contract 135 | 136 | 137 | class CooV1ProducerConfiguration(CooV1ConsumerConfiguration): 138 | """ 139 | Represents special case of energy producer 140 | """ 141 | def __init__(self, name: str, energy_meter: EnergyDevice, carbon_emission: CarbonEmissionAPI, 142 | smart_contract: EVMSmartContractClient): 143 | if not isinstance(carbon_emission, CarbonEmissionAPI): 144 | raise ConfigurationFileError('Carbon_emission must be of an CarbonEmissionAPI class or subclass.') 145 | self.carbon_emission = carbon_emission 146 | super().__init__(name=name, energy_meter=energy_meter, smart_contract=smart_contract) 147 | 148 | 149 | class CooV1Configuration(EnergywebAppConfiguration): 150 | """ 151 | When using one file configuration for multiple assets 152 | """ 153 | def __init__(self, consumption: [CooV1ConsumerConfiguration], production: [CooV1ConsumerConfiguration]): 154 | self.consumption = consumption 155 | self.production = production 156 | 157 | 158 | def parse_coo_v1(raw_configuration_file: dict) -> CooV1Configuration: 159 | """ 160 | Read and parse Certificatate of Orign v1 (release A) configuration dictionary into structured class instances. 161 | :param raw_configuration_file: Configuration file parsed as a dictionary. 162 | :return: Configuration instance 163 | """ 164 | if not isinstance(raw_configuration_file, dict): 165 | raise AssertionError("Configuration json must be deserialized first.") 166 | is_consuming = 'consumers' in raw_configuration_file 167 | is_producing = 'producers' in raw_configuration_file 168 | if not is_consuming and not is_producing: 169 | raise ConfigurationFileError 170 | 171 | consumption = [__parse_item(config) for config in raw_configuration_file['consumers']] 172 | production = [__parse_item(config) for config in raw_configuration_file['producers']] 173 | return CooV1Configuration(consumption, production) 174 | 175 | 176 | def parse_single_asset(raw_configuration_file: dict) -> CooV1ConsumerConfiguration: 177 | """ 178 | Read and parse single producer or consumer configuration dictionary into structured class instances. 179 | :param raw_configuration_file: Configuration file parsed as a dictionary. 180 | :return: EnergyConsumerConfiguration or EnergyProducerConfiguration 181 | """ 182 | if not isinstance(raw_configuration_file, dict): 183 | raise AssertionError("Configuration json must be deserialized first.") 184 | configuration = __parse_item(raw_configuration_file) 185 | 186 | 187 | def __parse_item(config_item: dict): 188 | """ 189 | Parse each item of the consumers and producers list. 190 | :param config_item: Item of the list 191 | :return: Either Producer or Consumer configuration depending if carbon-emission key is present 192 | """ 193 | item = { 194 | 'energy_meter': __parse_instance(config_item['energy-meter']), 195 | 'smart_contract': __parse_instance(config_item['smart-contract']), 196 | 'name': config_item['name'] 197 | } 198 | if 'carbon-emission' not in config_item: 199 | return CooV1ConsumerConfiguration(**item) 200 | else: 201 | item['carbon_emission'] = __parse_instance(config_item['carbon-emission']) 202 | return CooV1ProducerConfiguration(**item) 203 | 204 | 205 | def __parse_instance(submodule: dict) -> object: 206 | """ 207 | Reflection algorithm to dynamically load python modules referenced on the configuration json. 208 | :param submodule: Configuration dict must have the keys 'module', 'class_name', 'class_parameters'. 209 | :return: Class instance as in task_config file. 210 | """ 211 | module_instance = importlib.import_module(submodule['module']) 212 | class_obj = getattr(module_instance, submodule['class_name']) 213 | class_instance = class_obj(**submodule['class_parameters']) 214 | return class_instance -------------------------------------------------------------------------------- /energyweb/smart_contract/usn/rent_v1.py: -------------------------------------------------------------------------------- 1 | """ 2 | USN RentingSupport Smart-Contract interface 3 | """ 4 | contract = { 5 | "address": "0x85Ec283a3Ed4b66dF4da23656d4BF8A507383bca", 6 | "abi": [ 7 | { 8 | "constant": True, 9 | "inputs": [ 10 | { 11 | "name": "id", 12 | "type": "bytes32" 13 | }, 14 | { 15 | "name": "index", 16 | "type": "uint256" 17 | } 18 | ], 19 | "name": "getState", 20 | "outputs": [ 21 | { 22 | "name": "controller", 23 | "type": "address" 24 | }, 25 | { 26 | "name": "rentedFrom", 27 | "type": "uint64" 28 | }, 29 | { 30 | "name": "rentedUntil", 31 | "type": "uint64" 32 | }, 33 | { 34 | "name": "properties", 35 | "type": "uint128" 36 | } 37 | ], 38 | "payable": False, 39 | "stateMutability": "view", 40 | "type": "function" 41 | }, 42 | { 43 | "constant": True, 44 | "inputs": [ 45 | { 46 | "name": "id", 47 | "type": "bytes32" 48 | } 49 | ], 50 | "name": "getStateCount", 51 | "outputs": [ 52 | { 53 | "name": "", 54 | "type": "uint256" 55 | } 56 | ], 57 | "payable": False, 58 | "stateMutability": "view", 59 | "type": "function" 60 | }, 61 | { 62 | "constant": False, 63 | "inputs": [ 64 | { 65 | "name": "id", 66 | "type": "bytes32" 67 | }, 68 | { 69 | "name": "secondsToRent", 70 | "type": "uint32" 71 | }, 72 | { 73 | "name": "token", 74 | "type": "address" 75 | } 76 | ], 77 | "name": "rent", 78 | "outputs": [], 79 | "payable": True, 80 | "stateMutability": "payable", 81 | "type": "function" 82 | }, 83 | { 84 | "constant": True, 85 | "inputs": [ 86 | { 87 | "name": "id", 88 | "type": "bytes32" 89 | }, 90 | { 91 | "name": "user", 92 | "type": "address" 93 | } 94 | ], 95 | "name": "getRentingState", 96 | "outputs": [ 97 | { 98 | "name": "rentable", 99 | "type": "bool" 100 | }, 101 | { 102 | "name": "free", 103 | "type": "bool" 104 | }, 105 | { 106 | "name": "open", 107 | "type": "bool" 108 | }, 109 | { 110 | "name": "controller", 111 | "type": "address" 112 | }, 113 | { 114 | "name": "rentedUntil", 115 | "type": "uint64" 116 | }, 117 | { 118 | "name": "rentedFrom", 119 | "type": "uint64" 120 | }, 121 | { 122 | "name": "props", 123 | "type": "uint128" 124 | } 125 | ], 126 | "payable": False, 127 | "stateMutability": "view", 128 | "type": "function" 129 | }, 130 | { 131 | "constant": False, 132 | "inputs": [ 133 | { 134 | "name": "id", 135 | "type": "bytes32" 136 | } 137 | ], 138 | "name": "returnObject", 139 | "outputs": [], 140 | "payable": False, 141 | "stateMutability": "nonpayable", 142 | "type": "function" 143 | }, 144 | { 145 | "constant": True, 146 | "inputs": [ 147 | { 148 | "name": "id", 149 | "type": "bytes32" 150 | } 151 | ], 152 | "name": "supportedTokens", 153 | "outputs": [ 154 | { 155 | "name": "addresses", 156 | "type": "address[]" 157 | } 158 | ], 159 | "payable": False, 160 | "stateMutability": "view", 161 | "type": "function" 162 | }, 163 | { 164 | "constant": True, 165 | "inputs": [ 166 | { 167 | "name": "id", 168 | "type": "bytes32" 169 | }, 170 | { 171 | "name": "token", 172 | "type": "address" 173 | } 174 | ], 175 | "name": "tokenReceiver", 176 | "outputs": [ 177 | { 178 | "name": "", 179 | "type": "bytes32" 180 | } 181 | ], 182 | "payable": False, 183 | "stateMutability": "view", 184 | "type": "function" 185 | }, 186 | { 187 | "constant": True, 188 | "inputs": [ 189 | { 190 | "name": "id", 191 | "type": "bytes32" 192 | }, 193 | { 194 | "name": "user", 195 | "type": "address" 196 | }, 197 | { 198 | "name": "secondsToRent", 199 | "type": "uint32" 200 | }, 201 | { 202 | "name": "token", 203 | "type": "address" 204 | } 205 | ], 206 | "name": "price", 207 | "outputs": [ 208 | { 209 | "name": "", 210 | "type": "uint128" 211 | } 212 | ], 213 | "payable": False, 214 | "stateMutability": "view", 215 | "type": "function" 216 | }, 217 | { 218 | "anonymous": False, 219 | "inputs": [ 220 | { 221 | "indexed": True, 222 | "name": "fixFilter", 223 | "type": "bytes32" 224 | }, 225 | { 226 | "indexed": True, 227 | "name": "id", 228 | "type": "bytes32" 229 | }, 230 | { 231 | "indexed": False, 232 | "name": "controller", 233 | "type": "address" 234 | }, 235 | { 236 | "indexed": False, 237 | "name": "rentedFrom", 238 | "type": "uint64" 239 | }, 240 | { 241 | "indexed": False, 242 | "name": "rentedUntil", 243 | "type": "uint64" 244 | }, 245 | { 246 | "indexed": False, 247 | "name": "noReturn", 248 | "type": "bool" 249 | }, 250 | { 251 | "indexed": False, 252 | "name": "amount", 253 | "type": "uint128" 254 | }, 255 | { 256 | "indexed": False, 257 | "name": "token", 258 | "type": "address" 259 | }, 260 | { 261 | "indexed": False, 262 | "name": "properties", 263 | "type": "uint128" 264 | } 265 | ], 266 | "name": "LogRented", 267 | "type": "event" 268 | }, 269 | { 270 | "anonymous": False, 271 | "inputs": [ 272 | { 273 | "indexed": True, 274 | "name": "fixFilter", 275 | "type": "bytes32" 276 | }, 277 | { 278 | "indexed": True, 279 | "name": "id", 280 | "type": "bytes32" 281 | }, 282 | { 283 | "indexed": False, 284 | "name": "controller", 285 | "type": "address" 286 | }, 287 | { 288 | "indexed": False, 289 | "name": "rentedFrom", 290 | "type": "uint64" 291 | }, 292 | { 293 | "indexed": False, 294 | "name": "rentedUntil", 295 | "type": "uint64" 296 | }, 297 | { 298 | "indexed": False, 299 | "name": "paidBack", 300 | "type": "uint128" 301 | } 302 | ], 303 | "name": "LogReturned", 304 | "type": "event" 305 | } 306 | ], 307 | "bytecode": '0x' 308 | } 309 | -------------------------------------------------------------------------------- /energyweb/smart_contract/interfaces.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from web3 import Web3, HTTPProvider 4 | from web3.contract import ConciseContract 5 | from web3.utils.filters import Filter 6 | 7 | from energyweb.eds.interfaces import EnergyData 8 | from energyweb.carbonemission import CarbonEmissionData 9 | from energyweb.interfaces import BlockchainClient 10 | 11 | 12 | class GreenEnergy(EnergyData, CarbonEmissionData): 13 | """ 14 | Green energy data read from external data sources of energy and carbon emissions 15 | """ 16 | 17 | def __init__(self, energy: EnergyData, carbon_emission: CarbonEmissionData): 18 | self.energy = energy 19 | self.carbon_emission = carbon_emission 20 | super().__init__(None, None) 21 | 22 | def co2_saved(self, energy_offset: int = 0): 23 | """ 24 | Returns amount of carbon dioxide saved 25 | :param energy_offset: Energy already registered to add up in case the energy measured was not accumulated 26 | :return: Total amount of kg of carbon dioxide 27 | """ 28 | if not self.carbon_emission: 29 | return 0 30 | accumulated_energy = self.energy.energy 31 | if not self.energy.device.is_accumulated: 32 | accumulated_energy += energy_offset 33 | co2_saved = self.carbon_emission.accumulated_co2 34 | calculated_co2 = accumulated_energy * co2_saved 35 | co2_saved = int(calculated_co2 * pow(10, 3)) 36 | return co2_saved 37 | 38 | 39 | class EVMSmartContractClient(BlockchainClient): 40 | """ 41 | General EVM based blockchain client smart contract integration. 42 | Tested on: 43 | - https://github.com/paritytech/parity 44 | - https://github.com/energywebfoundation/energyweb-client 45 | """ 46 | 47 | def __init__(self, credentials: tuple, contracts: dict, client_url: str, max_retries: int, retry_pause: int): 48 | """ 49 | :param credentials: Network credentials ( address, password ) 50 | :param contracts: Contracts structure containing abi and bytecode keys. 51 | :param client_url: URL like address to the blockchain client api. 52 | :param max_retries: Software will try to connect to provider this amount of times 53 | :param retry_pause: Software will wait between reconnection trials this amount of seconds 54 | """ 55 | self.MAX_RETRIES = max_retries 56 | self.SECONDS_BETWEEN_RETRIES = retry_pause 57 | self.w3 = Web3(HTTPProvider(client_url)) 58 | self.credentials = credentials 59 | self.contracts = contracts 60 | 61 | def is_synced(self) -> bool: 62 | """ 63 | Simple algorithm to check if the blockchain client is synced to the latest block. 64 | :return: Is synced true or false 65 | """ 66 | synced_block = str(self.w3.eth.blockNumber) 67 | latest_block_obj = self.w3.eth.getBlock('latest') 68 | latest_block = str(latest_block_obj.number) 69 | return synced_block == latest_block 70 | 71 | def send(self, contract_name: str, method_name: str, *args) -> dict: 72 | """ 73 | Sends a regular transaction to call a smart-contract method. This requires the user have the keys previously 74 | imported to the blockchain client and to accept the transaction on Metamask or on the client's user interface. 75 | :param contract_name: Contract key as in the contracts list used to instantiate this class. 76 | :param method_name: Method name as in the contract abi. 77 | :param args: Arguments passed when calling the method. Must be in the same order as in the abi. 78 | :return: The transaction receipt after mining is confirmed. 79 | """ 80 | # TODO: Check for more elegant way of verifying the tx receipt 81 | if not self.is_synced(): 82 | raise ConnectionError('Client is not synced to the last block.') 83 | self.w3.personal.unlockAccount(account=self.w3.toChecksumAddress(self.credentials[0]), 84 | passphrase=self.credentials[1]) 85 | contract = self.contracts[contract_name] 86 | contract_instance = self.w3.eth.contract( 87 | abi=contract['abi'], 88 | address=self.w3.toChecksumAddress(contract['address']), 89 | bytecode=contract['bytecode'], 90 | ContractFactoryClass=ConciseContract) 91 | tx_hash = getattr(contract_instance, method_name)(*args, transact={ 92 | 'from': self.w3.toChecksumAddress(self.credentials[0])}) 93 | if not tx_hash: 94 | raise ConnectionError('Transaction was not sent.') 95 | tx_receipt = None 96 | for _ in range(self.MAX_RETRIES): 97 | tx_receipt = self.w3.eth.getTransactionReceipt(tx_hash) 98 | if tx_receipt and tx_receipt['blockNumber']: 99 | break 100 | time.sleep(self.SECONDS_BETWEEN_RETRIES) 101 | return tx_receipt 102 | 103 | def call(self, contract_name: str, method_name: str, *args) -> dict: 104 | """ 105 | Calls a smart-contract method without sending a transaction. Suitable for read-only operations. 106 | :param contract_name: Contract key as in the contracts list used to instantiate this class. 107 | :param method_name: Method name as in the contract abi. 108 | :param args: Arguments passed when calling the method. Must be in the same order as in the abi. 109 | :return: The transaction receipt after mining is confirmed. 110 | """ 111 | if not self.is_synced(): 112 | raise ConnectionError('Client is not synced to the last block.') 113 | contract = self.contracts[contract_name] 114 | contract_instance = self.w3.eth.contract( 115 | abi=contract['abi'], 116 | address=self.w3.toChecksumAddress(contract['address']), 117 | bytecode=contract['bytecode'], 118 | ContractFactoryClass=ConciseContract) 119 | return getattr(contract_instance, method_name)(*args) 120 | 121 | def send_raw(self, contract_name: str, method_name: str, *args) -> dict: 122 | """ 123 | Sends a raw transaction to call a smart-contract method. 124 | First it creates the transaction, then fetches the account tx count - to avoid repetition attacks, 125 | determines gas price, signs it, and finally sends it to the blockchain client. 126 | :param contract_name: Contract key as in the contracts list used to instantiate this class. 127 | :param method_name: Method name as in the contract abi. 128 | :param args: Arguments passed when calling the method. Must be in the same order as in the abi. 129 | :return: The transaction receipt after mining is confirmed. 130 | """ 131 | contract = self.contracts[contract_name] 132 | contract_instance = self.w3.eth.contract( 133 | abi=contract['abi'], 134 | address=self.w3.toChecksumAddress(contract['address']), 135 | bytecode=contract['bytecode']) 136 | 137 | if not self.is_synced(): 138 | raise ConnectionError('Client is not synced to the last block.') 139 | 140 | nonce = self.w3.eth.getTransactionCount(account=self.w3.toChecksumAddress(self.credentials[0])) 141 | transaction = { 142 | 'from': self.w3.toChecksumAddress(self.credentials[0]), 143 | 'gas': 400000, 144 | 'gasPrice': self.w3.toWei('0', 'gwei'), 145 | 'nonce': nonce, 146 | } 147 | tx = getattr(contract_instance.functions, method_name)(*args).buildTransaction(transaction) 148 | private_key = bytearray.fromhex(self.credentials[1]) 149 | signed_txn = self.w3.eth.account.signTransaction(tx, private_key=private_key) 150 | tx_hash = self.w3.eth.sendRawTransaction(signed_txn.rawTransaction) 151 | 152 | if not tx_hash: 153 | raise ConnectionError('Transaction was not sent.') 154 | tx_receipt = None 155 | for _ in range(self.MAX_RETRIES): 156 | tx_receipt = self.w3.eth.getTransactionReceipt(tx_hash) 157 | if tx_receipt and tx_receipt['blockNumber']: 158 | break 159 | time.sleep(self.SECONDS_BETWEEN_RETRIES) 160 | return tx_receipt 161 | 162 | def create_event_filter(self, contract_name: str, event_name: str, block_count: int = 1000) -> Filter: 163 | """ 164 | Create Filter on the client, the client must have the option enabled or it might fail. 165 | :param contract_name: Contract key as in the contracts list used to instantiate this class. 166 | :param event_name: Like written in the abi 167 | :param block_count: Number of blocks prior to the latest to start filtering from 168 | :return: Filter 169 | """ 170 | contract = self.contracts[contract_name] 171 | contract_instance = self.w3.eth.contract( 172 | abi=contract['abi'], 173 | address=self.w3.toChecksumAddress(contract['address']), 174 | bytecode=contract['bytecode']) 175 | latest_block = self.w3.eth.getBlock('latest') 176 | return getattr(contract_instance.events, event_name)().createFilter(fromBlock=latest_block.number - block_count) 177 | 178 | def create_event_trigger(self, contract_name: str, event_name: str, block_count: int = 1000) -> dict: 179 | """ 180 | Todo: Fix this to get the blocks and check for new events, demands memory or persistence 181 | :param contract_name: Contract key as in the contracts list used to instantiate this class. 182 | :param event_name: Like written in the abi 183 | :param block_count: Number of blocks prior to the latest to start filtering from 184 | :return: Filter 185 | """ 186 | raise NotImplemented 187 | 188 | def mint(self, energy: EnergyData) -> dict: 189 | """ 190 | Mint the measured energy in the blockchain smart-contract 191 | """ 192 | raise NotImplementedError 193 | -------------------------------------------------------------------------------- /energyweb/smart_contract/origin_v1.py: -------------------------------------------------------------------------------- 1 | """ 2 | Library containing the Certificate of Origin v1.0 integration classes 3 | """ 4 | from energyweb.eds.interfaces import EnergyData 5 | from energyweb.smart_contract.interfaces import EVMSmartContractClient 6 | from energyweb.smart_contract.origin.consumer_v1 import contract as consumer_v1 7 | from energyweb.smart_contract.origin.producer_v1 import contract as producer_v1 8 | from energyweb.smart_contract.origin.asset_reg_v1 import contract as asset_reg_v1 9 | 10 | 11 | class ProducedEnergy(EnergyData): 12 | """ 13 | Helper for coo smart-contract mint_produced method 14 | """ 15 | def __init__(self, value: int, is_meter_down: bool, previous_hash: str, co2_saved: int, is_co2_down: bool): 16 | """ 17 | :type previous_hash: previous 18 | :param value: Time the value was measured in epoch format 19 | :param is_meter_down: Measured value 20 | """ 21 | self.value = value 22 | self.is_meter_down = is_meter_down 23 | self.previous_hash = previous_hash 24 | self.co2_saved = co2_saved 25 | self.is_co2_down = is_co2_down 26 | 27 | 28 | class ConsumedEnergy(EnergyData): 29 | """ 30 | Helper for coo smart-contract mint_consumed method 31 | """ 32 | def __init__(self, value: int, previous_hash: str, is_meter_down: bool): 33 | """ 34 | :type previous_hash: previous 35 | :param value: Time the value was measured in epoch format 36 | :param is_meter_down: Measured value 37 | """ 38 | self.value = value 39 | self.is_meter_down = is_meter_down 40 | self.previous_hash = previous_hash 41 | 42 | 43 | class OriginV1(EVMSmartContractClient): 44 | """ 45 | Origin in as smart-contract system deployed on Energy Web Blockchain network. 46 | It is designed to issue and validate green energy certificates of origin. 47 | 48 | This class is only an interface to a ewf-client via json rpc calls and interact with the smart-contract. 49 | """ 50 | 51 | def mint(self, energy: EnergyData) -> dict: 52 | raise NotImplementedError 53 | 54 | def __init__(self, asset_id: int, wallet_add: str, wallet_pwd: str, client_url: str): 55 | """ 56 | :param asset_id: ID received in device registration. 57 | :param wallet_add: Network wallet address 58 | :param wallet_add: Network wallet password 59 | :param client_url: URL like address to the blockchain client api. 60 | contract_address is not used from task_config 61 | """ 62 | contracts = { 63 | "producer": producer_v1, 64 | "consumer": consumer_v1, 65 | "asset_reg": asset_reg_v1 66 | } 67 | credentials = (wallet_add, wallet_pwd) 68 | max_retries = 1000 69 | retry_pause = 5 70 | 71 | self.asset_id = asset_id 72 | super().__init__(credentials, contracts, client_url, max_retries, retry_pause) 73 | 74 | def register_asset(self, country: str, region: str, zip_code: str, city: str, street: str, house_number: str, 75 | latitude: str, longitude: str): 76 | """ 77 | Register device. The account signing the transaction must have "AssetAdmin" role to successfully register. 78 | The device registry is done manually on this project phase. Please contact the EWF's Ramp up team. 79 | 80 | Source: 81 | AssetLogic.sol 82 | Call stack: 83 | function createAsset() external onlyRole(RoleManagement.Role.AssetAdmin) 84 | function initLocation ( uint _index, bytes32 _country, bytes32 _region, bytes32 _zip, bytes32 _city, bytes32 _street, bytes32 _houseNumber, bytes32 _gpsLatitude, bytes32 _gpsLongitude ) external isInitialized onlyRole(RoleManagement.Role.AssetAdmin) 85 | function initGeneral ( uint _index, address _smartMeter, address _owner, AssetType _assetType, Compliance _compliance, uint _operationalSince, uint _capacityWh, bool _active ) external isInitialized userHasRole(RoleManagement.Role.AssetManager, _owner) onlyRole(RoleManagement.Role.AssetAdmin) 86 | Wait for: 87 | event LogAssetCreated(address sender, uint id); 88 | event LogAssetFullyInitialized(uint id); 89 | TODO: Implement in next releases 90 | """ 91 | pass 92 | 93 | 94 | class OriginProducer(OriginV1): 95 | """ 96 | Green Energy Producer 97 | 98 | Origin in as smart-contract system deployed on Energy Web Blockchain network. 99 | It is designed to issue and validate green energy certificates of origin. 100 | 101 | This class is only an interface to a ewf-client via json rpc calls and interact with the smart-contract. 102 | """ 103 | 104 | def mint(self, energy: ProducedEnergy) -> dict: 105 | """ 106 | Source: 107 | AssetProducingRegistryLogic.sol 108 | Call stack: 109 | function saveSmartMeterRead( uint _assetId, uint _newMeterRead, bool _smartMeterDown, bytes32 _lastSmartMeterReadFileHash, uint _CO2OffsetMeterRead, bool _CO2OffsetServiceDown ) external isInitialized onlyAccount(AssetProducingRegistryDB(address(db)).getSmartMeter(_assetId)) 110 | Wait for: 111 | event LogNewMeterRead(uint indexed _assetId, uint _oldMeterRead, uint _newMeterRead, bool _smartMeterDown, uint _certificatesCreatedForWh, uint _oldCO2OffsetReading, uint _newCO2OffsetReading, bool _serviceDown); 112 | """ 113 | if not isinstance(energy.value, int): 114 | raise ValueError('No Produced energy present or in wrong format.') 115 | if not isinstance(energy.is_meter_down, bool): 116 | raise ValueError('No Produced energy status present or in wrong format.') 117 | if not isinstance(energy.previous_hash, str): 118 | raise ValueError('No Produced hash of last file present or in wrong format.') 119 | if not isinstance(energy.co2_saved, int): 120 | raise ValueError('No Produced co2 present or in wrong format.') 121 | if not isinstance(energy.is_co2_down, bool): 122 | raise ValueError('No Produced co2 status present or in wrong format.') 123 | 124 | receipt = self.send_raw('producer', 'saveSmartMeterRead', self.asset_id, energy.value, 125 | energy.is_meter_down, energy.previous_hash.encode(), 126 | energy.co2_saved, energy.is_co2_down) 127 | if not receipt: 128 | raise ConnectionError 129 | return receipt 130 | 131 | def last_hash(self): 132 | """ 133 | Get last file hash registered from producer contract 134 | Source: 135 | AssetLogic.sol 136 | Call stack: 137 | function getAssetDataLog(uint _assetId) 138 | """ 139 | receipt = self.call('producer', 'getLastSmartMeterReadFileHash', self.asset_id) 140 | if not receipt: 141 | raise ConnectionError 142 | return receipt 143 | 144 | def last_state(self): 145 | """ 146 | Get complete state of initiated Asset 147 | Source: 148 | AssetLogic.sol 149 | Call stack: 150 | function getAssetGeneral(uint _assetId) 151 | external 152 | constant 153 | returns( 154 | address _smartMeter, 155 | address _owner, 156 | uint _operationalSince, 157 | uint _lastSmartMeterReadWh, 158 | bool _active, 159 | bytes32 _lastSmartMeterReadFileHash 160 | ) 161 | """ 162 | # TODO store with names keys 163 | receipt = self.call('producer', 'getAssetGeneral', self.asset_id) 164 | if not receipt: 165 | raise ConnectionError 166 | return receipt 167 | 168 | 169 | class OriginConsumer(OriginV1): 170 | """ 171 | Green Energy Consumer 172 | 173 | Origin in as smart-contract system deployed on Energy Web Blockchain network. 174 | It is designed to issue and validate green energy certificates of origin. 175 | 176 | This class is only an interface to a ewf-client via json rpc calls and interact with the smart-contract. 177 | """ 178 | 179 | def mint(self, energy: ConsumedEnergy) -> dict: 180 | """ 181 | Source: 182 | AssetConsumingRegistryLogic.sol 183 | Call stack: 184 | function saveSmartMeterRead(uint _assetId, uint _newMeterRead, bytes32 _lastSmartMeterReadFileHash, bool _smartMeterDown) external isInitialized onlyAccount(AssetConsumingRegistryDB(address(db)).getSmartMeter(_assetId)) 185 | Wait for: 186 | event LogNewMeterRead(uint indexed _assetId, uint _oldMeterRead, uint _newMeterRead, uint _certificatesUsedForWh, bool _smartMeterDown); 187 | """ 188 | if not isinstance(energy.value, int): 189 | raise ValueError('No Produced energy present or in wrong format.') 190 | if not isinstance(energy.is_meter_down, bool): 191 | raise ValueError('No Produced energy status present or in wrong format.') 192 | if not isinstance(energy.previous_hash, str): 193 | raise ValueError('No Produced hash of last file present or in wrong format.') 194 | receipt = self.send_raw('consumer', 'saveSmartMeterRead', self.asset_id, energy.value, 195 | energy.previous_hash.encode(), energy.is_meter_down) 196 | if not receipt: 197 | raise ConnectionError 198 | return receipt 199 | 200 | def last_hash(self): 201 | """ 202 | Get last file hash registered from consumer contract 203 | Source: 204 | AssetLogic.sol 205 | Call stack: 206 | function getAssetDataLog(uint _assetId) 207 | """ 208 | receipt = self.call('consumer', 'getLastSmartMeterReadFileHash', self.asset_id) 209 | if not receipt: 210 | raise ConnectionError 211 | return receipt 212 | 213 | def last_state(self): 214 | """ 215 | Get last file hash registered from producer contract 216 | Source: 217 | AssetLogic.sol 218 | Call stack: 219 | function getAssetGeneral(uint _assetId) 220 | external 221 | constant 222 | returns ( 223 | address _smartMeter, 224 | address _owner, 225 | uint _operationalSince, 226 | uint _capacityWh, 227 | bool _maxCapacitySet, 228 | uint _lastSmartMeterReadWh, 229 | uint _certificatesUsedForWh, 230 | bool _active, 231 | bytes32 _lastSmartMeterReadFileHash 232 | ) 233 | """ 234 | receipt = self.call('consumer', 'getAssetGeneral', self.asset_id) 235 | if not receipt: 236 | raise ConnectionError 237 | return receipt 238 | -------------------------------------------------------------------------------- /docs/api_contract.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.1 2 | info: 3 | title: Energy Measurement API 4 | description: Company name, project name, purpose of the api creation, date of last 5 | update, sometimes code author name. 6 | version: "3" 7 | servers: 8 | - url: https://api.yourcompany.com/v3 9 | description: replace with server endpoint 10 | security: 11 | - basicAuth: [] 12 | - appID: [] 13 | 14 | paths: 15 | /asset: 16 | post: 17 | tags: 18 | - Asset 19 | summary: Create a new asset, returns asset id 20 | parameters: 21 | - in: query 22 | name: role 23 | required: true 24 | description: Whether this assset is a producer or consumer 25 | schema: 26 | $ref: '#/components/schemas/AssetRole' 27 | - in: query 28 | name: manufacturer 29 | schema: 30 | type: string 31 | - in: query 32 | name: model 33 | schema: 34 | type: string 35 | - in: query 36 | name: serialNumber 37 | schema: 38 | type: string 39 | - in: query 40 | name: latitude 41 | schema: 42 | type: number 43 | - in: query 44 | name: longitude 45 | schema: 46 | type: number 47 | - in: query 48 | name: energyUnit 49 | required: true 50 | description: Warning Cannot be changed easily using PATCH 51 | schema: 52 | $ref: '#/components/schemas/EnergyUnit' 53 | - in: query 54 | name: isAccumulated 55 | required: true 56 | description: Whether this assset is reporting total measurement since beginning (true) or since previous measurement (false) 57 | schema: 58 | type: boolean 59 | responses: 60 | 201: 61 | description: Created 62 | content: 63 | application/json: 64 | schema: 65 | $ref: '#/components/schemas/Asset' 66 | links: 67 | GetAssetByID: 68 | operationId: getAsset 69 | parameters: 70 | assetID: '$response.body#/id' 71 | 400: 72 | $ref: '#/components/responses/BadRequest' 73 | 401: 74 | $ref: '#/components/responses/Unauthorized' 75 | 500: 76 | $ref: '#/components/responses/InternalServerError' 77 | 501: 78 | $ref: '#/components/responses/NotImplemented' 79 | 80 | /asset/{assetID}: 81 | parameters: 82 | - in: path 83 | name: assetID 84 | schema: 85 | type: integer 86 | required: true 87 | get: 88 | tags: 89 | - Asset 90 | summary: Get asset information by id 91 | security: [] # No authentication 92 | responses: 93 | 200: 94 | description: Success 95 | content: 96 | application/json: 97 | schema: 98 | $ref: '#/components/schemas/Asset' 99 | 404: 100 | $ref: '#/components/responses/NotFound' 101 | 500: 102 | $ref: '#/components/responses/InternalServerError' 103 | 501: 104 | $ref: '#/components/responses/NotImplemented' 105 | patch: 106 | tags: 107 | - Asset 108 | summary: Update asset information by id 109 | parameters: 110 | - in: query 111 | name: manufacturer 112 | schema: 113 | type: string 114 | - in: query 115 | name: model 116 | schema: 117 | type: string 118 | - in: query 119 | name: serialNumber 120 | schema: 121 | type: string 122 | - in: query 123 | name: latitude 124 | schema: 125 | type: number 126 | - in: query 127 | name: longitude 128 | schema: 129 | type: number 130 | responses: 131 | 200: 132 | description: Success 133 | content: 134 | application/json: 135 | schema: 136 | $ref: '#/components/schemas/Asset' 137 | 404: 138 | $ref: '#/components/responses/NotFound' 139 | 500: 140 | $ref: '#/components/responses/InternalServerError' 141 | 501: 142 | $ref: '#/components/responses/NotImplemented' 143 | delete: 144 | tags: 145 | - Asset 146 | summary: Delete an asset (and it's metering data) 147 | responses: 148 | 200: 149 | description: Success 150 | 404: 151 | $ref: '#/components/responses/NotFound' 152 | 500: 153 | $ref: '#/components/responses/InternalServerError' 154 | 501: 155 | $ref: '#/components/responses/NotImplemented' 156 | 157 | /asset/{assetID}/energy: 158 | parameters: 159 | - in: path 160 | name: assetID 161 | schema: 162 | type: integer 163 | required: true 164 | post: 165 | tags: 166 | - Energy 167 | summary: Publish new energy measurement 168 | parameters: 169 | - in: query 170 | name: energy 171 | description: Registered in the asset energy unit 172 | required: true 173 | schema: 174 | type: number 175 | - in: query 176 | name: measurementTime 177 | description: Time of measurement in the device, in RFC 3339 format. ie. 2018-03-14T17:12:20+00:00 178 | required: true 179 | schema: 180 | type: string 181 | format: date-time 182 | responses: 183 | 201: 184 | description: Created 185 | content: 186 | application/json: 187 | schema: 188 | $ref: '#/components/schemas/Energy' 189 | 400: 190 | $ref: '#/components/responses/BadRequestEnergy' 191 | 404: 192 | $ref: '#/components/responses/NotFound' 193 | 500: 194 | $ref: '#/components/responses/InternalServerError' 195 | 501: 196 | $ref: '#/components/responses/NotImplemented' 197 | get: 198 | tags: 199 | - Energy 200 | summary: Get energy measurements of asset 201 | parameters: 202 | - name: timeStart 203 | in: query 204 | description: Date in RFC 3339 format. ie. 2018-03-14T17:11:19+00:00 205 | required: false 206 | schema: 207 | type: string 208 | format: date-time 209 | - name: timeEnd 210 | in: query 211 | description: Date in RFC 3339 format. ie. 2018-03-14T17:12:20+00:00 212 | required: false 213 | schema: 214 | type: string 215 | format: date-time 216 | - name: accumulated 217 | in: query 218 | description: Set to True to get one accumulated measurement for the time frame. Note when the assets reports accumulated data, this is equal to the last reading withing the time frame and timeStart does not have an effect. 219 | required: false 220 | schema: 221 | type: boolean 222 | default: false 223 | - name: limit 224 | in: query 225 | description: How many items to return at one time. -1 for no limit, TODO add pagination 226 | required: false 227 | schema: 228 | type: integer 229 | format: int32 230 | default: 5 231 | 232 | responses: 233 | 200: 234 | description: Success 235 | content: 236 | application/json: 237 | schema: 238 | type: array 239 | items: 240 | $ref: '#/components/schemas/Energy' 241 | 400: 242 | $ref: '#/components/responses/BadRequestEnergy' 243 | 404: 244 | $ref: '#/components/responses/NotFound' 245 | 500: 246 | $ref: '#/components/responses/InternalServerError' 247 | 501: 248 | $ref: '#/components/responses/NotImplemented' 249 | 250 | components: 251 | schemas: 252 | 253 | AssetRole: 254 | type: string 255 | enum: 256 | - producer 257 | - consumer 258 | 259 | EnergyUnit: 260 | type: string 261 | enum: 262 | - joule 263 | - wattHour 264 | - kilowattHour 265 | - megawattHour 266 | - gigawattHour 267 | 268 | Asset: 269 | type: object 270 | properties: 271 | id: 272 | type: integer 273 | format: int64 274 | readOnly: true 275 | role: 276 | $ref: '#/components/schemas/AssetRole' 277 | manufacturer: 278 | type: string 279 | model: 280 | type: string 281 | serial_number: 282 | type: string 283 | latitude: 284 | type: number 285 | longitude: 286 | type: number 287 | energy_unit: 288 | $ref: '#/components/schemas/EnergyUnit' 289 | is_accumulated: 290 | type: boolean 291 | 292 | Energy: 293 | type: object 294 | description: Energy measured at a certain time 295 | properties: 296 | energy: 297 | type: number 298 | description: Registered in Mega Watts 299 | measurementTime: 300 | type: string 301 | description: Time of measurement in the device, in RFC 3339 format. ie. 2018-03-14T17:12:20+00:00 302 | format: date-time 303 | 304 | Error: 305 | type: object 306 | description: Error information 307 | properties: 308 | error: 309 | type: string 310 | description: The kind of error that occured 311 | message: 312 | type: string 313 | description: Additional error information 314 | 315 | responses: 316 | Unauthorized: 317 | description: 'Unauthorized: Check authorisation' 318 | content: 319 | application/json: 320 | schema: 321 | $ref: '#/components/schemas/Error' 322 | InternalServerError: 323 | description: 'Internal Server Error: Check error message for additional details.' 324 | content: 325 | application/json: 326 | schema: 327 | $ref: '#/components/schemas/Error' 328 | NotImplemented: 329 | description: 'Not Implemented: The server does not have this message implemented.' 330 | content: 331 | application/json: 332 | schema: 333 | $ref: '#/components/schemas/Error' 334 | BadRequest: 335 | description: 'Bad Request: All required fields need to be set. | energyUnit needs to be a valid choice.' 336 | content: 337 | application/json: 338 | schema: 339 | $ref: '#/components/schemas/Error' 340 | BadRequestEnergy: 341 | description: 'Bad Request: All required fields need to be set. | time needs to be valid dateTime string' 342 | content: 343 | application/json: 344 | schema: 345 | $ref: '#/components/schemas/Error' 346 | NotFound: 347 | description: 'Not Found: Asset with this id doesn`t exist' 348 | 349 | securitySchemes: 350 | basicAuth: 351 | type: http 352 | scheme: basic 353 | x-basicInfoFunc: api.controller.authorization.check_basicAuth 354 | 355 | appID: 356 | type: apiKey 357 | in: header 358 | name: X-API-Key 359 | x-apikeyInfoFunc: api.controller.authorization.check_appID -------------------------------------------------------------------------------- /docs/eth_module.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import unicode_literals 4 | 5 | from eth_abi import ( 6 | decode_single, 7 | ) 8 | 9 | from eth_utils import ( 10 | is_address, 11 | is_string, 12 | is_boolean, 13 | is_dict, 14 | is_integer, 15 | is_list_like, 16 | is_same_address, 17 | remove_0x_prefix, 18 | ) 19 | 20 | 21 | UNKOWN_HASH = '0xdeadbeef00000000000000000000000000000000000000000000000000000000' 22 | 23 | 24 | class EthModuleTest(object): 25 | def test_eth_protocolVersion(self, web3): 26 | protocol_version = web3.version.ethereum 27 | 28 | assert is_string(protocol_version) 29 | assert protocol_version.isdigit() 30 | 31 | def test_eth_syncing(self, web3): 32 | syncing = web3.eth.syncing 33 | 34 | assert is_boolean(syncing) or is_dict(syncing) 35 | 36 | if is_boolean(syncing): 37 | assert syncing is False 38 | elif is_dict(syncing): 39 | assert 'startingBlock' in syncing 40 | assert 'currentBlock' in syncing 41 | assert 'highestBlock' in syncing 42 | 43 | assert is_integer(syncing['startingBlock']) 44 | assert is_integer(syncing['currentBlock']) 45 | assert is_integer(syncing['highestBlock']) 46 | 47 | def test_eth_coinbase(self, web3): 48 | coinbase = web3.eth.coinbase 49 | assert is_address(coinbase) 50 | 51 | def test_eth_mining(self, web3): 52 | mining = web3.eth.mining 53 | assert is_boolean(mining) 54 | 55 | def test_eth_hashrate(self, web3): 56 | hashrate = web3.eth.hashrate 57 | assert is_integer(hashrate) 58 | assert hashrate >= 0 59 | 60 | def test_eth_gasPrice(self, web3): 61 | gas_price = web3.eth.gasPrice 62 | assert is_integer(gas_price) 63 | assert gas_price > 0 64 | 65 | def test_eth_accounts(self, web3): 66 | accounts = web3.eth.accounts 67 | assert is_list_like(accounts) 68 | assert len(accounts) != 0 69 | assert all(( 70 | is_address(account) 71 | for account 72 | in accounts 73 | )) 74 | assert web3.eth.coinbase in accounts 75 | 76 | def test_eth_blockNumber(self, web3): 77 | block_number = web3.eth.blockNumber 78 | assert is_integer(block_number) 79 | assert block_number >= 0 80 | 81 | def test_eth_getBalance(self, web3): 82 | coinbase = web3.eth.coinbase 83 | balance = web3.eth.getBalance(coinbase) 84 | 85 | assert is_integer(balance) 86 | assert balance >= 0 87 | 88 | def test_eth_getStorageAt(self, web3): 89 | # TODO: implement deployed contracts 90 | pass 91 | 92 | def test_eth_getTransactionCount(self, web3): 93 | coinbase = web3.eth.coinbase 94 | transaction_count = web3.eth.getTransactionCount(coinbase) 95 | 96 | assert is_integer(transaction_count) 97 | assert transaction_count >= 0 98 | 99 | def test_eth_getBlockTransactionCountByHash_empty_block(self, web3, empty_block): 100 | transaction_count = web3.eth.getBlockTransactionCount(empty_block['hash']) 101 | 102 | assert is_integer(transaction_count) 103 | assert transaction_count == 0 104 | 105 | def test_eth_getBlockTransactionCountByNumber_empty_block(self, web3, empty_block): 106 | transaction_count = web3.eth.getBlockTransactionCount(empty_block['number']) 107 | 108 | assert is_integer(transaction_count) 109 | assert transaction_count == 0 110 | 111 | def test_eth_getBlockTransactionCountByHash_block_with_txn(self, web3, block_with_txn): 112 | transaction_count = web3.eth.getBlockTransactionCount(block_with_txn['hash']) 113 | 114 | assert is_integer(transaction_count) 115 | assert transaction_count >= 1 116 | 117 | def test_eth_getBlockTransactionCountByNumber_block_with_txn(self, web3, block_with_txn): 118 | transaction_count = web3.eth.getBlockTransactionCount(block_with_txn['number']) 119 | 120 | assert is_integer(transaction_count) 121 | assert transaction_count >= 1 122 | 123 | def test_eth_getUncleCountByBlockHash(self, web3, empty_block): 124 | uncle_count = web3.eth.getUncleCount(empty_block['hash']) 125 | 126 | assert is_integer(uncle_count) 127 | assert uncle_count == 0 128 | 129 | def test_eth_getUncleCountByBlockNumber(self, web3, empty_block): 130 | uncle_count = web3.eth.getUncleCount(empty_block['number']) 131 | 132 | assert is_integer(uncle_count) 133 | assert uncle_count == 0 134 | 135 | def test_eth_getCode(self, web3, math_contract): 136 | code = web3.eth.getCode(math_contract.address) 137 | assert is_string(code) 138 | assert len(code) > 2 139 | 140 | def test_eth_sign(self, web3, unlocked_account): 141 | signature = web3.eth.sign(unlocked_account, text='Message tö sign. Longer than hash!') 142 | assert is_string(signature) 143 | assert len(remove_0x_prefix(signature)) == 130 144 | 145 | # test other formats 146 | hexsign = web3.eth.sign( 147 | unlocked_account, 148 | hexstr='0x4d6573736167652074c3b6207369676e2e204c6f6e676572207468616e206861736821' 149 | ) 150 | assert hexsign == signature 151 | 152 | intsign = web3.eth.sign( 153 | unlocked_account, 154 | 0x4d6573736167652074c3b6207369676e2e204c6f6e676572207468616e206861736821 155 | ) 156 | assert intsign == signature 157 | 158 | bytessign = web3.eth.sign(unlocked_account, b'Message t\xc3\xb6 sign. Longer than hash!') 159 | assert bytessign == signature 160 | 161 | new_signature = web3.eth.sign(unlocked_account, text='different message is different') 162 | assert new_signature != signature 163 | 164 | def test_eth_sendTransaction(self, web3, unlocked_account): 165 | txn_params = { 166 | 'from': unlocked_account, 167 | 'to': unlocked_account, 168 | 'value': 1, 169 | 'gas': 21000, 170 | 'gas_price': web3.eth.gasPrice, 171 | } 172 | txn_hash = web3.eth.sendTransaction(txn_params) 173 | txn = web3.eth.getTransaction(txn_hash) 174 | 175 | assert is_same_address(txn['from'], txn_params['from']) 176 | assert is_same_address(txn['to'], txn_params['to']) 177 | assert txn['value'] == 1 178 | assert txn['gas'] == 21000 179 | assert txn['gasPrice'] == txn_params['gas_price'] 180 | 181 | def test_eth_sendRawTransaction(self, web3, funded_account_for_raw_txn): 182 | txn_hash = web3.eth.sendRawTransaction( 183 | '0xf8648085174876e8008252089439eeed73fb1d3855e90cbd42f348b3d7b340aaa601801ba0ec1295f00936acd0c2cb90ab2cdaacb8bf5e11b3d9957833595aca9ceedb7aada05dfc8937baec0e26029057abd3a1ef8c505dca2cdc07ffacb046d090d2bea06a' # noqa: E501 184 | ) 185 | assert txn_hash == '0x1f80f8ab5f12a45be218f76404bda64d37270a6f4f86ededd0eb599f80548c13' 186 | 187 | def test_eth_call(self, web3, math_contract): 188 | coinbase = web3.eth.coinbase 189 | txn_params = math_contract._prepare_transaction( 190 | fn_name='add', 191 | fn_args=(7, 11), 192 | transaction={'from': coinbase, 'to': math_contract.address}, 193 | ) 194 | call_result = web3.eth.call(txn_params) 195 | assert is_string(call_result) 196 | result = decode_single('uint256', call_result) 197 | assert result == 18 198 | 199 | def test_eth_call_with_0_result(self, web3, math_contract): 200 | coinbase = web3.eth.coinbase 201 | txn_params = math_contract._prepare_transaction( 202 | fn_name='add', 203 | fn_args=(0, 0), 204 | transaction={'from': coinbase, 'to': math_contract.address}, 205 | ) 206 | call_result = web3.eth.call(txn_params) 207 | assert is_string(call_result) 208 | result = decode_single('uint256', call_result) 209 | assert result == 0 210 | 211 | def test_eth_estimateGas(self, web3): 212 | coinbase = web3.eth.coinbase 213 | gas_estimate = web3.eth.estimateGas({ 214 | 'from': coinbase, 215 | 'to': coinbase, 216 | 'value': 1, 217 | }) 218 | assert is_integer(gas_estimate) 219 | assert gas_estimate > 0 220 | 221 | def test_eth_getBlockByHash(self, web3, empty_block): 222 | block = web3.eth.getBlock(empty_block['hash']) 223 | assert block['hash'] == empty_block['hash'] 224 | 225 | def test_eth_getBlockByHash_not_found(self, web3, empty_block): 226 | block = web3.eth.getBlock(UNKOWN_HASH) 227 | assert block is None 228 | 229 | def test_eth_getBlockByNumber_with_integer(self, web3, empty_block): 230 | block = web3.eth.getBlock(empty_block['number']) 231 | assert block['number'] == empty_block['number'] 232 | 233 | def test_eth_getBlockByNumber_latest(self, web3, empty_block): 234 | current_block_number = web3.eth.blockNumber 235 | block = web3.eth.getBlock('latest') 236 | assert block['number'] == current_block_number 237 | 238 | def test_eth_getBlockByNumber_not_found(self, web3, empty_block): 239 | block = web3.eth.getBlock(12345) 240 | assert block is None 241 | 242 | def test_eth_getBlockByNumber_pending(self, web3, empty_block): 243 | current_block_number = web3.eth.blockNumber 244 | block = web3.eth.getBlock('pending') 245 | assert block['number'] == current_block_number + 1 246 | 247 | def test_eth_getBlockByNumber_earliest(self, web3, empty_block): 248 | genesis_block = web3.eth.getBlock(0) 249 | block = web3.eth.getBlock('earliest') 250 | assert block['number'] == 0 251 | assert block['hash'] == genesis_block['hash'] 252 | 253 | def test_eth_getBlockByNumber_full_transactions(self, web3, block_with_txn): 254 | block = web3.eth.getBlock(block_with_txn['number'], True) 255 | transaction = block['transactions'][0] 256 | assert transaction['hash'] == block_with_txn['transactions'][0] 257 | 258 | def test_eth_getTransactionByHash(self, web3, mined_txn_hash): 259 | transaction = web3.eth.getTransaction(mined_txn_hash) 260 | assert is_dict(transaction) 261 | assert transaction['hash'] == mined_txn_hash 262 | 263 | def test_eth_getTransactionByHash_contract_creation(self, 264 | web3, 265 | math_contract_deploy_txn_hash): 266 | transaction = web3.eth.getTransaction(math_contract_deploy_txn_hash) 267 | assert is_dict(transaction) 268 | assert transaction['to'] is None 269 | 270 | def test_eth_getTransactionByBlockHashAndIndex(self, web3, block_with_txn, mined_txn_hash): 271 | transaction = web3.eth.getTransactionFromBlock(block_with_txn['hash'], 0) 272 | assert is_dict(transaction) 273 | assert transaction['hash'] == mined_txn_hash 274 | 275 | def test_eth_getTransactionByBlockNumberAndIndex(self, web3, block_with_txn, mined_txn_hash): 276 | transaction = web3.eth.getTransactionFromBlock(block_with_txn['number'], 0) 277 | assert is_dict(transaction) 278 | assert transaction['hash'] == mined_txn_hash 279 | 280 | def test_eth_getTransactionReceipt_mined(self, web3, block_with_txn, mined_txn_hash): 281 | receipt = web3.eth.getTransactionReceipt(mined_txn_hash) 282 | assert is_dict(receipt) 283 | assert receipt['blockNumber'] == block_with_txn['number'] 284 | assert receipt['blockHash'] == block_with_txn['hash'] 285 | assert receipt['transactionIndex'] == 0 286 | assert receipt['transactionHash'] == mined_txn_hash 287 | 288 | def test_eth_getTransactionReceipt_unmined(self, web3, unlocked_account): 289 | txn_hash = web3.eth.sendTransaction({ 290 | 'from': unlocked_account, 291 | 'to': unlocked_account, 292 | 'value': 1, 293 | 'gas': 21000, 294 | 'gas_price': web3.eth.gasPrice, 295 | }) 296 | receipt = web3.eth.getTransactionReceipt(txn_hash) 297 | assert receipt is None 298 | 299 | def test_eth_getTransactionReceipt_with_log_entry(self, 300 | web3, 301 | block_with_txn_with_log, 302 | emitter_contract, 303 | txn_hash_with_log): 304 | receipt = web3.eth.getTransactionReceipt(txn_hash_with_log) 305 | assert is_dict(receipt) 306 | assert receipt['blockNumber'] == block_with_txn_with_log['number'] 307 | assert receipt['blockHash'] == block_with_txn_with_log['hash'] 308 | assert receipt['transactionIndex'] == 0 309 | assert receipt['transactionHash'] == txn_hash_with_log 310 | 311 | assert len(receipt['logs']) == 1 312 | log_entry = receipt['logs'][0] 313 | 314 | assert log_entry['blockNumber'] == block_with_txn_with_log['number'] 315 | assert log_entry['blockHash'] == block_with_txn_with_log['hash'] 316 | assert log_entry['logIndex'] == 0 317 | assert is_same_address(log_entry['address'], emitter_contract.address) 318 | assert log_entry['transactionIndex'] == 0 319 | assert log_entry['transactionHash'] == txn_hash_with_log 320 | 321 | def test_eth_getUncleByBlockHashAndIndex(self, web3): 322 | # TODO: how do we make uncles.... 323 | pass 324 | 325 | def test_eth_getUncleByBlockNumberAndIndex(self, web3): 326 | # TODO: how do we make uncles.... 327 | pass 328 | 329 | def test_eth_getCompilers(self, web3): 330 | # TODO: do we want to test this? 331 | pass 332 | 333 | def test_eth_compileSolidity(self, web3): 334 | # TODO: do we want to test this? 335 | pass 336 | 337 | def test_eth_compileLLL(self, web3): 338 | # TODO: do we want to test this? 339 | pass 340 | 341 | def test_eth_compileSerpent(self, web3): 342 | # TODO: do we want to test this? 343 | pass 344 | 345 | def test_eth_newFilter(self, web3): 346 | filter = web3.eth.filter({}) 347 | 348 | changes = web3.eth.getFilterChanges(filter.filter_id) 349 | assert is_list_like(changes) 350 | assert not changes 351 | 352 | logs = web3.eth.getFilterLogs(filter.filter_id) 353 | assert is_list_like(logs) 354 | assert not logs 355 | 356 | result = web3.eth.uninstallFilter(filter.filter_id) 357 | assert result is True 358 | 359 | def test_eth_newBlockFilter(self, web3): 360 | filter = web3.eth.filter('latest') 361 | assert is_string(filter.filter_id) 362 | 363 | changes = web3.eth.getFilterChanges(filter.filter_id) 364 | assert is_list_like(changes) 365 | assert not changes 366 | 367 | # TODO: figure out why this fails in go-ethereum 368 | # logs = web3.eth.getFilterLogs(filter.filter_id) 369 | # assert is_list_like(logs) 370 | # assert not logs 371 | 372 | result = web3.eth.uninstallFilter(filter.filter_id) 373 | assert result is True 374 | 375 | def test_eth_newPendingTransactionFilter(self, web3): 376 | filter = web3.eth.filter('pending') 377 | assert is_string(filter.filter_id) 378 | 379 | changes = web3.eth.getFilterChanges(filter.filter_id) 380 | assert is_list_like(changes) 381 | assert not changes 382 | 383 | # TODO: figure out why this fails in go-ethereum 384 | # logs = web3.eth.getFilterLogs(filter.filter_id) 385 | # assert is_list_like(logs) 386 | # assert not logs 387 | 388 | result = web3.eth.uninstallFilter(filter.filter_id) 389 | assert result is True 390 | 391 | def test_eth_uninstallFilter(self, web3): 392 | filter = web3.eth.filter({}) 393 | assert is_string(filter.filter_id) 394 | 395 | success = web3.eth.uninstallFilter(filter.filter_id) 396 | assert success is True 397 | 398 | failure = web3.eth.uninstallFilter(filter.filter_id) 399 | assert failure is False 400 | -------------------------------------------------------------------------------- /energyweb/smart_contract/origin/consumer_v1.py: -------------------------------------------------------------------------------- 1 | """ 2 | Energy consuming assets registry and certificate emitter Smart-Contract interface 3 | Source: AssetConsumingRegistryLogic.sol 4 | """ 5 | contract = { 6 | "address": "0xc68fb291a6ddf3d4d9e3a061de39563bf269d868", 7 | "abi": [{'constant': False, 8 | 'inputs': [{'name': '_assetId', 'type': 'uint256'}, {'name': '_newSmartMeter', 'type': 'address'}], 9 | 'name': 'updateSmartMeter', 'outputs': [], 'payable': False, 'stateMutability': 'nonpayable', 10 | 'type': 'function'}, 11 | {'constant': True, 'inputs': [{'name': '_assetId', 'type': 'uint256'}], 'name': 'getAssetLocation', 12 | 'outputs': [{'name': 'country', 'type': 'bytes32'}, {'name': 'region', 'type': 'bytes32'}, 13 | {'name': 'zip', 'type': 'bytes32'}, {'name': 'city', 'type': 'bytes32'}, 14 | {'name': 'street', 'type': 'bytes32'}, {'name': 'houseNumber', 'type': 'bytes32'}, 15 | {'name': 'gpsLatitude', 'type': 'bytes32'}, {'name': 'gpsLongitude', 'type': 'bytes32'}], 16 | 'payable': False, 'stateMutability': 'view', 'type': 'function'}, 17 | {'constant': False, 'inputs': [{'name': '_dbAddress', 'type': 'address'}], 'name': 'init', 'outputs': [], 18 | 'payable': False, 'stateMutability': 'nonpayable', 'type': 'function'}, 19 | {'constant': False, 'inputs': [{'name': '_newLogic', 'type': 'address'}], 'name': 'update', 'outputs': [], 20 | 'payable': False, 'stateMutability': 'nonpayable', 'type': 'function'}, 21 | {'constant': False, 'inputs': [], 'name': 'createAsset', 'outputs': [], 'payable': False, 22 | 'stateMutability': 'nonpayable', 'type': 'function'}, 23 | {'constant': True, 'inputs': [], 'name': 'db', 'outputs': [{'name': '', 'type': 'address'}], 24 | 'payable': False, 'stateMutability': 'view', 'type': 'function'}, {'constant': False, 'inputs': [ 25 | {'name': '_assetId', 'type': 'uint256'}, {'name': '_country', 'type': 'bytes32'}, 26 | {'name': '_region', 'type': 'bytes32'}, {'name': '_zip', 'type': 'bytes32'}, 27 | {'name': '_city', 'type': 'bytes32'}, {'name': '_street', 'type': 'bytes32'}, 28 | {'name': '_houseNumber', 'type': 'bytes32'}, {'name': '_gpsLatitude', 'type': 'bytes32'}, 29 | {'name': '_gpsLongitude', 'type': 'bytes32'}], 'name': 'initLocation', 'outputs': [], 'payable': False, 30 | 'stateMutability': 'nonpayable', 31 | 'type': 'function'}, 32 | {'constant': True, 'inputs': [], 'name': 'getAssetListLength', 'outputs': [{'name': '', 'type': 'uint256'}], 33 | 'payable': False, 'stateMutability': 'view', 'type': 'function'}, 34 | {'constant': True, 'inputs': [], 'name': 'cooContract', 'outputs': [{'name': '', 'type': 'address'}], 35 | 'payable': False, 'stateMutability': 'view', 'type': 'function'}, 36 | {'constant': True, 'inputs': [{'name': '_role', 'type': 'uint8'}, {'name': '_caller', 'type': 'address'}], 37 | 'name': 'isRole', 'outputs': [{'name': '', 'type': 'bool'}], 'payable': False, 'stateMutability': 'view', 38 | 'type': 'function'}, 39 | {'constant': True, 'inputs': [{'name': '_assetId', 'type': 'uint256'}], 'name': 'getActive', 40 | 'outputs': [{'name': '', 'type': 'bool'}], 'payable': False, 'stateMutability': 'view', 41 | 'type': 'function'}, {'constant': False, 'inputs': [{'name': '_assetId', 'type': 'uint256'}, 42 | {'name': '_active', 'type': 'bool'}], 43 | 'name': 'setActive', 'outputs': [], 'payable': False, 44 | 'stateMutability': 'nonpayable', 'type': 'function'}, 45 | {'constant': True, 'inputs': [{'name': '_assetId', 'type': 'uint256'}], 46 | 'name': 'getLastSmartMeterReadFileHash', 'outputs': [{'name': 'datalog', 'type': 'bytes32'}], 47 | 'payable': False, 'stateMutability': 'view', 'type': 'function'}, 48 | {'inputs': [{'name': '_cooContract', 'type': 'address'}], 'payable': False, 'stateMutability': 'nonpayable', 49 | 'type': 'constructor'}, {'anonymous': False, 50 | 'inputs': [{'indexed': True, 'name': '_assetId', 'type': 'uint256'}, 51 | {'indexed': True, 'name': '_fileHash', 'type': 'bytes32'}, 52 | {'indexed': False, 'name': '_oldMeterRead', 'type': 'uint256'}, 53 | {'indexed': False, 'name': '_newMeterRead', 'type': 'uint256'}, 54 | {'indexed': False, 'name': '_certificatesUsedForWh', 55 | 'type': 'uint256'}, 56 | {'indexed': False, 'name': '_smartMeterDown', 'type': 'bool'}], 57 | 'name': 'LogNewMeterRead', 'type': 'event'}, {'anonymous': False, 'inputs': [ 58 | {'indexed': False, 'name': 'sender', 'type': 'address'}, 59 | {'indexed': True, 'name': '_assetId', 'type': 'uint256'}], 'name': 'LogAssetCreated', 'type': 'event'}, 60 | {'anonymous': False, 'inputs': [{'indexed': True, 'name': '_assetId', 'type': 'uint256'}], 61 | 'name': 'LogAssetFullyInitialized', 'type': 'event'}, 62 | {'anonymous': False, 'inputs': [{'indexed': True, 'name': '_assetId', 'type': 'uint256'}], 63 | 'name': 'LogAssetSetActive', 'type': 'event'}, 64 | {'anonymous': False, 'inputs': [{'indexed': True, 'name': '_assetId', 'type': 'uint256'}], 65 | 'name': 'LogAssetSetInactive', 'type': 'event'}, {'constant': False, 66 | 'inputs': [{'name': '_assetId', 'type': 'uint256'}, 67 | {'name': '_smartMeter', 'type': 'address'}, 68 | {'name': '_owner', 'type': 'address'}, 69 | {'name': '_operationalSince', 70 | 'type': 'uint256'}, 71 | {'name': '_capacityWh', 'type': 'uint256'}, 72 | {'name': 'maxCapacitySet', 'type': 'bool'}, 73 | {'name': '_active', 'type': 'bool'}], 74 | 'name': 'initGeneral', 'outputs': [], 'payable': False, 75 | 'stateMutability': 'nonpayable', 'type': 'function'}, 76 | {'constant': False, 77 | 'inputs': [{'name': '_assetId', 'type': 'uint256'}, {'name': '_newMeterRead', 'type': 'uint256'}, 78 | {'name': '_lastSmartMeterReadFileHash', 'type': 'bytes32'}, 79 | {'name': '_smartMeterDown', 'type': 'bool'}], 'name': 'saveSmartMeterRead', 'outputs': [], 80 | 'payable': False, 'stateMutability': 'nonpayable', 'type': 'function'}, 81 | {'constant': True, 'inputs': [{'name': '_assetId', 'type': 'uint256'}], 'name': 'getAssetGeneral', 82 | 'outputs': [{'name': '_smartMeter', 'type': 'address'}, {'name': '_owner', 'type': 'address'}, 83 | {'name': '_operationalSince', 'type': 'uint256'}, {'name': '_capacityWh', 'type': 'uint256'}, 84 | {'name': '_maxCapacitySet', 'type': 'bool'}, 85 | {'name': '_lastSmartMeterReadWh', 'type': 'uint256'}, 86 | {'name': '_certificatesUsedForWh', 'type': 'uint256'}, {'name': '_active', 'type': 'bool'}, 87 | {'name': '_lastSmartMeterReadFileHash', 'type': 'bytes32'}], 'payable': False, 88 | 'stateMutability': 'view', 'type': 'function'}, 89 | {'constant': True, 'inputs': [{'name': '_assetId', 'type': 'uint256'}], 'name': 'getConsumingProperies', 90 | 'outputs': [{'name': 'capacityWh', 'type': 'uint256'}, {'name': 'maxCapacitySet', 'type': 'bool'}, 91 | {'name': 'certificatesUsedForWh', 'type': 'uint256'}], 'payable': False, 92 | 'stateMutability': 'view', 'type': 'function'}, {'constant': False, 93 | 'inputs': [{'name': '_assetId', 'type': 'uint256'}, 94 | {'name': '_consumed', 'type': 'uint256'}], 95 | 'name': 'setConsumptionForPeriode', 'outputs': [], 96 | 'payable': False, 'stateMutability': 'nonpayable', 97 | 'type': 'function'}], 98 | "bytecode": '0x6060604052341561000f57600080fd5b6040516020806114408339810160405280805160008054600160a060020a03909216600160a060020a031990921691909117905550506113ec806100546000396000f3006060604052600436106100e25763ffffffff60e060020a6000350416630705244281146100e75780631314394d1461010b57806319ab453c146101685780631c1b877214610187578063244a613a146101a6578063312f9a8c146101de57806331a5daf3146102185780634d655aff1461022b5780637715ad901461025a5780639963aece1461027b578063a861c6ef146102aa578063ade1e992146102c3578063ae51439c14610331578063b1f2cc9d14610356578063bb5a389e14610369578063c53fb1a5146103a2578063e60a955d146103b8578063f7105c05146103d3575b600080fd5b34156100f257600080fd5b610109600435600160a060020a03602435166103e9565b005b341561011657600080fd5b610121600435610487565b60405197885260208801969096526040808801959095526060870193909352608086019190915260a085015260c084015260e0830191909152610100909101905180910390f35b341561017357600080fd5b610109600160a060020a036004351661054a565b341561019257600080fd5b610109600160a060020a03600435166105a7565b34156101b157600080fd5b610109600435600160a060020a036024358116906044351660643560843560a435151560c435151561062b565b34156101e957600080fd5b6101f4600435610736565b60405192835290151560208301526040808301919091526060909101905180910390f35b341561022357600080fd5b6101096107c0565b341561023657600080fd5b61023e610880565b604051600160a060020a03909116815260200160405180910390f35b341561026557600080fd5b610109600435602435604435606435151561088f565b341561028657600080fd5b61010960043560243560443560643560843560a43560c43560e43561010435610c0c565b34156102b557600080fd5b610109600435602435610cc0565b34156102ce57600080fd5b6102d9600435610d92565b604051600160a060020a03998a1681529790981660208801526040808801969096526060870194909452911515608086015260a085015260c0840152151560e083015261010082019290925261012001905180910390f35b341561033c57600080fd5b610344610e70565b60405190815260200160405180910390f35b341561036157600080fd5b61023e610ed9565b341561037457600080fd5b61038e60ff60043516600160a060020a0360243516610ee8565b604051901515815260200160405180910390f35b34156103ad57600080fd5b61038e600435611063565b34156103c357600080fd5b61010960043560243515156110d6565b34156103de57600080fd5b6103446004356111d3565b600154600160a060020a0316151561040057600080fd5b600261040c8133610ee8565b151561041757600080fd5b600154600160a060020a0316630ff85b94848460405160e060020a63ffffffff85160281526004810192909252600160a060020a03166024820152604401600060405180830381600087803b151561046e57600080fd5b6102c65a03f1151561047f57600080fd5b505050505050565b6001546000908190819081908190819081908190600160a060020a0316631314394d8a83604051610100015260405160e060020a63ffffffff8416028152600481019190915260240161010060405180830381600087803b15156104ea57600080fd5b6102c65a03f115156104fb57600080fd5b5050506040518051906020018051906020018051906020018051906020018051906020018051906020018051906020018051905097509750975097509750975097509750919395975091939597565b60006105568133610ee8565b151561056157600080fd5b600154600160a060020a03161561057757600080fd5b506001805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0392909216919091179055565b600054600160a060020a0390811690331681146105c357600080fd5b600154600160a060020a031663a6f9dae18360405160e060020a63ffffffff8416028152600160a060020a039091166004820152602401600060405180830381600087803b151561061357600080fd5b6102c65a03f1151561062457600080fd5b5050505050565b600154600160a060020a0316151561064257600080fd5b60048561064f8282610ee8565b151561065a57600080fd5b60026106668133610ee8565b151561067157600080fd5b600154600160a060020a031663de280a638b8b8b8b8b8b6000808d8160405160e060020a63ffffffff8d16028152600481019a909a52600160a060020a0398891660248b015296909716604489015260648801949094526084870192909252151560a486015260c485015260e484015290151561010483015261012482015261014401600060405180830381600087803b151561070d57600080fd5b6102c65a03f1151561071e57600080fd5b50505061072a8a611226565b50505050505050505050565b60015460009081908190600160a060020a031663312f9a8c85836040516060015260405160e060020a63ffffffff84160281526004810191909152602401606060405180830381600087803b151561078d57600080fd5b6102c65a03f1151561079e57600080fd5b5050506040518051906020018051906020018051929791965091945092505050565b600060026107ce8133610ee8565b15156107d957600080fd5b600154600160a060020a03166331a5daf36000604051602001526040518163ffffffff1660e060020a028152600401602060405180830381600087803b151561082157600080fd5b6102c65a03f1151561083257600080fd5b505050604051805190509150817fd3ab0c36887772472a25b1c1cecc0dee794b7c5ca472edaa2db255346878ee6b33604051600160a060020a03909116815260200160405180910390a25050565b600154600160a060020a031681565b600154600090600160a060020a031615156108a957600080fd5b600154600160a060020a031663ed9c058b8660006040516020015260405160e060020a63ffffffff84160281526004810191909152602401602060405180830381600087803b15156108fa57600080fd5b6102c65a03f1151561090b57600080fd5b5050506040518051905080600160a060020a031633600160a060020a031614151561093557600080fd5b600154600160a060020a031663c53fb1a58760006040516020015260405160e060020a63ffffffff84160281526004810191909152602401602060405180830381600087803b151561098657600080fd5b6102c65a03f1151561099757600080fd5b5050506040518051905015156109ac57600080fd5b600154600160a060020a031663042157c28760006040516020015260405160e060020a63ffffffff84160281526004810191909152602401602060405180830381600087803b15156109fd57600080fd5b6102c65a03f11515610a0e57600080fd5b505050604051805160015490935085915087907fa0ab5f173b69a28f81848a665a77213faedda299f939f047032c1e714ce6e94b9085908990600160a060020a031663b91dccde8560006040516020015260405160e060020a63ffffffff84160281526004810191909152602401602060405180830381600087803b1515610a9557600080fd5b6102c65a03f11515610aa657600080fd5b5050506040518051905088604051938452602084019290925260408084019190915290151560608301526080909101905180910390a3600154600160a060020a031663a60b6fdc878660405160e060020a63ffffffff851602815260048101929092526024820152604401600060405180830381600087803b1515610b2a57600080fd5b6102c65a03f11515610b3b57600080fd5b5050600154600160a060020a031690506357c3bc60878760405160e060020a63ffffffff851602815260048101929092526024820152604401600060405180830381600087803b1515610b8d57600080fd5b6102c65a03f11515610b9e57600080fd5b5050600154600160a060020a031690506336016d71874260405160e060020a63ffffffff851602815260048101929092526024820152604401600060405180830381600087803b1515610bf057600080fd5b6102c65a03f11515610c0157600080fd5b505050505050505050565b600154600160a060020a03161515610c2357600080fd5b6002610c2f8133610ee8565b1515610c3a57600080fd5b600154600160a060020a0316639963aece8b8b8b8b8b8b8b8b8b60405160e060020a63ffffffff8c160281526004810199909952602489019790975260448801959095526064870193909352608486019190915260a485015260c484015260e483015261010482015261012401600060405180830381600087803b151561070d57600080fd5b60008054600160a060020a03169063a34b80b090604051602001526040518163ffffffff1660e060020a028152600401602060405180830381600087803b1515610d0957600080fd5b6102c65a03f11515610d1a57600080fd5b5050506040518051905080600160a060020a031633600160a060020a0316141515610d4457600080fd5b600154600160a060020a031663c2791306848460405160e060020a63ffffffff851602815260048101929092526024820152604401600060405180830381600087803b151561046e57600080fd5b6000806000806000806000806000600160009054906101000a9004600160a060020a0316600160a060020a031663ade1e9928b6000604051610120015260405160e060020a63ffffffff8416028152600481019190915260240161012060405180830381600087803b1515610e0657600080fd5b6102c65a03f11515610e1757600080fd5b505050604051805190602001805190602001805190602001805190602001805190602001805190602001805190602001805190602001805190509850985098509850985098509850985098509193959799909294969850565b600154600090600160a060020a031663ae51439c82604051602001526040518163ffffffff1660e060020a028152600401602060405180830381600087803b1515610eba57600080fd5b6102c65a03f11515610ecb57600080fd5b505050604051805191505090565b600054600160a060020a031681565b6000805481908190600160a060020a038086169116638da5cb5b83604051602001526040518163ffffffff1660e060020a028152600401602060405180830381600087803b1515610f3857600080fd5b6102c65a03f11515610f4957600080fd5b50505060405180519050600160a060020a03161415610f6b576001925061105b565b60008054600160a060020a031690635c7460d690604051602001526040518163ffffffff1660e060020a028152600401602060405180830381600087803b1515610fb457600080fd5b6102c65a03f11515610fc557600080fd5b50505060405180519050600160a060020a031663265209f28560006040516020015260405160e060020a63ffffffff8416028152600160a060020a039091166004820152602401602060405180830381600087803b151561102557600080fd5b6102c65a03f1151561103657600080fd5b50505060405180519050915084600681111561104e57fe5b60020a8281161515935090505b505092915050565b600154600090600160a060020a031663c53fb1a583836040516020015260405160e060020a63ffffffff84160281526004810191909152602401602060405180830381600087803b15156110b657600080fd5b6102c65a03f115156110c757600080fd5b50505060405180519392505050565b600154600160a060020a031615156110ed57600080fd5b60026110f98133610ee8565b151561110457600080fd5b600154600160a060020a031663e60a955d848460405160e060020a63ffffffff8516028152600481019290925215156024820152604401600060405180830381600087803b151561115457600080fd5b6102c65a03f1151561116557600080fd5b50505081156111a057827fbb4a2f3fc0f65f1ee98dc140226f7718191ba94892efdc55613785ccbf00a8e860405160405180910390a26111ce565b827f61f3fbe0c2049de9b0219669983a937b4377c1dced582b6a720a87b49ae3c08b60405160405180910390a25b505050565b600154600090600160a060020a031663f7105c0583836040516020015260405160e060020a63ffffffff84160281526004810191909152602401602060405180830381600087803b15156110b657600080fd5b60015460009081908190600160a060020a031663c6bc1a7685836040516060015260405160e060020a63ffffffff84160281526004810191909152602401606060405180830381600087803b151561127d57600080fd5b6102c65a03f1151561128e57600080fd5b505050604051805190602001805190602001805190509250925092508280156112b45750815b80156112be575080155b156113ba5760018054600160a060020a03169063054dddde90869060405160e060020a63ffffffff8516028152600481019290925215156024820152604401600060405180830381600087803b151561131657600080fd5b6102c65a03f1151561132757600080fd5b505050837f1952717c0461c82af1fee2f55fe9887230a44916d9214bcdf89905bd2c3918a960405160405180910390a2600154600160a060020a03166336016d71854260405160e060020a63ffffffff851602815260048101929092526024820152604401600060405180830381600087803b15156113a557600080fd5b6102c65a03f115156113b657600080fd5b5050505b505050505600a165627a7a72305820f3b55a3d2521c8621f59cd4bf65d6dadc2ccb2c988e7771ecd0dbb85be51a58b0029' 99 | } 100 | -------------------------------------------------------------------------------- /energyweb/smart_contract/origin/certificate_v1.py: -------------------------------------------------------------------------------- 1 | """ 2 | Certificate Logic Smart-Contract interface 3 | """ 4 | contract = { 5 | "address": "0x", 6 | "abi": [ 7 | { 8 | "constant": True, 9 | "inputs": [], 10 | "name": "cooContract", 11 | "outputs": [ 12 | { 13 | "name": "", 14 | "type": "address" 15 | } 16 | ], 17 | "payable": False, 18 | "stateMutability": "view", 19 | "type": "function" 20 | }, 21 | { 22 | "constant": True, 23 | "inputs": [ 24 | { 25 | "name": "_role", 26 | "type": "uint8" 27 | }, 28 | { 29 | "name": "_caller", 30 | "type": "address" 31 | } 32 | ], 33 | "name": "isRole", 34 | "outputs": [ 35 | { 36 | "name": "", 37 | "type": "bool" 38 | } 39 | ], 40 | "payable": False, 41 | "stateMutability": "view", 42 | "type": "function" 43 | }, 44 | { 45 | "constant": True, 46 | "inputs": [], 47 | "name": "certificateDb", 48 | "outputs": [ 49 | { 50 | "name": "", 51 | "type": "address" 52 | } 53 | ], 54 | "payable": False, 55 | "stateMutability": "view", 56 | "type": "function" 57 | }, 58 | { 59 | "inputs": [ 60 | { 61 | "name": "_coo", 62 | "type": "address" 63 | } 64 | ], 65 | "payable": False, 66 | "stateMutability": "nonpayable", 67 | "type": "constructor" 68 | }, 69 | { 70 | "anonymous": False, 71 | "inputs": [ 72 | { 73 | "indexed": True, 74 | "name": "_certificateId", 75 | "type": "uint256" 76 | }, 77 | { 78 | "indexed": False, 79 | "name": "powerInW", 80 | "type": "uint256" 81 | }, 82 | { 83 | "indexed": False, 84 | "name": "owner", 85 | "type": "address" 86 | }, 87 | { 88 | "indexed": False, 89 | "name": "escrow", 90 | "type": "address" 91 | } 92 | ], 93 | "name": "LogCreatedCertificate", 94 | "type": "event" 95 | }, 96 | { 97 | "anonymous": False, 98 | "inputs": [ 99 | { 100 | "indexed": True, 101 | "name": "_certificateId", 102 | "type": "uint256" 103 | }, 104 | { 105 | "indexed": False, 106 | "name": "_retire", 107 | "type": "bool" 108 | } 109 | ], 110 | "name": "LogRetireRequest", 111 | "type": "event" 112 | }, 113 | { 114 | "anonymous": False, 115 | "inputs": [ 116 | { 117 | "indexed": True, 118 | "name": "_certificateId", 119 | "type": "uint256" 120 | }, 121 | { 122 | "indexed": False, 123 | "name": "_oldOwner", 124 | "type": "address" 125 | }, 126 | { 127 | "indexed": False, 128 | "name": "_newOwner", 129 | "type": "address" 130 | }, 131 | { 132 | "indexed": False, 133 | "name": "_oldEscrow", 134 | "type": "address" 135 | }, 136 | { 137 | "indexed": False, 138 | "name": "_newEscrow", 139 | "type": "address" 140 | } 141 | ], 142 | "name": "LogCertificateOwnerChanged", 143 | "type": "event" 144 | }, 145 | { 146 | "constant": False, 147 | "inputs": [ 148 | { 149 | "name": "_database", 150 | "type": "address" 151 | } 152 | ], 153 | "name": "init", 154 | "outputs": [], 155 | "payable": False, 156 | "stateMutability": "nonpayable", 157 | "type": "function" 158 | }, 159 | { 160 | "constant": False, 161 | "inputs": [ 162 | { 163 | "name": "_assetId", 164 | "type": "uint256" 165 | }, 166 | { 167 | "name": "_owner", 168 | "type": "address" 169 | }, 170 | { 171 | "name": "_powerInW", 172 | "type": "uint256" 173 | } 174 | ], 175 | "name": "createCertificate", 176 | "outputs": [ 177 | { 178 | "name": "", 179 | "type": "uint256" 180 | } 181 | ], 182 | "payable": False, 183 | "stateMutability": "nonpayable", 184 | "type": "function" 185 | }, 186 | { 187 | "constant": False, 188 | "inputs": [ 189 | { 190 | "name": "_assetId", 191 | "type": "uint256" 192 | }, 193 | { 194 | "name": "_powerInW", 195 | "type": "uint256" 196 | } 197 | ], 198 | "name": "createCertificateForAssetOwner", 199 | "outputs": [ 200 | { 201 | "name": "", 202 | "type": "uint256" 203 | } 204 | ], 205 | "payable": False, 206 | "stateMutability": "nonpayable", 207 | "type": "function" 208 | }, 209 | { 210 | "constant": False, 211 | "inputs": [ 212 | { 213 | "name": "_id", 214 | "type": "uint256" 215 | } 216 | ], 217 | "name": "retireCertificate", 218 | "outputs": [], 219 | "payable": False, 220 | "stateMutability": "nonpayable", 221 | "type": "function" 222 | }, 223 | { 224 | "constant": False, 225 | "inputs": [ 226 | { 227 | "name": "_id", 228 | "type": "uint256" 229 | }, 230 | { 231 | "name": "_newOwner", 232 | "type": "address" 233 | } 234 | ], 235 | "name": "transferOwnershipByEscrow", 236 | "outputs": [], 237 | "payable": False, 238 | "stateMutability": "nonpayable", 239 | "type": "function" 240 | }, 241 | { 242 | "constant": False, 243 | "inputs": [ 244 | { 245 | "name": "_id", 246 | "type": "uint256" 247 | }, 248 | { 249 | "name": "_newOwner", 250 | "type": "address" 251 | } 252 | ], 253 | "name": "changeCertificateOwner", 254 | "outputs": [], 255 | "payable": False, 256 | "stateMutability": "nonpayable", 257 | "type": "function" 258 | }, 259 | { 260 | "constant": True, 261 | "inputs": [ 262 | { 263 | "name": "_certificateId", 264 | "type": "uint256" 265 | } 266 | ], 267 | "name": "getCertificate", 268 | "outputs": [ 269 | { 270 | "name": "_assetId", 271 | "type": "uint256" 272 | }, 273 | { 274 | "name": "_owner", 275 | "type": "address" 276 | }, 277 | { 278 | "name": "_powerInW", 279 | "type": "uint256" 280 | }, 281 | { 282 | "name": "_retired", 283 | "type": "bool" 284 | }, 285 | { 286 | "name": "_dataLog", 287 | "type": "bytes32" 288 | }, 289 | { 290 | "name": "_coSaved", 291 | "type": "uint256" 292 | }, 293 | { 294 | "name": "_escrow", 295 | "type": "address" 296 | }, 297 | { 298 | "name": "_creationTime", 299 | "type": "uint256" 300 | } 301 | ], 302 | "payable": False, 303 | "stateMutability": "view", 304 | "type": "function" 305 | }, 306 | { 307 | "constant": True, 308 | "inputs": [ 309 | { 310 | "name": "_sessionId", 311 | "type": "bytes32" 312 | } 313 | ], 314 | "name": "getCertificateIdBySessionId", 315 | "outputs": [ 316 | { 317 | "name": "", 318 | "type": "uint256" 319 | } 320 | ], 321 | "payable": False, 322 | "stateMutability": "view", 323 | "type": "function" 324 | }, 325 | { 326 | "constant": True, 327 | "inputs": [ 328 | { 329 | "name": "_certificateId", 330 | "type": "uint256" 331 | } 332 | ], 333 | "name": "getCertificateOwner", 334 | "outputs": [ 335 | { 336 | "name": "", 337 | "type": "address" 338 | } 339 | ], 340 | "payable": False, 341 | "stateMutability": "view", 342 | "type": "function" 343 | }, 344 | { 345 | "constant": True, 346 | "inputs": [ 347 | { 348 | "name": "_certificateId", 349 | "type": "uint256" 350 | } 351 | ], 352 | "name": "isRetired", 353 | "outputs": [ 354 | { 355 | "name": "", 356 | "type": "bool" 357 | } 358 | ], 359 | "payable": False, 360 | "stateMutability": "view", 361 | "type": "function" 362 | }, 363 | { 364 | "constant": True, 365 | "inputs": [], 366 | "name": "getCertificateListLength", 367 | "outputs": [ 368 | { 369 | "name": "", 370 | "type": "uint256" 371 | } 372 | ], 373 | "payable": False, 374 | "stateMutability": "view", 375 | "type": "function" 376 | }, 377 | { 378 | "constant": False, 379 | "inputs": [ 380 | { 381 | "name": "_newLogic", 382 | "type": "address" 383 | } 384 | ], 385 | "name": "update", 386 | "outputs": [], 387 | "payable": False, 388 | "stateMutability": "nonpayable", 389 | "type": "function" 390 | } 391 | ], 392 | "bytecode": "0x608060405234801561001057600080fd5b50604051602080611b91833981016040525160008054600160a060020a03909216600160a060020a0319909216919091179055611b3f806100526000396000f3006080604052600436106100c15763ffffffff60e060020a60003504166319ab453c81146100c65780631c1b8772146100e95780632c76a1b61461010a57806351640fee14610143578063544dd390146101aa57806384e814ef146101ce57806393ea5c9914610202578063af2773951461021a578063b1f2cc9d1461022f578063bb5a389e14610244578063c90ad7eb1461027f578063cbbf9f3814610294578063cf6ebee7146102b8578063d50bba1a146102d0578063daba9203146102e8575b600080fd5b3480156100d257600080fd5b506100e7600160a060020a0360043516610303565b005b3480156100f557600080fd5b506100e7600160a060020a0360043516610360565b34801561011657600080fd5b50610131600435600160a060020a0360243516604435610412565b60408051918252519081900360200190f35b34801561014f57600080fd5b5061015b6004356104d5565b60408051988952600160a060020a0397881660208a0152888101969096529315156060880152608087019290925260a086015290921660c084015260e083019190915251908190036101000190f35b3480156101b657600080fd5b506100e7600435600160a060020a03602435166105b1565b3480156101da57600080fd5b506101e6600435610a3a565b60408051600160a060020a039092168252519081900360200190f35b34801561020e57600080fd5b50610131600435610ad2565b34801561022657600080fd5b50610131610b38565b34801561023b57600080fd5b506101e6610bc8565b34801561025057600080fd5b5061026b60ff60043516600160a060020a0360243516610bd7565b604080519115158252519081900360200190f35b34801561028b57600080fd5b506101e6610dc7565b3480156102a057600080fd5b506100e7600435600160a060020a0360243516610dd6565b3480156102c457600080fd5b5061026b6004356110a3565b3480156102dc57600080fd5b506100e7600435611109565b3480156102f457600080fd5b50610131600435602435611324565b600061030f8133610bd7565b151561031a57600080fd5b600154600160a060020a03161561033057600080fd5b506001805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0392909216919091179055565b600054600160a060020a031633811461037857600080fd5b600054600160a060020a0316331461038f57600080fd5b600154604080517fa6f9dae1000000000000000000000000000000000000000000000000000000008152600160a060020a0385811660048301529151919092169163a6f9dae191602480830192600092919082900301818387803b1580156103f657600080fd5b505af115801561040a573d6000803e3d6000fd5b505050505050565b600154600090600160a060020a0316151561042c57600080fd5b6000809054906101000a9004600160a060020a0316600160a060020a031663a34b80b06040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561047e57600080fd5b505af1158015610492573d6000803e3d6000fd5b505050506040513d60208110156104a857600080fd5b505133600160a060020a038216146104bf57600080fd5b6104cc8585856000611589565b95945050505050565b600080600080600080600080600160009054906101000a9004600160a060020a0316600160a060020a03166351640fee8a6040518263ffffffff1660e060020a0281526004018082815260200191505061010060405180830381600087803b15801561054057600080fd5b505af1158015610554573d6000803e3d6000fd5b505050506040513d61010081101561056b57600080fd5b508051602082015160408301516060840151608085015160a086015160c087015160e090970151959e50939c50919a509850965094509092509050919395975091939597565b6001546000908190600160a060020a031615156105cd57600080fd5b826000809054906101000a9004600160a060020a0316600160a060020a0316635c7460d66040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561062057600080fd5b505af1158015610634573d6000803e3d6000fd5b505050506040513d602081101561064a57600080fd5b5051604080517fecb41054000000000000000000000000000000000000000000000000000000008152600160a060020a0384811660048301529151919092169163ecb410549160248083019260209291908290030181600087803b1580156106b157600080fd5b505af11580156106c5573d6000803e3d6000fd5b505050506040513d60208110156106db57600080fd5b505115156106e857600080fd5b600154604080517f84e814ef000000000000000000000000000000000000000000000000000000008152600481018890529051600160a060020a03909216916384e814ef916024808201926020929091908290030181600087803b15801561074f57600080fd5b505af1158015610763573d6000803e3d6000fd5b505050506040513d602081101561077957600080fd5b5051600160a060020a0316331480156108225750600154604080517fcf6ebee7000000000000000000000000000000000000000000000000000000008152600481018890529051600160a060020a039092169163cf6ebee7916024808201926020929091908290030181600087803b1580156107f457600080fd5b505af1158015610808573d6000803e3d6000fd5b505050506040513d602081101561081e57600080fd5b5051155b151561082d57600080fd5b600154604080517fbdd7504800000000000000000000000000000000000000000000000000000000815260048101889052600160a060020a0387811660248301529151919092169163bdd7504891604480830192600092919082900301818387803b15801561089b57600080fd5b505af11580156108af573d6000803e3d6000fd5b5050600154604080517f84e814ef000000000000000000000000000000000000000000000000000000008152600481018a90529051600160a060020a0390921693506384e814ef92506024808201926020929091908290030181600087803b15801561091a57600080fd5b505af115801561092e573d6000803e3d6000fd5b505050506040513d602081101561094457600080fd5b5051600154604080517fd90561ba000000000000000000000000000000000000000000000000000000008152600481018990529051929550600160a060020a039091169163d90561ba916024808201926020929091908290030181600087803b1580156109b057600080fd5b505af11580156109c4573d6000803e3d6000fd5b505050506040513d60208110156109da57600080fd5b505160408051600160a060020a038087168252808816602083015283168183018190526060820152905191935086917f1d7a993900fc1aacdea8768b0618d014b58445a8255572d28b19e64bb71d95959181900360800190a25050505050565b600154604080517f84e814ef000000000000000000000000000000000000000000000000000000008152600481018490529051600092600160a060020a0316916384e814ef91602480830192602092919082900301818787803b158015610aa057600080fd5b505af1158015610ab4573d6000803e3d6000fd5b505050506040513d6020811015610aca57600080fd5b505192915050565b600154604080517f93ea5c99000000000000000000000000000000000000000000000000000000008152600481018490529051600092600160a060020a0316916393ea5c9991602480830192602092919082900301818787803b158015610aa057600080fd5b600154604080517faf2773950000000000000000000000000000000000000000000000000000000081529051600092600160a060020a03169163af27739591600480830192602092919082900301818787803b158015610b9757600080fd5b505af1158015610bab573d6000803e3d6000fd5b505050506040513d6020811015610bc157600080fd5b5051905090565b600054600160a060020a031681565b60008060006007856006811115610bea57fe5b1115610bf557600080fd5b83600160a060020a03166000809054906101000a9004600160a060020a0316600160a060020a0316638da5cb5b6040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015610c5157600080fd5b505af1158015610c65573d6000803e3d6000fd5b505050506040513d6020811015610c7b57600080fd5b5051600160a060020a03161415610c955760019250610dbf565b6000809054906101000a9004600160a060020a0316600160a060020a0316635c7460d66040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015610ce757600080fd5b505af1158015610cfb573d6000803e3d6000fd5b505050506040513d6020811015610d1157600080fd5b5051604080517f265209f2000000000000000000000000000000000000000000000000000000008152600160a060020a0387811660048301529151919092169163265209f29160248083019260209291908290030181600087803b158015610d7857600080fd5b505af1158015610d8c573d6000803e3d6000fd5b505050506040513d6020811015610da257600080fd5b50519150846006811115610db257fe5b60020a8281161515935090505b505092915050565b600154600160a060020a031681565b60015460009081908190600160a060020a03161515610df457600080fd5b6000809054906101000a9004600160a060020a0316600160a060020a031663a34b80b06040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015610e4657600080fd5b505af1158015610e5a573d6000803e3d6000fd5b505050506040513d6020811015610e7057600080fd5b505133600160a060020a03821614610e8757600080fd5b600154604080517f51640fee000000000000000000000000000000000000000000000000000000008152600481018990529051600160a060020a03909216916351640fee91602480820192610100929091908290030181600087803b158015610eef57600080fd5b505af1158015610f03573d6000803e3d6000fd5b505050506040513d610100811015610f1a57600080fd5b506020810151606082015160c09092015190955090935091508215610f3e57600080fd5b600154604080517fbdd7504800000000000000000000000000000000000000000000000000000000815260048101899052600160a060020a0388811660248301529151919092169163bdd7504891604480830192600092919082900301818387803b158015610fac57600080fd5b505af1158015610fc0573d6000803e3d6000fd5b5050600154604080517f06ec8982000000000000000000000000000000000000000000000000000000008152600481018b90526000602482018190529151600160a060020a0390931694506306ec898293506044808201939182900301818387803b15801561102e57600080fd5b505af1158015611042573d6000803e3d6000fd5b505060408051600160a060020a038089168252808a1660208301528616818301526000606082015290518993507f1d7a993900fc1aacdea8768b0618d014b58445a8255572d28b19e64bb71d959592509081900360800190a2505050505050565b600154604080517fcf6ebee7000000000000000000000000000000000000000000000000000000008152600481018490529051600092600160a060020a03169163cf6ebee791602480830192602092919082900301818787803b158015610aa057600080fd5b6001546000908190600160a060020a0316151561112557600080fd5b600154604080517f51640fee000000000000000000000000000000000000000000000000000000008152600481018690529051600160a060020a03909216916351640fee91602480820192610100929091908290030181600087803b15801561118d57600080fd5b505af11580156111a1573d6000803e3d6000fd5b505050506040513d6101008110156111b857600080fd5b5060208101516060909101519092509050600160a060020a03821633146111de57600080fd5b80151561131f57600154604080517f06ec8982000000000000000000000000000000000000000000000000000000008152600481018690526000602482018190529151600160a060020a03909316926306ec89829260448084019391929182900301818387803b15801561125157600080fd5b505af1158015611265573d6000803e3d6000fd5b5050600154604080517fd50bba1a000000000000000000000000000000000000000000000000000000008152600481018890529051600160a060020a03909216935063d50bba1a925060248082019260009290919082900301818387803b1580156112cf57600080fd5b505af11580156112e3573d6000803e3d6000fd5b5050604080516001815290518693507f4c9e4e2878404da5e23cb4c5ef447b8e31155efa1229be3e2a8541cc5aab275d92509081900360200190a25b505050565b60008060066113338133610bd7565b151561133e57600080fd5b600154600160a060020a0316151561135557600080fd5b6000809054906101000a9004600160a060020a0316600160a060020a03166324a793c46040518163ffffffff1660e060020a028152600401602060405180830381600087803b1580156113a757600080fd5b505af11580156113bb573d6000803e3d6000fd5b505050506040513d60208110156113d157600080fd5b5051604080517fc6bc1a76000000000000000000000000000000000000000000000000000000008152600481018890529051600160a060020a039092169163c6bc1a76916024808201926020929091908290030181600087803b15801561143757600080fd5b505af115801561144b573d6000803e3d6000fd5b505050506040513d602081101561146157600080fd5b5051151561146e57600080fd5b6000809054906101000a9004600160a060020a0316600160a060020a03166324a793c46040518163ffffffff1660e060020a028152600401602060405180830381600087803b1580156114c057600080fd5b505af11580156114d4573d6000803e3d6000fd5b505050506040513d60208110156114ea57600080fd5b5051604080517fade1e992000000000000000000000000000000000000000000000000000000008152600481018890529051600160a060020a039092169163ade1e9929160248082019260c0929091908290030181600087803b15801561155057600080fd5b505af1158015611564573d6000803e3d6000fd5b505050506040513d60c081101561157a57600080fd5b506020015191506104cc858386335b600154600090819081908190600160a060020a031615156115a957600080fd5b6000809054906101000a9004600160a060020a0316600160a060020a03166324a793c46040518163ffffffff1660e060020a028152600401602060405180830381600087803b1580156115fb57600080fd5b505af115801561160f573d6000803e3d6000fd5b505050506040513d602081101561162557600080fd5b5051604080517f76b240ee000000000000000000000000000000000000000000000000000000008152600481018b9052602481018990529051600160a060020a03909216916376b240ee916044808201926020929091908290030181600087803b15801561169257600080fd5b505af11580156116a6573d6000803e3d6000fd5b505050506040513d60208110156116bc57600080fd5b505160008054604080517f24a793c40000000000000000000000000000000000000000000000000000000081529051939650600160a060020a03909116926324a793c492600480840193602093929083900390910190829087803b15801561172357600080fd5b505af1158015611737573d6000803e3d6000fd5b505050506040513d602081101561174d57600080fd5b5051604080517f1e13970b000000000000000000000000000000000000000000000000000000008152600481018b9052602481018990529051600160a060020a0390921691631e13970b916044808201926020929091908290030181600087803b1580156117ba57600080fd5b505af11580156117ce573d6000803e3d6000fd5b505050506040513d60208110156117e457600080fd5b505115156117f157600080fd5b6000809054906101000a9004600160a060020a0316600160a060020a03166324a793c46040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561184357600080fd5b505af1158015611857573d6000803e3d6000fd5b505050506040513d602081101561186d57600080fd5b5051604080517ff7105c05000000000000000000000000000000000000000000000000000000008152600481018b90529051600160a060020a039092169163f7105c05916024808201926020929091908290030181600087803b1580156118d357600080fd5b505af11580156118e7573d6000803e3d6000fd5b505050506040513d60208110156118fd57600080fd5b5051600154604080517f42c366a5000000000000000000000000000000000000000000000000000000008152600481018c9052600160a060020a038b81166024830152604482018b9052606482018590526084820188905289811660a483015291519395509116916342c366a59160c4808201926020929091908290030181600087803b15801561198d57600080fd5b505af11580156119a1573d6000803e3d6000fd5b505050506040513d60208110156119b757600080fd5b505160408051888152600160a060020a03808b166020830152881681830152905191925082917f05763c5a99ca059f9fba5a40ed660f24ddd30b3a3d6f367bc071fbc22a79ffaf9181900360600190a26000809054906101000a9004600160a060020a0316600160a060020a03166324a793c46040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015611a5957600080fd5b505af1158015611a6d573d6000803e3d6000fd5b505050506040513d6020811015611a8357600080fd5b5051604080517f50ddca55000000000000000000000000000000000000000000000000000000008152600481018b9052602481018690529051600160a060020a03909216916350ddca559160448082019260009290919082900301818387803b158015611aef57600080fd5b505af1158015611b03573d6000803e3d6000fd5b50929a99505050505050505050505600a165627a7a723058203fa18a25ccb260d6fe83e94e3af5e1670fb25dce432c8e1d65f6593d24721d460029", 393 | } 394 | --------------------------------------------------------------------------------