Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introducing Smart Contracts to BlockCerts #171

Open
wants to merge 71 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
454d033
set up
marcplustwo Jan 3, 2020
2395d2b
Update README.md
flamestro Jan 4, 2020
d19a0db
fix install instructions in README
marcplustwo Jan 3, 2020
1cb01a5
reqs: chainpoint -> chainpoint3, freeze versions
marcplustwo Jan 7, 2020
eb2e8c2
add setup test
marcplustwo Jan 7, 2020
1eb320d
with flamestro: change the way merkletools are imported
marcplustwo Jan 9, 2020
6344edb
built interface to blockcerts issuer, implement init as replacement f…
Jan 12, 2020
aa93a29
finished integration approach of our SC based issuer functions into c…
Jan 12, 2020
d137761
Merge pull request #1 from flamestro/setup
flo-weiss Jan 14, 2020
4d7b90c
Merge branch 'master' of github.com:flamestro/cert-issuer
marcplustwo Jan 14, 2020
a6dc235
Merge conf_ini_adjustments into cert_fields_test
marcplustwo Jan 14, 2020
a88930b
add options for smart_contract implementation
marcplustwo Jan 14, 2020
6d21eef
write relevant data to certificate
marcplustwo Jan 16, 2020
f404f4a
implement issuing to smart contract
marcplustwo Jan 17, 2020
1c94b95
clean up
marcplustwo Jan 18, 2020
da1d5c3
adapt conf_ethtest.ini to reflect new config options
marcplustwo Jan 20, 2020
3af1d87
add setup flag for smart_contract option and org. requirements
marcplustwo Jan 20, 2020
d3ea338
make better use of given models and abstractions
marcplustwo Jan 21, 2020
b9ac6b6
remove tx_utils for sc handler
marcplustwo Jan 21, 2020
28b16f1
add all necessary fields for validation to cert
marcplustwo Jan 22, 2020
8fbc410
verify ens entry before issuing
marcplustwo Jan 27, 2020
9670a78
wip: initialize connector with contr_addr sourced from ens
marcplustwo Jan 28, 2020
accc8b1
initialize connector with contr_addr sourced from ens
marcplustwo Jan 28, 2020
748dd0b
differentiate ens registry contracts for ropsten and mainnet
marcplustwo Jan 28, 2020
68c4389
Merge pull request #14 from flamestro/noexplicitcontractaddr
marcplustwo Jan 30, 2020
b250139
fixed docstrings
the-sea-ink Feb 2, 2020
b65cabb
removed exception handling
the-sea-ink Feb 2, 2020
a7b9b00
ensure that not yet revoked certificates get revoked at a later point…
marcplustwo Feb 3, 2020
25312ab
bug fixes: don't forget already revoked hashes
marcplustwo Feb 3, 2020
29429bd
Merge pull request #16 from flamestro/xeniacomments
the-sea-ink Feb 3, 2020
b9337a2
include revocations.json
marcplustwo Feb 4, 2020
fb3ab23
Merge pull request #17 from flamestro/revokecertificates
marcplustwo Feb 4, 2020
db63fef
branch to issue revocations in transaction handler to not break the s…
marcplustwo Feb 4, 2020
6cf43b7
raise error is necessary options aren't set
marcplustwo Feb 5, 2020
95d81d3
fix tests
marcplustwo Feb 5, 2020
1d4ff58
improve outputs and config
marcplustwo Feb 7, 2020
f49ec6f
use given gas management
marcplustwo Feb 9, 2020
4a3793b
static gas management
marcplustwo Feb 10, 2020
ef2aefb
Merge pull request #20 from flamestro/gas
marcplustwo Feb 10, 2020
6e8796e
Update README.md
marcplustwo Feb 10, 2020
039eeb6
Update README.md
marcplustwo Feb 10, 2020
465dcb4
add documentation
marcplustwo Feb 10, 2020
0c0ce7a
documentation more concise
marcplustwo Feb 10, 2020
df6a7f8
working on documentation
marcplustwo Feb 12, 2020
901eb3e
update ENS contract and certificate store abi
marcplustwo Feb 12, 2020
faf4d8e
finish documentation
marcplustwo Feb 13, 2020
7fe8c7c
Fix formatting for smart contract documentation
marcplustwo Feb 13, 2020
7d44b60
Merge pull request #21 from flamestro/documentation
marcplustwo Feb 13, 2020
0341196
write tx_id into certificate again
marcplustwo Feb 14, 2020
d8177a0
handle ens registry address more flexibly
marcplustwo Feb 14, 2020
adafb09
remove contract address option, require ENS
marcplustwo Feb 14, 2020
b77627f
Merge pull request #22 from flamestro/tx_id-source_id
marcplustwo Feb 14, 2020
cd4b70c
Update ethereum_smart_contract.md
marcplustwo Feb 14, 2020
c75d0de
Update ethereum_smart_contract.md
marcplustwo Feb 14, 2020
ad45c52
Update ethereum_smart_contract.md
marcplustwo Feb 14, 2020
85954ac
remove local copy of namehash implementation - use Web3 provided one …
marcplustwo Feb 15, 2020
f128fa1
sensible balance check and gas management
marcplustwo Feb 15, 2020
6323525
Merge branch 'master' of github.com:flamestro/cert-issuer
marcplustwo Feb 15, 2020
dff8d48
Update ethereum_smart_contract.md
marcplustwo Feb 16, 2020
5edf419
rework revocations and clean up transaction_handler
marcplustwo Feb 16, 2020
c1ca6c7
Merge branch 'master' of github.com:flamestro/cert-issuer
marcplustwo Feb 16, 2020
e54d7f7
Add example of revocations.json to documentation
marcplustwo Feb 16, 2020
a005852
remove record of already revoked certificates from revocations.json
marcplustwo Feb 16, 2020
b3b1097
use network provided gas
marcplustwo Feb 17, 2020
1695501
Update ethereum_smart_contract.md
marcplustwo Feb 17, 2020
b6e25bd
get abi from ens
marcplustwo Feb 18, 2020
224804e
Merge branch 'master' of github.com:flamestro/cert-issuer
marcplustwo Feb 18, 2020
01e9bd8
Update ethereum_smart_contract.md
marcplustwo Feb 25, 2020
d6dadda
Update ethereum_smart_contract.md
marcplustwo Mar 6, 2020
63f99e7
Update ethereum_smart_contract.md
marcplustwo Mar 6, 2020
b5fa437
Update README.md
marcplustwo Sep 18, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
[![PyPI version](https://badge.fury.io/py/cert-issuer.svg)](https://badge.fury.io/py/cert-issuer)



# cert-issuer

The cert-issuer project issues blockchain certificates by creating a transaction from the issuing institution to the
recipient on the Bitcoin blockchain that includes the hash of the certificate itself.
recipient on the Bitcoin blockchain that includes the hash of the certificate itself.

## What's special about this fork?
See our [documentation](docs/ethereum_smart_contract.md).

## Web resources
For development or testing using web requests, check out the documentation at [docs/web_resources.md](./docs/web_resources.md).
Expand Down Expand Up @@ -189,14 +193,18 @@ Decide which chain (Bitcoin or Ethereum) to issue to and follow the steps. The b
By default, cert-issuer issues to the Bitcoin blockchain. Run the default setup script if this is the mode you want:
```
python setup.py install

```

To issue to the ethereum blockchain, run the following:
```
python setup.py experimental --blockchain=ethereum
python setup.py install experimental --blockchain=ethereum
```

To issue to the ethereum blockchain using the smart contract backend, run the following:
```
python setup.py install experimental --blockchain=ethereum_smart_contract
```
For more information on the smart contract backend reference the [complete documentation](docs/ethereum_smart_contract.md).

### Create a Bitcoin issuing address

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def ensure_balance(self):
logging.error(error_message)
raise InsufficientFundsError(error_message)

def issue_transaction(self, blockchain_bytes):
def issue_transaction(self, blockchain_bytes, app_config):
op_return_value = b2h(blockchain_bytes)
prepared_tx = self.create_transaction(blockchain_bytes)
signed_tx = self.sign_transaction(prepared_tx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def ensure_balance(self):
logging.error(error_message)
raise InsufficientFundsError(error_message)

def issue_transaction(self, blockchain_bytes):
def issue_transaction(self, blockchain_bytes, app_config):
eth_data_field = b2h(blockchain_bytes)
prepared_tx = self.create_transaction(blockchain_bytes)
signed_tx = self.sign_transaction(prepared_tx)
Expand Down
96 changes: 96 additions & 0 deletions cert_issuer/blockchain_handlers/ethereum_sc/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import logging
import os

from cert_core import BlockchainType
from cert_core import Chain, UnknownChainError

from cert_issuer.certificate_handlers import CertificateBatchHandler, CertificateV2Handler
from cert_issuer.blockchain_handlers.ethereum_sc.connectors import EthereumSCServiceProviderConnector
from cert_issuer.blockchain_handlers.ethereum_sc.ens import ENSConnector
from cert_issuer.blockchain_handlers.ethereum_sc.signer import EthereumSCSigner
from cert_issuer.blockchain_handlers.ethereum_sc.transaction_handlers import EthereumSCTransactionHandler
from cert_issuer.merkle_tree_generator import MerkleTreeGenerator
from cert_issuer.models import MockTransactionHandler
from cert_issuer.signer import FileSecretManager
from cert_issuer.errors import ENSEntryError, MissingArgumentError


class EthereumTransactionCostConstants(object):
def __init__(self, recommended_gas_price, recommended_gas_limit):
self.recommended_gas_price = recommended_gas_price
self.recommended_gas_limit = recommended_gas_limit
logging.info('Set cost constants to recommended_gas_price=%f, recommended_gas_limit=%f',
self.recommended_gas_price, self.recommended_gas_limit)

"""
The below methods currently only use the supplied gasprice/limit.
These values can also be better estimated via a call to the Ethereum blockchain.
"""

def get_recommended_max_cost(self):
return self.recommended_gas_price * self.recommended_gas_limit

def get_gas_price(self):
return self.recommended_gas_price

def get_gas_limit(self):
return self.recommended_gas_limit


def initialize_signer(app_config):
path_to_secret = os.path.join(app_config.usb_name, app_config.key_file)

if app_config.chain.blockchain_type == BlockchainType.ethereum:
signer = EthereumSCSigner(ethereum_chain=app_config.chain)
elif app_config.chain == Chain.mockchain:
signer = None
else:
raise UnknownChainError(app_config.chain)
secret_manager = FileSecretManager(signer=signer, path_to_secret=path_to_secret,
safe_mode=app_config.safe_mode, issuing_address=app_config.issuing_address)
return secret_manager


def instantiate_connector(app_config, cost_constants):
# if contr_addr is not set explicitly (recommended), get it from ens entry
ens = ENSConnector(app_config)
contr_addr = ens.get_addr()

if contr_addr == "0x0000000000000000000000000000000000000000":
raise ENSEntryError(f"Resolved address {contr_addr} from ENS entry {app_config.ens_name}")

app_config.contract_address = contr_addr

connector = EthereumSCServiceProviderConnector(app_config, contr_addr, cost_constants=cost_constants)
return connector

def check_necessary_arguments(app_config):
# required arguments only for smart_contract method
if app_config.ens_name is None:
raise MissingArgumentError("Missing argument ens_name, check your config file.")
if app_config.node_url is None:
raise MissingArgumentError("Missing argument node_url, check your config file.")

# required only if revoke is set
if app_config.revoke is True and app_config.revocation_list_file is None:
raise MissingArgumentError("Missing argument revocation_list_file, check your config file.")

def instantiate_blockchain_handlers(app_config):
check_necessary_arguments(app_config)

issuing_address = app_config.issuing_address
chain = app_config.chain
secret_manager = initialize_signer(app_config)
certificate_batch_handler = CertificateBatchHandler(secret_manager=secret_manager,
certificate_handler=CertificateV2Handler(),
merkle_tree=MerkleTreeGenerator())
if chain == Chain.mockchain:
transaction_handler = MockTransactionHandler()
# ethereum chains
elif chain == Chain.ethereum_mainnet or chain == Chain.ethereum_ropsten:
cost_constants = EthereumTransactionCostConstants(app_config.gas_price, app_config.gas_limit)
connector = instantiate_connector(app_config, cost_constants)
transaction_handler = EthereumSCTransactionHandler(connector, cost_constants, secret_manager,
issuing_address=issuing_address)

return certificate_batch_handler, transaction_handler, connector
103 changes: 103 additions & 0 deletions cert_issuer/blockchain_handlers/ethereum_sc/connectors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import json
import os
import logging
from errors import UnableToSignTxError

from cert_issuer.models import ServiceProviderConnector
from web3 import Web3, HTTPProvider


def get_abi(contract):
"""
Returns smart contract abi.
possible values for contract: "blockcerts", "ens_registry"
"""

directory = os.path.dirname(os.path.abspath(__file__))
path = os.path.join(directory, f"data/{contract}_abi.json")

with open(path, "r") as f:
raw = f.read()
abi = json.loads(raw)
return abi


# this class can be used for both ENS contracts as well as our own ("cert_store")
class EthereumSCServiceProviderConnector(ServiceProviderConnector):
"""
Collects abi, address, contract data and instantiates a contract object
"""
def __init__(self, app_config, contract_address, abi_type="cert_store", private_key=None, cost_constants=None):
self.cost_constants = cost_constants

self.app_config = app_config
self._private_key = private_key

if abi_type == "cert_store":
from cert_issuer.blockchain_handlers.ethereum_sc.ens import ENSConnector
abi = ENSConnector(app_config).get_abi()
else:
abi = get_abi(abi_type)

self._w3 = Web3(HTTPProvider(self.app_config.node_url))
self._w3.eth.defaultAccount = self.app_config.issuing_address

self._contract_obj = self._w3.eth.contract(address=contract_address, abi=abi)

def get_balance(self, address):
return self._w3.eth.getBalance(address)

def create_transaction(self, method, *argv):
gas_limit = self.cost_constants.get_gas_limit()
estimated_gas = self._contract_obj.functions[method](*argv).estimateGas() * 2
if estimated_gas > gas_limit:
logging.warning("Estimated gas of %s more than gas limit of %s, transaction might fail. Please verify on etherescan.com.", estimated_gas, gas_limit)
estimated_gas = gas_limit

gas_price = self._w3.eth.gasPrice
gas_price_limit = self.cost_constants.get_gas_price()

if gas_price > gas_price_limit:
logging.warning("Gas price provided by network of %s higher than gas price of %s set in config, transaction might fail. Please verify on etherescan.com.", gas_price, gas_price_limit)
gas_price = gas_price_limit

tx_options = {
'nonce': self._w3.eth.getTransactionCount(self._w3.eth.defaultAccount),
'gas': estimated_gas,
'gasPrice': gas_price
}

construct_txn = self._contract_obj.functions[method](*argv).buildTransaction(tx_options)
return construct_txn

def broadcast_tx(self, signed_tx):
tx_hash = self._w3.eth.sendRawTransaction(signed_tx.rawTransaction)
tx_receipt = self._w3.eth.waitForTransactionReceipt(tx_hash)
return tx_receipt.transactionHash.hex()

def transact(self, method, *argv):
"""
Sends a signed transaction on the blockchain and waits for a response.
If initialized with private key this class can sign the transaction.
In general, an external signer should be used in conjunction with
create_transaction() and broadcast_tx.
"""
if self._private_key is None:
raise UnableToSignTxError("This method is only available if a private key was passed upon initialization")

prepared_tx = self.create_transaction(method, *argv)
signed_tx = self._sign_transaction(prepared_tx)
txid = self.broadcast_transaction(signed_tx)
return txid

def _sign_transaction(self, prepared_tx):
acct = self._w3.eth.account.from_key(self._private_key)

try:
signed_tx = acct.sign_transaction(prepared_tx)
return signed_tx
except Exception:
raise UnableToSignTxError('You are trying to sign a non transaction type')

def call(self, method, *argv):
return self._contract_obj.functions[method](*argv).call()
Loading