def __getitem__(self, hash_and_pc): res = self.txos.get_by_hash(_hash(hash_and_pc)) if not res: raise KeyError(hash_and_pc) utxo = IOput() utxo.deserialize(res) return utxo
def remove(self, n): self.commitments.remove(n) ser_removed_outputs = self.txos.remove(n) removed_outputs = [] for _ser in ser_removed_outputs: utxo = IOput() utxo.deserialize(_ser) removed_outputs.append(utxo) return removed_outputs
def remove(self, n, wtx): self.commitments.remove(n, wtx=wtx) ser_removed_outputs = self.txos.remove(n, wtx=wtx) removed_outputs = [] for _ser in ser_removed_outputs: utxo = IOput() utxo.deserialize_with_context(_ser) removed_outputs.append(utxo) return removed_outputs
def find(self, hash_and_pc): ''' In contrast with __getitem__ find will try to find even spent outputs for other (syncing) nodes. ''' res = self.txos.find_by_hash(_hash(hash_and_pc)) if not res: raise KeyError(hash_and_pc) utxo = IOput() utxo.deserialize(res) return utxo
def generate(self, change_address=None): if self.coinbase: raise Exception( "generate() can be used only for common transaction, to create block transaction as miner use compose_block_transaction" ) if not len(self.inputs): raise Exception("Tx should have at least one input") if not len(self._destinations): raise Exception("Tx should have at least one destination") for ioput in self.inputs: if not self.key_manager: raise Exception( "Trying to generate tx which spends unknown input (KeyManager is None)" ) if not ioput.detect_value(self.key_manager): raise Exception( "Trying to generate tx which spends unknown input") in_value = sum([ioput.value for ioput in self.inputs]) out_value = sum([destination[1] for destination in self._destinations]) relay_fee = self.calc_relay_fee() # +1 for destination is for change address self.fee = relay_fee + self.calc_new_outputs_fee( len(self.inputs), len(self._destinations) + 1) remainder = in_value - out_value - self.fee if remainder < 0: raise Exception("Not enough money in inputs to cover outputs") # TODO We need logic here to cover too low remainders (less than new output fee) change_address = change_address if change_address else self.key_manager.new_address( ) # TODO Check: for first glance, we cannot get here if key_manager is None. self._destinations.append((change_address, remainder)) privkey_sum = 0 out_blinding_key_sum = None for out_index in range(len(self._destinations) - 1): address, value = self._destinations[out_index] output = IOput() output.fill(address, value, generator=default_generator_ser) self.outputs.append(output) out_blinding_key_sum = out_blinding_key_sum + output.blinding_key if out_blinding_key_sum else output.blinding_key # privkey for the last one output isn't arbitrary address, value = self._destinations[-1] in_blinding_key_sum = None for _input in self.inputs: in_blinding_key_sum = in_blinding_key_sum + _input.blinding_key if in_blinding_key_sum else _input.blinding_key in_blinding_key_sum += self.key_manager.priv_by_pub( _input.address.pubkey ) # we can't get here if key_manager is None output = IOput() output.fill( address, value, blinding_key=in_blinding_key_sum - out_blinding_key_sum, relay_fee=relay_fee, generator=default_generator_ser ) #TODO relay fee should be distributed uniformly, privacy leak self.outputs.append(output) [output.generate() for output in self.outputs] self.sort_ioputs() self.verify()
def rollback(self, pruned_inputs, num_of_added_outputs, prev_state): for r_i in pruned_inputs: if self.storage_space.utxo_index: #r_i[0][2] is serialized txo (0 is txo, 2 is serialized object) utxo = IOput() utxo.deserialize(r_i[0][2]) self.storage_space.utxo_index.add_utxo(utxo) self.confirmed.unspend(r_i) outputs_for_mempool = self.confirmed.remove(num_of_added_outputs) for _o in outputs_for_mempool: self.mempool[_o.serialized_index] = _o self.storage_space.utxo_index.remove_utxo(_o) self.confirmed.set_state(prev_state)
def process_new_txos(message): try: serialized_utxos = bytes(message["txos"]) txos_hashes = bytes(message["txos_hashes"]) num = message["num"] #txos_hashes = [txos_hashes[i*65:(i+1)*65)] for i in range(0,num)] txos_lengths = message["txos_lengths"] #TODO we should use txos_hashes and txos_lengths to proccess only unknown txos for i in range(num): utxo = IOput() serialized_utxos = utxo.deserialize_raw(serialized_utxos) storage_space.txos_storage.mempool[utxo.serialized_index] = utxo storage_space.blockchain.update(reason="downloaded new txos") except Exception as e: raise e #XXX "DoS messages should be returned"
def give_block_template(self): if not self.key_manager: raise Exception("Key manager is not set") transaction_fees = self.give_tx().relay_fee if self.give_tx() else 0 value = next_reward( self.storage_space.blockchain.current_tip, self.storage_space.headers_storage) + transaction_fees coinbase = IOput() coinbase.fill( self.key_manager.new_address(), value, relay_fee=0, coinbase=True, lock_height=self.storage_space.blockchain.current_height + 1 + coinbase_maturity) coinbase.generate() self.storage_space.txos_storage.mempool[ coinbase.serialized_index] = coinbase tx = Transaction(txos_storage=self.storage_space.txos_storage, key_manager=self.key_manager) tx.add_coinbase(coinbase) tx.compose_block_transaction() block = generate_block_template(tx, self.storage_space) self.add_block_template(block) return block
def test_ioput_indexes(): _input1 = IOput() _input1.fill(adr1, 100) _input1.generate(exp=2, concealed_bits=3) assert _input1.index_len == 32 + 33 assert len(_input1.serialized_index) == 32 + 33 assert len(_input1.commitment_index) == 32 + 33 print("ioput_indexes len OK")
def get(self, hash_and_pc, rtx): res = self.txos.get_by_hash(sha256(hash_and_pc), rtx=rtx) if not res: raise KeyError(hash_and_pc) utxo = IOput() utxo.deserialize_with_context(res) utxo.set_verified_and_correct() #Trust saved outputs return utxo
def process_new_txos(message, wtx, core): try: serialized_utxos = bytes(message["txos"]) txos_hashes = bytes(message["txos_hashes"]) num = message["num"] txos_lengths = message["txos_lengths"] txos_hashes = [txos_hashes[i * 65:(i + 1) * 65] for i in range(0, num)] txos_lengths = [ int.from_bytes(txos_lengths[i * 2:(i + 1) * 2], "big") for i in range(0, num) ] for i in range(num): txo_len, txo_hash = txos_lengths[i], txos_hashes[i] if txo_hash in core.storage_space.txos_storage.mempool: serialized_utxos = serialized_utxos[txo_len:] continue utxo = IOput() serialized_utxos = utxo.deserialize_raw(serialized_utxos) core.storage_space.txos_storage.mempool[ utxo.serialized_index] = utxo core.storage_space.blockchain.update(wtx=wtx, reason="downloaded new txos") except Exception as e: raise DOSException() #TODO add info
def find(self, hash_and_pc, rtx): ''' In contrast with __getitem__ find will try to find even spent outputs for other (syncing) nodes. ''' res = self.txos.find_by_hash(sha256(hash_and_pc), rtx=rtx) if not res: raise KeyError(hash_and_pc) utxo = IOput() utxo.deserialize_with_context(res) utxo.set_verified_and_correct() #Trust saved outputs return utxo
def ioput_encrypted_messsage(): _input1 = IOput() _input1.fill(adr1, 100) _input1.generate(exp=2, concealed_bits=3) _input2 = IOput(binary_object=_input1.serialize()) _input2.verify() _input2.detect_value({'priv_by_pub': {adr1.serialized_pubkey: keys[0]}}) assert _input1.value == _input2.value assert _input1.blinding_key.serialize() == _input2.blinding_key.serialize() print("ioput_encrypted_message OK")
def deserialize(self, serialized_tx, rtx, skip_verification=False): self.serialized = None if len(serialized_tx) < 2: raise Exception( "Serialized transaction doesn't contain enough bytes for inputs array length" ) inputs_len_buffer, serialized_tx = serialized_tx[:2], serialized_tx[2:] (inputs_len, ) = struct.unpack("> H", inputs_len_buffer) for _input_index in range(inputs_len): if len(serialized_tx) < 2: raise Exception( "Serialized transaction doesn't contain enough bytes for input %s length" % _input_index) input_len_buffer, serialized_tx = serialized_tx[:2], serialized_tx[ 2:] (input_len, ) = struct.unpack("> H", input_len_buffer) if len(serialized_tx) < input_len: raise Exception( "Serialized transaction doesn't contain enough bytes for input %s" % _input_index) input_index_buffer, serialized_tx = serialized_tx[: input_len], serialized_tx[ input_len:] if not skip_verification: if (not self.txos_storage.confirmed.has(input_index_buffer, rtx=rtx)): raise Exception("Unknown input index") self.inputs.append( self.txos_storage.confirmed.get(input_index_buffer, rtx=rtx)) else: self.inputs.append(input_index_buffer) if len(serialized_tx) < 2: raise Exception( "Serialized transaction doesn't contain enough bytes for outputs array length" ) outputs_len_buffer, serialized_tx = serialized_tx[:2], serialized_tx[ 2:] (outputs_len, ) = struct.unpack("> H", outputs_len_buffer) for _output_index in range(outputs_len): if len(serialized_tx) < 2: raise Exception( "Serialized transaction doesn't contain enough bytes for output %s length" % _output_index) output_len_buffer, serialized_tx = serialized_tx[: 2], serialized_tx[ 2:] (output_len, ) = struct.unpack("> H", output_len_buffer) if len(serialized_tx) < output_len: raise Exception( "Serialized transaction doesn't contain enough bytes for output %s" % _output_index) output_buffer, serialized_tx = serialized_tx[: output_len], serialized_tx[ output_len:] self.outputs.append(IOput(binary_object=output_buffer)) if len(serialized_tx) < 2: raise Exception( "Serialized transaction doesn't contain enough bytes for additional excesses array length" ) aes_len_buffer, serialized_tx = serialized_tx[:2], serialized_tx[2:] (aes_len, ) = struct.unpack("> H", aes_len_buffer) for _ae in range(aes_len): if len(serialized_tx) < 2: raise Exception( "Serialized transaction doesn't contain enough bytes for additional excess %s length" % _ae) ae_len_buffer, serialized_tx = serialized_tx[:2], serialized_tx[2:] (ae_len, ) = struct.unpack("> H", ae_len_buffer) if len(serialized_tx) < ae_len: raise Exception( "Serialized transaction doesn't contain enough bytes for additional excess %s" % _ae) ae_buffer, serialized_tx = serialized_tx[:ae_len], serialized_tx[ ae_len:] e = Excess.from_serialized(ae_buffer) self.additional_excesses.append(e) for _ue in range(inputs_len): if len(serialized_tx) < 2: raise Exception( "Serialized transaction doesn't contain enough bytes for updated excess %s length" % _ue) ue_len_buffer, serialized_tx = serialized_tx[:2], serialized_tx[2:] (ue_len, ) = struct.unpack("> H", ue_len_buffer) if len(serialized_tx) < ue_len: raise Exception( "Serialized transaction doesn't contain enough bytes for updated excess %s" % _ue) ue_buffer, serialized_tx = serialized_tx[:ue_len], serialized_tx[ ue_len:] e = Excess.from_serialized(ue_buffer) #Depending on skif_verification self.inputs either contains inputs or serialized_indexes only if isinstance(self.inputs[_ue], bytes): self.updated_excesses[self.inputs[_ue]] = e elif isinstance(self.inputs[_ue], IOput): self.updated_excesses[self.inputs[_ue].serialized_index] = e if len(serialized_tx) < 32: raise Exception( "Serialized transaction doesn't contain enough bytes for mixer_offset" ) self.mixer_offset, serialized_tx = int.from_bytes( serialized_tx[:32], "big"), serialized_tx[32:] if not skip_verification: self.verify(rtx=rtx)
def blindly_generate(self, change_address, input_data, relay_fee_per_kb=0): self.serialized = None if self.coinbase: raise Exception( "generate() can be used only for common transaction, to create block transaction as miner use compose_block_transaction" ) if not len(input_data): raise Exception("Tx should have at least one input") if not len(self._destinations): raise Exception("Tx should have at least one destination") in_value = sum([ioput[1] for ioput in input_data]) out_value = sum([destination[1] for destination in self._destinations]) relay_fee = self.calc_relay_fee(relay_fee_per_kb=relay_fee_per_kb) # +1 for destination is for change address self.fee = relay_fee + self.calc_new_outputs_fee( len(input_data), len(self._destinations) + 1) remainder = in_value - out_value - self.fee if remainder < 0: raise Exception("Not enough money in inputs to cover outputs") # TODO We need logic here to cover too low remainders (less than new output fee) self._destinations.append((change_address, remainder, True)) privkey_sum = 0 out_blinding_key_sum = None need_proofs = [] excesses_key_sum = None for out_index in range(len(self._destinations) - 1): address, value, need_proof = self._destinations[out_index] output = IOput() output.fill(address, value, generator=default_generator_ser) self.outputs.append(output) out_blinding_key_sum = ( out_blinding_key_sum + output.blinding_key ) if out_blinding_key_sum else output.blinding_key if need_proof: need_proofs.append( (output, PrivateKey() )) #excesses will be generated after output generation offset_pk = PrivateKey() self.mixer_offset = int.from_bytes(offset_pk.private_key, "big") # privkey for the last one output isn't arbitrary address, value, need_proof = self._destinations[-1] if need_proof: need_proofs.append( (output, PrivateKey() )) #excesses will be generated after output generation in_blinding_key_sum = None burdens_to_be_covered = [] for i, v, priv_key, blinding_key, ser_apc in input_data: in_blinding_key_sum = ( in_blinding_key_sum + blinding_key) if in_blinding_key_sum else blinding_key in_blinding_key_sum += priv_key self.updated_excesses[i] = excess_from_private_key( priv_key, b"\x01\x00" + ser_apc) self.inputs.append(pseudoinput( i, ser_apc)) #For tx serialization and sorts if len(need_proofs): excesses_key_sum = need_proofs[0][1] for i in need_proofs[1:]: excesses_key_sum += i[1] output = IOput() last_blinding_key = in_blinding_key_sum - out_blinding_key_sum - offset_pk if excesses_key_sum: last_blinding_key += excesses_key_sum output.fill( address, value, blinding_key=last_blinding_key, relay_fee=relay_fee, generator=default_generator_ser ) #TODO relay fee should be distributed uniformly, privacy leak self.outputs.append(output) [output.generate() for output in self.outputs] for ae in need_proofs: script = generate_proof_script(ae[0]) e = excess_from_private_key(ae[1], script) self.additional_excesses.append(e) self.sort_lists()
def deserialize_raw(self, serialized, storage_space=None): if len(serialized) < 1: raise Exception("Not enough bytes for tx skeleton version marker") serialized, ser_version = serialized[1:], serialized[0] rich_format = ser_version & 1 self.version = ser_version >> 1 if not self.version in [0]: raise Exception("Unknown tx_sceleton version") if len(serialized) < 2: raise Exception("Not enough bytes for tx skeleton inputs len") serialized, _len_i = serialized[2:], int.from_bytes( serialized[:2], "big") if len(serialized) < 2: raise Exception("Not enough bytes for tx skeleton outputs len") serialized, _len_o = serialized[2:], int.from_bytes( serialized[:2], "big") if len(serialized) < 2: raise Exception( "Not enough bytes for tx skeleton additional excesses len") serialized, _len_ae = serialized[2:], int.from_bytes( serialized[:2], "big") serialized_index_len = IOput().index_len for i in range(_len_i): if len(serialized) < serialized_index_len: raise Exception( "Not enough bytes for tx skeleton' input index %d len" % i) _input_index, serialized = serialized[: serialized_index_len], serialized[ serialized_index_len:] self.input_indexes.append(_input_index) for i in range(_len_o): if len(serialized) < serialized_index_len: raise Exception( "Not enough bytes for tx skeleton' output index %d len" % i) _output_index, serialized = serialized[: serialized_index_len], serialized[ serialized_index_len:] self.output_indexes.append(_output_index) for i in range(_len_o): if len(serialized) < 4: raise Exception( "Not enough bytes for tx skeleton' output relay fee %d len" % i) ser_relay_fee, serialized = serialized[:4], serialized[4:] self.output_relay_fees.append(int.from_bytes(ser_relay_fee, "big")) for i in range(_len_ae): e = Excess() serialized = e.deserialize_raw(serialized) self.additional_excesses.append(e) for i in range(_len_i): e = Excess() serialized = e.deserialize_raw(serialized) self.updated_excesses[self.input_indexes[i]] = e if len(serialized) < 32: raise Exception("Not enough bytes for mixer offset") self.mixer_offset, serialized = int.from_bytes(serialized[:32], "big"), serialized[32:] if not self.verify(): #TODO consider renmaing verify to validate_excesses or make exception text more general raise Exception("Additional excesses are not signed properly") if rich_format and storage_space: txouts_num_serialized, serialized = serialized[:2], serialized[2:] txouts_num = int.from_bytes(txouts_num_serialized, "big") for _ in range(txouts_num): output = IOput() serialized = output.deserialize_raw(serialized) if not (output.serialized_index in self.output_indexes): raise Exception("Unknown output in rich txskel data") storage_space.txos_storage.mempool[ output.serialized_index] = output return serialized
def ioput_serialize_deserialize(): options = { 'address': [adr1], 'value': [1, 100, int(705e6 * 1e8)], 'relay_fee': [0, 100, int(1e8)], 'generator': [ None ], #b'\x0b\xf8x*\xe9\xdc\xb1\xfd\xe9k\x8eZ\xf9\x8250\xdcrLU`p\xbaD\xf1\xfdh\x93\xd7\x85\xb9\x9e\x07'], #TODO arbitraty value gen 'burden_hash': [None, b"\x44" * 32], 'coinbase': [True, False], 'lock_height': [0, 10, 10000] } all_possible_options = [{}] for opt in options: c = all_possible_options all_possible_options = [] for i in options[opt]: for prev in c: n = prev.copy() if i != None: n[opt] = i all_possible_options.append(n) for var in all_possible_options: _input1 = IOput() _input1.fill(**var) _input1.generate() _input2 = IOput(binary_object=_input1.serialize()) _input2.verify() assert _input1.version == _input2.version assert _input1.block_version == _input2.block_version assert _input1.lock_height == _input2.lock_height assert _input1.authorized_pedersen_commitment.serialize( ) == _input2.authorized_pedersen_commitment.serialize() assert _input1.address.serialize() == _input2.address.serialize() assert _input1.rangeproof.proof == _input2.rangeproof.proof assert _input1.encrypted_message == _input2.encrypted_message assert _input1.generator == _input2.generator assert _input1.relay_fee == _input2.relay_fee assert _input1.authorized_burden == _input2.authorized_burden print("ioput_serialize_deserialize OK")
def deserialize_raw(self, serialized, storage_space=None): if len(serialized) < 1: raise Exception( "Not enough bytes for tx skeleton rich format marker") serialized, rich_format = serialized[1:], bool(serialized[0]) if len(serialized) < 2: raise Exception("Not enough bytes for tx skeleton inputs len") serialized, _len_i = serialized[2:], int.from_bytes( serialized[:2], "big") if len(serialized) < 2: raise Exception("Not enough bytes for tx skeleton outputs len") serialized, _len_o = serialized[2:], int.from_bytes( serialized[:2], "big") if len(serialized) < 2: raise Exception( "Not enough bytes for tx skeleton additional excesses len") serialized, _len_ae = serialized[2:], int.from_bytes( serialized[:2], "big") serialized_index_len = IOput().index_len for i in range(_len_i): if len(serialized) < serialized_index_len: raise Exception( "Not enough bytes for tx skeleton' input index %d len" % i) _input_index, serialized = serialized[: serialized_index_len], serialized[ serialized_index_len:] self.input_indexes.append(_input_index) for i in range(_len_o): if len(serialized) < serialized_index_len: raise Exception( "Not enough bytes for tx skeleton' output index %d len" % i) _output_index, serialized = serialized[: serialized_index_len], serialized[ serialized_index_len:] self.output_indexes.append(_output_index) for i in range(_len_ae): e = Excess() serialized = e.deserialize_raw(serialized) self.additional_excesses.append(e) if not self.verify(): #TODO consider renmaing verify to validate_excesses or make exception text more general raise Exception("Additional excesses are not signed properly") if rich_format and storage_space: txouts_num_serialized, serialized = serialized[:2], serialized[2:] txouts_num = int.from_bytes(txouts_num_serialized, "big") for _ in range(txouts_num): output = IOput() serialized = output.deserialize_raw(serialized) if not (output.serialized_index in self.output_indexes): raise Exception("Unknown output in rich txskel data") storage_space.txos_storage.mempool[ output.serialized_index] = output if not GLOBAL_TEST['skip combined excesses']: raise NotImplemented return serialized
def ioput_proofs_info(): _input1 = IOput() _input1.fill(adr1, 100) _input1.version = 1 #To use RangeProof instead of BulletProofs _input1.generate(exp=2, concealed_bits=3) info = _input1.info() assert info['exp'] == 2 assert info['min_value'] == 0 assert info['max_value'] == 700 _input1 = IOput() _input1.fill(adr1, 3) _input1.version = 1 #To use RangeProof instead of BulletProofs _input1.generate(exp=0, concealed_bits=3, min_value=1) info = _input1.info() assert info['exp'] == 0 assert info['mantissa'] == 3 assert info['min_value'] == 1 assert info['max_value'] == 8 _input1 = IOput() _input1.fill(adr1, 300) _input1.version = 1 #To use RangeProof instead of BulletProofs #coceanled bits cannot be less than bits in value _input1.generate(exp=0, concealed_bits=3) info = _input1.info() assert info['exp'] == 0 assert info['mantissa'] == 9 assert info['min_value'] == 0 assert info['max_value'] == 511 print("ioput_proofs_info OK") _input1 = IOput() _input1.fill(adr1, 300) _input1.generate() info = _input1.info() assert info['exp'] == 0 assert info['mantissa'] == 0 assert info['min_value'] == 0 assert info['max_value'] == 2**64 - 1
def deserialize(self, serialized_tx): if len(serialized_tx) < 2: raise Exception( "Serialized transaction doesn't contain enough bytes for inputs array length" ) inputs_len_buffer, serialized_tx = serialized_tx[:2], serialized_tx[2:] (inputs_len, ) = struct.unpack("> H", inputs_len_buffer) for _input_index in range(inputs_len): if len(serialized_tx) < 2: raise Exception( "Serialized transaction doesn't contain enough bytes for input %s length" % _input_index) input_len_buffer, serialized_tx = serialized_tx[:2], serialized_tx[ 2:] (input_len, ) = struct.unpack("> H", input_len_buffer) if len(serialized_tx) < input_len: raise Exception( "Serialized transaction doesn't contain enough bytes for input %s" % _input_index) input_index_buffer, serialized_tx = serialized_tx[: input_len], serialized_tx[ input_len:] if not GLOBAL_TEST['spend from mempool']: raise NotImplemented else: if not input_index_buffer in self.txos_storage.confirmed: raise Exception("Unknown input index") self.inputs.append( self.txos_storage.confirmed[input_index_buffer]) if len(serialized_tx) < 2: raise Exception( "Serialized transaction doesn't contain enough bytes for outputs array length" ) outputs_len_buffer, serialized_tx = serialized_tx[:2], serialized_tx[ 2:] (outputs_len, ) = struct.unpack("> H", outputs_len_buffer) for _output_index in range(outputs_len): if len(serialized_tx) < 2: raise Exception( "Serialized transaction doesn't contain enough bytes for output %s length" % _output_index) output_len_buffer, serialized_tx = serialized_tx[: 2], serialized_tx[ 2:] (output_len, ) = struct.unpack("> H", output_len_buffer) if len(serialized_tx) < output_len: raise Exception( "Serialized transaction doesn't contain enough bytes for output %s" % _output_index) output_buffer, serialized_tx = serialized_tx[: output_len], serialized_tx[ output_len:] self.outputs.append(IOput(binary_object=output_buffer)) if len(serialized_tx) < 2: raise Exception( "Serialized transaction doesn't contain enough bytes for additional excesses array length" ) aes_len_buffer, serialized_tx = serialized_tx[:2], serialized_tx[2:] (aes_len, ) = struct.unpack("> H", aes_len_buffer) for _ae in range(aes_len): if len(serialized_tx) < 2: raise Exception( "Serialized transaction doesn't contain enough bytes for additional excess %s length" % _ae) ae_len_buffer, serialized_tx = serialized_tx[:2], serialized_tx[2:] (ae_len, ) = struct.unpack("> H", ae_len_buffer) if len(serialized_tx) < ae_len: raise Exception( "Serialized transaction doesn't contain enough bytes for additional excess %s" % _ae) ae_buffer, serialized_tx = serialized_tx[:ae_len], serialized_tx[ ae_len:] e = Excess() e.deserialize_raw(ae_buffer) self.additional_excesses.append(e) self.verify() if not GLOBAL_TEST['skip combined excesses']: raise NotImplemented