Skip to content
This repository has been archived by the owner on Feb 1, 2024. It is now read-only.

Commit

Permalink
Onchain namespace permission checked by validator
Browse files Browse the repository at this point in the history
The goal is to provide a mechanism to indicate to the
blockchain/network the namespaces transaction families are allowed to
write to and enforce it. This provides a way to implement a namespace
permissioning mechanism. This is essential to allocate certain
namespace to certain transaction families and prevent transactions to
write to addresses they are not allowed to.

For every transaction processed by the validator, it verifies that the
outputs of the transaction are prefixed with one of the namespace
listed by the transaction family it belongs to. The namespaces are
indicated onchain with the settings transaction family. If no
namespaces field is indicated in the onchain settings then it
artificially declare an empty prefix namespace such that any output
listed is valid. Therefore the change proposed is backward compatible.

The new feature is tested by injecting block_info, running some intkey
transactions and declaring valid namespace for block_info and
*invalid* namespaces for intkey such that when blocks are validated it
only includes block_info transactions.

Signed-off-by: Benoit Razet <benoit.razet@pokitdok.com>
  • Loading branch information
Benoit Razet committed Nov 16, 2017
1 parent 0403759 commit 94a66d8
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 1 deletion.
2 changes: 2 additions & 0 deletions bin/run_tests
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@ test_validator() {
copy_coverage .coverage.test_shutdown_smoke
run_docker_test test_events_and_receipts
copy_coverage .coverage.test_events_and_receipts
run_docker_test test_namespace_permission
copy_coverage .coverage.namespace_permission
}

test_go_sdk() {
Expand Down
119 changes: 119 additions & 0 deletions integration/sawtooth_integration/docker/test_namespace_permission.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Copyright 2017 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ------------------------------------------------------------------------------

version: "2.1"

services:

settings-tp:
image: sawtooth-settings-tp:$ISOLATION_ID
volumes:
- $SAWTOOTH_CORE:/project/sawtooth-core
expose:
- 4004
depends_on:
- validator
command: settings-tp -vv -C tcp://validator:4004
stop_signal: SIGKILL

intkey-tp-python:
image: sawtooth-intkey-tp-python:$ISOLATION_ID
volumes:
- $SAWTOOTH_CORE:/project/sawtooth-core
expose:
- 4004
depends_on:
- validator
command: intkey-tp-python -vvv -C tcp://validator:4004
stop_signal: SIGKILL

block-info-tp:
image: sawtooth-block-info-tp:$ISOLATION_ID
volumes:
- $SAWTOOTH_CORE:/project/sawtooth-core
expose:
- 4004
depends_on:
- validator
command: block-info-tp -v -C tcp://validator:4004
stop_signal: SIGKILL

validator:
image: sawtooth-validator:$ISOLATION_ID
volumes:
- $SAWTOOTH_CORE:/project/sawtooth-core
expose:
- 4004
- 8800
# start the validator with an empty genesis batch
command: "bash -c \"\
sawadm keygen && \
sawset genesis \
-k /etc/sawtooth/keys/validator.priv \
-o config-genesis.batch && \
sawset proposal create \
-k /etc/sawtooth/keys/validator.priv \
sawtooth.validator.transaction_families='[{\\\"family\\\": \\\"block_info\\\", \\\"version\\\": \\\"1.0\\\", \\\"namespaces\\\": [\\\"00b10c\\\"]}, {\\\"family\\\": \\\"intkey\\\", \\\"version\\\": \\\"1.0\\\", \\\"namespaces\\\": [\\\"1cf125\\\",\\\"1cf127\\\"]}, {\\\"family\\\":\\\"sawtooth_settings\\\", \\\"version\\\":\\\"1.0\\\", \\\"encoding\\\":\\\"application/protobuf\\\"}]' \
-o config_transaction_families.batch && \
sawset proposal create \
-k /etc/sawtooth/keys/validator.priv \
sawtooth.validator.batch_injectors=block_info \
'sawtooth.validator.block_validation_rules=NofX:1,block_info;XatY:block_info,0;local:0' \
-o config.batch && \
sawadm genesis \
config-genesis.batch config_transaction_families.batch config.batch && \
sawtooth-validator --endpoint tcp://validator:8800 -vv \
--bind component:tcp://eth0:4004 \
--bind network:tcp://eth0:8800 \
\""
environment:
PYTHONPATH: "/project/sawtooth-core/families/block_info"
stop_signal: SIGKILL

rest-api:
image: sawtooth-rest-api:$ISOLATION_ID
volumes:
- $SAWTOOTH_CORE:/project/sawtooth-core
expose:
- 4004
- 8080
depends_on:
- validator
command: sawtooth-rest-api -v --connect tcp://validator:4004 --bind rest-api:8080
stop_signal: SIGKILL

test-namespace-permission:
image: sawtooth-dev-python:$ISOLATION_ID
volumes:
- $SAWTOOTH_CORE:/project/sawtooth-core
expose:
- 8080
depends_on:
- validator
- rest-api
command: "bash -c \"\
nose2-3 \
-c /project/sawtooth-core/integration/sawtooth_integration/nose2.cfg \
-v \
-s /project/sawtooth-core/integration/sawtooth_integration/tests \
test_namespace_permission.TestNamespacePermission \
\""
stop_signal: SIGKILL
environment:
PYTHONPATH: "/project/sawtooth-core/sdk/python:\
/project/sawtooth-core/sdk/examples/intkey_python:\
/project/sawtooth-core/families/block_info:\
/project/sawtooth-core/integration:\
/project/sawtooth-core/signing"
104 changes: 104 additions & 0 deletions integration/sawtooth_integration/tests/test_namespace_permission.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Copyright 2017 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ------------------------------------------------------------------------------

import unittest
import logging
import json
import urllib.request
import urllib.error
import base64
import time

from sawtooth_block_info.common import CONFIG_ADDRESS
from sawtooth_block_info.common import create_block_address
from sawtooth_block_info.protobuf.block_info_pb2 import BlockInfoConfig
from sawtooth_block_info.protobuf.block_info_pb2 import BlockInfo

from sawtooth_intkey.intkey_message_factory import IntkeyMessageFactory


LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.INFO)


def get_blocks():
response = query_rest_api('/blocks')
return response['data']


def get_block_info_config():
bic = BlockInfoConfig()
bic.ParseFromString(get_state(CONFIG_ADDRESS))
return bic


def get_block_info(block_num):
bi = BlockInfo()
bi.ParseFromString(get_state(create_block_address(block_num)))
return bi


def get_state(address):
response = query_rest_api('/state/%s' % address)
return base64.b64decode(response['data'])


def post_batch(batch):
headers = {'Content-Type': 'application/octet-stream'}
response = query_rest_api(
'/batches', data=batch, headers=headers)
response = submit_request('{}'.format(response['link']))
return response


def query_rest_api(suffix='', data=None, headers={}):
url = 'http://rest-api:8080' + suffix
return submit_request(urllib.request.Request(url, data, headers))


def submit_request(request):
response = urllib.request.urlopen(request).read().decode('utf-8')
return json.loads(response)


def make_batches(keys):
imf = IntkeyMessageFactory()
return [imf.create_batch([('set', k, 0)]) for k in keys]

class TestNamespacePermission(unittest.TestCase):
def test_namespace_permission(self):
"""Tests that namespace permission onchain are letting block_info
transactions be injected and preventing intkey transactions to
be validated.
"""
batches = make_batches('abcd')

# Assert all block info transactions are committed
for i in range(len(batches)):
post_batch(batches[i])
time.sleep(1)
block_info = get_block_info(i)
self.assertEqual(block_info.block_num, i)


# Assert block info batches are first in the block and
# no other batch is included
for block in get_blocks()[:-1]:
print(block['header']['block_num'])
family_name = \
block['batches'][0]['transactions'][0]['header']['family_name']
self.assertEqual(family_name, 'block_info')
self.assertEqual(len(block['batches']), 1)

43 changes: 42 additions & 1 deletion validator/sawtooth_validator/execution/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def __init__(self,

if metrics_registry:
self._transaction_execution_count = CounterWrapper(
metrics_registry.counter('transaction_execution_count'))
metrics_registry.counter('transaction_execution_count'))
else:
self._transaction_execution_count = CounterWrapper()

Expand Down Expand Up @@ -215,6 +215,47 @@ def _execute_schedule(self):
is_valid=False,
context_id=None)
continue

if processor_type in required_transaction_processors:
# The txn processor type is in the required
# transaction processors: check all the outputs of
# the transaction match one namespace listed
transaction_family = \
next(t for t in transaction_families
if t.get('family') == header.family_name and
t.get('version') == header.family_version)

# if no namespaces are indicated, then the empty prefix is
# inserted by default
namespaces = transaction_family.get('namespaces', [''])
if not isinstance(namespaces, list):
LOGGER.warning("namespaces should be a list for "
"transaction family (name=%s, version=%s)",
processor_type.name,
processor_type.version)
prefixes = header.outputs
bad_prefixes = [prefix for prefix in prefixes if
not any(prefix.startswith(n)
for n in namespaces)]
for prefix in bad_prefixes:
# log each
LOGGER.debug("failing transaction %s of type (name=%s,"
"version=%s) because of no namespace listed "
"in %s from the configuration settings can "
"match the prefix %s",
txn.header_signature,
processor_type.name,
processor_type.version,
namespaces,
prefix)

if bad_prefixes:
self._scheduler.set_transaction_execution_result(
txn_signature=txn.header_signature,
is_valid=False,
context_id=None)
continue

try:
context_id = self._context_manager.create_context(
state_hash=txn_info.state_hash,
Expand Down

0 comments on commit 94a66d8

Please sign in to comment.