mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
commit
ca4fc88651
@ -7,18 +7,44 @@ import yaml
|
|||||||
from bigchaindb.common.exceptions import SchemaValidationError
|
from bigchaindb.common.exceptions import SchemaValidationError
|
||||||
|
|
||||||
|
|
||||||
TX_SCHEMA_PATH = os.path.join(os.path.dirname(__file__), 'transaction.yaml')
|
def drop_schema_descriptions(node):
|
||||||
with open(TX_SCHEMA_PATH) as handle:
|
""" Drop descriptions from schema, since they clutter log output """
|
||||||
TX_SCHEMA_YAML = handle.read()
|
if 'description' in node:
|
||||||
TX_SCHEMA = yaml.safe_load(TX_SCHEMA_YAML)
|
del node['description']
|
||||||
|
for n in node.get('properties', {}).values():
|
||||||
|
drop_schema_descriptions(n)
|
||||||
|
for n in node.get('definitions', {}).values():
|
||||||
|
drop_schema_descriptions(n)
|
||||||
|
for n in node.get('anyOf', []):
|
||||||
|
drop_schema_descriptions(n)
|
||||||
|
|
||||||
|
|
||||||
def validate_transaction_schema(tx_body):
|
def _load_schema(name):
|
||||||
""" Validate a transaction dict against a schema """
|
""" Load a schema from disk """
|
||||||
|
path = os.path.join(os.path.dirname(__file__), name + '.yaml')
|
||||||
|
with open(path) as handle:
|
||||||
|
schema = yaml.safe_load(handle)
|
||||||
|
drop_schema_descriptions(schema)
|
||||||
|
return path, schema
|
||||||
|
|
||||||
|
|
||||||
|
TX_SCHEMA_PATH, TX_SCHEMA = _load_schema('transaction')
|
||||||
|
VOTE_SCHEMA_PATH, VOTE_SCHEMA = _load_schema('vote')
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_schema(schema, body):
|
||||||
|
""" Validate data against a schema """
|
||||||
try:
|
try:
|
||||||
jsonschema.validate(tx_body, TX_SCHEMA)
|
jsonschema.validate(body, schema)
|
||||||
except jsonschema.ValidationError as exc:
|
except jsonschema.ValidationError as exc:
|
||||||
raise SchemaValidationError(str(exc)) from exc
|
raise SchemaValidationError(str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['TX_SCHEMA', 'TX_SCHEMA_YAML', 'validate_transaction_schema']
|
def validate_transaction_schema(tx):
|
||||||
|
""" Validate a transaction dict """
|
||||||
|
_validate_schema(TX_SCHEMA, tx)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_vote_schema(vote):
|
||||||
|
""" Validate a vote dict """
|
||||||
|
_validate_schema(VOTE_SCHEMA, vote)
|
||||||
|
73
bigchaindb/common/schema/vote.yaml
Normal file
73
bigchaindb/common/schema/vote.yaml
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#"
|
||||||
|
id: "http://www.bigchaindb.com/schema/vote.json"
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
title: Vote Schema
|
||||||
|
description: |
|
||||||
|
A Vote is an endorsement of a Block (identified by a hash) by
|
||||||
|
a node (identified by a public key).
|
||||||
|
|
||||||
|
The outer Vote object contains the details of the vote being made
|
||||||
|
as well as the signature and identifying information of the node
|
||||||
|
passing the vote.
|
||||||
|
required:
|
||||||
|
- node_pubkey
|
||||||
|
- signature
|
||||||
|
- vote
|
||||||
|
properties:
|
||||||
|
node_pubkey:
|
||||||
|
type: "string"
|
||||||
|
pattern: "[1-9a-zA-Z^OIl]{43,44}"
|
||||||
|
description: |
|
||||||
|
Ed25519 public key identifying the voting node.
|
||||||
|
signature:
|
||||||
|
type: "string"
|
||||||
|
pattern: "[1-9a-zA-Z^OIl]{86,88}"
|
||||||
|
description:
|
||||||
|
Ed25519 signature of the `Vote Details`_ object.
|
||||||
|
vote:
|
||||||
|
type: "object"
|
||||||
|
additionalProperties: false
|
||||||
|
description: |
|
||||||
|
`Vote Details`_ to be signed.
|
||||||
|
required:
|
||||||
|
- invalid_reason
|
||||||
|
- is_block_valid
|
||||||
|
- previous_block
|
||||||
|
- voting_for_block
|
||||||
|
- timestamp
|
||||||
|
properties:
|
||||||
|
previous_block:
|
||||||
|
"$ref": "#/definitions/sha3_hexdigest"
|
||||||
|
description: |
|
||||||
|
ID (SHA3 hash) of the block that precedes the block being voted on.
|
||||||
|
The notion of a "previous" block is subject to vote.
|
||||||
|
voting_for_block:
|
||||||
|
"$ref": "#/definitions/sha3_hexdigest"
|
||||||
|
description: |
|
||||||
|
ID (SHA3 hash) of the block being voted on.
|
||||||
|
is_block_valid:
|
||||||
|
type: "boolean"
|
||||||
|
description: |
|
||||||
|
This field is ``true`` if the block was deemed valid by the node.
|
||||||
|
invalid_reason:
|
||||||
|
anyOf:
|
||||||
|
- type: "string"
|
||||||
|
description: |
|
||||||
|
Reason the block is voted invalid, or ``null``.
|
||||||
|
|
||||||
|
.. container:: notice
|
||||||
|
|
||||||
|
**Note**: The invalid_reason was not being used and may be dropped in a future version of BigchainDB. See Issue `#217 <https://github.com/bigchaindb/bigchaindb/issues/217>`_ on GitHub.
|
||||||
|
- type: "null"
|
||||||
|
timestamp:
|
||||||
|
type: "string"
|
||||||
|
pattern: "[0-9]{10}"
|
||||||
|
description: |
|
||||||
|
Unix timestamp that the vote was created by the node, according
|
||||||
|
to the system time of the node.
|
||||||
|
definitions:
|
||||||
|
sha3_hexdigest:
|
||||||
|
pattern: "[0-9a-f]{64}"
|
||||||
|
type: string
|
@ -1,4 +1,11 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
from bigchaindb.util import verify_vote_signature
|
from bigchaindb.util import verify_vote_signature
|
||||||
|
from bigchaindb.common.schema import (SchemaValidationError,
|
||||||
|
validate_vote_schema)
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BaseConsensusRules():
|
class BaseConsensusRules():
|
||||||
@ -19,10 +26,19 @@ class BaseConsensusRules():
|
|||||||
return block.validate(bigchain)
|
return block.validate(bigchain)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def verify_vote_signature(voters, signed_vote):
|
def verify_vote(voters, signed_vote):
|
||||||
"""Verify the signature of a vote.
|
"""Verify the signature of a vote.
|
||||||
|
|
||||||
Refer to the documentation of
|
Refer to the documentation of
|
||||||
:func:`bigchaindb.util.verify_signature`.
|
:func:`bigchaindb.util.verify_signature`.
|
||||||
"""
|
"""
|
||||||
return verify_vote_signature(voters, signed_vote)
|
if verify_vote_signature(voters, signed_vote):
|
||||||
|
try:
|
||||||
|
validate_vote_schema(signed_vote)
|
||||||
|
return True
|
||||||
|
except SchemaValidationError as exc:
|
||||||
|
logger.warning(exc)
|
||||||
|
else:
|
||||||
|
logger.warning("Vote failed signature verification: "
|
||||||
|
"%s with voters: %s", signed_vote, voters)
|
||||||
|
return False
|
||||||
|
@ -648,7 +648,7 @@ class Bigchain(object):
|
|||||||
prev_block = [vote['vote']['previous_block'] for vote in votes]
|
prev_block = [vote['vote']['previous_block'] for vote in votes]
|
||||||
# vote_validity checks whether a vote is valid
|
# vote_validity checks whether a vote is valid
|
||||||
# or invalid, e.g. [False, True, True]
|
# or invalid, e.g. [False, True, True]
|
||||||
vote_validity = [self.consensus.verify_vote_signature(voters, vote) for vote in votes]
|
vote_validity = [self.consensus.verify_vote(voters, vote) for vote in votes]
|
||||||
|
|
||||||
# element-wise product of stated vote and validity of vote
|
# element-wise product of stated vote and validity of vote
|
||||||
# vote_cast = [True, True, False] and
|
# vote_cast = [True, True, False] and
|
||||||
|
@ -230,7 +230,8 @@ class RethinkDBBackend:
|
|||||||
"""
|
"""
|
||||||
return self.connection.run(
|
return self.connection.run(
|
||||||
r.table('votes', read_mode=self.read_mode)
|
r.table('votes', read_mode=self.read_mode)
|
||||||
.between([block_id, r.minval], [block_id, r.maxval], index='block_and_voter'))
|
.between([block_id, r.minval], [block_id, r.maxval], index='block_and_voter')
|
||||||
|
.without('id'))
|
||||||
|
|
||||||
def get_votes_by_block_id_and_voter(self, block_id, node_pubkey):
|
def get_votes_by_block_id_and_voter(self, block_id, node_pubkey):
|
||||||
"""Get all the votes casted for a specific block by a specific voter.
|
"""Get all the votes casted for a specific block by a specific voter.
|
||||||
@ -244,7 +245,8 @@ class RethinkDBBackend:
|
|||||||
"""
|
"""
|
||||||
return self.connection.run(
|
return self.connection.run(
|
||||||
r.table('votes', read_mode=self.read_mode)
|
r.table('votes', read_mode=self.read_mode)
|
||||||
.get_all([block_id, node_pubkey], index='block_and_voter'))
|
.get_all([block_id, node_pubkey], index='block_and_voter')
|
||||||
|
.without('id'))
|
||||||
|
|
||||||
def write_block(self, block, durability='soft'):
|
def write_block(self, block, durability='soft'):
|
||||||
"""Write a block to the bigchain table.
|
"""Write a block to the bigchain table.
|
||||||
|
@ -5,7 +5,7 @@ import os.path
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from bigchaindb.common.schema import TX_SCHEMA_YAML
|
from bigchaindb.common.schema import TX_SCHEMA_PATH, VOTE_SCHEMA_PATH
|
||||||
|
|
||||||
|
|
||||||
TPL_PROP = """\
|
TPL_PROP = """\
|
||||||
@ -18,7 +18,37 @@ TPL_PROP = """\
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
TPL_DOC = """\
|
TPL_STYLES = """
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#%(container)s h2 {
|
||||||
|
border-top: solid 3px #6ab0de;
|
||||||
|
background-color: #e7f2fa;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
#%(container)s h3 {
|
||||||
|
background: #f0f0f0;
|
||||||
|
border-left: solid 3px #ccc;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 6px;
|
||||||
|
font-size: 100%%;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
.document .section p {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.notice {
|
||||||
|
margin: 0px 16px 16px 16px;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid gold;
|
||||||
|
padding: 3px 6px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
TPL_TRANSACTION = TPL_STYLES + """\
|
||||||
.. This file was auto generated by %(file)s
|
.. This file was auto generated by %(file)s
|
||||||
|
|
||||||
==================
|
==================
|
||||||
@ -35,23 +65,6 @@ Transaction Schema
|
|||||||
|
|
||||||
* Metadata_
|
* Metadata_
|
||||||
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#transaction-schema h2 {
|
|
||||||
border-top: solid 3px #6ab0de;
|
|
||||||
background-color: #e7f2fa;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
#transaction-schema h3 {
|
|
||||||
background: #f0f0f0;
|
|
||||||
border-left: solid 3px #ccc;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 6px;
|
|
||||||
font-size: 100%%;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
Transaction
|
Transaction
|
||||||
-----------
|
-----------
|
||||||
@ -80,7 +93,57 @@ Metadata
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def ordered_load_yaml(stream):
|
def generate_transaction_docs():
|
||||||
|
schema = load_schema(TX_SCHEMA_PATH)
|
||||||
|
defs = schema['definitions']
|
||||||
|
|
||||||
|
doc = TPL_TRANSACTION % {
|
||||||
|
'transaction': render_section('Transaction', schema),
|
||||||
|
'condition': render_section('Condition', defs['condition']),
|
||||||
|
'fulfillment': render_section('Fulfillment', defs['fulfillment']),
|
||||||
|
'asset': render_section('Asset', defs['asset']),
|
||||||
|
'metadata': render_section('Metadata', defs['metadata']['anyOf'][0]),
|
||||||
|
'container': 'transaction-schema',
|
||||||
|
'file': os.path.basename(__file__),
|
||||||
|
}
|
||||||
|
|
||||||
|
write_schema_doc('transaction', doc)
|
||||||
|
|
||||||
|
|
||||||
|
TPL_VOTE = TPL_STYLES + """\
|
||||||
|
.. This file was auto generated by %(file)s
|
||||||
|
|
||||||
|
===========
|
||||||
|
Vote Schema
|
||||||
|
===========
|
||||||
|
|
||||||
|
Vote
|
||||||
|
----
|
||||||
|
|
||||||
|
%(vote)s
|
||||||
|
|
||||||
|
Vote Details
|
||||||
|
------------
|
||||||
|
|
||||||
|
%(vote_details)s
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def generate_vote_docs():
|
||||||
|
schema = load_schema(VOTE_SCHEMA_PATH)
|
||||||
|
|
||||||
|
doc = TPL_VOTE % {
|
||||||
|
'vote': render_section('Vote', schema),
|
||||||
|
'vote_details': render_section('Vote', schema['properties']['vote']),
|
||||||
|
'container': 'vote-schema',
|
||||||
|
'file': os.path.basename(__file__),
|
||||||
|
}
|
||||||
|
|
||||||
|
write_schema_doc('vote', doc)
|
||||||
|
|
||||||
|
|
||||||
|
def ordered_load_yaml(path):
|
||||||
""" Custom YAML loader to preserve key order """
|
""" Custom YAML loader to preserve key order """
|
||||||
class OrderedLoader(yaml.SafeLoader):
|
class OrderedLoader(yaml.SafeLoader):
|
||||||
pass
|
pass
|
||||||
@ -91,13 +154,26 @@ def ordered_load_yaml(stream):
|
|||||||
OrderedLoader.add_constructor(
|
OrderedLoader.add_constructor(
|
||||||
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
|
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
|
||||||
construct_mapping)
|
construct_mapping)
|
||||||
return yaml.load(stream, OrderedLoader)
|
with open(path) as handle:
|
||||||
|
return yaml.load(handle, OrderedLoader)
|
||||||
|
|
||||||
|
|
||||||
TX_SCHEMA = ordered_load_yaml(TX_SCHEMA_YAML)
|
def load_schema(path):
|
||||||
|
global DEFS
|
||||||
|
schema = ordered_load_yaml(path)
|
||||||
|
DEFS = schema['definitions']
|
||||||
|
return schema
|
||||||
|
|
||||||
|
|
||||||
DEFINITION_BASE_PATH = '#/definitions/'
|
def write_schema_doc(name, doc):
|
||||||
|
# Check base path exists
|
||||||
|
base_path = os.path.join(os.path.dirname(__file__), 'source/schema')
|
||||||
|
if not os.path.exists(base_path):
|
||||||
|
os.makedirs(base_path)
|
||||||
|
# Write doc
|
||||||
|
path = os.path.join(base_path, '%s.rst' % name)
|
||||||
|
with open(path, 'w') as handle:
|
||||||
|
handle.write(doc)
|
||||||
|
|
||||||
|
|
||||||
def render_section(section_name, obj):
|
def render_section(section_name, obj):
|
||||||
@ -141,32 +217,19 @@ def property_type(prop):
|
|||||||
raise ValueError("Could not resolve property type")
|
raise ValueError("Could not resolve property type")
|
||||||
|
|
||||||
|
|
||||||
|
DEFINITION_BASE_PATH = '#/definitions/'
|
||||||
|
|
||||||
|
|
||||||
def resolve_ref(ref):
|
def resolve_ref(ref):
|
||||||
""" Resolve definition reference """
|
""" Resolve definition reference """
|
||||||
assert ref.startswith(DEFINITION_BASE_PATH)
|
assert ref.startswith(DEFINITION_BASE_PATH)
|
||||||
return TX_SCHEMA['definitions'][ref[len(DEFINITION_BASE_PATH):]]
|
return DEFS[ref[len(DEFINITION_BASE_PATH):]]
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
""" Main function """
|
""" Main function """
|
||||||
defs = TX_SCHEMA['definitions']
|
generate_transaction_docs()
|
||||||
doc = TPL_DOC % {
|
generate_vote_docs()
|
||||||
'transaction': render_section('Transaction', TX_SCHEMA),
|
|
||||||
'condition': render_section('Condition', defs['condition']),
|
|
||||||
'fulfillment': render_section('Fulfillment', defs['fulfillment']),
|
|
||||||
'asset': render_section('Asset', defs['asset']),
|
|
||||||
'metadata': render_section('Metadata', defs['metadata']['anyOf'][0]),
|
|
||||||
'file': os.path.basename(__file__),
|
|
||||||
}
|
|
||||||
|
|
||||||
base_path = os.path.join(os.path.dirname(__file__), 'source/schema')
|
|
||||||
path = os.path.join(base_path, 'transaction.rst')
|
|
||||||
|
|
||||||
if not os.path.exists(base_path):
|
|
||||||
os.makedirs(base_path)
|
|
||||||
|
|
||||||
with open(path, 'w') as handle:
|
|
||||||
handle.write(doc)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(*_):
|
def setup(*_):
|
||||||
|
@ -16,5 +16,6 @@ BigchainDB Server Documentation
|
|||||||
topic-guides/index
|
topic-guides/index
|
||||||
data-models/index
|
data-models/index
|
||||||
schema/transaction
|
schema/transaction
|
||||||
|
schema/vote
|
||||||
release-notes
|
release-notes
|
||||||
appendices/index
|
appendices/index
|
||||||
|
69
tests/common/schema/test_schema.py
Normal file
69
tests/common/schema/test_schema.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
from bigchaindb.common.schema import (
|
||||||
|
TX_SCHEMA, VOTE_SCHEMA, drop_schema_descriptions)
|
||||||
|
|
||||||
|
|
||||||
|
def _test_additionalproperties(node, path=''):
|
||||||
|
"""
|
||||||
|
Validate that each object node has additionalProperties set, so that
|
||||||
|
objects with junk keys do not pass as valid.
|
||||||
|
"""
|
||||||
|
if isinstance(node, list):
|
||||||
|
for i, nnode in enumerate(node):
|
||||||
|
_test_additionalproperties(nnode, path + str(i) + '.')
|
||||||
|
if isinstance(node, dict):
|
||||||
|
if node.get('type') == 'object':
|
||||||
|
assert 'additionalProperties' in node, \
|
||||||
|
("additionalProperties not set at path:" + path)
|
||||||
|
for name, val in node.items():
|
||||||
|
_test_additionalproperties(val, path + name + '.')
|
||||||
|
|
||||||
|
|
||||||
|
def test_transaction_schema_additionalproperties():
|
||||||
|
_test_additionalproperties(TX_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
def test_vote_schema_additionalproperties():
|
||||||
|
_test_additionalproperties(VOTE_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
def test_drop_descriptions():
|
||||||
|
node = {
|
||||||
|
'description': 'abc',
|
||||||
|
'properties': {
|
||||||
|
'description': {
|
||||||
|
'description': ('The property named "description" should stay'
|
||||||
|
'but description meta field goes'),
|
||||||
|
},
|
||||||
|
'properties': {
|
||||||
|
'description': 'this must go'
|
||||||
|
},
|
||||||
|
'any': {
|
||||||
|
'anyOf': [
|
||||||
|
{
|
||||||
|
'description': 'must go'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'definitions': {
|
||||||
|
'wat': {
|
||||||
|
'description': "go"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expected = {
|
||||||
|
'properties': {
|
||||||
|
'description': {},
|
||||||
|
'properties': {},
|
||||||
|
'any': {
|
||||||
|
'anyOf': [
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'definitions': {
|
||||||
|
'wat': {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop_schema_descriptions(node)
|
||||||
|
assert node == expected
|
@ -1,7 +1,7 @@
|
|||||||
from pytest import raises
|
from pytest import raises
|
||||||
|
|
||||||
from bigchaindb.common.exceptions import SchemaValidationError
|
from bigchaindb.common.exceptions import SchemaValidationError
|
||||||
from bigchaindb.common.schema import TX_SCHEMA, validate_transaction_schema
|
from bigchaindb.common.schema import validate_transaction_schema
|
||||||
|
|
||||||
|
|
||||||
def test_validate_transaction_create(create_tx):
|
def test_validate_transaction_create(create_tx):
|
||||||
@ -16,6 +16,11 @@ def test_validate_transaction_signed_transfer(signed_transfer_tx):
|
|||||||
validate_transaction_schema(signed_transfer_tx.to_dict())
|
validate_transaction_schema(signed_transfer_tx.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_transaction_fails():
|
||||||
|
with raises(SchemaValidationError):
|
||||||
|
validate_transaction_schema({})
|
||||||
|
|
||||||
|
|
||||||
def test_validate_fails_metadata_empty_dict(create_tx):
|
def test_validate_fails_metadata_empty_dict(create_tx):
|
||||||
create_tx.metadata = {'a': 1}
|
create_tx.metadata = {'a': 1}
|
||||||
validate_transaction_schema(create_tx.to_dict())
|
validate_transaction_schema(create_tx.to_dict())
|
||||||
@ -24,27 +29,3 @@ def test_validate_fails_metadata_empty_dict(create_tx):
|
|||||||
create_tx.metadata = {}
|
create_tx.metadata = {}
|
||||||
with raises(SchemaValidationError):
|
with raises(SchemaValidationError):
|
||||||
validate_transaction_schema(create_tx.to_dict())
|
validate_transaction_schema(create_tx.to_dict())
|
||||||
|
|
||||||
|
|
||||||
def test_validation_fails():
|
|
||||||
with raises(SchemaValidationError):
|
|
||||||
validate_transaction_schema({})
|
|
||||||
|
|
||||||
|
|
||||||
def test_addition_properties_always_set():
|
|
||||||
"""
|
|
||||||
Validate that each object node has additionalProperties set, so that
|
|
||||||
transactions with junk keys do not pass as valid.
|
|
||||||
"""
|
|
||||||
def walk(node, path=''):
|
|
||||||
if isinstance(node, list):
|
|
||||||
for i, nnode in enumerate(node):
|
|
||||||
walk(nnode, path + str(i) + '.')
|
|
||||||
if isinstance(node, dict):
|
|
||||||
if node.get('type') == 'object':
|
|
||||||
assert 'additionalProperties' in node, \
|
|
||||||
("additionalProperties not set at path:" + path)
|
|
||||||
for name, val in node.items():
|
|
||||||
walk(val, path + name + '.')
|
|
||||||
|
|
||||||
walk(TX_SCHEMA)
|
|
13
tests/common/schema/test_vote_schema.py
Normal file
13
tests/common/schema/test_vote_schema.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from pytest import raises
|
||||||
|
|
||||||
|
from bigchaindb.common.exceptions import SchemaValidationError
|
||||||
|
from bigchaindb.common.schema import validate_vote_schema
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_vote(structurally_valid_vote):
|
||||||
|
validate_vote_schema(structurally_valid_vote)
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_vote_fails():
|
||||||
|
with raises(SchemaValidationError):
|
||||||
|
validate_vote_schema({})
|
@ -38,7 +38,8 @@ def ignore_local_config_file(monkeypatch):
|
|||||||
def mock_file_config(filename=None):
|
def mock_file_config(filename=None):
|
||||||
raise FileNotFoundError()
|
raise FileNotFoundError()
|
||||||
|
|
||||||
monkeypatch.setattr('bigchaindb.config_utils.file_config', mock_file_config)
|
monkeypatch.setattr('bigchaindb.config_utils.file_config',
|
||||||
|
mock_file_config)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function', autouse=True)
|
@pytest.fixture(scope='function', autouse=True)
|
||||||
@ -86,3 +87,18 @@ def signed_transfer_tx(signed_create_tx, user_pk, user_sk):
|
|||||||
inputs = signed_create_tx.to_inputs()
|
inputs = signed_create_tx.to_inputs()
|
||||||
tx = Transaction.transfer(inputs, [([user_pk], 1)], signed_create_tx.asset)
|
tx = Transaction.transfer(inputs, [([user_pk], 1)], signed_create_tx.asset)
|
||||||
return tx.sign([user_sk])
|
return tx.sign([user_sk])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def structurally_valid_vote():
|
||||||
|
return {
|
||||||
|
'node_pubkey': 'c' * 44,
|
||||||
|
'signature': 'd' * 86,
|
||||||
|
'vote': {
|
||||||
|
'voting_for_block': 'a' * 64,
|
||||||
|
'previous_block': 'b' * 64,
|
||||||
|
'is_block_valid': False,
|
||||||
|
'invalid_reason': None,
|
||||||
|
'timestamp': '1111111111'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -108,7 +108,7 @@ class TestBigchainApi(object):
|
|||||||
block2 = b.create_block([transfer_tx])
|
block2 = b.create_block([transfer_tx])
|
||||||
b.write_block(block2, durability='hard')
|
b.write_block(block2, durability='hard')
|
||||||
|
|
||||||
monkeypatch.setattr('time.time', lambda: 3)
|
monkeypatch.setattr('time.time', lambda: 3333333333)
|
||||||
transfer_tx2 = Transaction.transfer(tx.to_inputs(), [([b.me], 1)],
|
transfer_tx2 = Transaction.transfer(tx.to_inputs(), [([b.me], 1)],
|
||||||
tx.asset)
|
tx.asset)
|
||||||
transfer_tx2 = transfer_tx2.sign([b.me_private])
|
transfer_tx2 = transfer_tx2.sign([b.me_private])
|
||||||
@ -137,7 +137,7 @@ class TestBigchainApi(object):
|
|||||||
block1 = b.create_block([tx])
|
block1 = b.create_block([tx])
|
||||||
b.write_block(block1, durability='hard')
|
b.write_block(block1, durability='hard')
|
||||||
|
|
||||||
monkeypatch.setattr('time.time', lambda: 2)
|
monkeypatch.setattr('time.time', lambda: 2222222222)
|
||||||
block2 = b.create_block([tx])
|
block2 = b.create_block([tx])
|
||||||
b.write_block(block2, durability='hard')
|
b.write_block(block2, durability='hard')
|
||||||
|
|
||||||
@ -161,7 +161,7 @@ class TestBigchainApi(object):
|
|||||||
block1 = b.create_block([tx1])
|
block1 = b.create_block([tx1])
|
||||||
b.write_block(block1, durability='hard')
|
b.write_block(block1, durability='hard')
|
||||||
|
|
||||||
monkeypatch.setattr('time.time', lambda: 2)
|
monkeypatch.setattr('time.time', lambda: 2222222222)
|
||||||
tx2 = Transaction.create([b.me], [([b.me], 1)])
|
tx2 = Transaction.create([b.me], [([b.me], 1)])
|
||||||
tx2 = tx2.sign([b.me_private])
|
tx2 = tx2.sign([b.me_private])
|
||||||
block2 = b.create_block([tx2])
|
block2 = b.create_block([tx2])
|
||||||
|
@ -28,8 +28,10 @@ def test_check_for_quorum_invalid(b, user_pk):
|
|||||||
b.write_block(test_block)
|
b.write_block(test_block)
|
||||||
|
|
||||||
# split_vote (invalid)
|
# split_vote (invalid)
|
||||||
votes = [member.vote(test_block.id, 'abc', True) for member in test_federation[:2]] + \
|
votes = [member.vote(test_block.id, 'a' * 64, True)
|
||||||
[member.vote(test_block.id, 'abc', False) for member in test_federation[2:]]
|
for member in test_federation[:2]] + \
|
||||||
|
[member.vote(test_block.id, 'b' * 64, False)
|
||||||
|
for member in test_federation[2:]]
|
||||||
|
|
||||||
# cast votes
|
# cast votes
|
||||||
for vote in votes:
|
for vote in votes:
|
||||||
@ -49,8 +51,10 @@ def test_check_for_quorum_invalid_prev_node(b, user_pk):
|
|||||||
|
|
||||||
# simulate a federation with four voters
|
# simulate a federation with four voters
|
||||||
key_pairs = [crypto.generate_key_pair() for _ in range(4)]
|
key_pairs = [crypto.generate_key_pair() for _ in range(4)]
|
||||||
test_federation = [Bigchain(public_key=key_pair[1], private_key=key_pair[0])
|
test_federation = [
|
||||||
for key_pair in key_pairs]
|
Bigchain(public_key=key_pair[1], private_key=key_pair[0])
|
||||||
|
for key_pair in key_pairs
|
||||||
|
]
|
||||||
|
|
||||||
# add voters to block and write
|
# add voters to block and write
|
||||||
test_block.voters = [key_pair[1] for key_pair in key_pairs]
|
test_block.voters = [key_pair[1] for key_pair in key_pairs]
|
||||||
@ -58,8 +62,10 @@ def test_check_for_quorum_invalid_prev_node(b, user_pk):
|
|||||||
b.write_block(test_block)
|
b.write_block(test_block)
|
||||||
|
|
||||||
# split vote over prev node
|
# split vote over prev node
|
||||||
votes = [member.vote(test_block.id, 'abc', True) for member in test_federation[:2]] + \
|
votes = [member.vote(test_block.id, 'a' * 64, True)
|
||||||
[member.vote(test_block.id, 'def', True) for member in test_federation[2:]]
|
for member in test_federation[:2]] + \
|
||||||
|
[member.vote(test_block.id, 'b' * 64, True)
|
||||||
|
for member in test_federation[2:]]
|
||||||
|
|
||||||
# cast votes
|
# cast votes
|
||||||
for vote in votes:
|
for vote in votes:
|
||||||
@ -80,8 +86,10 @@ def test_check_for_quorum_valid(b, user_pk):
|
|||||||
|
|
||||||
# simulate a federation with four voters
|
# simulate a federation with four voters
|
||||||
key_pairs = [crypto.generate_key_pair() for _ in range(4)]
|
key_pairs = [crypto.generate_key_pair() for _ in range(4)]
|
||||||
test_federation = [Bigchain(public_key=key_pair[1], private_key=key_pair[0])
|
test_federation = [
|
||||||
for key_pair in key_pairs]
|
Bigchain(public_key=key_pair[1], private_key=key_pair[0])
|
||||||
|
for key_pair in key_pairs
|
||||||
|
]
|
||||||
|
|
||||||
# add voters to block and write
|
# add voters to block and write
|
||||||
test_block.voters = [key_pair[1] for key_pair in key_pairs]
|
test_block.voters = [key_pair[1] for key_pair in key_pairs]
|
||||||
@ -89,7 +97,7 @@ def test_check_for_quorum_valid(b, user_pk):
|
|||||||
b.write_block(test_block)
|
b.write_block(test_block)
|
||||||
|
|
||||||
# votes for block one
|
# votes for block one
|
||||||
votes = [member.vote(test_block.id, 'abc', True)
|
votes = [member.vote(test_block.id, 'a' * 64, True)
|
||||||
for member in test_federation]
|
for member in test_federation]
|
||||||
# cast votes
|
# cast votes
|
||||||
for vote in votes:
|
for vote in votes:
|
||||||
@ -158,8 +166,8 @@ def test_full_pipeline(b, user_pk):
|
|||||||
pipeline.start()
|
pipeline.start()
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
# vote one block valid, one invalid
|
# vote one block valid, one invalid
|
||||||
vote_valid = b.vote(valid_block.id, 'abc', True)
|
vote_valid = b.vote(valid_block.id, 'b' * 64, True)
|
||||||
vote_invalid = b.vote(invalid_block.id, 'abc', False)
|
vote_invalid = b.vote(invalid_block.id, 'c' * 64, False)
|
||||||
|
|
||||||
b.write_vote(vote_valid)
|
b.write_vote(vote_valid)
|
||||||
b.write_vote(vote_invalid)
|
b.write_vote(vote_invalid)
|
||||||
|
@ -17,6 +17,9 @@ def dummy_block(b):
|
|||||||
return block
|
return block
|
||||||
|
|
||||||
|
|
||||||
|
DUMMY_SHA3 = '0123456789abcdef' * 4
|
||||||
|
|
||||||
|
|
||||||
def test_vote_creation_valid(b):
|
def test_vote_creation_valid(b):
|
||||||
from bigchaindb.common import crypto
|
from bigchaindb.common import crypto
|
||||||
from bigchaindb.common.util import serialize
|
from bigchaindb.common.util import serialize
|
||||||
@ -24,17 +27,17 @@ def test_vote_creation_valid(b):
|
|||||||
# create valid block
|
# create valid block
|
||||||
block = dummy_block(b)
|
block = dummy_block(b)
|
||||||
# retrieve vote
|
# retrieve vote
|
||||||
vote = b.vote(block.id, 'abc', True)
|
vote = b.vote(block.id, DUMMY_SHA3, True)
|
||||||
|
|
||||||
# assert vote is correct
|
# assert vote is correct
|
||||||
assert vote['vote']['voting_for_block'] == block.id
|
assert vote['vote']['voting_for_block'] == block.id
|
||||||
assert vote['vote']['previous_block'] == 'abc'
|
assert vote['vote']['previous_block'] == DUMMY_SHA3
|
||||||
assert vote['vote']['is_block_valid'] is True
|
assert vote['vote']['is_block_valid'] is True
|
||||||
assert vote['vote']['invalid_reason'] is None
|
assert vote['vote']['invalid_reason'] is None
|
||||||
assert vote['node_pubkey'] == b.me
|
assert vote['node_pubkey'] == b.me
|
||||||
assert isinstance(vote['signature'], str)
|
assert isinstance(vote['signature'], str)
|
||||||
assert crypto.PublicKey(b.me).verify(serialize(vote['vote']).encode(),
|
assert crypto.PublicKey(b.me).verify(serialize(vote['vote']).encode(),
|
||||||
vote['signature']) is True
|
vote['signature']) is True
|
||||||
|
|
||||||
|
|
||||||
def test_vote_creation_invalid(b):
|
def test_vote_creation_invalid(b):
|
||||||
@ -44,16 +47,16 @@ def test_vote_creation_invalid(b):
|
|||||||
# create valid block
|
# create valid block
|
||||||
block = dummy_block(b)
|
block = dummy_block(b)
|
||||||
# retrieve vote
|
# retrieve vote
|
||||||
vote = b.vote(block.id, 'abc', False)
|
vote = b.vote(block.id, DUMMY_SHA3, False)
|
||||||
|
|
||||||
# assert vote is correct
|
# assert vote is correct
|
||||||
assert vote['vote']['voting_for_block'] == block.id
|
assert vote['vote']['voting_for_block'] == block.id
|
||||||
assert vote['vote']['previous_block'] == 'abc'
|
assert vote['vote']['previous_block'] == DUMMY_SHA3
|
||||||
assert vote['vote']['is_block_valid'] is False
|
assert vote['vote']['is_block_valid'] is False
|
||||||
assert vote['vote']['invalid_reason'] is None
|
assert vote['vote']['invalid_reason'] is None
|
||||||
assert vote['node_pubkey'] == b.me
|
assert vote['node_pubkey'] == b.me
|
||||||
assert crypto.PublicKey(b.me).verify(serialize(vote['vote']).encode(),
|
assert crypto.PublicKey(b.me).verify(serialize(vote['vote']).encode(),
|
||||||
vote['signature']) is True
|
vote['signature']) is True
|
||||||
|
|
||||||
|
|
||||||
def test_vote_ungroup_returns_a_set_of_results(b):
|
def test_vote_ungroup_returns_a_set_of_results(b):
|
||||||
@ -157,7 +160,7 @@ def test_valid_block_voting_sequential(b, monkeypatch):
|
|||||||
from bigchaindb.common import crypto, util
|
from bigchaindb.common import crypto, util
|
||||||
from bigchaindb.pipelines import vote
|
from bigchaindb.pipelines import vote
|
||||||
|
|
||||||
monkeypatch.setattr('time.time', lambda: 1)
|
monkeypatch.setattr('time.time', lambda: 1111111111)
|
||||||
genesis = b.create_genesis_block()
|
genesis = b.create_genesis_block()
|
||||||
vote_obj = vote.Vote()
|
vote_obj = vote.Vote()
|
||||||
block = dummy_block(b)
|
block = dummy_block(b)
|
||||||
@ -173,12 +176,12 @@ def test_valid_block_voting_sequential(b, monkeypatch):
|
|||||||
'previous_block': genesis.id,
|
'previous_block': genesis.id,
|
||||||
'is_block_valid': True,
|
'is_block_valid': True,
|
||||||
'invalid_reason': None,
|
'invalid_reason': None,
|
||||||
'timestamp': '1'}
|
'timestamp': '1111111111'}
|
||||||
|
|
||||||
serialized_vote = util.serialize(vote_doc['vote']).encode()
|
serialized_vote = util.serialize(vote_doc['vote']).encode()
|
||||||
assert vote_doc['node_pubkey'] == b.me
|
assert vote_doc['node_pubkey'] == b.me
|
||||||
assert crypto.PublicKey(b.me).verify(serialized_vote,
|
assert crypto.PublicKey(b.me).verify(serialized_vote,
|
||||||
vote_doc['signature']) is True
|
vote_doc['signature']) is True
|
||||||
|
|
||||||
|
|
||||||
def test_valid_block_voting_multiprocessing(b, monkeypatch):
|
def test_valid_block_voting_multiprocessing(b, monkeypatch):
|
||||||
@ -188,7 +191,7 @@ def test_valid_block_voting_multiprocessing(b, monkeypatch):
|
|||||||
inpipe = Pipe()
|
inpipe = Pipe()
|
||||||
outpipe = Pipe()
|
outpipe = Pipe()
|
||||||
|
|
||||||
monkeypatch.setattr('time.time', lambda: 1)
|
monkeypatch.setattr('time.time', lambda: 1111111111)
|
||||||
genesis = b.create_genesis_block()
|
genesis = b.create_genesis_block()
|
||||||
vote_pipeline = vote.create_pipeline()
|
vote_pipeline = vote.create_pipeline()
|
||||||
vote_pipeline.setup(indata=inpipe, outdata=outpipe)
|
vote_pipeline.setup(indata=inpipe, outdata=outpipe)
|
||||||
@ -207,12 +210,12 @@ def test_valid_block_voting_multiprocessing(b, monkeypatch):
|
|||||||
'previous_block': genesis.id,
|
'previous_block': genesis.id,
|
||||||
'is_block_valid': True,
|
'is_block_valid': True,
|
||||||
'invalid_reason': None,
|
'invalid_reason': None,
|
||||||
'timestamp': '1'}
|
'timestamp': '1111111111'}
|
||||||
|
|
||||||
serialized_vote = util.serialize(vote_doc['vote']).encode()
|
serialized_vote = util.serialize(vote_doc['vote']).encode()
|
||||||
assert vote_doc['node_pubkey'] == b.me
|
assert vote_doc['node_pubkey'] == b.me
|
||||||
assert crypto.PublicKey(b.me).verify(serialized_vote,
|
assert crypto.PublicKey(b.me).verify(serialized_vote,
|
||||||
vote_doc['signature']) is True
|
vote_doc['signature']) is True
|
||||||
|
|
||||||
|
|
||||||
def test_valid_block_voting_with_create_transaction(b, monkeypatch):
|
def test_valid_block_voting_with_create_transaction(b, monkeypatch):
|
||||||
@ -227,7 +230,7 @@ def test_valid_block_voting_with_create_transaction(b, monkeypatch):
|
|||||||
tx = Transaction.create([b.me], [([test_user_pub], 1)])
|
tx = Transaction.create([b.me], [([test_user_pub], 1)])
|
||||||
tx = tx.sign([b.me_private])
|
tx = tx.sign([b.me_private])
|
||||||
|
|
||||||
monkeypatch.setattr('time.time', lambda: 1)
|
monkeypatch.setattr('time.time', lambda: 1111111111)
|
||||||
block = b.create_block([tx])
|
block = b.create_block([tx])
|
||||||
|
|
||||||
inpipe = Pipe()
|
inpipe = Pipe()
|
||||||
@ -248,12 +251,12 @@ def test_valid_block_voting_with_create_transaction(b, monkeypatch):
|
|||||||
'previous_block': genesis.id,
|
'previous_block': genesis.id,
|
||||||
'is_block_valid': True,
|
'is_block_valid': True,
|
||||||
'invalid_reason': None,
|
'invalid_reason': None,
|
||||||
'timestamp': '1'}
|
'timestamp': '1111111111'}
|
||||||
|
|
||||||
serialized_vote = util.serialize(vote_doc['vote']).encode()
|
serialized_vote = util.serialize(vote_doc['vote']).encode()
|
||||||
assert vote_doc['node_pubkey'] == b.me
|
assert vote_doc['node_pubkey'] == b.me
|
||||||
assert crypto.PublicKey(b.me).verify(serialized_vote,
|
assert crypto.PublicKey(b.me).verify(serialized_vote,
|
||||||
vote_doc['signature']) is True
|
vote_doc['signature']) is True
|
||||||
|
|
||||||
|
|
||||||
def test_valid_block_voting_with_transfer_transactions(monkeypatch, b):
|
def test_valid_block_voting_with_transfer_transactions(monkeypatch, b):
|
||||||
@ -268,7 +271,7 @@ def test_valid_block_voting_with_transfer_transactions(monkeypatch, b):
|
|||||||
tx = Transaction.create([b.me], [([test_user_pub], 1)])
|
tx = Transaction.create([b.me], [([test_user_pub], 1)])
|
||||||
tx = tx.sign([b.me_private])
|
tx = tx.sign([b.me_private])
|
||||||
|
|
||||||
monkeypatch.setattr('time.time', lambda: 1)
|
monkeypatch.setattr('time.time', lambda: 1111111111)
|
||||||
block = b.create_block([tx])
|
block = b.create_block([tx])
|
||||||
b.write_block(block, durability='hard')
|
b.write_block(block, durability='hard')
|
||||||
|
|
||||||
@ -278,7 +281,7 @@ def test_valid_block_voting_with_transfer_transactions(monkeypatch, b):
|
|||||||
tx.asset)
|
tx.asset)
|
||||||
tx2 = tx2.sign([test_user_priv])
|
tx2 = tx2.sign([test_user_priv])
|
||||||
|
|
||||||
monkeypatch.setattr('time.time', lambda: 2)
|
monkeypatch.setattr('time.time', lambda: 2222222222)
|
||||||
block2 = b.create_block([tx2])
|
block2 = b.create_block([tx2])
|
||||||
b.write_block(block2, durability='hard')
|
b.write_block(block2, durability='hard')
|
||||||
|
|
||||||
@ -303,12 +306,12 @@ def test_valid_block_voting_with_transfer_transactions(monkeypatch, b):
|
|||||||
'previous_block': genesis.id,
|
'previous_block': genesis.id,
|
||||||
'is_block_valid': True,
|
'is_block_valid': True,
|
||||||
'invalid_reason': None,
|
'invalid_reason': None,
|
||||||
'timestamp': '2'}
|
'timestamp': '2222222222'}
|
||||||
|
|
||||||
serialized_vote = util.serialize(vote_doc['vote']).encode()
|
serialized_vote = util.serialize(vote_doc['vote']).encode()
|
||||||
assert vote_doc['node_pubkey'] == b.me
|
assert vote_doc['node_pubkey'] == b.me
|
||||||
assert crypto.PublicKey(b.me).verify(serialized_vote,
|
assert crypto.PublicKey(b.me).verify(serialized_vote,
|
||||||
vote_doc['signature']) is True
|
vote_doc['signature']) is True
|
||||||
|
|
||||||
vote2_rs = b.backend.get_votes_by_block_id_and_voter(block2.id, b.me)
|
vote2_rs = b.backend.get_votes_by_block_id_and_voter(block2.id, b.me)
|
||||||
vote2_doc = vote2_rs.next()
|
vote2_doc = vote2_rs.next()
|
||||||
@ -317,12 +320,12 @@ def test_valid_block_voting_with_transfer_transactions(monkeypatch, b):
|
|||||||
'previous_block': block.id,
|
'previous_block': block.id,
|
||||||
'is_block_valid': True,
|
'is_block_valid': True,
|
||||||
'invalid_reason': None,
|
'invalid_reason': None,
|
||||||
'timestamp': '2'}
|
'timestamp': '2222222222'}
|
||||||
|
|
||||||
serialized_vote2 = util.serialize(vote2_doc['vote']).encode()
|
serialized_vote2 = util.serialize(vote2_doc['vote']).encode()
|
||||||
assert vote2_doc['node_pubkey'] == b.me
|
assert vote2_doc['node_pubkey'] == b.me
|
||||||
assert crypto.PublicKey(b.me).verify(serialized_vote2,
|
assert crypto.PublicKey(b.me).verify(serialized_vote2,
|
||||||
vote2_doc['signature']) is True
|
vote2_doc['signature']) is True
|
||||||
|
|
||||||
|
|
||||||
def test_unsigned_tx_in_block_voting(monkeypatch, b, user_pk):
|
def test_unsigned_tx_in_block_voting(monkeypatch, b, user_pk):
|
||||||
@ -333,7 +336,7 @@ def test_unsigned_tx_in_block_voting(monkeypatch, b, user_pk):
|
|||||||
inpipe = Pipe()
|
inpipe = Pipe()
|
||||||
outpipe = Pipe()
|
outpipe = Pipe()
|
||||||
|
|
||||||
monkeypatch.setattr('time.time', lambda: 1)
|
monkeypatch.setattr('time.time', lambda: 1111111111)
|
||||||
genesis = b.create_genesis_block()
|
genesis = b.create_genesis_block()
|
||||||
vote_pipeline = vote.create_pipeline()
|
vote_pipeline = vote.create_pipeline()
|
||||||
vote_pipeline.setup(indata=inpipe, outdata=outpipe)
|
vote_pipeline.setup(indata=inpipe, outdata=outpipe)
|
||||||
@ -354,12 +357,12 @@ def test_unsigned_tx_in_block_voting(monkeypatch, b, user_pk):
|
|||||||
'previous_block': genesis.id,
|
'previous_block': genesis.id,
|
||||||
'is_block_valid': False,
|
'is_block_valid': False,
|
||||||
'invalid_reason': None,
|
'invalid_reason': None,
|
||||||
'timestamp': '1'}
|
'timestamp': '1111111111'}
|
||||||
|
|
||||||
serialized_vote = util.serialize(vote_doc['vote']).encode()
|
serialized_vote = util.serialize(vote_doc['vote']).encode()
|
||||||
assert vote_doc['node_pubkey'] == b.me
|
assert vote_doc['node_pubkey'] == b.me
|
||||||
assert crypto.PublicKey(b.me).verify(serialized_vote,
|
assert crypto.PublicKey(b.me).verify(serialized_vote,
|
||||||
vote_doc['signature']) is True
|
vote_doc['signature']) is True
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_id_tx_in_block_voting(monkeypatch, b, user_pk):
|
def test_invalid_id_tx_in_block_voting(monkeypatch, b, user_pk):
|
||||||
@ -370,7 +373,7 @@ def test_invalid_id_tx_in_block_voting(monkeypatch, b, user_pk):
|
|||||||
inpipe = Pipe()
|
inpipe = Pipe()
|
||||||
outpipe = Pipe()
|
outpipe = Pipe()
|
||||||
|
|
||||||
monkeypatch.setattr('time.time', lambda: 1)
|
monkeypatch.setattr('time.time', lambda: 1111111111)
|
||||||
genesis = b.create_genesis_block()
|
genesis = b.create_genesis_block()
|
||||||
vote_pipeline = vote.create_pipeline()
|
vote_pipeline = vote.create_pipeline()
|
||||||
vote_pipeline.setup(indata=inpipe, outdata=outpipe)
|
vote_pipeline.setup(indata=inpipe, outdata=outpipe)
|
||||||
@ -393,12 +396,12 @@ def test_invalid_id_tx_in_block_voting(monkeypatch, b, user_pk):
|
|||||||
'previous_block': genesis.id,
|
'previous_block': genesis.id,
|
||||||
'is_block_valid': False,
|
'is_block_valid': False,
|
||||||
'invalid_reason': None,
|
'invalid_reason': None,
|
||||||
'timestamp': '1'}
|
'timestamp': '1111111111'}
|
||||||
|
|
||||||
serialized_vote = util.serialize(vote_doc['vote']).encode()
|
serialized_vote = util.serialize(vote_doc['vote']).encode()
|
||||||
assert vote_doc['node_pubkey'] == b.me
|
assert vote_doc['node_pubkey'] == b.me
|
||||||
assert crypto.PublicKey(b.me).verify(serialized_vote,
|
assert crypto.PublicKey(b.me).verify(serialized_vote,
|
||||||
vote_doc['signature']) is True
|
vote_doc['signature']) is True
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_content_in_tx_in_block_voting(monkeypatch, b, user_pk):
|
def test_invalid_content_in_tx_in_block_voting(monkeypatch, b, user_pk):
|
||||||
@ -409,7 +412,7 @@ def test_invalid_content_in_tx_in_block_voting(monkeypatch, b, user_pk):
|
|||||||
inpipe = Pipe()
|
inpipe = Pipe()
|
||||||
outpipe = Pipe()
|
outpipe = Pipe()
|
||||||
|
|
||||||
monkeypatch.setattr('time.time', lambda: 1)
|
monkeypatch.setattr('time.time', lambda: 1111111111)
|
||||||
genesis = b.create_genesis_block()
|
genesis = b.create_genesis_block()
|
||||||
vote_pipeline = vote.create_pipeline()
|
vote_pipeline = vote.create_pipeline()
|
||||||
vote_pipeline.setup(indata=inpipe, outdata=outpipe)
|
vote_pipeline.setup(indata=inpipe, outdata=outpipe)
|
||||||
@ -432,12 +435,12 @@ def test_invalid_content_in_tx_in_block_voting(monkeypatch, b, user_pk):
|
|||||||
'previous_block': genesis.id,
|
'previous_block': genesis.id,
|
||||||
'is_block_valid': False,
|
'is_block_valid': False,
|
||||||
'invalid_reason': None,
|
'invalid_reason': None,
|
||||||
'timestamp': '1'}
|
'timestamp': '1111111111'}
|
||||||
|
|
||||||
serialized_vote = util.serialize(vote_doc['vote']).encode()
|
serialized_vote = util.serialize(vote_doc['vote']).encode()
|
||||||
assert vote_doc['node_pubkey'] == b.me
|
assert vote_doc['node_pubkey'] == b.me
|
||||||
assert crypto.PublicKey(b.me).verify(serialized_vote,
|
assert crypto.PublicKey(b.me).verify(serialized_vote,
|
||||||
vote_doc['signature']) is True
|
vote_doc['signature']) is True
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_block_voting(monkeypatch, b, user_pk):
|
def test_invalid_block_voting(monkeypatch, b, user_pk):
|
||||||
@ -447,7 +450,7 @@ def test_invalid_block_voting(monkeypatch, b, user_pk):
|
|||||||
inpipe = Pipe()
|
inpipe = Pipe()
|
||||||
outpipe = Pipe()
|
outpipe = Pipe()
|
||||||
|
|
||||||
monkeypatch.setattr('time.time', lambda: 1)
|
monkeypatch.setattr('time.time', lambda: 1111111111)
|
||||||
genesis = b.create_genesis_block()
|
genesis = b.create_genesis_block()
|
||||||
vote_pipeline = vote.create_pipeline()
|
vote_pipeline = vote.create_pipeline()
|
||||||
vote_pipeline.setup(indata=inpipe, outdata=outpipe)
|
vote_pipeline.setup(indata=inpipe, outdata=outpipe)
|
||||||
@ -467,12 +470,12 @@ def test_invalid_block_voting(monkeypatch, b, user_pk):
|
|||||||
'previous_block': genesis.id,
|
'previous_block': genesis.id,
|
||||||
'is_block_valid': False,
|
'is_block_valid': False,
|
||||||
'invalid_reason': None,
|
'invalid_reason': None,
|
||||||
'timestamp': '1'}
|
'timestamp': '1111111111'}
|
||||||
|
|
||||||
serialized_vote = util.serialize(vote_doc['vote']).encode()
|
serialized_vote = util.serialize(vote_doc['vote']).encode()
|
||||||
assert vote_doc['node_pubkey'] == b.me
|
assert vote_doc['node_pubkey'] == b.me
|
||||||
assert crypto.PublicKey(b.me).verify(serialized_vote,
|
assert crypto.PublicKey(b.me).verify(serialized_vote,
|
||||||
vote_doc['signature']) is True
|
vote_doc['signature']) is True
|
||||||
|
|
||||||
|
|
||||||
def test_voter_considers_unvoted_blocks_when_single_node(monkeypatch, b):
|
def test_voter_considers_unvoted_blocks_when_single_node(monkeypatch, b):
|
||||||
@ -480,17 +483,17 @@ def test_voter_considers_unvoted_blocks_when_single_node(monkeypatch, b):
|
|||||||
|
|
||||||
outpipe = Pipe()
|
outpipe = Pipe()
|
||||||
|
|
||||||
monkeypatch.setattr('time.time', lambda: 1)
|
monkeypatch.setattr('time.time', lambda: 1111111111)
|
||||||
b.create_genesis_block()
|
b.create_genesis_block()
|
||||||
|
|
||||||
block_ids = []
|
block_ids = []
|
||||||
# insert blocks in the database while the voter process is not listening
|
# insert blocks in the database while the voter process is not listening
|
||||||
# (these blocks won't appear in the changefeed)
|
# (these blocks won't appear in the changefeed)
|
||||||
monkeypatch.setattr('time.time', lambda: 2)
|
monkeypatch.setattr('time.time', lambda: 2222222222)
|
||||||
block_1 = dummy_block(b)
|
block_1 = dummy_block(b)
|
||||||
block_ids.append(block_1.id)
|
block_ids.append(block_1.id)
|
||||||
b.write_block(block_1, durability='hard')
|
b.write_block(block_1, durability='hard')
|
||||||
monkeypatch.setattr('time.time', lambda: 3)
|
monkeypatch.setattr('time.time', lambda: 3333333333)
|
||||||
block_2 = dummy_block(b)
|
block_2 = dummy_block(b)
|
||||||
block_ids.append(block_2.id)
|
block_ids.append(block_2.id)
|
||||||
b.write_block(block_2, durability='hard')
|
b.write_block(block_2, durability='hard')
|
||||||
@ -505,7 +508,7 @@ def test_voter_considers_unvoted_blocks_when_single_node(monkeypatch, b):
|
|||||||
outpipe.get()
|
outpipe.get()
|
||||||
|
|
||||||
# create a new block that will appear in the changefeed
|
# create a new block that will appear in the changefeed
|
||||||
monkeypatch.setattr('time.time', lambda: 4)
|
monkeypatch.setattr('time.time', lambda: 4444444444)
|
||||||
block_3 = dummy_block(b)
|
block_3 = dummy_block(b)
|
||||||
block_ids.append(block_3.id)
|
block_ids.append(block_3.id)
|
||||||
b.write_block(block_3, durability='hard')
|
b.write_block(block_3, durability='hard')
|
||||||
@ -527,16 +530,16 @@ def test_voter_chains_blocks_with_the_previous_ones(monkeypatch, b):
|
|||||||
|
|
||||||
outpipe = Pipe()
|
outpipe = Pipe()
|
||||||
|
|
||||||
monkeypatch.setattr('time.time', lambda: 1)
|
monkeypatch.setattr('time.time', lambda: 1111111111)
|
||||||
b.create_genesis_block()
|
b.create_genesis_block()
|
||||||
|
|
||||||
block_ids = []
|
block_ids = []
|
||||||
monkeypatch.setattr('time.time', lambda: 2)
|
monkeypatch.setattr('time.time', lambda: 2222222222)
|
||||||
block_1 = dummy_block(b)
|
block_1 = dummy_block(b)
|
||||||
block_ids.append(block_1.id)
|
block_ids.append(block_1.id)
|
||||||
b.write_block(block_1, durability='hard')
|
b.write_block(block_1, durability='hard')
|
||||||
|
|
||||||
monkeypatch.setattr('time.time', lambda: 3)
|
monkeypatch.setattr('time.time', lambda: 3333333333)
|
||||||
block_2 = dummy_block(b)
|
block_2 = dummy_block(b)
|
||||||
block_ids.append(block_2.id)
|
block_ids.append(block_2.id)
|
||||||
b.write_block(block_2, durability='hard')
|
b.write_block(block_2, durability='hard')
|
||||||
@ -553,12 +556,13 @@ def test_voter_chains_blocks_with_the_previous_ones(monkeypatch, b):
|
|||||||
|
|
||||||
# retrive blocks from bigchain
|
# retrive blocks from bigchain
|
||||||
blocks = [b.get_block(_id) for _id in block_ids]
|
blocks = [b.get_block(_id) for _id in block_ids]
|
||||||
|
|
||||||
# retrieve votes
|
# retrieve votes
|
||||||
votes = [list(b.backend.get_votes_by_block_id(_id))[0]
|
votes = [list(b.backend.get_votes_by_block_id(_id))[0]
|
||||||
for _id in block_ids]
|
for _id in block_ids]
|
||||||
|
|
||||||
assert votes[0]['vote']['voting_for_block'] in (blocks[0]['id'], blocks[1]['id'])
|
assert ({v['vote']['voting_for_block'] for v in votes} ==
|
||||||
assert votes[1]['vote']['voting_for_block'] in (blocks[0]['id'], blocks[1]['id'])
|
{block['id'] for block in blocks})
|
||||||
|
|
||||||
|
|
||||||
def test_voter_checks_for_previous_vote(monkeypatch, b):
|
def test_voter_checks_for_previous_vote(monkeypatch, b):
|
||||||
@ -567,10 +571,10 @@ def test_voter_checks_for_previous_vote(monkeypatch, b):
|
|||||||
inpipe = Pipe()
|
inpipe = Pipe()
|
||||||
outpipe = Pipe()
|
outpipe = Pipe()
|
||||||
|
|
||||||
monkeypatch.setattr('time.time', lambda: 1)
|
monkeypatch.setattr('time.time', lambda: 1111111111)
|
||||||
b.create_genesis_block()
|
b.create_genesis_block()
|
||||||
|
|
||||||
monkeypatch.setattr('time.time', lambda: 2)
|
monkeypatch.setattr('time.time', lambda: 2222222222)
|
||||||
block_1 = dummy_block(b)
|
block_1 = dummy_block(b)
|
||||||
inpipe.put(block_1.to_dict())
|
inpipe.put(block_1.to_dict())
|
||||||
assert len(list(b.backend.get_votes_by_block_id(block_1.id))) == 0
|
assert len(list(b.backend.get_votes_by_block_id(block_1.id))) == 0
|
||||||
@ -583,11 +587,11 @@ def test_voter_checks_for_previous_vote(monkeypatch, b):
|
|||||||
outpipe.get()
|
outpipe.get()
|
||||||
|
|
||||||
# queue block for voting AGAIN
|
# queue block for voting AGAIN
|
||||||
monkeypatch.setattr('time.time', lambda: 3)
|
monkeypatch.setattr('time.time', lambda: 3333333333)
|
||||||
inpipe.put(block_1.to_dict())
|
inpipe.put(block_1.to_dict())
|
||||||
|
|
||||||
# queue another block
|
# queue another block
|
||||||
monkeypatch.setattr('time.time', lambda: 4)
|
monkeypatch.setattr('time.time', lambda: 4444444444)
|
||||||
block_2 = dummy_block(b)
|
block_2 = dummy_block(b)
|
||||||
inpipe.put(block_2.to_dict())
|
inpipe.put(block_2.to_dict())
|
||||||
|
|
||||||
|
@ -1,2 +1,40 @@
|
|||||||
class TestBaseConsensusRules(object):
|
|
||||||
pass
|
def test_verify_vote_passes(b, structurally_valid_vote):
|
||||||
|
from bigchaindb.consensus import BaseConsensusRules
|
||||||
|
from bigchaindb.common import crypto
|
||||||
|
from bigchaindb.common.util import serialize
|
||||||
|
vote_body = structurally_valid_vote['vote']
|
||||||
|
vote_data = serialize(vote_body)
|
||||||
|
signature = crypto.PrivateKey(b.me_private).sign(vote_data.encode())
|
||||||
|
vote_signed = {
|
||||||
|
'node_pubkey': b.me,
|
||||||
|
'signature': signature.decode(),
|
||||||
|
'vote': vote_body
|
||||||
|
}
|
||||||
|
assert BaseConsensusRules.verify_vote([b.me], vote_signed)
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_vote_fails_signature(b, structurally_valid_vote):
|
||||||
|
from bigchaindb.consensus import BaseConsensusRules
|
||||||
|
vote_body = structurally_valid_vote['vote']
|
||||||
|
vote_signed = {
|
||||||
|
'node_pubkey': b.me,
|
||||||
|
'signature': 'a' * 86,
|
||||||
|
'vote': vote_body
|
||||||
|
}
|
||||||
|
assert not BaseConsensusRules.verify_vote([b.me], vote_signed)
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_vote_fails_schema(b):
|
||||||
|
from bigchaindb.consensus import BaseConsensusRules
|
||||||
|
from bigchaindb.common import crypto
|
||||||
|
from bigchaindb.common.util import serialize
|
||||||
|
vote_body = {}
|
||||||
|
vote_data = serialize(vote_body)
|
||||||
|
signature = crypto.PrivateKey(b.me_private).sign(vote_data.encode())
|
||||||
|
vote_signed = {
|
||||||
|
'node_pubkey': b.me,
|
||||||
|
'signature': signature.decode(),
|
||||||
|
'vote': vote_body
|
||||||
|
}
|
||||||
|
assert not BaseConsensusRules.verify_vote([b.me], vote_signed)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user