def test_token_req_handler_sum_inputs_success(helpers, token_handler_a): address = helpers.wallet.create_address() # Verify no outputs pre_add_outputs = token_handler_a.utxo_cache.get_unspent_outputs(address) assert pre_add_outputs == [] # add and verify new unspent output added token_handler_a.utxo_cache.add_output(Output(address, 5, 150)) post_add_outputs = token_handler_a.utxo_cache.get_unspent_outputs(address) assert post_add_outputs == [Output(address, 5, 150)] # add second unspent output and verify token_handler_a.utxo_cache.add_output(Output(address, 6, 100)) post_second_add_outputs = token_handler_a.utxo_cache.get_unspent_outputs( address) assert post_second_add_outputs == [ Output(address, 5, 150), Output(address, 6, 100) ] # Verify sum_inputs is working properly inputs = [{ "address": address, "seqNo": 5 }, { "address": address, "seqNo": 6 }] outputs = [] request = helpers.request.transfer(inputs, outputs) sum_inputs = token_handler_a._sum_inputs(request) assert sum_inputs == 250
def test_token_req_handler_commit_success( helpers, addresses, token_handler_b, txnPoolNodeSet ): [address1, address2] = addresses inputs = [{"address": address1, "seqNo": 1}] outputs = [{"address": address1, "amount": 30}, {"address": address2, "amount": 30}] request = helpers.request.transfer(inputs, outputs) # apply transaction state_root = txnPoolNodeSet[1].master_replica.stateRootHash(TOKEN_LEDGER_ID) txn_root = txnPoolNodeSet[1].master_replica.txnRootHash(TOKEN_LEDGER_ID) token_handler_b.apply(request, CONS_TIME) new_state_root = txnPoolNodeSet[1].master_replica.stateRootHash(TOKEN_LEDGER_ID) new_txn_root = txnPoolNodeSet[1].master_replica.txnRootHash(TOKEN_LEDGER_ID) # add batch token_handler_b.onBatchCreated(base58.b58decode(new_state_root.encode())) # commit batch assert token_handler_b.utxo_cache.get_unspent_outputs(address1, True) == [Output(address1, 1, 40)] assert token_handler_b.utxo_cache.get_unspent_outputs(address2, True) == [Output(address2, 1, 60)] commit_ret_val = token_handler_b.commit(1, new_state_root, new_txn_root, None) assert token_handler_b.utxo_cache.get_unspent_outputs(address1, True) == [Output(address1, 2, 30)] assert token_handler_b.utxo_cache.get_unspent_outputs(address2, True) == [ Output(address2, 1, 60), Output(address2, 2, 30) ] assert new_state_root != state_root assert new_txn_root != txn_root
def test_sum_inputs_different_addresses(utxo_cache): output1 = Output(VALID_ADDR_1, 10, 10) output2 = Output(VALID_ADDR_1, 11, 100) output3 = Output(VALID_ADDR_2, 11, 50) output4 = Output(VALID_ADDR_1, 21, 80) output5 = Output(VALID_ADDR_2, 39, 300) for b in (True, False): utxo_cache.add_output(output1, b) utxo_cache.add_output(output2, b) utxo_cache.add_output(output3, b) utxo_cache.add_output(output4, b) utxo_cache.add_output(output5, b) assert utxo_cache.sum_inputs([{ "address": VALID_ADDR_1, "seqNo": 10 }, { "address": VALID_ADDR_1, "seqNo": 11 }, { "address": VALID_ADDR_2, "seqNo": 11 }, { "address": VALID_ADDR_1, "seqNo": 21 }, { "address": VALID_ADDR_2, "seqNo": 39 }], b) == 540
def test_token_req_handler_apply_xfer_public_success(helpers, addresses, token_handler_a): [address1, address2] = addresses inputs = [{"address": address2, "seqNo": 1}] outputs = [{ "address": address1, "amount": 30 }, { "address": address2, "amount": 30 }] request = helpers.request.transfer(inputs, outputs) # test xfer now pre_apply_outputs_addr_1 = token_handler_a.utxo_cache.get_unspent_outputs( address1) pre_apply_outputs_addr_2 = token_handler_a.utxo_cache.get_unspent_outputs( address2) assert pre_apply_outputs_addr_1 == [Output(address1, 1, 40)] assert pre_apply_outputs_addr_2 == [Output(address2, 1, 60)] token_handler_a.apply(request, CONS_TIME) post_apply_outputs_addr_1 = token_handler_a.utxo_cache.get_unspent_outputs( address1) post_apply_outputs_addr_2 = token_handler_a.utxo_cache.get_unspent_outputs( address2) assert post_apply_outputs_addr_1 == [ Output(address1, 1, 40), Output(address1, 2, 30) ] assert post_apply_outputs_addr_2 == [Output(address2, 2, 30)]
def test_token_req_handler_apply_xfer_public_success(helpers, addresses, xfer_handler_a): [address1, address2] = addresses inputs = [{"source": utxo_from_addr_and_seq_no(address2, 1)}] outputs = [{ "address": address1, "amount": 30 }, { "address": address2, "amount": 30 }] request = helpers.request.transfer(inputs, outputs) # test xfer now address1 = libsovtoken_address_to_address(address1) address2 = libsovtoken_address_to_address(address2) utxo_cache = xfer_handler_a.database_manager.get_store(UTXO_CACHE_LABEL) pre_apply_outputs_addr_1 = utxo_cache.get_unspent_outputs(address1) pre_apply_outputs_addr_2 = utxo_cache.get_unspent_outputs(address2) assert pre_apply_outputs_addr_1 == [Output(address1, 1, 40)] assert pre_apply_outputs_addr_2 == [Output(address2, 1, 60)] xfer_handler_a.apply_request(request, CONS_TIME, None) post_apply_outputs_addr_1 = utxo_cache.get_unspent_outputs(address1) post_apply_outputs_addr_2 = utxo_cache.get_unspent_outputs(address2) assert post_apply_outputs_addr_1 == [ Output(address1, 1, 40), Output(address1, 2, 30) ] assert post_apply_outputs_addr_2 == [Output(address2, 2, 30)]
def test_get_unpsent_outputs_success_uncommited_and_committed(utxo_cache): output1_addr1 = Output(VALID_ADDR_1, 10, 10) output2_addr1 = Output(VALID_ADDR_1, 11, 10) output1_addr2 = Output(VALID_ADDR_2, 10, 10) utxo_cache.add_output(output1_addr1) utxo_cache.add_output(output2_addr1) utxo_cache.add_output(output1_addr2) unspent_outputs = utxo_cache.get_unspent_outputs(VALID_ADDR_1, False) assert output1_addr1 in unspent_outputs assert output2_addr1 in unspent_outputs assert output1_addr2 not in unspent_outputs
def test_get_unspent_outputs_invalid_address(utxo_cache): uncommitted_output1 = Output(VALID_ADDR_1, 10, 10) committed_output1 = Output(VALID_ADDR_1, 11, 100) committed_output2 = Output(VALID_ADDR_1, 12, 50) utxo_cache.add_output(uncommitted_output1, False) utxo_cache.add_output(committed_output1, True) utxo_cache.add_output(committed_output2, True) unspent_outputs = utxo_cache.get_unspent_outputs(VALID_ADDR_1, True) assert committed_output1 in unspent_outputs assert committed_output2 in unspent_outputs assert uncommitted_output1 not in unspent_outputs
def test_get_all_unspent_outputs(utxo_cache): num_addresses = 5 num_outputs_per_address = 4 address_outputs = gen_outputs(num_addresses) all_outputs = list( itertools.chain(*[[ Output(ao.address, ao.seqNo * (i + 1), ao.amount * (i + 1)) for i in range(num_outputs_per_address) ] for ao in address_outputs])) outputs_by_address = defaultdict(set) for out in all_outputs: outputs_by_address[out.address].add(out) for o in all_outputs: utxo_cache.add_output(o, True) for addr in outputs_by_address: assert set(utxo_cache.get_unspent_outputs( addr, True)) == outputs_by_address[addr] for addr, outs in outputs_by_address.items(): while outs: out = outs.pop() utxo_cache.spend_output(out, True) assert set(utxo_cache.get_unspent_outputs(addr, True)) == outs
def test_token_req_handler_get_all_utxo_success(helpers, addresses, get_utxo_handler): [address1, _] = addresses request = helpers.request.get_utxo(address1) results = get_utxo_handler.get_result(request) state_proof = results.pop(STATE_PROOF) assert state_proof assert results == { ADDRESS: libsovtoken_address_to_address(address1), TXN_TYPE: GET_UTXO, OUTPUTS: [ Output(address=libsovtoken_address_to_address(address1), seq_no=1, value=40) ], IDENTIFIER: base58.b58encode( base58.b58decode_check( libsovtoken_address_to_address(address1))).decode(), TXN_PAYLOAD_METADATA_REQ_ID: request.reqId }
def _update_state_with_single_txn(self, txn, is_committed=False): typ = get_type(txn) if typ == SET_FEES: payload = get_payload_data(txn) fees_from_req = payload.get(FEES) current_fees = self._get_fees() current_fees.update(fees_from_req) for fees_alias, fees_value in fees_from_req.items(): self._set_to_state(build_path_for_set_fees(alias=fees_alias), fees_value) self._set_to_state(build_path_for_set_fees(), current_fees) elif typ == FEE_TXN: for utxo in txn[TXN_PAYLOAD][TXN_PAYLOAD_DATA][INPUTS]: TokenReqHandler.spend_input(state=self.token_state, utxo_cache=self.utxo_cache, address=utxo[ADDRESS], seq_no=utxo[SEQNO], is_committed=is_committed) seq_no = get_seq_no(txn) for output in txn[TXN_PAYLOAD][TXN_PAYLOAD_DATA][OUTPUTS]: TokenReqHandler.add_new_output(state=self.token_state, utxo_cache=self.utxo_cache, output=Output( output[ADDRESS], seq_no, output[AMOUNT]), is_committed=is_committed)
def _update_state_mint_public_txn(self, txn, is_committed=False): payload = get_payload_data(txn) seq_no = get_seq_no(txn) for output in payload[OUTPUTS]: self._add_new_output(Output(output["address"], seq_no, output["amount"]), is_committed=is_committed)
def test_spend_output_success(utxo_cache): output = Output(VALID_ADDR_1, 10, 10) utxo_cache.add_output(output) try: utxo_cache.spend_output(output) except Exception: pytest.fail("This output failed to be spent")
def _update_state_with_single_txn(self, txn, is_committed=False): typ = get_type(txn) if typ == SET_FEES: payload = get_payload_data(txn) existing_fees = self._get_fees(is_committed=is_committed) existing_fees.update(payload[FEES]) val = self.state_serializer.serialize(existing_fees) self.state.set(self.fees_state_key, val) self.fees = existing_fees elif typ == FEE_TXN: for utxo in txn[TXN_PAYLOAD][TXN_PAYLOAD_DATA][INPUTS]: TokenReqHandler.spend_input(state=self.token_state, utxo_cache=self.utxo_cache, address=utxo[ADDRESS], seq_no=utxo[SEQNO], is_committed=is_committed) seq_no = get_seq_no(txn) for output in txn[TXN_PAYLOAD][TXN_PAYLOAD_DATA][OUTPUTS]: TokenReqHandler.add_new_output(state=self.token_state, utxo_cache=self.utxo_cache, output=Output( output[ADDRESS], seq_no, output[AMOUNT]), is_committed=is_committed) else: logger.warning('Unknown type {} found while updating ' 'state with txn {}'.format(typ, txn))
def test_token_req_handler_spend_input_success(helpers, token_handler_a): address = helpers.wallet.create_address() # add input to address token_handler_a.utxo_cache.add_output(Output(address, 7, 200)) # spend input to address token_handler_a._spend_input(address, 7) unspent_outputs = token_handler_a.utxo_cache.get_unspent_outputs(address) assert unspent_outputs == []
def spend_input(state, utxo_cache: UTXOCache, address, seq_no, is_committed=False): state_key = TokenStaticHelper.create_state_key(address, seq_no) state.remove(state_key) utxo_cache.spend_output(Output(address, seq_no, None), is_committed=is_committed)
def get_result(self, request: Request): address = request.operation[ADDRESS] from_seqno = request.operation.get(FROM_SEQNO) encoded_root_hash = state_roots_serializer.serialize( bytes(self.state.committedHeadHash)) proof, rv = self.state.generate_state_proof_for_keys_with_prefix( address, serialize=True, get_value=True) multi_sig = self.database_manager.bls_store.get(encoded_root_hash) if multi_sig: encoded_proof = proof_nodes_serializer.serialize(proof) proof = { MULTI_SIGNATURE: multi_sig.as_dict(), ROOT_HASH: encoded_root_hash, PROOF_NODES: encoded_proof } else: proof = {} # The outputs need to be returned in sorted order since each node's reply should be same. # Since no of outputs can be large, a concious choice to not use `operator.attrgetter` on an # already constructed list was made outputs = SortedItems() for k, v in rv.items(): addr, seq_no = TokenStaticHelper.parse_state_key(k.decode()) amount = rlp_decode(v)[0] if not amount: continue outputs.add(Output(addr, int(seq_no), int(amount))) utxos = outputs.sorted_list next_seqno = None if from_seqno: idx = next((idx for utxo, idx in zip(utxos, range(len(utxos))) if utxo.seqNo >= from_seqno), None) if idx: utxos = utxos[idx:] else: utxos = [] if len(utxos) > UTXO_LIMIT: next_seqno = utxos[UTXO_LIMIT].seqNo utxos = utxos[:UTXO_LIMIT] result = { f.IDENTIFIER.nm: request.identifier, f.REQ_ID.nm: request.reqId, OUTPUTS: utxos } result.update(request.operation) if next_seqno: result[NEXT_SEQNO] = next_seqno if proof: res_sub = deepcopy(result) res_sub[STATE_PROOF] = proof if len(json.dumps(res_sub)) <= self._msg_limit: result = res_sub return result
def test_spend_output_double_spend_fail(utxo_cache): output = Output(VALID_ADDR_1, 10, 10) utxo_cache.add_output(output) # First spend utxo_cache.spend_output(output) with pytest.raises(UTXOError): # Second spend fails as expected utxo_cache.spend_output(output)
def test_add_outputs(utxo_cache): output = Output(VALID_ADDR_1, 10, 10) try: utxo_cache.add_output(output) assert utxo_cache.get_unspent_outputs(output.address) == [ output, ] except Exception: pytest.fail("The output hasn't been added")
def test_as_output_list(): with pytest.raises(UTXOError): UTXOAmounts(VALID_ADDR_1, '2:20:3:30:30:40:40:30:20').as_output_list() with pytest.raises(UTXOError): UTXOAmounts(VALID_ADDR_1, '2:20:3:30:30:40:40:30:20:').as_output_list() with pytest.raises(UTXOError): UTXOAmounts(VALID_ADDR_1, ':20:3:30:30:40:40:30:20:1').as_output_list() with pytest.raises(UTXOError): UTXOAmounts(VALID_ADDR_1, '20:3:30:30:40:40:30:20:1').as_output_list() with pytest.raises(UTXOError): UTXOAmounts(VALID_ADDR_1, '2:20:3:30::40:40:30:20:1').as_output_list() val = '2:20:3:30:30:40:40:30:20:1' assert UTXOAmounts(VALID_ADDR_1, val).as_output_list() == [ Output(VALID_ADDR_1, 2, 20), Output(VALID_ADDR_1, 3, 30), Output(VALID_ADDR_1, 30, 40), Output(VALID_ADDR_1, 40, 30), Output(VALID_ADDR_1, 20, 1), ]
def _update_state_xfer_public(self, txn, is_committed=False): payload = get_payload_data(txn) for inp in payload[INPUTS]: self._spend_input(inp["address"], inp["seqNo"], is_committed=is_committed) for output in payload[OUTPUTS]: seq_no = get_seq_no(txn) self._add_new_output(Output(output["address"], seq_no, output["amount"]), is_committed=is_committed)
def test_spend_unspent_output(utxo_cache): num_outputs = 5 outputs = gen_outputs(num_outputs) for i in range(num_outputs): utxo_cache.add_output(outputs[i], True) new_out = Output(outputs[i].address, outputs[i].seqNo, None) assert utxo_cache.get_unspent_outputs(outputs[i].address, True) utxo_cache.spend_output(new_out, True) assert utxo_cache.get_unspent_outputs(outputs[i].address, True) == [] with pytest.raises(UTXOError): utxo_cache.spend_output(new_out, True)
def test_sum_inputs_same_address(utxo_cache): output1 = Output(VALID_ADDR_1, 10, 10) output2 = Output(VALID_ADDR_1, 11, 100) output3 = Output(VALID_ADDR_1, 12, 50) for b in (True, False): utxo_cache.add_output(output1, b) utxo_cache.add_output(output2, b) utxo_cache.add_output(output3, b) assert utxo_cache.sum_inputs([{ "address": VALID_ADDR_1, "seqNo": 10 }, { "address": VALID_ADDR_1, "seqNo": 11 }, { "address": VALID_ADDR_1, "seqNo": 12 }], b) == 160
def test_token_req_handler_apply_xfer_public_success( helpers, addresses, token_handler_a ): [address1, address2] = addresses inputs = [{"source": utxo_from_addr_and_seq_no(address2, 1)}] outputs = [{"address": address1, "amount": 30}, {"address": address2, "amount": 30}] request = helpers.request.transfer(inputs, outputs) # test xfer now address1 = address1.replace("pay:sov:", "") address2 = address2.replace("pay:sov:", "") pre_apply_outputs_addr_1 = token_handler_a.utxo_cache.get_unspent_outputs(address1) pre_apply_outputs_addr_2 = token_handler_a.utxo_cache.get_unspent_outputs(address2) assert pre_apply_outputs_addr_1 == [Output(address1, 1, 40)] assert pre_apply_outputs_addr_2 == [Output(address2, 1, 60)] token_handler_a.apply(request, CONS_TIME) post_apply_outputs_addr_1 = token_handler_a.utxo_cache.get_unspent_outputs(address1) post_apply_outputs_addr_2 = token_handler_a.utxo_cache.get_unspent_outputs(address2) assert post_apply_outputs_addr_1 == [Output(address1, 1, 40), Output(address1, 2, 30)] assert post_apply_outputs_addr_2 == [Output(address2, 2, 30)]
def spend_input(state, utxo_cache: UTXOCache, address, seq_no, is_committed=False, remove_spent=True): state_key = TokenStaticHelper.create_state_key(address, seq_no) if remove_spent: state.remove(state_key) else: state.set(state_key, b'') utxo_cache.spend_output(Output(address, seq_no, None), is_committed=is_committed)
def update_state(self, txn, prev_result, request, is_committed=False): try: payload = get_payload_data(txn) seq_no = get_seq_no(txn) for output in payload[OUTPUTS]: TokenStaticHelper.add_new_output( self.state, self.database_manager.get_store(UTXO_CACHE_LABEL), Output(output["address"], seq_no, output["amount"]), is_committed=is_committed) except UTXOError as ex: error = 'Exception {} while updating state'.format(ex) raise OperationError(error)
def test_token_req_handler_get_query_response_success(helpers, addresses, token_handler_a): address1 = addresses[0] request = helpers.request.get_utxo(address1) results = token_handler_a.get_query_response(request) state_proof = results.pop(STATE_PROOF) assert state_proof assert results == { ADDRESS: address1, TXN_TYPE: GET_UTXO, OUTPUTS: [Output(address=address1, seq_no=1, value=40)], IDENTIFIER: VALID_IDENTIFIER, TXN_PAYLOAD_METADATA_REQ_ID: request.reqId }
def update_token_state(self, txn, request, is_committed=False): for utxo in txn[TXN_PAYLOAD][TXN_PAYLOAD_DATA][INPUTS]: TokenStaticHelper.spend_input(state=self.token_state, utxo_cache=self.utxo_cache, address=utxo[ADDRESS], seq_no=utxo[SEQNO], is_committed=is_committed) seq_no = get_seq_no(txn) for output in txn[TXN_PAYLOAD][TXN_PAYLOAD_DATA][OUTPUTS]: TokenStaticHelper.add_new_output(state=self.token_state, utxo_cache=self.utxo_cache, output=Output( output[ADDRESS], seq_no, output[AMOUNT]), is_committed=is_committed)
def test_token_req_handler_onBatchCreated_success(addresses, token_handler_a, txnPoolNodeSet): address = addresses[0] output = Output(address, 10, 100) # add output to UTXO Cache token_handler_a.utxo_cache.add_output(output) state_root = txnPoolNodeSet[1].master_replica.stateRootHash( TOKEN_LEDGER_ID) # run onBatchCreated token_handler_a.onBatchCreated(state_root, CONS_TIME) # Verify onBatchCreated worked properly key = token_handler_a.utxo_cache._create_key(output) assert token_handler_a.utxo_cache.un_committed[0][0] == state_root assert key in token_handler_a.utxo_cache.un_committed[0][1] assert '{}:{}'.format(str(output.seqNo), str( output.amount)) in token_handler_a.utxo_cache.un_committed[0][1][key]
def as_output_list(self) -> List[Output]: if len(self.data) % 2 != 0: raise UTXOError('Length of seqNo-amount pairs must be even: items={}'.format(len(self.data))) rtn = [] for i in range(0, len(self.data), 2): try: seq_no = int(self.data[i]) amount = int(self.data[i + 1]) except ValueError: raise UTXOError("Invalid data -- not integers -- seq_no:{} amount:{}".format(self.data[i], self.data[i + 1])) rtn.append(Output(self.address, seq_no, amount)) return rtn
def test_token_req_handler_get_query_response_success( helpers, addresses, token_handler_a ): address1 = addresses[0] request = helpers.request.get_utxo(address1) results = token_handler_a.get_query_response(request) state_proof = results.pop(STATE_PROOF) address1 = address1.replace("pay:sov:", "") assert state_proof assert results == { ADDRESS: address1, TXN_TYPE: GET_UTXO, OUTPUTS: [Output(address=address1, seq_no=1, value=40)], IDENTIFIER: base58.b58encode(base58.b58decode_check(address1)).decode(), TXN_PAYLOAD_METADATA_REQ_ID: request.reqId }