Skip to content

Commit e0dc798

Browse files
authored
Merge pull request #81 from hyperledger/master
Master
2 parents 0853815 + c568eef commit e0dc798

File tree

117 files changed

+1387
-1823
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

117 files changed

+1387
-1823
lines changed

common/error.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
def error(msg: str, exc_type: Exception=Exception) -> Exception:
2+
"""
3+
Wrapper to get around Python's distinction between statements and expressions
4+
Can be used in lambdas and expressions such as: a if b else error(c)
5+
6+
:param msg: error message
7+
:param exc_type: type of exception to raise
8+
"""
9+
raise exc_type(msg)

common/serializers/signing_serializer.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"""
2323
from collections import Iterable
2424

25-
from plenum.common.error import error
25+
from common.error import error
2626
from stp_core.common.log import getlogger
2727

2828
logger = getlogger()
@@ -32,7 +32,8 @@
3232

3333

3434
class SigningSerializer:
35-
def serialize(self, obj, level=0, objname=None, topLevelKeysToIgnore=None, toBytes=True):
35+
def serialize(self, obj, level=0, objname=None, topLevelKeysToIgnore=None,
36+
toBytes=True):
3637
"""
3738
Create a string representation of the given object.
3839

docs/main.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
Outline of the system
2-
Node
2+
# TODO
3+

docs/plugins.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Plugins
2+
3+
Plugins allow for supporting new transactions without modifying/adding to the core parts of the system.
4+
Supporting any new transaction requires:
5+
- Defining new transaction types
6+
- Defining new message validation rules or updates to any message validation rules
7+
- Logic to handle new transactions, validation, making changes to state, etc (for more detail look at the doc on [request handling](request_handling.md)).
8+
- Storage of transactions in existing ledger or introducing new ledgers
9+
- Changes to existing state or introducing new states
10+
- Defining new constants
11+
- Defining any new config variables
12+
13+
14+
### Conventions that need to be followed when defining a new plugin:
15+
1. Each plugin is a separate python package that lives under the package defined in `PLUGIN_ROOT` in `config.py`. It is currently set to `plenum.server.plugin`
16+
2. The list of enabled plugins is defined in `ENABLED_PLUGINS` in `config.py`.
17+
3. Each plugin has unique name, this is the name in the list `ENABLED_PLUGINS` and has to match the package name of the plugin.
18+
4. The plugin package contains defines several items in the `__init__.py` file
19+
- It contains a ledger id for each new ledger that the plugin introduces, see `LEDGER_IDS`
20+
- For each new field that needs to be added in the `Request`, the plugin defines the name and its corresponding input validation function in a dict, see `CLIENT_REQUEST_FIELDS`
21+
- Each write or query transaction type is defined in `AcceptableWriteTypes` and `AcceptableQueryTypes` respectively
22+
5. Each plugin package contains a `main.py` that contains a method `integrate_plugin_in_node` to initialise and register any new ledgers, states, authenticators,
23+
request handlers, etc.
24+
6. If the plugin requires a new authenticator, it should be defined in `client_authn.py` in the plugin package.
25+
7. New storage layers should be defined in `storage.py` in the package
26+
8. New transactions need to be in `transactions.py` in the plugin package. To avoid conflicts in transaction type values, each plugin defines a unique prefix to be added before
27+
the value of the transaction type, see `PREFIX`.
28+
9. Any new config variables that have to be introduced are introduced in `get_config` in `config.py` in the plugin package.
29+
30+
31+
### Plugin setup
32+
When plenum initialises, initialises all enabled plugins; this is done through `setup_plugins` method defined in `plenum/__init__.py` Plenum defines 2 project level globals:
33+
`PLUGIN_LEDGER_IDS` which would be the set of ids of all new ledgers defined by any enabled plugin and `PLUGIN_CLIENT_REQUEST_FIELDS` would be a dictionary of all new `Request` fields
34+
defined by any plugin and its corresponding validator.
35+
`setup_plugins` iterates over each of the enabled plugin packages and updates `PLUGIN_LEDGER_IDS` and `PLUGIN_CLIENT_REQUEST_FIELDS`.
36+
It then reloads some modules which defines input validation objects and schemas, this is done since set of valid ledger ids of request fields might have changed.
37+
38+
39+
### Demo plugin
40+
The Demo plugin serves as an example of how a plugin should be written, though it does some extra setup actions that an actual plugin will not do.
41+
Since the configuration has been already loaded completely and the input validation objects have been initialised, the changes made by this plugin need to be reflected in them.
42+
Therefore after updating the loaded config in `tconf` in `plenum/test/plugin/demo_plugin/conftest.py`, in the `do_plugin_initialisation_for_tests`, all such modules are reloaded and `do_plugin_initialisation_for_tests` is called after the plugin is setup (`setup_plugins`)
43+
The demo plugin is located at `plenum/test/plugin/demo_plugin`. The demo plugin models a very trivial auction functionality.
44+
1. The plugin defines new transaction in `plenum/test/plugin/demo_plugin/transactions.py`
45+
2. The plugin defines constants denoting transaction types in `plenum/test/plugin/demo_plugin/constants.py`
46+
3. The demo plugin defines a new ledger for which it declares the ledger id `AUCTION_LEDGER_ID` in `plenum/test/plugin/demo_plugin/__init__.py`.
47+
Also the plugin defines a new field in request called `fix_length_dummy` and corresponding input validator for that.
48+
4. New ledger, hash store and state are defined in `plenum/test/plugin/demo_plugin/storage.py`
49+
5. A request handler for the plugin introduced transactions are defined, `AuctionReqHandler` in `plenum/test/plugin/demo_plugin/auction_req_handler.py`.
50+
The request handler defines methods for static validation, dynamic validation and state changes.
51+
6. New config entries for ledger name, state name, storage type are defined in `plenum/test/plugin/demo_plugin/config.py`
52+
7. An authenticator `AuctionAuthNr` is defined in `plenum/test/plugin/demo_plugin/client_authnr.py`, that specifies the query and write types.
53+
8. The integration of this plugin happens in `integrate_plugin_in_node` in `plenum/test/plugin/demo_plugin/main.py` which takes the node as an argument. In this integration method:
54+
- Update the node's config with the plugin's config
55+
- Initialise the plugin's ledger, state and other storage components
56+
- Register the storage components in the node.
57+
- Initialise the authenticator and register it with the node
58+
- Initialise and register the request handler with the node.
59+
9. Now the above method can be called on any initialised node. For the tests, the `TestNode`s are updated in the fixture `do_post_node_creation` in `plenum/test/plugin/demo_plugin/conftest.py`

ledger/test/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
from common.serializers.msgpack_serializer import MsgPackSerializer
77
from common.serializers.signing_serializer import SigningSerializer
88
from ledger.genesis_txn.genesis_txn_file_util import create_genesis_txn_init_ledger
9-
from ledger.test.helper import create_ledger, create_ledger_text_file_storage, create_ledger_chunked_file_storage, \
10-
create_ledger_leveldb_file_storage
9+
from ledger.test.helper import create_ledger, create_ledger_text_file_storage, \
10+
create_ledger_chunked_file_storage, create_ledger_leveldb_file_storage
1111

1212

1313
@pytest.fixture(scope='module')

ledger/test/test_ledger.py

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -320,29 +320,10 @@ def test_add_get_txns(ledger_no_genesis):
320320

321321
check_ledger_generator(ledger)
322322

323-
for s, t in ledger.getAllTxn(frm=1, to=20):
324-
assert txns[s - 1] == t
325-
326-
for s, t in ledger.getAllTxn(frm=3, to=8):
327-
assert txns[s - 1] == t
328-
329-
for s, t in ledger.getAllTxn(frm=5, to=17):
330-
assert txns[s - 1] == t
331-
332-
for s, t in ledger.getAllTxn(frm=6, to=10):
333-
assert txns[s - 1] == t
334-
335-
for s, t in ledger.getAllTxn(frm=3, to=3):
336-
assert txns[s - 1] == t
337-
338-
for s, t in ledger.getAllTxn(frm=3):
339-
assert txns[s - 1] == t
340-
341-
for s, t in ledger.getAllTxn(to=10):
342-
assert txns[s - 1] == t
343-
344-
for s, t in ledger.getAllTxn():
345-
assert txns[s - 1] == t
323+
for frm, to in [(1, 20), (3, 8), (5, 17), (6, 10), (3, 3),
324+
(3, None), (None, 10), (None, None)]:
325+
for s, t in ledger.getAllTxn(frm=frm, to=to):
326+
assert txns[s - 1] == t
346327

347328
# with pytest.raises(AssertionError):
348329
# list(ledger.getAllTxn(frm=3, to=1))

plenum/__init__.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,34 @@
99
raise ImportError("Python 3.5.0 or later required.")
1010

1111
import os # noqa
12+
import importlib # noqa
1213
from importlib.util import module_from_spec, spec_from_file_location # noqa: E402
1314

1415
import plenum # noqa: E402
1516
import plenum.server.plugin # noqa: E402
17+
1618
from plenum.common.config_util import getConfigOnce # noqa: E402
1719

1820
PLUGIN_LEDGER_IDS = set()
1921
PLUGIN_CLIENT_REQUEST_FIELDS = {}
2022

2123

2224
def setup_plugins():
25+
# TODO: Should have a check to make sure no plugin defines any conflicting ledger id or request field
2326
global PLUGIN_LEDGER_IDS
27+
global PLUGIN_CLIENT_REQUEST_FIELDS
2428

2529
config = getConfigOnce()
2630

27-
ENABLED_PLUGINS = config.ENABLED_PLUGINS
28-
for plugin_name in ENABLED_PLUGINS:
29-
plugin_path = os.path.join(plenum.server.plugin.__path__[0],
31+
plugin_root = config.PLUGIN_ROOT
32+
try:
33+
plugin_root = importlib.import_module(plugin_root)
34+
except ImportError:
35+
raise ImportError('Incorrect plugin root {}. No such package found'.
36+
format(plugin_root))
37+
enabled_plugins = config.ENABLED_PLUGINS
38+
for plugin_name in enabled_plugins:
39+
plugin_path = os.path.join(plugin_root.__path__[0],
3040
plugin_name, '__init__.py')
3141
spec = spec_from_file_location('__init__.py', plugin_path)
3242
init = module_from_spec(spec)
@@ -37,6 +47,12 @@ def setup_plugins():
3747
if 'CLIENT_REQUEST_FIELDS' in plugin_globals:
3848
PLUGIN_CLIENT_REQUEST_FIELDS.update(plugin_globals['CLIENT_REQUEST_FIELDS'])
3949

50+
# Reloading message types since some some schemas would have been changed
51+
import plenum.common.messages.node_messages
52+
import plenum.common.messages.node_message_factory
53+
importlib.reload(plenum.common.messages.node_messages)
54+
importlib.reload(plenum.common.messages.node_message_factory)
55+
4056

4157
setup_plugins()
4258

plenum/client/client.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@
4242
MULTI_SIGNATURE_SIGNATURE, MULTI_SIGNATURE_VALUE
4343
from plenum.common.txn_util import idr_from_req_data
4444
from plenum.common.types import f
45-
from plenum.common.util import getMaxFailures, checkIfMoreThanFSameItems, \
46-
rawToFriendly, mostCommonElement
45+
from plenum.common.util import getMaxFailures, rawToFriendly, mostCommonElement
4746
from plenum.persistence.client_req_rep_store_file import ClientReqRepStoreFile
4847
from plenum.persistence.client_txn_log import ClientTxnLog
4948
from plenum.server.has_action_queue import HasActionQueue
@@ -275,7 +274,7 @@ def start(self, loop):
275274
self.nodestack.start()
276275
self.nodestack.maintainConnections(force=True)
277276
if self.ledger:
278-
self.ledgerManager.setLedgerCanSync(0, True)
277+
self.ledgerManager.setLedgerCanSync(POOL_LEDGER_ID, True)
279278
self.mode = Mode.starting
280279

281280
async def prod(self, limit) -> int:

plenum/client/wallet.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@ def signMsg(self,
172172
:param otherIdentifier:
173173
:return: signature that then can be assigned to request
174174
"""
175-
176175
idr = self.requiredIdr(idr=identifier or otherIdentifier)
177176
signer = self._signerById(idr)
178177
signature = signer.sign(msg)
@@ -198,7 +197,7 @@ def signRequest(self,
198197
# QUESTION: `self.ids[idr]` would be overwritten if same identifier
199198
# is used to send 2 requests, why is `IdData` persisted?
200199
# self.ids[idr] = IdData(idData.signer, req.reqId)
201-
req.signature = self.signMsg(msg=req.signingState(),
200+
req.signature = self.signMsg(msg=req.signingState(identifier=idr),
202201
identifier=idr,
203202
otherIdentifier=req.identifier)
204203

plenum/common/batched.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from collections import deque
2-
from typing import Any, Iterable
2+
from typing import Any, Iterable, Dict
33

44
from plenum.common.constants import BATCH, OP_FIELD_NAME
55
from plenum.common.prepare_batch import split_messages_on_batches
@@ -117,8 +117,8 @@ def flushOutBoxes(self) -> None:
117117
timeout=self.messageTimeout,
118118
serialized=True)
119119
else:
120-
logger.warning("Cannot create batch(es) for {}".format(
121-
self, dest))
120+
logger.warning("{} cannot create batch(es) for {}"
121+
.format(self, dest))
122122
else:
123123
while msgs:
124124
msg = msgs.popleft()

plenum/common/config_helper.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22

33

4-
class PConfigHelper():
4+
class PConfigHelper:
55

66
def __init__(self, config, *, chroot=None):
77
assert config is not None
@@ -47,4 +47,5 @@ def __init__(self, name: str, config, *, chroot=None):
4747

4848
@property
4949
def ledger_dir(self):
50-
return self.chroot_if_needed(os.path.join(self.config.LEDGER_DIR, self.name))
50+
return self.chroot_if_needed(os.path.join(self.config.LEDGER_DIR,
51+
self.name))

plenum/common/config_util.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,14 @@ def _getConfig(general_config_dir: str = None):
7777
config = stp_config
7878
config.__dict__.update(plenum_config.__dict__)
7979

80-
if (general_config_dir):
80+
if general_config_dir:
8181
config.GENERAL_CONFIG_DIR = general_config_dir
8282

8383
if not config.GENERAL_CONFIG_DIR:
8484
raise Exception('GENERAL_CONFIG_DIR must be set')
8585

86-
extend_with_external_config(config, (config.GENERAL_CONFIG_DIR, config.GENERAL_CONFIG_FILE))
86+
extend_with_external_config(config, (config.GENERAL_CONFIG_DIR,
87+
config.GENERAL_CONFIG_FILE))
8788

8889
# "unsafe" is a set of attributes that can set certain behaviors that
8990
# are not safe, for example, 'disable_view_change' disables view changes

plenum/common/constants.py

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from plenum.common.plenum_protocol_version import PlenumProtocolVersion
55
from plenum.common.roles import Roles
66
from plenum.common.transactions import PlenumTransactions
7+
from plenum.common.util import UniqueSet
78

89
NOMINATE = "NOMINATE"
910
REELECTION = "REELECTION"
@@ -144,8 +145,10 @@ class KeyValueStorageType(IntEnum):
144145
@unique
145146
class LedgerState(IntEnum):
146147
not_synced = 1 # Still gathering consistency proofs
147-
syncing = 2 # Got sufficient consistency proofs, will be sending catchup requests and waiting for their replies
148-
synced = 3 # Got replies for all catchup requests, indicating catchup complete for the ledger
148+
syncing = 2 # Got sufficient consistency proofs, will be sending catchup
149+
# requests and waiting for their replies
150+
synced = 3 # Got replies for all catchup requests, indicating catchup
151+
# complete for the ledger
149152

150153

151154
OP_FIELD_NAME = "op"
@@ -165,36 +168,31 @@ class LedgerState(IntEnum):
165168
PLUGIN_BASE_DIR_PATH = "PluginBaseDirPath"
166169
POOL_LEDGER_ID = 0
167170
DOMAIN_LEDGER_ID = 1
171+
CONFIG_LEDGER_ID = 2
168172

169-
VALID_LEDGER_IDS = (POOL_LEDGER_ID, DOMAIN_LEDGER_ID)
173+
VALID_LEDGER_IDS = (POOL_LEDGER_ID, DOMAIN_LEDGER_ID, CONFIG_LEDGER_ID)
170174

171175
CURRENT_PROTOCOL_VERSION = PlenumProtocolVersion.STATE_PROOF_SUPPORT.value
172176

173-
PRE_STATIC_VALIDATION = 1
174-
POST_STATIC_VALIDATION = 2
175-
PRE_SIG_VERIFICATION = 3
176-
POST_SIG_VERIFICATION = 4
177-
PRE_DYNAMIC_VALIDATION = 5
178-
POST_DYNAMIC_VALIDATION = 6
179-
PRE_REQUEST_APPLICATION = 7
180-
POST_REQUEST_APPLICATION = 8
181-
PRE_REQUEST_COMMIT = 9
182-
POST_REQUEST_COMMIT = 10
183-
184-
NODE_HOOKS = [PRE_STATIC_VALIDATION, POST_STATIC_VALIDATION,
185-
PRE_SIG_VERIFICATION, POST_SIG_VERIFICATION,
186-
PRE_DYNAMIC_VALIDATION, POST_DYNAMIC_VALIDATION,
187-
PRE_REQUEST_APPLICATION, POST_REQUEST_APPLICATION,
188-
PRE_REQUEST_COMMIT, POST_REQUEST_COMMIT]
189-
190-
191-
CREATE_PPR = 1
192-
CREATE_PR = 2
193-
CREATE_CM = 3
194-
CREATE_ORD = 4
195-
RECV_PPR = 5
196-
RECV_PR = 6
197-
RECV_CM = 7
198-
199-
REPLICA_HOOKS = [CREATE_PPR, CREATE_PR, CREATE_CM, CREATE_ORD, RECV_PPR,
200-
RECV_PR, RECV_CM]
177+
178+
class NodeHooks(UniqueSet):
179+
PRE_STATIC_VALIDATION = 1
180+
POST_STATIC_VALIDATION = 2
181+
PRE_SIG_VERIFICATION = 3
182+
POST_SIG_VERIFICATION = 4
183+
PRE_DYNAMIC_VALIDATION = 5
184+
POST_DYNAMIC_VALIDATION = 6
185+
PRE_REQUEST_APPLICATION = 7
186+
POST_REQUEST_APPLICATION = 8
187+
PRE_REQUEST_COMMIT = 9
188+
POST_REQUEST_COMMIT = 10
189+
190+
191+
class ReplicaHooks(UniqueSet):
192+
CREATE_PPR = 1
193+
CREATE_PR = 2
194+
CREATE_CM = 3
195+
CREATE_ORD = 4
196+
RECV_PPR = 5
197+
RECV_PR = 6
198+
RECV_CM = 7

plenum/common/error.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,3 @@
44
def fault(ex: Exception, msg: str):
55
from stp_core.common.log import getlogger
66
getlogger().error(msg, exc_info=ex)
7-
8-
9-
def error(msg: str, exc_type: Exception=Exception) -> Exception:
10-
"""
11-
Wrapper to get around Python's distinction between statements and expressions
12-
Can be used in lambdas and expressions such as: a if b else error(c)
13-
14-
:param msg: error message
15-
:param exc_type: type of exception to raise
16-
"""
17-
raise exc_type(msg)

plenum/common/hook_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Callable, Dict, List
22

3-
from plenum.common.error import error
3+
from common.error import error
44

55

66
class HookManager:

0 commit comments

Comments
 (0)