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

Rebase/feat/586/integrate tx model #641

Merged
merged 54 commits into from
Sep 29, 2016

Conversation

sbellem
Copy link
Contributor

@sbellem sbellem commented Sep 26, 2016

No description provided.

@codecov-io
Copy link

codecov-io commented Sep 26, 2016

Current coverage is 95.84% (diff: 98.23%)

Merging #641 into master will decrease coverage by 0.02%

@@             master       #641   diff @@
==========================================
  Files            23         21     -2   
  Lines          1308       1155   -153   
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
- Hits           1254       1107   -147   
+ Misses           54         48     -6   
  Partials          0          0          

Sunburst

Powered by Codecov. Last update e74b4ee...df86def

@sbellem
Copy link
Contributor Author

sbellem commented Sep 26, 2016

Note to @TimDaub @rhsimplex @vrde and @diminator The rebase was quite messy, so it would be important that each one of you double-checks that nothing was lost or changed incorrectly along the way.

Some key things to look for (there may be others):

  • refactor of rethinkdb connection, pipeline -- @vrde
  • voting/election mechanisms, handling of stale txs -- @rhsimplex
  • server api -- @diminator
  • all new tx model integration changes -- @TimDaub

@@ -12,7 +12,7 @@ before_install:

install:
- sudo apt-get install rethinkdb
- pip install -e .[test]
- pip install -e .[test] --process-dependency-links
Copy link
Contributor

@TimDaub TimDaub Sep 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before merging into master:

  1. Push latest BDB-common to pip
  2. Remove this label
  3. Update version in setup.py

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -9,4 +9,4 @@ RUN pip install --upgrade pip

COPY . /usr/src/app/

RUN pip install --no-cache-dir -e .[dev]
RUN pip install --no-cache-dir --process-dependency-links -e .[dev]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before merging into master:

  1. Push latest BDB-common to pip
  2. Remove this label
  3. Update version in setup.py

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -268,7 +271,7 @@ def search_block_election_on_index(self, value, index):
index (str): name of a secondary index, e.g. 'transaction_id'

Returns:
A list of blocks with with only election information
A list of blocks (list(dict))with with only election information
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will take care of it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

raise DoubleSpend('input `{}` was already spent'
.format(input_txid))
else:
raise TypeError('`operation` must be either `TRANSFER`, `CREATE` '
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could take Transaction.ALLOWED_OPERATIONS here to create the error message.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might also be nice to tell the user what their given operation was too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

verifying_key = VerifyingKey(block['node_pubkey'])
try:
return verifying_key.verify(block_serialized, self.signature)
# TODO FOR CC: Shouldn't throw `AttributeError` or `ValueError` but
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've created a ticket for this in the cryptoconditions repo: bigchaindb/cryptoconditions#27

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, should we remove the TODO note?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -1,6 +1,6 @@
"""This module takes care of all the logic related to block creation.
Copy link
Contributor

@TimDaub TimDaub Sep 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed this class to now be called BlockPipeline, which is hopefully alright (@vrde and @rhsimplex?).
Shall we rename the file as well?

Copy link
Contributor Author

@sbellem sbellem Sep 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is redundant. The directory/sub-package pipelines acts as a namespace, so I do not understand the need for re-specifying "pipeline", whether for classes, modules, files, etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was renamed to BlockPipeline as I introduced another class called Block that is the actual block.

Since we're handling Blocks in BlockPipeline, I thought a separation of the two would be healthy for readability.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ok, I see

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with @TimDaub this was confusing to begin with (when I worked on it) and now that blocks are objects, the distinction is necessary

"""
if self.bigchain.transaction_exists(tx['id']):
tx = Transaction.from_dict(tx)
if self.bigchain.transaction_exists(tx.id):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rhsimplex: This is new logic that was added in this commit: c33d123

Would it make sense to put it in: https://github.com/bigchaindb/bigchaindb/pull/641/files#diff-9f1ab5b5daf1b3072e2123b6939bfb07R13 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. I think the established validation returns False if the transaction is in an undecided or invalid block, where the block process here treats undecided and valid blocks as the same.


Returns:
The block.
"""
r.table('backlog')\
.get_all(*[tx['id'] for tx in block['block']['transactions']])\
.get_all(*[tx['id'] for tx in block.to_dict()['block']['transactions']])\
Copy link
Contributor

@TimDaub TimDaub Sep 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of passing the whole block here, we should just pass the transaction ids to delete.

def delete_tx(self, transaction_ids):
...

Q: Do we really need the whole block to be returned? @vrde @rhsimplex

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't notice that this function was part of pipelines/block.py.
We'll have to return the block. I fixed it by not deserializing the block.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: 0e10a87

@@ -1,6 +1,7 @@
import logging
import multiprocessing as mp


Copy link
Contributor

@TimDaub TimDaub Sep 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove additional line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

new_tx = create_tx(b.me, transaction['fulfillments'][0]['owners_before'], None, 'CREATE', payload=payload)
return new_tx


def is_genesis_block(block):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not very proud of this method.
It handles both a dict-block as well as a Block-block.

Ideally, it should only handle Block-block.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are both needed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'bigchaindb-common>=0.0.2',
],
dependency_links=[
'git+https://github.com/bigchaindb/bigchaindb-common.git@integrate-transaction-model#egg=bigchaindb_common-0.0.2',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before merging into master, remove this line and push BDB-common to pip.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will take care of it



@pytest.fixture
def transfer_tx(signed_create_tx, user_vk, user_sk):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should call this signed_transfer_tx.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@patch('argparse.ArgumentParser.parse_args')
@patch('bigchaindb.commands.utils.base_parser')
@patch('bigchaindb.commands.utils.start')
def test_calling_main(start_mock, base_parser_mock, parse_args_mock,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @vrde proposed a better way to test this. Maybe he can comment on this and tell us what to do.

@@ -1,65 +0,0 @@
"""Custom exceptions used in the `bigchaindb` package.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to not leave these exceptions in bigchaindb? Does bigchaindb_common use these exceptions?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid point, lets handle (as you proposed) in this ticket: bigchaindb/bigchaindb-common#23



class BaseConsensusRules(AbstractConsensusRules):
class BaseConsensusRules():
Copy link
Contributor

@sohkai sohkai Sep 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can probably rename this to just ConsensusRules now? From my limited viewing, this class also doesn't seem very useful anymore. At best, it feels like these should all be module functions rather than statics.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my limited viewing, this class also doesn't seem very useful anymore. At best, it feels like these should all be module functions rather than statics.

There are functions to be added to the class: #501

Copy link
Contributor Author

@sbellem sbellem Sep 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of agree with @sohkai in the sense that functions would be sufficient. The module acts as a namespace.

In any case I would prefer that we treat that in a separate issue, as the current solution works, and this PR is already big enough, and has been time-consuming.

@@ -684,9 +621,10 @@ def get_last_voted_block(self):

except r.ReqlNonExistenceError:
# return last vote if last vote exists else return Genesis block
return list(self.connection.run(
block = list(self.connection.run(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is really misleading; it'd be nicer to split these out into two statements (assuming we really have to cast the rdb result to a list first):

res = self.connection.run(
    r.table('bigchain', read_mode=self.read_mode)
    .filter(util.is_genesis_block))
block = list(res)[0]

block = list(...) and then Block.from_dict(block) really makes you think how from_dict works with a list, until you see the trailing [0] at the end.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

"""Return all the blocks that have not been voted on by this node.

Returns:
block (list(dict)): a list of unvoted blocks
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably be :obj:listof:obj:dict?

In any case, why does this return a list of dicts, when it could be returning a list of Blocks?

Copy link
Contributor

@TimDaub TimDaub Sep 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case, why does this return a list of dicts, when it could be returning a list of Blocks?

This method is also the reason why https://github.com/bigchaindb/bigchaindb/pull/641/files/76c796429055d180dcf3caaf2a95e1591e0d743c#r80649416 (is_genesis_block) can handle both dict-blocks as well as Block-blocks.

To understand why, you have to see where its used. Fun fact: It's only used once in the entire code base:

https://github.com/bigchaindb/bigchaindb/blob/master/bigchaindb/pipelines/vote.py#L140

initialize runs only once when you freshly start bigchaindb. It basically simulates the changefeed and hence all blocks the newly initialized node has to work for. The changefeed is in json, so is this method's return value.

My proposal (this should maybe handled outside of this PR) is to put this function into vote.py.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll make that change:

Should probably be :obj:list of :obj:dict?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My proposal (this should maybe handled outside of this PR) is to put this function into vote.py.

Sounds like a plan!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


input_conditions = []
inputs_defined = reduce(and_, [bool(ffill.tx_input) for ffill
in self.fulfillments])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better way to do this is inputs_defined = all([ffill.tx_input for ffill in self.fulfillments)]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

input_conditions.append(input_tx.conditions[input_cid])

spent = bigchain.get_spent(input_txid, ffill.tx_input.cid)
if spent and spent.id != self.id:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should put this before input_conditions.append(); if we raise here, the append is wasted. Could also use input_cid instead of ffill.tx_input.cid.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

elif transactions is None:
self.transactions = []
else:
self.transactions = transactions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if self.transactions = transactions || [] is a typical python pattern?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.transactions = transactions or []

or

self.transactions = transactions if transactions else []

work

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

other = other.to_dict()
except AttributeError:
return False
return self.to_dict() == other
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to_dict can sometimes throw... which certainly makes for an unpleasant experience if you're trying to block1 == block2. Maybe catch OperationErrors here too?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puhh, that's a tough one. It could be that other and self both throw OperationError but are the same.
Not sure how to go about this. @vrde @sbellem to halp!

Copy link
Contributor Author

@sbellem sbellem Sep 28, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would handle this in a separate issue, and review the current OperationError mechanism. For instance, it is not clear to me why an empty block cannot have a dictionary representation. It could be an empty dict {}, perhaps, or something like:

{
    'id': None,
    'block': {
        'timestamp': self.timestamp,
        'transactions': [tx.to_dict() for tx in self.transactions],
        'node_pubkey': self.node_pubkey,
        'voters': self.voters,
    },
    'signature': self.signature,
 }

In any case, the point I am trying to make here is that the class initialization method (__init__) allows for no transaction, meanwhile the to_dict method does not, and moreover, the __eq__ comparison method relies on the to_dict method. Which means, if understand correctly, that the one can only compare two Block instances if they have transactions.

I think we could review the class design a bit, and for this reason, it makes more sense to tackle this in a separate issue.

Copy link
Contributor

@TimDaub TimDaub Sep 28, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For instance, it is not clear to me why an empty block cannot have a dictionary representation. It could be an empty dict {}, perhaps, or something like:

That's absolutely right. Usually we validate the block right after serialization anyways. So serializing with an empty list of transactions is OK IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, should we create a separate ticket for this? I think it would be simpler.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made #649.


def sign(self, private_key):
block_body = self.to_dict()
block_serialized = serialize(block_body['block'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't really like how to_dict() serializes the block (for creating the id), throws it away, and then this serializes it again. Maybe it's worth adding a method that just creates the block dict?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup.

'id': block_id,
'block': block,
'signature': self.signature,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, it might be nice to cache this since this is potentially quite expensive (1000 tx.to_dicts()) to be used each time for random look ups, like .id.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sohkai Would you mind creating a ticket for that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure: #643

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@sbellem sbellem force-pushed the rebase/feat/586/integrate-tx-model branch from 38443d8 to eef814c Compare September 28, 2016 14:48
@sbellem sbellem force-pushed the rebase/feat/586/integrate-tx-model branch from e18db11 to d3081e2 Compare September 28, 2016 15:20
@sbellem sbellem merged commit 50b0b3c into bigchaindb:master Sep 29, 2016
@TimDaub TimDaub mentioned this pull request Sep 30, 2016
27 tasks
sbellem added a commit to sbellem/bigchaindb that referenced this pull request Oct 24, 2016
* Adjust imports to bigchaindb_common

* Adjust get_spent function signature

* Adjust block serialization

* Fix BigchainApi Test

* Fix TestTransactionValidation tests

* Fix TestBlockValidation tests

* WIP: TestMultipleInputs

* Adjust tests to tx-model interface changes

- Fix old tests
- Fix tests in TestMultipleInputs class

* Remove fulfillment message tests

* Fix TransactionMalleability tests

* Remove Cryptoconditions tests

* Remove create_transaction

* Remove signing logic

* Remove consensus plugin

* Fix block_creation pipeline

* Fix election pipeline

* Replace some util functions with bdb_common ones

- timestamp ==> gen_timestamp
- serialize.

* Implement Block model

* Simplify function signatures for vote functions

Change parameter interface for the following functions:

- has_previous_vote
- verify_vote_signature
- block_election_status

so that they take a block's id and voters instead of a fake block.

* Integrate Block and Transaction model

* Fix leftover tests and cleanup conftest

* Add bigchaindb-common to install_requires

* Delete transactions after block is written (bigchaindb#609)

* delete transactions after block is written

* cleanup transaction_exists

* check for duplicate transactions

* delete invalid tx from backlog

* test duplicate transaction

* Remove dead code

* Test processes.py

* Test invalid tx in on server

* Fix tests for core.py

* Fix models tests

* Test commands main fn

* Add final coverage to vote pipeline

* Add more tests to voting pipeline

* Remove consensus plugin docs and misc

* Post rebase fixes

* Fix rebase mess

* Remove extra blank line

* Improve docstring

* Remove comment

handled in bigchaindb/cryptoconditions#27;
see bigchaindb/cryptoconditions#27

* Fix block serialization in block creation

* Add signed_ prefix to transfer_tx

* Improve docs

* Add library documentation page on pipelines

* PR feedback for models.py

* Impr. readability of get_last_voted_block

* Use dict comprehension

* Add docker-compose file to build and serve docs

locally for development purposes

* Change private_key for signing_key

* Improve docstrings

* Remove consensus docs

* Document new consensus module

* Create different transactions for the block

* Cleanup variable names in block.py

* Create different transactions for the block

* Cleanup variable names in block.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants