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

Delete gas used from state #630

Merged
merged 3 commits into from
May 3, 2018

Conversation

carver
Copy link
Contributor

@carver carver commented May 3, 2018

What was wrong?

Related to #599 & #507 - there was state mutation inside VM.apply_transaction(), to update gas_used, and get_block_by_header lived in VM (we're generally looking to move block manipulation out of VM, into Chain).

How was it fixed?

There wasn't really any need for gas_used to be in the state, we can verify the transaction against the header in the VM, before even calling state.apply_transaction.

  • add a new transaction verification, explicitly against the header
  • make_receipt new uses the header to generate the cumulative gas used
  • apply_transaction now builds on top of a passed-in header (rather than using a local mutation of self.block.header)
  • get_block_by_header really needed to belong in Chain instead of VM.
  • use only account_db instead of the full state to validate transaction (after separately running the transaction->header validation)

Yay for more use of (immutable) rlp object, and less state mutation.

Cute Animal Picture

put a cute animal picture link inside the parentheses

Copy link
Contributor Author

@carver carver left a comment

Choose a reason for hiding this comment

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

Just some comments of what I expect to follow up on in the morning. I'm checking out for the night.

else:
state_root = EIP658_TRANSACTION_STATUS_CODE_SUCCESS
success_code = EIP658_TRANSACTION_STATUS_CODE_SUCCESS
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm wrapping for the day, but on another review, this should probably be status_code instead of success_code

previous_header = result_header
receipts.append(receipt)

return result_header, receipts
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was nice to pull this chunk of logic out, but it was awkward to both:

  • chain the result of one application into the next, and
  • accumulate the results

A small version involved a lot of variable clobbering, like:

    def _apply_all_transactions(self, transactions, header):
        receipts = []

        for transaction in transactions:
            header, receipt, _ = self.apply_transaction(header, transaction)
            receipts.append(receipt)

        return header, receipts

I'm happy to hear suggestions about a way to do chaining & accumulation at the same time, in a more elegant way.

Copy link
Member

@pipermerriam pipermerriam May 3, 2018

Choose a reason for hiding this comment

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

Hammered out this accumulation based zero mutation way to do this.

import functools
from cytoolz import accumulate

transactions = ('a', 'b', 'c', 'd')


def apply_transaction(header, txn):
    return header + 1, txn * 3

def test_it():
    txn_fns = [
        functools.partial(apply_transaction, txn=transaction)
        for transaction in transactions
    ]
    return accumulate(lambda prev, txn_fn: txn_fn(prev[0]), txn_fns, (0, None))


if __name__ == "__main__":
    print(tuple(test_it()))

# ((0, None), (1, 'aaa'), (2, 'bbb'), (3, 'ccc'), (4, 'ddd'))

Requires using the toolz.accumulate because it allows you to supply an initial value. Result. Probably want to do away with the lambda 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.

Thanks! It got me thinking of some other ways to look at it.

Right now, to my eyes, that particular implementation is a drop in readability, compared to having some variable reuse in a short function.

I have an idea for an improvement to get rid of it, but I don't want to throw this PR off track, so I'll put it in the next one.

Copy link
Member

@pipermerriam pipermerriam May 3, 2018

Choose a reason for hiding this comment

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

I think in implementation it will look like this.

# top level helper function.
def _accumulate_apply_txn_fn(prev_result, apply_transaction_fn):
    return apply_transaction_fn(prev_result[0])

# in VM class
def import_block(self, block):
    ...
    if block.transactions:
        apply_transaction_fns = tuple(
            functools.partial(self.apply_transaction, transaction=transaction)
            for transaction in block.transactions
        )
        headers, receipts = zip(*tuple(accumulate(
            _accumulate_apply_txn_fn, 
            apply_transaction_fns,
            (self.block.header, None),
        ))[1:])
        last_header = headers[-1]
    else:
        last_header = self.block.header
        receipts = tuple()
     ...

I think I generally agree about the readability, but I'm also inclined to cut the extra API from the VM class. We could still use the logic above to encapulate the act of applying all of the transactions and getting back the resulting header and list of receipts but still keep it encapsulated in a utility function to keep the import_block method easier to read.

Dunno, your call. Just wanted to see it all written out mostly.

evm/vm/base.py Outdated
"""
Apply the transaction to the vm in the current block.

:param transaction: to apply
:param header: header of the block before application
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, I'll swap the order of these params in the docs. I went back and forth a few times locally. In the end a semi-chronological order made sense to me: the header is in the state before the transaction is applied, so comes first.

Copy link
Member

Choose a reason for hiding this comment

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

That's roughly the order I settled on as well. 👍

Rather than chronological I think hierarchical this is the natural order with header being higher in the API than transaction.

self,
block,
)
)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess this check will be moot, when this method moves up to Chain. But we are a couple days from moving this one, I think.

state_root, computation = self.state.apply_transaction(transaction)
receipt = self.make_receipt(transaction, computation, self.state)
# TODO: remove this mutation.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was the inspiration for most of the PR.

evm/vm/base.py Outdated
"""
Apply the transaction to the vm in the current block.

:param transaction: to apply
:param header: header of the block before application
Copy link
Member

Choose a reason for hiding this comment

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

That's roughly the order I settled on as well. 👍

Rather than chronological I think hierarchical this is the natural order with header being higher in the API than transaction.

previous_header = result_header
receipts.append(receipt)

return result_header, receipts
Copy link
Member

@pipermerriam pipermerriam May 3, 2018

Choose a reason for hiding this comment

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

Hammered out this accumulation based zero mutation way to do this.

import functools
from cytoolz import accumulate

transactions = ('a', 'b', 'c', 'd')


def apply_transaction(header, txn):
    return header + 1, txn * 3

def test_it():
    txn_fns = [
        functools.partial(apply_transaction, txn=transaction)
        for transaction in transactions
    ]
    return accumulate(lambda prev, txn_fn: txn_fn(prev[0]), txn_fns, (0, None))


if __name__ == "__main__":
    print(tuple(test_it()))

# ((0, None), (1, 'aaa'), (2, 'bbb'), (3, 'ccc'), (4, 'ddd'))

Requires using the toolz.accumulate because it allows you to supply an initial value. Result. Probably want to do away with the lambda for readability.

:param base_header: the header of the block before the transaction was applied.
:param transaction: the transaction used to generate the receipt
:param computation: the result of running the transaction computation
:param state: the resulting state, after executing the computation
Copy link
Contributor

Choose a reason for hiding this comment

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

Great to see you're taking the chance to directly improve the documentation ❤️

gas_used=receipt.gas_used,
state_root=state_root,
)

return new_header, receipt, computation

def _apply_all_transactions(self, transactions, base_header):
Copy link
Contributor

@cburgdorf cburgdorf May 3, 2018

Choose a reason for hiding this comment

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

How do you feel about sneaking in type definitions for new / changed functions? I do realize it (temporary) reduces conformity for that file in question when only few defs are typed while others are not but on the other hand it helps to work towards our goal to have full coverage and eventually enable stricter type checks to disallow untyped defs altogether.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

⚖️ I'm a little on the fence. These APIs will continue to change a lot. I guess I'm slightly 👎 at the moment, but when I feel like things start to settle, I'll be a lot more keen.

@carver carver force-pushed the delete-gas-used-from-state branch from 08ed4e3 to 9356fca Compare May 3, 2018 21:57
@carver carver merged commit b62eeb6 into ethereum:master May 3, 2018
@carver carver deleted the delete-gas-used-from-state branch May 3, 2018 22:05
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.

3 participants