def register_balanceproof(self, balance_proof): if not isinstance(balance_proof, BalanceProofState): raise ValueError('balance_proof must be a BalanceProof instance') unclaimed_locksroot = self.merkleroot_for_unclaimed() if balance_proof.locksroot != unclaimed_locksroot: raise InvalidLocksRoot(unclaimed_locksroot, balance_proof.locksroot) self.balance_proof = balance_proof
def register_direct_transfer(self, direct_transfer): if not isinstance(direct_transfer, DirectTransfer): raise ValueError('transfer must be a DirectTransfer') unclaimed_locksroot = self.merkleroot_for_unclaimed() if direct_transfer.locksroot != unclaimed_locksroot: raise InvalidLocksRoot(unclaimed_locksroot, direct_transfer.locksroot) self.transfer = direct_transfer self.hashlock_unlockedlocks = dict()
def register_direct_transfer(self, direct_transfer): """ Register a direct_transfer. Raises: InvalidLocksRoot: If the merkleroot of `direct_transfer` does not match the current value. """ balance_proof = direct_transfer.to_balanceproof() if balance_proof.locksroot != merkleroot(self.merkletree): raise InvalidLocksRoot(merkleroot(self.merkletree), balance_proof.locksroot) self.balance_proof = balance_proof
def register_locked_transfer(self, locked_transfer): """ Register the latest known transfer. The sender needs to use this method before sending a locked transfer, otherwise the calculate locksroot of the transfer message will be invalid and the transfer will be rejected by the partner. Since the sender wants the transfer to be accepted by the receiver otherwise the transfer won't proceed and the sender won't receive their fee. The receiver needs to use this method to update the container with a _valid_ transfer, otherwise the locksroot will not contain the pending transfer. The receiver needs to ensure that the merkle root has the hashlock included, otherwise it won't be able to claim it. Args: transfer (LockedTransfer): The transfer to be added. Raises: InvalidLocksRoot: If the merkleroot of `locked_transfer` does not match with the expected value. ValueError: If the transfer contains a lock that was registered previously. """ balance_proof = locked_transfer.to_balanceproof() lock = locked_transfer.lock lockhashed = sha3(lock.as_bytes) if self.is_known(lock.hashlock): raise ValueError('hashlock is already registered') leaves = list(self.merkletree.layers[LEAVES]) leaves.append(lockhashed) newtree = MerkleTreeState(compute_layers(leaves)) locksroot = merkleroot(newtree) if balance_proof.locksroot != locksroot: raise InvalidLocksRoot(locksroot, balance_proof.locksroot) self.hashlocks_to_pendinglocks[lock.hashlock] = PendingLock( lock, lockhashed) self.balance_proof = balance_proof self.merkletree = newtree
def register_balanceproof_with_lock(self, balance_proof, lock): lockhashed = sha3(lock.as_bytes) if not isinstance(balance_proof, BalanceProofState): raise ValueError('balance_proof must be a BalanceProof instance') if self.is_known(lock.hashlock): raise ValueError('hashlock is already registered') leafs = self.unclaimed_merkletree() leafs.append(lockhashed) new_locksroot = Merkletree(leafs).merkleroot if balance_proof.locksroot != new_locksroot: raise InvalidLocksRoot(new_locksroot, balance_proof.locksroot) self.hashlocks_to_pendinglocks[lock.hashlock] = PendingLock( lock, lockhashed) self.balance_proof = balance_proof
def register_balanceproof_without_lock(self, balance_proof, lock): lockhashed = sha3(lock.as_bytes) if not isinstance(balance_proof, BalanceProofState): raise ValueError('balance_proof must be a BalanceProof instance') if not self.is_known(lock.hashlock): raise ValueError('hashlock is not registered') leaves = self.unclaimed_merkletree() leaves.remove(lockhashed) new_locksroot = Merkletree(leaves).merkleroot if balance_proof.locksroot != new_locksroot: raise InvalidLocksRoot(new_locksroot, balance_proof.locksroot) if lock.hashlock in self.hashlocks_to_pendinglocks: del self.hashlocks_to_pendinglocks[lock.hashlock] else: del self.hashlocks_to_unclaimedlocks[lock.hashlock] self.balance_proof = balance_proof
def register_secretmessage(self, message_secret): balance_proof = message_secret.to_balanceproof() hashlock = sha3(message_secret.secret) pendinglock = self.hashlocks_to_pendinglocks.get(hashlock) if not pendinglock: pendinglock = self.hashlocks_to_unclaimedlocks[hashlock] lock = pendinglock.lock lockhashed = sha3(lock.as_bytes) if not isinstance(balance_proof, BalanceProofState): raise ValueError('balance_proof must be a BalanceProof instance') if not self.is_known(lock.hashlock): raise ValueError('hashlock is not registered') leaves = list(self.merkletree.layers[LEAVES]) leaves.remove(lockhashed) if leaves: layers = compute_layers(leaves) new_merkletree = MerkleTreeState(layers) new_locksroot = merkleroot(new_merkletree) else: new_merkletree = EMPTY_MERKLE_TREE new_locksroot = EMPTY_MERKLE_ROOT if balance_proof.locksroot != new_locksroot: raise InvalidLocksRoot(new_locksroot, balance_proof.locksroot) if lock.hashlock in self.hashlocks_to_pendinglocks: del self.hashlocks_to_pendinglocks[lock.hashlock] else: del self.hashlocks_to_unclaimedlocks[lock.hashlock] self.merkletree = new_merkletree self.balance_proof = balance_proof
def register_transfer_from_to(self, block_number, transfer, from_state, to_state): # noqa pylint: disable=too-many-branches,too-many-statements """ Validates and register a signed transfer, updating the channel's state accordingly. Note: The transfer must be registered before it is sent, not on acknowledgement. That is necessary for two reasons: - Guarantee that the transfer is valid. - Avoid sending a new transaction without funds. Raises: InsufficientBalance: If the transfer is negative or above the distributable amount. InvalidLocksRoot: If locksroot check fails. InvalidNonce: If the expected nonce does not match. ValueError: If there is an address mismatch (token or node address). """ if transfer.channel != self.channel_address: raise ValueError('Channel address mismatch') if transfer.sender != from_state.address: raise ValueError('Unsigned transfer') # nonce is changed only when a transfer is un/registered, if the test # fails either we are out of sync, a message out of order, or it's a # forged transfer is_invalid_nonce = (transfer.nonce < 1 or (from_state.nonce is not None and transfer.nonce != from_state.nonce + 1)) if is_invalid_nonce: # this may occur on normal operation if log.isEnabledFor(logging.INFO): log.info( 'INVALID NONCE', node=pex(self.our_address), from_=pex(transfer.sender), to=pex(to_state.address), expected_nonce=from_state.nonce, nonce=transfer.nonce, ) raise InvalidNonce(transfer) # if the locksroot is out-of-sync (because a transfer was created while # a Secret was in traffic) the balance _will_ be wrong, so first check # the locksroot and then the balance if isinstance(transfer, LockedTransfer): if from_state.is_known(transfer.lock.hashlock): # this may occur on normal operation if log.isEnabledFor(logging.INFO): lockhashes = list( from_state.hashlocks_to_unclaimedlocks.values()) lockhashes.extend( from_state.hashlocks_to_pendinglocks.values()) log.info( 'duplicated lock', node=pex(self.our_address), from_=pex(from_state.address), to=pex(to_state.address), hashlock=pex(transfer.lock.hashlock), lockhash=pex(sha3(transfer.lock.as_bytes)), lockhashes=lpex(lockhashes), received_locksroot=pex(transfer.locksroot), ) raise ValueError('hashlock is already registered') # As a receiver: Check that all locked transfers are registered in # the locksroot, if any hashlock is missing there is no way to # claim it while the channel is closing expected_locksroot = from_state.compute_merkleroot_with( transfer.lock) if expected_locksroot != transfer.locksroot: # this should not happen if log.isEnabledFor(logging.WARN): lockhashes = list( from_state.hashlocks_to_unclaimedlocks.values()) lockhashes.extend( from_state.hashlocks_to_pendinglocks.values()) log.warn( 'LOCKSROOT MISMATCH', node=pex(self.our_address), from_=pex(from_state.address), to=pex(to_state.address), lockhash=pex(sha3(transfer.lock.as_bytes)), lockhashes=lpex(lockhashes), expected_locksroot=pex(expected_locksroot), received_locksroot=pex(transfer.locksroot), ) raise InvalidLocksRoot(expected_locksroot, transfer.locksroot) # For mediators: This is registering the *mediator* paying # transfer. The expiration of the lock must be `reveal_timeout` # blocks smaller than the *received* paying transfer. This cannot # be checked by the paying channel alone. # # For the initiators: As there is no backing transfer, the # expiration is arbitrary, using the channel settle_timeout as an # upper limit because the node receiving the transfer will use it # as an upper bound while mediating. # # For the receiver: A lock that expires after the settle period # just means there is more time to withdraw it. end_settle_period = self.get_settle_expiration(block_number) expires_after_settle = transfer.lock.expiration > end_settle_period is_sender = transfer.sender == self.our_address if is_sender and expires_after_settle: if log.isEnabledFor(logging.ERROR): log.error( 'Lock expires after the settlement period.', node=pex(self.our_address), from_=pex(from_state.address), to=pex(to_state.address), lock_expiration=transfer.lock.expiration, current_block=block_number, end_settle_period=end_settle_period, ) raise ValueError('Lock expires after the settlement period.') # only check the balance if the locksroot matched if transfer.transferred_amount < from_state.transferred_amount: if log.isEnabledFor(logging.ERROR): log.error( 'NEGATIVE TRANSFER', node=pex(self.our_state.address), from_=pex(from_state.address), to=pex(to_state.address), transfer=transfer, ) raise ValueError('Negative transfer') amount = transfer.transferred_amount - from_state.transferred_amount distributable = from_state.distributable(to_state) if isinstance(transfer, DirectTransfer): if amount > distributable: raise InsufficientBalance(transfer) elif isinstance(transfer, LockedTransfer): if amount + transfer.lock.amount > distributable: raise InsufficientBalance(transfer) elif isinstance(transfer, Secret): hashlock = sha3(transfer.secret) lock = from_state.get_lock_by_hashlock(hashlock) transferred_amount = from_state.transferred_amount + lock.amount # transfer.transferred_amount could be larger than the previous # transferred_amount + lock.amount, that scenario is a bug of the # payer if transfer.transferred_amount != transferred_amount: raise ValueError( 'invalid transferred_amount, expected: {} got: {}'.format( transferred_amount, transfer.transferred_amount, )) # all checks need to be done before the internal state of the channel # is changed, otherwise if a check fails and the state was changed the # channel will be left trashed if isinstance(transfer, LockedTransfer): if log.isEnabledFor(logging.DEBUG): lockhashes = list( from_state.hashlocks_to_unclaimedlocks.values()) lockhashes.extend( from_state.hashlocks_to_pendinglocks.values()) log.debug( 'REGISTERED LOCK', node=pex(self.our_state.address), from_=pex(from_state.address), to=pex(to_state.address), currentlocksroot=pex(merkleroot(from_state.merkletree)), lockhashes=lpex(lockhashes), lock_amount=transfer.lock.amount, lock_expiration=transfer.lock.expiration, lock_hashlock=pex(transfer.lock.hashlock), lockhash=pex(sha3(transfer.lock.as_bytes)), ) from_state.register_locked_transfer(transfer) # register this channel as waiting for the secret (the secret can # be revealed through a message or a blockchain log) self.external_state.register_channel_for_hashlock( self, transfer.lock.hashlock, ) if isinstance(transfer, DirectTransfer): from_state.register_direct_transfer(transfer) elif isinstance(transfer, Secret): from_state.register_secretmessage(transfer) if log.isEnabledFor(logging.DEBUG): log.debug( 'REGISTERED TRANSFER', node=pex(self.our_state.address), from_=pex(from_state.address), to=pex(to_state.address), transfer=repr(transfer), transferred_amount=from_state.transferred_amount, nonce=from_state.nonce, current_locksroot=pex(merkleroot(from_state.merkletree)), )
def register_transfer_from_to(self, transfer, from_state, to_state): # noqa pylint: disable=too-many-branches,too-many-statements """ Validates and register a signed transfer, updating the channel's state accordingly. Note: The transfer must be registered before it is sent, not on acknowledgement. That is necessary for two reasons: - Guarantee that the transfer is valid. - Avoid sending a new transaction without funds. Raises: InsufficientBalance: If the transfer is negative or above the distributable amount. InvalidLocksRoot: If locksroot check fails. InvalidNonce: If the expected nonce does not match. ValueError: If there is an address mismatch (token or node address). """ if transfer.token != self.token_address: raise ValueError('Token address mismatch') if transfer.recipient != to_state.address: raise ValueError('Unknown recipient') if transfer.sender != from_state.address: raise ValueError('Unsigned transfer') # nonce is changed only when a transfer is un/registered, if the test # fails either we are out of sync, a message out of order, or it's a # forged transfer if transfer.nonce < 1 or transfer.nonce != from_state.nonce: if log.isEnabledFor(logging.WARN): log.warn( 'Received out of order transfer from %s. Expected ' 'nonce: %s but got nonce: %s', pex(transfer.sender), from_state.nonce, transfer.nonce, ) raise InvalidNonce(transfer) # if the locksroot is out-of-sync (because a transfer was created while # a Secret was in traffic) the balance _will_ be wrong, so first check # the locksroot and then the balance if isinstance(transfer, LockedTransfer): if to_state.balance_proof.is_pending(transfer.lock.hashlock): raise ValueError('hashlock is already registered') # As a receiver: Check that all locked transfers are registered in # the locksroot, if any hashlock is missing there is no way to # claim it while the channel is closing expected_locksroot = to_state.compute_merkleroot_with( transfer.lock) if expected_locksroot != transfer.locksroot: if log.isEnabledFor(logging.ERROR): log.error( 'LOCKSROOT MISMATCH node:%s %s > %s lockhash:%s lockhashes:%s', pex(self.our_state.address), pex(from_state.address), pex(to_state.address), pex(sha3(transfer.lock.as_bytes)), lpex(to_state.balance_proof.unclaimed_merkletree()), expected_locksroot=pex(expected_locksroot), received_locksroot=pex(transfer.locksroot), ) raise InvalidLocksRoot(expected_locksroot, transfer.locksroot) # If the lock expires after the settle_period a secret could be # revealed after the channel is settled and we won't be able to # claim the token. end_settle_period = self.block_number + self.settle_timeout expires_after_settle = transfer.lock.expiration > end_settle_period if expires_after_settle: log.error( 'Lock expires after the settlement period.', lock_expiration=transfer.lock.expiration, current_block=self.block_number, settle_timeout=self.settle_timeout, ) raise ValueError('Lock expires after the settlement period.') # If the lock expires within the unsafe_period we cannot accept the # transfer, since there is not enough time to properly settle # on-chain. end_unsafe_period = self.block_number + self.reveal_timeout expires_unsafe = transfer.lock.expiration < end_unsafe_period if expires_unsafe: log.error( 'Lock expires within the unsafe_period.', lock_expiration=transfer.lock.expiration, current_block=self.block_number, reveal_timeout=self.reveal_timeout, ) raise ValueError('Lock expires within the unsafe_period.') # only check the balance if the locksroot matched if transfer.transferred_amount < from_state.transferred_amount: if log.isEnabledFor(logging.ERROR): log.error( 'NEGATIVE TRANSFER node:%s %s > %s %s', pex(self.our_state.address), pex(from_state.address), pex(to_state.address), transfer, ) raise ValueError('Negative transfer') amount = transfer.transferred_amount - from_state.transferred_amount distributable = from_state.distributable(to_state) if amount > distributable: raise InsufficientBalance(transfer) if isinstance(transfer, LockedTransfer): if amount + transfer.lock.amount > distributable: raise InsufficientBalance(transfer) # all checks need to be done before the internal state of the channel # is changed, otherwise if a check fails and the state was changed the # channel will be left trashed if isinstance(transfer, LockedTransfer): if log.isEnabledFor(logging.DEBUG): log.debug( 'REGISTERED LOCK node:%s %s > %s currentlocksroot:%s lockhashes:%s', pex(self.our_state.address), pex(from_state.address), pex(to_state.address), pex(to_state.balance_proof.merkleroot_for_unclaimed()), lpex(to_state.balance_proof.unclaimed_merkletree()), lock_amount=transfer.lock.amount, lock_expiration=transfer.lock.expiration, lock_hashlock=pex(transfer.lock.hashlock), lockhash=pex(sha3(transfer.lock.as_bytes)), ) to_state.register_locked_transfer(transfer) # register this channel as waiting for the secret (the secret can # be revealed through a message or a blockchain log) self.external_state.register_channel_for_hashlock( self, transfer.lock.hashlock, ) if isinstance(transfer, DirectTransfer): to_state.register_direct_transfer(transfer) from_state.transferred_amount = transfer.transferred_amount from_state.nonce += 1 if log.isEnabledFor(logging.DEBUG): log.debug( 'REGISTERED TRANSFER node:%s %s > %s ' 'transfer:%s transferred_amount:%s nonce:%s ' 'current_locksroot:%s', pex(self.our_state.address), pex(from_state.address), pex(to_state.address), repr(transfer), from_state.transferred_amount, from_state.nonce, pex(to_state.balance_proof.merkleroot_for_unclaimed()), )