示例#1
0
def test_create_collation_with_txs():
    """Test create_collation with transactions
    """
    shard_id = 1
    t = chain(shard_id)

    parent_collation_hash = t.chain.shards[shard_id].head_hash
    expected_period_number = t.chain.get_expected_period_number()

    txqueue = TransactionQueue()
    tx1 = t.generate_shard_tx(shard_id, tester.k2, tester.a4,
                              int(0.03 * utils.denoms.ether))
    tx2 = t.generate_shard_tx(shard_id, tester.k3, tester.a5,
                              int(0.03 * utils.denoms.ether))
    txqueue.add_transaction(tx1)
    txqueue.add_transaction(tx2)

    collation = collator.create_collation(t.chain,
                                          shard_id,
                                          parent_collation_hash,
                                          expected_period_number,
                                          coinbase=tester.a0,
                                          key=tester.k0,
                                          txqueue=txqueue)
    assert collation.transaction_count == 2
示例#2
0
def mine_on_chain(chain,
                  parent=None,
                  transactions=[],
                  coinbase=None,
                  timestamp=None):
    """Mine the next block on a chain.

    The newly mined block will be considered to be the head of the chain,
    regardless of its total difficulty.

    :param parent: the parent of the block to mine, or `None` to use the
                   current chain head
    :param transactions: a list of transactions to include in the new block
    :param coinbase: optional coinbase to replace ``chain.coinbase``
    """
    txqueue = TransactionQueue()
    for t in transactions:
        txqueue.add_transaction(t)
    parent_timestamp = parent.timestamp if parent else chain.state.timestamp
    hc, _ = meta.make_head_candidate(chain, txqueue, parent, timestamp
                                     or parent_timestamp + 1, coinbase
                                     or '\x00' * 20)
    assert hc.difficulty == 1
    m = ethpow.Miner(hc)
    rounds = 100
    nonce = 0
    while True:
        b = m.mine(rounds=rounds, start_nonce=nonce)
        if b:
            break
        nonce += rounds
    assert chain.add_block(b)
    return b
示例#3
0
def test_apply_collation():
    """Apply collation to ShardChain
    """
    shard_id = 1
    t = chain(shard_id)

    txqueue = TransactionQueue()
    tx1 = t.generate_shard_tx(shard_id, tester.k2, tester.a4,
                              int(0.03 * utils.denoms.ether))
    tx2 = t.generate_shard_tx(shard_id, tester.k3, tester.a5,
                              int(0.03 * utils.denoms.ether))
    txqueue.add_transaction(tx1)
    txqueue.add_transaction(tx2)

    state = t.chain.shards[shard_id].state
    prev_state_root = state.trie.root_hash
    collation = t.generate_collation(shard_id=1,
                                     coinbase=tester.a1,
                                     key=tester.k1,
                                     txqueue=txqueue)
    period_start_prevblock = t.chain.get_block(
        collation.header.period_start_prevhash)

    collator.apply_collation(state, collation, period_start_prevblock)

    assert state.trie.root_hash != prev_state_root
    assert collation.header.post_state_root == state.trie.root_hash
    assert collation.header.post_state_root == t.chain.shards[
        shard_id].state.trie.root_hash
示例#4
0
def test_verify_collation_header():
    shard_id = 1
    t = chain(shard_id)

    parent_collation_hash = t.chain.shards[shard_id].head_hash
    expected_period_number = t.chain.get_expected_period_number()

    txqueue = TransactionQueue()
    tx1 = t.generate_shard_tx(shard_id, tester.k2, tester.a4,
                              int(0.03 * utils.denoms.ether))
    tx2 = t.generate_shard_tx(shard_id, tester.k3, tester.a5,
                              int(0.03 * utils.denoms.ether))
    txqueue.add_transaction(tx1)
    txqueue.add_transaction(tx2)

    collation = collator.create_collation(t.chain,
                                          shard_id,
                                          parent_collation_hash,
                                          expected_period_number,
                                          coinbase=tester.a0,
                                          key=tester.k0,
                                          txqueue=txqueue)

    # Verify collation header
    assert collator.verify_collation_header(t.chain, collation.header)

    # Bad collation header 1
    collation = collator.create_collation(t.chain,
                                          shard_id,
                                          parent_collation_hash,
                                          expected_period_number,
                                          coinbase=tester.a1,
                                          key=tester.k1,
                                          txqueue=txqueue)
    collation.header.shard_id = -1
    with pytest.raises(ValueError):
        collator.verify_collation_header(t.chain, collation.header)

    # Bad collation header 2 - call_msg_add_header error
    collation = collator.create_collation(t.chain,
                                          shard_id,
                                          parent_collation_hash,
                                          expected_period_number,
                                          coinbase=tester.a1,
                                          key=tester.k1,
                                          txqueue=txqueue)
    collation.header.sig = utils.sha3('hello')
    with pytest.raises(ValueError):
        collator.verify_collation_header(t.chain, collation.header)
示例#5
0
def test_create_collation_empty_txqueue():
    """Test create_collation without transactions
    """
    shard_id = 1
    t = chain(shard_id)

    parent_collation_hash = t.chain.shards[shard_id].head_hash
    expected_period_number = t.chain.get_expected_period_number()

    txqueue = TransactionQueue()
    collation = collator.create_collation(t.chain,
                                          shard_id,
                                          parent_collation_hash,
                                          expected_period_number,
                                          coinbase=tester.a1,
                                          key=tester.k1,
                                          txqueue=txqueue)

    assert collation.transaction_count == 0
    assert collation.header.coinbase == tester.a1

    # sign error
    with pytest.raises(TypeError):
        collation = collator.create_collation(t.chain,
                                              shard_id,
                                              parent_collation_hash,
                                              expected_period_number,
                                              coinbase=tester.a1,
                                              key=123,
                                              txqueue=txqueue)
示例#6
0
 def __init__(self, app):
     super(ChainServiceMock, self).__init__(app)
     self.on_new_head_cbs = []
     self.transaction_queue = TransactionQueue()
     self.is_syncing = False
     self.mined_block = None
     self.block_mined_event = Event()
     self.head_candidate = Block(BlockHeader(difficulty=DIFFICULTY),
                                 db=DB())
def test_transaction():
    """Test create and apply collation with transactions
    """
    shard_id = 1
    t = chain(shard_id)
    log.info('head state: {}'.format(
        encode_hex(t.chain.shards[shard_id].state.trie.root_hash)))

    tx1 = t.generate_shard_tx(shard_id, tester.k2, tester.a4,
                              int(0.03 * utils.denoms.ether))
    tx2 = t.generate_shard_tx(shard_id, tester.k3, tester.a5,
                              int(0.03 * utils.denoms.ether))

    # Prepare txqueue
    txqueue = TransactionQueue()
    txqueue.add_transaction(tx1)
    txqueue.add_transaction(tx2)

    collation = t.generate_collation(shard_id=1,
                                     coinbase=tester.a1,
                                     key=tester.k1,
                                     txqueue=txqueue)
    log.debug('collation: {}, transaction_count:{}'.format(
        collation.to_dict(), collation.transaction_count))

    period_start_prevblock = t.chain.get_block(
        collation.header.period_start_prevhash)
    log.debug('period_start_prevblock: {}'.format(
        encode_hex(period_start_prevblock.header.hash)))
    t.chain.shards[shard_id].add_collation(collation, period_start_prevblock,
                                           t.chain.handle_ignored_collation)

    state = t.chain.shards[shard_id].mk_poststate_of_collation_hash(
        collation.header.hash)

    # Check to addesss received value
    assert state.get_balance(tester.a4) == 1000030000000000000000
    # Check incentives
    assert state.get_balance(tester.a1) == 1000002000000000000000

    # mk_poststate_of_collation_hash error
    with pytest.raises(Exception):
        state = t.chain.shards[shard_id].mk_poststate_of_collation_hash(
            b'1234')
def test_validate_transaction_tree():
    """Test validate_transaction_tree(collation)
    """
    t = chain(shard_id)
    tx1 = t.generate_shard_tx(shard_id, tester.k2, tester.a4,
                              int(0.03 * utils.denoms.ether))
    tx2 = t.generate_shard_tx(shard_id, tester.k3, tester.a5,
                              int(0.03 * utils.denoms.ether))
    txqueue = TransactionQueue()
    txqueue.add_transaction(tx1)
    txqueue.add_transaction(tx2)

    collation = t.generate_collation(shard_id=1,
                                     coinbase=tester.a1,
                                     key=tester.k1,
                                     txqueue=txqueue)
    assert state_transition.validate_transaction_tree(collation)

    collation.header.tx_list_root = trie.BLANK_ROOT
    with pytest.raises(ValueError):
        state_transition.validate_transaction_tree(collation)
示例#9
0
文件: casper.py 项目: yep/research
 def __init__(self, genesis, key, network, env, time_offset=5):
     # Create a chain object
     self.chain = Chain(genesis, env=env)
     # Create a transaction queue
     self.txqueue = TransactionQueue()
     # Use the validator's time as the chain's time
     self.chain.time = lambda: self.get_timestamp()
     # My private key
     self.key = key
     # My address
     self.address = privtoaddr(key)
     # My randao
     self.randao = RandaoManager(sha3(self.key))
     # Pointer to the test p2p network
     self.network = network
     # Record of objects already received and processed
     self.received_objects = {}
     # The minimum eligible timestamp given a particular number of skips
     self.next_skip_count = 0
     self.next_skip_timestamp = 0
     # Is this validator active?
     self.active = False
     # Code that verifies signatures from this validator
     self.validation_code = generate_validation_code(privtoaddr(key))
     # Validation code hash
     self.vchash = sha3(self.validation_code)
     # Parents that this validator has already built a block on
     self.used_parents = {}
     # This validator's clock offset (for testing purposes)
     self.time_offset = random.randrange(time_offset) - (time_offset // 2)
     # Determine the epoch length
     self.epoch_length = self.call_casper('getEpochLength')
     # My minimum gas price
     self.mingasprice = 20 * 10**9
     # Give this validator a unique ID
     self.id = len(ids)
     ids.append(self.id)
     self.update_activity_status()
     self.cached_head = self.chain.head_hash
示例#10
0
 def __init__(self, key, genesis, network, valcode_addr=None, mining=False):
     self.key = key
     self.coinbase = utils.privtoaddr(self.key)
     self.chain = chain.Chain(genesis=genesis,
                              reset_genesis=True,
                              coinbase=self.coinbase,
                              new_head_cb=self._on_new_head)
     self.mining = mining
     self.nonce = self.chain.state.get_nonce(self.coinbase)
     self.valcode_tx = None
     self.deposit_tx = None
     self.valcode_addr = valcode_addr
     self.prepares = dict()
     self.prev_prepare_epoch = 0
     self.prev_commit_epoch = 0
     self.epoch_length = self.chain.env.config['EPOCH_LENGTH']
     # When the transaction_queue is modified, we must set
     # self._head_candidate_needs_updating to True in order to force the
     # head candidate to be updated.
     self.transaction_queue = TransactionQueue()
     self._head_candidate_needs_updating = True
     # Add validator to the network
     self.network = network
     self.network.join(self)
示例#11
0
 def __init__(self, genesis, key, network, env, time_offset=5):
     # Create a chain object
     self.chain = Chain(genesis, env=env)
     # Create a transaction queue
     self.txqueue = TransactionQueue()
     # Use the validator's time as the chain's time
     self.chain.time = lambda: self.get_timestamp()
     # My private key
     self.key = key
     # My address
     self.address = privtoaddr(key)
     # My randao
     self.randao = RandaoManager(sha3(self.key))
     # Pointer to the test p2p network
     self.network = network
     # Record of objects already received and processed
     self.received_objects = {}
     # The minimum eligible timestamp given a particular number of skips
     self.next_skip_count = 0
     self.next_skip_timestamp = 0
     # Is this validator active?
     self.active = False
     # Code that verifies signatures from this validator
     self.validation_code = generate_validation_code(privtoaddr(key))
     # Validation code hash
     self.vchash = sha3(self.validation_code)
     # Parents that this validator has already built a block on
     self.used_parents = {}
     # This validator's clock offset (for testing purposes)
     self.time_offset = random.randrange(time_offset) - (time_offset // 2)
     # Determine the epoch length
     self.epoch_length = self.call_casper('getEpochLength')
     # My minimum gas price
     self.mingasprice = 20 * 10**9
     # Give this validator a unique ID
     self.id = len(ids)
     ids.append(self.id)
     self.update_activity_status()
     self.cached_head = self.chain.head_hash
def test_add_transactions():
    """Test add_transactions(state, collation, txqueue, min_gasprice=0)
    """
    t = chain(shard_id)
    tx1 = t.generate_shard_tx(shard_id, tester.k2, tester.a4,
                              int(0.03 * utils.denoms.ether))
    tx2 = t.generate_shard_tx(shard_id, tester.k3, tester.a5,
                              int(0.03 * utils.denoms.ether))
    txqueue = TransactionQueue()
    txqueue.add_transaction(tx1)
    txqueue.add_transaction(tx2)

    coinbase = tester.a1
    state = t.chain.shards[shard_id].state.ephemeral_clone()
    collation = state_transition.mk_collation_from_prevstate(
        t.chain.shards[shard_id], state, coinbase)

    state_transition.add_transactions(state,
                                      collation,
                                      txqueue,
                                      shard_id,
                                      mainchain_state=t.head_state)
    assert collation.transaction_count == 2
    assert state.get_balance(tester.a4) == 1000 * utils.denoms.ether + int(
        0.03 * utils.denoms.ether)

    # InsufficientBalance -> don't include this transaction
    tx3 = t.generate_shard_tx(shard_id, tester.k2, tester.a4,
                              int(100000000000 * utils.denoms.ether))
    txqueue.add_transaction(tx3)
    state_transition.add_transactions(state,
                                      collation,
                                      txqueue,
                                      shard_id,
                                      mainchain_state=t.head_state)
    assert collation.transaction_count == 2
示例#13
0
class Validator():
    def __init__(self, genesis, key, network, env, time_offset=5):
        # Create a chain object
        self.chain = Chain(genesis, env=env)
        # Create a transaction queue
        self.txqueue = TransactionQueue()
        # Use the validator's time as the chain's time
        self.chain.time = lambda: self.get_timestamp()
        # My private key
        self.key = key
        # My address
        self.address = privtoaddr(key)
        # My randao
        self.randao = RandaoManager(sha3(self.key))
        # Pointer to the test p2p network
        self.network = network
        # Record of objects already received and processed
        self.received_objects = {}
        # The minimum eligible timestamp given a particular number of skips
        self.next_skip_count = 0
        self.next_skip_timestamp = 0
        # Is this validator active?
        self.active = False
        # Code that verifies signatures from this validator
        self.validation_code = generate_validation_code(privtoaddr(key))
        # Validation code hash
        self.vchash = sha3(self.validation_code)
        # Parents that this validator has already built a block on
        self.used_parents = {}
        # This validator's clock offset (for testing purposes)
        self.time_offset = random.randrange(time_offset) - (time_offset // 2)
        # Determine the epoch length
        self.epoch_length = self.call_casper('getEpochLength')
        # My minimum gas price
        self.mingasprice = 20 * 10**9
        # Give this validator a unique ID
        self.id = len(ids)
        ids.append(self.id)
        self.update_activity_status()
        self.cached_head = self.chain.head_hash

    def call_casper(self, fun, args=[]):
        return call_casper(self.chain.state, fun, args)

    def update_activity_status(self):
        start_epoch = self.call_casper('getStartEpoch', [self.vchash])
        now_epoch = self.call_casper('getEpoch')
        end_epoch = self.call_casper('getEndEpoch', [self.vchash])
        if start_epoch <= now_epoch < end_epoch:
            self.active = True
            self.next_skip_count = 0
            self.next_skip_timestamp = get_timestamp(self.chain, self.next_skip_count)
            print 'In current validator set'
        else:
            self.active = False

    def get_timestamp(self):
        return int(self.network.time * 0.01) + self.time_offset

    def on_receive(self, obj):
        if isinstance(obj, list):
            for _obj in obj:
                self.on_receive(_obj)
            return
        if obj.hash in self.received_objects:
            return
        if isinstance(obj, Block):
            print 'Receiving block', obj
            assert obj.hash not in self.chain
            block_success = self.chain.add_block(obj)
            self.network.broadcast(self, obj)
            self.network.broadcast(self, ChildRequest(obj.header.hash))
            self.update_head()
        elif isinstance(obj, Transaction):
            print 'Receiving transaction', obj
            if obj.gasprice >= self.mingasprice:
                self.txqueue.add_transaction(obj)
                print 'Added transaction, txqueue size %d' % len(self.txqueue.txs)
                self.network.broadcast(self, obj)
            else:
                print 'Gasprice too low'
        self.received_objects[obj.hash] = True
        for x in self.chain.get_chain():
            assert x.hash in self.received_objects

    def tick(self):
        # Try to create a block
        # Conditions:
        # (i) you are an active validator,
        # (ii) you have not yet made a block with this parent
        if self.active and self.chain.head_hash not in self.used_parents:
            t = self.get_timestamp()
            # Is it early enough to create the block?
            if t >= self.next_skip_timestamp and (not self.chain.head or t > self.chain.head.header.timestamp):
                # Wrong validator; in this case, just wait for the next skip count
                if not check_skips(self.chain, self.vchash, self.next_skip_count):
                    self.next_skip_count += 1
                    self.next_skip_timestamp = get_timestamp(self.chain, self.next_skip_count)
                    # print 'Incrementing proposed timestamp for block %d to %d' % \
                    #     (self.chain.head.header.number + 1 if self.chain.head else 0, self.next_skip_timestamp)
                    return
                self.used_parents[self.chain.head_hash] = True
                # Simulated 15% chance of validator failure to make a block
                if random.random() > 0.999:
                    print 'Simulating validator failure, block %d not created' % (self.chain.head.header.number + 1 if self.chain.head else 0)
                    return
                # Make the block
                s1 = self.chain.state.trie.root_hash
                pre_dunkle_count = self.call_casper('getTotalDunklesIncluded')
                dunkle_txs = get_dunkle_candidates(self.chain, self.chain.state)
                blk = make_head_candidate(self.chain, self.txqueue)
                randao = self.randao.get_parent(self.call_casper('getRandao', [self.vchash]))
                blk = sign_block(blk, self.key, randao, self.vchash, self.next_skip_count)
                # Make sure it's valid
                global global_block_counter
                global_block_counter += 1
                for dtx in dunkle_txs:
                    assert dtx in blk.transactions, (dtx, blk.transactions)
                print 'made block with timestamp %d and %d dunkles' % (blk.timestamp, len(dunkle_txs))
                s2 = self.chain.state.trie.root_hash
                assert s1 == s2
                assert blk.timestamp >= self.next_skip_timestamp
                assert self.chain.add_block(blk)
                self.update_head()
                post_dunkle_count = self.call_casper('getTotalDunklesIncluded')
                assert post_dunkle_count - pre_dunkle_count == len(dunkle_txs)
                self.received_objects[blk.hash] = True
                print 'Validator %d making block %d (%s)' % (self.id, blk.header.number, blk.header.hash[:8].encode('hex'))
                self.network.broadcast(self, blk)
        # Sometimes we received blocks too early or out of order;
        # run an occasional loop that processes these
        if random.random() < 0.02:
            self.chain.process_time_queue()
            self.chain.process_parent_queue()
            self.update_head()

    def update_head(self):
        if self.cached_head == self.chain.head_hash:
            return
        self.cached_head = self.chain.head_hash
        if self.chain.state.block_number % self.epoch_length == 0:
            self.update_activity_status()
        if self.active:
            self.next_skip_count = 0
            self.next_skip_timestamp = get_timestamp(self.chain, self.next_skip_count)
        print 'Head changed: %s, will attempt creating a block at %d' % (self.chain.head_hash.encode('hex'), self.next_skip_timestamp)

    def withdraw(self, gasprice=20 * 10**9):
        sigdata = make_withdrawal_signature(self.key)
        txdata = casper_ct.encode('startWithdrawal', [self.vchash, sigdata])
        tx = Transaction(self.chain.state.get_nonce(self.address), gasprice, 650000, self.chain.config['CASPER_ADDR'], 0, txdata).sign(self.key)
        self.txqueue.add_transaction(tx, force=True)
        self.network.broadcast(self, tx)
        print 'Withdrawing!'

    def deposit(self, gasprice=20 * 10**9):
        assert value * 10**18 >= self.chain.state.get_balance(self.address) + gasprice * 1000000
        tx = Transaction(self.chain.state.get_nonce(self.address) * 10**18, gasprice, 1000000,
                         casper_config['CASPER_ADDR'], value * 10**18,
                         ct.encode('deposit', [self.validation_code, self.randao.get(9999)]))
示例#14
0
class ChainService(WiredService):
    """
    Manages the chain and requests to it.
    """
    # required by BaseService
    name = 'chain'
    default_config = dict(eth=dict(network_id=0, genesis='', pruning=-1),
                          block=ethereum_config.default_config)

    # required by WiredService
    wire_protocol = eth_protocol.ETHProtocol  # create for each peer

    # initialized after configure:
    chain = None
    genesis = None
    synchronizer = None
    config = None
    block_queue_size = 1024
    processed_gas = 0
    processed_elapsed = 0
    process_time_queue_period = 5

    def __init__(self, app):
        self.config = app.config
        sce = self.config['eth']
        if int(sce['pruning']) >= 0:
            self.db = RefcountDB(app.services.db)
            if "I am not pruning" in self.db.db:
                raise RuntimeError(
                    "The database in '{}' was initialized as non-pruning. "
                    "Can not enable pruning now.".format(
                        self.config['data_dir']))
            self.db.ttl = int(sce['pruning'])
            self.db.db.put("I am pruning", "1")
        else:
            self.db = app.services.db
            if "I am pruning" in self.db:
                raise RuntimeError(
                    "The database in '{}' was initialized as pruning. "
                    "Can not disable pruning now".format(
                        self.config['data_dir']))
            self.db.put("I am not pruning", "1")

        if 'network_id' in self.db:
            db_network_id = self.db.get(b'network_id')
            if db_network_id != to_string(sce['network_id']):
                raise RuntimeError(
                    "The database in '{}' was initialized with network id {} and can not be used "
                    "when connecting to network id {}. Please choose a different data directory."
                    .format(self.config['data_dir'], db_network_id,
                            sce['network_id']))

        else:
            self.db.put(b'network_id', to_string(sce['network_id']))
            self.db.commit()

        assert self.db is not None

        super(ChainService, self).__init__(app)
        log.info('initializing chain')
        coinbase = app.services.accounts.coinbase
        env = Env(self.db, sce['block'])

        genesis_data = sce.get('genesis_data', {})
        if not genesis_data:
            genesis_data = mk_genesis_data(env)
        self.chain = Chain(env=env,
                           genesis=genesis_data,
                           coinbase=coinbase,
                           new_head_cb=self._on_new_head)
        header = self.chain.state.prev_headers[0]
        log.info('chain at', number=header.number)
        if 'genesis_hash' in sce:
            assert sce['genesis_hash'] == self.chain.genesis.hex_hash, \
                "Genesis hash mismatch.\n  Expected: %s\n  Got: %s" % (
                    sce['genesis_hash'], self.chain.genesis.hex_hash)

        self.dao_challenges = dict()
        self.synchronizer = Synchronizer(self, force_sync=None)

        self.block_queue = Queue(maxsize=self.block_queue_size)
        # When the transaction_queue is modified, we must set
        # self._head_candidate_needs_updating to True in order to force the
        # head candidate to be updated.
        self.transaction_queue = TransactionQueue()
        self._head_candidate_needs_updating = True
        # Initialize a new head candidate.
        _ = self.head_candidate
        self.min_gasprice = 20 * 10**9  # TODO: better be an option to validator service?
        self.add_blocks_lock = False
        self.add_transaction_lock = gevent.lock.Semaphore()
        self.broadcast_filter = DuplicatesFilter()
        self.on_new_head_cbs = []
        self.newblock_processing_times = deque(maxlen=1000)
        gevent.spawn_later(self.process_time_queue_period,
                           self.process_time_queue)

    @property
    def is_syncing(self):
        return self.synchronizer.synctask is not None

    @property
    def is_mining(self):
        if 'pow' in self.app.services:
            return self.app.services.pow.active
        if 'validator' in self.app.services:
            return self.app.services.validator.active
        return False

    def process_time_queue(self):
        try:
            self.chain.process_time_queue()
        except Exception as e:
            log.info(str(e))
        finally:
            gevent.spawn_later(self.process_time_queue_period,
                               self.process_time_queue)

    # TODO: Move to pyethereum
    def get_receipts(self, block):
        # Receipts are no longer stored in the database, so need to generate
        # them on the fly here.
        temp_state = self.chain.mk_poststate_of_blockhash(
            block.header.prevhash)
        initialize(temp_state, block)
        for tx in block.transactions:
            apply_transaction(temp_state, tx)
        return temp_state.receipts

    def _on_new_head(self, block):
        log.debug('new head cbs', num=len(self.on_new_head_cbs))
        self.transaction_queue = self.transaction_queue.diff(
            block.transactions)
        self._head_candidate_needs_updating = True
        for cb in self.on_new_head_cbs:
            cb(block)

    @property
    def head_candidate(self):
        if self._head_candidate_needs_updating:
            self._head_candidate_needs_updating = False
            # Make a copy of self.transaction_queue because
            # make_head_candidate modifies it.
            txqueue = copy.deepcopy(self.transaction_queue)
            self._head_candidate, self._head_candidate_state = make_head_candidate(
                self.chain, txqueue, timestamp=int(time.time()))
        return self._head_candidate

    def add_transaction(self,
                        tx,
                        origin=None,
                        force_broadcast=False,
                        force=False):
        if self.is_syncing:
            if force_broadcast:
                assert origin is None  # only allowed for local txs
                log.debug('force broadcasting unvalidated tx')
                self.broadcast_transaction(tx, origin=origin)
            return  # we can not evaluate the tx based on outdated state
        log.debug('add_transaction',
                  locked=(not self.add_transaction_lock.locked()),
                  tx=tx)
        assert isinstance(tx, Transaction)
        assert origin is None or isinstance(origin, BaseProtocol)

        if tx.hash in self.broadcast_filter:
            log.debug('discarding known tx')  # discard early
            return

        # validate transaction
        try:
            # Transaction validation for broadcasting. Transaction is validated
            # against the current head candidate.
            validate_transaction(self._head_candidate_state, tx)

            log.debug('valid tx, broadcasting')
            self.broadcast_transaction(tx, origin=origin)  # asap
        except InvalidTransaction as e:
            log.debug('invalid tx', error=e)
            return

        if origin is not None:  # not locally added via jsonrpc
            if not self.is_mining or self.is_syncing:
                log.debug('discarding tx',
                          syncing=self.is_syncing,
                          mining=self.is_mining)
                return

        if tx.gasprice >= self.min_gasprice:
            self.add_transaction_lock.acquire()
            self.transaction_queue.add_transaction(tx, force=force)
            self._head_candidate_needs_updating = True
            self.add_transaction_lock.release()
        else:
            log.info("too low gasprice, ignore",
                     tx=encode_hex(tx.hash)[:8],
                     gasprice=tx.gasprice)

    def check_header(self, header):
        return check_pow(self.chain.state, header)

    def add_block(self, t_block, proto):
        "adds a block to the block_queue and spawns _add_block if not running"
        self.block_queue.put((t_block, proto))  # blocks if full
        if not self.add_blocks_lock:
            self.add_blocks_lock = True  # need to lock here (ctx switch is later)
            gevent.spawn(self._add_blocks)

    def add_mined_block(self, block):
        log.debug('adding mined block', block=block)
        assert isinstance(block, Block)
        if self.chain.add_block(block):
            log.debug('added', block=block, ts=time.time())
            assert block == self.chain.head
            self.transaction_queue = self.transaction_queue.diff(
                block.transactions)
            self._head_candidate_needs_updating = True
            self.broadcast_newblock(
                block, chain_difficulty=self.chain.get_score(block))
            return True
        log.debug('failed to add', block=block, ts=time.time())
        return False

    def knows_block(self, block_hash):
        "if block is in chain or in queue"
        if self.chain.has_blockhash(block_hash):
            return True
        # check if queued or processed
        for i in range(len(self.block_queue.queue)):
            if block_hash == self.block_queue.queue[i][0].header.hash:
                return True
        return False

    def _add_blocks(self):
        log.debug('add_blocks',
                  qsize=self.block_queue.qsize(),
                  add_tx_lock=self.add_transaction_lock.locked())
        assert self.add_blocks_lock is True
        self.add_transaction_lock.acquire()
        try:
            while not self.block_queue.empty():
                # sleep at the beginning because continue keywords will skip bottom
                gevent.sleep(0.001)

                t_block, proto = self.block_queue.peek(
                )  # peek: knows_block while processing
                if self.chain.has_blockhash(t_block.header.hash):
                    log.warn('known block', block=t_block)
                    self.block_queue.get()
                    continue
                if not self.chain.has_blockhash(t_block.header.prevhash):
                    log.warn('missing parent',
                             block=t_block,
                             head=self.chain.head)
                    self.block_queue.get()
                    continue
                try:  # deserialize
                    st = time.time()
                    block = t_block.to_block()
                    elapsed = time.time() - st
                    log.debug('deserialized',
                              elapsed='%.4fs' % elapsed,
                              ts=time.time(),
                              gas_used=block.gas_used,
                              gpsec=self.gpsec(block.gas_used, elapsed))
                except InvalidTransaction as e:
                    log.warn('invalid transaction',
                             block=t_block,
                             error=e,
                             FIXME='ban node')
                    errtype = \
                        'InvalidNonce' if isinstance(e, InvalidNonce) else \
                        'NotEnoughCash' if isinstance(e, InsufficientBalance) else \
                        'OutOfGasBase' if isinstance(e, InsufficientStartGas) else \
                        'other_transaction_error'
                    sentry.warn_invalid(t_block, errtype)
                    self.block_queue.get()
                    continue
                except VerificationFailed as e:
                    log.warn('verification failed', error=e, FIXME='ban node')
                    sentry.warn_invalid(t_block, 'other_block_error')
                    self.block_queue.get()
                    continue

                # All checks passed
                log.debug('adding', block=block, ts=time.time())
                if self.chain.add_block(block):
                    now = time.time()
                    log.info('added',
                             block=block,
                             txs=block.transaction_count,
                             gas_used=block.gas_used)
                    if t_block.newblock_timestamp:
                        total = now - t_block.newblock_timestamp
                        self.newblock_processing_times.append(total)
                        avg = statistics.mean(self.newblock_processing_times)
                        med = statistics.median(self.newblock_processing_times)
                        max_ = max(self.newblock_processing_times)
                        min_ = min(self.newblock_processing_times)
                        log.info('processing time',
                                 last=total,
                                 avg=avg,
                                 max=max_,
                                 min=min_,
                                 median=med)
                    if self.is_mining:
                        self.transaction_queue = self.transaction_queue.diff(
                            block.transactions)
                else:
                    log.warn('could not add', block=block)

                self.block_queue.get(
                )  # remove block from queue (we peeked only)
        finally:
            self.add_blocks_lock = False
            self.add_transaction_lock.release()

    def gpsec(self, gas_spent=0, elapsed=0):
        if gas_spent:
            self.processed_gas += gas_spent
            self.processed_elapsed += elapsed
        return int(
            old_div(self.processed_gas, (0.001 + self.processed_elapsed)))

    def broadcast_newblock(self, block, chain_difficulty=None, origin=None):
        if not chain_difficulty:
            assert self.chain.has_blockhash(block.hash)
            chain_difficulty = self.chain.get_score(block)
        assert isinstance(block, (eth_protocol.TransientBlock, Block))
        if self.broadcast_filter.update(block.header.hash):
            log.debug('broadcasting newblock', origin=origin)
            bcast = self.app.services.peermanager.broadcast
            bcast(eth_protocol.ETHProtocol,
                  'newblock',
                  args=(block, chain_difficulty),
                  exclude_peers=[origin.peer] if origin else [])
        else:
            log.debug('already broadcasted block')

    def broadcast_transaction(self, tx, origin=None):
        assert isinstance(tx, Transaction)
        if self.broadcast_filter.update(tx.hash):
            log.debug('broadcasting tx', origin=origin)
            bcast = self.app.services.peermanager.broadcast
            bcast(eth_protocol.ETHProtocol,
                  'transactions',
                  args=(tx, ),
                  exclude_peers=[origin.peer] if origin else [])
        else:
            log.debug('already broadcasted tx')

    def query_headers(self,
                      hash_mode,
                      max_hashes,
                      skip,
                      reverse,
                      origin_hash=None,
                      number=None):
        headers = []
        unknown = False
        while not unknown and len(headers) < max_hashes:
            if hash_mode:
                if not origin_hash:
                    break
                block = self.chain.get_block(origin_hash)
                if not block:
                    break
                # If reached genesis, stop
                if block.number == 0:
                    break
                origin = block.header
            else:
                # If reached genesis, stop
                if number is None or number == 0:
                    break
                block = self.chain.get_block_by_number(number)
                if block is None:
                    break
                origin = block.header

            headers.append(origin)

            if hash_mode:  # hash traversal
                if reverse:
                    for i in range(skip + 1):
                        try:
                            block = self.chain.get_block(origin_hash)
                            if block:
                                origin_hash = block.prevhash
                            else:
                                unknown = True
                                break
                        except KeyError:
                            unknown = True
                            break
                else:
                    blockhash = self.chain.get_blockhash_by_number(
                        origin.number + skip + 1)
                    try:
                        # block = self.chain.get_block(blockhash)
                        if block and self.chain.get_blockhashes_from_hash(
                                blockhash, skip + 1)[skip] == origin_hash:
                            origin_hash = blockhash
                        else:
                            unknown = True
                    except KeyError:
                        unknown = True
            else:  # number traversal
                if reverse:
                    if number >= (skip + 1):
                        number -= (skip + 1)
                    else:
                        unknown = True
                else:
                    number += (skip + 1)
        return headers

    # wire protocol receivers ###########

    def on_wire_protocol_start(self, proto):
        log.debug('----------------------------------')
        log.debug('on_wire_protocol_start', proto=proto)
        assert isinstance(proto, self.wire_protocol)
        # register callbacks
        proto.receive_status_callbacks.append(self.on_receive_status)
        proto.receive_newblockhashes_callbacks.append(self.on_newblockhashes)
        proto.receive_transactions_callbacks.append(
            self.on_receive_transactions)
        proto.receive_getblockheaders_callbacks.append(
            self.on_receive_getblockheaders)
        proto.receive_blockheaders_callbacks.append(
            self.on_receive_blockheaders)
        proto.receive_getblockbodies_callbacks.append(
            self.on_receive_getblockbodies)
        proto.receive_blockbodies_callbacks.append(self.on_receive_blockbodies)
        proto.receive_newblock_callbacks.append(self.on_receive_newblock)

        # send status
        head = self.chain.head
        proto.send_status(chain_difficulty=self.chain.get_score(head),
                          chain_head_hash=head.hash,
                          genesis_hash=self.chain.genesis.hash)

    def on_wire_protocol_stop(self, proto):
        assert isinstance(proto, self.wire_protocol)
        log.debug('----------------------------------')
        log.debug('on_wire_protocol_stop', proto=proto)

    def on_receive_status(self, proto, eth_version, network_id,
                          chain_difficulty, chain_head_hash, genesis_hash):
        log.debug('----------------------------------')
        log.debug('status received', proto=proto, eth_version=eth_version)

        if eth_version != proto.version:
            if ('eth', proto.version) in proto.peer.remote_capabilities:
                # if remote peer is capable of our version, keep the connection
                # even the peer tried a different version
                pass
            else:
                log.debug("no capable protocol to use, disconnect",
                          proto=proto,
                          eth_version=eth_version)
                proto.send_disconnect(proto.disconnect.reason.useless_peer)
                return

        if network_id != self.config['eth'].get('network_id',
                                                proto.network_id):
            log.debug("invalid network id",
                      remote_network_id=network_id,
                      expected_network_id=self.config['eth'].get(
                          'network_id', proto.network_id))
            raise eth_protocol.ETHProtocolError('wrong network_id')

        # check genesis
        if genesis_hash != self.chain.genesis.hash:
            log.warn("invalid genesis hash",
                     remote_id=proto,
                     genesis=encode_hex(genesis_hash))
            raise eth_protocol.ETHProtocolError('wrong genesis block')

        # initiate DAO challenge
        self.dao_challenges[proto] = (DAOChallenger(self, proto),
                                      chain_head_hash, chain_difficulty)

    def on_dao_challenge_answer(self, proto, result):
        if result:
            log.debug("DAO challenge passed")
            _, chain_head_hash, chain_difficulty = self.dao_challenges[proto]

            # request chain
            self.synchronizer.receive_status(proto, chain_head_hash,
                                             chain_difficulty)
            # send transactions
            transactions = self.transaction_queue.peek()
            if transactions:
                log.debug("sending transactions", remote_id=proto)
                proto.send_transactions(*transactions)
        else:
            log.debug("peer failed to answer DAO challenge, stop.",
                      proto=proto)
            if proto.peer:
                proto.peer.stop()
        del self.dao_challenges[proto]

    # transactions

    def on_receive_transactions(self, proto, transactions):
        "receives rlp.decoded serialized"
        log.debug('----------------------------------')
        log.debug('remote_transactions_received',
                  count=len(transactions),
                  remote_id=proto)
        for tx in transactions:
            self.add_transaction(tx, origin=proto)

    # blockhashes ###########

    def on_newblockhashes(self, proto, newblockhashes):
        """
        msg sent out if not the full block is propagated
        chances are high, that we get the newblock, though.
        """
        log.debug('----------------------------------')
        log.debug("recv newblockhashes",
                  num=len(newblockhashes),
                  remote_id=proto)
        assert len(newblockhashes) <= 256
        self.synchronizer.receive_newblockhashes(proto, newblockhashes)

    def on_receive_getblockheaders(self, proto, hash_or_number, block, amount,
                                   skip, reverse):
        hash_mode = 1 if hash_or_number[0] else 0
        block_id = encode_hex(
            hash_or_number[0]) if hash_mode else hash_or_number[1]
        log.debug('----------------------------------')
        log.debug("handle_getblockheaders", amount=amount, block=block_id)

        headers = []
        max_hashes = min(amount, self.wire_protocol.max_getblockheaders_count)

        if hash_mode:
            origin_hash = hash_or_number[0]
        else:
            if is_dao_challenge(self.config['eth']['block'], hash_or_number[1],
                                amount, skip):
                log.debug("sending: answer DAO challenge")
                headers.append(build_dao_header(self.config['eth']['block']))
                proto.send_blockheaders(*headers)
                return
            try:
                origin_hash = self.chain.get_blockhash_by_number(
                    hash_or_number[1])
            except KeyError:
                origin_hash = b''
        if not origin_hash or not self.chain.has_blockhash(origin_hash):
            log.debug('unknown block: {}'.format(encode_hex(origin_hash)))
            proto.send_blockheaders(*[])
            return

        headers = self.query_headers(
            hash_mode,
            max_hashes,
            skip,
            reverse,
            origin_hash=origin_hash,
            number=block_id,
        )

        log.debug("sending: found blockheaders", count=len(headers))
        proto.send_blockheaders(*headers)

    def on_receive_blockheaders(self, proto, blockheaders):
        log.debug('----------------------------------')
        if blockheaders:
            log.debug("on_receive_blockheaders",
                      count=len(blockheaders),
                      remote_id=proto,
                      first=encode_hex(blockheaders[0].hash),
                      last=encode_hex(blockheaders[-1].hash))
        else:
            log.debug("recv 0 remote block headers, signifying genesis block")

        if proto in self.dao_challenges:
            self.dao_challenges[proto][0].receive_blockheaders(
                proto, blockheaders)
        else:
            self.synchronizer.receive_blockheaders(proto, blockheaders)

    # blocks ################

    def on_receive_getblockbodies(self, proto, blockhashes):
        log.debug('----------------------------------')
        log.debug("on_receive_getblockbodies", count=len(blockhashes))
        found = []
        for bh in blockhashes[:self.wire_protocol.max_getblocks_count]:
            try:
                found.append(self.chain.get_block(bh))
            except KeyError:
                log.debug("unknown block requested", block_hash=encode_hex(bh))
        if found:
            log.debug("found", count=len(found))
            proto.send_blockbodies(*found)

    def on_receive_blockbodies(self, proto, bodies):
        log.debug('----------------------------------')
        log.debug("recv block bodies", count=len(bodies), remote_id=proto)
        if bodies:
            self.synchronizer.receive_blockbodies(proto, bodies)

    def on_receive_newblock(self, proto, block, chain_difficulty):
        log.debug('----------------------------------')
        log.debug("recv newblock", block=block, remote_id=proto)
        self.synchronizer.receive_newblock(proto, block, chain_difficulty)
示例#15
0
    def __init__(self, app):
        self.config = app.config
        sce = self.config['eth']
        if int(sce['pruning']) >= 0:
            self.db = RefcountDB(app.services.db)
            if "I am not pruning" in self.db.db:
                raise RuntimeError(
                    "The database in '{}' was initialized as non-pruning. "
                    "Can not enable pruning now.".format(
                        self.config['data_dir']))
            self.db.ttl = int(sce['pruning'])
            self.db.db.put("I am pruning", "1")
        else:
            self.db = app.services.db
            if "I am pruning" in self.db:
                raise RuntimeError(
                    "The database in '{}' was initialized as pruning. "
                    "Can not disable pruning now".format(
                        self.config['data_dir']))
            self.db.put("I am not pruning", "1")

        if 'network_id' in self.db:
            db_network_id = self.db.get(b'network_id')
            if db_network_id != to_string(sce['network_id']):
                raise RuntimeError(
                    "The database in '{}' was initialized with network id {} and can not be used "
                    "when connecting to network id {}. Please choose a different data directory."
                    .format(self.config['data_dir'], db_network_id,
                            sce['network_id']))

        else:
            self.db.put(b'network_id', to_string(sce['network_id']))
            self.db.commit()

        assert self.db is not None

        super(ChainService, self).__init__(app)
        log.info('initializing chain')
        coinbase = app.services.accounts.coinbase
        env = Env(self.db, sce['block'])

        genesis_data = sce.get('genesis_data', {})
        if not genesis_data:
            genesis_data = mk_genesis_data(env)
        self.chain = Chain(env=env,
                           genesis=genesis_data,
                           coinbase=coinbase,
                           new_head_cb=self._on_new_head)
        header = self.chain.state.prev_headers[0]
        log.info('chain at', number=header.number)
        if 'genesis_hash' in sce:
            assert sce['genesis_hash'] == self.chain.genesis.hex_hash, \
                "Genesis hash mismatch.\n  Expected: %s\n  Got: %s" % (
                    sce['genesis_hash'], self.chain.genesis.hex_hash)

        self.dao_challenges = dict()
        self.synchronizer = Synchronizer(self, force_sync=None)

        self.block_queue = Queue(maxsize=self.block_queue_size)
        # When the transaction_queue is modified, we must set
        # self._head_candidate_needs_updating to True in order to force the
        # head candidate to be updated.
        self.transaction_queue = TransactionQueue()
        self._head_candidate_needs_updating = True
        # Initialize a new head candidate.
        _ = self.head_candidate
        self.min_gasprice = 20 * 10**9  # TODO: better be an option to validator service?
        self.add_blocks_lock = False
        self.add_transaction_lock = gevent.lock.Semaphore()
        self.broadcast_filter = DuplicatesFilter()
        self.on_new_head_cbs = []
        self.newblock_processing_times = deque(maxlen=1000)
        gevent.spawn_later(self.process_time_queue_period,
                           self.process_time_queue)
示例#16
0
class Validator(object):
    def __init__(self, key, genesis, network, valcode_addr=None, mining=False):
        self.key = key
        self.coinbase = utils.privtoaddr(self.key)
        self.chain = chain.Chain(genesis=genesis,
                                 reset_genesis=True,
                                 coinbase=self.coinbase,
                                 new_head_cb=self._on_new_head)
        self.mining = mining
        self.nonce = self.chain.state.get_nonce(self.coinbase)
        self.valcode_tx = None
        self.deposit_tx = None
        self.valcode_addr = valcode_addr
        self.prepares = dict()
        self.prev_prepare_epoch = 0
        self.prev_commit_epoch = 0
        self.epoch_length = self.chain.env.config['EPOCH_LENGTH']
        # When the transaction_queue is modified, we must set
        # self._head_candidate_needs_updating to True in order to force the
        # head candidate to be updated.
        self.transaction_queue = TransactionQueue()
        self._head_candidate_needs_updating = True
        # Add validator to the network
        self.network = network
        self.network.join(self)

    @property
    def head_candidate(self):
        if self._head_candidate_needs_updating:
            self._head_candidate_needs_updating = False
            # Make a copy of self.transaction_queue because
            # make_head_candidate modifies it.
            txqueue = copy.deepcopy(self.transaction_queue)
            self._head_candidate, self._head_candidate_state = make_head_candidate(
                self.chain,
                txqueue=txqueue,
                timestamp=self.chain.state.timestamp + 14)
        return self._head_candidate

    def _on_new_head(self, block):
        self.transaction_queue = self.transaction_queue.diff(
            block.transactions)
        self._head_candidate_needs_updating = True

    def epoch_blockhash(self, state, epoch):
        if epoch == 0:
            return b'\x00' * 32
        return state.prev_headers[epoch * self.epoch_length * -1 - 1].hash

    def get_recommended_casper_msg_contents(self, casper, validator_index):
        return \
            casper.get_current_epoch(), casper.get_recommended_ancestry_hash(), \
            casper.get_recommended_source_epoch(), casper.get_recommended_source_ancestry_hash(), \
            casper.get_validators__prev_commit_epoch(validator_index)

    def get_validator_index(self, state):
        t = tester.State(state.ephemeral_clone())
        t.state.gas_limit = 9999999999
        casper = tester.ABIContract(t, casper_utils.casper_abi,
                                    self.chain.casper_address)
        if self.valcode_addr is None:
            raise Exception('Valcode address not set')
        try:
            return casper.get_validator_indexes(self.coinbase)
        except tester.TransactionFailed:
            return None

    # Check the state, and determine if we should commit or prepare
    def on_receive(self, msg):
        if isinstance(msg, block.Block):
            self.accept_block(msg)
        elif isinstance(msg, transactions.Transaction):
            self.accept_transaction(msg)

    def accept_block(self, block):
        self.chain.process_time_queue()
        if not self.chain.add_block(block):
            return
        # Verify this block is a part of our head chain
        if block != self.chain.get_block_by_number(block.header.number):
            return
        # Verify this block is far enough in our epoch
        if block.header.number % self.epoch_length < self.epoch_length // 3:
            return
        # Block is part of the head chain, so attempt to prepare & commit:
        # Create a poststate based on the blockhash we recieved
        post_state = self.chain.mk_poststate_of_blockhash(block.hash)
        post_state.gas_limit = 9999999999999
        # Generate prepare & commit messages and broadcast if possible
        prepare_msg = self.generate_prepare_message(post_state)
        if prepare_msg:
            prepare_tx = self.mk_prepare_tx(prepare_msg)
            self.broadcast_transaction(prepare_tx)
        commit_msg = self.generate_commit_message(post_state)
        if commit_msg:
            commit_tx = self.mk_commit_tx(commit_msg)
            self.broadcast_transaction(commit_tx)

    def accept_transaction(self, tx):
        self.transaction_queue.add_transaction(tx)
        if self.mining:
            log.info('Mining tx: {}'.format(tx))
            self.mine_and_broadcast_blocks(1)

    def broadcast_transaction(self, tx):
        log.info('Broadcasting transaction {} from validator {}'.format(
            str(tx), utils.encode_hex(self.valcode_addr)))
        self.network.broadcast(tx)

    def broadcast_newblock(self, block):
        log.info('Broadcasting block with hash: %s and txs: %s' %
                 (utils.encode_hex(block.hash), str(block.transactions)))
        self.network.broadcast(block)

    def generate_prepare_message(self, state):
        epoch = state.block_number // self.epoch_length
        # NO_DBL_PREPARE: Don't prepare if we have already
        if epoch in self.prepares:
            return None
        # Create a Casper contract which we can use to get related values
        casper = tester.ABIContract(tester.State(state),
                                    casper_utils.casper_abi,
                                    self.chain.casper_address)
        # Get the ancestry hash and source ancestry hash
        validator_index = self.get_validator_index(state)
        _e, _a, _se, _sa, _pce = self.get_recommended_casper_msg_contents(
            casper, validator_index)
        # PREPARE_COMMIT_CONSISTENCY
        if _se < self.prev_commit_epoch and self.prev_commit_epoch < epoch:
            return None
        prepare_msg = casper_utils.mk_prepare(validator_index, _e, _a, _se,
                                              _sa, self.key)
        try:  # Attempt to submit the prepare, to make sure that it is justified
            casper.prepare(prepare_msg)
        except tester.TransactionFailed:
            log.info(
                'Prepare failed! Validator {} - hash justified {} - validator start {} - valcode addr {}'
                .format(
                    self.get_validator_index(state),
                    casper.get_consensus_messages__ancestry_hash_justified(
                        epoch, _a),
                    casper.get_validators__dynasty_start(validator_index),
                    utils.encode_hex(self.valcode_addr)))
            return None
        # Save the prepare message we generated
        self.prepares[epoch] = prepare_msg
        # Save the highest source epoch we have referenced in our prepare's source epoch
        if epoch > self.prev_prepare_epoch:
            self.prev_prepare_epoch = epoch
        log.info(
            'Prepare submitted: validator %d - epoch %d - prev_commit_epoch %d - hash %s'
            % (self.get_validator_index(state), epoch, self.prev_commit_epoch,
               utils.encode_hex(self.epoch_blockhash(state, epoch))))
        return prepare_msg

    def generate_commit_message(self, state):
        epoch = state.block_number // self.epoch_length
        # PREPARE_COMMIT_CONSISTENCY
        if self.prev_prepare_epoch < self.prev_commit_epoch and self.prev_commit_epoch < epoch:
            return None
        # Create a Casper contract which we can use to get related values
        casper = tester.ABIContract(tester.State(state),
                                    casper_utils.casper_abi,
                                    self.chain.casper_address)
        validator_index = self.get_validator_index(state)
        _e, _a, _se, _sa, _pce = self.get_recommended_casper_msg_contents(
            casper, validator_index)
        # Make the commit message
        commit_msg = casper_utils.mk_commit(validator_index, _e, _a, _pce,
                                            self.key)
        try:  # Attempt to submit the commit, to make sure that it doesn't doesn't violate DBL_PREPARE & it is justified
            casper.commit(commit_msg)
        except tester.TransactionFailed:
            log.info(
                'Commit failed! Validator {} - blockhash {} - valcode addr {}'.
                format(self.get_validator_index(state),
                       self.epoch_blockhash(state, epoch),
                       utils.encode_hex(self.valcode_addr)))
            return None
        # Save the commit as now our last commit epoch
        log.info(
            'Commit submitted: validator %d - epoch %d - prev_commit_epoch %d - hash %s'
            % (self.get_validator_index(state), epoch, self.prev_commit_epoch,
               utils.encode_hex(self.epoch_blockhash(state, epoch))))
        self.prev_commit_epoch = epoch
        return commit_msg

    def mine_and_broadcast_blocks(self, number_of_blocks=1):
        for i in range(number_of_blocks):
            self._head_candidate_needs_updating = True
            block = Miner(self.head_candidate).mine(rounds=100, start_nonce=0)
            self.transaction_queue = self.transaction_queue.diff(
                block.transactions)
            self.broadcast_newblock(block)

    def broadcast_deposit(self):
        if not self.valcode_tx or not self.deposit_tx:
            # Generate transactions
            valcode_tx = self.mk_validation_code_tx()
            valcode_addr = utils.mk_contract_address(self.coinbase,
                                                     self.nonce - 1)
            deposit_tx = self.mk_deposit_tx(3 * 10**18, valcode_addr)
            # Verify the transactions pass
            temp_state = self.chain.state.ephemeral_clone()
            valcode_success, o1 = apply_transaction(temp_state, valcode_tx)
            deposit_success, o2 = apply_transaction(temp_state, deposit_tx)
            if not (valcode_success and deposit_success):
                self.nonce = self.chain.state.get_nonce(self.coinbase)
                raise Exception('Valcode tx or deposit tx failed')
            self.valcode_tx = valcode_tx
            log.info('Valcode Tx generated: {}'.format(str(valcode_tx)))
            self.valcode_addr = valcode_addr
            self.deposit_tx = deposit_tx
            log.info('Deposit Tx generated: {}'.format(str(deposit_tx)))
        self.broadcast_transaction(self.valcode_tx)
        self.broadcast_transaction(self.deposit_tx)

    def broadcast_logout(self, login_logout_flag):
        epoch = self.chain.state.block_number // self.epoch_length
        # Generage the message
        logout_msg = casper_utils.mk_logout(
            self.get_validator_index(self.chain.state), epoch, self.key)
        # Generate transactions
        logout_tx = self.mk_logout(logout_msg)
        # Verify the transactions pass
        temp_state = self.chain.state.ephemeral_clone()
        logout_success, o1 = apply_transaction(temp_state, logout_tx)
        if not logout_success:
            self.nonce = self.chain.state.get_nonce(self.coinbase)
            raise Exception('Valcode tx or deposit tx failed')
        log.info('Login/logout Tx generated: {}'.format(str(logout_tx)))
        self.broadcast_transaction(logout_tx)

    def mk_transaction(self,
                       to=b'\x00' * 20,
                       value=0,
                       data=b'',
                       gasprice=tester.GASPRICE,
                       startgas=tester.STARTGAS):
        tx = transactions.Transaction(self.nonce, gasprice, startgas, to,
                                      value, data).sign(self.key)
        self.nonce += 1
        return tx

    def mk_validation_code_tx(self):
        valcode_tx = self.mk_transaction(
            '', 0, casper_utils.mk_validation_code(self.coinbase))
        return valcode_tx

    def mk_deposit_tx(self, value, valcode_addr):
        casper_ct = abi.ContractTranslator(casper_utils.casper_abi)
        deposit_func = casper_ct.encode('deposit',
                                        [valcode_addr, self.coinbase])
        deposit_tx = self.mk_transaction(self.chain.casper_address, value,
                                         deposit_func)
        return deposit_tx

    def mk_logout(self, login_logout_msg):
        casper_ct = abi.ContractTranslator(casper_utils.casper_abi)
        logout_func = casper_ct.encode('logout', [login_logout_msg])
        logout_tx = self.mk_transaction(self.chain.casper_address,
                                        data=logout_func)
        return logout_tx

    def mk_prepare_tx(self, prepare_msg):
        casper_ct = abi.ContractTranslator(casper_utils.casper_abi)
        prepare_func = casper_ct.encode('prepare', [prepare_msg])
        prepare_tx = self.mk_transaction(to=self.chain.casper_address,
                                         value=0,
                                         data=prepare_func)
        return prepare_tx

    def mk_commit_tx(self, commit_msg):
        casper_ct = abi.ContractTranslator(casper_utils.casper_abi)
        commit_func = casper_ct.encode('commit', [commit_msg])
        commit_tx = self.mk_transaction(self.chain.casper_address, 0,
                                        commit_func)
        return commit_tx
示例#17
0
def test_apply_collation_wrong_root():
    """Test apply_collation with wrong roots in header
    test verify_execution_results
    """
    shard_id = 1
    t = chain(shard_id)

    # test 1 - arrange
    state = t.chain.shards[shard_id].state
    txqueue = TransactionQueue()
    tx1 = t.generate_shard_tx(shard_id, tester.k2, tester.a4,
                              int(0.03 * utils.denoms.ether))
    txqueue.add_transaction(tx1)
    # post_state_root
    collation = t.generate_collation(shard_id=1,
                                     coinbase=tester.a1,
                                     key=tester.k1,
                                     txqueue=txqueue)
    period_start_prevblock = t.chain.get_block(
        collation.header.period_start_prevhash)
    # Set wrong root
    collation.header.post_state_root = trie.BLANK_ROOT
    with pytest.raises(ValueError):
        collator.apply_collation(state, collation, period_start_prevblock)

    # test 2 - arrange
    state = t.chain.shards[shard_id].state
    txqueue = TransactionQueue()
    tx1 = t.generate_shard_tx(shard_id, tester.k2, tester.a4,
                              int(0.03 * utils.denoms.ether))
    txqueue.add_transaction(tx1)
    # receipts_root
    collation = t.generate_collation(shard_id=1,
                                     coinbase=tester.a1,
                                     key=tester.k1,
                                     txqueue=txqueue)
    period_start_prevblock = t.chain.get_block(
        collation.header.period_start_prevhash)
    # Set wrong root
    collation.header.receipts_root = trie.BLANK_ROOT
    with pytest.raises(ValueError):
        collator.apply_collation(state, collation, period_start_prevblock)

    # test 3 - arrange
    state = t.chain.shards[shard_id].state
    txqueue = TransactionQueue()
    tx1 = t.generate_shard_tx(shard_id, tester.k2, tester.a4,
                              int(0.03 * utils.denoms.ether))
    txqueue.add_transaction(tx1)
    # receipts_root
    collation = t.generate_collation(shard_id=1,
                                     coinbase=tester.a1,
                                     key=tester.k1,
                                     txqueue=txqueue)
    period_start_prevblock = t.chain.get_block(
        collation.header.period_start_prevhash)
    # Set wrong root
    collation.header.tx_list_root = trie.BLANK_ROOT
    with pytest.raises(ValueError):
        collator.apply_collation(state, collation, period_start_prevblock)
示例#18
0
def make_block(chain, key, randao, vchash, skips):
    h, _ = make_head_candidate(chain, TransactionQueue(), timestamp=get_timestamp(chain, skips))
    return sign_block(h, key, randao.get_parent(call_casper(chain.state, 'getRandao', [vchash])), vchash, skips)
示例#19
0
文件: casper.py 项目: yep/research
class Validator():
    def __init__(self, genesis, key, network, env, time_offset=5):
        # Create a chain object
        self.chain = Chain(genesis, env=env)
        # Create a transaction queue
        self.txqueue = TransactionQueue()
        # Use the validator's time as the chain's time
        self.chain.time = lambda: self.get_timestamp()
        # My private key
        self.key = key
        # My address
        self.address = privtoaddr(key)
        # My randao
        self.randao = RandaoManager(sha3(self.key))
        # Pointer to the test p2p network
        self.network = network
        # Record of objects already received and processed
        self.received_objects = {}
        # The minimum eligible timestamp given a particular number of skips
        self.next_skip_count = 0
        self.next_skip_timestamp = 0
        # Is this validator active?
        self.active = False
        # Code that verifies signatures from this validator
        self.validation_code = generate_validation_code(privtoaddr(key))
        # Validation code hash
        self.vchash = sha3(self.validation_code)
        # Parents that this validator has already built a block on
        self.used_parents = {}
        # This validator's clock offset (for testing purposes)
        self.time_offset = random.randrange(time_offset) - (time_offset // 2)
        # Determine the epoch length
        self.epoch_length = self.call_casper('getEpochLength')
        # My minimum gas price
        self.mingasprice = 20 * 10**9
        # Give this validator a unique ID
        self.id = len(ids)
        ids.append(self.id)
        self.update_activity_status()
        self.cached_head = self.chain.head_hash

    def call_casper(self, fun, args=[]):
        return call_casper(self.chain.state, fun, args)

    def update_activity_status(self):
        start_epoch = self.call_casper('getStartEpoch', [self.vchash])
        now_epoch = self.call_casper('getEpoch')
        end_epoch = self.call_casper('getEndEpoch', [self.vchash])
        if start_epoch <= now_epoch < end_epoch:
            self.active = True
            self.next_skip_count = 0
            self.next_skip_timestamp = get_timestamp(self.chain,
                                                     self.next_skip_count)
            print 'In current validator set'
        else:
            self.active = False

    def get_timestamp(self):
        return int(self.network.time * 0.01) + self.time_offset

    def on_receive(self, obj):
        if isinstance(obj, list):
            for _obj in obj:
                self.on_receive(_obj)
            return
        if obj.hash in self.received_objects:
            return
        if isinstance(obj, Block):
            print 'Receiving block', obj
            assert obj.hash not in self.chain
            block_success = self.chain.add_block(obj)
            self.network.broadcast(self, obj)
            self.network.broadcast(self, ChildRequest(obj.header.hash))
            self.update_head()
        elif isinstance(obj, Transaction):
            print 'Receiving transaction', obj
            if obj.gasprice >= self.mingasprice:
                self.txqueue.add_transaction(obj)
                print 'Added transaction, txqueue size %d' % len(
                    self.txqueue.txs)
                self.network.broadcast(self, obj)
            else:
                print 'Gasprice too low'
        self.received_objects[obj.hash] = True
        for x in self.chain.get_chain():
            assert x.hash in self.received_objects

    def tick(self):
        # Try to create a block
        # Conditions:
        # (i) you are an active validator,
        # (ii) you have not yet made a block with this parent
        if self.active and self.chain.head_hash not in self.used_parents:
            t = self.get_timestamp()
            # Is it early enough to create the block?
            if t >= self.next_skip_timestamp and (
                    not self.chain.head
                    or t > self.chain.head.header.timestamp):
                # Wrong validator; in this case, just wait for the next skip count
                if not check_skips(self.chain, self.vchash,
                                   self.next_skip_count):
                    self.next_skip_count += 1
                    self.next_skip_timestamp = get_timestamp(
                        self.chain, self.next_skip_count)
                    # print 'Incrementing proposed timestamp for block %d to %d' % \
                    #     (self.chain.head.header.number + 1 if self.chain.head else 0, self.next_skip_timestamp)
                    return
                self.used_parents[self.chain.head_hash] = True
                # Simulated 15% chance of validator failure to make a block
                if random.random() > 0.999:
                    print 'Simulating validator failure, block %d not created' % (
                        self.chain.head.header.number +
                        1 if self.chain.head else 0)
                    return
                # Make the block
                s1 = self.chain.state.trie.root_hash
                pre_dunkle_count = self.call_casper('getTotalDunklesIncluded')
                dunkle_txs = get_dunkle_candidates(self.chain,
                                                   self.chain.state)
                blk = make_head_candidate(self.chain, self.txqueue)
                randao = self.randao.get_parent(
                    self.call_casper('getRandao', [self.vchash]))
                blk = sign_block(blk, self.key, randao, self.vchash,
                                 self.next_skip_count)
                # Make sure it's valid
                global global_block_counter
                global_block_counter += 1
                for dtx in dunkle_txs:
                    assert dtx in blk.transactions, (dtx, blk.transactions)
                print 'made block with timestamp %d and %d dunkles' % (
                    blk.timestamp, len(dunkle_txs))
                s2 = self.chain.state.trie.root_hash
                assert s1 == s2
                assert blk.timestamp >= self.next_skip_timestamp
                assert self.chain.add_block(blk)
                self.update_head()
                post_dunkle_count = self.call_casper('getTotalDunklesIncluded')
                assert post_dunkle_count - pre_dunkle_count == len(dunkle_txs)
                self.received_objects[blk.hash] = True
                print 'Validator %d making block %d (%s)' % (
                    self.id, blk.header.number,
                    blk.header.hash[:8].encode('hex'))
                self.network.broadcast(self, blk)
        # Sometimes we received blocks too early or out of order;
        # run an occasional loop that processes these
        if random.random() < 0.02:
            self.chain.process_time_queue()
            self.chain.process_parent_queue()
            self.update_head()

    def update_head(self):
        if self.cached_head == self.chain.head_hash:
            return
        self.cached_head = self.chain.head_hash
        if self.chain.state.block_number % self.epoch_length == 0:
            self.update_activity_status()
        if self.active:
            self.next_skip_count = 0
            self.next_skip_timestamp = get_timestamp(self.chain,
                                                     self.next_skip_count)
        print 'Head changed: %s, will attempt creating a block at %d' % (
            self.chain.head_hash.encode('hex'), self.next_skip_timestamp)

    def withdraw(self, gasprice=20 * 10**9):
        sigdata = make_withdrawal_signature(self.key)
        txdata = casper_ct.encode('startWithdrawal', [self.vchash, sigdata])
        tx = Transaction(self.chain.state.get_nonce(self.address), gasprice,
                         650000, self.chain.config['CASPER_ADDR'], 0,
                         txdata).sign(self.key)
        self.txqueue.add_transaction(tx, force=True)
        self.network.broadcast(self, tx)
        print 'Withdrawing!'

    def deposit(self, gasprice=20 * 10**9):
        assert value * 10**18 >= self.chain.state.get_balance(
            self.address) + gasprice * 1000000
        tx = Transaction(
            self.chain.state.get_nonce(self.address) * 10**18, gasprice,
            1000000, casper_config['CASPER_ADDR'], value * 10**18,
            ct.encode('deposit', [self.validation_code,
                                  self.randao.get(9999)]))